Help debugging indicator - it's infinite looping

 

I'm trying to create a recency-weighted performance indicator that averages the annualized performance of various lookback periods. For each lookback period, it uses a 5-day moving average to avoid the problem of idiosyncratic results depending on the daily results of the specific lookback day.

I'm using my best understanding (such as it is) of keeping track of the previously calculated values to improve performance. Somehow or another, I've ended up with this in an infinite loop and I can't figure out why. What am I missing?

//+------------------------------------------------------------------+
//|                                          RecencyWeightedPerf.mq5 |
//|                                      Copyright 2024, Scott Allen |
//+------------------------------------------------------------------+
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_color1 Blue

//--- input parameters
input bool     Use_5_Days = true;   // Use 5 days lookback
input bool     Use_21_Days = true;  // Use 21 days lookback
input bool     Use_63_Days = true;  // Use 63 days lookback
input bool     Use_126_Days = true; // Use 126 days lookback
input bool     Use_252_Days = true; // Use 252 days lookback
input bool     Use_756_Days = true; // Use 756 days lookback

//--- indicator buffer
double RecencyWeightedPerfBuffer[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- indicator buffer mapping
   SetIndexBuffer(0, RecencyWeightedPerfBuffer);

   //--- name for DataWindow and indicator subwindow label
   IndicatorSetString(INDICATOR_SHORTNAME, "Recency Weighted Performance");
   PlotIndexSetString(0, PLOT_LABEL, "RWP");

   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   //--- check if enough bars
   int lookbackPeriods[] = {5, 21, 63, 126, 252, 756};
   bool usePeriods[] = {Use_5_Days, Use_21_Days, Use_63_Days, Use_126_Days, Use_252_Days, Use_756_Days};
   int periodsCount = ArraySize(lookbackPeriods);

   if (rates_total < 756 + 5) // At least the largest lookback period plus MA period
   {
      Print("Not enough bars for calculation.");
      return 0;
   }

   //--- calculate daily returns
   static double returns[];
   ArraySetAsSeries(returns, true);
   ArrayResize(returns, rates_total);

   for (int i = 1; i < rates_total; i++)
      returns[i] = (close[i] - close[i - 1]) / close[i - 1];

   //--- calculate Recency Weighted Performance
   int start = prev_calculated > 0 ? prev_calculated - 1 : 756 + 5; // Start from the last calculated bar or the minimum required bars

   Print("Start index: ", start);

   for (int i = start; i < rates_total; i++)
     {
      double weightedSum = 0.0;
      int activePeriods = 0;

      for (int j = 0; j < periodsCount; j++)
        {
         if (usePeriods[j])
           {
            double sum = 0.0;
            bool valid = true;
            for (int k = 0; k < 5; k++)
              {
               int index = i - lookbackPeriods[j] - k;
               if (index < 0)
                 {
                  valid = false;
                  break;
                 }
               sum += returns[index];
              }

            if (valid)
              {
               double perfMA = sum / 5.0;
               double annualizedPerf = perfMA * 252 / lookbackPeriods[j];
               weightedSum += annualizedPerf;
               activePeriods++;
              }
           }
        }

      if (activePeriods > 0)
         RecencyWeightedPerfBuffer[i] = weightedSum / activePeriods;
      else
         RecencyWeightedPerfBuffer[i] = 0.0;
     }

   Print("End index: ", rates_total);

   //--- debug prints
   Print("rates_total: ", rates_total);
   Print("prev_calculated: ", prev_calculated);

   for (int i = start; i < rates_total; i++)
     {
      Print("RecencyWeightedPerfBuffer[", i, "]: ", RecencyWeightedPerfBuffer[i]);
     }

   return rates_total;
  }
//+------------------------------------------------------------------+
 

OnCalculate is designed to recalculate every tick. Add a flag to ensure that the calculation runs only once. Initially set the flag to false, and finally set it to true within the condition


//+------------------------------------------------------------------+
//|                                          RecencyWeightedPerf.mq5 |
//|                                      Copyright 2024, Scott Allen |
//+------------------------------------------------------------------+
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_color1 Blue

//--- input parameters
input bool     Use_5_Days = true;   // Use 5 days lookback
input bool     Use_21_Days = true;  // Use 21 days lookback
input bool     Use_63_Days = true;  // Use 63 days lookback
input bool     Use_126_Days = true; // Use 126 days lookback
input bool     Use_252_Days = true; // Use 252 days lookback
input bool     Use_756_Days = true; // Use 756 days lookback

//--- indicator buffer
double RecencyWeightedPerfBuffer[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- indicator buffer mapping
   SetIndexBuffer(0, RecencyWeightedPerfBuffer);

   //--- name for DataWindow and indicator subwindow label
   IndicatorSetString(INDICATOR_SHORTNAME, "Recency Weighted Performance");
   PlotIndexSetString(0, PLOT_LABEL, "RWP");

   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   //--- check if enough bars
   int lookbackPeriods[] = {5, 21, 63, 126, 252, 756};
   bool usePeriods[] = {Use_5_Days, Use_21_Days, Use_63_Days, Use_126_Days, Use_252_Days, Use_756_Days};
   int periodsCount = ArraySize(lookbackPeriods);

   if (rates_total < 756 + 5) // At least the largest lookback period plus MA period
   {
      Print("Not enough bars for calculation.");
      return 0;
   }

   //--- calculate daily returns
   static double returns[];
   ArraySetAsSeries(returns, true);
   ArrayResize(returns, rates_total);

   for (int i = 1; i < rates_total; i++)
      returns[i] = (close[i] - close[i - 1]) / close[i - 1];

   //--- calculate Recency Weighted Performance
   int start = prev_calculated > 0 ? prev_calculated - 1: 756 + 5; // Start from the last calculated bar or the minimum required bars
   
  static bool completedProcessing = false;
   
  // Print("Start index: ", start);
  if(!completedProcessing){
   
   for (int i = start; i < rates_total; i++)
     {
      double weightedSum = 0.0;
      int activePeriods = 0;

      for (int j = 0; j < periodsCount; j++)
        {
         if (usePeriods[j])
           {
            double sum = 0.0;
            bool valid = true;
            for (int k = 0; k < 5; k++)
              {
               int index = i - lookbackPeriods[j] - k;
               if (index < 0)
                 {
                  valid = false;
                  break;
                 }
               sum += returns[index];
              }

            if (valid)
              {
               double perfMA = sum / 5.0;
               double annualizedPerf = perfMA * 252 / lookbackPeriods[j];
               weightedSum += annualizedPerf;
               activePeriods++;
              }
           }
        }

      if (activePeriods > 0)
         RecencyWeightedPerfBuffer[i] = weightedSum / activePeriods;
      else
         RecencyWeightedPerfBuffer[i] = 0.0;
     }
   }

  // Print("End index: ", rates_total);
   

   
   if(!completedProcessing){
   //--- debug prints
   Print("rates_total: ", rates_total);
   Print("prev_calculated: ", prev_calculated);
  

   //for (int i = start; i < rates_total; i++)
   //  {
   //   Print("RecencyWeightedPerfBuffer[", i, "]: ", RecencyWeightedPerfBuffer[i]);
   //  } 
     
     Print("RWP: ", RecencyWeightedPerfBuffer[rates_total-1]);
     
     completedProcessing = true; 
   }
   

   return rates_total;
  }
//+------------------------------------------------------------------+

 
Conor Mcnamara #:

OnCalculate is designed to recalculate every tick. Add a flag to ensure that the calculation runs only once. Initially set the flag to false, and finally set it to true within the condition


Thanks! That took care of the seemingly endless loop.

It's still not displaying the indicator though. Apparently I'm missing something else.

 
Scott Allen #:

Thanks! That took care of the seemingly endless loop.

It's still not displaying the indicator though. Apparently I'm missing something else.

That depends on what you want the indicator to display in the separate window. What should it display?

If it should just display the reading on the chart, you can use the Comment function (https://www.mql5.com/en/docs/common/comment) along with 

#property indicator_chart_window


Add the required properties to the top of the script if it should display a line for example:

#property indicator_separate_window

#property indicator_buffers 1
#property indicator_plots 1

#property indicator_color1 clrBlue
#property indicator_type1   DRAW_LINE


then you can comment out the completedProcessing statement, because it's only prints which will keep looping in the journal

Documentation on MQL5: Common Functions / Comment
Documentation on MQL5: Common Functions / Comment
  • www.mql5.com
This function outputs a comment defined by a user in the top left corner of a chart. Parameters ... [in]   Any values, separated by commas. To...