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

Библиотека для простого и быстрого создания программ для MetaTrader (Часть VII): События срабатывания StopLimit-ордеров, подготовка функционала для событий модификации ордеров и позиций

MetaTrader 5Примеры | 30 апреля 2019, 19:28
2 784 11
Artyom Trishkin
Artyom Trishkin

Содержание

Концепция

В прошлых частях описания кроссплатформенной библиотеки для MetaTrader 5 и MetaTrader 4 мы подготовили инструментарий для создания user-case функций для быстрого доступа из своих программ к любым данным любых ордеров и позиций на счётах с типом хэдж и неттинг. Это функции для отслеживания событий, происходящих с ордерами и позициями — выставление, удаление и срабатывание отложенных ордеров, открытие и закрытие позиций.
Но пока остаётся нереализованным функционал для отслеживания срабатывания уже выставленных StopLimit-ордеров и модификации рыночных ордеров и позиций.

В данной статье реализуем отслеживание события срабатывания StopLimit-ордера, результатом которого будет выставление Limit-ордера.
Библиотека будет отслеживать такие события и отправлять в программу необходимые сообщения для дальнейшего их использования в ней.

Реализация

При тестировании моментов срабатывания выставленных StopLimit-ордеров я заметил, что это событие никак не отображается в истории счёта в терминале, и его не получится просто взять, и получить из истории счёта "as is". Поэтому придётся отслеживать состояние существующих ордеров до момента изменения этого состояния (в данном случае — изменение типа выставленного ордера с одним и тем же тикетом).

Подойдём к реализации отслеживания срабатывания StopLimit-ордеров с практической точки зрения — помимо создания данного функционала, сделаем его пригодным для отслеживания и остальных событий по изменению уже существующих ордеров и позиций (изменение цены установки существующих отложенных ордеров, их уровней StopLoss и TakeProfit и этих же уровней у открытой позиции).

Логика подготавливаемого функционала будет такой:

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

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

  • Для учёта изменения цены установки ордера нам необходимо учитывать эту цену
  • Для учёта изменения цен StopLoss и TakeProfit нам необходимо учитывать и эти цены.

Значит, к хэш-сумме добавим эти три цены, но каждую преобразуем в семиразрядное ulong-число — просто отбросим запятую и увеличим на один порядок разрядность числа (для учёта шестизначных котировок): например, если цена равна 1.12345, то число для хэш-суммы будет 1123450.

Закончим с теорией и приступим к реализации.

В файл Defines.mqh добавим перечисления с флагами возможных вариантов изменения свойств ордеров и позиций, и сами варианты, которые будем отслеживать:

//+------------------------------------------------------------------+
//| Список флагов возможных вариантов изменения ордеров и позиций    |
//+------------------------------------------------------------------+
enum ENUM_CHANGE_TYPE_FLAGS
  {
   CHANGE_TYPE_FLAG_NO_CHANGE    =  0,                      // Нет изменений
   CHANGE_TYPE_FLAG_TYPE         =  1,                      // Изменение типа ордера
   CHANGE_TYPE_FLAG_PRICE        =  2,                      // Изменение цены
   CHANGE_TYPE_FLAG_STOP         =  4,                      // Изменение StopLoss
   CHANGE_TYPE_FLAG_TAKE         =  8,                      // Изменение TakeProfit
   CHANGE_TYPE_FLAG_ORDER        =  16                      // Флаг изменения свойств ордера
  };
//+------------------------------------------------------------------+
//| Возможные варианты изменения ордеров и позиций                   |
//+------------------------------------------------------------------+
enum ENUM_CHANGE_TYPE
  {
   CHANGE_TYPE_NO_CHANGE,                                   // Нет изменений
   CHANGE_TYPE_ORDER_TYPE,                                  // Изменение типа ордера
   CHANGE_TYPE_ORDER_PRICE,                                 // Изменение цены установки ордера
   CHANGE_TYPE_ORDER_PRICE_STOP_LOSS,                       // Изменение цены установки ордера и StopLoss 
   CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT,                     // Изменение цены установки ордера и TakeProfit
   CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT,           // Изменение цены установки ордера, StopLoss и TakeProfit
   CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT,                 // Изменение StopLoss и TakeProfit
   CHANGE_TYPE_ORDER_STOP_LOSS,                             // Изменение StopLoss ордера
   CHANGE_TYPE_ORDER_TAKE_PROFIT,                           // Изменение TakeProfit ордера
   CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT,              // Изменение StopLoss и TakeProfit позиции
   CHANGE_TYPE_POSITION_STOP_LOSS,                          // Изменение StopLoss позиции
   CHANGE_TYPE_POSITION_TAKE_PROFIT,                        // Изменение TakeProfit позиции
  };
//+------------------------------------------------------------------+

Относительно флагов возможных вариантов изменения свойств ордеров и позиций:

  • флаг изменения типа ордера будет устанавливаться при срабатывании StopLimit-ордера,
  • флаг изменения цены будет устанавливаться при модификации цены установки отложенного ордера,
  • флаги изменения стоплосс и тейкпрофит — понятно,
  • флаг ордера — для идентификации изменения свойств ордера (не позиции)
По флагу ордера, думаю, требуется уточнение: тип и цена установки могут измениться однозначно только у отложенных ордеров (изменение типа позиции (переворот) на неттинг-счёте здесь во внимание не берём — это уже всё отслеживается, и было сделано нами в шестой части описания библиотеки), а вот цены StopLoss и TakeProfit могут быть модифицированы как у ордера, так и у позиции. Вот тут нам и нужен флаг ордера — чтобы однозначно идентифицировать событие и отправить тип этого события в класс отслеживания событий.

В перечислении возможных вариантов модификаций ордеров и позиций собраны все варианты, которые будем в дальнейшем отслеживать. Сегодня сделаем лишь отслеживание события срабатывания StopLimit-ордера (CHANGE_TYPE_ORDER_TYPE).

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

//+------------------------------------------------------------------+
//| Список возможных торговых событий на счёте                       |
//+------------------------------------------------------------------+
enum ENUM_TRADE_EVENT
  {
   TRADE_EVENT_NO_EVENT = 0,                                // Нет торгового события
   TRADE_EVENT_PENDING_ORDER_PLASED,                        // Отложенный ордер установлен
   TRADE_EVENT_PENDING_ORDER_REMOVED,                       // Отложенный ордер удалён
//--- члены перечисления, совпадающие с членами перечисления ENUM_DEAL_TYPE
//--- (порядок следования констант ниже менять нельзя, удалять и добавлять новые - нельзя)
   TRADE_EVENT_ACCOUNT_CREDIT = DEAL_TYPE_CREDIT,           // Начисление кредита (3)
   TRADE_EVENT_ACCOUNT_CHARGE,                              // Дополнительные сборы
   TRADE_EVENT_ACCOUNT_CORRECTION,                          // Корректирующая запись
   TRADE_EVENT_ACCOUNT_BONUS,                               // Перечисление бонусов
   TRADE_EVENT_ACCOUNT_COMISSION,                           // Дополнительные комиссии
   TRADE_EVENT_ACCOUNT_COMISSION_DAILY,                     // Комиссия, начисляемая в конце торгового дня
   TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY,                   // Комиссия, начисляемая в конце месяца
   TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY,               // Агентская комиссия, начисляемая в конце торгового дня
   TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY,             // Агентская комиссия, начисляемая в конце месяца
   TRADE_EVENT_ACCOUNT_INTEREST,                            // Начисления процентов на свободные средства
   TRADE_EVENT_BUY_CANCELLED,                               // Отмененная сделка покупки
   TRADE_EVENT_SELL_CANCELLED,                              // Отмененная сделка продажи
   TRADE_EVENT_DIVIDENT,                                    // Начисление дивиденда
   TRADE_EVENT_DIVIDENT_FRANKED,                            // Начисление франкированного дивиденда
   TRADE_EVENT_TAX                        = DEAL_TAX,       // Начисление налога
//--- константы, относящиеся к типу сделки DEAL_TYPE_BALANCE из перечисления ENUM_DEAL_TYPE
   TRADE_EVENT_ACCOUNT_BALANCE_REFILL     = DEAL_TAX+1,     // Пополнение средств на балансе
   TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL = DEAL_TAX+2,     // Снятие средств с баланса
//--- Остальные возможные торговый события
//--- (менять порядок следования констант ниже, удалять и добавлять новые - можно)
   TRADE_EVENT_PENDING_ORDER_ACTIVATED    = DEAL_TAX+3,     // Отложенный ордер активирован ценой
   TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL,             // Отложенный ордер активирован ценой частично
   TRADE_EVENT_POSITION_OPENED,                             // Позиция открыта
   TRADE_EVENT_POSITION_OPENED_PARTIAL,                     // Позиция открыта частично
   TRADE_EVENT_POSITION_CLOSED,                             // Позиция закрыта
   TRADE_EVENT_POSITION_CLOSED_BY_POS,                      // Позиция закрыта встречной
   TRADE_EVENT_POSITION_CLOSED_BY_SL,                       // Позиция закрыта по StopLoss
   TRADE_EVENT_POSITION_CLOSED_BY_TP,                       // Позиция закрыта по TakeProfit
   TRADE_EVENT_POSITION_REVERSED_BY_MARKET,                 // Разворот позиции новой сделкой (неттинг)
   TRADE_EVENT_POSITION_REVERSED_BY_PENDING,                // Разворот позиции активацией отложенного ордера (неттинг)
   TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL,         // Разворот позиции частичным исполнением маркет-ордера (неттинг)
   TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL,        // Разворот позиции частичной активацией отложенного ордера (неттинг)
   TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET,               // Добавлен объём к позиции новой сделкой (неттинг)
   TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL,       // Добавлен объём к позиции частичным исполнением маркет-ордера (неттинг)
   TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING,              // Добавлен объём к позиции активацией отложенного ордера (неттинг)
   TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL,      // Добавлен объём к позиции частичной активацией отложенного ордера (неттинг)
   TRADE_EVENT_POSITION_CLOSED_PARTIAL,                     // Позиция закрыта частично
   TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS,              // Позиция закрыта частично встречной
   TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL,               // Позиция закрыта частично по StopLoss
   TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP,               // Позиция закрыта частично по TakeProfit
   TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER,                  // Срабатывание StopLimit ордера
   TRADE_EVENT_MODIFY_ORDER_PRICE,                          // Изменение цены установки ордера
   TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS,                // Изменение цены установки ордера и StopLoss 
   TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT,              // Изменение цены установки ордера и TakeProfit
   TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT,    // Изменение цены установки ордера, StopLoss и TakeProfit
   TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT,          // Изменение StopLoss и TakeProfit ордера
   TRADE_EVENT_MODIFY_POSITION_STOP_LOSS,                   // Изменение StopLoss позиции
   TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT,                 // Изменение TakeProfit позиции
  };

И наконец в список перечислений причин событий ENUM_EVENT_REASON добавим новую константу, описывающую срабатывание StopLimit-ордера:

//+------------------------------------------------------------------+
//| Причина события                                                  |
//+------------------------------------------------------------------+
enum ENUM_EVENT_REASON
  {
   EVENT_REASON_REVERSE,                                    // Разворот позиции (неттинг)
   EVENT_REASON_REVERSE_PARTIALLY,                          // Разворот позиции частичным исполнением заявки (неттинг)
   EVENT_REASON_REVERSE_BY_PENDING,                         // Разворот позиции при срабатывании отложенного ордера (неттинг)
   EVENT_REASON_REVERSE_BY_PENDING_PARTIALLY,               // Разворот позиции при при частичном срабатывании отложенного ордера (неттинг)
   //--- Все константы, относящиеся к развороту позиции, должны быть в списке выше
   EVENT_REASON_ACTIVATED_PENDING,                          // Срабатывание отложенного ордера
   EVENT_REASON_ACTIVATED_PENDING_PARTIALLY,                // Частичное срабатывание отложенного ордера
   EVENT_REASON_STOPLIMIT_TRIGGERED,                        // Срабатывание StopLimit-ордера
   EVENT_REASON_CANCEL,                                     // Отмена
   EVENT_REASON_EXPIRED,                                    // Истечение срока действия ордера
   EVENT_REASON_DONE,                                       // Заявка исполнена полностью
   EVENT_REASON_DONE_PARTIALLY,                             // Заявка исполнена частично
   EVENT_REASON_VOLUME_ADD,                                 // Добавление объёма к позиции (неттинг)
   EVENT_REASON_VOLUME_ADD_PARTIALLY,                       // Добавление объёма к позиции частичным исполнением заявки (неттинг)
   EVENT_REASON_VOLUME_ADD_BY_PENDING,                      // Добавление объёма к позиции при срабатывании отложенного ордера (неттинг)
   EVENT_REASON_VOLUME_ADD_BY_PENDING_PARTIALLY,            // Добавление объёма к позиции при частичном срабатывании отложенного ордера (неттинг)
   EVENT_REASON_DONE_SL,                                    // Закрытие по StopLoss
   EVENT_REASON_DONE_SL_PARTIALLY,                          // Частичное закрытие по StopLoss
   EVENT_REASON_DONE_TP,                                    // Закрытие по TakeProfit
   EVENT_REASON_DONE_TP_PARTIALLY,                          // Частичное закрытие по TakeProfit
   EVENT_REASON_DONE_BY_POS,                                // Закрытие встречной позицией
   EVENT_REASON_DONE_PARTIALLY_BY_POS,                      // Частичное закрытие встречной позицией
   EVENT_REASON_DONE_BY_POS_PARTIALLY,                      // Закрытие частичным объёмом встречной позиции
   EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY,            // Частичное закрытие частичным объёмом встречной позиции
   //--- Константы, относящиеся к типу сделки DEAL_TYPE_BALANCE из перечисления ENUM_DEAL_TYPE
   EVENT_REASON_BALANCE_REFILL,                             // Пополнение счёта
   EVENT_REASON_BALANCE_WITHDRAWAL,                         // Снятие средств со счёта
   //--- Список констант соотносится с TRADE_EVENT_ACCOUNT_CREDIT из перечисления ENUM_TRADE_EVENT, смещено на +13 относительно ENUM_DEAL_TYPE (EVENT_REASON_ACCOUNT_CREDIT-3)
   EVENT_REASON_ACCOUNT_CREDIT,                             // Начисление кредита
   EVENT_REASON_ACCOUNT_CHARGE,                             // Дополнительные сборы
   EVENT_REASON_ACCOUNT_CORRECTION,                         // Корректирующая запись
   EVENT_REASON_ACCOUNT_BONUS,                              // Перечисление бонусов
   EVENT_REASON_ACCOUNT_COMISSION,                          // Дополнительные комиссии
   EVENT_REASON_ACCOUNT_COMISSION_DAILY,                    // Комиссия, начисляемая в конце торгового дня
   EVENT_REASON_ACCOUNT_COMISSION_MONTHLY,                  // Комиссия, начисляемая в конце месяца
   EVENT_REASON_ACCOUNT_COMISSION_AGENT_DAILY,              // Агентская комиссия, начисляемая в конце торгового дня
   EVENT_REASON_ACCOUNT_COMISSION_AGENT_MONTHLY,            // Агентская комиссия, начисляемая в конце месяца
   EVENT_REASON_ACCOUNT_INTEREST,                           // Начисления процентов на свободные средства
   EVENT_REASON_BUY_CANCELLED,                              // Отмененная сделка покупки
   EVENT_REASON_SELL_CANCELLED,                             // Отмененная сделка продажи
   EVENT_REASON_DIVIDENT,                                   // Начисление дивиденда
   EVENT_REASON_DIVIDENT_FRANKED,                           // Начисление франкированного дивиденда
   EVENT_REASON_TAX                                         // Начисление налога
  };
#define REASON_EVENT_SHIFT    (EVENT_REASON_ACCOUNT_CREDIT-3)

На этом изменения в файле Defines.mqh завершены.

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

Создадим класс объекта-контрольного ордера.

Создадим в папке библиотеки Collections новый класс с названием файла OrderControl.mqh. Базовым классом сделаем класс стандартной библиотеки CObject и подключим необходимые для работа класса файлы:

//+------------------------------------------------------------------+
//|                                                 OrderControl.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\Defines.mqh"             
#include "..\Objects\Orders\Order.mqh"
#include <Object.mqh>                 
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class COrderControl : public CObject
  {
private:

public:
                     COrderControl();
                    ~COrderControl();
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
COrderControl::COrderControl()
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
COrderControl::~COrderControl()
  {
  }
//+------------------------------------------------------------------+

Сразу объявим все необходимые переменные и методы в приватной секции класса:

private:
   ENUM_CHANGE_TYPE  m_changed_type;                                    // Тип изменения ордера
   MqlTick           m_tick;                                            // Структура тика
   string            m_symbol;                                          // Символ
   ulong             m_position_id;                                     // Идентификатор позиции
   ulong             m_ticket;                                          // Тикет ордера
   long              m_magic;                                           // Магический номер
   ulong             m_type_order;                                      // Тип ордера
   ulong             m_type_order_prev;                                 // Предыдущий тип ордера
   double            m_price;                                           // Цена ордера
   double            m_price_prev;                                      // Предыдущая цена ордера
   double            m_stop;                                            // Цена StopLoss
   double            m_stop_prev;                                       // Предыдущая цена StopLoss
   double            m_take;                                            // Цена TakeProfit
   double            m_take_prev;                                       // Предыдущая цена TakeProfit
   double            m_volume;                                          // Объём ордера
   datetime          m_time;                                            // Время установки ордера
   datetime          m_time_prev;                                       // Предыдущее время установки ордера
   int               m_change_code;                                     // Код изменения ордера
//--- возвращает факт наличия флага изменения свойства
   bool              IsPresentChangeFlag(const int change_flag)   const { return (this.m_change_code & change_flag)==change_flag;   }
//--- Рассчитывает тип изменения параметров ордера
   void              CalculateChangedType(void);

Все переменные-члены класса имеют понятные описания. Поясню о переменной, хранящей структуру тика: при срабатывании StopLimit-ордера нам необходимо будет записать время срабатывания. Так как время требуется записывать в милисекундах, а TimeCurrent() возвращает время без милисекунд, то для того, чтобы получить время последнего тика, на котором произошло срабатывание ордера, с милисекундами, будем использовать стандартную функцию SymbolInfoTick(), которая заполняет структуру тика данными, в числе которых есть и время тика с милисекундами.

Код изменения ордера будет составляться из флагов, описанных нами в перечислении ENUM_CHANGE_TYPE_FLAGS, и будет зависеть от произошедших изменений свойств ордера. Проверкой флагов и созданием кода изменения ордера будет заниматься приватный метод CalculateChangedType(), который будет рассмотрен нами ниже.

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

//+------------------------------------------------------------------+
//|                                                 OrderControl.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\Defines.mqh"
#include "..\Objects\Orders\Order.mqh"
#include <Object.mqh>
//+------------------------------------------------------------------+
//| Класс контроля ордеров и позиций                                 |
//+------------------------------------------------------------------+
class COrderControl : public CObject
  {
private:
   ENUM_CHANGE_TYPE  m_changed_type;                                    // Тип изменения ордера
   MqlTick           m_tick;                                            // Структура тика
   string            m_symbol;                                          // Символ
   ulong             m_position_id;                                     // Идентификатор позиции
   ulong             m_ticket;                                          // Тикет ордера
   long              m_magic;                                           // Магический номер
   ulong             m_type_order;                                      // Тип ордера
   ulong             m_type_order_prev;                                 // Предыдущий тип ордера
   double            m_price;                                           // Цена ордера
   double            m_price_prev;                                      // Предыдущая цена ордера
   double            m_stop;                                            // Цена StopLoss
   double            m_stop_prev;                                       // Предыдущая цена StopLoss
   double            m_take;                                            // Цена TakeProfit
   double            m_take_prev;                                       // Предыдущая цена TakeProfit
   double            m_volume;                                          // Объём ордера
   datetime          m_time;                                            // Время установки ордера
   datetime          m_time_prev;                                       // Предыдущее время установки ордера
   int               m_change_code;                                     // Код изменения ордера
//--- возвращает факт наличия флага изменения свойства
   bool              IsPresentChangeFlag(const int change_flag)   const { return (this.m_change_code & change_flag)==change_flag;   }
//--- Рассчитывает тип изменения параметров ордера
   void              CalculateChangedType(void);
public:
//--- Устанавливает (1,2) текущий и прошлый тип (2,3) текущую и прошлую цену, (4,5) текущий и прошлый StopLoss,
//--- (6,7) текущий и прошлый TakeProfit, (8,9) текущее и прошлое время установки, (10) объём
   void              SetTypeOrder(const ulong type)                     { this.m_type_order=type;        }
   void              SetTypeOrderPrev(const ulong type)                 { this.m_type_order_prev=type;   }
   void              SetPrice(const double price)                       { this.m_price=price;            }
   void              SetPricePrev(const double price)                   { this.m_price_prev=price;       }
   void              SetStopLoss(const double stop_loss)                { this.m_stop=stop_loss;         }
   void              SetStopLossPrev(const double stop_loss)            { this.m_stop_prev=stop_loss;    }
   void              SetTakeProfit(const double take_profit)            { this.m_take=take_profit;       }
   void              SetTakeProfitPrev(const double take_profit)        { this.m_take_prev=take_profit;  }
   void              SetTime(const datetime time)                       { this.m_time=time;              }
   void              SetTimePrev(const datetime time)                   { this.m_time_prev=time;         }
   void              SetVolume(const double volume)                     { this.m_volume=volume;          }
//--- Устанавливает (1) тип изменения, (2) новое текущее состояние
   void              SetChangedType(const ENUM_CHANGE_TYPE type)        { this.m_changed_type=type;      }
   void              SetNewState(COrder* order);
//--- Проверяет и устанавливает флаги изменения параметров ордера и возвращает тип изменения
   ENUM_CHANGE_TYPE  ChangeControl(COrder* compared_order);

//--- Возвращает (1,2,3,4) идентификатор позиции, тикет, магик и символ, (5,6) текущий и прошлый тип (7,8) текущую и прошлую цену,
//--- (9,10) текущий и прошлый StopLoss, (11,12) текущий и прошлый TakeProfit, (13,14) текущее и прошлое время установки, (15) объём
   ulong             PositionID(void)                             const { return this.m_position_id;     }
   ulong             Ticket(void)                                 const { return this.m_ticket;          }
   long              Magic(void)                                  const { return this.m_magic;           }
   string            Symbol(void)                                 const { return this.m_symbol;          }
   ulong             TypeOrder(void)                              const { return this.m_type_order;      }
   ulong             TypeOrderPrev(void)                          const { return this.m_type_order_prev; }
   double            Price(void)                                  const { return this.m_price;           }
   double            PricePrev(void)                              const { return this.m_price_prev;      }
   double            StopLoss(void)                               const { return this.m_stop;            }
   double            StopLossPrev(void)                           const { return this.m_stop_prev;       }
   double            TakeProfit(void)                             const { return this.m_take;            }
   double            TakeProfitPrev(void)                         const { return this.m_take_prev;       }
   ulong             Time(void)                                   const { return this.m_time;            }
   ulong             TimePrev(void)                               const { return this.m_time_prev;       }
   double            Volume(void)                                 const { return this.m_volume;          }
//--- Возвращает тип изменения
   ENUM_CHANGE_TYPE  GetChangeType(void)                          const { return this.m_changed_type;    }

//--- Конструктор
                     COrderControl(const ulong position_id,const ulong ticket,const long magic,const string symbol) :
                                                                        m_change_code(CHANGE_TYPE_FLAG_NO_CHANGE),
                                                                        m_changed_type(CHANGE_TYPE_NO_CHANGE),
                                                                        m_position_id(position_id),m_symbol(symbol),m_ticket(ticket),m_magic(magic) {;}
  };
//+------------------------------------------------------------------+

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

За пределами тела класса напишем реализацию объявленных методов.
Приватный метод, рассчитывающий тип изменения параметров ордера/позиции:

//+------------------------------------------------------------------+
//| Рассчитывает тип изменения параметров ордера                     |
//+------------------------------------------------------------------+
void COrderControl::CalculateChangedType(void)
  {
   this.m_changed_type=
     (
      //--- Если стоит флаг ордера
      this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_ORDER) ? 
        (
         //--- Если сработал StopLimit-ордер
         this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TYPE)    ?  CHANGE_TYPE_ORDER_TYPE :
         //--- Если модифицирована цена установки ордера
         this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_PRICE)   ? 
           (
            //--- Если вместе с ценой установки модифицированы StopLoss и TakeProfit
            this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT :
            //--- Если вместе с ценой установки модифицирован TakeProfit
            this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT : 
            //--- Если вместе с ценой установки модифицирован StopLoss
            this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS   :
            //--- Модифицирована только цена установки ордера
            CHANGE_TYPE_ORDER_PRICE
           ) : 
         //--- Цена установки не модифицирована
         //--- Если модифицированы StopLoss и TakeProfit
         this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT :
         //--- Если модифицирован TakeProfit
         this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_TAKE_PROFIT : 
         //--- Если модифицирован StopLoss
         this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS   :
         //--- Нет изменений
         CHANGE_TYPE_NO_CHANGE
        ) : 
      //--- Позиция
      //--- Если у позиции модифицированы StopLoss и TakeProfit
      this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT :
      //--- Если у позиции модифицирован TakeProfit
      this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_POSITION_TAKE_PROFIT : 
      //--- Если у позиции модифицирован StopLoss
      this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS   :
      //--- Нет изменений
      CHANGE_TYPE_NO_CHANGE
     );
  }
//+------------------------------------------------------------------+

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

bool IsPresentChangeFlag(const int change_flag const { return (this.m_change_code & change_flag)==change_flag }

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

Метод, устанавливающий новое текущее состояние свойств ордера/позиции:

//+------------------------------------------------------------------+
//| Устанавливает новое текущее состояние                            |
//+------------------------------------------------------------------+
void COrderControl::SetNewState(COrder* order)
  {
   if(order==NULL || !::SymbolInfoTick(this.Symbol(),this.m_tick))
      return;
   //--- Новый тип
   this.SetTypeOrderPrev(this.TypeOrder());
   this.SetTypeOrder(order.TypeOrder());
   //--- Новая цена
   this.SetPricePrev(this.Price());
   this.SetPrice(order.PriceOpen());
   //--- Новый StopLoss
   this.SetStopLossPrev(this.StopLoss());
   this.SetStopLoss(order.StopLoss());
   //--- Новый TakeProfit
   this.SetTakeProfitPrev(this.TakeProfit());
   this.SetTakeProfit(order.TakeProfit());
   //--- Новое время
   this.SetTimePrev(this.Time());
   this.SetTime(this.m_tick.time_msc);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Проверяет и устанавливает флаги изменения параметров ордера      |
//+------------------------------------------------------------------+
ENUM_CHANGE_TYPE COrderControl::ChangeControl(COrder *compared_order)
  {
   this.m_change_code=CHANGE_TYPE_FLAG_NO_CHANGE;
   if(compared_order==NULL || compared_order.Ticket()!=this.m_ticket)
      return CHANGE_TYPE_NO_CHANGE;
   if(compared_order.Status()==ORDER_STATUS_MARKET_ORDER || compared_order.Status()==ORDER_STATUS_MARKET_PENDING)
      this.m_change_code+=CHANGE_TYPE_FLAG_ORDER;
   if(compared_order.TypeOrder()!=this.m_type_order)
      this.m_change_code+=CHANGE_TYPE_FLAG_TYPE;
   if(compared_order.PriceOpen()!=this.m_price)
      this.m_change_code+=CHANGE_TYPE_FLAG_PRICE;
   if(compared_order.StopLoss()!=this.m_stop)
      this.m_change_code+=CHANGE_TYPE_FLAG_STOP;
   if(compared_order.TakeProfit()!=this.m_take)
      this.m_change_code+=CHANGE_TYPE_FLAG_TAKE;
   this.CalculateChangedType();
   return this.GetChangeType();
  }
//+------------------------------------------------------------------+

В метод передаётся указатель на проверяемый ордер/позицию и инициализируется код изменения. Если передан пустой объект сравниваемого ордера или его тикет не равен тикету текущего контрольного ордера, то возвращаем код отсутствия изменения.

Затем проверяем на неравенсто все отслеживаемые свойства контрольного и проверяемого ордеров, и если оно найдено, то в код изменения добавляется нужный флаг, описывающий данное изменение.
Далее по полностью сформированному коду изменения рассчитывается тип изменения в методе CalculateChangedType() и возвращается в вызывающую программу методом GetChangeType().

Полный листинг класса контрольного ордера:

//+------------------------------------------------------------------+
//|                                                 OrderControl.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\Defines.mqh"
#include "..\Objects\Orders\Order.mqh"
#include <Object.mqh>
//+------------------------------------------------------------------+
//| Класс контроля ордеров и позиций                                 |
//+------------------------------------------------------------------+
class COrderControl : public CObject
  {
private:
   ENUM_CHANGE_TYPE  m_changed_type;                                    // Тип изменения ордера
   MqlTick           m_tick;                                            // Структура тика
   string            m_symbol;                                          // Символ
   ulong             m_position_id;                                     // Идентификатор позиции
   ulong             m_ticket;                                          // Тикет ордера
   long              m_magic;                                           // Магический номер
   ulong             m_type_order;                                      // Тип ордера
   ulong             m_type_order_prev;                                 // Предыдущий тип ордера
   double            m_price;                                           // Цена ордера
   double            m_price_prev;                                      // Предыдущая цена ордера
   double            m_stop;                                            // Цена StopLoss
   double            m_stop_prev;                                       // Предыдущая цена StopLoss
   double            m_take;                                            // Цена TakeProfit
   double            m_take_prev;                                       // Предыдущая цена TakeProfit
   double            m_volume;                                          // Объём ордера
   datetime          m_time;                                            // Время установки ордера
   datetime          m_time_prev;                                       // Предыдущее время установки ордера
   int               m_change_code;                                     // Код изменения ордера
//--- возвращает факт наличия флага изменения свойства
   bool              IsPresentChangeFlag(const int change_flag)   const { return (this.m_change_code & change_flag)==change_flag;   }
//--- Рассчитывает тип изменения параметров ордера
   void              CalculateChangedType(void);
public:
//--- Устанавливает (1,2) текущий и прошлый тип (2,3) текущую и прошлую цену, (4,5) текущий и прошлый StopLoss,
//--- (6,7) текущий и прошлый TakeProfit, (8,9) текущее и прошлое время установки, (10) объём
   void              SetTypeOrder(const ulong type)                     { this.m_type_order=type;        }
   void              SetTypeOrderPrev(const ulong type)                 { this.m_type_order_prev=type;   }
   void              SetPrice(const double price)                       { this.m_price=price;            }
   void              SetPricePrev(const double price)                   { this.m_price_prev=price;       }
   void              SetStopLoss(const double stop_loss)                { this.m_stop=stop_loss;         }
   void              SetStopLossPrev(const double stop_loss)            { this.m_stop_prev=stop_loss;    }
   void              SetTakeProfit(const double take_profit)            { this.m_take=take_profit;       }
   void              SetTakeProfitPrev(const double take_profit)        { this.m_take_prev=take_profit;  }
   void              SetTime(const datetime time)                       { this.m_time=time;              }
   void              SetTimePrev(const datetime time)                   { this.m_time_prev=time;         }
   void              SetVolume(const double volume)                     { this.m_volume=volume;          }
//--- Устанавливает (1) тип изменения, (2) новое текущее состояние
   void              SetChangedType(const ENUM_CHANGE_TYPE type)        { this.m_changed_type=type;      }
   void              SetNewState(COrder* order);
//--- Проверяет и устанавливает флаги изменения параметров ордера и возвращает тип изменения
   ENUM_CHANGE_TYPE  ChangeControl(COrder* compared_order);

//--- Возвращает (1,2,3,4) идентификатор позиции, тикет, магик и символ, (5,6) текущий и прошлый тип (7,8) текущую и прошлую цену,
//--- (9,10) текущий и прошлый StopLoss, (11,12) текущий и прошлый TakeProfit, (13,14) текущее и прошлое время установки, (15) объём
   ulong             PositionID(void)                             const { return this.m_position_id;     }
   ulong             Ticket(void)                                 const { return this.m_ticket;          }
   long              Magic(void)                                  const { return this.m_magic;           }
   string            Symbol(void)                                 const { return this.m_symbol;          }
   ulong             TypeOrder(void)                              const { return this.m_type_order;      }
   ulong             TypeOrderPrev(void)                          const { return this.m_type_order_prev; }
   double            Price(void)                                  const { return this.m_price;           }
   double            PricePrev(void)                              const { return this.m_price_prev;      }
   double            StopLoss(void)                               const { return this.m_stop;            }
   double            StopLossPrev(void)                           const { return this.m_stop_prev;       }
   double            TakeProfit(void)                             const { return this.m_take;            }
   double            TakeProfitPrev(void)                         const { return this.m_take_prev;       }
   ulong             Time(void)                                   const { return this.m_time;            }
   ulong             TimePrev(void)                               const { return this.m_time_prev;       }
   double            Volume(void)                                 const { return this.m_volume;          }
//--- Возвращает тип изменения
   ENUM_CHANGE_TYPE  GetChangeType(void)                          const { return this.m_changed_type;    }

//--- Конструктор
                     COrderControl(const ulong position_id,const ulong ticket,const long magic,const string symbol) :
                                                                        m_change_code(CHANGE_TYPE_FLAG_NO_CHANGE),
                                                                        m_changed_type(CHANGE_TYPE_NO_CHANGE),
                                                                        m_position_id(position_id),m_symbol(symbol),m_ticket(ticket),m_magic(magic) {;}
  };
//+------------------------------------------------------------------+
//| Проверяет и устанавливает флаги изменения параметров ордера      |
//+------------------------------------------------------------------+
ENUM_CHANGE_TYPE COrderControl::ChangeControl(COrder *compared_order)
  {
   this.m_change_code=CHANGE_TYPE_FLAG_NO_CHANGE;
   if(compared_order==NULL || compared_order.Ticket()!=this.m_ticket)
      return CHANGE_TYPE_NO_CHANGE;
   if(compared_order.Status()==ORDER_STATUS_MARKET_ORDER || compared_order.Status()==ORDER_STATUS_MARKET_PENDING)
      this.m_change_code+=CHANGE_TYPE_FLAG_ORDER;
   if(compared_order.TypeOrder()!=this.m_type_order)
      this.m_change_code+=CHANGE_TYPE_FLAG_TYPE;
   if(compared_order.PriceOpen()!=this.m_price)
      this.m_change_code+=CHANGE_TYPE_FLAG_PRICE;
   if(compared_order.StopLoss()!=this.m_stop)
      this.m_change_code+=CHANGE_TYPE_FLAG_STOP;
   if(compared_order.TakeProfit()!=this.m_take)
      this.m_change_code+=CHANGE_TYPE_FLAG_TAKE;
   this.CalculateChangedType();
   return this.GetChangeType();
  }
//+------------------------------------------------------------------+
//| Рассчитывает тип изменения параметров ордера                     |
//+------------------------------------------------------------------+
void COrderControl::CalculateChangedType(void)
  {
   this.m_changed_type=
     (
      //--- Если стоит флаг ордера
      this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_ORDER) ? 
        (
         //--- Если сработал StopLimit-ордер
         this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TYPE)    ?  CHANGE_TYPE_ORDER_TYPE :
         //--- Если модифицирована цена установки ордера
         this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_PRICE)   ? 
           (
            //--- Если вместе с ценой установки модифицированы StopLoss и TakeProfit
            this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT :
            //--- Если вместе с ценой установки модифицирован TakeProfit
            this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT : 
            //--- Если вместе с ценой установки модифицирован StopLoss
            this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS   :
            //--- Модифицирована только цена установки ордера
            CHANGE_TYPE_ORDER_PRICE
           ) : 
         //--- Цена установки не модифицирована
         //--- Если модифицированы StopLoss и TakeProfit
         this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT :
         //--- Если модифицирован TakeProfit
         this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_TAKE_PROFIT : 
         //--- Если модифицирован StopLoss
         this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS   :
         //--- Нет изменений
         CHANGE_TYPE_NO_CHANGE
        ) : 
      //--- Позиция
      //--- Если у позиции модифицированы StopLoss и TakeProfit
      this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT :
      //--- Если у позиции модифицирован TakeProfit
      this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_POSITION_TAKE_PROFIT : 
      //--- Если у позиции модифицирован StopLoss
      this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS   :
      //--- Нет изменений
      CHANGE_TYPE_NO_CHANGE
     );
  }
//+------------------------------------------------------------------+
//| Устанавливает новое текущее состояние                            |
//+------------------------------------------------------------------+
void COrderControl::SetNewState(COrder* order)
  {
   if(order==NULL || !::SymbolInfoTick(this.Symbol(),this.m_tick))
      return;
   //--- Новый тип
   this.SetTypeOrderPrev(this.TypeOrder());
   this.SetTypeOrder(order.TypeOrder());
   //--- Новая цена
   this.SetPricePrev(this.Price());
   this.SetPrice(order.PriceOpen());
   //--- Новый StopLoss
   this.SetStopLossPrev(this.StopLoss());
   this.SetStopLoss(order.StopLoss());
   //--- Новый TakeProfit
   this.SetTakeProfitPrev(this.TakeProfit());
   this.SetTakeProfit(order.TakeProfit());
   //--- Новое время
   this.SetTimePrev(this.Time());
   this.SetTime(this.m_tick.time_msc);
  }
//+------------------------------------------------------------------+


Займёмся доработкой класса-коллекции рыночных ордеров и позиций CMarketCollection.

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

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

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

//+------------------------------------------------------------------+
//|                                             MarketCollection.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Orders\MarketOrder.mqh"
#include "..\Objects\Orders\MarketPending.mqh"
#include "..\Objects\Orders\MarketPosition.mqh"
#include "OrderControl.mqh"
//+------------------------------------------------------------------+
//| Коллекция рыночных ордеров и позиций                             |
//+------------------------------------------------------------------+
class CMarketCollection : public CListObj
  {
private:
   struct MqlDataCollection
     {
      ulong          hash_sum_acc;           // Хэш-сумма всех ордеров и позиций на счёте
      int            total_market;           // Количество маркет-ордеров на счёте
      int            total_pending;          // Количество отложенных ордеров на счёте
      int            total_positions;        // Количество позиций на счёте
      double         total_volumes;          // Общий объём ордеров и позиций на счёте
     };
   MqlDataCollection m_struct_curr_market;   // Текущие данные рыночных ордеров и позиций на счёте
   MqlDataCollection m_struct_prev_market;   // Прошлые данные рыночных ордеров и позиций на счёте
   CListObj          m_list_all_orders;      // Список отложенных ордеров и позиций на счёте
   CArrayObj         m_list_control;         // Список контрольных ордеров
   CArrayObj         m_list_changed;         // Список изменённых ордеров
   COrder            m_order_instance;       // Объект-ордер для поиска по свойству
   ENUM_CHANGE_TYPE  m_change_type;          // Тип изменения ордера
   bool              m_is_trade_event;       // Флаг торгового события
   bool              m_is_change_volume;     // Флаг изменения общего объёма
   double            m_change_volume_value;  // Величина изменения общего объёма
   ulong             m_k_pow;                // Коэффициент для преобразования цены в число хэш-суммы
   int               m_new_market_orders;    // Количество новых маркет-ордеров
   int               m_new_positions;        // Количество новых позиций
   int               m_new_pendings;         // Количество новых отложенных ордеров
//--- Сохраняет текущие значения состояния данных счёта как прошлые
   void              SavePrevValues(void)                                                                { this.m_struct_prev_market=this.m_struct_curr_market;                  }
//--- Конвертирует данные ордера в число для хэш-суммы
   ulong             ConvertToHS(COrder* order) const;
//--- Добавляет ордер или позицию в список отложенных ордеров и позиций на счёте, устанавливает данные рыночных ордеров и позиций на счёте
   bool              AddToListMarket(COrder* order);
//--- (1) Создаёт и добавляет контрольный ордер в список контрольных ордеров, (2) контрольный ордер в список изменённых контрольных ордеров
   bool              AddToListControl(COrder* order);
   bool              AddToListChanges(COrderControl* order_control);
//--- Удаляет из списка контрольных ордеров ордер по тикету и идентификатору позиции
   bool              DeleteOrderFromListControl(const ulong ticket,const ulong id);
//--- Возвращает индекс контрольного ордера в списке по тикету и идентификатору позиции
   int               IndexControlOrder(const ulong ticket,const ulong id);
//--- Обработчик события изменения существующего ордера/позиции
   void              OnChangeEvent(COrder* order,const int index);
public:
//--- Возвращает список (1) всех отложенных ордеров и открытых позиций, (2) модифицированных ордеров и позиций
   CArrayObj*        GetList(void)                                                                       { return &this.m_list_all_orders;                                       }
   CArrayObj*        GetListChanges(void)                                                                { return &this.m_list_changed;                                          }
//--- Возвращает список ордеров и позиций со временем открытия в диапазоне от begin_time до end_time
   CArrayObj*        GetListByTime(const datetime begin_time=0,const datetime end_time=0);
//--- Возвращает список ордеров и позиций по выбранному (1) double, (2) integer и (3) string свойству, удовлетворяющему сравниваемому условию
   CArrayObj*        GetList(ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
   CArrayObj*        GetList(ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
   CArrayObj*        GetList(ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
//--- Возвращает количество (1) новых маркет-ордеров, (2) новых отложенных ордеров, (3) новых позиций, (4) флаг произошедшего торгового события (5) величину изменённого объёма
   int               NewMarketOrders(void)                                                         const { return this.m_new_market_orders;                                      }
   int               NewPendingOrders(void)                                                        const { return this.m_new_pendings;                                           }
   int               NewPositions(void)                                                            const { return this.m_new_positions;                                          }
   bool              IsTradeEvent(void)                                                            const { return this.m_is_trade_event;                                         }
   double            ChangedVolumeValue(void)                                                      const { return this.m_change_volume_value;                                    }
//--- Конструктор
                     CMarketCollection(void);
//--- Обновляет список отложенных ордеров и позиций
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

В конструкторе класса впишем очистку и сортировку двух списков — списка контрольных ордеров и списка изменённых ордеров, а так же расчёт коэффициента для расчёта хэш-суммы:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CMarketCollection::CMarketCollection(void) : m_is_trade_event(false),m_is_change_volume(false),m_change_volume_value(0)
  {
   this.m_list_all_orders.Sort(SORT_BY_ORDER_TIME_OPEN);
   this.m_list_all_orders.Clear();
   ::ZeroMemory(this.m_struct_prev_market);
   this.m_struct_prev_market.hash_sum_acc=WRONG_VALUE;
   this.m_list_all_orders.Type(COLLECTION_MARKET_ID);
   this.m_list_control.Clear();  
   this.m_list_control.Sort();   
   this.m_list_changed.Clear();  
   this.m_list_changed.Sort();   
   this.m_k_pow=(ulong)pow(10,6);
  }
//+------------------------------------------------------------------+

Метод конвертации свойств ордера в число для расчёта хэш-суммы:

//+------------------------------------------------------------------+
//| Преобразовывает цены ордера и его тип в число для хэш-суммы      |
//+------------------------------------------------------------------+
ulong CMarketCollection::ConvertToHS(COrder *order) const
  {
   if(order==NULL)
      return 0;
   ulong price=ulong(order.PriceOpen()*this.m_k_pow);
   ulong stop=ulong(order.StopLoss()*this.m_k_pow);
   ulong take=ulong(order.TakeProfit()*this.m_k_pow);
   ulong type=order.TypeOrder();
   ulong ticket=order.Ticket();
   return price+stop+take+type+ticket;
  }
//+------------------------------------------------------------------+

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

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

Изменения коснулись добавления объектов в список ордеров и позиций — теперь эти однотипные строки расположены в одном методе AddToListMarket(). После добавления объекта-ордера в список ордеров и позиций, проверяется наличие такого же ордера в списке контрольных ордеров, и если его там нету, то создаётся объект-контрольный ордер и добавляется в список контрольных ордеров при помощи метода AddToListControl(). Если же проверка наличия контрольного ордера показала его существование, то вызывается метод сравнения свойств текущего ордера со свойствами контрольного OnChangeEvent().

В листинге метода в комментариях к строкам описаны все производимые действия, и помечены в тексте выделением.

//+------------------------------------------------------------------+
//| Обновляет список ордеров                                         |
//+------------------------------------------------------------------+
void CMarketCollection::Refresh(void)
  {
   ::ZeroMemory(this.m_struct_curr_market);
   this.m_is_trade_event=false;
   this.m_is_change_volume=false;
   this.m_new_pendings=0;
   this.m_new_positions=0;
   this.m_change_volume_value=0;
   this.m_list_all_orders.Clear();
#ifdef __MQL4__
   int total=::OrdersTotal();
   for(int i=0; i<total; i++)
     {
      if(!::OrderSelect(i,SELECT_BY_POS)) continue;
      long ticket=::OrderTicket();
      //--- Получаем индекс контрольного ордера по тикету и идентификатору позиции
      int index=this.IndexControlOrder(ticket);
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderType();
      if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL)
        {
         CMarketPosition *position=new CMarketPosition(ticket);
         if(position==NULL) continue;
         //--- Добавляем объект-позицию в список рыночных ордеров и позиций
         if(!this.AddToListMarket(position))
            continue;
         //--- Если ордера нет в списке контрольных ордеров и позиций - добавляем
         if(index==WRONG_VALUE)
           {
            if(!this.AddToListControl(order))
              {
               ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольный ордер ","Failed to add a control order "),order.TypeDescription()," #",order.Ticket());
              }
           }
         //--- Если ордер уже есть в списке контрольных ордеров - проверяем его на предмет изменения свойств
         if(index>WRONG_VALUE)
           {
            this.OnChangeEvent(position,index);
           }
        }
      else
        {
         CMarketPending *order=new CMarketPending(ticket);
         if(order==NULL) continue;
         //--- Добавляем объект-отложенный ордер в список рыночных ордеров и позиций
         if(!this.AddToListMarket(order))
            continue;
         //--- Если ордера нет в списке контрольных ордеров и позиций - добавляем
         if(index==WRONG_VALUE)
           {
            if(!this.AddToListControl(order))
              {
               ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольный ордер ","Failed to add a control order "),order.TypeDescription()," #",order.Ticket());
              }
           }
         //--- Если ордер уже есть в списке контрольных ордеров - проверяем его на предмет изменения свойств
         if(index>WRONG_VALUE)
           {
            this.OnChangeEvent(order,index);
           }
        }
     }
//--- MQ5
#else 
//--- Позиции
   int total_positions=::PositionsTotal();
   for(int i=0; i<total_positions; i++)
     {
      ulong ticket=::PositionGetTicket(i);
      if(ticket==0) continue;
      CMarketPosition *position=new CMarketPosition(ticket);
      if(position==NULL) continue;
      //--- Добавляем объект-позицию в список рыночных ордеров и позиций
      if(!this.AddToListMarket(position))
         continue;
      //--- Получаем индекс контрольного ордера по тикету и идентификатору позиции
      int index=this.IndexControlOrder(ticket,position.PositionID());
      //--- Если ордера нет в списке контрольных ордеров - добавляем
      if(index==WRONG_VALUE)
        {
         if(!this.AddToListControl(position))
           {
            ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольую позицию ","Failed to add a control position "),position.TypeDescription()," #",position.Ticket());
           }
        }
      //--- Если ордер уже есть в списке контрольных ордеров - проверяем его на предмет изменения свойств
      else if(index>WRONG_VALUE)
        {
         this.OnChangeEvent(position,index);
        }
     }
//--- Ордера
   int total_orders=::OrdersTotal();
   for(int i=0; i<total_orders; i++)
     {
      ulong ticket=::OrderGetTicket(i);
      if(ticket==0) continue;
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderGetInteger(ORDER_TYPE);
      //--- Маркет-ордер
      if(type<ORDER_TYPE_BUY_LIMIT)
        {
         CMarketOrder *order=new CMarketOrder(ticket);
         if(order==NULL) continue;
         //--- Добавляем объект-маркет-ордер в список рыночных ордеров и позиций
         if(!this.AddToListMarket(order))
            continue;
        }
      //--- Отложенный ордер
      else
        {
         CMarketPending *order=new CMarketPending(ticket);
         if(order==NULL) continue;
         //--- Добавляем объект-отложенный ордер в список рыночных ордеров и позиций
         if(!this.AddToListMarket(order))
            continue;
         //--- Получаем индекс контрольного ордера по тикету и идентификатору позиции
         int index=this.IndexControlOrder(ticket,order.PositionID());
         //--- Если ордера нет в списке контрольных ордеров - добавляем
         if(index==WRONG_VALUE)
           {
            if(!this.AddToListControl(order))
              {
               ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольный ордер ","Failed to add a control order "),order.TypeDescription()," #",order.Ticket());
              }
           }
         //--- Если ордер уже есть в списке контрольных ордеров - проверяем его на предмет изменения свойств
         else if(index>WRONG_VALUE)
           {
            this.OnChangeEvent(order,index);
           }
        }
     }
#endif 
//--- Первый запуск
   if(this.m_struct_prev_market.hash_sum_acc==WRONG_VALUE)
     {
      this.SavePrevValues();
     }
//--- Если хэш-сумма всех ордеров и позиций изменилась
   if(this.m_struct_curr_market.hash_sum_acc!=this.m_struct_prev_market.hash_sum_acc)
     {
      this.m_new_market_orders=this.m_struct_curr_market.total_market-this.m_struct_prev_market.total_market;
      this.m_new_pendings=this.m_struct_curr_market.total_pending-this.m_struct_prev_market.total_pending;
      this.m_new_positions=this.m_struct_curr_market.total_positions-this.m_struct_prev_market.total_positions;
      this.m_change_volume_value=::NormalizeDouble(this.m_struct_curr_market.total_volumes-this.m_struct_prev_market.total_volumes,4);
      this.m_is_change_volume=(this.m_change_volume_value!=0 ? true : false);
      this.m_is_trade_event=true;
      this.SavePrevValues();
     }
  }
//+------------------------------------------------------------------+

Метод, добавляющий ордер или позицию в список рыночных ордеров и позиций коллекции:

//+------------------------------------------------------------------+
//| Добавляет ордер или позицию в список ордеров и позиций на счёте  |
//+------------------------------------------------------------------+
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_MSC)+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.TimeOpenMSC());         
   order_control.SetTimePrev(order.TimeOpenMSC());     
   order_control.SetVolume(order.Volume());            
   order_control.SetTime(order.TimeOpenMSC());         
   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;
  }
//+------------------------------------------------------------------+

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

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

Отмечу, что метод пока только объявлен, но не реализован. Реализацию сделаем после того, как полностью будет готов весь функционал отслеживания модификации ордеров и позиций.

Метод, возвращающий индекс контрольного ордера в списке контрольных ордеров по тикету и идентификатору позиции:

//+------------------------------------------------------------------+
//| Возвращает индекс ордера по тикету в списке контрольных ордеров  |
//+------------------------------------------------------------------+
int CMarketCollection::IndexControlOrder(const ulong ticket,const ulong id)
  {
   int total=this.m_list_control.Total();
   for(int i=0;i<total;i++)
     {
      COrderControl* order=this.m_list_control.At(i);
      if(order==NULL)
         continue;
      if(order.PositionID()==id && order.Ticket()==ticket)
         return i;
     }
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+

В метод передаётся тикет ордера/позиции и идентификатор позиции. В цикле по всем контрольным ордерам списка ищется контрольный ордер с равными тикетом и идентификатором, и возвращается его индекс в списке контрольных ореров. Если ордер не найден, возвращается -1.

Метод-обработчик события изменения существующего ордера/позиции:

//+------------------------------------------------------------------+
//| Обработчик события изменения существующего ордера/позиции        |
//+------------------------------------------------------------------+
void CMarketCollection::OnChangeEvent(COrder* order,const int index)
  {
   COrderControl* order_control=this.m_list_control.At(index);
   if(order_control!=NULL)
     {
      this.m_change_type=order_control.ChangeControl(order);
      ENUM_CHANGE_TYPE change_type=(order.Status()==ORDER_STATUS_MARKET_POSITION ? CHANGE_TYPE_ORDER_TAKE_PROFIT : CHANGE_TYPE_NO_CHANGE);
      if(this.m_change_type>change_type)
        {
         order_control.SetNewState(order);
         if(!this.AddToListChanges(order_control))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить модифицированный ордер в список изменённых ордеров","Could not add modified order to the list of modified orders"));
           }
        }
     }
  }
//+------------------------------------------------------------------+

В метод передаются указатель на проверяемый ордер и индекс соответствующего ему контрольного ордера в списке контрольных ордеров.
Получаем контрольный ордер из списка по его индексу и проверяем наличие изменений свойств контрольного ордера, соответствующих свойствам проверяемого при помощи метода ChangeControl() контрольного ордера. В метод передаётся указатель на контрольный ордер, и при наличии разницы метод возвращает тип изменения, который записывается в переменную-член класса m_change_type.
Далее проверяем статус проверяемого ордера и задаём значение, выше которого будет считаться, что изменение произошло. Для позиции — это значение должно быть больше значения константы CHANGE_TYPE_ORDER_TAKE_PROFIT из перечисления ENUM_CHANGE_TYPE так как все значения, которые равны и меньше значения этой константы, относятся только к отложенному ордеру. Для отложенного ордера значение должно быть больше значения константы CHANGE_TYPE_NO_CHANGE.
Если полученное значение переменной m_change_type больше заданного — значит есть факт модификации, и необходимо сначала сохранить текущее состояние контрольного ордера для последующей проверки, и разместить копию контрольного ордера в список изменённых ордеров для последующей обработки этого списка в классе CEventsCollection.

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

//+------------------------------------------------------------------+
//|Создаёт и добавляет контрольный ордер в список изменённых ордеров |
//+------------------------------------------------------------------+
bool CMarketCollection::AddToListChanges(COrderControl* order_control)
  {
   if(order_control==NULL)
      return false;
   COrderControl* order_changed=new COrderControl(order_control.PositionID(),order_control.Ticket(),order_control.Magic(),order_control.Symbol());
   if(order_changed==NULL)
      return false;
   order_changed.SetTime(order_control.Time());                    
   order_changed.SetTimePrev(order_control.TimePrev());            
   order_changed.SetVolume(order_control.Volume());                
   order_changed.SetTypeOrder(order_control.TypeOrder());          
   order_changed.SetTypeOrderPrev(order_control.TypeOrderPrev());  
   order_changed.SetPrice(order_control.Price());                  
   order_changed.SetPricePrev(order_control.PricePrev());          
   order_changed.SetStopLoss(order_control.StopLoss());            
   order_changed.SetStopLossPrev(order_control.StopLossPrev());    
   order_changed.SetTakeProfit(order_control.TakeProfit());        
   order_changed.SetTakeProfitPrev(order_control.TakeProfitPrev());
   order_changed.SetChangedType(order_control.GetChangeType());    
   if(!this.m_list_changed.Add(order_changed))
     {
      delete order_changed;
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

В метод передаётся указатель на контрольный ордер, который был модифицирован, и копию которого необходимо разместить в списке изменённых контрольных ордеров и позиций.
Затем создаётся новый контрольный ордер — при создании ему сразу же присваиваются идентификатор позиции, тикет, магик и символ, такие же, как и у изменённого контрольного ордера.
Далее — простое поэлементное копирование свойств изменённого контрольного ордера в свойства вновь созданного.
И наконец — размещаем вновь созданную копию изменённого контрольного ордера в список изменённых ордеров.
Если не удалось разместить вновь созданный ордер в список, то вновь созданный объект-ордер удаляется и возвращается false.

На этом необходимые изменения класса CMarketCollection завершены, очередь за классом CEventsCollection.

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

В приватную секцию класса к уже существующему методу добавим определение двух методов: нового перегруженного метода создания нового события и метода-обработчика события изменения существующего ордера/позиции, а в метод Refresh() в его параметры добавим передачу в метод списка изменённых ордеров:

//+------------------------------------------------------------------+
//|                                             EventsCollection.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Orders\Order.mqh"
#include "..\Objects\Events\EventBalanceOperation.mqh"
#include "..\Objects\Events\EventOrderPlaced.mqh"
#include "..\Objects\Events\EventOrderRemoved.mqh"
#include "..\Objects\Events\EventPositionOpen.mqh"
#include "..\Objects\Events\EventPositionClose.mqh"
//+------------------------------------------------------------------+
//| Коллекция событий счёта                                          |
//+------------------------------------------------------------------+
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;                // Объект-событие для поиска по свойству
   
//--- Создаёт торговое событие (1) в зависимости от статуса и (2) типа изменения ордера
   void              CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market);
   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);
//--- Выбирает из списка и возвращает список рыночных отложенных ордеров
   CArrayObj*        GetListMarketPendings(CArrayObj* list);
//--- Выбирает из списка и возвращает список исторических (1) удалённых отложенных ордеров, (2) сделок, (3) всех закрывающих ордеров 
   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);
//--- Возвращает флаг наличия объекта-события в списке событий
   bool              IsPresentEventInList(CEvent* compared_event);
//--- Обработчик события изменения существующего ордера/позиции
   void              OnChangeEvent(CArrayObj* list_changes,CArrayObj* list_history,CArrayObj* list_market,const int index);
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,
                             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);
//--- Устанавливает идентификатор графика управляющей программы
   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);
  };
//+------------------------------------------------------------------+

За пределами тела класса напишем реализацию новых методов.

Перегруженный метод создания события модификации ордера или позиции:

//+------------------------------------------------------------------+
//| Создаёт торговое событие в зависимости от типа изменения ордера  |
//+------------------------------------------------------------------+
void CEventsCollection::CreateNewEvent(COrderControl* order)
  {
   CEvent* event=NULL;
//--- Установлен отложенный StopLimit-ордер
   if(order.GetChangeType()==CHANGE_TYPE_ORDER_TYPE)
     {
      this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_PLASED;
      event=new CEventOrderPlased(this.m_trade_event_code,order.Ticket());
     }
//--- 
   if(event!=NULL)
     {
      event.SetProperty(EVENT_PROP_TIME_EVENT,order.Time());                        // Время события
      event.SetProperty(EVENT_PROP_REASON_EVENT,EVENT_REASON_STOPLIMIT_TRIGGERED);  // Причина события (из перечисления ENUM_EVENT_REASON)
      event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrderPrev());          // Тип ордера, срабатывание которого привело к событию
      event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());               // Тикет ордера , срабатывание которого привело к событию
      event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder());             // Тип ордера события
      event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket());              // Тикет ордера события
      event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder());          // Тип первого ордера позиции
      event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());           // Тикет первого ордера позиции
      event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());                 // Идентификатор позиции
      event.SetProperty(EVENT_PROP_POSITION_BY_ID,0);                               // Идентификатор встречной позиции
      event.SetProperty(EVENT_PROP_MAGIC_BY_ID,0);                                  // Магический номер встречной позиции
         
      event.SetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE,order.TypeOrderPrev());      // Тип ордера позиции до смены направления
      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_MAGIC_ORDER,order.Magic());                      // Магический номер ордера
      event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimePrev());           // Время первого ордера позиции
      event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PricePrev());                  // Цена, на которой произошло событие
      event.SetProperty(EVENT_PROP_PRICE_OPEN,order.Price());                       // Цена установки ордера
      event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.Price());                      // Цена закрытия ордера
      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,0);                        // Исполненный объём ордера
      event.SetProperty(EVENT_PROP_VOLUME_ORDER_CURRENT,order.Volume());            // Оставшийся (неисполненный) объём ордера
      event.SetProperty(EVENT_PROP_VOLUME_POSITION_EXECUTED,0);                     // Исполненный объём позиции
      event.SetProperty(EVENT_PROP_PROFIT,0);                                       // Профит
      event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                          // Символ ордера
      event.SetProperty(EVENT_PROP_SYMBOL_BY_ID,order.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;
        }
     }
  }
//+------------------------------------------------------------------+

Метод создания нового события мы рассматривали в пятой части описания библиотеки при создании коллекции событий.
Данный метод устроен практически идентично. Разница лишь в типе ордера, указатель на который передаётся в метод.
В самом начале метода проверяется тип произошедшего с ордером изменения и в соответствии с типом изменения устанавливается код изменения в переменную-член класса m_trade_event_code.
Далее создаётся соответствующее типу изменения событие, заполняются его свойства в соответствии с типом изменения, событие помещается в список событий и отправляется в управляющую программу.

Доработанный метод обновления списка событий:

//+------------------------------------------------------------------+
//| Обновляет список событий                                         |
//+------------------------------------------------------------------+
void CEventsCollection::Refresh(CArrayObj* list_history,
                                CArrayObj* list_market,
                                CArrayObj* list_changes,
                                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)
  {
//--- Если списки пустые - выход
   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);
           }                                   
        }                                      
      //--- если увеличилось количество установленных отложенных ордеров
      if(new_market_pendings>0)
        {
         //--- Получаем список только установленных отложенных ордеров
         CArrayObj* list=this.GetListMarketPendings(list_market);
         if(list!=NULL)
           {
            //--- Сортируем новый список по времени установки ордера
            list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
            //--- Берём в цикле с конца списка количество ордеров, равное количеству новых установленных ордеров (последние 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);
              }
           }
        }
     }
//--- Если событие в истории счёта
   if(is_history_event)
     {
      //--- Если увеличилось количество исторических ордеров
      if(new_history_orders>0)
        {
         //--- Получаем список только удалённых отложенных ордеров
         CArrayObj* list=this.GetListHistoryPendings(list_history);
         if(list!=NULL)
           {
            //--- Сортируем новый список по времени удаления ордера
            list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC);
            //--- Берём в цикле с конца списка количество ордеров, равное количеству новых удалённых отложенных ордеров (последние 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);
              }
           }
        }
      //--- Если увеличилось количество сделок
      if(new_deals>0)
        {
         //--- Получаем список только сделок
         CArrayObj* list=this.GetListDeals(list_history);
         if(list!=NULL)
           {
            //--- Сортируем новый список по времени совершения сделки
            list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
            //--- Берём в цикле с конца списка количество сделок, равное количеству новых сделок (последние 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);
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

 Этот метод так же был рассмотрен нами в пятой части описания библиотеки при создании коллекции событий. Отличием от рассмотренного метода является добавление блока кода обработки событий модификации в случае, если размер списка изменённых ордеров ненулевой: в цикле берём из списка каждый изменённый ордер и обрабатываем его в Методе-обработчике события изменения ордера:

//+------------------------------------------------------------------+
//| Обработчик события изменения существующего ордера/позиции        |
//+------------------------------------------------------------------+
void CEventsCollection::OnChangeEvent(CArrayObj* list_changes,const int index)
  {
   COrderControl* order_changed=list_changes.Detach(index);
   if(order_changed!=NULL)
     {
      if(order_changed.GetChangeType()==CHANGE_TYPE_ORDER_TYPE)
        {
         this.CreateNewEvent(order_changed);
        }
      delete order_changed;
     }
  }
//+------------------------------------------------------------------+

При обработке списка изменённых ордеров нам необходимо не только получать из списка модифицированный ордер, но и по завершению обработки очередного ордера удалять как сам объект-ордер, так и указатель на него из списка — чтобы по многу раз не обрабатывать одно и то же событие.
К счастью, в Стандартной библиотеке при работе с динамическим массивом указателей на объекты CArrayObj уже предусмотрена такая возможность — метод Detach(), который получает элемент из указанной позиции с удалением его из массива. Т.е., мы одновременно получаем указатель на объёкт, хранящийся в массиве по индексу, и удаляем этот указатель из массива. Если тип изменения равен CHANGE_TYPE_ORDER_TYPE (изменение типа ордера — срабатывание отложенного StopLimit-ордера и превращению его в Limit-ордер), создаём новое событие — срабатывание StopLimit-ордера. По завершению обработки объекта по указателю, полученному при помощи метода Detach(), сам уазатель (уже не нужный нам) просто удаляем.

На этом доработка класса CEventsCollection завершена.

Чтобы все изменения вступили в силу, необходимо в главном объекте библиотеки — в классе CEngine, в его методе TradeEventsControl() — получать список изменённых ордеров из класса-коллекции рыночных ордеров и позиций, записывать его размер и при вызове метода обновления событий Refresh() класса-коллекции событий, проверять дополнительно размер списка изменённых ордеров, а в метод Refresh() коллекции событий передавать список модифицированных ордеров для его обработки:

//+------------------------------------------------------------------+
//| Проверка торговых событий                                        |
//+------------------------------------------------------------------+
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_is_history_trade_event,this.m_is_market_trade_event,
                            this.m_history.NewOrders(),this.m_market.NewPendingOrders(),
                            this.m_market.NewMarketOrders(),this.m_history.NewDeals());
      //--- Получаем последнее торговое событие на счёте
      this.m_acc_trade_event=this.m_events.GetLastTradeEvent();
     }
  }
//+------------------------------------------------------------------+

Так как срабатывание StopLimit-ордера приводит к выставлению Limit-ордера, то это событие будем "квалифицировать" как выставление отложенного ордера, а причиной события будет активация StopLimit-ордера EVENT_REASON_STOPLIMIT_TRIGGERED, константу которой мы уже прописали в файле Defines.mqh в перечислении ENUM_EVENT_REASON.

Для вывода в журнал и отправки в управляющую программу данного события, доработаем класс EventOrderPlased:

Просто добавим обработку причины события EVENT_REASON_STOPLIMIT_TRIGGERED.

//+------------------------------------------------------------------+
//|                                             EventOrderPlased.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "Event.mqh"
//+------------------------------------------------------------------+
//| Событие установки отложенного ордера                             |
//+------------------------------------------------------------------+
class CEventOrderPlased : public CEvent
  {
public:
//--- Конструктор
                     CEventOrderPlased(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_MARKET_PENDING,event_code,ticket) {}
//--- Поддерживаемые свойства ордера (1) вещественные, (2) целочисленные
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
//--- (1) Выводит в журнал краткое сообщение о событии, (2) Отправляет событие на график
   virtual void      PrintShort(void);
   virtual void      SendEvent(void);
  };
//+------------------------------------------------------------------+
//| Возвращает истину, если событие поддерживает переданное          |
//| целочисленное свойство, возвращает ложь в противном случае       |
//+------------------------------------------------------------------+
bool CEventOrderPlased::SupportProperty(ENUM_EVENT_PROP_INTEGER property)
  {
   if(property==EVENT_PROP_TYPE_DEAL_EVENT         ||
      property==EVENT_PROP_TICKET_DEAL_EVENT       ||
      property==EVENT_PROP_TYPE_ORDER_POSITION     ||
      property==EVENT_PROP_TICKET_ORDER_POSITION   ||
      property==EVENT_PROP_POSITION_ID             ||
      property==EVENT_PROP_POSITION_BY_ID          ||
      property==EVENT_PROP_TIME_ORDER_POSITION
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Возвращает истину, если событие поддерживает переданное          |
//| вещественное свойство, возвращает ложь в противном случае        |
//+------------------------------------------------------------------+
bool CEventOrderPlased::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
  {
   if(property==EVENT_PROP_PRICE_CLOSE             ||
      property==EVENT_PROP_PROFIT
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Выводит в журнал краткое сообщение о событии                     |
//+------------------------------------------------------------------+
void CEventOrderPlased::PrintShort(void)
  {
   int    digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS);
   string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   string sl=(this.PriceStopLoss()>0 ? ", sl "+::DoubleToString(this.PriceStopLoss(),digits) : "");
   string tp=(this.PriceTakeProfit()>0 ? ", tp "+::DoubleToString(this.PriceTakeProfit(),digits) : "");
   string vol=::DoubleToString(this.VolumeOrderInitial(),DigitsLots(this.Symbol()));
   string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : "");
   string type=this.TypeOrderFirstDescription()+" #"+(string)this.TicketOrderEvent();
   string event=TextByLanguage(" Установлен "," Placed ");
   string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceOpen(),digits);
   string txt=head+this.Symbol()+event+vol+" "+type+price+sl+tp+magic;
   //--- Если сработал StopLimit-ордер
   if(this.Reason()==EVENT_REASON_STOPLIMIT_TRIGGERED)
     {
      head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimeEvent())+" -\n";
      event=TextByLanguage(" Сработал "," Triggered ");
      type=
        (
         OrderTypeDescription(this.TypeOrderPosPrevious())+" #"+(string)this.TicketOrderEvent()+
         TextByLanguage(" по цене "," at price ")+DoubleToString(this.PriceEvent(),digits)+" -->\n"+
         vol+" "+OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderEvent()+
         TextByLanguage(" на цену "," on price ")+DoubleToString(this.PriceOpen(),digits)
        );
      txt=head+this.Symbol()+event+"("+TimeMSCtoString(this.TimePosition())+") "+vol+" "+type+sl+tp+magic;
     }
   ::Print(txt);
  }
//+------------------------------------------------------------------+
//| Отправляет событие на график                                     |
//+------------------------------------------------------------------+
void CEventOrderPlased::SendEvent(void)
  {
   this.PrintShort();
   ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.TicketOrderEvent(),this.PriceOpen(),this.Symbol());
  }
//+------------------------------------------------------------------+

Тут всё достаточно просто и понятно — не будем останавляваться на описании простых действий.

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

Тест

Для проверки проведённых доработок, воспользуемя советником из прошлой статьи. Просто переименуем советник TestDoEasyPart06.mq5 из папки \MQL5\Experts\TestDoEasy\Part06 в TestDoEasyPart07.mq5 и сохраним его в новой подпапке \MQL5\Experts\TestDoEasy\Part07.

Скомпилируем, запустим в тестере, выставим StopLimit-ордер и дождёмся его срабатывания:



Что дальше

Реализованный в данной статье функционал, как несложно догадаться, включает в себя возможность быстрого добавления отслеживания иных событий — модификацию свойств отложенных ордеров — их цены установки и уровней StopLoss и TakeProfit, и модификацию уровней StopLoss и TakeProfit у позиций. Что и будем делать в следующей статье.

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

К содержанию

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

Часть 1. Концепция, организация данных.
Часть 2. Коллекция исторических ордеров и сделок.
Часть 3. Коллекция рыночных ордеров и позиций, организация поиска.
Часть 4. Торговые события. Концепция.
Часть 5. Классы и коллекция торговых событий. Отправка событий в программу.
Часть 6. События на счёте с типом неттинг.


Прикрепленные файлы |
MQL5.zip (91.97 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (11)
Реter Konow
Реter Konow | 1 мая 2019 в 13:04
Artyom Trishkin:

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

Это значит, что библиотека станет штатной?
Artyom Trishkin
Artyom Trishkin | 1 мая 2019 в 14:07
Реter Konow:
Это значит, что библиотека станет штатной?
Нет. Это означает, что одной библиотеки будет вполне достаточно.
Реter Konow
Реter Konow | 1 мая 2019 в 14:29
Artyom Trishkin:
Нет. Это означает, что одной библиотеки будет вполне достаточно.
Удачи.
BmC
BmC | 27 апр. 2020 в 12:43

Артем, спасибо!

В методе:

//+------------------------------------------------------------------+
//| Обновляет список ордеров                                         |
//+------------------------------------------------------------------+
void CMarketCollection::Refresh(void)

Создается динамически "маркет-ордер"

#else 
//--- Позиции
   int total_positions=::PositionsTotal();
   for(int i=0; i<total_positions; i++)
     {
      ulong ticket=::PositionGetTicket(i);
      if(ticket==0) continue;
      CMarketPosition *position=new CMarketPosition(ticket);
      if(position==NULL) continue;
      //--- Добавляем объект-позицию в список рыночных ордеров и позиций
      if(!this.AddToListMarket(position))
         continue;
      //--- Получаем индекс контрольного ордера по тикету и идентификатору позиции
      int index=this.IndexControlOrder(ticket,position.PositionID());
      //--- Если ордера нет в списке контрольных ордеров - добавляем
      if(index==WRONG_VALUE)
        {
         if(!this.AddToListControl(position))
           {
            ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольую позицию ","Failed to add a control position "),position.TypeDescription()," #",position.Ticket());
           }
        }
      //--- Если ордер уже есть в списке контрольных ордеров - проверяем его на предмет изменения свойств
      else if(index>WRONG_VALUE)
        {
         this.OnChangeEvent(position,index);
        }
     }
//--- Ордера
   int total_orders=::OrdersTotal();
   for(int i=0; i<total_orders; i++)
     {
      ulong ticket=::OrderGetTicket(i);
      if(ticket==0) continue;
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderGetInteger(ORDER_TYPE);
      //--- Маркет-ордер
      if(type<ORDER_TYPE_BUY_LIMIT)
        {
         CMarketOrder *order=new CMarketOrder(ticket);
         if(order==NULL) continue;
         //--- Добавляем объект-маркет-ордер в список рыночных ордеров и позиций
         if(!this.AddToListMarket(order))
            continue;
        }
      //--- Отложенный ордер
      else
        {
         CMarketPending *order=new CMarketPending(ticket);
         if(order==NULL) continue;
         //--- Добавляем объект-отложенный ордер в список рыночных ордеров и позиций
         if(!this.AddToListMarket(order))
            continue;
         //--- Получаем индекс контрольного ордера по тикету и идентификатору позиции
         int index=this.IndexControlOrder(ticket,order.PositionID());
         //--- Если ордера нет в списке контрольных ордеров - добавляем
         if(index==WRONG_VALUE)
           {
            if(!this.AddToListControl(order))
              {
               ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольный ордер ","Failed to add a control order "),order.TypeDescription()," #",order.Ticket());
              }
           }
         //--- Если ордер уже есть в списке контрольных ордеров - проверяем его на предмет изменения свойств
         else if(index>WRONG_VALUE)
           {
            this.OnChangeEvent(order,index);
           }
        }
     }
#endif 

Потом по ссылке передается в метод :

         //--- Добавляем объект-маркет-ордер в список рыночных ордеров и позиций
         if(!this.AddToListMarket(order))
            continue;

В методе "AddToListMarket" маркет-ордер не учитывается в хеш_сумме "hash_sum" тогда зачем его заносить и контролировать?
Объясните пожалуйста зачем он нам, если мы всю информацию можем узнать у позиции или отложенного-ордера?

Artyom Trishkin
Artyom Trishkin | 27 апр. 2020 в 15:25
BmC:

Артем, спасибо!

В методе:

Создается динамически "маркет-ордер"

Потом по ссылке передается в метод :

В методе "AddToListMarket" маркет-ордер не учитывается в хеш_сумме "hash_sum" тогда зачем его заносить и контролировать?
Объясните пожалуйста зачем он нам, если мы всю информацию можем узнать у позиции или отложенного-ордера?

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

Применение OLAP в трейдинге (Часть 1): Основы оперативного анализа многомерных данных Применение OLAP в трейдинге (Часть 1): Основы оперативного анализа многомерных данных
В статье описываются общие принципы построения фреймворка для оперативного анализа многомерных данных (OLAP), его реализация на MQL и применение в среде MetaTrader на примере обработки торговой истории счета.
ZUP - зигзаг универсальный с паттернами Песавенто: Графический интерфейс. Дополнения и изменения. Вилы Эндрюса в ZUP ZUP - зигзаг универсальный с паттернами Песавенто: Графический интерфейс. Дополнения и изменения. Вилы Эндрюса в ZUP
В версии 153 редактирование почти всех параметров ZUP можно осуществлять через графический интерфейс. В статье дано описание последних изменений в графическом интерфейсе ZUP. Описаны также основные элементы вил Эндрюса в ZUP для использования этого инструмента при анализе рыночной ситуации.
Исследование методов свечного анализа (Часть IV): Обновление и дополнение приложения Исследование методов свечного анализа (Часть IV): Обновление и дополнение приложения
В этой статье представлена следующая версия приложения Pattern Analyzer. В нем были исправлены некоторые недоработки, добавлены новые возможности, пересмотрено удобство и актуальность текущего интерфейса. При этом были рассмотрены пожелания и идеи из комментариев предыдущих статей. Что в итоге получилось — читайте далее в этой статье.
Библиотека для простого и быстрого создания программ для MetaTrader (Часть VI): События на счёте с типом неттинг Библиотека для простого и быстрого создания программ для MetaTrader (Часть VI): События на счёте с типом неттинг
В предыдущих статьях мы начали создавать большую кроссплатформенную библиотеку, целью которой является упростить написания программ для платформы MetaTrader 5 и MetaTrader 4. В пятой части мы создали классы торговых событий и коллекцию событий, откуда события отправляются в базовый объект библиотеки Engine и на график управляющей программы. В данной части повествования добавим возможность работы библиотеки на счетах с типом неттинг.