Сравниваем скорость самокэширующихся индикаторов

6 марта 2018, 10:07
Vladimir Karputov
10
2 029

Введение

Предположим, что нам вдруг наскучил классический 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 графика, на котором работает эксперт.

Есть ли смысл в кэшировании хэндлов?

Исходные данные (таймфрейм, символ, тестируемый промежуток времени и тип генерации тиков) будут такими:

Cache test Settings

Рис. 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:

MQL5 test 1

Рис. 2. "MQL5 test.mq5". Девять индикаторов

Параметры советника Cache test.mq5:

  • Use Timer ("0" -> off timer) — использовать таймер (значение 0 — таймер не используется).
  • Use indicator ("false" -> 9 indicators, "true" - 1 indicator) — количество отпрашиваемых индикаторов (1 или 9).

Cache test 1

Рис. 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):

MQL5 vs MQL4 9 indicators

Рис. 4. Затраты времени на доступ к девяти индикаторам

При сравнении результатов прошу учесть, что тестовый советник многократно усложнил задачу:

  • данные получаем одновременно с ДЕВЯТИ индикаторов;
  • обращаемся к индикаторам НА КАЖДОМ тике;
  • таймфрейм M1 — при этом было сгенерировано 26169180 тиков и 370355 баров.

А теперь проведем тест, когда вызывается только один индикатор (у обоих советников: MQL5 test.mq5 и Cache test.mq5 значение параметра Use indicator...  "true", у Cache test.mq5 значение параметра Use Timer "0")

MQL5 vs MQL4 1 indicator

Рис. 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

Обязательно выставим таймер (в этом примере таймер выставляется на шесть секунд, мы получаем доступ к одному индикатору)

Cache test 2

Рис. 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 — класса, который содержит параметры и настройки индикатора.

Scheme

Рис. 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 vs MQL4 9 indicators Summary chart


Заключение

Мы провели несколько интересных экспериментов, которые идут вразрез с парадигмой правильного MQL5-доступа к индикаторам. В итоге мы подробнее узнали о внутреннем механизме работы с хэндлами внутри ядра MQL5:

  • о счётчике хендлов;
  • о кэшировании и контроле хендлов.

Результаты тестирования различных способов доступа к индикаторам показали , что MQL5-стиль доступа к индикаторам по скорости на голову опережает любые MQL4-стили (как без кэширования хэндлов, так и с ним).


Прикрепленные файлы |
MQL5.zip (11.79 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (10)
fxsaber
fxsaber | 7 мар 2018 в 09:28
Комбинатор:
я про эту трактовку и говорю

"Внутренний учет" трактовал, как счетчик. Честно говоря, не понял, зачем в статье первая часть - про хендлы? Вроде, все до этого было разжевано не единожды и подано в более удобных формулировках. Вплоть до причин возможности выполняющихся индикаторов в Терминале, где нет открытых чартов.

MetaQuotes
Renat Fatkhullin | 7 мар 2018 в 10:45
Комбинатор:

это абсолютно неверный вывод. хэндл один и тот же и это подтверждается тем что id совпадает.

первый true результат свидетельствует только о том что уменьшился счетчик ссылок хендла.

Да, это ошибка в статье.


Вообще бросать надо придумывать и писать «в MQL4 стиле». MQL5 быстрее и правильнее. Именно понимание костылей и ограничений MQL4 привело нас к созданию нового языка и отказу от совместимости, чтобы не тянуть плохую схему доступа к данным.

fxsaber
fxsaber | 7 мар 2018 в 10:55
Renat Fatkhullin:

Вообще бросать надо придумывать и писать «в MQL4 стиле». MQL5 быстрее и правильнее. Именно понимание костылей и ограничений MQL4 привело нас к созданию нового языка и отказу от совместимости, чтобы не тянуть плохую схему доступа к данным.

Вперед!

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Обсуждение статьи "Сравниваем скорость самокэширующихся индикаторов"

fxsaber, 2018.03.07 08:17

В общем случае ТС вызывает индикаторы с вычисляемыми (а не жестко заданными) входными параметрами. И тут без MQL4-style+cache никак не обойтись.

Думаю, не сложно в КБ найти MT4-советник подобного уровня. Перевод его в то, что называется в статье MQL5-style, будет невозможен.

MetaQuotes
Renat Fatkhullin | 7 мар 2018 в 10:57
fxsaber:

Вперед!

Притянуто за уши.

Не довод
fxsaber
fxsaber | 7 мар 2018 в 10:57
Renat Fatkhullin:

Притянуто за уши.

Не довод

Продолжайте и дальше ратовать за примитивные ТС.

Глубокие нейросети (Часть VI). Ансамбль нейросетевых классификаторов: bagging Глубокие нейросети (Часть VI). Ансамбль нейросетевых классификаторов: bagging

Рассмотрим методы построения и обучения ансамблей нейросетей со структурой bagging. Определим особенности оптимизации гиперпараметров индивидуальных нейросетевых классификаторов, составляющих ансамбль. Сравним качество оптимизированной нейросети, полученной в предыдущей статье серии, и созданного ансамбля нейросетей. Рассмотрим возможности дальнейшего улучшения качества классификации полученного ансамбля.

Управляемая оптимизация: метод отжига Управляемая оптимизация: метод отжига

В тестере стратегий торговой платформы MetaTrader 5 есть только два варианта оптимизации: полный перебор параметров и генетический алгоритм. В этой статье предложен новый вариант оптимизации торговых стратегий — метод отжига. Приводится алгоритм метода, его реализация и способ подключения к любому советнику. Разработанный алгоритм протестирован на советнике Moving Average.

Как составить Техническое задание при заказе индикатора Как составить Техническое задание при заказе индикатора

Трейдеры ищут закономерности в поведении рынка, указывающие на благоприятные моменты для совершения торговых сделок. Чаще всего первым шагом при разработке торговой системы является создание технического индикатора, который помогает увидеть на графике цен нужную ему информацию. Статья поможет вам составить Техническое задание для заказа индикатора во Фрилансе.

Мультисимвольный график баланса в MetaTrader 5 Мультисимвольный график баланса в MetaTrader 5

В статье продемонстрирован пример MQL-приложения с графическим интерфейсом, в котором отображаются графики мультисимвольного баланса и просадки депозита по результатам последнего теста.