Difference between indicator price values and self calculated price values using the same formula

 

Hello Community,

I observed a difference in the price values (OHLC) coming from the native Heiken Ashi indicator and the Heiken Ashi values I calculated myself using the exact same formula as used in the native MT5 Heiken Ashi indicator. 

I found two forum postings describing the same issue:

https://www.mql5.com/en/forum/275547/page2

https://www.mql5.com/en/forum/250775

In the second post, Alain Verleyen posted a link of an mql5 article addressing the origin of this difference:

https://www.mql5.com/en/articles/239

Unfortunately, I do not fully understand the explanations in this article. What I have understood so far is that the values of the Heiken Ashi indicator are based or calculated on the closed prices whereas the way I retrieve and calculate them is based on the open prices. However, this is still not clear to me.

Therefore, I was wondering whether someone would be kind enough and able to further explain this to me and add a fictive calculation example showing where the differences come from?

I understood that this is not a specific Heiken Ashi issue but a general problem using any indicators and self calculated values retreived within the EA.


I created a small and executable EA (.mq5) which highlights this issue. I am operating in M1 but this issue occures in any timeframe:

static datetime   PreviousBar;
datetime          CurrentBar;

int               hHeiken_Ashi;

MqlRates          rates[];

double            inOpen[];
double            inHigh[];
double            inLow[];
double            inClose[];

struct ha
{  datetime haTime;
   double   haOpen;
   double   haHigh;
   double   haLow;
   double   haClose;
}
haValues[];

struct in
{  datetime inTime;
   double   inOpen;
   double   inHigh;
   double   inLow;
   double   inClose;
}
inValues[];
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
{  hHeiken_Ashi=iCustom(NULL,PERIOD_CURRENT,"Examples\\Heiken_Ashi");

   ArrayResize(rates,2);
   ArrayResize(inOpen,1);
   ArrayResize(inHigh,1);
   ArrayResize(inLow,1);
   ArrayResize(inClose,1);
   ArrayResize(haValues,1);
   ArrayResize(inValues,1);

   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnTick()
{  if(IsNewBar())
   {  
      // Rates values
      CopyRates(NULL,PERIOD_CURRENT,1,2,rates);

      // Heiken Ashi calculated values based on the exact same formula used in the native indicator
      haValues[0].haOpen=NormalizeDouble((rates[0].open+rates[0].close)/2,2);
      haValues[0].haClose=NormalizeDouble((rates[1].open+rates[1].high+rates[1].low+rates[1].close)/4,2);
      haValues[0].haHigh=NormalizeDouble(MathMax(rates[1].high,MathMax(rates[1].open,rates[1].close)),2);
      haValues[0].haLow=NormalizeDouble(MathMin(rates[1].low,MathMin(rates[1].open,rates[1].close)),2);
      haValues[0].haTime=rates[1].time;

      // Heiken Ashi indicator values
      CopyBuffer(hHeiken_Ashi,0,1,1,inOpen);
      CopyBuffer(hHeiken_Ashi,1,1,1,inHigh);
      CopyBuffer(hHeiken_Ashi,2,1,1,inLow);
      CopyBuffer(hHeiken_Ashi,3,1,1,inClose);

      inValues[0].inOpen=inOpen[0];
      inValues[0].inHigh=inHigh[0];
      inValues[0].inLow=inLow[0];
      inValues[0].inClose=inClose[0];
      inValues[0].inTime=rates[1].time;
      
      if(haValues[0].haOpen!=inValues[0].inOpen || haValues[0].haHigh!=inValues[0].inHigh || haValues[0].haLow!=inValues[0].inLow || haValues[0].haClose!=inValues[0].inClose)
      {  ArrayPrint(haValues);
         ArrayPrint(inValues);
         DebugBreak();
      }
      
   }
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool IsNewBar()
{  CurrentBar=(datetime)SeriesInfoInteger(_Symbol,_Period,SERIES_LASTBAR_DATE);

   if(PreviousBar!=CurrentBar)
   {  PreviousBar=CurrentBar;
      return true;
   }
   return false;
}
//+------------------------------------------------------------------+


Here is an example showing the difference in the Open and Low Prices between the two ways of retrieving the values. These differences can, however, occure in any OHLC values

Getting different values of Heiken Ashi candles in Data Window & in my code
Getting different values of Heiken Ashi candles in Data Window & in my code
  • 2018.08.25
  • www.mql5.com
Hello, Why I am getting different values of Heiken Ashi candles in Data Window & in my code values I am getting from my code is mentioned in hi...
 
algotrader01: I observed a difference in the price values (OHLC) coming from the native Heiken Ashi indicator and the Heiken Ashi values I calculated myself using the exact same formula as used in the native MT5 Heiken Ashi indicator.
      haValues[0].haOpen=NormalizeDouble((rates[0].open+rates[0].close)/2,2);
Not the same formula. Compare HA code:
   for(i=pos; i<rates_total; i++){
      haOpen=(ExtOpenBuffer[i-1]+ExtCloseBuffer[i-1])/2;

The open is recursively computed from the previous HA candle.

And there are no NormalizeDouble calls. Move them to your test

 
William Roeder:
Not the same formula. Compare HA code:

The open is recursively computed from the previous HA candle.

And there are no NormalizeDouble calls. Move them to your test

Hi William,


add NormalizeDouble:

NormalizeDouble as used in my code normalizes the double at the second position behind the decimal point but the differences in prices between the two approaches can be seen at the first position before the decimal point. Subsequently, this is not the cause of difference. See new screenshot and code adjustment.


add Recursion:

I also do not see the differences coming from the recursion. The loop in the native Heiken Ashi indicator starts from the past 1970:... and runs to the present bar. For each iteration it gets the OHLC prices of the current bars as is and calculates the corresponding values. I also added a for loop in the code starting from the past to the present using recursion to calcualte the values. The most present values are exactly the same as calcualted with the two other approches using NormalizeDouble and without the normalization. Please see new screenshot and code as well.


Screen2


//+------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2020, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
static datetime   PreviousBar;
datetime          CurrentBar;

int               hHeiken_Ashi;

MqlRates          rates[], rates2[];

double            inOpen[];
double            inHigh[];
double            inLow[];
double            inClose[];

struct ha
{  datetime haTime;
   double   haOpen;
   double   haHigh;
   double   haLow;
   double   haClose;
}
haValues[], haValues2[], haValues3[];

struct in
{  datetime inTime;
   double   inOpen;
   double   inHigh;
   double   inLow;
   double   inClose;
}
inValues[];
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
{  hHeiken_Ashi=iCustom(NULL,PERIOD_CURRENT,"Examples\\Heiken_Ashi");

   ArrayResize(rates,2);
   ArrayResize(rates2,10000);
   ArrayResize(inOpen,1);
   ArrayResize(inHigh,1);
   ArrayResize(inLow,1);
   ArrayResize(inClose,1);
   ArrayResize(haValues,1);
   ArrayResize(haValues2,1);
   ArrayResize(haValues3,1);
   ArrayResize(inValues,1);

   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnTick()
{  if(IsNewBar())
   {  // Rates values
      CopyRates(NULL,PERIOD_CURRENT,1,2,rates);
      CopyRates(NULL,PERIOD_CURRENT,1,10000,rates2);

      // Heiken Ashi calculated values based on the exact same formula used in the native indicator
      haValues[0].haOpen=NormalizeDouble((rates[0].open+rates[0].close)/2,2);
      haValues[0].haClose=NormalizeDouble((rates[1].open+rates[1].high+rates[1].low+rates[1].close)/4,2);
      haValues[0].haHigh=NormalizeDouble(MathMax(rates[1].high,MathMax(rates[1].open,rates[1].close)),2);
      haValues[0].haLow=NormalizeDouble(MathMin(rates[1].low,MathMin(rates[1].open,rates[1].close)),2);
      haValues[0].haTime=rates[1].time;

      // Heiken Ashi calculated values based on the exact same formula used in the native indicator
      haValues2[0].haOpen=(rates[0].open+rates[0].close)/2;
      haValues2[0].haClose=(rates[1].open+rates[1].high+rates[1].low+rates[1].close)/4;
      haValues2[0].haHigh=MathMax(rates[1].high,MathMax(rates[1].open,rates[1].close));
      haValues2[0].haLow=MathMin(rates[1].low,MathMin(rates[1].open,rates[1].close));
      haValues2[0].haTime=rates[1].time;
      
      // Start from past to present
      for(int i=1; i<ArraySize(rates2); i++)
      {  // Heiken Ashi calculated values based on the exact same formula used in the native indicator
         // Print("i: ", i, " time: ", rates2[i].time);
         haValues3[0].haOpen=(rates2[i-1].open+rates2[i-1].close)/2;
         haValues3[0].haClose=(rates2[i].open+rates2[i].high+rates2[i].low+rates2[i].close)/4;
         haValues3[0].haHigh=MathMax(rates2[i].high,MathMax(rates2[i].open,rates2[i].close));
         haValues3[0].haLow=MathMin(rates2[i].low,MathMin(rates2[i].open,rates2[i].close));
         haValues3[0].haTime=rates2[i].time;
      }

      // Heiken Ashi indicator values
      CopyBuffer(hHeiken_Ashi,0,1,1,inOpen);
      CopyBuffer(hHeiken_Ashi,1,1,1,inHigh);
      CopyBuffer(hHeiken_Ashi,2,1,1,inLow);
      CopyBuffer(hHeiken_Ashi,3,1,1,inClose);

      inValues[0].inOpen=inOpen[0];
      inValues[0].inHigh=inHigh[0];
      inValues[0].inLow=inLow[0];
      inValues[0].inClose=inClose[0];
      inValues[0].inTime=rates[1].time;

      if(haValues[0].haOpen!=inValues[0].inOpen || haValues[0].haHigh!=inValues[0].inHigh || haValues[0].haLow!=inValues[0].inLow || haValues[0].haClose!=inValues[0].inClose)
      {  Print("---------------------------------------");
         ArrayPrint(haValues3);
         ArrayPrint(haValues2);
         ArrayPrint(haValues);
         ArrayPrint(inValues);
         DebugBreak();
      }

   }
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool IsNewBar()
{  CurrentBar=(datetime)SeriesInfoInteger(_Symbol,_Period,SERIES_LASTBAR_DATE);

   if(PreviousBar!=CurrentBar)
   {  PreviousBar=CurrentBar;
      return true;
   }
   return false;
}
//+------------------------------------------------------------------+
 
  1. algotrader01: I also do not see the differences coming from the recursion.
    You don't? Look closer:
    Your code
    HAopen = (Open[pre]+Close[pre]) / 2
    HAClose= (Open[cur]+Close[cur]+High[cur]+Low[cur]) / 4
    HA
    haOpen=(ExtOpenBuffer[pre]+ExtCloseBuffer[pre])/2;
    haClose=(open[cur]+high[cur]+low[cur]+close[cur])/4;
    ⋮
  2. algotrader01: Subsequently, this is not the cause of difference.
    Because it is recursive, you've changed every value. That is the cause.
 
William Roeder:
  1. You don't? Look closer:
    Your code
    HA
  2. Because it is recursive, you've changed every value. That is the cause.

Good morning William and thank you for your reply!

I think, I have now understood the difference. The following code is a snippet from the native mql5 Heiken Ashi indicator:

 int i,limit;
//--- preliminary calculations
   if(prev_calculated==0)
     {
      //--- set first candle
      ExtLBuffer[0]=low[0];
      ExtHBuffer[0]=high[0];
      ExtOBuffer[0]=open[0];
      ExtCBuffer[0]=close[0];
      limit=1;
     }
   else limit=prev_calculated-1;

//--- the main loop of calculations
   for(i=limit;i<rates_total && !IsStopped();i++)
     {
      double haOpen=(ExtOBuffer[i-1]+ExtCBuffer[i-1])/2;
      double haClose=(open[i]+high[i]+low[i]+close[i])/4;
      double haHigh=MathMax(high[i],MathMax(haOpen,haClose));
      double haLow=MathMin(low[i],MathMin(haOpen,haClose));

      ExtLBuffer[i]=haLow;
      ExtHBuffer[i]=haHigh;
      ExtOBuffer[i]=haOpen;
      ExtCBuffer[i]=haClose;

      //--- set candle color
      if(haOpen<haClose) ExtColorBuffer[i]=0.0; // set color DodgerBlue
      else               ExtColorBuffer[i]=1.0; // set color Red
     }

As can be seen, the first open buffer value is equal to the open value: 

ExtOBuffer[0]=open[0];

However, then, all the following values are only based on the ExtOBuffer and not on the open values:

double haOpen=(ExtOBuffer[i-1]+ExtCBuffer[i-1])/2;

Thus, if I understood correctly, the difference is basically introduced at the second calculation of the haOpen value as from this time onwards it uses the buffer values for calcualtion only and not the original open values. Thus, the difference is prologned into future haOpen values as well.

And, since the haLow and haHigh values are based on the haOpen values one can see a difference in these values as well.


William, is this, in your opinion, the right explanation for the differences I was observing? Thank you!

Reason: