Different display of indicator in tester and chart

 

Hi, I have written a simple range detector because others I found did not display in tester and had too much visuals like boxes that I don't need. Its losely based on the LuxAlgo range detector in TradingView. Anyway when I load the code into a chart It looks pretty similar to the TradingView version but if running in the tester It creates a lot of extry tiny ranges. I need to make sure it works in the tester because I want to integrate it into an EA using iCustom, so I want to be able to rely on the test results. Below are first 2 screenshots and then the code.

This is the expected behavior when loading into the chart

Indicator in Chart

This is what I see when running in tester. I made sure that the candles look the same, so no use of Tick Data Manager.

Range Tester

Any idea why this happens? Below is my code:

//+------------------------------------------------------------------+
//|                                               range_detector.mq4 |
//|                                                  ralf@mrghome.de |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "ralf@mrghome.de"
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
#property indicator_chart_window

//+------------------------------------------------------------------+
//| Range Detector - MQL4 Version                                    |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_buffers 3
#property indicator_plots 2
#property indicator_color1 clrBlue  // Range Top color
#property indicator_color2 clrRed   // Range Bottom color

// Inputs
input int length = 20;    // Minimum Range Length
input double mult = 1.0;  // Range Width
input int atrLen = 500;   // ATR Length

// Buffers for plotting
double RangeTop[], RangeBottom[], lastCount[];
bool rangeIsActive = false;
double lastMa, lastAtr;
datetime NewCandleTime = TimeCurrent();

bool IsNewCandle() {
  if (NewCandleTime == iTime(Symbol(), 0, 0))
    return false;
  else {
    NewCandleTime = iTime(Symbol(), 0, 0);
    return true;
  }
}

// Indicator initialization
int OnInit() {
  SetIndexBuffer(0, RangeTop);
  SetIndexStyle(0, DRAW_LINE, STYLE_SOLID, 2);
  SetIndexBuffer(1, RangeBottom);
  SetIndexStyle(1, DRAW_LINE, STYLE_SOLID, 2);
  IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
  SetIndexBuffer(2, lastCount);
  SetIndexStyle(2, DRAW_NONE, EMPTY, EMPTY, clrNONE);

  // Initialize buffers with EMPTY_VALUE to avoid unwanted plotting
  ArrayInitialize(RangeTop, EMPTY_VALUE);
  ArrayInitialize(RangeBottom, EMPTY_VALUE);
  ArrayInitialize(lastCount, EMPTY_VALUE);

  lastMa = 0;
  lastAtr = 0;

  return (INIT_SUCCEEDED);
}

// Main calculation 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[]) {
  double atr, ma;
  int count;
  bool inRange = false;  // Flag to track if we're currently within a range

  int limit;
  int CountedBars = IndicatorCounted();

  limit = Bars - 1 - CountedBars;
  if (limit == rates_total - 1) limit = rates_total - 2;

  // Loop through each bar from the last calculated bar
  for (int i = limit; i >= 1; i--) {
    atr = iATR(NULL, 0, atrLen, 0) *
          mult;  // Using the latest ATR value for simplicity

    ma = NormalizeDouble(iMA(NULL, 0, length, 0, MODE_SMA, PRICE_CLOSE, i),
                         Digits);
    // Calculate count of out-of-range close values within the range
    if (!rangeIsActive) {
      count = 0;
      for (int j = 0; j < length && (i - j) >= 0; j++) {
        if (MathAbs(close[i - j] - ma) > atr) {
          count++;
        }
      }
      lastCount[i] = count;
      if (count == 0 && lastCount[i + 1] == 0 && lastCount[i + 2] == 0) {
        RangeTop[i] = ma + atr;
        RangeBottom[i] = ma - atr;
        lastAtr = atr;
        lastMa = ma;
        rangeIsActive = true;
        Print("RangeStarted ", RangeTop[i], " ", RangeBottom[i]);
      }
    } else {
      count = 0;
      for (int j = 0; j < length && (i - j) >= 0; j++) {
        if (MathAbs(close[i - j] - lastMa) > lastAtr) {
          count++;
        }
      }
      if (count == 0) {
        RangeBottom[i] = RangeBottom[i + 1];
        RangeTop[i] = RangeTop[i + 1];
        Print("RangeContinued: ", RangeBottom[i], " ", RangeTop[i]);
      } else {
        Print("Range stopped");
        rangeIsActive = false;
      }
    }
  }
  return (rates_total);
}


Thanks

 
Your topic has been moved to the section: MQL4 and MetaTrader 4
Please consider which section is most appropriate — https://www.mql5.com/en/forum/172166/page6#comment_49114893
 

OK, found one small issue myself. The ATR calculation should be ``atr = iATR(NULL, 0, atrLen, i) * mult;`` using i instead of 0. But the overall issue remains.

Thanks

 
rgraefe #: OK, found one small issue myself. The ATR calculation should be ``atr = iATR(NULL, 0, atrLen, i) * mult;`` using i instead of 0. But the overall issue remains.
  1. Your loop (i) is as-series, zero is current bar.

    In MT4, buffers and MT4 predefined arrays are all ordered AsSeries. There is a difference between the arrays passed to OnCalculate (e.g. low[]) and the MT4 predefined variables (e.g. Low[].) The passed arrays have no default direction, just like MT5.

    To determine the indexing direction of time[], open[], high[], low[], close[], tick_volume[], volume[] and spread[] arrays, call ArrayGetAsSeries(). In order not to depend on default values, you should unconditionally call the ArraySetAsSeries() function for those arrays, which are expected to work with.
              Event Handling Functions - Functions - Language Basics - MQL4 Reference

         for (int j = 0; j < length && (i - j) >= 0; j++) {
            if (MathAbs(close[i - j] - ma) > atr) {

    If you intend to access the array as-series, then you are accessing future values (relative to i).

  2.   int CountedBars = IndicatorCounted();
    
      limit = Bars - 1 - CountedBars;
      if (limit == rates_total - 1) limit = rates_total - 2;
    

    You should stop using the old event handlers (init, start, deinit) and IndicatorCounted() and start using new event handlers (OnInit, OnTick/OnCalculate, OnDeinit).
              Event Handling Functions - MQL4 Reference
              How to do your lookbacks correctly - MQL4 programming forum #9-14 & #19 (2016)

        atr = iATR(NULL, 0, atrLen, 0) *
              mult;  // Using the latest ATR value for simplicity
    
        ma = NormalizeDouble(iMA(NULL, 0, length, 0, MODE_SMA, PRICE_CLOSE, i),
                             Digits);

    Your lookback is the maximum of atrLen and length.

 

Great, thanks for the quick help. Here is the working script:

//+------------------------------------------------------------------+
//|                                               range_detector.mq4 |
//|                                                  ralf@mrghome.de |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "ralf@mrghome.de"
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
#property indicator_chart_window

//+------------------------------------------------------------------+
//| Range Detector - MQL4 Version                                    |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_buffers 3
#property indicator_plots 2
#property indicator_color1 clrBlue  // Range Top color
#property indicator_color2 clrRed   // Range Bottom color

// Inputs
input int length = 20;    // Minimum Range Length
input double mult = 1.0;  // Range Width
input int atrLen = 500;   // ATR Length

// Buffers for plotting
double RangeTop[], RangeBottom[], lastCount[];
bool rangeIsActive = false;
double lastMa, lastAtr;
datetime NewCandleTime = TimeCurrent();
int lookback = MathMax(length,atrLen);

bool IsNewCandle() {
  if (NewCandleTime == iTime(Symbol(), 0, 0))
    return false;
  else {
    NewCandleTime = iTime(Symbol(), 0, 0);
    return true;
  }
}

// Indicator initialization
int OnInit() {
  SetIndexBuffer(0, RangeTop);
  SetIndexStyle(0, DRAW_LINE, STYLE_SOLID, 2);
  SetIndexBuffer(1, RangeBottom);
  SetIndexStyle(1, DRAW_LINE, STYLE_SOLID, 2);
  IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
  SetIndexBuffer(2, lastCount);
  SetIndexStyle(2, DRAW_NONE, EMPTY, EMPTY, clrNONE);

  // Initialize buffers with EMPTY_VALUE to avoid unwanted plotting
  ArrayInitialize(RangeTop, EMPTY_VALUE);
  ArrayInitialize(RangeBottom, EMPTY_VALUE);
  ArrayInitialize(lastCount, EMPTY_VALUE);

  lastMa = 0;
  lastAtr = 0;

  return (INIT_SUCCEEDED);
}

// Main calculation 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[]) {
  double atr, ma;
  int count;
  bool inRange = false;  // Flag to track if we're currently within a range
  
  ArraySetAsSeries(close, true);
  
  int limit = Bars-1-MathMax(lookback, prev_calculated);
  if (IsNewCandle())
  {
     // Loop through each bar from the last calculated bar
     
     for(int i = limit; i >= 0; --i){
       atr = iATR(NULL, 0, atrLen, i+1) * mult;  // Using the latest ATR value for simplicity
   
       ma = NormalizeDouble(iMA(NULL, 0, length, 0, MODE_SMA, PRICE_CLOSE, i),Digits);
       // Calculate count of out-of-range close values within the range
       if (!rangeIsActive) {
         count = 0;
         for (int j = 0; j < length; j++) {
           if (MathAbs(close[i + j] - ma) > atr) {
             count++;
           }
         }
         lastCount[i] = count;
         if (count == 0 && lastCount[i + 1] == 0 && lastCount[i + 2] == 0) {
           RangeTop[i] = ma + atr;
           RangeBottom[i] = ma - atr;
   
           lastAtr = atr;
           lastMa = ma;
           rangeIsActive = true;
           Print("RangeStarted + atr + ma ", iTime(_Symbol,0,i), " " ,RangeTop[i], " ", RangeBottom[i], " ", atr, " ", ma);
         }
       } else {
         count = 0;
         for (int j = 0; j < length && (i - j) >= 0; j++) {
           if (MathAbs(close[i + j] - lastMa) > lastAtr) {
             count++;
           }
         }
         if (count == 0) {
           RangeBottom[i] = RangeBottom[i + 1];
           RangeTop[i] = RangeTop[i + 1];
   
           Print("RangeContinued: ", RangeBottom[i], " ", RangeTop[i]);
         } else {
           Print("Range stopped");
           rangeIsActive = false;
         }
       }
     }
  }
  return (rates_total);
}