English 中文 Español Deutsch 日本語 Português
Библиотека для простого и быстрого создания программ для MetaTrader (Часть IX): Совместимость с MQL4 - Подготовка данных

Библиотека для простого и быстрого создания программ для MetaTrader (Часть IX): Совместимость с MQL4 - Подготовка данных

MetaTrader 5Примеры | 17 мая 2019, 08:50
2 097 9
Artyom Trishkin
Artyom Trishkin

Содержание

В прошлых частях описания кроссплатформенной библиотеки для 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

Тип сделки

ENUM_DEAL_TYPE

DEAL_ENTRY

Направление сделки – вход в рынок, выход из рынка или разворот

ENUM_DEAL_ENTRY

DEAL_MAGIC

Magic number для сделки (смотри ORDER_MAGIC)

long

DEAL_REASON

Причина или источник проведения сделки

ENUM_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. События модификации ордеров и позиций.


Прикрепленные файлы |
MQL5.zip (95.04 KB)
MQL4.zip (95.04 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (9)
Sergey Voytsekhovsky
Sergey Voytsekhovsky | 21 мая 2019 в 12:53
Artyom Trishkin:

Постепенно в библиотеку будет добавлено столько функционала, что работать над алгоритмами будет очень просто - так, как вы и хотите. И именно для таких целей она и задумывалась.

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

Спасибо, буду изучать.

Nikolai Karetnikov
Nikolai Karetnikov | 19 июн. 2020 в 20:58
Sergey Voytsekhovsky:

Добрый день. Понравился Ваш пробный советник. Хочу попробовать использовать его в качестве ядра, которое будет принимать сигналы и фильтры от различных индикаторов, их сочетаний, либо управляться вручную, через нажатия кнопок.

Первый из таких советников вы уже видели и помогали мне вдохнуть в него жизнь на соседней ветке этого форума.

Можете ли вы показать в этом Вашем пробном советнике, как можно програмно нажимать на кнопки???

Есть есть подходящая функция - можете ли Вы ею поделиться?

Или подскажите как это лучше всего сделать, плиз.

Добрый день! 

Sergey, поддержу вас, потому что вижу вы в схожей ситуации. 

Да, статьи замечательные, но в них очень мало информации о том, как использовать написанный код. Библиотеки, вообще говоря, ценны сокрытием реализации и предоставлением понятного интерфейса для выполнения практических задач. В справке функции https://docs.mql4.com/strings/stringsubstr нет ни слова о ее внутреннем устройстве. Описание входящих параметров, результат их обработки и пример(ы). Вот что хотелось бы видеть. 

Да, Артем,  вы несомненно талантливый программист, но прикладникам нужно максимально быстро разработать очередной алгоритм, а не корпеть часами над сотнями строк чужого кода в поисках просветления. Цикл статей пока что больше теоретический.

Это уже не первый мой пост на эту тему ). Ни в коем случае не хочу умалить достоинств серии. Напротив - надеюсь, Артем, вы примите во внимание просьбы форумчан и написанные библиотеки будут использоваться в советниках с такой же охотой, как разбирают на цитаты хорошие фильмы. 

Artyom Trishkin
Artyom Trishkin | 19 июн. 2020 в 21:29
Nikolai Karetnikov:

Добрый день! 

Sergey, поддержу вас, потому что вижу вы в схожей ситуации. 

Да, статьи замечательные, но в них очень мало информации о том, как использовать написанный код. Библиотеки, вообще говоря, ценны сокрытием реализации и предоставлением понятного интерфейса для выполнения практических задач. В справке функции https://docs.mql4.com/strings/stringsubstr нет ни слова о ее внутреннем устройстве. Описание входящих параметров, результат их обработки и пример(ы). Вот что хотелось бы видеть. 

Да, Артем,  вы несомненно талантливый программист, но прикладникам нужно решать практические задачи, а не корпеть часами над сотнями строк чужого кода в поисках просветления. Цикл статей пока что больше теоретический.

Это уже не первый мой пост на эту тему ). Ни в коем случае не хочу умалить достоинств серии. Напротив - надеюсь, Артем, вы примите во внимание просьбы форумчан и написанные библиотеки будут использоваться в советниках с такой же охотой, как разбирают на цитаты хорошие фильмы. 

Цель - провести читателя от начала создания библиотеки до её завершения.
Понимаете - статьи имеют больше обучающий характер, неся при этом полезную практическую цель, и не одну. Конструкция кодов - доступная для понимания, без использования изворотов и недокументированных возможностей ради изворотов и "крутизны". Зато есть неоспоримый плюс - сколько вышло бета-версий терминала, и сколько людей уже сказали, что их коды перестали работать, а библиотека - живёт от билда к билду без вынужденных исправлений из-за того, что что-то вдруг работать перестало...

Библиотека на данный момент имеет одну точку входа - класс CEngine (будет ещё вторая точка входа, но много позже), и объект этого класса в советнике даёт полный доступ ко всем возможностям.

А делее - не сложно создать такой объект, например: CEngine lib; и в коде набрать lib и поставить точку (вот так: lib.) - после точки редактор покажет вам окно со списком всех доступных для использования методов библиотеки. В основном у них осмысленные названия - при небольшой практике можно применять. Все методы описаны в статьях. В каждой статье - пример тестовой программы, отображающей, впрочем, малую часть возможностей.

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

Справочный материал будет. Но в самом конце - когда будет создана библиотека. И примеры, конечно же тоже.

А пока - можно задавать практические вопросы. Показывать часть своего кода, и я подскажу. Я тут, и никуда не собираюсь уходить - не в моих правилах бросать начатое.

Nikolai Karetnikov
Nikolai Karetnikov | 17 июл. 2020 в 21:54
Artyom Trishkin:

Цель - провести читателя от начала создания библиотеки до её завершения.
Понимаете - статьи имеют больше обучающий характер, неся при этом полезную практическую цель, и не одну. Конструкция кодов - доступная для понимания, без использования изворотов и недокументированных возможностей ради изворотов и "крутизны". Зато есть неоспоримый плюс - сколько вышло бета-версий терминала, и сколько людей уже сказали, что их коды перестали работать, а библиотека - живёт от билда к билду без вынужденных исправлений из-за того, что что-то вдруг работать перестало...

Библиотека на данный момент имеет одну точку входа - класс CEngine (будет ещё вторая точка входа, но много позже), и объект этого класса в советнике даёт полный доступ ко всем возможностям.

А делее - не сложно создать такой объект, например: CEngine lib; и в коде набрать lib и поставить точку (вот так: lib.) - после точки редактор покажет вам окно со списком всех доступных для использования методов библиотеки. В основном у них осмысленные названия - при небольшой практике можно применять. Все методы описаны в статьях. В каждой статье - пример тестовой программы, отображающей, впрочем, малую часть возможностей.

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

Справочный материал будет. Но в самом конце - когда будет создана библиотека. И примеры, конечно же тоже.

А пока - можно задавать практические вопросы. Показывать часть своего кода, и я подскажу. Я тут, и никуда не собираюсь уходить - не в моих правилах бросать начатое.

я понимаю, намерения у вас самые добрые и, наверное, много свободного времени )

Просто увидев ваши статьи серии "ДелатьПростой"[Библиотека для простого и быстрого создания программ для MetaTrader], я решил, что через 10-15 минут чтения уже смогу применять полезный код. Ожидал увидеть классическую статью вроде https://www.mql5.com/ru/articles/272, где скрыта логика и открыт интерфейс, даны ответы на вопросы: "зачем это нужно", "как с этим работать", приведены примеры. Оказалось, что цель - обучение, а не RAD (быстрая разработка). 

Что же, ждем с нетерпением когда и вы такую напишете! ))

Artyom Trishkin
Artyom Trishkin | 17 июл. 2020 в 22:23
Nikolai Karetnikov:

я понимаю, намерения у вас самые добрые и, наверное, много свободного времени )

Просто увидев ваши статьи серии "ДелатьПростой"[Библиотека для простого и быстрого создания программ для MetaTrader], я решил, что через 10-15 минут чтения уже смогу применять полезный код. Ожидал увидеть классическую статью вроде https://www.mql5.com/ru/articles/272, где скрыта логика и открыт интерфейс, даны ответы на вопросы: "зачем это нужно", "как с этим работать", приведены примеры. Оказалось, что цель - обучение, а не RAD (быстрая разработка). 

Что же, ждем с нетерпением когда и вы такую напишете! ))

Цель - обучение + быстрая разработка. О быстрой разработке стоит лишь задавать вопросы практического применения, если читать лень, и пока нет справочного материала с примерами.

Название скорее переводится как "Делай легко". (Английский.., позволяет переводить как угодно, если нет контекста)

Создание графических интерфейсов на базе .Net Framework и C# (Часть 2): Дополнительные графические элементы Создание графических интерфейсов на базе .Net Framework и C# (Часть 2): Дополнительные графические элементы
Статья является логическим продолжением предыдущей публикации "Создание графических интерфейсов для экспертов и индикаторов на базе .Net Framework и C#" и знакомит читателей с новыми графическими элементами для создания графических интерфейсов.
Применение OLAP в трейдинге (Часть 2): Визуализация результатов интерактивного анализа многомерных данных Применение OLAP в трейдинге (Часть 2): Визуализация результатов интерактивного анализа многомерных данных
В статье рассматриваются различные аспекты создания интерактивного графического интерфейса MQL-программы, предназначенной для OLAP-обработки истории счета и торговых отчетов. Для получения наглядного результата используются максимизируемые и масштабируемые окна, адаптивная раскладка "резиновых" элементов управления, новый "контрол" для вывода диаграмм. На основе этого реализован GUI с выбором показателей по координатным осям, агрегатных функций, типов графиков и сортировок.
Библиотека для простого и быстрого создания программ для MetaTrader (Часть X): Совместимость с MQL4 - События открытия позиции и активации отложенных ордеров Библиотека для простого и быстрого создания программ для MetaTrader (Часть X): Совместимость с MQL4 - События открытия позиции и активации отложенных ордеров
В предыдущих статьях мы начали создавать большую кроссплатформенную библиотеку, целью которой является упростить написание программ для платформ MetaTrader 5 и MetaTrader 4. В девятой части начали дорабатывать классы библиотеки для работы в MQL4. В данной статье продолжим доработку библиотеки с целью полной её совместимости с MQL4.
Библиотека для простого и быстрого создания программ для MetaTrader (Часть VIII): События модификации ордеров и позиций Библиотека для простого и быстрого создания программ для MetaTrader (Часть VIII): События модификации ордеров и позиций
В предыдущих статьях мы начали создавать большую кроссплатформенную библиотеку, целью которой является упростить написание программ для платформ MetaTrader 5 и MetaTrader 4. В седьмой части мы добавили отслеживание событий срабатывания StopLimit-ордеров и подготовили функционал для отслеживания остальных событий, происходящих с ордерами и позициями. В данной статье сделаем класс для отслеживания событий модификации рыночных ордеров и позиций.