LifeHack для трейдера: готовим фастфуд из индикаторов
Если нельзя, но очень хочется - то можно. Русская поговорка
Простота 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:
Рис. 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-названия индикаторных линий) и торговые классы CPositionInfo, CTrade, CSymbolInfo и 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 |
Все три советника показали в режиме "Все тики" одинаковые графики:
Рис. 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".
- простота написания кода — одна строка вместо нескольких;
- наглядность и краткость — чем меньше кода, тем проще его понимать.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Попытка восстановления:
Выкладываю свою версию измерения. За базовую версию советника был взят 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%.
- у Вас был красивый код и описание результатов замеров.
- у Вас был красивый код и описание результатов замеров.
Я код посмотреть не успел
Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий
Обсуждение статьи "LifeHack для трейдера: готовим фастфуд из индикаторов"
Vasiliy Sokolov, 2018.01.29 10:43
Выкладываю свою версию измерения. За базовую версию советника был взят MACD Sample One value at a 5. В него были внесены небольшие изменения. Т.к. все значения индикаторов собираются в одном месте было не сложно сделать простую макроподстановку: Выводы думаю очевидны: при вызове индикаторов в MQL4 режиме, скорость ниже на 40%.
MQL5-style
MQL4-style (без кеша)
MQL4-style (с кешем)
На 32% медленнее кешевый вариант, по сравнению с MQL5-style. Что же касается безкешевого варианта, то упомянутых 40% получить не удалось. Как и прежде, в два раза медленнее. Но совпадение по профиту, конечно, присутствует.