Inbuilt AMA inconsistency when using iCustom

 

I have a working script - to a huge part thanks to the precious help I had received from this forum. The script now successfully performs the following steps:

  1. gets a filename input and reads the corresponding csv file that contains Yahoo Finance OHLCV data for a given stock, e.g. Microsoft (MSFT.csv),
  2. creates a custom symbol for these input values, and fills them up into an MqlRates array,
  3. applies iCustom on the symbol,
  4. write indicator buffer out to a new csv file (datetime and indicator values).

The script runs and I have already checked it for a number of indicators (RSI, Stochastic, On-Balance Volume, Accelerator, ADX) for which the calculated values (written into the output csv file) matched perfectly with the indicator values when applied on the chart. However, for some indicators (such as AMA and Alligator) I found an odd behavior: the calculated values did not match with what I saw on the screen. The values were always close though. 

The code at the end of this post illustrates using MetaTrader's inbuilt AMA indicator (in Indicators/Examples folder) with all default parameters. I also attached the following three files:

  • an abbreviated MSFT.csv (to be placed in MQL5/Files), containing 73 days of Microsoft OHLCV data from 16/12/2020 to 1/4/2021,
  • a file called MSFT_AMA.csv (created by the script in MQL5/Files), with the calculated AMA values (all parameters default) for the OHLCV data in MSFT.csv,
  • and a png image showing the AMA indicator on the MSFT.csv data on the chart. 

Observing the chart and comparing it with the csv output (MSFT_AMA.csv) shows that the AMA values are slightly off:

  • on 1/4/2021, 235.221 on chart vs. 234.723 in csv,
  • on 31/3/2021, 235.166 on chart vs. 234.251 in csv,
  • on 30/3/2021, 235.179 on chart vs. 234.250 in csv,
  • etc.

Can someone please help where my method is wrong?


My full code is as follows:

//AMA inputs
input int InpPeriodAMA = 10;
input int InpFastPeriodEMA = 2;
input int InpSlowPeriodEMA = 30;
input int InpShiftAMA = 0;

input string Indicator_Directory_And_Name="Examples\\AMA";
input string Ticker = "MSFT";

class CCsvData
  {
public:
   datetime          time;
   double            open,high,low,close,adj_close,volume;
  };

CCsvData CsvList[];
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
      
      string csvSymbol = Ticker;
      ReadCsvData(csvSymbol, CsvList);
      
      ///fill csv data to MqlRates array
      MqlRates rates[];
      int csvSz=ArraySize(CsvList);
      ArrayResize(rates,csvSz);
      Print("rates size: ", ArraySize(rates));
      for(int ii=0; ii<csvSz; ii++)//reverse csv array order for custom symbol 
        {
         rates[ii].time=CsvList[csvSz-ii-1].time;
         rates[ii].open=CsvList[csvSz-ii-1].open;
         rates[ii].high=CsvList[csvSz-ii-1].high;
         rates[ii].low=CsvList[csvSz-ii-1].low;
         rates[ii].close=CsvList[csvSz-ii-1].close;
         rates[ii].tick_volume=(int)CsvList[csvSz-ii-1].volume;
        }
      
      ExportFile(csvSymbol, rates);
      
      CustomRatesDelete(csvSymbol,0,INT_MAX);//purge the custom symbol data
      CustomRatesUpdate(csvSymbol,rates);//update MqlRates to custom symbol

      // avoid removing csvSymbol from MarketWatch in order to be able to visually 
      // inspect indicator values and compare with csv values
      //SymbolSelect(csvSymbol,false);
      
   return(INIT_SUCCEEDED);
  }

void ReadCsvData(string symbol, CCsvData &CsvData[]) {
   
   CustomSymbolCreate(symbol,"");
   SymbolSelect(symbol,true);
   CustomSymbolSetInteger(symbol,SYMBOL_SELECT,false);
   CustomSymbolSetInteger(symbol,SYMBOL_VISIBLE,false);
   CustomSymbolSetInteger(symbol,SYMBOL_DIGITS,2);
   
   
   ArrayResize(CsvData,0);
   string FileName = symbol + ".csv";
   Print("Reading ", symbol, ".csv...");
   
   ResetLastError();
   int fHandle=FileOpen(FileName,FILE_BIN|FILE_READ);
   if(fHandle==INVALID_HANDLE)
     {
      Print("failed to open csv file: ", symbol, ".csv, error code: ",GetLastError());
      return;
     }
   uchar buf[];
   int ii,csvColumnSz=7;//yahoo finance csv has 7 columns
   string readStr="";
   FileSeek(fHandle,0,SEEK_SET);
   FileReadArray(fHandle,buf,0,WHOLE_ARRAY);
   FileClose(fHandle);
   readStr=CharArrayToString(buf,0,WHOLE_ARRAY,CP_UTF8);//yahoo csv's text coding is utf-8
   if(readStr!="")
     {
      string elArr[],dataArr[],tmpStr="";
      StringSplit(readStr,'\n',elArr);//yahoo's csv row separator is 0x0a (i.e. \n)
      for(ii=0; ii<ArraySize(elArr); ii++)
        {
         if(elArr[ii]=="" || StringToDouble(elArr[ii])==0)//filter out empty row and first title row
            continue;
         StringSplit(elArr[ii],',',dataArr);// ',' is an inline separator
         if(ArraySize(dataArr)<csvColumnSz)
            continue;
         ArrayResize(CsvData,ArraySize(CsvData)+1);
         int lastIndex=ArraySize(CsvData)-1;
         tmpStr=dataArr[0];
         StringReplace(tmpStr,"-",".");//reformat csv's "yyyy-MM-dd" to mql's "yyyy.MM.dd" time string
         CsvData[lastIndex].time=StringToTime(tmpStr);
         CsvData[lastIndex].open=StringToDouble(dataArr[1]);
         CsvData[lastIndex].high=StringToDouble(dataArr[2]);
         CsvData[lastIndex].low=StringToDouble(dataArr[3]);
         CsvData[lastIndex].close=StringToDouble(dataArr[4]);
         CsvData[lastIndex].adj_close=StringToDouble(dataArr[5]);
         CsvData[lastIndex].volume=StringToDouble(dataArr[6]);
        }
      
      Print("Csv file row count: ",ArraySize(CsvData));
     }
  }

void ExportFile(string symbol, MqlRates& rates[]){
   // Prepare file name: "MSFT_AMA.csv"
   string ExtFileName = symbol;
   string  sPeriod=EnumToString(Period());
   int pos=StringFind(Indicator_Directory_And_Name,"\\",0);
   string indicatorName=StringSubstr(Indicator_Directory_And_Name,pos+1,-1);  
   StringConcatenate(ExtFileName,symbol,"_",indicatorName,".csv");
   
   Print("Size of rates: ", ArraySize(rates));

   double IndicatorBuffer[];
   SetIndexBuffer(0,IndicatorBuffer,INDICATOR_DATA);
   int bars=Bars(symbol,PERIOD_CURRENT);
   Print("bars: ", bars);
   
   int diff_bars = bars-ArraySize(rates);
   Print("Diff bars: ", diff_bars);

   // -------------------------------------------------------------------------------------
   // apply any custom indicator on the given ticker (symbol)
   int indicatorHandle=iCustom(symbol,PERIOD_CURRENT,Indicator_Directory_And_Name,InpPeriodAMA,InpFastPeriodEMA,InpSlowPeriodEMA,InpShiftAMA);
   // -------------------------------------------------------------------------------------
   
   
   CopyBuffer(indicatorHandle,0,diff_bars,ArraySize(rates),IndicatorBuffer);
   ArraySetAsSeries(IndicatorBuffer,true);
   Print("Size of IndicatorBuffer: ", ArraySize(IndicatorBuffer));
   
   int fileHandle=FileOpen(ExtFileName,FILE_CSV|FILE_WRITE, ";");
   Print("File opening: ", ExtFileName, " in progress...");
   
   for(int i=ArraySize(IndicatorBuffer)-1; i>=0; i--)
   {  
      
      // values above 1e300 are invalid garbage values, replace them by 0.0
      if(IndicatorBuffer[i] > MathPow(10.0,300.0)){
         IndicatorBuffer[i] = 0.0;
      }
      string outputData=StringFormat("%s",TimeToString(rates[i].time,TIME_DATE));
      outputData+=";"+TimeToString(rates[i].time,TIME_MINUTES);
      outputData+=";"+ DoubleToString(IndicatorBuffer[i],4);
      outputData+="\n";
      
      FileWriteString(fileHandle,outputData);     

   }
   
   FileClose(fileHandle);
   Print("File: ", ExtFileName, " written & closed successfully.");
   return;
}
Files:
MSFT.csv  8 kb
MSFT_AMA.csv  4 kb
MSFT_AMA.png  62 kb
 

OnInit serves ONLY for initialization. You need to get values in Oncalculate or OnTick.

I saw this error and did not look further. Correct the bug and post the corrected code - then you can look further.

 
Vladimir Karputov:

OnInit serves ONLY for initialization. You need to get values in Oncalculate or OnTick.

I saw this error and did not look further. Correct the bug and post the corrected code - then you can look further.

Thank you, Vladimir Karputov, that's a valid suggestion. My idea behind moving everything to OnInit was because there is only one set of instructions to run on csv values for a ticker. Interestingly though, the script worked fine for many indicators (e.g. RSI, Stoch) even this way, so I never noticed this might be an issue.

Now, based on your suggestions, I moved everything to OnCalculate() because none of the code I had was in fact init-related. Unfortunately by doing so, I broke something since the script no longer works; it runs but the output array (the one to be written to the output csv, MSFT_AMA.csv) is an empty array. 

Here is my code with OnCalculate():

//AMA inputs
input int InpPeriodAMA = 10;
input int InpFastPeriodEMA = 2;
input int InpSlowPeriodEMA = 30;
input int InpShiftAMA = 0;

input string Indicator_Directory_And_Name="Examples\\AMA";
input string Ticker = "MSFT";

class CCsvData
  {
public:
   datetime          time;
   double            open,high,low,close,adj_close,volume;
  };

CCsvData CsvList[];
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {      
   return(INIT_SUCCEEDED);
  }

int OnCalculate()
  {
   
   string csvSymbol = Ticker;
   ReadCsvData(csvSymbol, CsvList);
      
   ///fill csv data to MqlRates array
   MqlRates rates[];
   int csvSz=ArraySize(CsvList);
   ArrayResize(rates,csvSz);
   Print("rates size: ", ArraySize(rates));
   for(int ii=0; ii<csvSz; ii++)//reverse csv array order for custom symbol 
     {
      rates[ii].time=CsvList[csvSz-ii-1].time;
      rates[ii].open=CsvList[csvSz-ii-1].open;
      rates[ii].high=CsvList[csvSz-ii-1].high;
      rates[ii].low=CsvList[csvSz-ii-1].low;
      rates[ii].close=CsvList[csvSz-ii-1].close;
      rates[ii].tick_volume=(int)CsvList[csvSz-ii-1].volume;
     }
   
   ExportFile(csvSymbol, rates);
   
   CustomRatesDelete(csvSymbol,0,INT_MAX);//purge the custom symbol data
   CustomRatesUpdate(csvSymbol,rates);//update MqlRates to custom symbol
   //SymbolSelect(csvSymbol,false);
  
   return(0);
  }

void ReadCsvData(string symbol, CCsvData &CsvData[]) {
   
   CustomSymbolCreate(symbol,"");
   SymbolSelect(symbol,true);
   CustomSymbolSetInteger(symbol,SYMBOL_SELECT,false);
   CustomSymbolSetInteger(symbol,SYMBOL_VISIBLE,false);
   CustomSymbolSetInteger(symbol,SYMBOL_DIGITS,2);
   
   
   ArrayResize(CsvData,0);
   string FileName = symbol + ".csv";
   Print("Reading ", symbol, ".csv...");
   
   ResetLastError();
   int fHandle=FileOpen(FileName,FILE_BIN|FILE_READ);
   if(fHandle==INVALID_HANDLE)
     {
      Print("failed to open csv file: ", symbol, ".csv, error code: ",GetLastError());
      return;
     }
   uchar buf[];
   int ii,csvColumnSz=7;//yahoo finance csv has 7 columns
   string readStr="";
   FileSeek(fHandle,0,SEEK_SET);
   FileReadArray(fHandle,buf,0,WHOLE_ARRAY);
   FileClose(fHandle);
   readStr=CharArrayToString(buf,0,WHOLE_ARRAY,CP_UTF8);//yahoo csv's text coding is utf-8
   if(readStr!="")
     {
      string elArr[],dataArr[],tmpStr="";
      StringSplit(readStr,'\n',elArr);//yahoo's csv row separator is 0x0a (i.e. \n)
      for(ii=0; ii<ArraySize(elArr); ii++)
        {
         if(elArr[ii]=="" || StringToDouble(elArr[ii])==0)//filter out empty row and first title row
            continue;
         StringSplit(elArr[ii],',',dataArr);// ',' is an inline separator
         if(ArraySize(dataArr)<csvColumnSz)
            continue;
         ArrayResize(CsvData,ArraySize(CsvData)+1);
         int lastIndex=ArraySize(CsvData)-1;
         tmpStr=dataArr[0];
         StringReplace(tmpStr,"-",".");//reformat csv's "yyyy-MM-dd" to mql's "yyyy.MM.dd" time string
         CsvData[lastIndex].time=StringToTime(tmpStr);
         CsvData[lastIndex].open=StringToDouble(dataArr[1]);
         CsvData[lastIndex].high=StringToDouble(dataArr[2]);
         CsvData[lastIndex].low=StringToDouble(dataArr[3]);
         CsvData[lastIndex].close=StringToDouble(dataArr[4]);
         CsvData[lastIndex].adj_close=StringToDouble(dataArr[5]);
         CsvData[lastIndex].volume=StringToDouble(dataArr[6]);
        }
      
      Print("Csv file row count: ",ArraySize(CsvData));
     }
  }

void ExportFile(string symbol, MqlRates& rates[]){
   // Prepare file name: "MSFT_AMA.csv"
   string ExtFileName = symbol;
   string  sPeriod=EnumToString(Period());
   int pos=StringFind(Indicator_Directory_And_Name,"\\",0);
   string indicatorName=StringSubstr(Indicator_Directory_And_Name,pos+1,-1);  
   StringConcatenate(ExtFileName,symbol,"_",indicatorName,".csv");
   
   Print("Size of rates: ", ArraySize(rates));

   double IndicatorBuffer[];
   SetIndexBuffer(0,IndicatorBuffer,INDICATOR_DATA);
   int bars=Bars(symbol,PERIOD_CURRENT);
   Print("bars: ", bars);
   
   int diff_bars = bars-ArraySize(rates);
   Print("Diff bars: ", diff_bars);

   // -------------------------------------------------------------------------------------
   // apply any custom indicator on the given ticker (symbol)
   int indicatorHandle=iCustom(symbol,PERIOD_CURRENT,Indicator_Directory_And_Name,InpPeriodAMA,InpFastPeriodEMA,InpSlowPeriodEMA,InpShiftAMA);
   // -------------------------------------------------------------------------------------
   
   
   CopyBuffer(indicatorHandle,0,diff_bars,ArraySize(rates),IndicatorBuffer);
   ArraySetAsSeries(IndicatorBuffer,true);
   Print("Size of IndicatorBuffer: ", ArraySize(IndicatorBuffer));
   
   int fileHandle=FileOpen(ExtFileName,FILE_CSV|FILE_WRITE, ";");
   Print("File opening: ", ExtFileName, " in progress...");
   
   for(int i=ArraySize(IndicatorBuffer)-1; i>=0; i--)
   {  
      
      // values above 1e300 are invalid garbage values, replace them by 0.0
      if(IndicatorBuffer[i] > MathPow(10.0,300.0)){
         IndicatorBuffer[i] = 0.0;
      }
      string outputData=StringFormat("%s",TimeToString(rates[i].time,TIME_DATE));
      outputData+=";"+TimeToString(rates[i].time,TIME_MINUTES);
      outputData+=";"+ DoubleToString(IndicatorBuffer[i],4);
      outputData+="\n";
      
      FileWriteString(fileHandle,outputData);     

   }
   
   FileClose(fileHandle);
   Print("File: ", ExtFileName, " written & closed successfully.");
   return;
}
 

The console outputs are as follows:

2021.04.05 16:00:03.504 AMAQuestion (EURUSD,H1) Reading MSFT.csv...
2021.04.05 16:00:03.505 AMAQuestion (EURUSD,H1) Csv file row count: 73
2021.04.05 16:00:03.505 AMAQuestion (EURUSD,H1) rates size: 73
2021.04.05 16:00:03.505 AMAQuestion (EURUSD,H1) Size of rates: 73
2021.04.05 16:00:03.505 AMAQuestion (EURUSD,H1) bars: 73
2021.04.05 16:00:03.505 AMAQuestion (EURUSD,H1) Size of IndicatorBuffer: 0
2021.04.05 16:00:03.506 AMAQuestion (EURUSD,H1) File opening: MSFT_AMA.csv in progress...
2021.04.05 16:00:03.506 AMAQuestion (EURUSD,H1) File: MSFT_AMA.csv written & closed successfully.
2021.04.05 16:00:03.506 AMAQuestion (EURUSD,H1) Indicator buffer #0 detached

What is apparent from this is that the csv is correctly read, data is correctly copied into an MqlRates array, a custom symbol (MSFT) is correctly added with the correct number of bars. But at the end, when copying indicator buffer values over to the IndicatorBuffer double array, something is now not okay when using the OnCalculate. This part worked too when I had everything in OnInit, albeit with slightly off final values. 

In the following snippet, IndicatorBuffer array remains empty with the new approach (using OnCalculate). If I include (uncomment) the ArrayResize() row, nothing changes.

double IndicatorBuffer[];
SetIndexBuffer(0,IndicatorBuffer,INDICATOR_DATA);
int bars=Bars(symbol,PERIOD_CURRENT);
Print("bars: ", bars);
 
int diff_bars = bars-ArraySize(rates);

// -------------------------------------------------------------------------------------
// apply any custom indicator on the given ticker (symbol)
int indicatorHandle=iCustom(symbol,PERIOD_CURRENT,Indicator_Directory_And_Name,InpPeriodAMA,InpFastPeriodEMA,InpSlowPeriodEMA,InpShiftAMA);
// -------------------------------------------------------------------------------------
 
//ArrayResize(IndicatorBuffer,ArraySize(rates)-diff_bars);
CopyBuffer(indicatorHandle,0,diff_bars,ArraySize(rates),IndicatorBuffer);
ArraySetAsSeries(IndicatorBuffer,true);
Print("Size of IndicatorBuffer: ", ArraySize(IndicatorBuffer));
 
Levente Csibi:

I have a working script - to a huge part thanks to the precious help I had received from this forum. The script now successfully performs the following steps:

  1. gets a filename input and reads the corresponding csv file that contains Yahoo Finance OHLCV data for a given stock, e.g. Microsoft (MSFT.csv),
  2. creates a custom symbol for these input values, and fills them up into an MqlRates array,
  3. applies iCustom on the symbol,
  4. write indicator buffer out to a new csv file (datetime and indicator values).

The script runs and I have already checked it for a number of indicators (RSI, Stochastic, On-Balance Volume, Accelerator, ADX) for which the calculated values (written into the output csv file) matched perfectly with the indicator values when applied on the chart. However, for some indicators (such as AMA and Alligator) I found an odd behavior: the calculated values did not match with what I saw on the screen. The values were always close though. 

The code at the end of this post illustrates using MetaTrader's inbuilt AMA indicator (in Indicators/Examples folder) with all default parameters. I also attached the following three files:

  • an abbreviated MSFT.csv (to be placed in MQL5/Files), containing 73 days of Microsoft OHLCV data from 16/12/2020 to 1/4/2021,
  • a file called MSFT_AMA.csv (created by the script in MQL5/Files), with the calculated AMA values (all parameters default) for the OHLCV data in MSFT.csv,
  • and a png image showing the AMA indicator on the MSFT.csv data on the chart. 

Observing the chart and comparing it with the csv output (MSFT_AMA.csv) shows that the AMA values are slightly off:

  • on 1/4/2021, 235.221 on chart vs. 234.723 in csv,
  • on 31/3/2021, 235.166 on chart vs. 234.251 in csv,
  • on 30/3/2021, 235.179 on chart vs. 234.250 in csv,
  • etc.

Can someone please help where my method is wrong?


My full code is as follows:

Chose the price of the AMA from example folder to close (default will be open) or to be the same as the built in ama that you are comparing it to and then the built in that one from examples will produce the same result
 
Mladen Rakic:
Chose the price of the AMA from example folder to close (default will be open) or to be the same as the built in ama that you are comparing it to and then the built in that one from examples will produce the same result

Wow! Very nice catch, @Mladen Rasic, thanks a lot - I didn't think of this option.

But now I started wondering, how can such an inconsistency even occur? I find this really really weird. The original AMA.mq5 (located in Examples/AMA) has, by default, PRICE_OPEN set as a property. Yet, as you very nicely pointed out, when calling the very same Examples/AMA using iCustom, it somehow calculates the values using PRICE_CLOSE; even though the file iCustom is meant to work from should contain the PRICE_OPEN property.

What's going on here? 

 

Oh, so turns out I can pass additional parameters to iCustom to modify the #property values too in the indicators called. This one uses open prices to compute: 

int indicatorHandle=iCustom(symbol,PERIOD_CURRENT,Indicator_Directory_And_Name,InpPeriodAMA,InpFastPeriodEMA,InpSlowPeriodEMA,InpShiftAMA,PRICE_OPEN);

 while this one uses close prices to compute indicator values on: 

int indicatorHandle=iCustom(symbol,PERIOD_CURRENT,Indicator_Directory_And_Name,InpPeriodAMA,InpFastPeriodEMA,InpSlowPeriodEMA,InpShiftAMA,PRICE_CLOSE);

And obviously, iCustom, by default, uses PRICE_CLOSE. While, the default in the mq5 file of AMA uses PRICE_OPEN. All clear now ! Thanks/hvala once again for steering me into the right direction, @Mladen Rasic!

Reason: