Сравниваем скорость самокэширующихся индикаторов
Введение
Предположим, что нам вдруг наскучил классический MQL5-доступ к индикаторам и захотелось сравнить скорость доступа в сравнении с альтернативными вариантами. Например, сравним его с доступом к индикаторам в стиле MQL4 без кэширования и с кэшированием. Идеи с доступом в MQL4-стиле были взяты из статьи LifeHack для трейдера: готовим фастфуд из индикаторов и дополнены.
Исследуем MQL5-нумерацию хэндлов индикаторов
Есть предположение, что нумерация хэндлов индикаторов в терминале сквозная и начинается с нуля. Для проверки этой гипотезы создадим небольшой советник "iMACD and IndicatorRelease.mq5" — он создаёт несколько хэндлов индикаторов, сразу распечатывает их и периодически в 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)); } //+------------------------------------------------------------------+
Эксперимент 1
Исходные данные: в терминале открыты графики AUDJPY M15, USDJPY M15 и EURUSD M15, на которых нет ни индикаторов, ни советников. Параметр Count MACD indicators советника iMACD and IndicatorRelease.mq5 равен 6.
Сразу после перезапуска терминала прикрепляем советник iMACD and IndicatorRelease.mq5 на первый график AUDJPY, M15 (ChartID 131571247244850509):
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)
Видно, что нумерация хэндлов начинается не с 0, а с 10.
Эксперимент 2
Исходные данные: на первом графике (AUDJPY M15) прикреплён советник iMACD and IndicatorRelease.mq5, параметр Count MACD indicators равен 6.
Прикрепляем советник iMACD and IndicatorRelease.mq5 на второй график 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)
Видно, что нумерация хэндлов на графике (USDJPY M15) также начинается не с 0, а с 10.
Вывод: нумерация хэндлов индикаторов в терминале (та, что отдаётся пользователю) НЕ сквозная и начинается НЕ с нуля.
Эксперимент 3
Два одинаковых графика AUDJPY, M15 (ChartID 131571247244850509) и AUDJPY, M15 (ChartID 131571247244850510). На каждый прикреплен советник iMACD and IndicatorRelease.mq5 с параметром Count MACD indicators, равным 6.
Несквозная нумерация создаваемых хэндлов индикаторов подтверждает, что MQL5 ведёт для них свой внутренний учёт (счётчик для каждого уникального хендла). Чтобы в этом точно удостовериться, закомментируем приращение периода:
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
Таким образом мы пытаемся создать несколько хэндлов индикатора MACD с абсолютно одинаковыми настройками.
Удаляем с графиков советники, оставшиеся после экспериментов №№ 1 и 2 и прикрепляем советник iMACD and IndicatorRelease.mq5 на первый график 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)
Видим, что в ответ на создание абсолютно одинаковых индикаторов был возвращен один и тот же хэндл.
Прикрепим советник iMACD and IndicatorRelease.mq5 (тоже с закомментированным приращением периода) на второй график 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)
Снова видим, что возвращается один и тот же хэндл. Но возникает вопрос: хэндлы "10" на первом и втором графике — это один и тот же хэндл или два разных? Чтобы это проверить, удалим советник с графиков (напомню, что советник в OnDeinit() обходит массив хэндлов и удаляет каждый при помощи 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
Результат получился ожидаемый, если учесть раздел документации Выполнение программ:
Эксперт выполняется в собственном потоке, сколько экспертов — столько потоков выполнения для них
То есть если два эксперта на одинаковых графиках (одинаковые символ и таймфрейм) создают индикаторы с одинаковыми входными параметрами — MQL5 в своём внутреннем учёте будет идентифицировать их как два разных хэндла.
Общий вывод применительно к созданию индикаторов в экспертах
Нумерация хэндлов индикаторов в терминале (та, что отдаётся пользователю) НЕ сквозная и начинается НЕ с нуля, при этом MQL5 в своём внутреннем учёте хэндлов учитывает:
- функцию технического индикатора (iMA, iAC, iMACD, iIchimoku и т.п);
- входные параметры индикатора;
- символ, на котором создаётся индикатор;
- таймфрейм, на котором создаётся индикатор;
- ChartID графика, на котором работает эксперт.
Есть ли смысл в кэшировании хэндлов?
Исходные данные (таймфрейм, символ, тестируемый промежуток времени и тип генерации тиков) будут такими:
Рис. 1. Настройки
Тесты с доступом к индикаторам в MQL4-стиле (с кэшированием хэндлов и без него) выполняются при помощи советника Cache test.mq5, а тесты с доступом в MQL5-стиле — при помощи советника 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:
Рис. 2. "MQL5 test.mq5". Девять индикаторов
Параметры советника Cache test.mq5:
- Use Timer ("0" -> off timer) — использовать таймер (значение 0 — таймер не используется).
- Use indicator ("false" -> 9 indicators, "true" - 1 indicator) — количество отпрашиваемых индикаторов (1 или 9).
Рис. 3. "Cache test.mq5". Без таймера, с девятью индикаторами
Для замера "MQL4-стиль без кэширования хендлов" используются файлы IndicatorsMQL4.mq, который подключается при помощи "SimpleCallMQL4.mqh (см. статью LifeHack для трейдера: замешиваем ForEach на дефайнах (#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
Для замера "MQL4-стиль с кэшированием хэндлов" в файл IndicatorsMQL4.mqh добавлен код кэширования хэндлов из поста #113 (только для MACD, остальные функции удалены). Файл сохранён под новым именем IndicatorsMQL4Caching.mqh — он подключается при помощи файла 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
Результаты сравнения стилей доступа к девяти индикаторам (настройки даны на рис. 1):
Рис. 4. Затраты времени на доступ к девяти индикаторам
При сравнении результатов прошу учесть, что тестовый советник многократно усложнил задачу:
- данные получаем одновременно с ДЕВЯТИ индикаторов;
- обращаемся к индикаторам НА КАЖДОМ тике;
- таймфрейм M1 — при этом было сгенерировано 26169180 тиков и 370355 баров.
А теперь проведем тест, когда вызывается только один индикатор (у обоих советников: MQL5 test.mq5 и Cache test.mq5 значение параметра Use indicator... "true", у Cache test.mq5 значение параметра Use Timer "0")
Рис. 5. Затраты времени на доступ к одному индикатору
Вывод
MQL4-стиль с кэшированием хэндлов даёт выигрыш по сравнению с MQL4-стилем без кэширования, но при этом MQL4-стиль всухую проигрывает MQL5-стилю.
Отсутствие контроля валидности хэндла
А теперь нужно сказать об огромной опасности применения кэширования хэндлов: нигде нет контроля существования хэндла в пользовательском кэше. То есть, никак не обрабатывается ситуация, когда хэндл индикатора удален.
Представим ситуацию: работаем с индикаторами в стиле MQL4 и кэшируем хэндлы. После первого же обращения из советника:
double macd_main_30=iMACD(NULL,0,30,26,9,PRICE_CLOSE,MODE_MAIN,0);
хэндл сохранился в пользовательском кэше (это может быть массив структур или массив строк). После этого все последующие обращения из советника
double macd_main_30=iMACD(NULL,0,30,26,9,PRICE_CLOSE,MODE_MAIN,0);
не будут пропускаться к ядру MQL5, а будут возвращаться значения индикатора по хэндлу, взятому из кэша. И вот теперь в OnTimer() удаляем хэндл — допустим, мы знаем, что он равен "10". Для теста используем файл Cache test.mq5, в который нужно подключить файл 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
Обязательно выставим таймер (в этом примере таймер выставляется на шесть секунд, мы получаем доступ к одному индикатору)
Рис. 6. Настойки теста с удалением хэндла
После первого же входа OnTimer()
OnTimer, IndicatorRelease(10)=true iMACD: CopyBuffer error=4807 iMACD: CopyBuffer error=4807 iMACD: CopyBuffer error=4807 iMACD: CopyBuffer error=4807
получаем ошибку 4807:
ERR_INDICATOR_WRONG_HANDLE | 4807 | Ошибочный хэндл индикатора |
Это означает, что нет контроля валидности хэндла индикатора.
Кэшируем хэндлы индикаторов. Как это устроено
Общий принцип работы с кэшированием хэндлов индикаторов таков:
- создаётся пользовательский кэш хэндлов;
- при запросе данных с индикатора проверяем, создавался ли уже ранее такой хэндл при запрошенных настройках (символ, таймфрейм, период усреднения и тому подобное):
- если он уже есть в пользовательском кэше — тогда возвращаем по нему данные с индикатора;
- если такого хэндла ещё не создавалось, то создаём его, заносим в кэш и возвращаем по нему данные с индикатора.
Вариант 1: массив структур
Реализация происходит в файле IndicatorsMQL4Caching.mqh (подключается к эксперту Cache test.mq5 при помощи SimpleCallMQL4Caching.mqh).
В эксперте Cache test.mq5 подключаем файл 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
Сначала продемонстрирую большой блок кода, который вставляется в файл и в функцию iMACD:
... //+------------------------------------------------------------------+ //| 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) ...
Опишем его работу. Сначала в советнике идёт запрос на получение данных с индикатора MACD:
double macd_main_30=iMACD(NULL,0,30,26,9,PRICE_CLOSE,MODE_MAIN,0);
Затем мы попадаем в функцию iMACD и переходим в 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[] с типом SMacd (он создается при первом входе и не пересоздается при последующих входах) и создаётся объект Inputs с типом SMacd, который сразу инициализируется параметрами.
После этого передаём по ссылкам массив Handles[] и объект Inputs в функцию GetHandle (обратите внимание, не в SHandle::GetHandle и не в 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()); }
В этой функции или возвращаем найденный хэндл индикатора в массиве или, если хэндл не найден — то получаем его в SHandle::GetHandle.
Но поскольку это первое обращение и такого хэндла ещё нет,
//+------------------------------------------------------------------+ //| SHandle::GetHandle | //+------------------------------------------------------------------+ int GetHandle() { return((this.Handle != INVALID_HANDLE) ? this.Handle : (this.Handle = this.Inputs.GetHandle())); }
то мы создаём его в 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)); }
Вариант 2: массив строк
Реализация происходит в файле IndicatorsMQL4String.mqh (подключается к эксперту Cache test.mq5 при помощи SimpleCallString.mqh).
В эксперте Cache test.mq5 подключаем файл 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
Сразу отмечу, что работа со строками — это жутко дорогое удовольствие в плане быстродействия. Чуть далее мы удостоверимся в этом. Итак, идея сохранения параметров в виде строки выглядит так:
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);
Обращаться к iMACD будем из эксперта с параметрами, представленными выше, на рис. 1.
NN | Код | Время |
---|---|---|
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 |
Тест 1 — это эталонный тест с доступом к индикаторам в MQL4-стиле, без работы со строками. В тесте 2 уже идёт работа со строками, и строка формируется через "+". В тесте 3 строка формируется при помощи StringConcatenate.
По замерам времени видно, что хотя StringConcatenate и даёт выигрыш на 21% по времени в сравнении с тестом 2, но при этом общая производительность всё равно в 2.5 раза меньше, по сравнению с тестом 1.
Поэтому идею сохранения хэндлов в виде строк отбрасываем.
Вариант 3 — класс, кэширующий хэндлы (класс iIndicators.mqh подключается к эксперту Cache test.mq5 при помощи SimpleCallMQL4CachingCiIndicators.mqh).
В эксперте Cache test.mq5 подключаем файл 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>
Для каждого индикатора (внутри соответствующей функции в MQL4-стиле) создаётся статический объект класса CHandle. Он выступает хранилищем объектов класса CiIndicators — класса, который содержит параметры и настройки индикатора.
Рис. 6. Схема
Сам класс CiIndicators строится на пяти переменных private:
//+------------------------------------------------------------------+ //| 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:
Они один в один соответствуют переменным функции IndicatorCreate. Это сделано не зря, так как хэндл индикатора мы получаем именно через IndicatorCreate.
Класс CHandle строится на двух массивах:
//+------------------------------------------------------------------+ //| Class CHandle | //+------------------------------------------------------------------+ class CHandle { private: int m_handle[]; CiIndicators m_indicators[]; public:
Массив m_handle содержит созданные хэндлы индикаторов, а массив m_indicators — это массив класса CiIndicators.
Код работы с классами CiIndicators и CHandle, на примере MACD, выглядит так:
//+------------------------------------------------------------------+ //| 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 класса CHandle — в нём будут храниться созданные хэндлы и параметры индикаторов MACD.
- Создаётся и инициализируется объект MACD_Indicator класса CiIndicators.
- Сам хэндл индикатора создаётся (или отдаётся, если он уже был создан для таких параметров) в функции Handles_MACD::GetHandle.
Время работы класса CiIndicators.mqh с доступом в MQL4-стиле и кэшированием хэндлов заняло 2 минуты и 30 секунд.
Итоговый график скорости доступа к девяти индикаторам
MQL4-стиль без кэширования и с кэшированием проверим при помощи советника "Cache test.mq5", а тесты стандартного MQL5-стиля проведем с использованием советника "MQL5 test.mq5".
Заключение
Мы провели несколько интересных экспериментов, которые идут вразрез с парадигмой правильного MQL5-доступа к индикаторам. В итоге мы подробнее узнали о внутреннем механизме работы с хэндлами внутри ядра MQL5:
- о счётчике хендлов;
- о кэшировании и контроле хендлов.
Результаты тестирования различных способов доступа к индикаторам показали , что MQL5-стиль доступа к индикаторам по скорости на голову опережает любые MQL4-стили (как без кэширования хэндлов, так и с ним).
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
я про эту трактовку и говорю
"Внутренний учет" трактовал, как счетчик. Честно говоря, не понял, зачем в статье первая часть - про хендлы? Вроде, все до этого было разжевано не единожды и подано в более удобных формулировках. Вплоть до причин возможности выполняющихся индикаторов в Терминале, где нет открытых чартов.
это абсолютно неверный вывод. хэндл один и тот же и это подтверждается тем что id совпадает.
первый true результат свидетельствует только о том что уменьшился счетчик ссылок хендла.
Да, это ошибка в статье.
Вообще бросать надо придумывать и писать «в MQL4 стиле». MQL5 быстрее и правильнее. Именно понимание костылей и ограничений MQL4 привело нас к созданию нового языка и отказу от совместимости, чтобы не тянуть плохую схему доступа к данным.
Вообще бросать надо придумывать и писать «в MQL4 стиле». MQL5 быстрее и правильнее. Именно понимание костылей и ограничений MQL4 привело нас к созданию нового языка и отказу от совместимости, чтобы не тянуть плохую схему доступа к данным.
Вперед!
Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий
Обсуждение статьи "Сравниваем скорость самокэширующихся индикаторов"
fxsaber, 2018.03.07 08:17
В общем случае ТС вызывает индикаторы с вычисляемыми (а не жестко заданными) входными параметрами. И тут без MQL4-style+cache никак не обойтись.
Думаю, не сложно в КБ найти MT4-советник подобного уровня. Перевод его в то, что называется в статье MQL5-style, будет невозможен.
Вперед!
Притянуто за уши.
Не доводПритянуто за уши.
Не доводПродолжайте и дальше ратовать за примитивные ТС.