Библиотека для простого и быстрого создания программ для MetaTrader (Часть IX): Совместимость с MQL4 - Подготовка данных
Содержание
В прошлых частях описания кроссплатформенной библиотеки для MetaTrader 5 и MetaTrader 4 мы подготовили инструментарий:
- для создания user-case функций для быстрого доступа из своих программ к любым данным любых ордеров и позиций на счётах с типом хэдж и неттинг,
- для отслеживания событий, происходящих с ордерами и позициями — выставление, удаление и срабатывание отложенных ордеров, открытие, закрытие позиций и модификация позиций и ордеров.
Теперь мы вплотную подошли к реализации совместимости библиотеки с MQL4, так как далее нам нужно будет делать торговые классы, и библиотека должна для этого корректно работать и на MQL5 и на MQL4.
В данной статье начнём доработку библиотеки для реализации её кроссплатформенности.
Отличия MQL4 от MQL5
Скопируем всю папку нашей библиотеки в соответствующую папку MetaTrader 4: \MQL4\Include\DoEasy, а тестовые советники будем брать из соответствующих статьям папок с MQL5-советниками и сохранять их с расширением *.mq4 в каталог экспертов \MQL4\Experts\TestDoEasy в папку, соответствующую номеру статьи — в данном случае Part09 — в ней будет сохранён тестовый советник для этой статьи.
Найдём в навигаторе редактора папку с библиотекой \MQL4\Include\DoEasy, щёлкнем по ней правой кнопкой мыши и выберем "Компилировать".
Это приведёт к компиляции всех файлов библиотеки и получению в итоге более двух тысяч ошибок компиляции:
Если изучить полученные ошибки, то увидим, что основная часть связано с константами и перечеслениями MQL5, о которых MQL4 ничего не знает. Значит нам необходимо "познакомить" MQL4 с используемыми библиотекой константами. Есть конечно и ошибки иного плана — отсутствие в MQL4 функций MQL5, используемых нами — будем делать логику их работы при помощи имеющихся функций MQL4.
И, естественно, ордерные системы MQL4 и MQL5 сильно отличаются — здесь нам придётся делать для MQL4 отдельный, отличающийся от уже сделанного для MQL5 обработчик событий, так как, к сожалению, в MQL4 в списке исторических ордеров предоставлено гораздо меньше информации об ордерах (о сделках вообще нет информации), и здесь не получится брать данные об ордерах и сделках прямо из списков терминала — тут нам придётся логически сравнивать происходящее в списках активных и исторических рыночных ордеров, и на основе сравнения определять произошедшие события.Доработка
В корневой папке библиотеки DoEasy создадим новый включаемый файл ToMQL4.mqh — здесь будем описывать все необходимые константы и перечисления для MQL4. И сразу же подключим его к файлу Defines.mqh для MQL4-компиляции в самом начале листинга Defines.mqh:
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/ru/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/ru/users/artmedia70" //+------------------------------------------------------------------+ //| Включаемые файлы | //+------------------------------------------------------------------+ #ifdef __MQL4__ #include "ToMQL4.mqh" #endif //+------------------------------------------------------------------+
После этого полключения вся библиотека при компиляции для MQL4 будет видеть всё, что будет прописано в файле ToMQL4.mqh.
Перейдём в самое начало списка ошибок в журнале редактора "Ошибки", нажав клавишу NumPad "Home", или просто перемотав мышкой на начало, сделаем двойной клик по самой первой ошибке:
Редактор нас переместит в файл Defines.mqh на строку, где была получена данная ошибка:
//+------------------------------------------------------------------+ //| Список возможных торговых событий на счёте | //+------------------------------------------------------------------+ enum ENUM_TRADE_EVENT { TRADE_EVENT_NO_EVENT = 0, // Нет торгового события TRADE_EVENT_PENDING_ORDER_PLASED, // Отложенный ордер установлен TRADE_EVENT_PENDING_ORDER_REMOVED, // Отложенный ордер удалён //--- члены перечисления, совпадающие с членами перечисления ENUM_DEAL_TYPE //--- (порядок следования констант ниже менять нельзя, удалять и добавлять новые - нельзя) TRADE_EVENT_ACCOUNT_CREDIT = DEAL_TYPE_CREDIT, // Начисление кредита (3) TRADE_EVENT_ACCOUNT_CHARGE, // Дополнительные сборы
Естественно, что MQL4 ничего не знает о сделках и об их типах. Нужно ему о них сообщить. Можно просто открыть справочник редактора MQL5 и найти в поиске по DEAL_TYPE_CREDIT необходимую нам информацию о свойствах сделок:
Идентификатор | Описание | Тип |
DEAL_TICKET | Тикет сделки. Уникальное число, которое присваивается каждой сделке | long |
DEAL_ORDER | Ордер, на основание которого выполнена сделка | long |
DEAL_TIME | Время совершения сделки | datetime |
DEAL_TIME_MSC | Время совершения сделки в миллисекундах с 01.01.1970 | long |
DEAL_TYPE | Тип сделки | |
DEAL_ENTRY | Направление сделки – вход в рынок, выход из рынка или разворот | |
DEAL_MAGIC | Magic number для сделки (смотри ORDER_MAGIC) | long |
DEAL_REASON | Причина или источник проведения сделки | |
DEAL_POSITION_ID | Идентификатор позиции, в открытии, изменении или закрытии которой участвовала эта сделка. Каждая позиция имеет уникальный идентификатор, который присваивается всем сделкам, совершенным на инструменте в течение всей жизни позиции. | long |
В данной таблице нас интересует ENUM_DEAL_TYPE. Переходим по ссылке, и получаем список всех типов сделок:
Идентификатор | Описание |
DEAL_TYPE_BUY | Покупка |
DEAL_TYPE_SELL | Продажа |
DEAL_TYPE_BALANCE | Начисление баланса |
DEAL_TYPE_CREDIT | Начисление кредита |
DEAL_TYPE_CHARGE | Дополнительные сборы |
DEAL_TYPE_CORRECTION | Корректирующая запись |
DEAL_TYPE_BONUS | Перечисление бонусов |
DEAL_TYPE_COMMISSION | Дополнительные комиссии |
DEAL_TYPE_COMMISSION_DAILY | Комиссия, начисляемая в конце торгового дня |
DEAL_TYPE_COMMISSION_MONTHLY | Комиссия, начисляемая в конце месяца |
DEAL_TYPE_COMMISSION_AGENT_DAILY | Агентская комиссия, начисляемая в конце торгового дня |
DEAL_TYPE_COMMISSION_AGENT_MONTHLY | Агентская комиссия, начисляемая в конце месяца |
DEAL_TYPE_INTEREST | Начисления процентов на свободные средства |
DEAL_TYPE_BUY_CANCELED | Отмененная сделка покупки. Возможная ситуация, когда ранее совершенная сделка на покупку отменяется. В таком случае тип ранее совершенной сделки (DEAL_TYPE_BUY) меняется на DEAL_TYPE_BUY_CANCELED, а ее прибыль/убыток обнуляется. Ранее полученная прибыль/убыток начисляется/списывается со счета отдельной балансовой операцией |
DEAL_TYPE_SELL_CANCELED | Отмененная сделка продажи. Возможная ситуация, когда ранее совершенная сделка на продажу отменяется. В таком случае тип ранее совершенной сделки (DEAL_TYPE_SELL) меняется на DEAL_TYPE_SELL_CANCELED, а ее прибыль/убыток обнуляется. Ранее полученная прибыль/убыток начисляется/списывается со счета отдельной балансовой операцией |
DEAL_DIVIDEND | Начисление дивиденда |
DEAL_DIVIDEND_FRANKED | Начисление франкированного дивиденда (освобожденного от уплаты налога) |
DEAL_TAX | Начисление налога |
Добавим типы сделок из перечисления ENUM_DEAL_TYPE в файл ToMQL4.mqh:
//+------------------------------------------------------------------+ //| ToMQL4.mqh | //| Copyright 2017, Artem A. Trishkin, Skype artmedia70 | //| https://www.mql5.com/ru/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2017, Artem A. Trishkin, Skype artmedia70" #property link "https://www.mql5.com/ru/users/artmedia70" #property strict #ifdef __MQL4__ //+------------------------------------------------------------------+ //| Типы сделок MQL5 | //+------------------------------------------------------------------+ enum ENUM_DEAL_TYPE { DEAL_TYPE_BUY, DEAL_TYPE_SELL, DEAL_TYPE_BALANCE, DEAL_TYPE_CREDIT, DEAL_TYPE_CHARGE, DEAL_TYPE_CORRECTION, DEAL_TYPE_BONUS, DEAL_TYPE_COMMISSION, DEAL_TYPE_COMMISSION_DAILY, DEAL_TYPE_COMMISSION_MONTHLY, DEAL_TYPE_COMMISSION_AGENT_DAILY, DEAL_TYPE_COMMISSION_AGENT_MONTHLY, DEAL_TYPE_INTEREST, DEAL_TYPE_BUY_CANCELED, DEAL_TYPE_SELL_CANCELED, DEAL_DIVIDEND, DEAL_DIVIDEND_FRANKED, DEAL_TAX }; //+------------------------------------------------------------------+ #endif
Сохраним файл и вновь скомпилируем все файлы библиотеки. Ошибок стало меньше:
Вновь перейдём в начало списка ошибок и щёлкнем по первой — теперь это ENUM_POSITION_TYPE, добавляем:
//+------------------------------------------------------------------+ //| ToMQL4.mqh | //| Copyright 2017, Artem A. Trishkin, Skype artmedia70 | //| https://www.mql5.com/ru/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2017, Artem A. Trishkin, Skype artmedia70" #property link "https://www.mql5.com/ru/users/artmedia70" #property strict #ifdef __MQL4__ //+------------------------------------------------------------------+ //| Типы сделок MQL5 | //+------------------------------------------------------------------+ enum ENUM_DEAL_TYPE { DEAL_TYPE_BUY, DEAL_TYPE_SELL, DEAL_TYPE_BALANCE, DEAL_TYPE_CREDIT, DEAL_TYPE_CHARGE, DEAL_TYPE_CORRECTION, DEAL_TYPE_BONUS, DEAL_TYPE_COMMISSION, DEAL_TYPE_COMMISSION_DAILY, DEAL_TYPE_COMMISSION_MONTHLY, DEAL_TYPE_COMMISSION_AGENT_DAILY, DEAL_TYPE_COMMISSION_AGENT_MONTHLY, DEAL_TYPE_INTEREST, DEAL_TYPE_BUY_CANCELED, DEAL_TYPE_SELL_CANCELED, DEAL_DIVIDEND, DEAL_DIVIDEND_FRANKED, DEAL_TAX }; //+------------------------------------------------------------------+ //| Направление открытой позиции | //+------------------------------------------------------------------+ enum ENUM_POSITION_TYPE { POSITION_TYPE_BUY, POSITION_TYPE_SELL }; //+------------------------------------------------------------------+ #endif
Компилируем, получаем ещё меньше ошибок, переходим к первой ошибке в списке, определяем причину — добавляем следующее перечисление:
//+------------------------------------------------------------------+ //| ToMQL4.mqh | //| Copyright 2017, Artem A. Trishkin, Skype artmedia70 | //| https://www.mql5.com/ru/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2017, Artem A. Trishkin, Skype artmedia70" #property link "https://www.mql5.com/ru/users/artmedia70" #property strict #ifdef __MQL4__ //+------------------------------------------------------------------+ //| Типы сделок MQL5 | //+------------------------------------------------------------------+ enum ENUM_DEAL_TYPE { DEAL_TYPE_BUY, DEAL_TYPE_SELL, DEAL_TYPE_BALANCE, DEAL_TYPE_CREDIT, DEAL_TYPE_CHARGE, DEAL_TYPE_CORRECTION, DEAL_TYPE_BONUS, DEAL_TYPE_COMMISSION, DEAL_TYPE_COMMISSION_DAILY, DEAL_TYPE_COMMISSION_MONTHLY, DEAL_TYPE_COMMISSION_AGENT_DAILY, DEAL_TYPE_COMMISSION_AGENT_MONTHLY, DEAL_TYPE_INTEREST, DEAL_TYPE_BUY_CANCELED, DEAL_TYPE_SELL_CANCELED, DEAL_DIVIDEND, DEAL_DIVIDEND_FRANKED, DEAL_TAX }; //+------------------------------------------------------------------+ //| Направление открытой позиции | //+------------------------------------------------------------------+ enum ENUM_POSITION_TYPE { POSITION_TYPE_BUY, POSITION_TYPE_SELL }; //+------------------------------------------------------------------+ //| Состояние ордера | //+------------------------------------------------------------------+ enum ENUM_ORDER_STATE { ORDER_STATE_STARTED, ORDER_STATE_PLACED, ORDER_STATE_CANCELED, ORDER_STATE_PARTIAL, ORDER_STATE_FILLED, ORDER_STATE_REJECTED, ORDER_STATE_EXPIRED, ORDER_STATE_REQUEST_ADD, ORDER_STATE_REQUEST_MODIFY, ORDER_STATE_REQUEST_CANCEL }; //+------------------------------------------------------------------+ #endif
При следующей компиляции мы попали на ошибочный тип ордера ORDER_TYPE_BUY_STOP_LIMIT.
В MQL4 уже существует перечисление ENUM_ORDER_TYPE, и в него мы не сможем добавить новые константы. Значит — добавим их как макроподстановки.
В MQL5 константа ORDER_TYPE_BUY_STOP_LIMIT из перечисления ENUM_ORDER_TYPE имеет значение 6. А вот в MQL4 такой тип ордера существует — это балансовая операция, равно как и ORDER_TYPE_SELL_STOP_LIMIT в MQL5 имеет значение 7, а в MQL4 такой тип ордера — кредитная операция.
Поэтому зададим им значения больше константы закрывающего ордера ORDER_TYPE_CLOSE_BY в MQL5: ORDER_TYPE_CLOSE_BY+1 и ORDER_TYPE_CLOSE_BY+2 соответственно:
//+------------------------------------------------------------------+ //| ToMQL4.mqh | //| Copyright 2017, Artem A. Trishkin, Skype artmedia70 | //| https://www.mql5.com/ru/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2017, Artem A. Trishkin, Skype artmedia70" #property link "https://www.mql5.com/ru/users/artmedia70" #property strict #ifdef __MQL4__ //+------------------------------------------------------------------+ //| Типы сделок MQL5 | //+------------------------------------------------------------------+ enum ENUM_DEAL_TYPE { DEAL_TYPE_BUY, DEAL_TYPE_SELL, DEAL_TYPE_BALANCE, DEAL_TYPE_CREDIT, DEAL_TYPE_CHARGE, DEAL_TYPE_CORRECTION, DEAL_TYPE_BONUS, DEAL_TYPE_COMMISSION, DEAL_TYPE_COMMISSION_DAILY, DEAL_TYPE_COMMISSION_MONTHLY, DEAL_TYPE_COMMISSION_AGENT_DAILY, DEAL_TYPE_COMMISSION_AGENT_MONTHLY, DEAL_TYPE_INTEREST, DEAL_TYPE_BUY_CANCELED, DEAL_TYPE_SELL_CANCELED, DEAL_DIVIDEND, DEAL_DIVIDEND_FRANKED, DEAL_TAX }; //+------------------------------------------------------------------+ //| Направление открытой позиции | //+------------------------------------------------------------------+ enum ENUM_POSITION_TYPE { POSITION_TYPE_BUY, POSITION_TYPE_SELL }; //+------------------------------------------------------------------+ //| Состояние ордера | //+------------------------------------------------------------------+ enum ENUM_ORDER_STATE { ORDER_STATE_STARTED, ORDER_STATE_PLACED, ORDER_STATE_CANCELED, ORDER_STATE_PARTIAL, ORDER_STATE_FILLED, ORDER_STATE_REJECTED, ORDER_STATE_EXPIRED, ORDER_STATE_REQUEST_ADD, ORDER_STATE_REQUEST_MODIFY, ORDER_STATE_REQUEST_CANCEL }; //+------------------------------------------------------------------+ //| Типы ордеров | //+------------------------------------------------------------------+ #define ORDER_TYPE_CLOSE_BY (8) #define ORDER_TYPE_BUY_STOP_LIMIT (9) #define ORDER_TYPE_SELL_STOP_LIMIT (10) //+------------------------------------------------------------------+ #endif
Скомпилируем всю библиотеку. Теперь, после ввода макроподстановок типов StopLimit-ордеров, у нас ошибка указывает на файл DELib.mqh на функции, возвращающие корректную цену установки ордера, а именно — на то, что в перечислении ENUM_ORDER_TYPE нет значений 9 и 10 — ведь мы используем в операторе switch значение типа ордера, а оно имеет тип перечисления ENUM_ORDER_TYPE:
//+------------------------------------------------------------------+ //| Возвращает корректную цену постановки ордера | //| относительно StopLevel | //+------------------------------------------------------------------+ double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double price=0,const int spread_multiplier=2) { double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0; int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS); switch(order_type) { case ORDER_TYPE_BUY_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg); case ORDER_TYPE_BUY_STOP : case ORDER_TYPE_BUY_STOP_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg); case ORDER_TYPE_SELL_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg); case ORDER_TYPE_SELL_STOP : case ORDER_TYPE_SELL_STOP_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg); default : Print(DFUN,TextByLanguage("Не правильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0; } } //+------------------------------------------------------------------+ //| Возвращает корректную цену постановки ордера | //| относительно StopLevel | //+------------------------------------------------------------------+ double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const int distance_set,const double price=0,const int spread_multiplier=2) { double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0; int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS); switch(order_type) { case ORDER_TYPE_BUY_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg); case ORDER_TYPE_BUY_STOP : case ORDER_TYPE_BUY_STOP_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg); case ORDER_TYPE_SELL_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg); case ORDER_TYPE_SELL_STOP : case ORDER_TYPE_SELL_STOP_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg); default : Print(DFUN,TextByLanguage("Не правильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0; } } //+------------------------------------------------------------------+
Решение простое — order_type в switch преобразуем к целочисленному типу:
//+------------------------------------------------------------------+ //| Возвращает корректную цену постановки ордера | //| относительно StopLevel | //+------------------------------------------------------------------+ double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double price=0,const int spread_multiplier=2) { double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0; int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS); switch((int)order_type) { case ORDER_TYPE_BUY_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg); case ORDER_TYPE_BUY_STOP : case ORDER_TYPE_BUY_STOP_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg); case ORDER_TYPE_SELL_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg); case ORDER_TYPE_SELL_STOP : case ORDER_TYPE_SELL_STOP_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg); default : Print(DFUN,TextByLanguage("Не правильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0; } } //+------------------------------------------------------------------+ //| Возвращает корректную цену постановки ордера | //| относительно StopLevel | //+------------------------------------------------------------------+ double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const int distance_set,const double price=0,const int spread_multiplier=2) { double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0; int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS); switch((int)order_type) { case ORDER_TYPE_BUY_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg); case ORDER_TYPE_BUY_STOP : case ORDER_TYPE_BUY_STOP_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg); case ORDER_TYPE_SELL_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg); case ORDER_TYPE_SELL_STOP : case ORDER_TYPE_SELL_STOP_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg); default : Print(DFUN,TextByLanguage("Не правильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0; } } //+------------------------------------------------------------------+
Компилируем. Теперь ошибка в файле Order.mqh — MQL4 не знает значений констант ORDER_FILLING_RETURN, ORDER_TIME_GTC, ORDER_REASON_SL, ORDER_REASON_TP и ORDER_REASON_EXPERT.
//+------------------------------------------------------------------+ //| Возвращает тип исполнения по остатку | //+------------------------------------------------------------------+ long COrder::OrderTypeFilling(void) const { #ifdef __MQL4__ return (long)ORDER_FILLING_RETURN; #else long res=0; switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS)) { case ORDER_STATUS_MARKET_ORDER : case ORDER_STATUS_MARKET_PENDING : res=::OrderGetInteger(ORDER_TYPE_FILLING); break; case ORDER_STATUS_HISTORY_PENDING : case ORDER_STATUS_HISTORY_ORDER : res=::HistoryOrderGetInteger(m_ticket,ORDER_TYPE_FILLING);break; default : res=0; break; } return res; #endif } //+------------------------------------------------------------------+ //| Возвращает время жизни ордера | //+------------------------------------------------------------------+ long COrder::OrderTypeTime(void) const { #ifdef __MQL4__ return (long)ORDER_TIME_GTC; #else long res=0; switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS)) { case ORDER_STATUS_MARKET_ORDER : case ORDER_STATUS_MARKET_PENDING : res=::OrderGetInteger(ORDER_TYPE_TIME); break; case ORDER_STATUS_HISTORY_PENDING : case ORDER_STATUS_HISTORY_ORDER : res=::HistoryOrderGetInteger(m_ticket,ORDER_TYPE_TIME);break; default : res=0; break; } return res; #endif } //+------------------------------------------------------------------+ //| Причина или источник выставления ордера | //+------------------------------------------------------------------+ long COrder::OrderReason(void) const { #ifdef __MQL4__ return ( this.OrderCloseByStopLoss() ? ORDER_REASON_SL : this.OrderCloseByTakeProfit() ? ORDER_REASON_TP : this.OrderMagicNumber()!=0 ? ORDER_REASON_EXPERT : WRONG_VALUE ); #else long res=WRONG_VALUE; switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS)) { case ORDER_STATUS_MARKET_POSITION : res=::PositionGetInteger(POSITION_REASON); break; case ORDER_STATUS_MARKET_ORDER : case ORDER_STATUS_MARKET_PENDING : res=::OrderGetInteger(ORDER_REASON); break; case ORDER_STATUS_HISTORY_PENDING : case ORDER_STATUS_HISTORY_ORDER : res=::HistoryOrderGetInteger(m_ticket,ORDER_REASON);break; case ORDER_STATUS_DEAL : res=::HistoryDealGetInteger(m_ticket,DEAL_REASON); break; default : res=WRONG_VALUE; break; } return res; #endif } //+------------------------------------------------------------------+
Добавим макроподстановки в файл ToMQL4.mqh (добавляем в конец, полный листинг приводить не буду для экономии места):
//+------------------------------------------------------------------+ //| Типы ордеров, политика исполнения, срок действия, причины | //+------------------------------------------------------------------+ #define ORDER_TYPE_CLOSE_BY (8) #define ORDER_TYPE_BUY_STOP_LIMIT (9) #define ORDER_TYPE_SELL_STOP_LIMIT (10) #define ORDER_FILLING_RETURN (2) #define ORDER_TIME_GTC (0) #define ORDER_REASON_EXPERT (3) #define ORDER_REASON_SL (4) #define ORDER_REASON_TP (5) //+------------------------------------------------------------------+ #endif
Очередная компиляция приводит нас к отсутствующей функции MQL5 HistoryOrderGetTicket() в файле HistoryCollection.mqh в методе CHistoryCollection::OrderSearch(). Смотрим в код, и понимаем, что тут проще воспользоваться директивами условной компиляции. Дописываем метод:
//+------------------------------------------------------------------+ //| Возвращает тип и тикет "потерянного" ордера | //+------------------------------------------------------------------+ ulong CHistoryCollection::OrderSearch(const int start,ENUM_ORDER_TYPE &order_type) { ulong order_ticket=0; #ifdef __MQL5__ for(int i=start-1;i>=0;i--) { ulong ticket=::HistoryOrderGetTicket(i); if(ticket==0) continue; ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::HistoryOrderGetInteger(ticket,ORDER_TYPE); if(this.IsPresentOrderInList(ticket,type)) continue; order_ticket=ticket; order_type=type; } #else for(int i=start-1;i>=0;i--) { if(!::OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)) continue; ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderType(); ulong ticket=::OrderTicket(); if(ticket==0 || type<ORDER_TYPE_BUY_LIMIT || type>ORDER_TYPE_SELL_STOP) continue; if(this.IsPresentOrderInList(ticket,type)) continue; order_ticket=ticket; order_type=type; } #endif return order_ticket; } //+------------------------------------------------------------------+
Всё, что было в нём для MQL5, обрамляем директивой #ifdef __MQL5__ и дописываем код для MQL4 после директивы #else до директивы #endif.
Далее попадаем на ошибку в конструкторе класса CEvent и дописываем код с использованием тех же директив условной компиляции:
//+------------------------------------------------------------------+ //| Конструктор | //+------------------------------------------------------------------+ CEvent::CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket) : m_event_code(event_code),m_digits(0) { this.m_long_prop[EVENT_PROP_STATUS_EVENT] = event_status; this.m_long_prop[EVENT_PROP_TICKET_ORDER_EVENT] = (long)ticket; this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif; this.m_digits_acc=#ifdef __MQL4__ 2 #else (int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS) #endif; this.m_chart_id=::ChartID(); } //+------------------------------------------------------------------+
Здесь: при проверке счёта на тип "хедж" возникает ошибка отсутствия константы, поэтому просто возвращаем сразу true — на MetaTrader 4 все счета имеют тип хедж.
И при получении количества знаков после запятой в валюте счёта тоже возвращаем 2, так как в MQL4 нет возможности получить это значение.
Следующая компиляция приводит нас к методу CEventsCollection::NewDealEventHedge() — создание события для хеджевого счёта для MetaTrader 5. В нём идёт работа со сделками, которых нет в MQL4. Пока полностью отключим данный метод, заключив весь код метода в рамки условной компиляции:
В начале метода вставляем директиву
//+------------------------------------------------------------------+ //| Создаёт событие для хеджевого счёта | //+------------------------------------------------------------------+ void CEventsCollection::NewDealEventHedge(COrder* deal,CArrayObj* list_history,CArrayObj* list_market) { #ifdef __MQL5__ double ask=::SymbolInfoDouble(deal.Symbol(),SYMBOL_ASK); double bid=::SymbolInfoDouble(deal.Symbol(),SYMBOL_BID); //--- Вход в рынок
и в конце метода
#endif } //+------------------------------------------------------------------+
Далее попадаем на ошибку в методе CEventsCollection::NewDealEventNetto() — создание события для неттингового счёта. Решение такое же, как и предыдущее — обрамляем весь код метода NewDealEventNetto() директивой условной компиляции.
Компилируем и попадаем на ошибку неизвестной константы DEAL_ENTRY_IN в методе CEventsCollection::GetListAllDealsInByPosID(). Добавим нужное перечисление в файл ToMQL4.mqh (можно было опять отключить код условной компиляцией, но подумалось, что данное перечисление нам может пригодиться в дальнейшем):
//+------------------------------------------------------------------+ //| Типы сделок MQL5 | //+------------------------------------------------------------------+ enum ENUM_DEAL_TYPE { DEAL_TYPE_BUY, DEAL_TYPE_SELL, DEAL_TYPE_BALANCE, DEAL_TYPE_CREDIT, DEAL_TYPE_CHARGE, DEAL_TYPE_CORRECTION, DEAL_TYPE_BONUS, DEAL_TYPE_COMMISSION, DEAL_TYPE_COMMISSION_DAILY, DEAL_TYPE_COMMISSION_MONTHLY, DEAL_TYPE_COMMISSION_AGENT_DAILY, DEAL_TYPE_COMMISSION_AGENT_MONTHLY, DEAL_TYPE_INTEREST, DEAL_TYPE_BUY_CANCELED, DEAL_TYPE_SELL_CANCELED, DEAL_DIVIDEND, DEAL_DIVIDEND_FRANKED, DEAL_TAX }; //+------------------------------------------------------------------+ //| Способ изменения позиции | //+------------------------------------------------------------------+ enum ENUM_DEAL_ENTRY { DEAL_ENTRY_IN, DEAL_ENTRY_OUT, DEAL_ENTRY_INOUT, DEAL_ENTRY_OUT_BY }; //+------------------------------------------------------------------+ //| Направление открытой позиции | //+------------------------------------------------------------------+ enum ENUM_POSITION_TYPE { POSITION_TYPE_BUY, POSITION_TYPE_SELL }; //+------------------------------------------------------------------+
Далее попадаем на знакомую уже ошибку проверки счёта на тип "хедж", но теперь в конструкторе класса-коллекции событий, исправляем:
//+------------------------------------------------------------------+ //| Конструктор | //+------------------------------------------------------------------+ CEventsCollection::CEventsCollection(void) : m_trade_event(TRADE_EVENT_NO_EVENT),m_trade_event_code(TRADE_EVENT_FLAG_NO_EVENT) { this.m_list_events.Clear(); this.m_list_events.Sort(SORT_BY_EVENT_TIME_EVENT); this.m_list_events.Type(COLLECTION_EVENTS_ID); this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif; this.m_chart_id=::ChartID(); ::ZeroMemory(this.m_tick); } //+------------------------------------------------------------------+
Далее вносим то же исправление в конструктор класса CEngine:
//+------------------------------------------------------------------+ //| CEngine конструктор | //+------------------------------------------------------------------+ CEngine::CEngine() : m_first_start(true),m_acc_trade_event(TRADE_EVENT_NO_EVENT) { ::ResetLastError(); if(!::EventSetMillisecondTimer(TIMER_FREQUENCY)) Print(DFUN,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError()); this.m_list_counters.Sort(); this.m_list_counters.Clear(); this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE); this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif; } //+------------------------------------------------------------------+
Всё. Теперь вся библиотека компилируется без ошибок. Но это только первый этап. Теперь предстоит запустить её в работу, а ведь мы несколько методов просто отключили условной компиляцией — их нужно будет создать для работы в MetaTrader 4.
Так как в MQL5 балансовые операции — это сделки, и расположены они у нас в списке исторических ордеров и сделок, то в MQL4 балансовые операции — это ордера с типом ORDER_TYPE_BALANCE (6) и ORDER_TYPE_CREDIT (7). Поэтому для MQL4 было решено сделать отдельный класс объекта-балансовой операции, и хранить его также в списке исторических ордеров и позиций.
Создадим новый класс CHistoryBalance в папке \MQL4\Include\DoEasy\Objects\Orders в файле HistoryBalance.mqh. Базовым классом должен быть COrder:
//+------------------------------------------------------------------+ //| HistoryBalance.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/ru/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/ru/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Включаемые файлы | //+------------------------------------------------------------------+ #include "Order.mqh" //+------------------------------------------------------------------+ //| Историческая балансовая операция | //+------------------------------------------------------------------+ class CHistoryBalance : public COrder { public: //--- Конструктор CHistoryBalance(const ulong ticket) : COrder(ORDER_STATUS_BALANCE,ticket) {} //--- Поддерживаемые свойства сделки (1) вещественные, (2) целочисленные virtual bool SupportProperty(ENUM_ORDER_PROP_INTEGER property); virtual bool SupportProperty(ENUM_ORDER_PROP_DOUBLE property); virtual bool SupportProperty(ENUM_ORDER_PROP_STRING property); }; //+------------------------------------------------------------------+ //| Возвращает истину, если ордер поддерживает переданное | //| целочисленное свойство, возвращает ложь в противном случае | //+------------------------------------------------------------------+ bool CHistoryBalance::SupportProperty(ENUM_ORDER_PROP_INTEGER property) { if(property==ORDER_PROP_TICKET || property==ORDER_PROP_TIME_OPEN || property==ORDER_PROP_STATUS || property==ORDER_PROP_TYPE || property==ORDER_PROP_REASON ) return true; return false; } //+------------------------------------------------------------------+ //| Возвращает истину, если ордер поддерживает переданное | //| вещественное свойство, возвращает ложь в противном случае | //+------------------------------------------------------------------+ bool CHistoryBalance::SupportProperty(ENUM_ORDER_PROP_DOUBLE property) { return(property==ORDER_PROP_PROFIT ? true : false); } //+------------------------------------------------------------------+ //| Возвращает истину, если ордер поддерживает переданное | //| строковое свойство, возвращает ложь в противном случае | //+------------------------------------------------------------------+ bool CHistoryBalance::SupportProperty(ENUM_ORDER_PROP_STRING property) { if(property==ORDER_PROP_SYMBOL || property==ORDER_PROP_EXT_ID) return false; return true; } //+------------------------------------------------------------------+
В данном классе для нас нет ничего нового — все классы исторических ордеров мы уже рассматривали во второй части описания библиотеки.
Хочу отметить, что у нас есть два типа операций с балансом — балансовая и кредитная операции. Соответственно, их типы имеют числовые значения 6 и 7. Мы будем для обоих типов использовать один класс-балансную операцию, а уточнять конкретный тип будем в свойстве ордера "причина".
Добавим две недостающие "причины" появления ордера. В файле ToMQL4.mqh допишем их:
//+------------------------------------------------------------------+ //| Типы ордеров, политика исполнения, срок действия, причины | //+------------------------------------------------------------------+ #define ORDER_TYPE_CLOSE_BY (8) #define ORDER_TYPE_BUY_STOP_LIMIT (9) #define ORDER_TYPE_SELL_STOP_LIMIT (10) #define ORDER_FILLING_RETURN (2) #define ORDER_TIME_GTC (0) #define ORDER_REASON_EXPERT (3) #define ORDER_REASON_SL (4) #define ORDER_REASON_TP (5) #define ORDER_REASON_BALANCE (6) #define ORDER_REASON_CREDIT (7) //+------------------------------------------------------------------+
И раз уж у нас есть новый класс-наследник класса абстрактного ордера, то необходимо дописать недостающий функционал в COrder.
В методе COrder::OrderPositionID() заменим для MQL4 возврат магика
//+------------------------------------------------------------------+ //| Возвращает идентификатор позиции | //+------------------------------------------------------------------+ long COrder::OrderPositionID(void) const { #ifdef __MQL4__ return ::OrderMagicNumber(); #else
на возврат тикета (позже организуем подобие PositionID для позиций MQL4):
//+------------------------------------------------------------------+ //| Возвращает идентификатор позиции | //+------------------------------------------------------------------+ long COrder::OrderPositionID(void) const { #ifdef __MQL4__ return ::OrderTicket(); #else
В методе, возвращающем состояние ордера у нас для MQL4, всегда возвращался ORDER_STATE_FILLED из перечисления ENUM_ORDER_STATE, что не совсем верно для удалённых отложенных ордеров. Сделаем проверку статуса ордера, и если это удалённый отложенный ордер, то будем возвращать ORDER_STATE_CANCELED.
//+------------------------------------------------------------------+ //| Возвращает состояние ордера | //+------------------------------------------------------------------+ long COrder::OrderState(void) const { #ifdef __MQL4__ return(this.Status()==ORDER_STATUS_HISTORY_ORDER ? ORDER_STATE_FILLED : ORDER_STATE_CANCELED); #else long res=0; switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS)) { case ORDER_STATUS_HISTORY_PENDING : case ORDER_STATUS_HISTORY_ORDER : res=::HistoryOrderGetInteger(m_ticket,ORDER_STATE); break; case ORDER_STATUS_MARKET_ORDER : case ORDER_STATUS_MARKET_PENDING : res=::OrderGetInteger(ORDER_STATE); break; case ORDER_STATUS_MARKET_POSITION : case ORDER_STATUS_DEAL : default : res=0; break; } return res; #endif } //+------------------------------------------------------------------+
В метод, возвращающий причину ордера для MQL4, допишем две вновь добавленные "причины":
//+------------------------------------------------------------------+ //| Причина или источник выставления ордера | //+------------------------------------------------------------------+ long COrder::OrderReason(void) const { #ifdef __MQL4__ return ( this.TypeOrder()==ORDER_TYPE_BALANCE ? ORDER_REASON_BALANCE : this.TypeOrder()==ORDER_TYPE_CREDIT ? ORDER_REASON_CREDIT : this.OrderCloseByStopLoss() ? ORDER_REASON_SL : this.OrderCloseByTakeProfit() ? ORDER_REASON_TP : this.OrderMagicNumber()!=0 ? ORDER_REASON_EXPERT : WRONG_VALUE ); #else
Метод, возвращающий невыполненный объём для MQL4, у нас всегда возвращал лот ордера, что не правильно для позиций. Для удалённых отложенных ордеров будем возвращать лот ордера, а для позиций — ноль:
//+------------------------------------------------------------------+ //| Возвращает невыполненный объем | //+------------------------------------------------------------------+ double COrder::OrderVolumeCurrent(void) const { #ifdef __MQL4__ return(this.Status()==ORDER_STATUS_HISTORY_PENDING ? ::OrderLots() : 0); #else
В методе, возвращающем описание причины ордера, допишем описания двух новых "причин". Для балансовых и кредитных операций будем проверять профит, и если он больше ноля — значит приход средств, иначе — снятие:
//+------------------------------------------------------------------+ //| Описание причины | //+------------------------------------------------------------------+ string COrder::GetReasonDescription(const long reason) const { #ifdef __MQL4__ return ( this.IsCloseByStopLoss() ? TextByLanguage("Срабатывание StopLoss","Due to StopLoss") : this.IsCloseByTakeProfit() ? TextByLanguage("Срабатывание TakeProfit","Due to TakeProfit") : this.Reason()==ORDER_REASON_EXPERT ? TextByLanguage("Выставлен из mql4-программы","Placed from mql4 program") : this.Comment()=="cancelled" ? TextByLanguage("Отменён","Cancelled") : this.Reason()==ORDER_REASON_BALANCE ? ( this.Profit()>0 ? TextByLanguage("Пополнение баланса","Deposit of funds on the account balance") : TextByLanguage("Снятие средств с баланса","Withdrawals from the balance") ) : this.Reason()==ORDER_REASON_CREDIT ? ( this.Profit()>0 ? TextByLanguage("Начисление кредитных средств","Received credit funds") : TextByLanguage("Изъятие кредитных средств","Withdrawal of credit") ) : TextByLanguage("Свойство не поддерживается в MQL4","Property is not supported in MQL4") ); #else
Также были сделаны мелкие правки, описывать которые не имеет практического смысла. В основном касаемые выводимого текста в журнал в MQL5 и MQL4. Все правки доступны в прилагаемых к статье файлах библиотеки.
Теперь доработаем класс исторической коллекции в файле HistoryCollection.mqh.
Первым делом подключим к нему файл нового класса:
//+------------------------------------------------------------------+ //| Включаемые файлы | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Services\Select.mqh" #include "..\Objects\Orders\HistoryOrder.mqh" #include "..\Objects\Orders\HistoryPending.mqh" #include "..\Objects\Orders\HistoryDeal.mqh" #ifdef __MQL4__ #include "..\Objects\Orders\HistoryBalance.mqh" #endif //+------------------------------------------------------------------+
Так как класс CHistoryBalance нужен нам только для MQL4-версии библиотеки, то и подключение файла данного класса у нас заключено в директивы условной компиляции для MQL4.
Теперь у нас есть новый класс балансовых операций и, для его создания и размещения в коллекцию, нам необходимо добавить в методе Refresh() класса CHistoryCollection для MQL4 проверку типов ордеров на тип балансовойи кредитной операции и добавление их в коллекцию:
//+------------------------------------------------------------------+ //| Обновляет список ордеров и сделок | //+------------------------------------------------------------------+ void CHistoryCollection::Refresh(void) { #ifdef __MQL4__ int total=::OrdersHistoryTotal(),i=m_index_order; for(; i<total; i++) { if(!::OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)) continue; ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)::OrderType(); //--- Закрытые позиции if(order_type<ORDER_TYPE_BUY_LIMIT) { CHistoryOrder *order=new CHistoryOrder(::OrderTicket()); if(order==NULL) continue; if(!this.m_list_all_orders.InsertSort(order)) { ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to the list")); delete order; } } //--- Балансовые/кредитные операции else if(order_type>ORDER_TYPE_SELL_STOP) { CHistoryBalance *order=new CHistoryBalance(::OrderTicket()); if(order==NULL) continue; if(!this.m_list_all_orders.InsertSort(order)) { ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to the list")); delete order; } } else { //--- Удалённые отложенные ордера CHistoryPending *order=new CHistoryPending(::OrderTicket()); if(order==NULL) continue; if(!this.m_list_all_orders.InsertSort(order)) { ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to the list")); delete order; } } } //--- int delta_order=i-m_index_order; this.m_index_order=i; this.m_delta_order=delta_order; this.m_is_trade_event=(this.m_delta_order!=0 ? true : false); //--- __MQL5__ #elseВ классе исторического ордера сделаем некоторые поправки:
//+------------------------------------------------------------------+ //| Возвращает истину, если ордер поддерживает переданное | //| вещественное свойство, возвращает ложь в противном случае | //+------------------------------------------------------------------+ bool CHistoryOrder::SupportProperty(ENUM_ORDER_PROP_DOUBLE property) { if( #ifdef __MQL5__ property==ORDER_PROP_PROFIT || property==ORDER_PROP_PROFIT_FULL || property==ORDER_PROP_SWAP || property==ORDER_PROP_COMMISSION || property==ORDER_PROP_PRICE_CLOSE || ( property==ORDER_PROP_PRICE_STOP_LIMIT && ( this.TypeOrder()<ORDER_TYPE_BUY_STOP_LIMIT || this.TypeOrder()>ORDER_TYPE_SELL_STOP_LIMIT ) ) #else property==ORDER_PROP_PRICE_STOP_LIMIT && this.Status()==ORDER_STATUS_HISTORY_ORDER #endif ) return false; return true; } //+------------------------------------------------------------------+
У нас было так, что цена установки StopLimit-ордера в MQL5 не выводилась в журнал, поэтому сделали проверку, что если проверяемое свойство — цена постановки StopLimit-ордера, и при этом тип ордера не является StopLimit, то только в этом случае свойство не используется, иначе — это StopLimit-ордер, и это свойство нужно.
Для MQL4 цена установки StopLimit-ордера для позиций не используется.
Доработка первого этапа совместимости с MQL4 завершена.
Тестирование
Для тестирования возьмём советник TestDoEasyPart03_1.mq5 из папки MQL5-советников \MQL5\Experts\TestDoEasy\Part03 и сохраним его под именем TestDoEasyPart09.mq4 в папке MQL4-советников \MQL4\Experts\TestDoEasy\Part09.
Советник без каких-либо изменений компилируется, но если поглядеть в код, то окажется, что он использует список сделок, которого нет в MQL4:
//--- enums enum ENUM_TYPE_ORDERS { TYPE_ORDER_MARKET, // Market-orders TYPE_ORDER_PENDING, // Pending orders TYPE_ORDER_DEAL // Deals }; //--- input parameters
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- обновляем историю history.Refresh(); //--- получаем список-коллекцию в диапазоне дат CArrayObj* list=history.GetListByTime(InpTimeBegin,InpTimeEnd,SELECT_BY_TIME_CLOSE); if(list==NULL) { Print("Could not get collection list"); return INIT_FAILED; } int total=list.Total(); for(int i=0;i<total;i++) { //--- получаем ордер из списка COrder* order=list.At(i); if(order==NULL) continue; //--- если это сделка if(order.Status()==ORDER_STATUS_DEAL && InpOrderType==TYPE_ORDER_DEAL) order.Print(); //--- если это исторический маркет-ордер if(order.Status()==ORDER_STATUS_HISTORY_ORDER && InpOrderType==TYPE_ORDER_MARKET) order.Print(); //--- если это удалённый отложенный ордер if(order.Status()==ORDER_STATUS_HISTORY_PENDING && InpOrderType==TYPE_ORDER_PENDING) order.Print(); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Просто заменим сделки на балансовые операции. В данном случае будем прямо в советнике использовать условную компиляцию, что не правильно для конечного продукта, где все действия по разграничению по версиям языка должны быть скрыты от пользователя. Но мы же просто тестируем результат доработки библиотеки, так что это не критично.
Добавим небольшие изменения в код советника, где сделки MQL5 заменим на балансовые операции MQL4:
//+------------------------------------------------------------------+ //| TestDoEasyPart03_1.mq4 | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/ru/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/ru/users/artmedia70" #property version "1.00" //--- includes #include <DoEasy\Collections\HistoryCollection.mqh> //--- enums enum ENUM_TYPE_ORDERS { TYPE_ORDER_MARKET, // Market-orders TYPE_ORDER_PENDING, // Pending orders #ifdef __MQL5__ TYPE_ORDER_DEAL // Deals #else TYPE_ORDER_BALANCE // Balance/Credit #endif }; //--- input parameters input ENUM_TYPE_ORDERS InpOrderType = TYPE_ORDER_MARKET; // Show type: input datetime InpTimeBegin = 0; // Start date of required range input datetime InpTimeEnd = END_TIME; // End date of required range //--- global variables CHistoryCollection history; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- обновляем историю history.Refresh(); //--- получаем список-коллекцию в диапазоне дат CArrayObj* list=history.GetListByTime(InpTimeBegin,InpTimeEnd,SELECT_BY_TIME_CLOSE); if(list==NULL) { Print("Could not get collection list"); return INIT_FAILED; } int total=list.Total(); for(int i=0;i<total;i++) { //--- получаем ордер из списка COrder* order=list.At(i); if(order==NULL) continue; //--- если это сделка #ifdef __MQL5__ if(order.Status()==ORDER_STATUS_DEAL && InpOrderType==TYPE_ORDER_DEAL) order.Print(); #else //--- если это балансовая/кредитная операция if(order.Status()==ORDER_STATUS_BALANCE && InpOrderType==TYPE_ORDER_BALANCE) order.Print(); #endif //--- если это исторический маркет-ордер if(order.Status()==ORDER_STATUS_HISTORY_ORDER && InpOrderType==TYPE_ORDER_MARKET) order.Print(); //--- если это удалённый отложенный ордер if(order.Status()==ORDER_STATUS_HISTORY_PENDING && InpOrderType==TYPE_ORDER_PENDING) order.Print(); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+
Скомпилируем и запустим советник в терминале (данный тестовый советник из третьей статьи работает только в обработчике OnInit(), поэтому он единожды будет выводить требуемый список исторической коллекции после запуска, либо после смены списка в настройках).
Перед запуском советника, желательно в терминале во вкладке "История счёта" из контекстного меню правой кнопки мыши выбрать пункт "Вся история", так как в MetaTrader 4 количество доступной программам истории зависит от того, какой размер истории был выбран в этой вкладке.
В настройках выбран "Balance/Credit", и самое первое пополнение баланса отображается в журнале:
Теперь нужно проверить правильность поиска и отображения закрытых позиций. Так как счёт на MetaTrader 4 у меня был недавно открытым, то никакой торговли на нём не было. Я открыл позицию Sell, выставил ей StopLoss и TakeProfit и ушёл варить кофе. Вернулся — позиция закрыта по стопу, ну и далее рынок ушёл в направлении открытой позиции на продажу — как всегда :)
Но зато теперь есть одна закрытая позиция для теста.
В настройках выбран "Market-orders":
Теперь проверим список удалённых отложенных ордеров. Я выставил пару ордеров, а затем их удалил.
В настройках выбран "Pending orders":
Список удалённых отложенных ордеров также отображается.
Что дальше
В следующей статье сделаем работу с рыночными позициями и активными отложенными ордерами в MQL4.
Ниже прикреплены все файлы текущей версии библиотеки и файлы тестового
советника. Их можно скачать и протестировать всё самостоятельно.
При возникновении вопросов, замечаний и пожеланий вы можете озвучить их в комментариях к статье.
Статьи этой серии:
Часть 1. Концепция, организация данных.
Часть 2. Коллекция исторических ордеров и сделок.
Часть 3. Коллекция рыночных ордеров и позиций, организация поиска.
Часть 4. Торговые события. Концепция.
Часть 5. Классы и коллекция торговых событий. Отправка событий в программу.
Часть 6. События на счёте с типом неттинг.
Часть 7. События срабатывания StopLimit-ордеров, подготовка функционала для регистрации событий модификации ордеров и позиций.
Часть 8. События модификации ордеров и позиций.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Постепенно в библиотеку будет добавлено столько функционала, что работать над алгоритмами будет очень просто - так, как вы и хотите. И именно для таких целей она и задумывалась.
Сейчас же, пока нет там такого функционала, вы можете посмотреть как в тестовом советнике сделана работа с торговым классом CTrade , включенного в стандартную библиотеку в MQL5, и писать похожие конструкции для вызова нужных торговых функций. Там же (в тестовом советнике) есть вызов тестерных торговых функций для MQL4.
Спасибо, буду изучать.
Добрый день. Понравился Ваш пробный советник. Хочу попробовать использовать его в качестве ядра, которое будет принимать сигналы и фильтры от различных индикаторов, их сочетаний, либо управляться вручную, через нажатия кнопок.
Первый из таких советников вы уже видели и помогали мне вдохнуть в него жизнь на соседней ветке этого форума.
Можете ли вы показать в этом Вашем пробном советнике, как можно програмно нажимать на кнопки???
Есть есть подходящая функция - можете ли Вы ею поделиться?
Или подскажите как это лучше всего сделать, плиз.
Добрый день!
Sergey, поддержу вас, потому что вижу вы в схожей ситуации.
Да, статьи замечательные, но в них очень мало информации о том, как использовать написанный код. Библиотеки, вообще говоря, ценны сокрытием реализации и предоставлением понятного интерфейса для выполнения практических задач. В справке функции https://docs.mql4.com/strings/stringsubstr нет ни слова о ее внутреннем устройстве. Описание входящих параметров, результат их обработки и пример(ы). Вот что хотелось бы видеть.
Да, Артем, вы несомненно талантливый программист, но прикладникам нужно максимально быстро разработать очередной алгоритм, а не корпеть часами над сотнями строк чужого кода в поисках просветления. Цикл статей пока что больше теоретический.
Это уже не первый мой пост на эту тему ). Ни в коем случае не хочу умалить достоинств серии. Напротив - надеюсь, Артем, вы примите во внимание просьбы форумчан и написанные библиотеки будут использоваться в советниках с такой же охотой, как разбирают на цитаты хорошие фильмы.
Добрый день!
Sergey, поддержу вас, потому что вижу вы в схожей ситуации.
Да, статьи замечательные, но в них очень мало информации о том, как использовать написанный код. Библиотеки, вообще говоря, ценны сокрытием реализации и предоставлением понятного интерфейса для выполнения практических задач. В справке функции https://docs.mql4.com/strings/stringsubstr нет ни слова о ее внутреннем устройстве. Описание входящих параметров, результат их обработки и пример(ы). Вот что хотелось бы видеть.
Да, Артем, вы несомненно талантливый программист, но прикладникам нужно решать практические задачи, а не корпеть часами над сотнями строк чужого кода в поисках просветления. Цикл статей пока что больше теоретический.
Это уже не первый мой пост на эту тему ). Ни в коем случае не хочу умалить достоинств серии. Напротив - надеюсь, Артем, вы примите во внимание просьбы форумчан и написанные библиотеки будут использоваться в советниках с такой же охотой, как разбирают на цитаты хорошие фильмы.
Цель - провести читателя от начала создания библиотеки до её завершения.
Понимаете - статьи имеют больше обучающий характер, неся при этом полезную практическую цель, и не одну. Конструкция кодов - доступная для понимания, без использования изворотов и недокументированных возможностей ради изворотов и "крутизны". Зато есть неоспоримый плюс - сколько вышло бета-версий терминала, и сколько людей уже сказали, что их коды перестали работать, а библиотека - живёт от билда к билду без вынужденных исправлений из-за того, что что-то вдруг работать перестало...
Библиотека на данный момент имеет одну точку входа - класс CEngine (будет ещё вторая точка входа, но много позже), и объект этого класса в советнике даёт полный доступ ко всем возможностям.
А делее - не сложно создать такой объект, например: CEngine lib; и в коде набрать lib и поставить точку (вот так: lib.) - после точки редактор покажет вам окно со списком всех доступных для использования методов библиотеки. В основном у них осмысленные названия - при небольшой практике можно применять. Все методы описаны в статьях. В каждой статье - пример тестовой программы, отображающей, впрочем, малую часть возможностей.
Согласен - искать показанные методы и их применение по многочисленным статьям, не имея на руках справочного материала - то ещё занятие... Но цикл статей - на то и цикл, чтобы читатель прошёл его вместе со мной, и тогда уж что-то, да отложится в голове :) А цель, напомню - обучающая.
Справочный материал будет. Но в самом конце - когда будет создана библиотека. И примеры, конечно же тоже.
А пока - можно задавать практические вопросы. Показывать часть своего кода, и я подскажу. Я тут, и никуда не собираюсь уходить - не в моих правилах бросать начатое.
Цель - провести читателя от начала создания библиотеки до её завершения.
Понимаете - статьи имеют больше обучающий характер, неся при этом полезную практическую цель, и не одну. Конструкция кодов - доступная для понимания, без использования изворотов и недокументированных возможностей ради изворотов и "крутизны". Зато есть неоспоримый плюс - сколько вышло бета-версий терминала, и сколько людей уже сказали, что их коды перестали работать, а библиотека - живёт от билда к билду без вынужденных исправлений из-за того, что что-то вдруг работать перестало...
Библиотека на данный момент имеет одну точку входа - класс CEngine (будет ещё вторая точка входа, но много позже), и объект этого класса в советнике даёт полный доступ ко всем возможностям.
А делее - не сложно создать такой объект, например: CEngine lib; и в коде набрать lib и поставить точку (вот так: lib.) - после точки редактор покажет вам окно со списком всех доступных для использования методов библиотеки. В основном у них осмысленные названия - при небольшой практике можно применять. Все методы описаны в статьях. В каждой статье - пример тестовой программы, отображающей, впрочем, малую часть возможностей.
Согласен - искать показанные методы и их применение по многочисленным статьям, не имея на руках справочного материала - то ещё занятие... Но цикл статей - на то и цикл, чтобы читатель прошёл его вместе со мной, и тогда уж что-то, да отложится в голове :) А цель, напомню - обучающая.
Справочный материал будет. Но в самом конце - когда будет создана библиотека. И примеры, конечно же тоже.
А пока - можно задавать практические вопросы. Показывать часть своего кода, и я подскажу. Я тут, и никуда не собираюсь уходить - не в моих правилах бросать начатое.
я понимаю, намерения у вас самые добрые и, наверное, много свободного времени )
Просто увидев ваши статьи серии "ДелатьПростой"[Библиотека для простого и быстрого создания программ для MetaTrader], я решил, что через 10-15 минут чтения уже смогу применять полезный код. Ожидал увидеть классическую статью вроде https://www.mql5.com/ru/articles/272, где скрыта логика и открыт интерфейс, даны ответы на вопросы: "зачем это нужно", "как с этим работать", приведены примеры. Оказалось, что цель - обучение, а не RAD (быстрая разработка).
Что же, ждем с нетерпением когда и вы такую напишете! ))
я понимаю, намерения у вас самые добрые и, наверное, много свободного времени )
Просто увидев ваши статьи серии "ДелатьПростой"[Библиотека для простого и быстрого создания программ для MetaTrader], я решил, что через 10-15 минут чтения уже смогу применять полезный код. Ожидал увидеть классическую статью вроде https://www.mql5.com/ru/articles/272, где скрыта логика и открыт интерфейс, даны ответы на вопросы: "зачем это нужно", "как с этим работать", приведены примеры. Оказалось, что цель - обучение, а не RAD (быстрая разработка).
Что же, ждем с нетерпением когда и вы такую напишете! ))
Цель - обучение + быстрая разработка. О быстрой разработке стоит лишь задавать вопросы практического применения, если читать лень, и пока нет справочного материала с примерами.
Название скорее переводится как "Делай легко". (Английский.., позволяет переводить как угодно, если нет контекста)