English 中文 Español Deutsch 日本語 Português
LifeHack для трейдера: готовим фастфуд из индикаторов

LifeHack для трейдера: готовим фастфуд из индикаторов

MetaTrader 5Интеграция | 25 января 2018, 08:54
6 281 122
Vladimir Karputov
Vladimir Karputov

Если нельзя, но очень хочется - то можно.  Русская поговорка


Простота vs Надежность

В далеком 2005 году состоялся официальный релиз MetaTrader 4, в котором на смену простому скриптовому языку MQL-II пришел MQL4. Смешно вспоминать, но поначалу трейдеры встретили новый Си-подобный язык в штыки, на форумах было много яростных дебатов и обвинений разработчика MetaQuotes Software Corp. в том, что язык очень сложный и его невозможно освоить.

Теперь, по прошествии 12 лет, современным трейдерам жалобы на сложность MQL4 кажутся непонятными, но история повторяется. Так же, как и тогда, некоторые трейдеры заявляют, что MQL5 сложен для изучения и написания стратегий, не то что MQL4. Значит, общий уровень программирования торговых роботов за эти годы существенно поднялся, и всё это — благодаря тому, что разработчик не побоялся двигаться дальше и дал алготрейдерам еще более мощные инструменты языка C++. Новый MQL5 позволяет программисту максимально подробно проверять результаты всех операций (особенно это важно для обработки торговых транзакций) и аккуратно, по требованию, пользоваться оперативной памятью. В старом MQL4, до того, как его подтянули до уровня MQL5, таких возможностей было значительно меньше. Да и сам синтаксис был менее строгим.

Думается, что пройдет еще немного времени — и споры о том, сложнее язык MQL5 или нет, также станут достоянием истории. Но поскольку еще многие трейдеры помнят "MQL4, милый MQL4", то попробуем для них показать, как могут выглядеть знакомые им MQL4-функции, реализованные на MQL5.

Если вы переходите на MQL5 только сейчас, то эта статья для вас: с одной стороны, доступ к данным индикаторов и к сериям выполнен в привычном вам MQL4-стиле, с другой — вся реализация этой простоты написана на MQL5. Абсолютно все функции максимально понятны и отлично подходят для пошаговой отладки.


1. Можно ли в MQL5 работать с индикаторами в стиле MQL4?

Главное отличие в работе с индикаторами состоит в том, что в MQL4 строка обращения за данными индикатора — это, по сути, команда на создание индикатора ( iMACD(NULL,0,12,26,9,PRICE_CLOSE ) и сразу же запрос данных с нужного индикаторного буфера ( MODE_MAIN ) и нужного индекса ( 1 ).

//+------------------------------------------------------------------+
//|                                                        iMACd.mq4 |
//|                              Copyright © 2018, Vladimir Karputov |
//|                                           http://wmua.ru/slesar/ |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2018, Vladimir Karputov"
#property link      "http://wmua.ru/slesar/"
#property version   "1.00"
#property strict
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   double macd_main_1=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1);
  }
//+------------------------------------------------------------------+

Итого всего одна строка — всего один шаг.

В MQL5 аналог этого кода содержит несколько шагов:

  • объявление переменной, в которой будет храниться хэндл индикатора;
  • создание и проверка хэндла индикатора;
  • отдельная функция, которая выдаёт значение индикатора.
//+------------------------------------------------------------------+
//|                                                        iMACD.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"

int    handle_iMACD;                         // variable for storing the handle of the iMACD indicator
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create handle of the indicator iMACD
   handle_iMACD=iMACD(Symbol(),Period(),12,26,9,PRICE_CLOSE);
//--- if the handle is not created 
   if(handle_iMACD==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);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   double macd_main_1=iMACDGet(MAIN_LINE,1);
  }
//+------------------------------------------------------------------+
//| Get value of buffers for the iMACD                               |
//|  the buffer numbers are the following:                           |
//|   0 - MAIN_LINE, 1 - SIGNAL_LINE                                 |
//+------------------------------------------------------------------+
double iMACDGet(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]);
  }
//+------------------------------------------------------------------+

Перепишем этот же код в MQL4-стиле.

Создание хэндла индикатора и получение данных с индикатора пропишем в одной функции:

//+------------------------------------------------------------------+
//| iMACD function in MQL4 notation                                  |
//+------------------------------------------------------------------+
double iMACD(
             string              symbol,              // symbol name 
             ENUM_TIMEFRAMES     period,              // period 
             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=NULL;
//---
   int handle=iMACD(symbol,period,fast_ema_period,slow_ema_period,signal_period,
                    applied_price);
   double val[1];
   int copied=CopyBuffer(handle,buffer,shift,1,val);
   if(copied>0)
      result=val[0];
   return(result);
  }

А сейчас ВНИМАНИЕ! Написав эту функцию, мы будем создавать хэндл индикатора НА КАЖДОМ тике. Но ведь такое "творчество" документация не рекомендует. Вот что говорит по этому поводу справка Функции для работы с техническими индикаторами:

Нельзя обратиться к данным индикатора сразу после его создания, так как на расчет значений индикатора требуется некоторое время. Поэтому создавать хэндлы индикаторов лучше всего в OnInit().

Так почему же этот код работает и не пожирает память? Ответ находится в том же разделе, ниже:

Примечание. Многократное обращение к функции индикатора с одними и теми же параметрами в пределах одной MQL5-программы не приводит к многократному увеличению счетчика ссылок, счетчик будет увеличен всего один раз на 1. Однако рекомендуется получать хэндлы индикаторов в функции OnInit() или в конструкторе класса, с последующим использованием полученных хэндлов в остальных функциях. Счетчик ссылок уменьшается при деинициализации mql5-программы.

Другими словами, MQL5 спроектирован оптимально: он сам контролирует создание хэндлов и не позволит многократно создавать один и тот же индикатор с одними и теми же параметрами. В случае повторной попытки создания хэндла-копии индикатора вам просто вернётся хэндл ранее созданного индикатора с соответствующими настройками. И тем не менее, всё равно рекомендуется получать хэндлы один раз в OnInit() — почему именно, мы увидим чуть ниже.

Обратите внимание: проверки на корректность созданного хэндла нет.

Теперь код, получающий значения iMACD индикатора, будет выглядеть так:

//+------------------------------------------------------------------+
//|                                           MACD MQL4 style EA.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"

#define MODE_MAIN 0
#define MODE_SIGNAL 1
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   double macd_main_1=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1);
  }
//+------------------------------------------------------------------+
//| iMACD function in MQL4 notation                                  |
//+------------------------------------------------------------------+
double iMACD(
             string              symbol,              // symbol name 
             ENUM_TIMEFRAMES     period,              // period 
             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=NULL;
//---
   int handle=iMACD(symbol,period,fast_ema_period,slow_ema_period,signal_period,
                    applied_price);
   double val[1];
   int copied=CopyBuffer(handle,buffer,shift,1,val);
   if(copied>0)
      result=val[0];
   return(result);
  }
//+------------------------------------------------------------------+

ВНИМАНИЕ: стремление обращаться к индикаторам в MQL4-стиле лишает нас варианта с проверкой возвращаемого значения, поскольку все функции в MQL4 стиле возвращают ТОЛЬКО значения double . Возможное решение покажем в разделе 1.1.

Выглядит всё пока довольно громоздко, поэтому блок define'ов и функцию double iMACD() вынесем в отдельный подключаемый файл "IndicatorsMQL5.mqh", который разместим в отдельной папке "[data folder]\MQL5\Include\SimpleCall". Тогда код становится совсем коротким. Обратите внимание:  мы подключаем файл "IndicatorsMQL5.mqh". Это означает, что названия индикаторных линий при обращении к MACD необходимо передавать в виде MQL5 MAIN_LINE, а не в виде MQL4 MODE_MAIN:

//+------------------------------------------------------------------+
//|                                     MACD MQL4 style EA short.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"
#include <SimpleCall\IndicatorsMQL5.mqh>
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   double macd_main_1=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MAIN_LINE,1);
   Comment("MACD, main buffer, index 1: ",DoubleToString(macd_main_1,Digits()+1));
  }
//+------------------------------------------------------------------+

"Comment" я ввёл исключительно для проверки. Сверить работу можно в тестере, если запустить "MACD MQL4 style EA short.mq5" в визуальном режиме и поместить курсор на бар с индексом #1:

"MACD MQL4 style EA short.mh5" in tester

Рис. 1. "MACD MQL4 style EA short.mh5" in tester

1.1. Тонкости при работе с "IndicatorsXXXX.mqh"


Обработка ошибки в возвращаемом значении

Все индикаторы передают свои данные как double. В этом и заключается проблема отправки сообщения пользователю в случае, если вдруг не удалось получить данные с индикатора. Такая ситуация может возникнуть при отказе в создании хэндла индикатора (например, если был задан несуществующий символ) или при ошибке копирования при вызове CopyBuffer.

Просто передать "0.0" в случае ошибки — не выход, так как для многих индикаторов число "0.0" — это вполне нормальный показатель (например у MACD). Возвращать константу EMPTY_VALUE (которая, кстати, имеет значение DBL_MAX) — тоже не вариант, так как индикатор Fractals заполняет индексы буфера значениями EMPTY_VALUE, а значит, для него это не ошибка.

Остаётся вариант передавать "не число" — NaN. Для этого на глобальном уровне объявляется переменная "NaN", которая как раз и инициализируется "не числом":

double NaN=double("nan");
//+------------------------------------------------------------------+
//| iAC function in MQL4 notation                                    |
//+------------------------------------------------------------------+
double   iAC(
             string                       symbol,              // symbol name 
             ENUM_TIMEFRAMES              timeframe,           // timeframe 
             int                          shift                // shift
             )
  {
   double result=NaN;
//---
   int handle=iAC(symbol,timeframe);
   if(handle==INVALID_HANDLE)
     {
      Print(__FUNCTION__,": INVALID_HANDLE error=",GetLastError());
      return(result);
     }
   double val[1];
   int copied=CopyBuffer(handle,0,shift,1,val);
   if(copied>0)
      result=val[0];
   else
      Print(__FUNCTION__,": CopyBuffer error=",GetLastError());
   return(result);
  }

Преимущество такого подхода еще и в том, что теперь в случае ошибки вернется NaN, и результатом его сравнения с любым числом будет false.

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- пример сравнения NaN
   double NaN=double("nan");
   double a=10.3;
   double b=-5;
   double otherNaN=double("nan");
   Print("NaN>10.3=",NaN>a);
   Print("NaN<-5=",NaN<b);
   Print("(NaN==0)=",NaN==0);
   Print("(NaN==NaN)=",NaN==otherNaN);
//--- результат
   NaN>10.3=false
   NaN<-5=false
   (NaN==0)=false
   (NaN==NaN)=false
//---
  }

Поэтому, если мы хотим использовать эти функции в стиле MQL4, то проводить торговые операции (как и любые другие важные действия) нужно, только если результатом сравнения будет true.  Хотя лично я в таком случае настаиваю на том, чтобы проверить возвращаемое значение функцией MathIsValidNumber

Идентификаторы линий индикаторов в MQL4 и MQL5

Существует проблема совместимости в части значений констант, описывающих линии индикаторов. Например, возьмём iAlligator:

  • MQL4: 1 - MODE_GATORJAW, 2 - MODE_GATORTEETH, 3 - MODE_GATORLIPS
  • MQL5: 0 - GATORJAW_LINE,   1 - GATORTEETH_LINE,   2 - GATORLIPS_LINE 

Проблема в том, что в конечном счёте в функции "IndicatorsXXXX.mqh" линия индикатора приходит как число. И если это число, например, равно 1, то никто не сможет сказать, что имел в виду пользователь: то ли он работал в стиле MQL4 (и имел в виду 1 - MODE_GATORJAW), то ли он работал в стиле MQL5 (и имел в виду совершенно другую индикаторную линию 1 - GATORTEETH_LINE).

В связи с этим решено создать два включаемых файла — практически близнецов: "IndicatorsMQL4.mqh" и "IndicatorsMQL5.mqh". Их различие в том, что файл "IndicatorsMQL4.mqh" понимает линии индикаторов ТОЛЬКО в MQL4-стиле, а файл "IndicatorsMQL5.mqh" — ТОЛЬКО в MQL5-стиле. Причём в "IndicatorsMQL4.mqh" преобразование линии индикатора во входном параметре производится непосредственно внутри функций iADX, iAlligator ... — вынести эти преобразования в #define  нельзя.

Поясню причину такого запрета на примере iBands и iEnvelopes:

//+------------------------------------------------------------------+
//| iBands function in MQL4 notation                                 |
//|   The buffer numbers are the following:                          |
//|      MQL4 0 - MODE_MAIN, 1 - MODE_UPPER, 2 - MODE_LOWER          |
//|      MQL5 0 - BASE_LINE, 1 - UPPER_BAND, 2 - LOWER_BAND          |
//+------------------------------------------------------------------+
double   iBands(
...
//+------------------------------------------------------------------+
//| iEnvelopes function in MQL4 notation                             |
//|   The buffer numbers are the following:                          |
//|      MQL4 0 - MODE_MAIN,  1 - MODE_UPPER, 2 - MODE_LOWER         | ???
//|      MQL5 0 - UPPER_LINE, 1 - LOWER_LINE,        -/-             |
//+------------------------------------------------------------------+
double   iEnvelopes(

В MQL4 MODE_UPPER для индикатора Bands преобразовывается в 1, а для индикатора Envelopes — в 0.

2. Какова ситуация с расходом памяти, если на каждом тике работать с индикаторами в MQL4-стиле?

Сравним расход памяти двух советников: "iMACD.mq5" — советника с правильным доступом к индикаторам, и советника "MACD MQL4 style EA short.mq5" — с доступом к индикаторам в MQL4-стиле. В настройках терминала максимум баров в окне установлен в значение "100 000". Создадим два профиля из 14 графиков:

  • профиль "iMACd" — на 13 графиках прикреплён советник "iMACd.mq5", все 13 графиков имеют таймфрейм М30;
  • профиль "MACD MQL4 style EA short" — на 13 графиках прикреплён советник "MACD MQL4 style EA short.mq5".

На четырнадцатом графике будет индикатор "Terminal memory used.mq5", который каждые 10 секунд распечатывает идентификатор TERMINAL_MEMORY_USED.

Сравнивать будем два значения: сколько памяти (ОЗУ) потребляет терминал (данные из диспетчера задача) и распечатанный идентификатор TERMINAL_MEMORY_USED. Наблюдение будет вестись 10 минут — если память начнёт пожираться, мы это увидим. Главное условие: после запуска терминала ничего в нем не делать — не открывать новые вкладки, не читать чат.

Профиль Диспетчер задач TERMINAL_MEMORY_USED Диспетчер задач (через 10 минут) TERMINAL_MEMORY_USED (через 10 минут)
iMACd 279.7 Мб 745 Мб 279.7 Мб 745 Мб
MACD MQL4 style EA short 279.9 Мб 745 Мб 280.0 Мб 745 Мб

Теперь видоизменим тест: после 10 минут работы переключим таймфрейм всех 13 графиков на таймфрейм H1.

Профиль Диспетчер задач TERMINAL_MEMORY_USED Диспетчер задач (через 10 минут) TERMINAL_MEMORY_USED (через 10 минут)
iMACd 398.0 Мб 869 Мб 398.3 Мб 869 Мб
MACD MQL4 style EA short 319.2 Мб 874 Мб 330.5 Мб 874 Мб

Итоговая таблица, для наглядности использования памяти:

Профиль Диспетчер задач
(M30), Mb
TERMINAL_MEMORY_USED
(M30), Mb
Диспетчер задач
(H1), Mb
TERMINAL_MEMORY_USED
(H1), Mb

старт ч-з 10 мин. старт ч-з 10 мин. старт ч-з 10 мин. старт ч-з 10 мин.
iMACd 279.7 279.7 745 745 398.0 869 398.3 869
MACD MQL4 style EA short 279.9 280.0 745 745 319.2 874 330.5 874

3. Новая жизнь советника MACD Sample.mq4

Проверим скорость выполнения, расход памяти и соответствие торговли советника [data folder]\MQL4\Experts\MACD Sample.mq4 (который напишем в на MQL5, но в стиле MQL4 — как "MACD MQL4 style EA short.mq5") и советника [data folder]\MQL5\Experts\Examples\MACD\MACD Sample.mq5.

3.1. Изменим советник "MACD Sample.mq5" — за один раз будем получать только одно значение

"MACD Sample.mq5" из стандартной поставки получает сразу по два значения индикатора:

//+------------------------------------------------------------------+
//| main function returns true if any position processed             |
//+------------------------------------------------------------------+
bool CSampleExpert::Processing(void)
  {
//--- refresh rates
   if(!m_symbol.RefreshRates())
      return(false);
//--- refresh indicators
   if(BarsCalculated(m_handle_macd)<2 || BarsCalculated(m_handle_ema)<2)
      return(false);
   if(CopyBuffer(m_handle_macd,0,0,2,m_buff_MACD_main)  !=2 ||
      CopyBuffer(m_handle_macd,1,0,2,m_buff_MACD_signal)!=2 ||
      CopyBuffer(m_handle_ema,0,0,2,m_buff_EMA)         !=2)
      return(false);
//   m_indicators.Refresh();
//--- to simplify the coding and speed up access
//--- data are put into internal variables
   m_macd_current   =m_buff_MACD_main[0];
   m_macd_previous  =m_buff_MACD_main[1];
   m_signal_current =m_buff_MACD_signal[0];
   m_signal_previous=m_buff_MACD_signal[1];
   m_ema_current    =m_buff_EMA[0];
   m_ema_previous   =m_buff_EMA[1];

После этого переменным присваиваются данные из массивов размерностью "2". Почему сделано именно так? Очевидно, что при копировании хоть по одному, хоть по два значения за один раз мы всё равно будем использовать CopyBuffer. Но при копировании по два значения за один раз экономится одна операция записи в массив.

Однако советник "MACD Sample.mq4" за один раз получает по одному значению индикатора:

//--- to simplify the coding and speed up access data are put into internal variables
   MacdCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,0);
   MacdPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1);
   SignalCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,0);
   SignalPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,1);
   MaCurrent=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,0);
   MaPrevious=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,1);

Два раза опрашивается главная линия MACD, два раза — сигнальная линия MACD и два раза — Moving Average. Поэтому и советник "MACD Sample.mq5" нужно привести к такому же виду. Назовём эту версию советника "MACD Sample One value at a time.mq5". Вот его изменение, в котором за один раз получаем по одному значению:

//--- refresh indicators
   if(BarsCalculated(m_handle_macd)<2 || BarsCalculated(m_handle_ema)<2)
      return(false);
//   if(CopyBuffer(m_handle_macd,0,0,2,m_buff_MACD_main)  !=2 ||
//      CopyBuffer(m_handle_macd,1,0,2,m_buff_MACD_signal)!=2 ||
//      CopyBuffer(m_handle_ema,0,0,2,m_buff_EMA)         !=2)
//      return(false);
//   m_indicators.Refresh();
//--- to simplify the coding and speed up access
//--- data are put into internal variables
   CopyBuffer(m_handle_macd,0,0,1,m_buff_MACD_main);
   m_macd_current=m_buff_MACD_main[0];
   CopyBuffer(m_handle_macd,0,1,1,m_buff_MACD_main);
   m_macd_previous=m_buff_MACD_main[0];
   CopyBuffer(m_handle_macd,1,0,1,m_buff_MACD_signal);
   m_signal_current=m_buff_MACD_signal[0];
   CopyBuffer(m_handle_macd,1,1,1,m_buff_MACD_signal);
   m_signal_previous=m_buff_MACD_signal[0];
   CopyBuffer(m_handle_ema,0,0,1,m_buff_EMA);
   m_ema_current=m_buff_EMA[0];
   CopyBuffer(m_handle_ema,0,1,1,m_buff_EMA);
   m_ema_previous=m_buff_EMA[0];

Этот код сохранён в советнике "MACD Sample One value at a time.mq5", прикрепленном в конце статьи.

3.2. Преобразуем советник "MACD Sample.mq4" в MQL5-код

Чтобы в советнике можно было обращаться к индикаторам в MQL4-стиле, а также для работы с позициями и для возможности торговли подключим файл "IndicatorsMQL4.mqh" (напоминаю, что этот файл при работе с индикаторами понимает только MQL4-названия индикаторных линий) и торговые классы CPositionInfoCTradeCSymbolInfo и CAccountInfo. Также, для правильного обращения к индикаторам в "IndicatorsMQL4.mqh", в советник необходимо добавить блок defin'ов — названий индикаторных линий:

#property description " and the indicators are accessed in the style of MQL4"
#define MODE_MAIN    0
#define MODE_SIGNAL  1 
#include <SimpleCall\IndicatorsMQL4.mqh>
//---
#include <Trade\PositionInfo.mqh>
#include <Trade\Trade.mqh>
#include <Trade\SymbolInfo.mqh>  
#include <Trade\AccountInfo.mqh>
CPositionInfo  m_position;                   // trade position object
CTrade         m_trade;                      // trading object
CSymbolInfo    m_symbol;                     // symbol info object
CAccountInfo   m_account;                    // account info wrapper
//---
input double TakeProfit    =50;

Также для подстройки под трёх- или пятизначные котировки нужен специальный множитель:

input double MACDCloseLevel=2;
input int    MATrendPeriod =26;
//---
double       m_adjusted_point;               // point value adjusted for 3 or 5 points
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+

Для получения текущих цен я использую объект m_symbol торгового класса CSymbolInfo:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   if(!m_symbol.Name(Symbol())) // sets symbol name
      return(INIT_FAILED);
   RefreshRates();

Метод RefreshRates() обновляет цены и следит за тем, чтобы не было цен со значением "0.0":

//+------------------------------------------------------------------+
//| Refreshes the symbol quotes data                                 |
//+------------------------------------------------------------------+
bool RefreshRates(void)
  {
//--- refresh rates
   if(!m_symbol.RefreshRates())
     {
      Print("RefreshRates error");
      return(false);
     }
//--- protection against the return value of "zero"
   if(m_symbol.Ask()==0 || m_symbol.Bid()==0)
      return(false);
//---
   return(true);
  }

Инициализация множителя m_adjusted_point производится в OnInit(), после инициализации объекта m_symbol:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   if(!m_symbol.Name(Symbol())) // sets symbol name
      return(INIT_FAILED);
   RefreshRates();
   //--- tuning for 3 or 5 digits
   int digits_adjust=1;
   if(m_symbol.Digits()==3 || m_symbol.Digits()==5)
      digits_adjust=10;
   m_adjusted_point=m_symbol.Point()*digits_adjust;
//---
   return(INIT_SUCCEEDED);
  }

В OnTick(), благодаря подключенному файлу "IndicatorsMQL4Style.mqh", обращаемся к индикаторам в MQL4-стиле:

   if(!RefreshRates())
      return;
//--- to simplify the coding and speed up access data are put into internal variables
   MacdCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MAIN_LINE,0);
   MacdPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MAIN_LINE,1);
   SignalCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,SIGNAL_LINE,0);
   SignalPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,SIGNAL_LINE,1);
   MaCurrent=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,0);
   MaPrevious=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,1);

3.2.1. Работа с позициями

Для максимального соответствия определяем отсутствие позиций как

   total=PositionsTotal();
   if(total<1)
     {

Хотя такой подход не совсем правильный, поскольку в нем не учитывается наличие позиций по другим символам и/или с другими идентификаторами (мэджиками).

3.2.2. Позиции Buy открываются при помощи метода Buy торгового класса CTrade, а корректность выполнения проверяем методом ResultDeal этого же класса. ResultDeal возвращает тикет сделки, если она совершена.

      //--- check for long position (BUY) possibility
      if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious && 
         MathAbs(MacdCurrent)>(MACDOpenLevel*m_adjusted_point) && MaCurrent>MaPrevious)
        {
         m_trade.Buy(Lots,m_symbol.Name(),m_symbol.Ask(),
                     0.0,
                     m_symbol.NormalizePrice(m_symbol.Ask()+TakeProfit*m_adjusted_point),
                     "macd sample");
         if(m_trade.ResultDeal()!=0)
            Print("BUY position opened : ",m_trade.ResultPrice());
         else
            Print("Error opening BUY position : ",m_trade.ResultRetcodeDescription());
         return;
        }

Обратите внимание, что цена в торговом запросе нормализируется методом NormalizePrice торгового класса CSymbolInfo. Этот метод позволяет учитывать квантование: минимальное изменение цены и количество знаков после десятичной точки. 

Для открытия позиции Sell используем аналогичные методы.

3.2.3. Блок обхода позиций: закрытие или модификация.

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

   for(int i=PositionsTotal()-1;i>=0;i--)
      if(m_position.SelectByIndex(i)) // selects the position by index for further access to its properties

Закрытие позиции происходит методом PositionClose, а модификация — PositionModify. Обратите внимание, что при модификации снова используется метод нормализации цен NormalizePrice торгового класса CSymbolInfo.

Весь блок обхода позиций:

//--- it is important to enter the market correctly, but it is more important to exit it correctly...   
   for(int i=PositionsTotal()-1;i>=0;i--)
      if(m_position.SelectByIndex(i)) // selects the position by index for further access to its properties
         if(m_position.Symbol()==m_symbol.Name())
           {
            //--- long position is opened
            if(m_position.PositionType()==POSITION_TYPE_BUY)
              {
               //--- should it be closed?
               if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious && 
                  MacdCurrent>(MACDCloseLevel*m_adjusted_point))
                 {
                  //--- close position and exit
                  if(!m_trade.PositionClose(m_position.Ticket()))
                     Print("PositionClose error ",m_trade.ResultRetcodeDescription());
                  return;
                 }
               //--- check for trailing stop
               if(TrailingStop>0)
                 {
                  if(m_position.PriceCurrent()-m_position.PriceOpen()>m_adjusted_point*TrailingStop)
                    {
                     if(m_position.StopLoss()<m_symbol.Bid()-m_adjusted_point*TrailingStop)
                       {
                        //--- modify position and exit
                        if(!m_trade.PositionModify(m_position.Ticket(),
                           m_symbol.NormalizePrice(m_position.PriceCurrent()-m_adjusted_point*TrailingStop),
                           m_position.TakeProfit()))
                           Print("PositionModify error ",m_trade.ResultRetcodeDescription());
                        return;
                       }
                    }
                 }
              }

            if(m_position.PositionType()==POSITION_TYPE_SELL)
              {
               //--- should it be closed?
               if(MacdCurrent<0 && MacdCurrent>SignalCurrent && 
                  MacdPrevious<SignalPrevious && MathAbs(MacdCurrent)>(MACDCloseLevel*m_adjusted_point))
                 {
                  //--- close position and exit
                  if(!m_trade.PositionClose(m_position.Ticket()))
                     Print("PositionClose error ",m_trade.ResultRetcodeDescription());
                  return;
                 }
               //--- check for trailing stop
               if(TrailingStop>0)
                 {
                  if((m_position.PriceOpen()-m_position.PriceCurrent())>(m_adjusted_point*TrailingStop))
                    {
                     if((m_position.StopLoss()>(m_symbol.Ask()+m_adjusted_point*TrailingStop)) || (m_position.StopLoss()==0.0))
                       {
                        //--- modify position and exit
                        if(!m_trade.PositionModify(m_position.Ticket(),
                           m_symbol.NormalizePrice(m_symbol.Ask()+m_adjusted_point*TrailingStop),
                           m_position.TakeProfit()))
                           Print("PositionModify error ",m_trade.ResultRetcodeDescription());
                        return;
                       }
                    }
                 }
              }
           }

Это все изменения, итоговый файл "MACD Sample 4 to 5 MQL4 style.mq5" прикреплён в конце этой статьи.

3.3. Сравним скорость выполнения советников на базе MACD

В сравнении будут принимать участие:

  • "MACD Sample.mq5" — советник из стандартной поставки с правильным доступом к индикаторам
  • "MACD Sample One value at a time.mq5" — аналог "MACD Sample.mq5", в котором за один раз получаем по одному значению от индикаторов
  • "MACD Sample 4 to 5 MQL4 style.mq5" — советник MQL4, переписанный на MQL5 c минимальными переделками и с доступом к индикаторам в MQL4-стиле

Тестирование проводилось на USDJPY,M30 c 2017.02.01 по 2018.01.16 на сервере MetaQuotes-Demo. После каждого теста (будь то смена советника или смена режима генерации тиков) терминал перезагружался. Конфигурация компьютера:

Windows 10 (build 16299) x64, IE 11, UAC, Intel Core i3-3120M  @ 2.50GHz, Memory: 4217 / 8077 Mb, Disk: 335 / 464 Gb, GMT+2
№ п/п Советник Каждый тик на основе реальных тиков Все тики OHLC


Время теста Трейдов Сделок Время теста Трейдов Сделок Время теста Трейдов Сделок
 1  MACD Sample.mq5  0:01:19.485  122  244  0:00:53.750  122  244  0:00:03.735  119  238
 2  MACD Sample One value at a time.mq5  0:01:20.344  122  244  0:00:56.297  122  244  0:00:03.687  119  238
 3  MACD Sample 4 to 5 MQL4 style.mq5  0:02:37.422  122  244  0:01:52.171  122  244  0:00:06.312  119  238

Все три советника показали в режиме "Все тики" одинаковые графики:

MACD Sample

Рис. 2. MACD Sample XXXX в тестере стратегий


ВЫВОД: советник "MACD Sample 4 to 5 MQL4 style.mq5", с доступом к индикаторам в MQL4 стиле в два раза проигрывает по скорости аналогичным советникам с правильным доступом к индикаторам.

3.4. Сравним потребление памяти советников на базе MACD

Для этого используются те же 14 графиков, что и в пункте 2. Что будет с расходом памяти, если на каждом тике работать с индикаторами в MQL4-стиле? На первом графике неизменно остаётся индикатор "Terminal memory used.mq5", который каждые 10 секунд распечатывает идентификатор TERMINAL_MEMORY_USED, а на оставшиеся 13 по очереди прикрепляются советники. Перед каждым замером терминал перезагружался.

№ п/п Советник Диспетчер задача, Мб TERMINAL_MEMORY_USED, Мб
 1  MACD Sample.mq5  334.6  813
 2  MACD Sample One value at a time.mq5  335.8  813
 3  MACD Sample 4 to 5 MQL4 style.mq5  342.2  818

ВЫВОД: По расходу памяти советники на базе MACD с правильным доступом к индикаторам и советник на базе MACD с доступом к индикаторам в MQL4 стиле сопоставимы. То есть, память они потребляют примерно одинаково.


4. Новая жизнь советника [data folder]\MQL4\Experts\Moving Average.mq4

Если в главе 3 мы преобразовывали MQL4 в MQL5, то в случае с советником Movinge Average.mq4 я предлагаю просто изменить советник Moving Average.mq5 путём подключения файла "IndicatorsMQL5.mqh"

#property version   "1.00"
#include <SimpleCall\IndicatorsMQL5.mqh>

#include <Trade\Trade.mqh>

и замены CopyBuffer

//--- get current Moving Average 
   double   ma[1];
   if(CopyBuffer(ExtHandle,0,0,1,ma)!=1)
     {
      Print("CopyBuffer from iMA failed, no data");
      return;
     }

на MQL4-стиль обращения к индикаторам:

//--- get Moving Average 
   ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);

Обращение к индикаторам в стиле MQL4 оставляет нам всего одну возможность для проверки итога операции — сравнить полученные данные с нулём. С учётом этого итоговая запись в блоках "CheckForOpen" и "CheckForClose" была такой:

//--- get current Moving Average 
   double   ma[1];
   if(CopyBuffer(ExtHandle,0,0,1,ma)!=1)
     {
      Print("CopyBuffer from iMA failed, no data");
      return;
     }

а станет такой:

//--- get current Moving Average 
   double   ma[1];
   ma[0]=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//if(CopyBuffer(ExtHandle,0,0,1,ma)!=1)
   if(ma[0]==0.0)
     {
      //Print("CopyBuffer from iMA failed, no data");
      Print("Get iMA in MQL4 style failed, no data");
      return;
     }

Это все изменения, которые сохраним в советнике "Moving Average MQL4 style.mq5". Советник прикреплён в конце статьи. Замерим производительность и потребление памяти между стандартным "Moving Average.mq5" и "Moving Average MQL4 style.mq5". 

Напомню, что тесты проводились на оборудовании

Windows 10 (build 16299) x64, IE 11, UAC, Intel Core i3-3120M  @ 2.50GHz, Memory: 4217 / 8077 Mb, Disk: 335 / 464 Gb, GMT+2

 и после каждого теста терминал перезагружался. Тестировались на EURUSD,M15 c 2017.02.01 по 2018.01.16 на сервере MetaQuotes-Demo.

№ п/п Советник Каждый тик на основе реальных тиков Все тики OHLC


Время теста Трейдов Сделок Время теста Трейдов Сделок Время теста Трейдов Сделок
 1  Moving Average.mq5  0:00:33.359  1135  2270  0:00:22.562  1114  2228  0:00:02.531  1114  2228
 2  Moving Average MQL4 style.mq5  0:00:34.984  1135  2270  0:00:23.750  1114  2228  0:00:02.578  1114  2228

ВЫВОД: Вероятно, что в MACD Sample при обращении к индикаторам в MQL4-стиле ядру MQL5 приходилось на каждом тике производить поиск среди двух хэндлов — и именно на такой поиск тратилось время.

В случае же с советником Moving Average при обращении к индикатору в MQL4-стиле ядро MQL5 не тратит время на поиск нужного хэндла, поскольку он единственный. 

Сравним потребление памяти советников на базе Moving Average

Для этого используются те же 14 графиков, что и в пункте 2. На первом графике неизменно остаётся индикатор "Terminal memory used.mq5", который каждые 10 секунд распечатывает идентификатор TERMINAL_MEMORY_USED, а на оставшиеся 13 по очереди прикрепляются советники. Перед каждым замером терминал перезагружался.

№ п/п Советник Диспетчер задача, Мб TERMINAL_MEMORY_USED, Мб
 1  Moving Average.mq5  295.6  771
 2  Moving Average MQL4 style.mq5  283.6  760

ВЫВОД: Потребление памяти практически идентично. Небольшие расхождения можно списать на "внутреннюю жизнь" терминала: обновление новостей и т.п.


5. Аналоги серий iXXXX

Раз уж мы сделали получение значений индикаторов в стиле MQL4, то заодно напишем и функции раздела Доступ к таймсериям и индикаторам. Реализация будет в [data folder]\MQL5\Include\SimpleCall\Series.mqh.

Список функций в "Series.mqh", обеспечивающих доступ к значениям таймсерий, как в MQL4:


Для функций iHighest и iLowest доступны предопределённые идентификаторы серий MODE_OPEN, MODE_LOW, MODE_HIGH, MODE_CLOSE, MODE_VOLUME, MODE_TIME.

Пример реализации функции iClose:

//+------------------------------------------------------------------+
//| iClose function in MQL4 notation                                 |
//+------------------------------------------------------------------+
double   iClose(
                string                    symbol,              // symbol
                ENUM_TIMEFRAMES           timeframe,           // timeframe
                int                       shift                // shift
                )
  {
   double result=0.0;
//---
   double val[1];
   ResetLastError();
   int copied=CopyClose(symbol,timeframe,shift,1,val);
   if(copied>0)
      result=val[0];
   else
      Print(__FUNCTION__,": CopyClose error=",GetLastError());
//---
   return(result);
  }

Значение цены закрытия бара shift получаем при помощи CopyClose — первой формы вызова (обращение по начальной позиции и количеству требуемых элементов):

int  CopyClose( 
   string           symbol_name,       // имя символа 
   ENUM_TIMEFRAMES  timeframe,         // период 
   int              start_pos,         // откуда начнем  
   int              count,             // сколько копируем 
   double           close_array[]      // массив для копирования цен закрытия 
   );


Заключение

Как видим, MQL5 позволяет приверженцам MQL4 получать значения индикаторов и таймсерий в своем любимом стиле. Они говорят, что такой код короче и читается проще. Разработчики платформы, в свою очередь, требуют более тщательной работы с кодом и максимума проверок при вызове функций (и я с ними полностью согласен). Перечислим кратко плюсы и минусы функций, рассмотренных в статье.


Минусы:
  • ограничение в обработке возвращаемой ошибки при доступе к индикаторам;
  • падение скорости тестирования при одновременном доступе более чем к одному индикатору;
  • необходимость правильно указывать линии индикаторов в зависимости от подключения "IndicatorsMQL5.mqh" или "IndicatorsMQL4.mqh".
Плюсы
  • простота написания кода — одна строка вместо нескольких;
  • наглядность и краткость — чем меньше кода, тем проще его понимать.
Тем не менее, я остаюсь приверженцем классического MQL5-подхода в доступе к индикаторам, а в этой статье только лишь протестировал альтернативный вариант.

Прикрепленные файлы |
MQL5.zip (26.02 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (122)
Vasiliy Sokolov
Vasiliy Sokolov | 29 янв. 2018 в 13:22
Обновились блин....
Anatoli Kazharski
Anatoli Kazharski | 29 янв. 2018 в 13:31
Vladimir Karputov
Vladimir Karputov | 29 янв. 2018 в 14:09
Vasiliy Sokolov:

Попытка восстановления:

Выкладываю свою версию измерения. За базовую версию советника был взят MACD Sample One value at a 5. В него были внесены небольшие изменения. Т.к. все значения индикаторов собираются в одном месте было не сложно сделать простую макроподстановку: Выводы думаю очевидны: при вызове индикаторов в MQL4 режиме, скорость ниже на 40%.

Вы можете восстановить (отредактировать) свой пост:

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

Обсуждение статьи "LifeHack для трейдера: готовим фастфуд из индикаторов"

Vasiliy Sokolov, 2018.01.29 10:43

Попытка восстановления:

Выкладываю свою версию измерения. За базовую версию советника был взят MACD Sample One value at a 5. В него были внесены небольшие изменения. Т.к. все значения индикаторов собираются в одном месте было не сложно сделать простую макроподстановку: Выводы думаю очевидны: при вызове индикаторов в MQL4 режиме, скорость ниже на 40%.


- у Вас был красивый код и описание результатов замеров.

Rashid Umarov
Rashid Umarov | 29 янв. 2018 в 14:14
Vladimir Karputov:

- у Вас был красивый код и описание результатов замеров.

Я код посмотреть не успел

fxsaber
fxsaber | 29 янв. 2018 в 14:18

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

Обсуждение статьи "LifeHack для трейдера: готовим фастфуд из индикаторов"

Vasiliy Sokolov, 2018.01.29 10:43

Выкладываю свою версию измерения. За базовую версию советника был взят MACD Sample One value at a 5. В него были внесены небольшие изменения. Т.к. все значения индикаторов собираются в одном месте было не сложно сделать простую макроподстановку: Выводы думаю очевидны: при вызове индикаторов в MQL4 режиме, скорость ниже на 40%.

MQL5-style

i = 0 Pass = 0 OnTester = 7.679 s.: Count = 9986677, 1300517.9 unit/sec, Agent = C:\Program Files\Alpari Limited MT5\Tester\Agent-127.0.0.1-3000 build = 1755
i = 1 Pass = 1 OnTester = 7.645 s.: Count = 9986677, 1306301.8 unit/sec, Agent = C:\Program Files\Alpari Limited MT5\Tester\Agent-127.0.0.1-3000 build = 1755


MQL4-style (без кеша)

i = 0 Pass = 0 OnTester = 14.117 s.: Count = 9986677, 707422.0 unit/sec, Agent = C:\Program Files\Alpari Limited MT5\Tester\Agent-127.0.0.1-3000 build = 1755
i = 1 Pass = 1 OnTester = 14.067 s.: Count = 9986677, 709936.5 unit/sec, Agent = C:\Program Files\Alpari Limited MT5\Tester\Agent-127.0.0.1-3000 build = 1755


MQL4-style (с кешем)

i = 0 Pass = 0 OnTester = 10.077 s.: Count = 9986677, 991036.7 unit/sec, Agent = C:\Program Files\Alpari Limited MT5\Tester\Agent-127.0.0.1-3000 build = 1755
i = 1 Pass = 1 OnTester = 10.104 s.: Count = 9986677, 988388.5 unit/sec, Agent = C:\Program Files\Alpari Limited MT5\Tester\Agent-127.0.0.1-3000 build = 1755

На 32% медленнее кешевый вариант, по сравнению с MQL5-style. Что же касается безкешевого варианта, то упомянутых 40% получить не удалось. Как и прежде, в два раза медленнее. Но совпадение по профиту, конечно, присутствует.

Глубокие нейросети (Часть V). Байесовская  оптимизация гиперпараметров DNN Глубокие нейросети (Часть V). Байесовская оптимизация гиперпараметров DNN
В статье рассматриваются возможности байесовской оптимизации гиперпараметров глубоких нейросетей, полученных различными вариантами обучения. Сравнивается качество классификации DNN с оптимальными гиперпараметрами при различных вариантах обучения. Форвард-тестами проверена глубина эффективности оптимальных гиперпараметров DNN. Определены возможные направления улучшения качества классификации.
Автоматическое построение линий поддержки и сопротивления Автоматическое построение линий поддержки и сопротивления
В статье рассматривается автоматическое построение линий поддержки и сопротивления через локальные максимумы и минимумы ценовых графиков. Для определения этих экстремумов применяется всем известный индикатор ZigZag.
LifeHack для трейдера: замешиваем ForEach на дефайнах (#define) LifeHack для трейдера: замешиваем ForEach на дефайнах (#define)
Промежуточная ступенька для тех, кто всё ещё пишет на MQL4, но никак не может перейти на MQL5. Мы продолжаем искать возможности для написания кода в стиле MQL4. На этот раз рассмотрим макроподстановку препроцессора - #define.
Управление капиталом по Винсу. Реализация в виде модуля Мастера MQL5 Управление капиталом по Винсу. Реализация в виде модуля Мастера MQL5
Статья написана на основе книги Р.Винса "Математика управления капиталом". В ней рассматриваются эмпирические и параметрические методы нахождения оптимального размера торгового лота, на основе которых написаны торговые модули управления капиталом для мастера MLQ5.