Comparing speeds of self-caching indicators
Introduction
Suppose that we have suddenly got bored with the classic MQL5 access to indicators. Let's compare the access speed with alternative options. For example, we can compare it with access to indicators in MQL4 style with and without caching. Ideas about MQL4-style access have been derived from the article "LifeHack for traders: Fast food made of indicators" and improved along the way.
Analyzing MQL5 numbering of the indicator handles
Assume that the terminal features the consecutive numbering of the indicator handles starting with zero. To check this assumption, let's create a simple Expert Advisor iMACD and IndicatorRelease.mq5 — it creates several indicator handles, immediately prints them and accesses them regularly in OnTick():
//+------------------------------------------------------------------+ //| iMACD and IndicatorRelease.mq5 | //| Copyright © 2018, Vladimir Karputov | //| http://wmua.ru/slesar/ | //+------------------------------------------------------------------+ #property copyright "Copyright © 2018, Vladimir Karputov" #property link "http://wmua.ru/slesar/" #property version "1.003" //--- input parameter input int count=6; // Count MACD indicators int handles_array[]; // array for storing the handles of the iMACD indicators //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { int array_resize=ArrayResize(handles_array,count); if(array_resize==-1) { Print("ArrayResize error# ",GetLastError()); return(INIT_FAILED); } if(array_resize!=count) { Print("ArrayResize != \"Count MACD indicators\""); return(INIT_FAILED); } ArrayInitialize(handles_array,0); for(int i=0;i<count;i++) { handles_array[i]=CreateHandleMACD(12+i); //--- if the handle is not created if(handles_array[i]==INVALID_HANDLE) { //--- tell about the failure and output the error code PrintFormat("Failed to create handle of the iMACD indicator for the symbol %s/%s, error code %d", Symbol(), EnumToString(Period()), GetLastError()); //--- the indicator is stopped early return(INIT_FAILED); } Print("ChartID: ",ChartID(),": ",Symbol(),",",StringSubstr(EnumToString(Period()),7), ", create handle iMACD (",handles_array[i],")"); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Comment(""); for(int i=0;i<count;i++) { Print("ChartID: ",ChartID(),": ",Symbol(),",",StringSubstr(EnumToString(Period()),7), ", remove handle iMACD (",handles_array[i],"): ",IndicatorRelease(handles_array[i])); } } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- string text=""; for(int i=0;i<count;i++) { double macd_main_1=iMACDGet(handles_array[i],MAIN_LINE,1); if(i<15) { text+="\n"+"ChartID: "+IntegerToString(ChartID())+": "+Symbol()+ ", MACD#"+IntegerToString(i)+" "+DoubleToString(macd_main_1,Digits()+1); Comment(text); } else if(i==15) { text+="\n"+"only the first 15 indicators are displayed ..."; Comment(text); } } } //+------------------------------------------------------------------+ //| Get value of buffers for the iMACD | //| the buffer numbers are the following: | //| 0 - MAIN_LINE, 1 - SIGNAL_LINE | //+------------------------------------------------------------------+ double iMACDGet(const int handle_iMACD,const int buffer,const int index) { double MACD[1]; //--- reset error code ResetLastError(); //--- fill a part of the iMACDBuffer array with values from the indicator buffer that has 0 index if(CopyBuffer(handle_iMACD,buffer,index,1,MACD)<0) { //--- if the copying fails, tell the error code PrintFormat("Failed to copy data from the iMACD indicator, error code %d",GetLastError()); //--- quit with zero result - it means that the indicator is considered as not calculated return(0.0); } return(MACD[0]); } //+------------------------------------------------------------------+ //| Create handle MACD | //+------------------------------------------------------------------+ int CreateHandleMACD(const int fast_ema_period) { //--- create handle of the indicator iMACD return(iMACD(Symbol(),Period(),fast_ema_period,52,9,PRICE_CLOSE)); } //+------------------------------------------------------------------+
Experiment 1
Source data: the terminal has open AUDJPY M15, USDJPY M15 and EURUSD M15 charts with no indicators and no EAs. Count MACD indicators parameter of iMACD and IndicatorRelease.mq5 is 6.
Attach iMACD and IndicatorRelease.mq5 to AUDJPY M15 (ChartID 131571247244850509) immediately after restarting the terminal:
2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10) 2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (11) 2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (12) 2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (13) 2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (14) 2018.02.16 09:36:30.240 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (15)
We can see that handles numbering starts from 10 rather than 0.
Experiment 2
Source data: iMACD and IndicatorRelease.mq5 is launched on AUDJPY M15, Count MACD indicators is 6.
Attach iMACD and IndicatorRelease.mq5 to USDJPY, M15 (ChartID 131571247244850510):
2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (10) 2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (11) 2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (12) 2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (13) 2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (14) 2018.02.16 09:37:32.118 iMACD and IndicatorRelease (USDJPY,M15) ChartID: 131571247244850510: USDJPY,M15, create handle iMACD (15)
We can see that handles numbering on the chart (USDJPY M15) also starts from 10, not 0.
Conclusion: the numbering of indicator handles in the terminal (the one provided to a user) is NOT consecutive and does NOT start with zero.
Experiment 3
Two identical charts AUDJPY, M15 (ChartID 131571247244850509) and AUDJPY, M15 (ChartID 131571247244850510). Each has iMACD and IndicatorRelease.mq5 with Count MACD indicators equal to 6.
The non-consecutive numbering of created indicator handles confirms that MQL5 maintains internal accounting for them (counter for each unique handle). To make sure of this, let's comment out the period expansion:
int OnInit() { *** ArrayInitialize(handles_array,0); for(int i=0;i<count;i++) { handles_array[i]=CreateHandleMACD(12/*+i*/); //--- if the handle is not created
Thus, we try to create several MACD indicator handles with exactly the same settings.
Remove the charts left after Experiments 1 and 2 and launch iMACD and IndicatorRelease.mq5 on AUDJPY, M15 (ChartID 131571247244850509):
2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10) 2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10) 2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10) 2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10) 2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10) 2018.02.18 07:53:13.600 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, create handle iMACD (10)
As we can see, the same handle is returned as a response to creating absolutely identical indicators.
Attach iMACD and IndicatorRelease.mq5 EA (also with the period extension commented out) on AUDJPY, M15 (ChartID 131571247244850510):
2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10) 2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10) 2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10) 2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10) 2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10) 2018.02.18 07:53:20.218 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, create handle iMACD (10)
The same handle is returned again. Are the "10" handles on the first and second charts one and the same or two different handles? To check this, remove the EA from the charts (as you remember, the EA passes the array of handles in OnDeinit() and removes each one using IndicatorRelease).
2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): true 2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): false 2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): false 2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): false 2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): false 2018.02.18 07:53:26.716 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850509: AUDJPY,M15, remove handle iMACD (10): false 2018.02.18 07:53:36.116 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): true 2018.02.18 07:53:36.117 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): false 2018.02.18 07:53:36.117 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): false 2018.02.18 07:53:36.117 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): false 2018.02.18 07:53:36.117 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): false 2018.02.18 07:53:36.117 iMACD and IndicatorRelease (AUDJPY,M15) ChartID: 131571247244850510: AUDJPY,M15, remove handle iMACD (10): false
The result has turned out to be expected if we consider Program Running documentation section:
The EA is executed in its own thread, there are as many threads of execution as there are EAs
This means that if two EAs on the same charts (same symbol and timeframe) create indicators with the same inputs, MQL5 identifies them as two different handles in its internal accounting.
General conclusion concerning the development of indicators in EAs
The numbering of indicator handles in the terminal (the one provided to a user) is NOT consecutive and does NOT start with zero, while in its internal handle accounting, MQL5 considers:
- technical indicator function (iMA, iAC, iMACD, iIchimoku, etc.);
- indicator inputs;
- symbol the indicator is created on;
- timeframe the indicator is created on;
- ChartID of a chart the EA works on.
Does caching handles have a point?
Initial data (timeframe, symbol, tested time interval and tick generation type) are as follows:
Fig. 1. Settings
Tests with access to indicators in MQL4 style (with and without handle caching) are performed with the help of Cache test.mq5 EA, while tests featuring MQL5-style access are conducted using MQL5 test.mq5:
//+------------------------------------------------------------------+ //| MQL5 test.mq5 | //| Copyright © 2018, Vladimir Karputov | //| http://wmua.ru/slesar/ | //+------------------------------------------------------------------+ #property copyright "Copyright © 2018, Vladimir Karputov" #property link "http://wmua.ru/slesar/" #property version "1.000" //--- input parameters input bool UseOneIndicator=false; // Use indicator: "false" -> 9 indicators, "true" - 1 indicator //--- int arr_handle_iMACD[]; // array for storing the handles of the iMACD indicators //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { if(UseOneIndicator) ArrayResize(arr_handle_iMACD,1); else ArrayResize(arr_handle_iMACD,9); if(!CreateHandle(arr_handle_iMACD)) return(INIT_FAILED); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- int arr_size=ArraySize(arr_handle_iMACD); for(int i=0;i<arr_size;i++) { double macd_main_30=iMACDGet(arr_handle_iMACD[i],MAIN_LINE,0); } } //+------------------------------------------------------------------+ //| CreateHandle | //+------------------------------------------------------------------+ bool CreateHandle(int &arr_handles[]) { int arr_size=ArraySize(arr_handles); for(int i=0;i<arr_size;i++) { int fast_ema_repiod=30+10*i; //--- create handle of the indicator iMACD arr_handles[i]=iMACD(NULL,0,fast_ema_repiod,26,9,PRICE_CLOSE); //--- if the handle is not created if(arr_handles[i]==INVALID_HANDLE) { //--- tell about the failure and output the error code PrintFormat("Failed to create handle of the iMACD indicator for the symbol %s/%s, error code %d", Symbol(), EnumToString(Period()), GetLastError()); //--- the indicator is stopped early return(false); } } return(true); } //+------------------------------------------------------------------+ //| Get value of buffers for the iMACD | //| the buffer numbers are the following: | //| 0 - MAIN_LINE, 1 - SIGNAL_LINE | //+------------------------------------------------------------------+ double iMACDGet(const int handle_iMACD,const int buffer,const int index) { double MACD[1]; //--- reset error code ResetLastError(); //--- fill a part of the iMACDBuffer array with values from the indicator buffer that has 0 index if(CopyBuffer(handle_iMACD,buffer,index,1,MACD)<0) { //--- if the copying fails, tell the error code PrintFormat("Failed to copy data from the iMACD indicator, error code %d",GetLastError()); //--- quit with zero result - it means that the indicator is considered as not calculated return(0.0); } return(MACD[0]); } //+------------------------------------------------------------------+
MQL5 test.mq5 EA parameter:
Fig. 2. MQL5 test.mq5. Nine indicators
Cache test.mq5 EA parameters:
- Use Timer ("0" -> off timer) — use the timer (0 — not used).
- Use indicator ("false" -> 9 indicators, "true" - 1 indicator) — number of surveyed indicators (1 or 9).
Fig. 3. Cache test.mq5. No timer, nine indicators
IndicatorsMQL4.mq file is used to measure "MQL4 style without handle caching". The file is connected using SimpleCallMQL4.mqh (see the article "LifeHack for traders: Blending ForEach with defines (#define)" ).
#include <SimpleCall\SimpleCallMQL4.mqh> // for tests without caching of the handles //#include <SimpleCall\SimpleCallMQL4Caching.mqh> // for tests with caching of the handles //#include <SimpleCall\SimpleCallString.mqh> // for tests with string
To measure "MQL4 style with handle caching", the handle caching code from the post #113 is added to IndicatorsMQL4.mqh (for MACD only, other functions are deleted). The file is saved as IndicatorsMQL4Caching.mqh — it is connected by SimpleCallCaching.mqh:
//#include <SimpleCall\SimpleCallMQL4.mqh> // for tests without caching of the handles #include <SimpleCall\SimpleCallMQL4Caching.mqh> // for tests with caching of the handles //#include <SimpleCall\SimpleCallString.mqh> // for tests with string
Results of comparing access styles to the nine indicators (settings are provided in Fig. 1):
Fig. 4. Time spent for accessing the nine indicators
When comparing the results, please note that the test EA has considerably complicated the task:
- data are obtained from NINE indicators simultaneously;
- indicators are accessed AT EACH tick;
- M1 timeframe — 26 169 180 ticks and 370 355 bars were generated.
Now let's conduct a test: call only one indicator (for both EAs, MQL5 test.mq5 and Cache test.mq5, Use indicator... parameter is "true", while for Cache test.mq5, Use Timer is "0")
Fig. 5. Time spent for accessing one indicator
Conclusion
MQL4 style with handle caching provides an advantage as compared to MQL4 style without handle caching. However, MQL4 style loses completely to MQL5 one.
No handle validity control
Now we should mention the huge drawback of using handles caching: It provides no check for the existence of the handle in the user's cache. In other words, the case of deleted indicator handle is not processed in any way.
Let's consider the following situation: we work with indicators in MQL4 style and cache the handles. After the first access from the EA:
double macd_main_30=iMACD(NULL,0,30,26,9,PRICE_CLOSE,MODE_MAIN,0);
the handle is stored in the user cache (this can be an array of structures or an array of strings). After that, all subsequent accesses from the EA
double macd_main_30=iMACD(NULL,0,30,26,9,PRICE_CLOSE,MODE_MAIN,0);
are not passed to MQL5 core. Instead, the indicator values by the handle taken from the cache are returned. Now, delete the handle in OnTimer() — suppose that we know it is equal to "10". As a test, let's use Cache test.mq5 file, to which SimpleCallMQL4Caching.mqh file should be included:
//#include <SimpleCall\SimpleCallMQL4.mqh> // for tests without caching of the handles #include <SimpleCall\SimpleCallMQL4Caching.mqh> // for tests with caching of the handles //#include <SimpleCall\SimpleCallString.mqh> // for tests with string
Make sure to set the timer (here, the timer is set for six seconds, we gain access to one indicator)
Fig. 6. Test settings with the handle removal
After the very first OnTimer() entry
OnTimer, IndicatorRelease(10)=true iMACD: CopyBuffer error=4807 iMACD: CopyBuffer error=4807 iMACD: CopyBuffer error=4807 iMACD: CopyBuffer error=4807
we get the error 4807:
ERR_INDICATOR_WRONG_HANDLE | 4807 | Invalid indicator handle |
This means the indicator handle validity control is absent.
Caching the indicator handles. How it works
The general principle of caching the indicator handles is as follows:
- create a custom handles cache;
- when requesting data from the indicator, check if the handle has already been created at the requested settings (symbol, timeframe, averaging period, etc.):
- if it already exists in the custom cache, return data on it from the indicator;
- if no such handle exists yet, create it, save it in the cache and return data on it from the indicator.
Option 1: Array of structures
Implementation is performed in IndicatorsMQL4Caching.mqh (connected to Cache test.mq5 using SimpleCallMQL4Caching.mqh).
In Cache test.mq5, include SimpleCallMQL4Caching.mqh:
//#include <SimpleCall\SimpleCallMQL4.mqh> // for tests without caching of the handles #include <SimpleCall\SimpleCallMQL4Caching.mqh> // for tests with caching of the handles //#include <SimpleCall\SimpleCallString.mqh> // for tests with string
First, let's have a look at a large code block inserted to the file and iMACD function:
... //+------------------------------------------------------------------+ //| Struct CHandle | //+------------------------------------------------------------------+ template<typename T> struct SHandle { private: int Handle; T Inputs; public: //+------------------------------------------------------------------+ //| A constructor with an initialization list | //+------------------------------------------------------------------+ SHandle() : Handle(INVALID_HANDLE) { } //+------------------------------------------------------------------+ //| Operation Overloading "==" | //+------------------------------------------------------------------+ bool operator==(const T &Inputs2) const { return(this.Inputs == Inputs2); } //+------------------------------------------------------------------+ //| Operation Overloading "=" | //+------------------------------------------------------------------+ void operator=(const T &Inputs2) { this.Inputs=Inputs2; } //+------------------------------------------------------------------+ //| SHandle::GetHandle | //+------------------------------------------------------------------+ int GetHandle() { return((this.Handle != INVALID_HANDLE) ? this.Handle : (this.Handle = this.Inputs.GetHandle())); } }; //+------------------------------------------------------------------+ //| Get Handle | //+------------------------------------------------------------------+ template<typename T> int GetHandle(SHandle<T>&Handles[],const T &Inputs) { const int Size=ArraySize(Handles); for(int i=0; i<Size; i++) if(Handles[i]==Inputs) return(Handles[i].GetHandle()); ArrayResize(Handles,Size+1); Handles[Size]=Inputs; return(Handles[Size].GetHandle()); } //+------------------------------------------------------------------+ //| Struct Macd | //+------------------------------------------------------------------+ struct SMacd { string symbol; ENUM_TIMEFRAMES period; int fast_ema_period; int slow_ema_period; int signal_period; ENUM_APPLIED_PRICE applied_price; //+------------------------------------------------------------------+ //| An empty default constructor | //+------------------------------------------------------------------+ SMacd(void) { } //+------------------------------------------------------------------+ //| A constructor with an initialization list | //+------------------------------------------------------------------+ SMacd(const string &isymbol, const ENUM_TIMEFRAMES &iperiod, const int &ifast_ema_period, const int &islow_ema_period, const int &isignal_period, const ENUM_APPLIED_PRICE &iapplied_price) : symbol((isymbol== NULL)||(isymbol == "") ? Symbol() : isymbol), period(iperiod == PERIOD_CURRENT ? Period() : iperiod), fast_ema_period(ifast_ema_period), slow_ema_period(islow_ema_period), signal_period(isignal_period), applied_price(iapplied_price) { } //+------------------------------------------------------------------+ //| SMacd::GetHandle | //+------------------------------------------------------------------+ int GetHandle(void) const { return(iMACD(this.symbol, this.period, this.fast_ema_period, this.slow_ema_period, this.signal_period, this.applied_price)); } //+------------------------------------------------------------------+ //| Operation Overloading "==" | //+------------------------------------------------------------------+ bool operator==(const SMacd &Inputs) const { return((this.symbol == Inputs.symbol) && (this.period == Inputs.period) && (this.fast_ema_period == Inputs.fast_ema_period) && (this.slow_ema_period == Inputs.slow_ema_period) && (this.signal_period == Inputs.signal_period) && (this.applied_price == Inputs.applied_price)); } }; //+------------------------------------------------------------------+ //| iMACD2 function in MQL4 notation | //| The buffer numbers are the following: | //| MQL4 0 - MODE_MAIN, 1 - MODE_SIGNAL | //| MQL5 0 - MAIN_LINE, 1 - SIGNAL_LINE | //+------------------------------------------------------------------+ int iMACD2(const string symbol, const ENUM_TIMEFRAMES period, const int fast_ema_period, const int slow_ema_period, const int signal_period, const ENUM_APPLIED_PRICE applied_price) { static SHandle<SMacd>Handles[]; const SMacd Inputs(symbol,period,fast_ema_period,slow_ema_period,signal_period,applied_price); return(GetHandle(Handles, Inputs)); } //+------------------------------------------------------------------+ //| iAC function in MQL4 notation | ... //+------------------------------------------------------------------+ //| iMACD function in MQL4 notation | //| The buffer numbers are the following: | //| MQL4 0 - MODE_MAIN, 1 - MODE_SIGNAL | //| MQL5 0 - MAIN_LINE, 1 - SIGNAL_LINE | //+------------------------------------------------------------------+ double iMACD( string symbol, // symbol name ENUM_TIMEFRAMES timeframe, // timeframe int fast_ema_period, // period for Fast average calculation int slow_ema_period, // period for Slow average calculation int signal_period, // period for their difference averaging ENUM_APPLIED_PRICE applied_price, // type of price or handle int buffer, // buffer int shift // shift ) { double result=NaN; //--- int handle=iMACD2(symbol,timeframe,fast_ema_period,slow_ema_period,signal_period,applied_price); if(handle==INVALID_HANDLE) ...
Let's describe its work. First, there is a request for data from MACD:
double macd_main_30=iMACD(NULL,0,30,26,9,PRICE_CLOSE,MODE_MAIN,0);
Then we get into iMACD function and go to iMACD2:
//+------------------------------------------------------------------+ //| iMACD2 function in MQL4 notation | //| The buffer numbers are the following: | //| MQL4 0 - MODE_MAIN, 1 - MODE_SIGNAL | //| MQL5 0 - MAIN_LINE, 1 - SIGNAL_LINE | //+------------------------------------------------------------------+ int iMACD2(const string symbol, const ENUM_TIMEFRAMES period, const int fast_ema_period, const int slow_ema_period, const int signal_period, const ENUM_APPLIED_PRICE applied_price) { static SHandle<SMacd>Handles[]; const SMacd Inputs(symbol,period,fast_ema_period,slow_ema_period,signal_period,applied_price); return(GetHandle(Handles, Inputs)); }
Handles[] static array with SMacd type is declared here (it is created during the first entry and is not re-created at subsequent entries). Also, Inputs object with SMacd type is created and initialized with parameters at once.
After that, use the links to pass Handles[] array and Inputs object to GetHandle function (not to SHandle::GetHandle and SMacd::GetHandle):
//+------------------------------------------------------------------+ //| Get Handle | //+------------------------------------------------------------------+ template<typename T> int GetHandle(SHandle<T>&Handles[],const T &Inputs) { const int Size=ArraySize(Handles); for(int i=0; i<Size; i++) if(Handles[i]==Inputs) return(Handles[i].GetHandle()); ArrayResize(Handles,Size+1); Handles[Size]=Inputs; return(Handles[Size].GetHandle()); }
In this function, return the found indicator handle in the array or, if the handle is not found, receive it in SHandle::GetHandle.
But since this is the first access and there is no such handle yet,
//+------------------------------------------------------------------+ //| SHandle::GetHandle | //+------------------------------------------------------------------+ int GetHandle() { return((this.Handle != INVALID_HANDLE) ? this.Handle : (this.Handle = this.Inputs.GetHandle())); }
create it in SMacd::GetHandle:
//+------------------------------------------------------------------+ //| SMacd::GetHandle | //+------------------------------------------------------------------+ int GetHandle(void) const { return(iMACD(this.symbol, this.period, this.fast_ema_period, this.slow_ema_period, this.signal_period, this.applied_price)); }
Option 2: String array
Implementation is performed in IndicatorsMQL4String.mqh file (connected to Cache test.mq5 using SimpleCallString.mqh).
In Cache test.mq5 EA, include SimpleCallString.mqh:
//#include <SimpleCall\SimpleCallMQL4.mqh> // for tests without caching of the handles //#include <SimpleCall\SimpleCallMQL4Caching.mqh> // for tests with caching of the handles #include <SimpleCall\SimpleCallString.mqh> // for tests with string
Working with strings is terribly expensive in terms of speed. We will see that a bit later. So, the idea of saving parameters as a string looks as follows:
string Hashes[]; static int Handles[]; string hash=((symbol==NULL) || (symbol=="") ? Symbol() : symbol)+ (string)(timeframe==PERIOD_CURRENT ? Period() : timeframe)+ (string)(fast_ema_period)+ (string)(slow_ema_period)+ (string)(signal_period)+ (string)(applied_price);
We will access iMACD from the EA with parameters provided above, in Fig. 1.
NN | Code | Time |
---|---|---|
1 | //--- NN2 //static string Hashes[]; //static int Handles[]; //string hash=((symbol==NULL) || (symbol=="") ? Symbol() : symbol)+ // (string)(timeframe==PERIOD_CURRENT ? Period() : timeframe)+ // (string)(fast_ema_period)+ // (string)(slow_ema_period)+ // (string)(signal_period)+ // (string)(applied_price); //--- NN3 //static string Hashes[]; //static int Handles[]; //string hash=""; //StringConcatenate(hash, // ((symbol==NULL) || (symbol=="") ? Symbol() : symbol), // (timeframe==PERIOD_CURRENT ? Period() : timeframe), // fast_ema_period, // slow_ema_period, // signal_period, // applied_price); |
0:01:40.953 |
2 | //--- NN2 static string Hashes[]; static int Handles[]; string hash=((symbol==NULL) || (symbol=="") ? Symbol() : symbol)+ (string)(timeframe==PERIOD_CURRENT ? Period() : timeframe)+ (string)(fast_ema_period)+ (string)(slow_ema_period)+ (string)(signal_period)+ (string)(applied_price); //--- NN3 //static string Hashes[]; //static int Handles[]; //string hash=""; //StringConcatenate(hash, // ((symbol==NULL) || (symbol=="") ? Symbol() : symbol), // (timeframe==PERIOD_CURRENT ? Period() : timeframe), // fast_ema_period, // slow_ema_period, // signal_period, // applied_price); |
0:05:20.953 |
3 | //--- NN2 //static string Hashes[]; //static int Handles[]; //string hash=((symbol==NULL) || (symbol=="") ? Symbol() : symbol)+ // (string)(timeframe==PERIOD_CURRENT ? Period() : timeframe)+ // (string)(fast_ema_period)+ // (string)(slow_ema_period)+ // (string)(signal_period)+ // (string)(applied_price); //--- NN3 static string Hashes[]; static int Handles[]; string hash=""; StringConcatenate(hash, ((symbol==NULL) || (symbol=="") ? Symbol() : symbol), (timeframe==PERIOD_CURRENT ? Period() : timeframe), fast_ema_period, slow_ema_period, signal_period, applied_price); |
0:04:12.672 |
Test 1 is a benchmark test with MQL4-style access to indicators without working with strings. In test 2, we already work with strings and the string is formed using "+". In test 3, the string is formed using StringConcatenate.
According to the time measurements, it is clear that, although StringConcatenate gives 21% time gain in comparison with test 2, the overall performance is still 2.5 times less than in test 1.
Therefore, the idea of saving the indicator handles as strings can be discarded.
Option 3 — class caching handles (iIndicators.mqh class is connected to Cache test.mq5 EA using SimpleCallMQL4CachingCiIndicators.mqh).
In Cache test.mq5 EA, we include SimpleCallMQL4CachingCiIndicators.mqh:
//#include <SimpleCall\SimpleCallMQL4.mqh> // for tests without caching of the handles //#include <SimpleCall\SimpleCallMQL4Caching.mqh> // for tests with caching of the handles //#include <SimpleCall\SimpleCallString.mqh> // for tests with string #include <SimpleCall\SimpleCallMQL4CachingCiIndicators.mqh>
Static object of CHandle class is created (inside the appropriate MQL4-style function) for each indicator. It serves as CiIndicators class object storage — class containing the indicator parameters and settings.
Fig. 7. Structure
CiIndicators class is based on five 'private' variables:
//+------------------------------------------------------------------+ //| Class iIndicators | //+------------------------------------------------------------------+ class CiIndicators { private: string m_symbol; // symbol name ENUM_TIMEFRAMES m_period; // timeframe ENUM_INDICATOR m_indicator_type; // indicator type from the enumeration ENUM_INDICATOR int m_parameters_cnt; // number of parameters MqlParam m_parameters_array[]; // array of parameters public:
It is completely corresponds to IndicatorCreate function variables. This is not done for nothing, since we receive the indicator handle via IndicatorCreate.
CHandle class is built using two arrays:
//+------------------------------------------------------------------+ //| Class CHandle | //+------------------------------------------------------------------+ class CHandle { private: int m_handle[]; CiIndicators m_indicators[]; public:
m_handle array contains created indicator handles, while m_indicators array is CiIndicators class array.
Code of working with CiIndicators and CHandle classes looks as follows using MACD as an example:
//+------------------------------------------------------------------+ //| iMACD function in MQL4 notation | //| The buffer numbers are the following: | //| MQL4 0 - MODE_MAIN, 1 - MODE_SIGNAL | //| MQL5 0 - MAIN_LINE, 1 - SIGNAL_LINE | //+------------------------------------------------------------------+ double iMACD( string symbol, // symbol name ENUM_TIMEFRAMES timeframe, // timeframe int fast_ema_period, // period for Fast average calculation int slow_ema_period, // period for Slow average calculation int signal_period, // period for their difference averaging ENUM_APPLIED_PRICE applied_price, // type of price or handle int buffer, // buffer int shift // shift ) { //--- static CHandle Handles_MACD; //--- fill the structure with parameters of the indicator MqlParam pars[4]; //--- period of fast ma pars[0].type=TYPE_INT; pars[0].integer_value=fast_ema_period; //--- period of slow ma pars[1].type=TYPE_INT; pars[1].integer_value=slow_ema_period; //--- period of averaging of difference between the fast and the slow moving average pars[2].type=TYPE_INT; pars[2].integer_value=signal_period; //--- type of price pars[3].type=TYPE_INT; pars[3].integer_value=applied_price; CiIndicators MACD_Indicator; MACD_Indicator.Init(Symbol(),Period(),IND_MACD,4); int handle=Handles_MACD.GetHandle(MACD_Indicator,Symbol(),Period(),IND_MACD,4,pars); //--- double result=NaN; //--- if(handle==INVALID_HANDLE) { Print(__FUNCTION__,": INVALID_HANDLE error=",GetLastError()); return(result); } double val[1]; int copied=CopyBuffer(handle,buffer,shift,1,val); if(copied>0) result=val[0]; else Print(__FUNCTION__,": CopyBuffer error=",GetLastError()); return(result); }
- Handles_MACD static array of CHandle class is declared — it is to store generated handles and MACD parameters.
- MACD_Indicator object of CiIndicators class is created and initialized.
- The indicator handle is created (or passed if it has already been created for such parameters) in Handles_MACD::GetHandle function.
CiIndicators.mqh class operation time with MQL4-style access and handles caching took 2 minutes and 30 seconds.
Final graph of the access speed to the nine indicators
MQL4 style with and without caching is checked using Cache test.mq5, while standard MQL5 style tests are conducted using MQL5 test.mq5.
Conclusion
We have conducted some interesting experiments that go against the paradigm of correct MQL5 access to indicators. As a result, we have learned more about the internal mechanism of processing the handles inside the MQL5 core:
- about the handle counter;
- about caching and handle management.
The results of testing various methods of accessing the indicators showed that MQL5 access style is much faster than any MQL4 styles (both with and without handle caching).
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/4388
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use