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

Библиотека для простого и быстрого создания программ для MetaTrader (Часть XI). Совместимость с MQL4 - События закрытия позиций

MetaTrader 5Примеры | 31 мая 2019, 12:21
3 308 0
Artyom Trishkin
Artyom Trishkin

Содержание


Удаляем лишнее

Работая над определением событий, столкнулся с тем, что для MQL5 мы везде, где необходим выбор по времени, делаем его по времени в милисекундах. В MQL4 нет таких свойств у ордеров и позиций, но ничто не мешает нам использовать время в секундах, выраженное в милисекундах, для MQL4. Таким образом, приходим к пониманию, что любое время в секундах у нас просто дублируется временем в милисекундах, да ещё и нигде не используется. А при получении и отображении времени, когда оно требуется в секундах, получение его в милисекундах совершенно идентично, за исключением "хвостика" из трёх цифр, указывающих на количество милисекунд, в формате выводимого времени.

Поэтому было решено убрать из свойств ордера все свойства времени, выраженного в секундах, если у ордера есть такое же свойство, но в милисекундах.
Но раз мы решили что-то убавить, то неплохо было бы и что-то добавить — добавим каждому ордеру новое свойство "пользовательский комментарий". Его можно будет установить любому ордеру или позиции. Как открытым, так и уже закрытым/удалённым ордерам и позициям. В любое время. Зачем это нужно? Ну хотя бы для текстовых меток для ордеров, удовлетворяющим неким условиям. Например, для визуального отображения (далее в библиотеке будет своя собственная графическая оболочка), и помеченные текстовыми метками ордера будет легко показывать на графике при помощи всеразличных графических построений.

Откроем файл Defines.mqh, найдём при помощи Ctrl+F все свойства ордера, где есть время в секундах, и есть точно такое же свойство, но с окончанием "_MSC" — такое свойство выражено в милисекундах, и удалим "милисекундные" свойства ордера , оставив "секундные" свойства, и заменим количество целочисленных свойств с 24 на 21:

//+------------------------------------------------------------------+
//| Целочисленные свойства ордера, сделки, позиции                   |
//+------------------------------------------------------------------+
enum ENUM_ORDER_PROP_INTEGER
  {
   ORDER_PROP_TICKET = 0,                                   // Тикет ордера
   ORDER_PROP_MAGIC,                                        // Мэджик ордера
   ORDER_PROP_TIME_OPEN,                                    // Время открытия (MQL5 Время сделки)
   ORDER_PROP_TIME_CLOSE,                                   // Время закрытия (MQL5 Время исполнения или снятия - ORDER_TIME_DONE)
   ORDER_PROP_TIME_OPEN_MSC,                                // Время открытия в милисекундах (MQL5 Время сделки в мсек.)
   ORDER_PROP_TIME_CLOSE_MSC,                               // Время закрытия в милисекундах (MQL5 Время исполнения или снятия - ORDER_TIME_DONE_MSC)
   ORDER_PROP_TIME_EXP,                                     // Дата экспирации ордера (для отложенных ордеров)
   ORDER_PROP_STATUS,                                       // Статус ордера (из перечисления ENUM_ORDER_STATUS)
   ORDER_PROP_TYPE,                                         // Тип ордера/сделки
   ORDER_PROP_REASON,                                       // Причина или источник сделки/ордера/позиции
   ORDER_PROP_STATE,                                        // Состояние ордера (из перечисления ENUM_ORDER_STATE)
   ORDER_PROP_POSITION_ID,                                  // Идентификатор позиции
   ORDER_PROP_POSITION_BY_ID,                               // Идентификатор встречной позиции
   ORDER_PROP_DEAL_ORDER_TICKET,                            // Тикет ордера, на основании которого выполнена сделка
   ORDER_PROP_DEAL_ENTRY,                                   // Направление сделки – IN, OUT или IN/OUT
   ORDER_PROP_TIME_UPDATE,                                  // Время изменения позиции в секундах
   ORDER_PROP_TIME_UPDATE_MSC,                              // Время изменения позиции в милисекундах
   ORDER_PROP_TICKET_FROM,                                  // Тикет родительского ордера
   ORDER_PROP_TICKET_TO,                                    // Тикет дочернего ордера
   ORDER_PROP_PROFIT_PT,                                    // Профит в пунктах
   ORDER_PROP_CLOSE_BY_SL,                                  // Признак закрытия по StopLoss
   ORDER_PROP_CLOSE_BY_TP,                                  // Признак закрытия по TakeProfit
   ORDER_PROP_GROUP_ID,                                     // Идентификатор группы ордеров/позиций
   ORDER_PROP_DIRECTION,                                    // Тип по направлению (Buy, Sell)
  }; 
#define ORDER_PROP_INTEGER_TOTAL    (24)                    // Общее количество целочисленных свойств
#define ORDER_PROP_INTEGER_SKIP     (0)                     // Количество неиспользуемых в сортировке свойств ордера
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Целочисленные свойства ордера, сделки, позиции                   |
//+------------------------------------------------------------------+
enum ENUM_ORDER_PROP_INTEGER
  {
   ORDER_PROP_TICKET = 0,                                   // Тикет ордера
   ORDER_PROP_MAGIC,                                        // Мэджик ордера
   ORDER_PROP_TIME_OPEN,                                    // Время открытия в милисекундах (MQL5 Время сделки)
   ORDER_PROP_TIME_CLOSE,                                   // Время закрытия в милисекундах (MQL5 Время исполнения или снятия - ORDER_TIME_DONE)
   ORDER_PROP_TIME_EXP,                                     // Дата экспирации ордера (для отложенных ордеров)
   ORDER_PROP_STATUS,                                       // Статус ордера (из перечисления ENUM_ORDER_STATUS)
   ORDER_PROP_TYPE,                                         // Тип ордера/сделки
   ORDER_PROP_REASON,                                       // Причина или источник сделки/ордера/позиции
   ORDER_PROP_STATE,                                        // Состояние ордера (из перечисления ENUM_ORDER_STATE)
   ORDER_PROP_POSITION_ID,                                  // Идентификатор позиции
   ORDER_PROP_POSITION_BY_ID,                               // Идентификатор встречной позиции
   ORDER_PROP_DEAL_ORDER_TICKET,                            // Тикет ордера, на основании которого выполнена сделка
   ORDER_PROP_DEAL_ENTRY,                                   // Направление сделки – IN, OUT или IN/OUT
   ORDER_PROP_TIME_UPDATE,                                  // Время изменения позиции  в милисекундах
   ORDER_PROP_TICKET_FROM,                                  // Тикет родительского ордера
   ORDER_PROP_TICKET_TO,                                    // Тикет дочернего ордера
   ORDER_PROP_PROFIT_PT,                                    // Профит в пунктах
   ORDER_PROP_CLOSE_BY_SL,                                  // Признак закрытия по StopLoss
   ORDER_PROP_CLOSE_BY_TP,                                  // Признак закрытия по TakeProfit
   ORDER_PROP_GROUP_ID,                                     // Идентификатор группы ордеров/позиций
   ORDER_PROP_DIRECTION,                                    // Тип по направлению (Buy, Sell)
  }; 
#define ORDER_PROP_INTEGER_TOTAL    (21)                    // Общее количество целочисленных свойств
#define ORDER_PROP_INTEGER_SKIP     (0)                     // Количество неиспользуемых в сортировке свойств ордера
//+------------------------------------------------------------------+

Найдём перечисление возможных вариантов выбора по времени и удалим константы выбора в милисекундах:

//+------------------------------------------------------------------+
//| Возможные варианты выбора по времени                             |
//+------------------------------------------------------------------+
enum ENUM_SELECT_BY_TIME
  {
   SELECT_BY_TIME_OPEN,                                     // По времени открытия
   SELECT_BY_TIME_CLOSE,                                    // По времени закрытия
   SELECT_BY_TIME_OPEN_MSC,                                 // По времени открытия в милисекундах
   SELECT_BY_TIME_CLOSE_MSC,                                // По времени закрытия в милисекундах
  };
//+------------------------------------------------------------------+

Данное перечисление будет состоять только из двух констант:

//+------------------------------------------------------------------+
//| Возможные варианты выбора по времени                             |
//+------------------------------------------------------------------+
enum ENUM_SELECT_BY_TIME
  {
   SELECT_BY_TIME_OPEN,                                     // По времени открытия (в милисекундах)
   SELECT_BY_TIME_CLOSE,                                    // По времени закрытия (в милисекундах)
  };
//+------------------------------------------------------------------+

Теперь в любом случае при указании выбора по времени, выбор будет производиться по времени в милисекундах для MQL5 и в секундах для MQL4.

В строковые свойства ордера добавим новое свойство "пользовательский комментарий" и увеличим общее количество строковых свойств до 4:

//+------------------------------------------------------------------+
//| Строковые свойства ордера, сделки, позиции                       |
//+------------------------------------------------------------------+
enum ENUM_ORDER_PROP_STRING
  {
   ORDER_PROP_SYMBOL = (ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL), // Символ ордера
   ORDER_PROP_COMMENT,                                      // Комментарий ордера
   ORDER_PROP_COMMENT_EXT,                                  // Пользовательский комментарий ордера
   ORDER_PROP_EXT_ID                                        // Идентификатор ордера во внешней торговой системе
  };
#define ORDER_PROP_STRING_TOTAL     (4)                     // Общее количество строковых свойств
//+------------------------------------------------------------------+

В перечислении возможных критериев сортировки удалим все упоминания о милисекундах — они у нас теперь используются по умолчанию при сортировке по времени, и добавим критерий сортировки по пользовательскому комментарию:

//+------------------------------------------------------------------+
//| Возможные критерии сортировки ордеров и сделок                   |
//+------------------------------------------------------------------+
#define FIRST_ORD_DBL_PROP          (ORDER_PROP_INTEGER_TOTAL-ORDER_PROP_INTEGER_SKIP)
#define FIRST_ORD_STR_PROP          (ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL-ORDER_PROP_INTEGER_SKIP)
enum ENUM_SORT_ORDERS_MODE
  {
   //--- Сортировка по целочисленным свойствам
   SORT_BY_ORDER_TICKET          =  0,                      // Сортировать по тикету ордера
   SORT_BY_ORDER_MAGIC           =  1,                      // Сортировать по магику ордера
   SORT_BY_ORDER_TIME_OPEN       =  2,                      // Сортировать по времени открытия ордера в милисекундах
   SORT_BY_ORDER_TIME_CLOSE      =  3,                      // Сортировать по времени закрытия ордера в милисекундах
   SORT_BY_ORDER_TIME_EXP        =  4,                      // Сортировать по дате экспирации ордера
   SORT_BY_ORDER_STATUS          =  5,                      // Сортировать по статусу ордера (маркет-ордер/отложенный ордер/сделка/балансовая,кредитная операция)
   SORT_BY_ORDER_TYPE            =  6,                      // Сортировать по типу ордера
   SORT_BY_ORDER_REASON          =  7,                      // Сортировать по причине/источнику сделки/ордера/позиции
   SORT_BY_ORDER_STATE           =  8,                     // Сортировать по состоянию ордера
   SORT_BY_ORDER_POSITION_ID     =  9,                     // Сортировать по идентификатору позиции
   SORT_BY_ORDER_POSITION_BY_ID  =  10,                     // Сортировать по идентификатору встречной позиции
   SORT_BY_ORDER_DEAL_ORDER      =  11,                     // Сортировать по ордеру, на основание которого выполнена сделка
   SORT_BY_ORDER_DEAL_ENTRY      =  12,                     // Сортировать по направлению сделки – IN, OUT или IN/OUT
   SORT_BY_ORDER_TIME_UPDATE     =  13,                     // Сортировать по времени изменения позиции в секундах
   SORT_BY_ORDER_TICKET_FROM     =  14,                     // Сортировать по тикету родительского ордера
   SORT_BY_ORDER_TICKET_TO       =  15,                     // Сортировать по тикету дочернего ордера
   SORT_BY_ORDER_PROFIT_PT       =  16,                     // Сортировать по профиту ордера в пунктах
   SORT_BY_ORDER_CLOSE_BY_SL     =  17,                     // Сортировать по признаку закрытия ордера по StopLoss
   SORT_BY_ORDER_CLOSE_BY_TP     =  18,                     // Сортировать по признаку закрытия ордера по TakeProfit
   SORT_BY_ORDER_GROUP_ID        =  19,                     // Сортировать по идентификатору группы ордеров/позиций
   SORT_BY_ORDER_DIRECTION       =  20,                     // Сортировать по направлению (Buy, Sell)
   //--- Сортировка по вещественным свойствам
   SORT_BY_ORDER_PRICE_OPEN      =  FIRST_ORD_DBL_PROP,     // Сортировать по цене открытия
   SORT_BY_ORDER_PRICE_CLOSE     =  FIRST_ORD_DBL_PROP+1,   // Сортировать по цене закрытия
   SORT_BY_ORDER_SL              =  FIRST_ORD_DBL_PROP+2,   // Сортировать по цене StopLoss
   SORT_BY_ORDER_TP              =  FIRST_ORD_DBL_PROP+3,   // Сортировать по цене TaleProfit
   SORT_BY_ORDER_PROFIT          =  FIRST_ORD_DBL_PROP+4,   // Сортировать по профиту
   SORT_BY_ORDER_COMMISSION      =  FIRST_ORD_DBL_PROP+5,   // Сортировать по комиссии
   SORT_BY_ORDER_SWAP            =  FIRST_ORD_DBL_PROP+6,   // Сортировать по свопу
   SORT_BY_ORDER_VOLUME          =  FIRST_ORD_DBL_PROP+7,   // Сортировать по объёму
   SORT_BY_ORDER_VOLUME_CURRENT  =  FIRST_ORD_DBL_PROP+8,   // Сортировать по невыполненному объему
   SORT_BY_ORDER_PROFIT_FULL     =  FIRST_ORD_DBL_PROP+9,   // Сортировать по критерию профит+комиссия+своп
   SORT_BY_ORDER_PRICE_STOP_LIMIT=  FIRST_ORD_DBL_PROP+10,  // Сортировать по цене постановки Limit ордера при срабатывании StopLimit ордера
   //--- Сортировка по строковым свойствам
   SORT_BY_ORDER_SYMBOL          =  FIRST_ORD_STR_PROP,     // Сортировать по символу
   SORT_BY_ORDER_COMMENT         =  FIRST_ORD_STR_PROP+1,   // Сортировать по комментарию
   SORT_BY_ORDER_COMMENT_EXT     =  FIRST_ORD_STR_PROP+2,   // Сортировать по пользовательскому комментарию
   SORT_BY_ORDER_EXT_ID          =  FIRST_ORD_STR_PROP+3    // Сортировать по идентификатору ордера во внешней торговой системе
  };
//+------------------------------------------------------------------+

На этом изменения в Defines.mqh завершены. Теперь нам необходимо убрать все ссылки на удалённые свойства ордера в файлах библиотеки:

во всех файлах библиотеки все вхождения режимов сортировки

SORT_BY_ORDER_TIME_OPEN_MSC

и

SORT_BY_ORDER_TIME_CLOSE_MSC

заменим на

SORT_BY_ORDER_TIME_OPEN

и

SORT_BY_ORDER_TIME_CLOSE

В файлах классов-наследников абстрактного ордера HistoryDeal.mqh, HistoryOrder.mqh, HistoryPending.mqh, MarketOrder.mqh, MarketPending.mqh и MarketPosition.mqh нам нужно удалить все упоминания о милисекундных свойствах ордеров (они теперь по умолчанию милисекундные):

ORDER_PROP_TIME_CLOSE_MSC

и
ORDER_PROP_TIME_UPDATE_MSC

В файле Order.mqh класса абстрактного ордера COrder, из приватной секции удалим методы, возвращающие время в секундах:

   datetime          OrderOpenTime(void)           const;
   datetime          OrderCloseTime(void)          const;
   datetime          OrderExpiration(void)         const;
   datetime          PositionTimeUpdate(void)      const;
   datetime          PositionTimeUpdateMSC(void)   const;

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

//+------------------------------------------------------------------+
//| Методы упрощённого доступа к свойствам объекта-ордера            |
//+------------------------------------------------------------------+
   //--- Возвращает (1) тикет, (2) тикет родительского ордера, (3) тикет дочернего ордера, (4) магик, (5) причину выставления ордера,
   //--- (6) идентификатор позиции, (7) идентификатор встречной позиции, (8) идентификатор группы, (9) тип, (10) флаг закрытия по StopLoss,
   //--- (11) флаг закрытия по TakeProfit (12) время открытия, (13) время закрытия, (14) время открытия в милисекундах,
   //--- (15) время закрытия в милисекундах, (16) дату экспирации, (17) состояние, (18) статус (19) тип ордера по направлению
   long              Ticket(void)                                       const { return this.GetProperty(ORDER_PROP_TICKET);                     }
   long              TicketFrom(void)                                   const { return this.GetProperty(ORDER_PROP_TICKET_FROM);                }
   long              TicketTo(void)                                     const { return this.GetProperty(ORDER_PROP_TICKET_TO);                  }
   long              Magic(void)                                        const { return this.GetProperty(ORDER_PROP_MAGIC);                      }
   long              Reason(void)                                       const { return this.GetProperty(ORDER_PROP_REASON);                     }
   long              PositionID(void)                                   const { return this.GetProperty(ORDER_PROP_POSITION_ID);                }
   long              PositionByID(void)                                 const { return this.GetProperty(ORDER_PROP_POSITION_BY_ID);             }
   long              GroupID(void)                                      const { return this.GetProperty(ORDER_PROP_GROUP_ID);                   }
   long              TypeOrder(void)                                    const { return this.GetProperty(ORDER_PROP_TYPE);                       }
   bool              IsCloseByStopLoss(void)                            const { return (bool)this.GetProperty(ORDER_PROP_CLOSE_BY_SL);          }
   bool              IsCloseByTakeProfit(void)                          const { return (bool)this.GetProperty(ORDER_PROP_CLOSE_BY_TP);          }
   datetime          TimeOpen(void)                                     const { return (datetime)this.GetProperty(ORDER_PROP_TIME_OPEN);        }
   datetime          TimeClose(void)                                    const { return (datetime)this.GetProperty(ORDER_PROP_TIME_CLOSE);       }
   datetime          TimeOpenMSC(void                                 const { return (datetime)this.GetProperty(ORDER_PROP_TIME_OPEN_MSC);    }
   datetime          TimeCloseMSC(void)                                 const { return (datetime)this.GetProperty(ORDER_PROP_TIME_CLOSE_MSC);   }
   datetime          TimeExpiration(void)                               const { return (datetime)this.GetProperty(ORDER_PROP_TIME_EXP);         }
   ENUM_ORDER_STATE  State(void)                                        const { return (ENUM_ORDER_STATE)this.GetProperty(ORDER_PROP_STATE);    }
   ENUM_ORDER_STATUS Status(void)                                       const { return (ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS);  }
   ENUM_ORDER_TYPE   TypeByDirection(void)                              const { return (ENUM_ORDER_TYPE)this.GetProperty(ORDER_PROP_DIRECTION); }
   
   //--- Возвращает (1) цену открытия, (2) цену закрытия, (3) профит, (4) комиссию, (5) своп, (6) объём, 

Там же добавим методы, возвращающий и устанавливающий пользовательский комментарий ордера:

   //--- Возвращает (1) символ, (2) комментарий, (3) идентификатор на бирже
   string            Symbol(void)                                       const { return this.GetProperty(ORDER_PROP_SYMBOL);                     }
   string            Comment(void)                                      const { return this.GetProperty(ORDER_PROP_COMMENT);                    }
   string            CommentExt(void)                                   const { return this.GetProperty(ORDER_PROP_COMMENT_EXT);                }
   string            ExternalID(void)                                   const { return this.GetProperty(ORDER_PROP_EXT_ID);                     }

   //--- Возвращает полный профит ордера
   double            ProfitFull(void)                                   const { return this.Profit()+this.Comission()+this.Swap();              }
   //--- Возвращает профит ордера в пунктах
   int               ProfitInPoints(void) const;
//--- Устанавливает (1) идентификатор группы, (2) пользовательский комментарий
   void              SetGroupID(const long group_id)                          { this.SetProperty(ORDER_PROP_GROUP_ID,group_id);                 }
   void              SetCommentExt(const string comment_ext)                  { this.SetProperty(ORDER_PROP_COMMENT_EXT,comment_ext);           }
   

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

//+------------------------------------------------------------------+
//| Закрытый параметрический конструктор                             |
//+------------------------------------------------------------------+
COrder::COrder(ENUM_ORDER_STATUS order_status,const ulong ticket)
  {
//--- Сохранение целочисленных свойств
   this.m_ticket=ticket;
   this.m_long_prop[ORDER_PROP_STATUS]                               = order_status;
   this.m_long_prop[ORDER_PROP_MAGIC]                                = this.OrderMagicNumber();
   this.m_long_prop[ORDER_PROP_TICKET]                               = this.OrderTicket();
   this.m_long_prop[ORDER_PROP_TIME_EXP]                             = this.OrderExpiration();
   this.m_long_prop[ORDER_PROP_TYPE]                                 = this.OrderType();
   this.m_long_prop[ORDER_PROP_STATE]                                = this.OrderState();
   this.m_long_prop[ORDER_PROP_DIRECTION]                            = this.OrderTypeByDirection();
   this.m_long_prop[ORDER_PROP_POSITION_ID]                          = this.OrderPositionID();
   this.m_long_prop[ORDER_PROP_REASON]                               = this.OrderReason();
   this.m_long_prop[ORDER_PROP_DEAL_ORDER_TICKET]                    = this.DealOrderTicket();
   this.m_long_prop[ORDER_PROP_DEAL_ENTRY]                           = this.DealEntry();
   this.m_long_prop[ORDER_PROP_POSITION_BY_ID]                       = this.OrderPositionByID();
   this.m_long_prop[ORDER_PROP_TIME_OPEN]                            = this.OrderOpenTimeMSC();     
   this.m_long_prop[ORDER_PROP_TIME_CLOSE]                           = this.OrderCloseTimeMSC();    
   this.m_long_prop[ORDER_PROP_TIME_UPDATE]                          = this.PositionTimeUpdateMSC();

   
//--- Сохранение вещественных свойств
   this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_OPEN)]         = this.OrderOpenPrice();
   this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_CLOSE)]        = this.OrderClosePrice();
   this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT)]             = this.OrderProfit();
   this.m_double_prop[this.IndexProp(ORDER_PROP_COMMISSION)]         = this.OrderCommission();
   this.m_double_prop[this.IndexProp(ORDER_PROP_SWAP)]               = this.OrderSwap();
   this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME)]             = this.OrderVolume();
   this.m_double_prop[this.IndexProp(ORDER_PROP_SL)]                 = this.OrderStopLoss();
   this.m_double_prop[this.IndexProp(ORDER_PROP_TP)]                 = this.OrderTakeProfit();
   this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME_CURRENT)]     = this.OrderVolumeCurrent();
   this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_STOP_LIMIT)]   = this.OrderPriceStopLimit();
   
//--- Сохранение строковых свойств
   this.m_string_prop[this.IndexProp(ORDER_PROP_SYMBOL)]             = this.OrderSymbol();
   this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT)]            = this.OrderComment();
   this.m_string_prop[this.IndexProp(ORDER_PROP_EXT_ID)]             = this.OrderExternalID();
   
//--- Сохранение дополнительных целочисленных свойств
   this.m_long_prop[ORDER_PROP_PROFIT_PT]                            = this.ProfitInPoints();
   this.m_long_prop[ORDER_PROP_TICKET_FROM]                          = this.OrderTicketFrom();
   this.m_long_prop[ORDER_PROP_TICKET_TO]                            = this.OrderTicketTo();
   this.m_long_prop[ORDER_PROP_CLOSE_BY_SL]                          = this.OrderCloseByStopLoss();
   this.m_long_prop[ORDER_PROP_CLOSE_BY_TP]                          = this.OrderCloseByTakeProfit();
   this.m_long_prop[ORDER_PROP_GROUP_ID]                             = 0;
   
//--- Сохранение дополнительных вещественных свойств
   this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT_FULL)]        = this.ProfitFull();
   
//--- Сохранение дополнительных строковых свойств
   this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT_EXT)]        = "";
  }
//+------------------------------------------------------------------+

В методе, возвращающем идентификатор позиции для MQL4 сделаем так: если это рыночная позиция, то вернём её тикет, иначе — ноль. Ведь идентификатором позиции в MQL5 является тикет открывающего позицию ордера, и на протяжении всей жизни позиции её идентификатор не меняется.

В MQL4 идентификатором позиции таким образом может служить только тикет позиции. А у отложенного ордера в MQL4 такого идентификатора нету. Совсем — если ордер удалён, то значит, что никакая позиция по этому ордеру открыта не была, а если же этот ордер был активирован, то в истории ордеров данного ордера в MQL4 нету, но позиция получает его тикет — соответственно, и идентификатором позиции будет этот тикет.
//+------------------------------------------------------------------+
//| Возвращает идентификатор позиции                                 |
//+------------------------------------------------------------------+
long COrder::OrderPositionID(void) const
  {
#ifdef __MQL4__
   return(this.Status()==ORDER_STATUS_MARKET_POSITION ? this.Ticket() : 0);
#else
   long id=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_POSITION   : id=::PositionGetInteger(POSITION_IDENTIFIER);             break;
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : id=::OrderGetInteger(ORDER_POSITION_ID);                  break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : id=::HistoryOrderGetInteger(m_ticket,ORDER_POSITION_ID);  break;
      case ORDER_STATUS_DEAL              : id=::HistoryDealGetInteger(m_ticket,DEAL_POSITION_ID);    break;
      default                             : id=0;                                                     break;
     }
   return id;
#endif
  }
//+------------------------------------------------------------------+

Допишем метод, возвращающий идентификатор встречной позиции для MQL4:

//+------------------------------------------------------------------+
//| Возвращает идентификатор встречной позиции                       |
//+------------------------------------------------------------------+
long COrder::OrderPositionByID(void) const
  {
   long ticket=0;
#ifdef __MQL4__
   string order_comment=::OrderComment();
   if(::StringFind(order_comment,"close hedge by #")>WRONG_VALUE) ticket=::StringToInteger(::StringSubstr(order_comment,16));
#else
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : ticket=::OrderGetInteger(ORDER_POSITION_BY_ID);                 break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : ticket=::HistoryOrderGetInteger(m_ticket,ORDER_POSITION_BY_ID); break;
      default                             : ticket=0;                                                       break;
     }
#endif
   return ticket;
  }
//+------------------------------------------------------------------+

Здесь: если это MQL4 и если в коментарии ордера присутствует строка "close hedge by #", то вычисляем в строке комментария индекс начала номера тикета встречного ордера и присваиваем его возвращаемому данным методом значению.

Удалим из листинга класса реализацию двух уже не нужных методов, так как мы отказались от получения секундного времени:

//+------------------------------------------------------------------+
//| Возвращает время открытия                                        |
//+------------------------------------------------------------------+
datetime COrder::OrderOpenTime(void) const
  {
#ifdef __MQL4__
   return ::OrderOpenTime();
#else 
   datetime res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_POSITION   : res=(datetime)::PositionGetInteger(POSITION_TIME);                 break;
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : res=(datetime)::OrderGetInteger(ORDER_TIME_SETUP);                 break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=(datetime)::HistoryOrderGetInteger(m_ticket,ORDER_TIME_SETUP); break;
      case ORDER_STATUS_DEAL              : res=(datetime)::HistoryDealGetInteger(m_ticket,DEAL_TIME);         break;
      default                             : res=0;                                                             break;
     }
   return res;
#endif 
  }
//+------------------------------------------------------------------+
//| Возвращает время закрытия                                        |
//+------------------------------------------------------------------+
datetime COrder::OrderCloseTime(void) const
  {
#ifdef __MQL4__
   return ::OrderCloseTime();
#else 
   datetime res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=(datetime)::HistoryOrderGetInteger(m_ticket,ORDER_TIME_DONE);  break;
      case ORDER_STATUS_DEAL              : res=(datetime)::HistoryDealGetInteger(m_ticket,DEAL_TIME);         break;
      default                             : res=0;                                                             break;
     }
   return res;
#endif 
  }
//+------------------------------------------------------------------+

Для более осмысленного вывода описания статуса ордера в MQL4 добавим в метод, возвращающий описание статуса ордера небольшие исправления:

//+------------------------------------------------------------------+
//| Возвращает наименование статуса ордера                           |
//+------------------------------------------------------------------+
string COrder::StatusDescription(void) const
  {
   ENUM_ORDER_STATUS status=this.Status();
   ENUM_ORDER_TYPE   type=(ENUM_ORDER_TYPE)this.TypeOrder();
   return
     (
      status==ORDER_STATUS_BALANCE           ?  TextByLanguage("Балансовая операция","Balance operation") :
      #ifdef __MQL5__
      status==ORDER_STATUS_MARKET_ORDER || status==ORDER_STATUS_HISTORY_ORDER ?  
         (
          type==ORDER_TYPE_CLOSE_BY ? TextByLanguage("Закрывающий ордер","Order for closing by")         :
          TextByLanguage("Ордер на ","The order to ")+(type==ORDER_TYPE_BUY ? TextByLanguage("покупку","buy") : TextByLanguage("продажу","sell"))
         ) :
      #else 
      status==ORDER_STATUS_HISTORY_ORDER     ?  TextByLanguage("Исторический ордер","History order")     :
      #endif 
      status==ORDER_STATUS_DEAL              ?  TextByLanguage("Сделка","Deal")                          :
      status==ORDER_STATUS_MARKET_POSITION   ?  TextByLanguage("Позиция","Active position")              :
      status==ORDER_STATUS_MARKET_PENDING    ?  TextByLanguage("Установленный отложенный ордер","Active pending order") :
      status==ORDER_STATUS_HISTORY_PENDING   ?  TextByLanguage("Отложенный ордер","Pending order") :
      EnumToString(status)
     );
  }
//+------------------------------------------------------------------+

Здесь: для удалённого отложенного ордера и закрытой позиции в MQL4 будем возвращать описание статуса как "Исторический ордер".

В методе, возвращающем описание целочисленного свойства ордера, сделаем исправления в строках с описанием свойств ORDER_PROP_TIME_OPEN, ORDER_PROP_TIME_CLOSE и ORDER_PROP_TIME_UPDATE так, чтобы для этих свойств возвращались милисекундные свойства:

//+------------------------------------------------------------------+
//| Возвращает описание целочисленного свойства ордера               |
//+------------------------------------------------------------------+
string COrder::GetPropertyDescription(ENUM_ORDER_PROP_INTEGER property)
  {
   return
     (
   //--- Общие свойства
      property==ORDER_PROP_MAGIC             ?  TextByLanguage("Магик","Magic")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property is not support") :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TICKET            ?  TextByLanguage("Тикет","Ticket")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property is not support") :
          " #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TICKET_FROM       ?  TextByLanguage("Тикет родительского ордера","Ticket of parent order")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property is not support") :
          " #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TICKET_TO         ?  TextByLanguage("Тикет наследуемого ордера","Inherited order ticket")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property is not support") :
          " #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TIME_EXP          ?  TextByLanguage("Дата экспирации","Date of expiration")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property is not support") :
          (this.GetProperty(property)==0     ?  TextByLanguage(": Не задана",": Not set") :
          ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS))
         )  :
      property==ORDER_PROP_TYPE              ?  TextByLanguage("Тип","Type")+": "+this.TypeDescription()                   :
      property==ORDER_PROP_DIRECTION         ?  TextByLanguage("Тип по направлению","Type by direction")+": "+this.DirectionDescription() :
      
      property==ORDER_PROP_REASON            ?  TextByLanguage("Причина","Reason")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property is not support") :
          ": "+this.GetReasonDescription(this.GetProperty(property))
         )  :
      property==ORDER_PROP_POSITION_ID       ?  TextByLanguage("Идентификатор позиции","Position identifier")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property is not support") :
          ": #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_DEAL_ORDER_TICKET ?  TextByLanguage("Сделка на основании ордера с тикетом","Deal by order ticket")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property is not support") :
          ": #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_DEAL_ENTRY        ?  TextByLanguage("Направление сделки","Deal entry")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property is not support") :
          ": "+this.GetEntryDescription(this.GetProperty(property))
         )  :
      property==ORDER_PROP_POSITION_BY_ID    ?  TextByLanguage("Идентификатор встречной позиции","Opposite position identifier")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property is not support") :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TIME_OPEN         ?  TextByLanguage("Время открытия в милисекундах","Opening time in milliseconds")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property is not support") :
          ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")"
         )  :
      property==ORDER_PROP_TIME_CLOSE        ?  TextByLanguage("Время закрытия в милисекундах","Closing time in milliseconds")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property is not support") :
          ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")"
         )  :
      property==ORDER_PROP_TIME_UPDATE       ?  TextByLanguage("Время изменения позиции в милисекундах","Time to change the position in milliseconds")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property is not support") :
          ": "+(this.GetProperty(property)!=0 ? TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")" : "0")
         )  :
      property==ORDER_PROP_STATE             ?  TextByLanguage("Состояние","Statе")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property is not support") :
          ": \""+this.StateDescription()+"\""
         )  :
   //--- Дополнительное свойство
      property==ORDER_PROP_STATUS            ?  TextByLanguage("Статус","Status")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property is not support") :
          ": \""+this.StatusDescription()+"\""
         )  :
      property==ORDER_PROP_PROFIT_PT         ?  (
                                                 this.Status()==ORDER_STATUS_MARKET_PENDING ? 
                                                 TextByLanguage("Дистанция от цены в пунктах","Distance from price in points") : 
                                                 TextByLanguage("Прибыль в пунктах","Profit in points")
                                                )+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property is not support") :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_CLOSE_BY_SL       ?  TextByLanguage("Закрытие по StopLoss","Close by StopLoss")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property is not support") :
          ": "+(this.GetProperty(property)   ?  TextByLanguage("Да","Yes") : TextByLanguage("Нет","No"))
         )  :
      property==ORDER_PROP_CLOSE_BY_TP       ?  TextByLanguage("Закрытие по TakeProfit","Close by TakeProfit")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property is not support") :
          ": "+(this.GetProperty(property)   ?  TextByLanguage("Да","Yes") : TextByLanguage("Нет","No"))
         )  :
      property==ORDER_PROP_GROUP_ID          ?  TextByLanguage("Идентификатор группы","Group's identifier")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property is not support") :
          ": "+(string)this.GetProperty(property)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

и допишем в метод, возвращающий описание строкового свойства, возврат описания пользовательского комментария:

//+------------------------------------------------------------------+
//| Возвращает описание строкового свойства ордера                   |
//+------------------------------------------------------------------+
string COrder::GetPropertyDescription(ENUM_ORDER_PROP_STRING property)
  {
   return
     (
      property==ORDER_PROP_SYMBOL         ?  TextByLanguage("Символ","Symbol")+": \""+this.GetProperty(property)+"\""            :
      property==ORDER_PROP_COMMENT        ?  TextByLanguage("Комментарий","Comment")+
         (this.GetProperty(property)==""  ?  TextByLanguage(": Отсутствует",": Not set"):": \""+this.GetProperty(property)+"\"") :
      property==ORDER_PROP_COMMENT_EXT    ?  TextByLanguage("Пользовательский комментарий","Custom comment")+
         (this.GetProperty(property)==""  ?  TextByLanguage(": Не задан",": Not set"):": \""+this.GetProperty(property)+"\"") :
      property==ORDER_PROP_EXT_ID         ?  TextByLanguage("Идентификатор на бирже","Exchange identifier")+
         (!this.SupportProperty(property) ?  TextByLanguage(": Свойство не поддерживается",": Property is not support") :
         (this.GetProperty(property)==""  ?  TextByLanguage(": Отсутствует",": Not set"):": \""+this.GetProperty(property)+"\"")):
      ""
     );
  }
//+------------------------------------------------------------------+

На этом изменения в классе абстрактного ордера COrder завершены.

В файле сервисных функций DELib.mqh сделаем небольшую доработку в функции, возвращающей наименование ордера/позиции по типу ордера:

//+------------------------------------------------------------------+
//| Возвращает наименование ордера                                   |
//+------------------------------------------------------------------+
string OrderTypeDescription(const ENUM_ORDER_TYPE type,bool as_order=true)
  {
   string pref=(#ifdef __MQL5__ "Market order" #else (as_order ? "Market order" : "Position") #endif );
   return
     (
      type==ORDER_TYPE_BUY_LIMIT       ?  "Buy Limit"                                                 :
      type==ORDER_TYPE_BUY_STOP        ?  "Buy Stop"                                                  :
      type==ORDER_TYPE_SELL_LIMIT      ?  "Sell Limit"                                                :
      type==ORDER_TYPE_SELL_STOP       ?  "Sell Stop"                                                 :
   #ifdef __MQL5__
      type==ORDER_TYPE_BUY_STOP_LIMIT  ?  "Buy Stop Limit"                                            :
      type==ORDER_TYPE_SELL_STOP_LIMIT ?  "Sell Stop Limit"                                           :
      type==ORDER_TYPE_CLOSE_BY        ?  TextByLanguage("Закрывающий ордер","Order for closing by")  :  
   #else 
      type==ORDER_TYPE_BALANCE         ?  TextByLanguage("Балансовая операция","Balance operation")   :
      type==ORDER_TYPE_CREDIT          ?  TextByLanguage("Кредитная операция","Credit operation")     :
   #endif 
      type==ORDER_TYPE_BUY             ?  pref+" Buy"                                                 :
      type==ORDER_TYPE_SELL            ?  pref+" Sell"                                                :  
      TextByLanguage("Неизвестный тип ордера","Unknown order type")
     );
  }
//+------------------------------------------------------------------+

Здесь мы добавили флаг, управляющий выводом наименования ордера для MQL4 либо как ордер, либо как позиция. По умолчанию будет вывод для MQL4 "как ордер". Для чего это сделано? Например, при выводе в журнал события открытия позиции, в квадратных скобках выводится ордер, на основании которого открыта позиция. Так вот, чтобы не выводить [Position Sell #123] для позиции Sell, открытой рыночным приказом (не отложенным ордером) с тикетом 123 в качестве ордера, на основании которого была открыта позиция, а писать более понятную запись [Market order Sell #123], это и сделано.

Внесём исправление вметод AddToListMarket() класса коллекции рыночных ордеров и позиций. Вместо времени обновления позиции в милисекундах ORDER_PROP_TIME_UPDATE_MSC мы теперь используем время обновления позиции ORDER_PROP_TIME_UPDATE (оно по умолчанию в милисекундах):

//+------------------------------------------------------------------+
//| Добавляет ордер или позицию в список ордеров и позиций на счёте  |
//+------------------------------------------------------------------+
bool CMarketCollection::AddToListMarket(COrder *order)
  {
   if(order==NULL)
      return false;
   ENUM_ORDER_STATUS status=order.Status();
   if(this.m_list_all_orders.InsertSort(order))
     {
      if(status==ORDER_STATUS_MARKET_POSITION)
        {
         this.m_struct_curr_market.hash_sum_acc+=order.GetProperty(ORDER_PROP_TIME_UPDATE)+this.ConvertToHS(order);
         this.m_struct_curr_market.total_volumes+=order.Volume();
         this.m_struct_curr_market.total_positions++;
         return true;
        }
      if(status==ORDER_STATUS_MARKET_PENDING)
        {
         this.m_struct_curr_market.hash_sum_acc+=this.ConvertToHS(order);
         this.m_struct_curr_market.total_volumes+=order.Volume();
         this.m_struct_curr_market.total_pending++;
         return true;
        }
     }
   else
     {
      ::Print(DFUN,order.TypeDescription()," #",order.Ticket()," ",TextByLanguage("не удалось добавить в список","failed to add to the list"));
      delete order;
     }
   return false;
  }
//+------------------------------------------------------------------+

В методе создания контрольных ордеров и добавления их в список поменяем время ордера в милисекундах на время ордера (по той же самой причине):

//+------------------------------------------------------------------+
//| Создаёт и добавляет ордер в список контрольных ордеров           |
//+------------------------------------------------------------------+
bool CMarketCollection::AddToListControl(COrder *order)
  {
   if(order==NULL)
      return false;
   COrderControl* order_control=new COrderControl(order.PositionID(),order.Ticket(),order.Magic(),order.Symbol());
   if(order_control==NULL)
      return false;
   order_control.SetTime(order.TimeOpen());
   order_control.SetTimePrev(order.TimeOpen());
   order_control.SetVolume(order.Volume());
   order_control.SetTime(order.TimeOpen());
   order_control.SetTypeOrder(order.TypeOrder());
   order_control.SetTypeOrderPrev(order.TypeOrder());
   order_control.SetPrice(order.PriceOpen());
   order_control.SetPricePrev(order.PriceOpen());
   order_control.SetStopLoss(order.StopLoss());
   order_control.SetStopLossPrev(order.StopLoss());
   order_control.SetTakeProfit(order.TakeProfit());
   order_control.SetTakeProfitPrev(order.TakeProfit());
   if(!this.m_list_control.Add(order_control))
     {
      delete order_control;
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

В файле HistoryCollection.mqh в методе выбора ордеров по времени класса-коллекции исторических ордеров и сделок CHistoryCollection внесём исправление в выбор сравниваемого свойства.
Так как ранее у нас был выбор из четырёх свойств (время открытия в милисекундах, время закрытия в милисекундах, время открытия в секундах и время закрытия в секундах), а теперь два свойства мы убрали, то и выбор упростился:

//+------------------------------------------------------------------+
//| Выбирает ордера из коллекции со временем                         |
//| в диапазоне от begin_time, до end_time                           |
//+------------------------------------------------------------------+
CArrayObj *CHistoryCollection::GetListByTime(const datetime begin_time=0,const datetime end_time=0,
                                             const ENUM_SELECT_BY_TIME select_time_mode=SELECT_BY_TIME_CLOSE)
  {
   ENUM_ORDER_PROP_INTEGER property=(select_time_mode==SELECT_BY_TIME_CLOSE ? ORDER_PROP_TIME_CLOSE : ORDER_PROP_TIME_OPEN);

   CArrayObj *list=new CArrayObj();
   if(list==NULL)
     {
      ::Print(DFUN+TextByLanguage("Ошибка создания временного списка","Error creating temporary list"));
      return NULL;
     }
   datetime begin=begin_time,end=(end_time==0 ? END_TIME : end_time);
   if(begin_time>end_time) begin=0;
   list.FreeMode(false);
   ListStorage.Add(list);
   //---
   this.m_order_instance.SetProperty(property,begin);
   int index_begin=this.m_list_all_orders.SearchGreatOrEqual(&m_order_instance);
   if(index_begin==WRONG_VALUE)
      return list;
   this.m_order_instance.SetProperty(property,end);
   int index_end=this.m_list_all_orders.SearchLessOrEqual(&m_order_instance);
   if(index_end==WRONG_VALUE)
      return list;
   for(int i=index_begin; i<=index_end; i++)
      list.Add(this.m_list_all_orders.At(i));
   return list;
  }
//+------------------------------------------------------------------+

В файле CEngine.mqh заменим все вхождения константы времени в милисекундах SORT_BY_ORDER_TIME_OPEN_MSC на константу времени SORT_BY_ORDER_TIME_OPEN — теперь по этой константе используется время в милисекундах по умолчанию.
И на этом исправления файлов библиотеки по удалению времени в секундах завершены.

Реализация событий закрытия позиций для MQL4

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

  • увеличение количества исторических ордеров
  • уменьшение общего объёма
  • количество рыночных позиций осталось без изменения.

Для определения того, что это событие принадлежит удалению отложенного ордера (ведь позиция в MQL4 — это тоже ордер), мы проверяем количество открытых позиций — если оно не изменилось, значит действие проведено с отложенным ордером. Тут вроде всё нормально и логично. Но до той поры, пока ме не закроем частично любую из открытых позиций (ордер) в MetaTrader 4. При событии частичного закрытия позиции что имеем? А имеем мы то же самое, что и при удалении отложенного ордера:

  • количество исторических ордеров увеличилось (в историю попала частично закрытая позиция — её ордер),
  • объём на счёте уменьшился — мы же частично закрыли позицию,
  • количество открытых позиций не уменьшилось — при частичном закрытии позиции у нас количество позиций не меняется.

А это то же состояние, что и при удалении отложенного ордера. Факт данной недоработки мы можем наблюдать, если запустим в тестере стратегий тестовый советник из прошлой статьи, откроем позицию, частично закроем её, выставим отложенный ордер и затем удалим его. Вот при последнем действии — удалении отложенного ордера, в журнал будут выведены сразу два события: частичное закрытие позиции и удаление отложенного ордера — выше мы разобрали "одинаковость" критериев определения этих двух событий, и программа просто определяет сразу два события, и одно из них неверное.
И вот тут нам на помощь приходит проверка изменившегося количества отложенных ордеров — при определении частичного закрытия позиции будем проверять ещё и факт изменения количества рыночных отложенных ордеров. Если их количество не изменилось, то это событие является частичным закрытием позиции.

Вроде бы тоже всё логично, но данный подход накладывает ограничения на разрешённую очерёдность проведения торговых операций в MetaTrader 4. Т.е. мы не сможем в одном цикле удалить отложенный ордер и частично закрыть позицию — это приведёт к нарушению вышеозвученной логики определения событий. В принципе, есть решение для обхода этого ограничения, но такое решение приведёт к переработке классов рыночной и исторической коллекции ордеров и позиций — для определения факта изменения в рыночном окружении нужно будет контролировать не количество произошедших изменений, а использовать временные списки ордеров и позиций, с которыми были произведены манипуляции на счёте. И обрабатывать события нужно будет уже по данным этих списков — тогда каждый тип события будет иметь свой собственный список, и создание событий для отправки их в программу должно будет проводиться по спискам изменённых ордеров и позиций.

Возможно, по окончании данного цикла статей, мы организуем такой поиск и обработку событий. Но пока воспользуемся контролем количества рыночных отложенных ордеров, и в дальнейшем для MetaTrader 4 будем помнить про такое ограничение, и делать функции закрытия/удаления ордеров с учётом данного ограничения — всё по порядку. К слову: для конечного пользователя это ограничение останется сокрытым — он не должен будет его контролировать, ведь будут предоставлены функции для работы с библиотекой, в которых для MQL4 будет учтено данное ограничение.

Для определения факта частичного закрытия нам необходимо контролировать общий объём на счёте, а значит нам нужно передавать величину его изменения в метод Refresh() класса CEventsCollection. Всё у нас, как обычно, начинается из базового объекта библиотеки. А значит — сразу внесём необходимое дополнение в вызов метода обновления класса коллекции событий.

Внесём дополнительный передаваемый параметр в метод Refresh класса CEventsCollection в методе класса CEngine::TradeEventsControl():

//+------------------------------------------------------------------+
//| Проверка торговых событий                                        |
//+------------------------------------------------------------------+
void CEngine::TradeEventsControl(void)
  {
//--- Инициализация кода и флагов торговых событий
   this.m_is_market_trade_event=false;
   this.m_is_history_trade_event=false;
//--- Обновление списков 
   this.m_market.Refresh();
   this.m_history.Refresh();
//--- Действия при первом запуске
   if(this.IsFirstStart())
     {
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      return;
     }
//--- Проверка изменений в рыночном состоянии и в истории счёта 
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();

//--- Если есть любое событие, отправляем списки, флаги и количество новых ордеров и сделок в коллекцию событий и обновляем коллекцию событий
   int change_total=0;
   CArrayObj* list_changes=this.m_market.GetListChanges();
   if(list_changes!=NULL)
      change_total=list_changes.Total();
   if(this.m_is_history_trade_event || this.m_is_market_trade_event || change_total>0)
     {
      this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),list_changes,this.m_market.GetListControl(),
                            this.m_is_history_trade_event,this.m_is_market_trade_event,
                            this.m_history.NewOrders(),this.m_market.NewPendingOrders(),
                            this.m_market.NewPositions(),this.m_history.NewDeals(),
                            this.m_market.ChangedVolumeValue());
      //--- Получаем последнее торговое событие на счёте
      this.m_acc_trade_event=this.m_events.GetLastTradeEvent();
     }
  }
//+------------------------------------------------------------------+

Теперь нужно изменить сам метод Refresh() класса CEventsCollection — внести в его параметры ещё один — величину изменения объёма.
В файле EventsCollection.mqh допишем в определение метода Refresh() новый параметр:

public:
//--- Выбирает события из коллекции со временем в диапазоне от begin_time до end_time
   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0);
//--- Возвращает полный список-коллекцию событий "как есть"
   CArrayObj        *GetList(void)                                                                       { return &this.m_list_events;                                           }
//--- Возвращает список по выбранному (1) целочисленному, (2) вещественному и (3) строковому свойству, удовлетворяющему сравниваемому критерию
   CArrayObj        *GetList(ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
//--- Обновляет список событий
   void              Refresh(CArrayObj* list_history,
                             CArrayObj* list_market,
                             CArrayObj* list_changes,
                             CArrayObj* list_control,
                             const bool is_history_event,
                             const bool is_market_event,
                             const int  new_history_orders,
                             const int  new_market_pendings,
                             const int  new_market_positions,
                             const int  new_deals,
                             const double changed_volume);
//--- Устанавливает идентификатор графика управляющей программы
   void              SetChartID(const long id)        { this.m_chart_id=id;         }
//--- Возвращает последнее торговое событие на счёте
   ENUM_TRADE_EVENT  GetLastTradeEvent(void)    const { return this.m_trade_event;  }
//--- Сбрасывает последнее торговое событие
   void              ResetLastTradeEvent(void)        { this.m_trade_event=TRADE_EVENT_NO_EVENT;   }
//--- Конструктор
                     CEventsCollection(void);
  };

И в реализацию метода Refresh() так же необходимо его дописать:

//+------------------------------------------------------------------+
//| Обновляет список событий                                         |
//+------------------------------------------------------------------+
void CEventsCollection::Refresh(CArrayObj* list_history,
                                CArrayObj* list_market,
                                CArrayObj* list_changes,
                                CArrayObj* list_control,
                                const bool is_history_event,
                                const bool is_market_event,
                                const int  new_history_orders,
                                const int  new_market_pendings,
                                const int  new_market_positions,
                                const int  new_deals,
                                const double changed_volume)
  {

Теперь нам нужно создать обработчик событий закрытия позиции и частичного её закрытия, а также решим проблему с неверным определением события удаления отложенного ордера при частичном закрытии позиции.

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

class CEventsCollection : public CListObj
  {
private:
   CListObj          m_list_events;                   // Список событий
   bool              m_is_hedge;                      // Флаг хедж-счёта
   long              m_chart_id;                      // Идентификатор графика управляющей программы
   int               m_trade_event_code;              // Код торгового события
   ENUM_TRADE_EVENT  m_trade_event;                   // Торговое событие на счёте
   CEvent            m_event_instance;                // Объект-событие для поиска по свойству
   MqlTick           m_tick;                          // Структура последнего тика
   ulong             m_position_id;                   // Идентификатор позиции (MQL4)
   ENUM_ORDER_TYPE   m_type_first;                    // Тип открывающего ордера (MQL4)
   
//--- Создаёт торговое событие в зависимости от (1) статуса и (2) типа изменения ордера
   void              CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market,CArrayObj* list_control);
   void              CreateNewEvent(COrderControl* order);
//--- Создаёт событие для (1) хеджевого счёта, (2) неттингового счёта
   void              NewDealEventHedge(COrder* deal,CArrayObj* list_history,CArrayObj* list_market);
   void              NewDealEventNetto(COrder* deal,CArrayObj* list_history,CArrayObj* list_market);
//--- Выбирает из списка и возвращает список (1) рыночных отложенных ордеров, (2) открытых позиций
   CArrayObj*        GetListMarketPendings(CArrayObj* list);
   CArrayObj*        GetListPositions(CArrayObj* list);
//--- Выбирает из списка и возвращает список исторических (1) закрытых позиций,
//--- (2) удалённых отложенных ордеров, (3) сделок, (4) всех закрывающих ордеров 
   CArrayObj*        GetListHistoryPositions(CArrayObj* list);
   CArrayObj*        GetListHistoryPendings(CArrayObj* list);
   CArrayObj*        GetListDeals(CArrayObj* list);
   CArrayObj*        GetListCloseByOrders(CArrayObj* list);
//--- Возвращает список (1) всех ордеров позиции по её идентификатору, (2) всех сделок позиции по её идентификатору
//--- (3) всех сделок на вход в рынок по идентификатору позиции, (4) всех сделок на выход из рынка по идентификатору позиции,
//--- (5) всех сделок на разворот позиции по идентификатору позиции
   CArrayObj*        GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsInByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsOutByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsInOutByPosID(CArrayObj* list,const ulong position_id);
//--- Возвращает суммарный объём всех сделок (1) IN, (2) OUT позиции по её идентификатору
   double            SummaryVolumeDealsInByPosID(CArrayObj* list,const ulong position_id);
   double            SummaryVolumeDealsOutByPosID(CArrayObj* list,const ulong position_id);
//--- Возвращает (1) первый, (2) последний и (3) закрывающий ордер из списка всех ордеров позиции,
//--- (4) ордер по тикету, (5) рыночную позицию по идентификатору,
//--- (6) последнюю и (7) предпоследнюю сделку InOut по идентификатору позиции
   COrder*           GetFirstOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetLastOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetCloseByOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetHistoryOrderByTicket(CArrayObj* list,const ulong order_ticket);
   COrder*           GetPositionByID(CArrayObj* list,const ulong position_id);
//--- Возвращает (1) контрольный ордер по тикету, (2) тип открывающего ордера по тикету позиции (MQL4)
   COrderControl*    GetOrderControlByTicket(CArrayObj* list,const ulong ticket);
   ENUM_ORDER_TYPE   GetTypeFirst(CArrayObj* list,const ulong ticket);
//--- Возвращает флаг наличия объекта-события в списке событий
   bool              IsPresentEventInList(CEvent* compared_event);
//--- Обработчик события изменения существующего ордера/позиции
   void              OnChangeEvent(CArrayObj* list_changes,const int index);

public:

За пределами тела класса напишем реализацию метода, возвращающего список закрытых позиций:

//+------------------------------------------------------------------+
//| Выбирает из списка только закрытые позиции                       |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListHistoryPositions(CArrayObj *list)
  {
   if(list.Type()!=COLLECTION_HISTORY_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of the history collection"));
      return NULL;
     }
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_ORDER,EQUAL);
   return list_orders;
  }
//+------------------------------------------------------------------+

В методе для нас уже точно нет ничего нового — идентичные методы неоднократно рассматривались в предыдущих частях описания библиотеки.

Напишем реализацию метода, возвращающего контрольный ордер по тикету и изменим метод, возвращающий тип контрольного ордера по тикету:

//+------------------------------------------------------------------+
//| Возвращает контрольный ордер по тикету позиции (MQL4)            |
//+------------------------------------------------------------------+
COrderControl* CEventsCollection::GetOrderControlByTicket(CArrayObj *list,const ulong ticket)
  {
   if(list==NULL)
      return NULL;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      COrderControl* ctrl=list.At(i);
      if(ctrl==NULL)
         continue;
      if(ctrl.Ticket()==ticket)
         return ctrl;
     }
   return NULL;
  }
//+------------------------------------------------------------------+
//| Возвращает тип открывающего ордера по тикету позиции (MQL4)      |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE CEventsCollection::GetTypeFirst(CArrayObj* list,const ulong ticket)
  {
   if(list==NULL)
      return WRONG_VALUE;
   COrderControl* ctrl=this.GetOrderControlByTicket(list,ticket);
   if(ctrl==NULL)        
      return WRONG_VALUE;
   return (ENUM_ORDER_TYPE)ctrl.TypeOrder();
  }
//+------------------------------------------------------------------+

Метод, возвращающий контрольный ордер по тикету прост, как и ранее рассмотренные идентичные методы: в метод передаётся список контрольных ордеров и тикет позиции, контрольный ордер которой мы хотим получить.
В цикле по размеру списка берём из него ордер и сравниваем с переданным в метод тикетом. Если тикеты равны, то возвращаем указатель на контрольный ордер, иначе — возвращаем NULL.

Метод GetTypeFirst(), возвращающий тип контрольного ордера, ранее состоял из цикла по списку контрольных ордеров с поиском ордера с тикетом, равным переданному в метод, и при нахождении такого ордера, возвращался его тип.
Сейчас же, когда у нас есть метод, возвращающий контрольный ордер по тикету позиции, мы можем удалить из метода GetTypeFirst() цикл поиска. Что мы и сделали: теперь в методе получаем контрольный ордер по тикету позиции при помощи метода GetOrderControlByTicket(), и при успешном его получении (не NULL), возвращаем тип полученного ордера. Иначе — возвращаем -1.

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

//+------------------------------------------------------------------+
//| Обновляет список событий                                         |
//+------------------------------------------------------------------+
void CEventsCollection::Refresh(CArrayObj* list_history,
                                CArrayObj* list_market,
                                CArrayObj* list_changes,
                                CArrayObj* list_control,
                                const bool is_history_event,
                                const bool is_market_event,
                                const int  new_history_orders,
                                const int  new_market_pendings,
                                const int  new_market_positions,
                                const int  new_deals,
                                const double changed_volume)
  {
//--- Если списки пустые - выход
   if(list_history==NULL || list_market==NULL)
      return;
//--- Если событие в рыночном окружении
   if(is_market_event)
     {
      //--- если было изменение свойств ордера
      int total_changes=list_changes.Total();
      if(total_changes>0)
        {
         for(int i=total_changes-1;i>=0;i--)
           {
            this.OnChangeEvent(list_changes,i);
           }
        }
      //--- если увеличилось количество установленных отложенных ордеров (MQL5, MQL4)
      if(new_market_pendings>0)
        {
         //--- Получаем список только установленных отложенных ордеров
         CArrayObj* list=this.GetListMarketPendings(list_market);
         if(list!=NULL)
           {
            //--- Сортируем новый список по времени установки ордера
            list.Sort(SORT_BY_ORDER_TIME_OPEN);
            //--- Берём в цикле с конца списка количество ордеров, равное количеству новых установленных ордеров (последние N событий)
            int total=list.Total(), n=new_market_pendings;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Получаем ордер из списка, и если это отложенный ордер - устанавливаем торговое событие
               COrder* order=list.At(i);
               if(order!=NULL && order.Status()==ORDER_STATUS_MARKET_PENDING)
                  this.CreateNewEvent(order,list_history,list_market,list_control);
              }
           }
        }
      #ifdef __MQL4__
         //--- Если увеличилось количество позиций (MQL4)
         if(new_market_positions>0)
           {
            //--- Получаем список открытых позиций
            CArrayObj* list=this.GetListPositions(list_market);
            if(list!=NULL)
              {
               //--- Сортируем новый список по времени открытия позиции
               list.Sort(SORT_BY_ORDER_TIME_OPEN);
               //--- Берём в цикле с конца списка количество позиций, равное количеству новых открытых позиций (последние N событий)
               int total=list.Total(), n=new_market_positions;
               for(int i=total-1; i>=0 && n>0; i--,n--)
                 {
                  //--- Получаем позицию из списка, и если это позиция - ищем данные открывающего ордера и устанавливаем торговое событие
                  COrder* position=list.At(i);
                  if(position!=NULL && position.Status()==ORDER_STATUS_MARKET_POSITION)
                    {
                     //--- Находим ордер и устанавливаем (1) тип ордера, на основании которого была открыта позиция и (2) идентификатор позиции 
                     this.m_type_first=this.GetTypeFirst(list_control,position.Ticket());
                     this.m_position_id=position.Ticket();
                     this.CreateNewEvent(position,list_history,list_market,list_control);
                    }
                 }
              }
           }
         //--- Если уменьшилось количество позиций или позиция частично закрыта (MQL4)
         else if(new_market_positions<0 || (new_market_positions==0 && changed_volume<0 && new_history_orders>0 && new_market_pendings>WRONG_VALUE))
           {
            //--- Получаем список закрытых позиций
            CArrayObj* list=this.GetListHistoryPositions(list_history);
            if(list!=NULL)
              {
               //--- Сортируем новый список по времени закрытия позиции
               list.Sort(SORT_BY_ORDER_TIME_CLOSE);
               //--- Берём в цикле с конца списка количество позиций, равное количеству новых закрытых позиций (последние N событий)
               int total=list.Total(), n=new_history_orders;
               for(int i=total-1; i>=0 && n>0; i--,n--)
                 {
                  //--- Получаем позицию из списка, и если это позиция - ищем данные открывающего ордера и устанавливаем торговое событие
                  COrder* position=list.At(i);
                  if(position!=NULL && position.Status()==ORDER_STATUS_HISTORY_ORDER)
                    {
                     //--- Если есть контрольный ордер закрытой позиции
                     COrderControl* ctrl=this.GetOrderControlByTicket(list_control,position.Ticket());
                     if(ctrl!=NULL)
                       {
                        //--- Устанавливаем (1) тип ордера, на основании которого была открыта позиция, (2) идентификатор позиции и создаём событие закрытия позиции
                        this.m_type_first=(ENUM_ORDER_TYPE)ctrl.TypeOrder();
                        this.m_position_id=position.Ticket();
                        this.CreateNewEvent(position,list_history,list_market,list_control);
                       }
                    }
                 }
              }
           }
      #endif 
     }
//--- Если событие в истории счёта
   if(is_history_event)
     {
      //--- Если увеличилось количество исторических ордеров (MQL5, MQL4)
      if(new_history_orders>0)
        {
         //--- Получаем список только удалённых отложенных ордеров
         CArrayObj* list=this.GetListHistoryPendings(list_history);
         if(list!=NULL)
           {
            //--- Сортируем новый список по времени удаления ордера
            list.Sort(SORT_BY_ORDER_TIME_CLOSE);
            //--- Берём в цикле с конца списка количество ордеров, равное количеству новых удалённых отложенных ордеров (последние N событий)
            int total=list.Total(), n=new_history_orders;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Получаем ордер из списка, и если это удалённый отложенный ордер и у него нет идентификатора позиции, 
               //--- то это удаление ордера - устанавливаем торговое событие
               COrder* order=list.At(i);
               if(order!=NULL && order.Status()==ORDER_STATUS_HISTORY_PENDING && order.PositionID()==0)
                  this.CreateNewEvent(order,list_history,list_market,list_control);
              }
           }
        }
      //--- Если увеличилось количество сделок (MQL5)
      #ifdef __MQL5__
         if(new_deals>0)
           {
            //--- Получаем список только сделок
            CArrayObj* list=this.GetListDeals(list_history);
            if(list!=NULL)
              {
               //--- Сортируем новый список по времени совершения сделки
               list.Sort(SORT_BY_ORDER_TIME_OPEN);
               //--- Берём в цикле с конца списка количество сделок, равное количеству новых сделок (последние N событий)
               int total=list.Total(), n=new_deals;
               for(int i=total-1; i>=0 && n>0; i--,n--)
                 {
                  //--- Получаем сделку из списка и устанавливаем торговое событие
                  COrder* order=list.At(i);
                  if(order!=NULL)
                     this.CreateNewEvent(order,list_history,list_market);
                 }
              }
           }
      #endif 
     }
  }
//+------------------------------------------------------------------+

Обработка закрытия позиций вполне понятна и прозрачна, её описание прокомментировано в листинге, и задерживаться на разборе логики мы не будем — там всё должно быть понятно из комментариев к коду.

Так как мы теперь в первую форму вызова метода создания нового события передаём на один список больше,
то добавим передачу списка контрольных ордеров в вызов метода для MQL5 в методе Refresh() класса коллекции событий CEventsCollection:

      //--- Если увеличилось количество сделок (MQL5)
      #ifdef __MQL5__
         if(new_deals>0)
           {
            //--- Получаем список только сделок
            CArrayObj* list=this.GetListDeals(list_history);
            if(list!=NULL)
              {
               //--- Сортируем новый список по времени совершения сделки
               list.Sort(SORT_BY_ORDER_TIME_OPEN);
               //--- Берём в цикле с конца списка количество сделок, равное количеству новых сделок (последние N событий)
               int total=list.Total(), n=new_deals;
               for(int i=total-1; i>=0 && n>0; i--,n--)
                 {
                  //--- Получаем сделку из списка и устанавливаем торговое событие
                  COrder* order=list.At(i);
                  if(order!=NULL)
                     this.CreateNewEvent(order,list_history,list_market,list_control);
                 }
              }
           }
      #endif 

Теперь допишем в метод создания события, в первую форму его вызова код создания события закрытия позиций:

CEventsCollection::CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market,CArrayObj* list_control).

Так как метод достаточно объёмен, рассмотрим только код создания события закрытия позиции для MQL4:

//--- Закрыта позиция (__MQL4__)
   if(status==ORDER_STATUS_HISTORY_ORDER)
     {
      //--- Установим код торгового события "позиция закрыта"
      this.m_trade_event_code=TRADE_EVENT_FLAG_POSITION_CLOSED;
      //--- Установим причину "запрос исполнен полностью""
      ENUM_EVENT_REASON reason=EVENT_REASON_DONE;
      //--- Если у ордера установлен флаг закрытия по StopLoss - значит позиция закрыта по StopLoss
      if(order.IsCloseByStopLoss())
        {
         //--- установим причину "закрытие по StopLoss"
         reason=EVENT_REASON_DONE_SL;
         //--- добавим к коду события флаг закрытия по StopLoss
         this.m_trade_event_code+=TRADE_EVENT_FLAG_SL;
        }
      //--- Если у ордера установлен флаг закрытия по TakeProfit - значит позиция закрыта по TakeProfit
      if(order.IsCloseByTakeProfit())
        {
         //--- установим причину "закрытие по TakeProfit"
         reason=EVENT_REASON_DONE_TP;
         //--- добавим к коду события флаг закрытия по TakeProfit
         this.m_trade_event_code+=TRADE_EVENT_FLAG_TP;
        }
      //--- Если у ордера заполнено свойство с наследуемым ордером - значит позиция закрыта частично
      if(order.TicketTo()>0)
        {
         //--- установим причину "частичное закрытие"
         reason=EVENT_REASON_DONE_PARTIALLY;
         //--- добавим к коду события флаг частичного закрытия
         this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
        }
      //--- Проверка закрытия встречной позицией
      COrder* order_close_by=this.GetCloseByOrderFromList(list_history,order.Ticket());
      //--- Объявим переменные свойств встречного ордера и инициализируем их значениями текущей закрытой позиции 
      ENUM_ORDER_TYPE close_by_type=this.m_type_first;
      double close_by_volume=order.Volume();
      ulong  close_by_ticket=order.Ticket();
      long   close_by_magic=order.Magic();
      string close_by_symbol=order.Symbol();
      //--- Если в списке исторических ордеров есть ордер с идентификатором закрытой позиции - значит позиция закрыта встречной
      if(order_close_by!=NULL)
        {
         //--- Заполним свойства встречного закрывающего ордера данными свойств встречной позиции
         close_by_type=(ENUM_ORDER_TYPE)order_close_by.TypeOrder();
         close_by_ticket=order_close_by.Ticket();
         close_by_magic=order_close_by.Magic();
         close_by_symbol=order_close_by.Symbol();
         close_by_volume=order_close_by.Volume();
         //--- установим причину "закрытие встречной"
         reason=EVENT_REASON_DONE_BY_POS;
         //--- добавим к коду события флаг закрытия встречной
         this.m_trade_event_code+=TRADE_EVENT_FLAG_BY_POS;
         //--- Возьмём данные о (1) закрытой и (2) встречной позициях из списка контрольных ордеров
         //--- (в этом списке свойства двух встречных позиций остались какими были до закрытия)
         COrderControl* ctrl_closed=this.GetOrderControlByTicket(list_control,order.Ticket());
         COrderControl* ctrl_close_by=this.GetOrderControlByTicket(list_control,close_by_ticket);
         double vol_closed=0;
         double vol_close_by=0;
         //--- Если эти два встречных ордера получены без ошибок
         if(ctrl_closed!=NULL && ctrl_close_by!=NULL)
           {
            //--- Рассчитаем закрытые объёмы (1) закрытой и (2) встречной позиций
            vol_closed=ctrl_closed.Volume()-order.Volume();
            vol_close_by=vol_closed-close_by_volume;
            //--- Если позиция закрыта частично (прошлый объём был больше, чем текущий закрытый)
            if(ctrl_closed.Volume()>order.Volume())
              {
               //--- добавим к коду события флаг частичного закрытия
               this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
               //--- установим причину "частичное закрытие"
               reason=EVENT_REASON_DONE_PARTIALLY_BY_POS;
              }
           }
        }
      //--- Создаём событие закрытия позиции
      CEvent* event=new CEventPositionClose(this.m_trade_event_code,order.Ticket());
      if(event!=NULL && order.PositionByID()==0)
        {
         event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeClose());                               // Время события
         event.SetProperty(EVENT_PROP_REASON_EVENT,reason);                                        // Причина события (из перечисления ENUM_EVENT_REASON)
         event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,close_by_type);                              // Тип сделки события
         event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());                           // Тикет сделки события
         event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,close_by_type);                             // Тип ордера, на основании которого открыта сделка события (последний ордер позиции)
         event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,this.m_type_first);                      // Тип ордера, на основании которого открыта сделка позиции (первый ордер позиции)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,close_by_ticket);                         // Тикет ордера, на основании которого открыта сделка события (последний ордер позиции)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());                       // Тикет ордера, на основании которого открыта сделка позиции (первый ордер позиции)
         event.SetProperty(EVENT_PROP_POSITION_ID,this.m_position_id);                             // Идентификатор позиции
         event.SetProperty(EVENT_PROP_POSITION_BY_ID,close_by_ticket);                             // Идентификатор встречной позиции
         event.SetProperty(EVENT_PROP_MAGIC_BY_ID,close_by_magic);                                 // Магический номер встречной позиции
            
         event.SetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE,order.TypeOrder());                      // Тип ордера позиции до смены направления
         event.SetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE,order.Ticket());                       // Тикет ордера позиции до смены направления
         event.SetProperty(EVENT_PROP_TYPE_ORD_POS_CURRENT,order.TypeOrder());                     // Тип ордера текущей позиции
         event.SetProperty(EVENT_PROP_TICKET_ORD_POS_CURRENT,order.Ticket());                      // Тикет ордера текущей позиции
      
         event.SetProperty(EVENT_PROP_PRICE_OPEN_BEFORE,order.PriceOpen());                        // Цена установки ордера до модификации
         event.SetProperty(EVENT_PROP_PRICE_SL_BEFORE,order.StopLoss());                           // Цена StopLoss до модификации
         event.SetProperty(EVENT_PROP_PRICE_TP_BEFORE,order.TakeProfit());                         // Цена TakeProfit до модификации
         event.SetProperty(EVENT_PROP_PRICE_EVENT_ASK,this.m_tick.ask);                            // Цена Ask в момент события
         event.SetProperty(EVENT_PROP_PRICE_EVENT_BID,this.m_tick.bid);                            // Цена Bid в момент события
         
         event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                                  // Магический номер ордера/сделки/позиции
         event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpen());                       // Время ордера, на основании которого открыта сделка позиции (первый ордер позиции)
         event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());                              // Цена, на которой произошло событие
         event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());                               // Цена открытия ордера/сделки/позиции
         event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());                             // Цена закрытия ордера/сделки/позиции
         event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());                                  // Цена StopLoss позиции
         event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());                                // Цена TakeProfit позиции
         event.SetProperty(EVENT_PROP_VOLUME_ORDER_INITIAL,order.Volume());                        // Запрашиваемый объём ордера
         event.SetProperty(EVENT_PROP_VOLUME_ORDER_EXECUTED,order.Volume()-order.VolumeCurrent()); // Исполненный объём ордера
         event.SetProperty(EVENT_PROP_VOLUME_ORDER_CURRENT,order.VolumeCurrent());                 // Оставшийся (неисполненный) объём ордера
         event.SetProperty(EVENT_PROP_VOLUME_POSITION_EXECUTED,order.Volume());                    // Исполненный объём позиции
         event.SetProperty(EVENT_PROP_PROFIT,order.Profit());                                      // Профит
         event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                                      // Символ ордера
         event.SetProperty(EVENT_PROP_SYMBOL_BY_ID,close_by_symbol);                               // Символ встречной позиции
         //--- Установка идентификатора графика управляющей программы и расшифровка кода события и установка типа события
         event.SetChartID(this.m_chart_id);
         event.SetTypeEvent();
         //--- Если объекта-события нет в списке - добавляем
         if(!this.IsPresentEventInList(event))
           {
            this.m_list_events.InsertSort(event);
            //--- Отправляем сообщение о событии и устанавливаем значение последнего торгового события
            event.SendEvent();
            this.m_trade_event=event.TradeEvent();
           }
         //--- Если это событие уже есть в списке - удаляем новый объект-событие и выводим отладочное сообщение
         else
           {
            ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event is already in the list."));
            delete event;
           }
        }
     }
#endif 

Так как логику создания событий мы уже рассматривали в предыдущих статьях, то ограничимся лишь кратким описанием логики: сначала устанавливаем код события как "закрытие позиции" и причину события как "заявка выполнена полностью". Далее смотрим различные свойства закрытого ордера, и на основании этих свойств добавляем в код события нужные флаги и изменяем причину события если это требуется. При определении закрытия встречной позиции, мы используем данные контрольных ордеров о закрытых позициях — из этих данных мы можем получить всю информацию о встречных ордерах ещё до их совместного закрытия, что позволяет нам определить типы ордеров и их объём, а так же узнать какой из них был инициатором встречного закрытия.
Далее все собранные данные записываются в свойства события и создаётся новое событие закрытия позиции.

Что ещё... Был доработан метод, возвращающий для MQL4 список всех закрывающих ордеров позиции:

//+------------------------------------------------------------------+
//|  Возвращает список всех закрывающих ордеров CloseBy из списка    |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListCloseByOrders(CArrayObj *list)
  {
   if(list.Type()!=COLLECTION_HISTORY_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of the history collection"));
      return NULL;
     }
#ifdef __MQL5__
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,ORDER_TYPE_CLOSE_BY,EQUAL);
#else 
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_BY_ID,0,NO_EQUAL);
#endif 
   return list_orders;
  }
//+------------------------------------------------------------------+

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

Так же был доработан метод, возвращающий для MQL4 последний, закрывающий ордер позиции:

//+------------------------------------------------------------------+
//| Возвращает последний закрывающий ордер                           |
//| из списка всех ордеров позиции                                   |
//+------------------------------------------------------------------+
COrder* CEventsCollection::GetCloseByOrderFromList(CArrayObj *list,const ulong position_id)
  {
#ifdef __MQL5__
   CArrayObj* list_orders=this.GetListAllOrdersByPosID(list,position_id);
   list_orders=CSelect::ByOrderProperty(list_orders,ORDER_PROP_TYPE,ORDER_TYPE_CLOSE_BY,EQUAL);
   if(list_orders==NULL || list_orders.Total()==0) return NULL;
   list_orders.Sort(SORT_BY_ORDER_TIME_OPEN);
#else 
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_BY_ID,position_id,EQUAL);
   if(list_orders==NULL || list_orders.Total()==0) return NULL;
   list_orders.Sort(SORT_BY_ORDER_TIME_CLOSE);
#endif 
   COrder* order=list_orders.At(list_orders.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

Если в MQL5 мы в любой момент можем получить последний ордер, принадлежащий позиции, то в MQL4 такой возможности у нас нет. Поэтому было решено для MQL4 возвращать ордер встречной позиции, если он есть, либо NULL, если позиция была закрыта не встречной — таким образом мы сможем частично реализовать получение закрывающего ордера в случае, если позиция была закрыта встречной.

Это все изменения и доработки, необходимые для определения закрытия позиций для MQL4.

С полными листингами всех классов можно ознакомиться в файлах, прикреплённых в конце статьи.

Тестирование

Для тестирования возьмём тестовый советник из прошлой статьи  TestDoEasyPart10.mq4 из папки \MQL4\Experts\TestDoEasy\Part10 и сохраним его в новой папке \MQL4\Experts\TestDoEasy\Part11 под новым именем TestDoEasyPart11.mq4.

В связи с удалением из перечисления возможных критериев сортировки ордеров и сделок ENUM_SORT_ORDERS_MODE констант времени в милисекундах, нам необходимо в советнике, в обработчике нажатия кнопки удаления отложенных ордеров исправить сортировку по времени установки, где прописана удалённая константа 

list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);

на сортировку по времени:

      //--- Если нажата кнопка BUTT_DELETE_PENDING: Удалить первый отложенный ордер
      else if(button==EnumToString(BUTT_DELETE_PENDING))
        {
         //--- Получаем список всех ордеров
         CArrayObj* list=engine.GetListMarketPendings();
         if(list!=NULL)
           {
            //--- Сортируем список по времени установки
            list.Sort(SORT_BY_ORDER_TIME_OPEN);
            int total=list.Total();
            //--- В цикле от позиции с наибольшим временем
            for(int i=total-1;i>=0;i--)
              {
               COrder* order=list.At(i);
               if(order==NULL)
                  continue;
               //--- удаяем ордер по его тикету
               #ifdef __MQL5__
                  trade.OrderDelete(order.Ticket());
               #else 
                  PendingOrderDelete(order.Ticket());
               #endif 
              }
           }
        }

Скомпилируем советник, установим в тестере значения входных параметров StopLoss in points и TakeProfit in points нулевыми — чтобы позиции открывались без стоп-приказов. Запустим советник в тестере, откроем позицию и затем частично её закроем.
Затем выставим и удалим отложенный ордер:


Теперь у нас события частичного закрытия и удаления отложенного ордера не определяются как одно и то же событие закрытия позиции, а различаются.

Запустим ещё раз и понажимаем кнопки, наблюдая за определением событий:


Видим, что события определяются верно. Событие закрытия встречной позицией определяется, модификация стоп-уровней позиций и цены установки отложенных ордеров так же работают и отслеживаются.

Что дальше

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

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

К содержанию

Статьи этой серии:

Часть 1. Концепция, организация данных.
Часть 2. Коллекция исторических ордеров и сделок.
Часть 3. Коллекция рыночных ордеров и позиций, организация поиска.
Часть 4. Торговые события. Концепция.
Часть 5. Классы и коллекция торговых событий. Отправка событий в программу.
Часть 6. События на счёте с типом неттинг.
Часть 7. События срабатывания StopLimit-ордеров, подготовка функционала для регистрации событий модификации ордеров и позиций.
Часть 8. События модификации ордеров и позиций.
Часть 9. Совместимость с MQL4 - Подготовка данных.
Часть 10. Совместимость с MQL4 - События открытия позиций и активации отложенных ордеров.


Прикрепленные файлы |
MQL5.zip (106.3 KB)
MQL4.zip (106.29 KB)
Методы измерения скорости движения цены Методы измерения скорости движения цены
Существует множество различных подходов к исследованию и анализу рынков. Но основных обычно два: технический и фундаментальный. В первом случае происходит сбор, обработка и изучение каких-либо числовых данных и характеристик, связанных с рынком: цены, объемы и так далее. Во втором делается анализ событий и новостей, которые, в свою очередь, влияют прямо или косвенно на рынки. В статье рассматриваются методы измерения скорости движения цены и исследование торговых стратегий на их основе.
Оценка индекса фрактальности, показателя Херста и возможность предсказания финансовых временных рядов Оценка индекса фрактальности, показателя Херста и возможность предсказания финансовых временных рядов
Поиски и изучение фрактального поведения финансовых данных подразумевают, что за внешне хаотическим поведением экономических временных рядов скрываются и действуют устойчивые механизмы коллективного поведения участников. На бирже такие механизмы могут приводить к возникновению ценовой динамики, которая определяет и описывает специфические свойства ценовых рядов. В трейдинге были бы интересны такие индикаторы, которые могут эффективно и устойчиво оценивать параметры фрактальности на том масштабе и диапазоне времени, которые актуальны на практике.
Библиотека для простого и быстрого создания программ для MetaTrader (Часть XII): Класс объекта "аккаунт", коллекция объектов-аккаунтов Библиотека для простого и быстрого создания программ для MetaTrader (Часть XII): Класс объекта "аккаунт", коллекция объектов-аккаунтов
В предыдущей статье мы определили события закрытия позиций для MQL4 в библиотеке и избавились от оказавшихся невостребованными свойств ордеров. В данной статье рассмотрим создание объекта "Аккаунт", создадим коллекцию объектов-аккаунтов и подготовим функционал для отслеживания событий аккаунтов.
Библиотека для простого и быстрого создания программ для MetaTrader (Часть X): Совместимость с MQL4 - События открытия позиции и активации отложенных ордеров Библиотека для простого и быстрого создания программ для MetaTrader (Часть X): Совместимость с MQL4 - События открытия позиции и активации отложенных ордеров
В предыдущих статьях мы начали создавать большую кроссплатформенную библиотеку, целью которой является упростить написание программ для платформ MetaTrader 5 и MetaTrader 4. В девятой части начали дорабатывать классы библиотеки для работы в MQL4. В данной статье продолжим доработку библиотеки с целью полной её совместимости с MQL4.