Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte VIII): Eventos de modificación de órdenes y posiciones

Artyom Trishkin | 7 agosto, 2019

Contenido

En el artículo anterior, implementamos el seguimiento de eventos de activación de órdenes StopLimit. Como el lector podrá adivinar, hemos hecho ampliable la funcionalidad utilizada para definir estos eventos, para que, usándola como base, podamos escribir con facilidad la búsqueda del resto de eventos que necesitamos.

Hemos "calificado" el evento de activación de una orden StopLimit como la colocación de una nueva orden pendiente, lo que en principio resulta lógico, ya que un nuevo tipo de orden requiere de un nuevo evento de colocación. En este artículo, vamos a monitorear eventos de un tipo totalmente distinto: la modificación de órdenes y posiciones ya existentes, sobre cuya colocación y apertura ya hemos recibido eventos en el programa. Esto significa que debemos crear otra clase heredera de la clase de evento abstracto CEvent: la clase de evento de modificación.

Clase de evento de modificación

Vamos a comenzar como siempre, preparando las constantes de enumeración necesarias.
Abrimos el archivo de la biblioteca Defines.mqh, y en las propiedades de tipo entero de la orden, en la macrosustitución que contiene el número de propiedades de tipo entero de la orden no utilizadas al realizar la clasificación según estas propiedades, escribimos el número de propiedades omitidas igual a cero: vamos a necesitar todas las propiedades de tipo entero de la orden. Anteriormente, al realizar la búsqueda y la clasificación, se omitía una propiedad de la lista de constantes de enumeración: ORDER_PROP_DIRECTION, el tipo de orden según su dirección (recordemos que colocamos todas las propiedades no usadas al final de la lista de constantes de enumeración). En el presente artículo y en lo sucesivo, vamos a necesitar esta propiedad para buscar todas las órdenes pendientes en la misma dirección en la lista de la colección de mercado.

//+------------------------------------------------------------------+
//| Propiedades de tipo entero de la orden, transacción, posición                   |
//+------------------------------------------------------------------+
enum ENUM_ORDER_PROP_INTEGER
  {
   ORDER_PROP_TICKET = 0,                                   // Ticket de la orden
   ORDER_PROP_MAGIC,                                        // Мэджик ордера
   ORDER_PROP_TIME_OPEN,                                    // Время открытия (MQL5 Время сделки)
   ORDER_PROP_TIME_CLOSE,                                   // Время закрытия (MQL5 Время исполнения или снятия - ORDER_TIME_DONE)
   ORDER_PROP_TIME_OPEN_MSC,                                // Время открытия в милисекундах (MQL5 Время сделки в мсек.)
   ORDER_PROP_TIME_CLOSE_MSC,                               // Время закрытия в милисекундах (MQL5 Время исполнения или снятия - ORDER_TIME_DONE_MSC)
   ORDER_PROP_TIME_EXP,                                     // Fecha de expiración de la orden (para las órdenes pendientes)
   ORDER_PROP_STATUS,                                       // Estado de la orden (de la enumeración ENUM_ORDER_STATUS)
   ORDER_PROP_TYPE,                                         // Тип ордера/сделки
   ORDER_PROP_REASON,                                       // Motivo o razón de la transacción/orden/posición
   ORDER_PROP_STATE,                                        // Состояние ордера (из перечисления ENUM_ORDER_STATE)
   ORDER_PROP_POSITION_ID,                                  // Indicador de la posición
   ORDER_PROP_POSITION_BY_ID,                               // Indicador de la posición opuesta
   ORDER_PROP_DEAL_ORDER_TICKET,                            // Тикет ордера, на основании которого выполнена сделка
   ORDER_PROP_DEAL_ENTRY,                                   // Dirección de la transacción – IN, OUT o IN/OUT
   ORDER_PROP_TIME_UPDATE,                                  // Hora de cambio de la posición en el segundos
   ORDER_PROP_TIME_UPDATE_MSC,                              // Hora de cambio de la posición en milisegundos
   ORDER_PROP_TICKET_FROM,                                  // Ticket de la orden padre
   ORDER_PROP_TICKET_TO,                                    // Ticket de la orden derivada
   ORDER_PROP_PROFIT_PT,                                    // Beneficio en puntos
   ORDER_PROP_CLOSE_BY_SL,                                  // Señal de cierre por StopLoss
   ORDER_PROP_CLOSE_BY_TP,                                  // Señal de cierre por TakeProfit
   ORDER_PROP_GROUP_ID,                                     // Идентификатор группы ордеров/позиций
   ORDER_PROP_DIRECTION,                                    // Tipo según la dirección (Buy, Sell)
  }; 
#define ORDER_PROP_INTEGER_TOTAL    (24)                    // Общее количество целочисленных свойств
#define ORDER_PROP_INTEGER_SKIP     (0)                     // Количество неиспользуемых в сортировке свойств ордера
//+------------------------------------------------------------------+

Ya que hemos eliminado la omisión de propiedades, debemos añadir también la posibilidad de clasificar según esta propiedad, así que escribimos el criterio de clasificación según la dirección de la orden en la enumeración de los posibles criterios de clasificación de las órdenes y posiciones:

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

Para crear el identificador de evento, vamos a usar, como el lector recordará, un código de evento que consta de un grupo de banderas que en su conjunto indican precisamente el tipo de evento sucedido. Puesto que vamos a monitorear los eventos de modificación, necesitaremos añadir las banderas necesarias a la enumeración de las banderas de evento comercial:

//+------------------------------------------------------------------+
//| Список флагов торговых событий на счёте                          |
//+------------------------------------------------------------------+
enum ENUM_TRADE_EVENT_FLAGS
  {
   TRADE_EVENT_FLAG_NO_EVENT        =  0,                   // Нет события
   TRADE_EVENT_FLAG_ORDER_PLASED    =  1,                   // Отложенный ордер установлен
   TRADE_EVENT_FLAG_ORDER_REMOVED   =  2,                   // Отложенный ордер удалён
   TRADE_EVENT_FLAG_ORDER_ACTIVATED =  4,                   // Отложенный ордер активирован ценой
   TRADE_EVENT_FLAG_POSITION_OPENED =  8,                   // Позиция открыта
   TRADE_EVENT_FLAG_POSITION_CHANGED=  16,                  // Позиция изменена
   TRADE_EVENT_FLAG_POSITION_REVERSE=  32,                  // Разворот позиции
   TRADE_EVENT_FLAG_POSITION_CLOSED =  64,                  // Позиция закрыта
   TRADE_EVENT_FLAG_ACCOUNT_BALANCE =  128,                 // Балансная операция (уточнение в типе сделки)
   TRADE_EVENT_FLAG_PARTIAL         =  256,                 // Частичное исполнение
   TRADE_EVENT_FLAG_BY_POS          =  512,                 // Исполнение встречной позицией
   TRADE_EVENT_FLAG_PRICE           =  1024               // Модификация цены установки
   TRADE_EVENT_FLAG_SL              =  2048,                // Исполнение по StopLoss
   TRADE_EVENT_FLAG_TP              =  4096,                // Исполнение по TakeProfit
   TRADE_EVENT_FLAG_ORDER_MODIFY    =  8192               // Модификация ордера
   TRADE_EVENT_FLAG_POSITION_MODIFY =  16384,               // Модификация позиции
  };
//+------------------------------------------------------------------+

Añadimos a la lista de posibles eventos comerciales en la cuenta el evento de modificación de órdenes y posiciones (ya hemos añadido anteriormente algunos eventos a la lista, pero se trataba de la declaración preliminar de variables):

//+------------------------------------------------------------------+
//| Список возможных торговых событий на счёте                       |
//+------------------------------------------------------------------+
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_ORDER_STOP_LOSS,                      // Изменение StopLoss ордера
   TRADE_EVENT_MODIFY_ORDER_TAKE_PROFIT,                    // Изменение TakeProfit ордера
   TRADE_EVENT_MODIFY_POSITION_STOP_LOSS_TAKE_PROFIT,       // Изменение StopLoss и TakeProfit позиции
   TRADE_EVENT_MODIFY_POSITION_STOP_LOSS,                   // Изменение StopLoss позиции
   TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT,                 // Изменение TakeProfit позиции
  };
//+------------------------------------------------------------------+

Dado que hoy vamos a hacer una nueva clase heredera de la clase de evento abstracto CEvent, necesitaremos establecer otro estado de evento adicional, la "modificiación", para la nueva clase heredera. Lo añadimos a la lista de estados de los eventos:

//+------------------------------------------------------------------+
//| Статус события                                                   |
//+------------------------------------------------------------------+
enum ENUM_EVENT_STATUS
  {
   EVENT_STATUS_MARKET_POSITION,                            // Событие рыночной позиции (открытие, частичное открытие, частичное закрытие, добавление объёма, разворот)
   EVENT_STATUS_MARKET_PENDING,                             // Событие рыночного отложенного ордера (установка)
   EVENT_STATUS_HISTORY_PENDING,                            // Событие исторического отложенного ордера (удаление)
   EVENT_STATUS_HISTORY_POSITION,                           // Событие исторической позиции (закрытие)
   EVENT_STATUS_BALANCE,                                    // Событие балансной операции (начисление баланса, снятие средств и события из перечисления ENUM_DEAL_TYPE)
   EVENT_STATUS_MODIFY                                      // Событие модификации ордера/позиции
  };
//+------------------------------------------------------------------+

Y completamos la lista que enumera los motivos de los eventos con el motivo de evento "modificación":

//+------------------------------------------------------------------+
//| Причина события                                                  |
//+------------------------------------------------------------------+
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_MODIFY,                                     // Модификация
   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)
//+------------------------------------------------------------------+

Para poder saber en cualquier momento qué ha cambiado en las propiedades de una orden o posición, añadimos a las propiedades de tipo real del evento los precios de las propiedades de la orden o posición hasta la modificación (los precios después de la modificación se tomarán de las propiedades ya existentes), las propiedades para registrar los precios acutales en el momento del evento, y también modificamos los valores del número de propiedades de tipo real del evento de 10 a 15 y añadimos el número de propiedades no utilizadas al realizar la búsqueda y la clasificación (no vamos a usar los datos sobre la modificación y los precios en el momento del evento de modificación a la hora de realizar la búsqueda y clasificación):

//+------------------------------------------------------------------+
//| Вещественные свойства события                                    |
//+------------------------------------------------------------------+
enum ENUM_EVENT_PROP_DOUBLE
  {
   EVENT_PROP_PRICE_EVENT = EVENT_PROP_INTEGER_TOTAL,       // Цена, на которой произошло событие
   EVENT_PROP_PRICE_OPEN,                                   // Цена открытия ордера/сделки/позиции
   EVENT_PROP_PRICE_CLOSE,                                  // Цена закрытия ордера/сделки/позиции
   EVENT_PROP_PRICE_SL,                                     // Цена StopLoss ордера/сделки/позиции
   EVENT_PROP_PRICE_TP,                                     // Цена TakeProfit ордера/сделки/позиции
   EVENT_PROP_VOLUME_ORDER_INITIAL,                         // Запрашиваемый объём ордера
   EVENT_PROP_VOLUME_ORDER_EXECUTED,                        // Исполненный объём ордера
   EVENT_PROP_VOLUME_ORDER_CURRENT,                         // Оставшийся объём ордера
   EVENT_PROP_VOLUME_POSITION_EXECUTED,                     // Текущий исполненный объём позиции после сделки
   EVENT_PROP_PROFIT,                                       // Профит
   //---
   EVENT_PROP_PRICE_OPEN_BEFORE,                            // Цена установки ордера до модификации
   EVENT_PROP_PRICE_SL_BEFORE,                              // Цена StopLoss до модификации
   EVENT_PROP_PRICE_TP_BEFORE,                              // Цена TakeProfit до модификации
   EVENT_PROP_PRICE_EVENT_ASK,                              // Цена Ask в момент события
   EVENT_PROP_PRICE_EVENT_BID,                              // Цена Bid в момент события
  };
#define EVENT_PROP_DOUBLE_TOTAL  (15)                       // Общее количество вещественных свойств события
#define EVENT_PROP_DOUBLE_SKIP   (5                       // Количество неиспользуемых в сортировке свойств события
//+------------------------------------------------------------------+

Para calcular exactamente el índice de la primera propiedad de tipo string del evento en la enumeración de los criterios de clasificación de eventos, modificamos el cálculo del valor de la macrosustitución correspondiente:

//+------------------------------------------------------------------+
//| Возможные критерии сортировки событий                            |
//+------------------------------------------------------------------+
#define FIRST_EVN_DBL_PROP       (EVENT_PROP_INTEGER_TOTAL-EVENT_PROP_INTEGER_SKIP)
#define FIRST_EVN_STR_PROP       (EVENT_PROP_INTEGER_TOTAL-EVENT_PROP_INTEGER_SKIP+EVENT_PROP_DOUBLE_TOTAL-EVENT_PROP_DOUBLE_SKIP)
enum ENUM_SORT_EVENTS_MODE
  {
   //--- Сортировка по целочисленным свойствам
   SORT_BY_EVENT_TYPE_EVENT               = 0,                       // Сортировать по типу события
   SORT_BY_EVENT_TIME_EVENT               = 1,                       // Сортировать по времени события
   SORT_BY_EVENT_STATUS_EVENT             = 2,                       // Сортировать по статусу события (из перечисления ENUM_EVENT_STATUS)
   SORT_BY_EVENT_REASON_EVENT             = 3,                       // Сортировать по причине события (из перечисления ENUM_EVENT_REASON)
   SORT_BY_EVENT_TYPE_DEAL_EVENT          = 4,                       // Сортировать по типу сделки события
   SORT_BY_EVENT_TICKET_DEAL_EVENT        = 5,                       // Сортировать по тикету сделки события
   SORT_BY_EVENT_TYPE_ORDER_EVENT         = 6,                       // Сортировать по типу ордера, на основании которого открыта сделка события (последний ордер позиции)
   SORT_BY_EVENT_TICKET_ORDER_EVENT       = 7,                       // Сортировать по тикету ордера, на основании которого открыта сделка события (последний ордер позиции)
   SORT_BY_EVENT_TIME_ORDER_POSITION      = 8,                       // Сортировать по времени ордера, на основании которого открыта сделка позиции (первый ордер позиции)
   SORT_BY_EVENT_TYPE_ORDER_POSITION      = 9,                       // Сортировать по типу ордера, на основании которого открыта сделка позиции (первый ордер позиции)
   SORT_BY_EVENT_TICKET_ORDER_POSITION    = 10,                      // Сортировать по тикету ордера, на основании которого открыта сделка позиции (первый ордер позиции)
   SORT_BY_EVENT_POSITION_ID              = 11,                      // Сортировать по идентификатору позиции
   SORT_BY_EVENT_POSITION_BY_ID           = 12,                      // Сортировать по идентификатору встречной позиции
   SORT_BY_EVENT_MAGIC_ORDER              = 13,                      // Сортировать по магическому номеру ордера/сделки/позиции
   SORT_BY_EVENT_MAGIC_BY_ID              = 14,                      // Сортировать по магическому номеру встречной позиции
   //--- Сортировка по вещественным свойствам
   SORT_BY_EVENT_PRICE_EVENT              =  FIRST_EVN_DBL_PROP,     // Сортировать по цене, на которой произошло событие
   SORT_BY_EVENT_PRICE_OPEN               =  FIRST_EVN_DBL_PROP+1,   // Сортировать по цене открытия позиции
   SORT_BY_EVENT_PRICE_CLOSE              =  FIRST_EVN_DBL_PROP+2,   // Сортировать по цене закрытия позиции
   SORT_BY_EVENT_PRICE_SL                 =  FIRST_EVN_DBL_PROP+3,   // Сортировать по цене StopLoss позиции
   SORT_BY_EVENT_PRICE_TP                 =  FIRST_EVN_DBL_PROP+4,   // Сортировать по цене TakeProfit позиции
   SORT_BY_EVENT_VOLUME_ORDER_INITIAL     =  FIRST_EVN_DBL_PROP+5,   // Сортировать по первоначальному объёму
   SORT_BY_EVENT_VOLUME_ORDER_EXECUTED    =  FIRST_EVN_DBL_PROP+6,   // Сортировать по текущему объёму
   SORT_BY_EVENT_VOLUME_ORDER_CURRENT     =  FIRST_EVN_DBL_PROP+7,   // Сортировать по оставшемуся объёму
   SORT_BY_EVENT_VOLUME_POSITION_EXECUTED =  FIRST_EVN_DBL_PROP+8,   // Сортировать по оставшемуся объёму
   SORT_BY_EVENT_PROFIT                   =  FIRST_EVN_DBL_PROP+9,   // Сортировать по профиту
   //--- Сортировка по строковым свойствам
   SORT_BY_EVENT_SYMBOL                   =  FIRST_EVN_STR_PROP,     // Сортировать по символу ордера/позици/сделки
   SORT_BY_EVENT_SYMBOL_BY_ID                                        // Сортировать по символу встречной позици
  };
//+------------------------------------------------------------------+

Vamos a mejorar la clase de evento abstracto CEvent.

Dado que en las clases herederas del evento abstracto CEvent mostramos la información en el diario, vamos a necesitar conocer el número de dígitos decimales en la clasificación del símbolo en el que ha sucedido el evento, el Digits() del símbolo. Para no obtenerlo cada vez en cada uno de los herederos, lo obtendremos una sola vez en la clase padre.

Declaramos en la sección privada de la clase la variable de miembro de clase para guardar el valor Digits() del símbolo del evento, e inicializamos esta variable en el constructor de la clase, en su lista de inicialización:

//+------------------------------------------------------------------+
//| Класс абстрактного события                                       |
//+------------------------------------------------------------------+
class CEvent : public CObject
  {
private:
   int               m_event_code;                                   // Код события
//--- Возвращает индекс массива, по которому фактически расположено (1) double-свойство и (2) string-свойство события
   int               IndexProp(ENUM_EVENT_PROP_DOUBLE property)const { return(int)property-EVENT_PROP_INTEGER_TOTAL;                         }
   int               IndexProp(ENUM_EVENT_PROP_STRING property)const { return(int)property-EVENT_PROP_INTEGER_TOTAL-EVENT_PROP_DOUBLE_TOTAL; }
protected:
   ENUM_TRADE_EVENT  m_trade_event;                                  // Торговое событие
   bool              m_is_hedge;                                     // Флаг хедж-счёта
   long              m_chart_id;                                     // Идентификатор графика управляющей программы
   int               m_digits;                                       // Digits() символа
   int               m_digits_acc;                                   // Количество знаков после запятой для валюты счета
   long              m_long_prop[EVENT_PROP_INTEGER_TOTAL];          // Целочисленные свойства события
   double            m_double_prop[EVENT_PROP_DOUBLE_TOTAL];         // Вещественные свойства события
   string            m_string_prop[EVENT_PROP_STRING_TOTAL];         // Строковые свойства события
//--- возвращает факт наличия флага в торговом событии
   bool              IsPresentEventFlag(const int event_code)  const { return (this.m_event_code & event_code)==event_code;            }

//--- Constructor paramétrico protegido
                     CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket);
public:
//--- Constructor por defecto
                     CEvent(void){;}

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CEvent::CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket) : m_event_code(event_code),m_digits(0)
  {
   this.m_long_prop[EVENT_PROP_STATUS_EVENT]       =  event_status;
   this.m_long_prop[EVENT_PROP_TICKET_ORDER_EVENT] =  (long)ticket;
   this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
   this.m_digits_acc=(int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS);
   this.m_chart_id=::ChartID();
  }
//+------------------------------------------------------------------+

Añadimos a los métodos que retornan las descripciones de las propiedades de tipo entero y real las descripciones de las nuevas propiedades del evento:

//+------------------------------------------------------------------+
//| Возвращает описание целочисленного свойства события              |
//+------------------------------------------------------------------+
string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_INTEGER property)
  {
   return
     (
      property==EVENT_PROP_TYPE_EVENT              ?  TextByLanguage("Тип события","Event's type")+": "+this.TypeEventDescription()                                                       :
      property==EVENT_PROP_TIME_EVENT              ?  TextByLanguage("Время события","Time of event")+": "+TimeMSCtoString(this.GetProperty(property))                                    :
      property==EVENT_PROP_STATUS_EVENT            ?  TextByLanguage("Статус события","Status of event")+": \""+this.StatusDescription()+"\""                                             :
      property==EVENT_PROP_REASON_EVENT            ?  TextByLanguage("Причина события","Reason of event")+": "+this.ReasonDescription()                                                   :
      property==EVENT_PROP_TYPE_DEAL_EVENT         ?  TextByLanguage("Тип сделки","Deal's type")+": "+DealTypeDescription((ENUM_DEAL_TYPE)this.GetProperty(property))                     :
      property==EVENT_PROP_TICKET_DEAL_EVENT       ?  TextByLanguage("Тикет сделки","Deal's ticket")+": #"+(string)this.GetProperty(property)                                              :
      property==EVENT_PROP_TYPE_ORDER_EVENT        ?  TextByLanguage("Тип ордера события","Event's order type")+": "+OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(property))    :
      property==EVENT_PROP_TYPE_ORDER_POSITION     ?  TextByLanguage("Тип ордера позиции","Position's order type")+": "+OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(property)) :
      property==EVENT_PROP_TICKET_ORDER_POSITION   ?  TextByLanguage("Тикет первого ордера позиции","Position's first order ticket")+": #"+(string)this.GetProperty(property)              :
      property==EVENT_PROP_TICKET_ORDER_EVENT      ?  TextByLanguage("Тикет ордера события","Event's order ticket")+": #"+(string)this.GetProperty(property)                               :
      property==EVENT_PROP_POSITION_ID             ?  TextByLanguage("Идентификатор позиции","Position ID")+": #"+(string)this.GetProperty(property)                                       :
      property==EVENT_PROP_POSITION_BY_ID          ?  TextByLanguage("Идентификатор встречной позиции","Opposite position's ID")+": #"+(string)this.GetProperty(property)                  :
      property==EVENT_PROP_MAGIC_ORDER             ?  TextByLanguage("Магический номер","Magic number")+": "+(string)this.GetProperty(property)                                           :
      property==EVENT_PROP_MAGIC_BY_ID             ?  TextByLanguage("Магический номер встречной позиции","Magic number of opposite position")+": "+(string)this.GetProperty(property)    :
      property==EVENT_PROP_TIME_ORDER_POSITION     ?  TextByLanguage("Время открытия позиции","Position's opened time")+": "+TimeMSCtoString(this.GetProperty(property))                  :
      property==EVENT_PROP_TYPE_ORD_POS_BEFORE     ?  TextByLanguage("Тип ордера позиции до смены направления","Type order of position before changing direction")+": "+OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(property)) :
      property==EVENT_PROP_TICKET_ORD_POS_BEFORE   ?  TextByLanguage("Тикет ордера позиции до смены направления","Ticket order of position before changing direction")+": #"+(string)this.GetProperty(property)                            :
      property==EVENT_PROP_TYPE_ORD_POS_CURRENT    ?  TextByLanguage("Тип ордера текущей позиции","Type order of current position")+": "+OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(property))                                :
      property==EVENT_PROP_TICKET_ORD_POS_CURRENT  ?  TextByLanguage("Тикет ордера текущей позиции","Ticket order of current position")+": #"+(string)this.GetProperty(property)                                                           :
      EnumToString(property)
     );
  }
//+------------------------------------------------------------------+
//| Возвращает описание вещественного свойства события               |
//+------------------------------------------------------------------+
string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_DOUBLE property)
  {
   int dg=(int)::SymbolInfoInteger(this.GetProperty(EVENT_PROP_SYMBOL),SYMBOL_DIGITS);
   int dgl=(int)DigitsLots(this.GetProperty(EVENT_PROP_SYMBOL));
   return
     (
      property==EVENT_PROP_PRICE_EVENT             ?  TextByLanguage("Цена на момент события","Price at the time of the event")+": "+::DoubleToString(this.GetProperty(property),dg)               :
      property==EVENT_PROP_PRICE_OPEN              ?  TextByLanguage("Цена открытия","Price open")+": "+::DoubleToString(this.GetProperty(property),dg)                                            :
      property==EVENT_PROP_PRICE_CLOSE             ?  TextByLanguage("Цена закрытия","Price close")+": "+::DoubleToString(this.GetProperty(property),dg)                                           :
      property==EVENT_PROP_PRICE_SL                ?  TextByLanguage("Цена StopLoss","Price StopLoss")+": "+::DoubleToString(this.GetProperty(property),dg)                                        :
      property==EVENT_PROP_PRICE_TP                ?  TextByLanguage("Цена TakeProfit","Price TakeProfit")+": "+::DoubleToString(this.GetProperty(property),dg)                                    :
      property==EVENT_PROP_VOLUME_ORDER_INITIAL    ?  TextByLanguage("Начальный объём ордера","Order initial volume")+": "+::DoubleToString(this.GetProperty(property),dgl)                        :
      property==EVENT_PROP_VOLUME_ORDER_EXECUTED   ?  TextByLanguage("Исполненный объём ордера","Order executed volume")+": "+::DoubleToString(this.GetProperty(property),dgl)                     :
      property==EVENT_PROP_VOLUME_ORDER_CURRENT    ?  TextByLanguage("Оставшийся объём ордера","Order remaining volume")+": "+::DoubleToString(this.GetProperty(property),dgl)                     :
      property==EVENT_PROP_VOLUME_POSITION_EXECUTED ? TextByLanguage("Текущий объём позиции","Position current volume")+": "+::DoubleToString(this.GetProperty(property),dgl)                      :
      property==EVENT_PROP_PROFIT                  ?  TextByLanguage("Профит","Profit")+": "+::DoubleToString(this.GetProperty(property),this.m_digits_acc)                                        :
      property==EVENT_PROP_PRICE_OPEN_BEFORE       ?  TextByLanguage("Цена открытия до модификации","Price open before modification")+": "+::DoubleToString(this.GetProperty(property),dg)         :
      property==EVENT_PROP_PRICE_SL_BEFORE         ?  TextByLanguage("Цена StopLoss до модификации","Price StopLoss before modification")+": "+::DoubleToString(this.GetProperty(property),dg)     :
      property==EVENT_PROP_PRICE_TP_BEFORE         ?  TextByLanguage("Цена TakeProfit до модификации","Price TakeProfit before modification")+": "+::DoubleToString(this.GetProperty(property),dg) :
      property==EVENT_PROP_PRICE_EVENT_ASK         ?  TextByLanguage("Цена Ask в момент события","Price Ask at the time of the event")+": "+::DoubleToString(this.GetProperty(property),dg)        :
      property==EVENT_PROP_PRICE_EVENT_BID         ?  TextByLanguage("Цена Bid в момент события","Price Bid at the time of the event")+": "+::DoubleToString(this.GetProperty(property),dg)        :
      EnumToString(property)
     );
  }
//+------------------------------------------------------------------+

Añadimos al método que retorna la denominación de los eventos comerciales la descripción de la ausencia de evento, las descripciones de los eventos nuevamente añadidos y la descripción del evento desconocido:

//+------------------------------------------------------------------+
//| Возвращает наименование торгового события                        |
//+------------------------------------------------------------------+
string CEvent::TypeEventDescription(void) const
  {
   ENUM_TRADE_EVENT event=this.TypeEvent();
   return
     (
      event==TRADE_EVENT_NO_EVENT                              ?  TextByLanguage("Нет торгового события","No trade event")                                              :
      event==TRADE_EVENT_PENDING_ORDER_PLASED                  ?  TextByLanguage("Отложенный ордер установлен","Pending order placed")                                  :
      event==TRADE_EVENT_PENDING_ORDER_REMOVED                 ?  TextByLanguage("Отложенный ордер удалён","Pending order removed")                                     :
      event==TRADE_EVENT_ACCOUNT_CREDIT                        ?  TextByLanguage("Начисление кредита","Credit")                                                         :
      event==TRADE_EVENT_ACCOUNT_CHARGE                        ?  TextByLanguage("Дополнительные сборы","Additional charge")                                            :
      event==TRADE_EVENT_ACCOUNT_CORRECTION                    ?  TextByLanguage("Корректирующая запись","Correction")                                                  :
      event==TRADE_EVENT_ACCOUNT_BONUS                         ?  TextByLanguage("Перечисление бонусов","Bonus")                                                        :
      event==TRADE_EVENT_ACCOUNT_COMISSION                     ?  TextByLanguage("Дополнительные комиссии","Additional commission")                                     :
      event==TRADE_EVENT_ACCOUNT_COMISSION_DAILY               ?  TextByLanguage("Комиссия, начисляемая в конце торгового дня","Daily commission")                      :
      event==TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY             ?  TextByLanguage("Комиссия, начисляемая в конце месяца","Monthly commission")                           :
      event==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY         ?  TextByLanguage("Агентская комиссия, начисляемая в конце торгового дня","Daily agent commission")      :
      event==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY       ?  TextByLanguage("Агентская комиссия, начисляемая в конце месяца","Monthly agent commission")           :
      event==TRADE_EVENT_ACCOUNT_INTEREST                      ?  TextByLanguage("Начисления процентов на свободные средства","Interest rate")                          :
      event==TRADE_EVENT_BUY_CANCELLED                         ?  TextByLanguage("Отмененная сделка покупки","Canceled buy deal")                                       :
      event==TRADE_EVENT_SELL_CANCELLED                        ?  TextByLanguage("Отмененная сделка продажи","Canceled sell deal")                                      :
      event==TRADE_EVENT_DIVIDENT                              ?  TextByLanguage("Начисление дивиденда","Dividend operations")                                          :
      event==TRADE_EVENT_DIVIDENT_FRANKED                      ?  TextByLanguage("Начисление франкированного дивиденда","Franked (non-taxable) dividend operations")    :
      event==TRADE_EVENT_TAX                                   ?  TextByLanguage("Начисление налога","Tax charges")                                                     :
      event==TRADE_EVENT_ACCOUNT_BALANCE_REFILL                ?  TextByLanguage("Пополнение средств на балансе","Balance refill")                                      :
      event==TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL            ?  TextByLanguage("Снятие средств с баланса","Withdrawals")                                              :
      event==TRADE_EVENT_PENDING_ORDER_ACTIVATED               ?  TextByLanguage("Отложенный ордер активирован ценой","Pending order activated")                        :
      event==TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL       ?  TextByLanguage("Отложенный ордер активирован ценой частично","Pending order activated partially")     :
      event==TRADE_EVENT_POSITION_OPENED                       ?  TextByLanguage("Позиция открыта","Position is open")                                                  :
      event==TRADE_EVENT_POSITION_OPENED_PARTIAL               ?  TextByLanguage("Позиция открыта частично","Position is open partially")                               :
      event==TRADE_EVENT_POSITION_CLOSED                       ?  TextByLanguage("Позиция закрыта","Position closed")                                                   :
      event==TRADE_EVENT_POSITION_CLOSED_PARTIAL               ?  TextByLanguage("Позиция закрыта частично","Position closed partially")                                :
      event==TRADE_EVENT_POSITION_CLOSED_BY_POS                ?  TextByLanguage("Позиция закрыта встречной","Position closed by opposite position")                    :
      event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS        ?  TextByLanguage("Позиция закрыта встречной частично","Position closed partially by opposite position") :
      event==TRADE_EVENT_POSITION_CLOSED_BY_SL                 ?  TextByLanguage("Позиция закрыта по StopLoss","Position closed by StopLoss")                           :
      event==TRADE_EVENT_POSITION_CLOSED_BY_TP                 ?  TextByLanguage("Позиция закрыта по TakeProfit","Position closed by TakeProfit")                       :
      event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL         ?  TextByLanguage("Позиция закрыта частично по StopLoss","Position closed partially by StopLoss")        :
      event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP         ?  TextByLanguage("Позиция закрыта частично по TakeProfit","Position closed partially by TakeProfit")    :
      event==TRADE_EVENT_POSITION_REVERSED_BY_MARKET           ?  TextByLanguage("Разворот позиции по рыночному запросу","Position reversal by market request")         :
      event==TRADE_EVENT_POSITION_REVERSED_BY_PENDING          ?  TextByLanguage("Разворот позиции срабатыванием отложенного ордера","Position reversal by triggered a pending order")                            :
      event==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET         ?  TextByLanguage("Добавлен объём к позиции по рыночному запросу","Added volume to position by market request")                                    :
      event==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING        ?  TextByLanguage("Добавлен объём к позиции активацией отложенного ордера","Added volume to the position by activation of a pending order")        :
      
      event==TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL   ?  TextByLanguage("Разворот позиции частичным исполнением запроса","Position reversal by partially completed of market request")                   :
      event==TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL  ?  TextByLanguage("Разворот позиции частичным срабатыванием отложенного ордера","Position reversal by partially triggered a pending order")        :
      event==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL ?  TextByLanguage("Добавлен объём к позиции частичным исполнением запроса","Added volume to position by partially completed of market request")    :
      event==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL ? TextByLanguage("Добавлен объём к позиции активацией отложенного ордера","Added volume to the position by partially triggered a pending order")  :

      event==TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER            ?  TextByLanguage("Сработал StopLimit-ордер","StopLimit order triggered.")                                                                         :
      event==TRADE_EVENT_MODIFY_ORDER_PRICE                    ?  TextByLanguage("Модифицирована цена установки ордера ","Modified order price")                                                                  :
      event==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS          ?  TextByLanguage("Модифицированы цена установки и StopLoss ордера","Modified of order price and StopLoss")                                        :
      event==TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT        ?  TextByLanguage("Модифицированы цена установки и TakeProfit ордера","Modified of order price and TakeProfit")                                    :
      event==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT ?  TextByLanguage("Модифицированы цена установки, StopLoss и TakeProfit ордера","Modified of order price, StopLoss and TakeProfit")             :
      event==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT    ?  TextByLanguage("Модифицированы цены StopLoss и TakeProfit ордера","Modified of order StopLoss and TakeProfit")                                  :
      event==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS                ?  TextByLanguage("Модифицирован StopLoss ордера","Modified order StopLoss")                                                                       :
      event==TRADE_EVENT_MODIFY_ORDER_TAKE_PROFIT              ?  TextByLanguage("Модифицирован TakeProfit ордера","Modified order TakeProfit")                                                                   :
      event==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS_TAKE_PROFIT ?  TextByLanguage("Модифицированы цены StopLoss и TakeProfit позиции","Modified of position StopLoss and TakeProfit")                              :
      event==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS             ?  TextByLanguage("Модифицирован StopLoss позиции","Modified position StopLoss")                                                                   :
      event==TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT           ?  TextByLanguage("Модифицирован TakeProfit позиции","Modified position TakeProfit")                                                               :
      EnumToString(event)
     );   
  }
//+------------------------------------------------------------------+

Añadimos al método que retorna la descripción del motivo del evento dos nuevos motivos:

//+------------------------------------------------------------------+
//| Возвращает наименование причины сделки/ордера/позиции            |
//+------------------------------------------------------------------+
string CEvent::ReasonDescription(void) const
  {
   ENUM_EVENT_REASON reason=this.Reason();
   return 
     (
      reason==EVENT_REASON_ACTIVATED_PENDING                ?  TextByLanguage("Активирован отложенный ордер","Pending order activated")                           :
      reason==EVENT_REASON_ACTIVATED_PENDING_PARTIALLY      ?  TextByLanguage("Частичное срабатывание отложенного ордера","Pending order partially triggered")    :
      reason==EVENT_REASON_STOPLIMIT_TRIGGERED              ?  TextByLanguage("Срабатывание StopLimit-ордера","StopLimit-order triggered")                        :
      reason==EVENT_REASON_MODIFY                           ?  TextByLanguage("Модификация","Modified")                                                           :
      reason==EVENT_REASON_CANCEL                           ?  TextByLanguage("Отмена","Canceled")                                                                :
      reason==EVENT_REASON_EXPIRED                          ?  TextByLanguage("Истёк срок действия","Expired")                                                    :
      reason==EVENT_REASON_DONE                             ?  TextByLanguage("Рыночный запрос, выполненный в полном объёме","Fully completed market request")    :
      reason==EVENT_REASON_DONE_PARTIALLY                   ?  TextByLanguage("Выполненный частично рыночный запрос","Partially completed market request")        :
      reason==EVENT_REASON_VOLUME_ADD                       ?  TextByLanguage("Добавлен объём к позиции","Added volume to position")                              :
      reason==EVENT_REASON_VOLUME_ADD_PARTIALLY             ?  TextByLanguage("Добавлен объём к позиции частичным исполнением заявки","Volume added to the position by partially completed request")                 :
      reason==EVENT_REASON_VOLUME_ADD_BY_PENDING            ?  TextByLanguage("Добавлен объём к позиции активацией отложенного ордера","Added volume to position by pending order's triggered")                      :
      reason==EVENT_REASON_VOLUME_ADD_BY_PENDING_PARTIALLY  ?  TextByLanguage("Добавлен объём к позиции частичной активацией отложенного ордера","Added volume to position by pending order's triggered partially")  :
      reason==EVENT_REASON_REVERSE                          ?  TextByLanguage("Разворот позиции","Position reversal")  :
      reason==EVENT_REASON_REVERSE_PARTIALLY                ?  TextByLanguage("Разворот позиции частичным исполнением заявки","Position reversal by partially completed of the request")                             :
      reason==EVENT_REASON_REVERSE_BY_PENDING               ?  TextByLanguage("Разворот позиции при срабатывании отложенного ордера","Position reversal on a triggered pending order")                               :
      reason==EVENT_REASON_REVERSE_BY_PENDING_PARTIALLY     ?  TextByLanguage("Разворот позиции при при частичном срабатывании отложенного ордера","Position reversal on a partially triggered pending order")       :
      reason==EVENT_REASON_DONE_SL                          ?  TextByLanguage("Закрытие по StopLoss","Close by StopLoss triggered")                               :
      reason==EVENT_REASON_DONE_SL_PARTIALLY                ?  TextByLanguage("Частичное закрытие по StopLoss","Partially close by StopLoss triggered")           :
      reason==EVENT_REASON_DONE_TP                          ?  TextByLanguage("Закрытие по TakeProfit","Close by TakeProfit triggered")                           :
      reason==EVENT_REASON_DONE_TP_PARTIALLY                ?  TextByLanguage("Частичное закрытие по TakeProfit","Partially close by TakeProfit triggered")       :
      reason==EVENT_REASON_DONE_BY_POS                      ?  TextByLanguage("Закрытие встречной позицией","Closed by opposite position")                        :
      reason==EVENT_REASON_DONE_PARTIALLY_BY_POS            ?  TextByLanguage("Частичное закрытие встречной позицией","Closed partially by opposite position")    :
      reason==EVENT_REASON_DONE_BY_POS_PARTIALLY            ?  TextByLanguage("Закрытие частью объёма встречной позиции","Closed by incomplete volume of opposite position") :
      reason==EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY  ?  TextByLanguage("Частичное закрытие частью объёма встречной позиции","Closed partially by incomplete volume of opposite position")  :
      reason==EVENT_REASON_BALANCE_REFILL                   ?  TextByLanguage("Пополнение баланса","Balance refill")                                              :
      reason==EVENT_REASON_BALANCE_WITHDRAWAL               ?  TextByLanguage("Снятие средств с баланса","Withdrawals from the balance")                          :
      reason==EVENT_REASON_ACCOUNT_CREDIT                   ?  TextByLanguage("Начисление кредита","Credit")                                                      :
      reason==EVENT_REASON_ACCOUNT_CHARGE                   ?  TextByLanguage("Дополнительные сборы","Additional charge")                                         :
      reason==EVENT_REASON_ACCOUNT_CORRECTION               ?  TextByLanguage("Корректирующая запись","Correction")                                               :
      reason==EVENT_REASON_ACCOUNT_BONUS                    ?  TextByLanguage("Перечисление бонусов","Bonus")                                                     :
      reason==EVENT_REASON_ACCOUNT_COMISSION                ?  TextByLanguage("Дополнительные комиссии","Additional commission")                                  :
      reason==EVENT_REASON_ACCOUNT_COMISSION_DAILY          ?  TextByLanguage("Комиссия, начисляемая в конце торгового дня","Daily commission")                   :
      reason==EVENT_REASON_ACCOUNT_COMISSION_MONTHLY        ?  TextByLanguage("Комиссия, начисляемая в конце месяца","Monthly commission")                        :
      reason==EVENT_REASON_ACCOUNT_COMISSION_AGENT_DAILY    ?  TextByLanguage("Агентская комиссия, начисляемая в конце торгового дня","Daily agent commission")   :
      reason==EVENT_REASON_ACCOUNT_COMISSION_AGENT_MONTHLY  ?  TextByLanguage("Агентская комиссия, начисляемая в конце месяца","Monthly agent commission")        :
      reason==EVENT_REASON_ACCOUNT_INTEREST                 ?  TextByLanguage("Начисления процентов на свободные средства","Interest rate")                       :
      reason==EVENT_REASON_BUY_CANCELLED                    ?  TextByLanguage("Отмененная сделка покупки","Canceled buy deal")                                    :
      reason==EVENT_REASON_SELL_CANCELLED                   ?  TextByLanguage("Отмененная сделка продажи","Canceled sell deal")                                   :
      reason==EVENT_REASON_DIVIDENT                         ?  TextByLanguage("Начисление дивиденда","Dividend operations")                                       :
      reason==EVENT_REASON_DIVIDENT_FRANKED                 ?  TextByLanguage("Начисление франкированного дивиденда","Franked (non-taxable) dividend operations") :
      reason==EVENT_REASON_TAX                              ?  TextByLanguage("Начисление налога","Tax charges")                                                  :
      EnumToString(reason)
     );
  }
//+------------------------------------------------------------------+

Añadimos a la sección pública de la clase - en el apartado de acceso simplificado a las propiedades del evento - los métodos que retornan las nuevas propiedades añadidas:

//+------------------------------------------------------------------+
//| Методы упрощённого доступа к свойствам объекта-события           |
//+------------------------------------------------------------------+
//--- Возвращает (1) тип события, (2) время события в милисекундах, (3) статус события, (4) причину события, (5) тип сделки, (6) тикет сделки, 
//--- (7) тип ордера, на основании которого исполнена сделка, (8) тип открывающего ордера позиции, (9) тикет последнего ордера позиции, 
//--- (10) тикет первого ордера позиции, (11) идентификатор позиции, (12) идентификатор встречной позиции, (13) магик, (14) магик встречной, (15) время открытия позиции
   ENUM_TRADE_EVENT  TypeEvent(void)                                    const { return (ENUM_TRADE_EVENT)this.GetProperty(EVENT_PROP_TYPE_EVENT);           }
   long              TimeEvent(void)                                    const { return this.GetProperty(EVENT_PROP_TIME_EVENT);                             }
   ENUM_EVENT_STATUS Status(void)                                       const { return (ENUM_EVENT_STATUS)this.GetProperty(EVENT_PROP_STATUS_EVENT);        }
   ENUM_EVENT_REASON Reason(void)                                       const { return (ENUM_EVENT_REASON)this.GetProperty(EVENT_PROP_REASON_EVENT);        }
   ENUM_DEAL_TYPE    TypeDeal(void)                                     const { return (ENUM_DEAL_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT);        }
   long              TicketDeal(void)                                   const { return this.GetProperty(EVENT_PROP_TICKET_DEAL_EVENT);                      }
   ENUM_ORDER_TYPE   TypeOrderEvent(void)                               const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_EVENT);      }
   ENUM_ORDER_TYPE   TypeFirstOrderPosition(void)                       const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION);   }
   long              TicketOrderEvent(void)                             const { return this.GetProperty(EVENT_PROP_TICKET_ORDER_EVENT);                     }
   long              TicketFirstOrderPosition(void)                     const { return this.GetProperty(EVENT_PROP_TICKET_ORDER_POSITION);                  }
   long              PositionID(void)                                   const { return this.GetProperty(EVENT_PROP_POSITION_ID);                            }
   long              PositionByID(void)                                 const { return this.GetProperty(EVENT_PROP_POSITION_BY_ID);                         }
   long              Magic(void)                                        const { return this.GetProperty(EVENT_PROP_MAGIC_ORDER);                            }
   long              MagicCloseBy(void)                                 const { return this.GetProperty(EVENT_PROP_MAGIC_BY_ID);                            }
   long              TimePosition(void)                                 const { return this.GetProperty(EVENT_PROP_TIME_ORDER_POSITION);                    }

//--- При смене направления позиции возвращает (1) тип ордера предыдущей позиции, (2) тикет ордера предыдущей позиции
//--- (3) тип ордера текущей позиции, (4) тикет ордера текущей позиции
//--- (5) тип и (6) тикет позиции до смены направления, (7) тип и (8) тикет позиции после смены направления
   ENUM_ORDER_TYPE   TypeOrderPosPrevious(void)                         const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE);   }
   long              TicketOrderPosPrevious(void)                       const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE); }
   ENUM_ORDER_TYPE   TypeOrderPosCurrent(void)                          const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORD_POS_CURRENT);  }
   long              TicketOrderPosCurrent(void)                        const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TICKET_ORD_POS_CURRENT);}
   ENUM_POSITION_TYPE TypePositionPrevious(void)                        const { return PositionTypeByOrderType(this.TypeOrderPosPrevious());                }
   ulong             TicketPositionPrevious(void)                       const { return this.TicketOrderPosPrevious();                                       }
   ENUM_POSITION_TYPE TypePositionCurrent(void)                         const { return PositionTypeByOrderType(this.TypeOrderPosCurrent());                 }
   ulong             TicketPositionCurrent(void)                        const { return this.TicketOrderPosCurrent();                                        }
   
//--- Возвращает (1) цену, на которой произошло событие, (2) цену открытия, (3) цену закрытия,
//--- (4) цену StopLoss, (5) цену TakeProfit, (6) профит, (7) Запрашиваемый объём ордера, 
//--- 8) Исполненный объём ордера, (9) Оставшийся объём ордера, (10) исполненный объём позиции
   double            PriceEvent(void)                                   const { return this.GetProperty(EVENT_PROP_PRICE_EVENT);                            }
   double            PriceOpen(void)                                    const { return this.GetProperty(EVENT_PROP_PRICE_OPEN);                             }
   double            PriceClose(void)                                   const { return this.GetProperty(EVENT_PROP_PRICE_CLOSE);                            }
   double            PriceStopLoss(void)                                const { return this.GetProperty(EVENT_PROP_PRICE_SL);                               }
   double            PriceTakeProfit(void)                              const { return this.GetProperty(EVENT_PROP_PRICE_TP);                               }
   double            Profit(void)                                       const { return this.GetProperty(EVENT_PROP_PROFIT);                                 }
   double            VolumeOrderInitial(void)                           const { return this.GetProperty(EVENT_PROP_VOLUME_ORDER_INITIAL);                   }
   double            VolumeOrderExecuted(void)                          const { return this.GetProperty(EVENT_PROP_VOLUME_ORDER_EXECUTED);                  }
   double            VolumeOrderCurrent(void)                           const { return this.GetProperty(EVENT_PROP_VOLUME_ORDER_CURRENT);                   }
   double            VolumePositionExecuted(void)                       const { return this.GetProperty(EVENT_PROP_VOLUME_POSITION_EXECUTED);               }

//--- При модификации цен возвращает (1) цену установки ордера, (2) StopLoss, (3) TakeProfit до модификации
   double            PriceOpenBefore(void)                              const { return this.GetProperty(EVENT_PROP_PRICE_OPEN_BEFORE);                      }
   double            PriceStopLossBefore(void)                          const { return this.GetProperty(EVENT_PROP_PRICE_SL_BEFORE);                        }
   double            PriceTakeProfitBefore(void)                        const { return this.GetProperty(EVENT_PROP_PRICE_TP_BEFORE);                        }
   double            PriceEventAsk(void)                                const { return this.GetProperty(EVENT_PROP_PRICE_EVENT_ASK);                        }
   double            PriceEventBid(void)                                const { return this.GetProperty(EVENT_PROP_PRICE_EVENT_BID);                        }

//--- Возвращает (1) символ, (2) символ встречной позиции
   string            Symbol(void)                                       const { return this.GetProperty(EVENT_PROP_SYMBOL);                                 }
   string            SymbolCloseBy(void)                                const { return this.GetProperty(EVENT_PROP_SYMBOL_BY_ID);                           }
   
//+------------------------------------------------------------------+

Dado que la mayoría de las propiedades de la clase se rellenan en la clase de colección de eventos después de crear un nuevo objeto de clase de evento en el método CreateNewEvent(), y después se establece el tipo de evento llamando el método SetTypeEvent() de la clase CEvent, vamos a implementar el establecimiento del valor Digits() del símbolo del evento en el método SetTypeEvent() de la clase CEvent, junto con la definición del evento de modificación:

//+------------------------------------------------------------------+
//| Расшифровывает код события и устанавливает торговое событие      |
//+------------------------------------------------------------------+
void CEvent::SetTypeEvent(void)
  {
//--- Установка Digits() символа события
   this.m_digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS);
//--- Отложенный ордер установлен (проверяем на равенство коду события, так как здесь может быть только один флаг)
   if(this.m_event_code==TRADE_EVENT_FLAG_ORDER_PLASED)
     {
      this.m_trade_event=TRADE_EVENT_PENDING_ORDER_PLASED;
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
//--- Отложенный ордер удалён (проверяем на равенство коду события, так как здесь может быть только один флаг)
   if(this.m_event_code==TRADE_EVENT_FLAG_ORDER_REMOVED)
     {
      this.m_trade_event=TRADE_EVENT_PENDING_ORDER_REMOVED;
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
//--- Модифицирован отложенный ордер
   if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_MODIFY))
     {
      //--- Если модифицирована цена установки
      if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_PRICE))
        {
         this.m_trade_event=TRADE_EVENT_MODIFY_ORDER_PRICE;
         //--- Если модифицированы StopLoss и TakeProfit
         if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL) && this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP))
            this.m_trade_event=TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT;
         //--- Если модифицирован StopLoss
         else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL))
            this.m_trade_event=TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS;
         //--- Если модифицирован TakeProfit
         else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP))
            this.m_trade_event=TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT;
        }
      //--- Если цена установки не модифицирована
      else
        {
         //--- Если модифицированы StopLoss и TakeProfit
         if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL) && this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP))
            this.m_trade_event=TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT;
         //--- Если модифицирован StopLoss
         else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL))
            this.m_trade_event=TRADE_EVENT_MODIFY_ORDER_STOP_LOSS;
         //--- Если модифицирован TakeProfit
         else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP))
            this.m_trade_event=TRADE_EVENT_MODIFY_ORDER_TAKE_PROFIT;
        }
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
//--- Модифицирована позиция
   if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_MODIFY))
     {
      //--- Если модифицированы StopLoss и TakeProfit
      if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL) && this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP))
         this.m_trade_event=TRADE_EVENT_MODIFY_POSITION_STOP_LOSS_TAKE_PROFIT;
      //--- Если модифицирован StopLoss
      else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL))
         this.m_trade_event=TRADE_EVENT_MODIFY_POSITION_STOP_LOSS;
      //--- Если модифицирован TakeProfit
      else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP))
         this.m_trade_event=TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT;
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
//--- Позиция открыта (Проверяем наличие множества флагов в коде события)
   if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_OPENED))
     {
   //--- Если это изменение существующей позиции
      if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_CHANGED))
        {
         //--- Если это отложенный ордер активирован ценой
         if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED))
           {
            //--- Если это разворот позиции
            if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_REVERSE))
              {
               //--- проверяем флаг частичного открытия и устанавливаем торговое событие 
               //--- "разворот позиции активацией отложенного ордера" или "разворот позиции частичной активацией отложенного ордера"
               this.m_trade_event=
                 (
                  !this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? 
                  TRADE_EVENT_POSITION_REVERSED_BY_PENDING : 
                  TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL
                 );
               this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
               return;
              }
            //--- Если это добавление объёма к позиции
            else
              {
               //--- проверяем флаг частичного открытия и устанавливаем торговое событие 
               //--- "добавлен объём к позиции активацией отложенного ордера" или "добавлен объём к позиции частичной активацией отложенного ордера"
               this.m_trade_event=
                 (
                  !this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? 
                  TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING : 
                  TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL
                 );
               this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
               return;
              }
           }
         //--- Если изменение позиции было осуществлено сделкой ро рынку
         else
           {
            //--- Если это разворот позиции
            if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_REVERSE))
              {
               //--- проверяем флаг частичного открытия и устанавливаем торговое событие "разворот позиции" или "разворот позиции частичным исполнением"
               this.m_trade_event=
                 (
                  !this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? 
                  TRADE_EVENT_POSITION_REVERSED_BY_MARKET : 
                  TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL
                 );
               this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
               return;
              }
            //--- Если это добавление объёма к позиции
            else
              {
               //--- проверяем флаг частичного открытия и устанавливаем торговое событие "добавлен объём к позиции" или "добавлен объём к позиции частичным исполнением"
               this.m_trade_event=
                 (
                  !this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? 
                  TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET : 
                  TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL
                 );
               this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
               return;
              }
           }
        }
   //--- Если это открытие новой позиции
      else
        {
         //--- Если это отложенный ордер активирован ценой
         if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED))
           {
            //--- проверяем флаг частичного открытия и устанавливаем торговое событие "отложенный ордер активирован" или "отложенный ордер активирован частично"
            this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_PENDING_ORDER_ACTIVATED : TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL);
            this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
            return;
           }
         //--- проверяем флаг частичного открытия и устанавливаем торговое событие "Позиция открыта" или "Позиция открыта частично"
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_OPENED : TRADE_EVENT_POSITION_OPENED_PARTIAL);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
     }
     
//--- Позиция закрыта (Проверяем наличие множества флагов в коде события)
   if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_CLOSED))
     {
      //--- если позиция закрыта по StopLoss
      if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL))
        {
         //--- проверяем флаг частичного закрытия и устанавливаем торговое событие "Позиция закрыта по StopLoss" или "Позиция закрыта по StopLoss частично"
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_SL : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
      //--- если позиция закрыта по TakeProfit
      else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP))
        {
         //--- проверяем флаг частичного закрытия и устанавливаем торговое событие "Позиция закрыта по TakeProfit" или "Позиция закрыта по TakeProfit частично"
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_TP : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
      //--- если позиция закрыта встречной
      else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_BY_POS))
        {
         //--- проверяем флаг частичного закрытия и устанавливаем торговое событие "Позиция закрыта встречной" или "Позиция закрыта встречной частично"
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_POS : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
      //--- Если позиция закрыта
      else
        {
         //--- проверяем флаг частичного закрытия и устанавливаем торговое событие "Позиция закрыта" или "Позиция закрыта частично"
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED : TRADE_EVENT_POSITION_CLOSED_PARTIAL);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
     }
//--- Балансная операция на счёте (уточняем событие по типу сделки)
   if(this.m_event_code==TRADE_EVENT_FLAG_ACCOUNT_BALANCE)
     {
      //--- Инициализируем торговое событие
      this.m_trade_event=TRADE_EVENT_NO_EVENT;
      //--- Возьмём тип сделки
      ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT);
      //--- если сделка - балансная операция
      if(deal_type==DEAL_TYPE_BALANCE)
        {
        //--- проверим профит сделки и установим событие (пополнение или снятие средств)
         this.m_trade_event=(this.GetProperty(EVENT_PROP_PROFIT)>0 ? TRADE_EVENT_ACCOUNT_BALANCE_REFILL : TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL);
        }
      //--- Остальные типы балансной операции совпадают с перечислением ENUM_DEAL_TYPE начиная от DEAL_TYPE_CREDIT
      else if(deal_type>DEAL_TYPE_BALANCE)
        {
        //--- установим это событие
         this.m_trade_event=(ENUM_TRADE_EVENT)deal_type;
        }
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
  }
//+------------------------------------------------------------------+

Hemos escrito todas las comprobaciones y acciones necesarias en los comentarios al código del listado del método, así que no vamos a distraernos en la descripción de acciones ya descritas. Consideramos que todo es lo suficientemente sencillo y claro como para estuadirlo de forma autónoma.
Con esto, hemos finalizado la clase de evento abstracto.

Adelantándonos un poco: al comprobar el seguimiento de la modificación de los precios de colocación de órdenes pendientes en el asesor de prueba, ha surgido la necesidad de encontrar la orden más alejada del precio. Al analizar las propiedades de las órdenes, hemos comprendido que no existe una solución rápida y unívoca para esta cuestión. Como solución, usaremos uno de las propiedades adicionales de tipo entero de la orden: el beneficio en puntos. Para las órdenes pendientes, se tratará de la distancia de la orden en puntos con respecto al precio. De esta forma, para encontrar la orden más alejada del precio, solo tenemos que buscar la orden con mayor "beneficio" (distancia) en puntos.
Por cierto, los mismo sucede con la búsqueda de todas las órdenes pendientes según su dirección: para encontrar la orden pendiente más alejada del precio, seleccionamos todas las órdenes en una dirección y filtramos la lista obtenida según la mayor distancia. Como resultado, obtenemos una sola orden de todas las órdenes de diverso tipo, pero en una dirección (BuyLimit, BuyStop y BuyStopLimit, todas ellas tienen una misma dirección: Buy. Para Sell, todo será al contrario).

Vamos a modificar el método de obtención del tipo de orden según su dirección en el listado de la clase de orden abstracta Order.mqh:

//+------------------------------------------------------------------+
//| Retorna el beneficio de la orden en puntos                               |
//+------------------------------------------------------------------+
int COrder::ProfitInPoints(void) const
  {
   MqlTick tick={0};
   string symbol=this.Symbol();
   if(!::SymbolInfoTick(symbol,tick))
      return 0;
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)this.TypeOrder();
   double point=::SymbolInfoDouble(symbol,SYMBOL_POINT);
   if(type==ORDER_TYPE_CLOSE_BY || point==0) return 0;
   if(this.Status()==ORDER_STATUS_HISTORY_ORDER)
      return int(type==ORDER_TYPE_BUY ? (this.PriceClose()-this.PriceOpen())/point : type==ORDER_TYPE_SELL ? (this.PriceOpen()-this.PriceClose())/point : 0);
   else if(this.Status()==ORDER_STATUS_MARKET_POSITION)
     {
      if(type==ORDER_TYPE_BUY)
         return int((tick.bid-this.PriceOpen())/point);
      else if(type==ORDER_TYPE_SELL)
         return int((this.PriceOpen()-tick.ask)/point);
     }
   else if(this.Status()==ORDER_STATUS_MARKET_PENDING)
     {
      if(type==ORDER_TYPE_BUY_LIMIT || type==ORDER_TYPE_BUY_STOP || type==ORDER_TYPE_BUY_STOP_LIMIT)
         return (int)fabs((tick.bid-this.PriceOpen())/point);
      else if(type==ORDER_TYPE_SELL_LIMIT || type==ORDER_TYPE_SELL_STOP || type==ORDER_TYPE_SELL_STOP_LIMIT)
         return (int)fabs((this.PriceOpen()-tick.ask)/point);
     }
   return 0;
  }
//+------------------------------------------------------------------+

En esencia, aquí hemos añadido solo la comprobación de órdenes pendientes, además de retornar la distancia en puntos desde el precio de colocación de la orden hasta el precio actual.

Vamos a añadir la descripción de la distancia en puntos desde el precio hasta la orden pendiente en el método de descripción de las propiedades de tipo entero de la clase de orden abstracta:

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

Aquí, comprobamos el estado de la orden, y si se tarata de una orden pendiente activa, se muestra un mensaje sobre la distancia, de lo contrario, se muestra un mensaje sobre el beneficio en puntos.

Con esto, hemos terminado de modificar la clase de orden abstracta.

Ahora, tenemos que crear otra clase heredera de la clase de orden abstracta CEvent. Será una clase de evento de modificación.

En el sexto artículo, al implementar el funcionamiento en la cuentas de compensación, se mejoró la clase de evento de apertura de posiciones: se añadió a la clase CEventPositionOpen un método que crea un texto de mesnaje breve dependiendo del estado del evento y la presencia de ciertas propiedades del objeto de evento.
Al crear un nuevo evento de modificación, actuaremos de forma similar: comprobaremos el tipo del evento de modificación y crearemos el texto del evento dependiendo del tipo obtenido. Además, al enviar un evento al gráfico del programa de control, necesitaremos decidir qué precio transmitimos en el parámetro dparam de la función EventChartCustom(). Si en la clase del evento de apertura de la posición hemos transmitido con este parámetro el precio de apertura, en la clase de evento de modificación, en cambio, serán posibles varias opciones de cambio de precios, y necesitamos decidir cuál de los precios vamos enviar en el parámetro dparam del evento de usuario:

De esta forma, podemos ver que al cambiar solo un precio, enviamos al evento precisamente el precio cambiado, y al cambiar simulatáneamente varios precios, enviamos solo el precio de apertura de la posición o el de la colocación de la orden (que, a su vez, también puede ser modificado). En nuestro programa, siempre será posible precisar el cambio de cada uno de los precios (si se modifican simultáneamente) según el tipo del evento de modificación sucedido.

Vamos a crear en el nuevo archivo EventModify.mqh del directorio de la biblioteca \MQL5\Include\DoEasy\Objects\Events la nueva clase CEventModify.
Indicaremos como clase básica para ella la clase de evento abstracto CEvent.
No debemos olvidarnos de incluir el archivo de la clase de evento abstracto en el archivo de la clase de modificación.
Dado que nuestra clase no será grande, vamos a mostrar su listado completo, para que el lector pueda estuadiarla por sí mismo, tanto más que ya hemos analizado una clase idéntica en la sexta parte de la descripción de la bibilioteca, al implementar los cambios de la clase CEventPositionOpen.

//+------------------------------------------------------------------+
//|                                                  EventModify.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Archivos de inclusión                                                 |
//+------------------------------------------------------------------+
#include "Event.mqh"
//+------------------------------------------------------------------+
//| Событие установки отложенного ордера                             |
//+------------------------------------------------------------------+
class CEventModify : public CEvent
  {
private:
   double            m_price;                               // Цена, отправляемая в событие
//--- Создаёт и возвращает краткое сообщение события
   string            EventsMessage(void);
public:
//--- Конструктор
                     CEventModify(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_MODIFY,event_code,ticket),m_price(0) {}
//--- Поддерживаемые свойства ордера (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 CEventModify::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 CEventModify::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
  {
   if(property==EVENT_PROP_PRICE_CLOSE             ||
      property==EVENT_PROP_PROFIT
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Выводит в журнал краткое сообщение о событии                     |
//+------------------------------------------------------------------+
void CEventModify::PrintShort(void)
  {
   ::Print(this.EventsMessage());
  }
//+------------------------------------------------------------------+
//| Отправляет событие на график                                     |
//+------------------------------------------------------------------+
void CEventModify::SendEvent(void)
  {
   this.PrintShort();
   ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.TicketOrderEvent(),this.m_price,this.Symbol());
  }
//+------------------------------------------------------------------+
//| Создаёт и возвращает краткое сообщение события                   |
//+------------------------------------------------------------------+
string CEventModify::EventsMessage(void)
  {
//--- (1) заголовок, (2) магический номер
   string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : "");
   string text="";

//--- Модифицирована цена отложенного ордера
   if(this.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE)
     {
      string order=OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderPosCurrent();
      string price="["+::DoubleToString(this.PriceOpenBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceOpen(),this.m_digits)+"]";
      text=order+TextByLanguage(": модифицирована цена: ",": modified price: ")+price+magic;
      this.m_price=this.PriceOpen();
     }
//--- Модифицирована цена отложенного ордера и его StopLoss
   else if(this.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS)
     {
      string order=OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderPosCurrent();
      string price="["+::DoubleToString(this.PriceOpenBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceOpen(),this.m_digits)+"]";
      string sl="["+::DoubleToString(this.PriceStopLossBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceStopLoss(),this.m_digits)+"]";
      text=order+TextByLanguage(": модифицирована цена: ",": modified price: ")+price+TextByLanguage(" и"," and")+" StopLoss: "+sl+magic;
      this.m_price=this.PriceOpen();
     }
//--- Модифицирована цена отложенного ордера и его TakeProfit
   else if(this.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT)
     {
      string order=OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderPosCurrent();
      string price="["+::DoubleToString(this.PriceOpenBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceOpen(),this.m_digits)+"]";
      string tp="["+::DoubleToString(this.PriceTakeProfitBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceTakeProfit(),this.m_digits)+"]";
      text=order+TextByLanguage(": модифицирована цена: ",": modified price: ")+price+TextByLanguage(" и"," and")+" TakeProfit: "+tp+magic;
      this.m_price=this.PriceOpen();
     }
//--- Модифицирована цена отложенного ордера, его StopLoss и TakeProfit
   else if(this.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT)
     {
      string order=OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderPosCurrent();
      string price="["+::DoubleToString(this.PriceOpenBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceOpen(),this.m_digits)+"]";
      string sl="["+::DoubleToString(this.PriceStopLossBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceStopLoss(),this.m_digits)+"]";
      string tp="["+::DoubleToString(this.PriceTakeProfitBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceTakeProfit(),this.m_digits)+"]";
      text=order+TextByLanguage(": модифицирована цена: ",": modified price: ")+price+", StopLoss: "+sl+TextByLanguage(" и"," and")+" TakeProfit: "+tp+magic;
      this.m_price=this.PriceOpen();
     }
//--- Модифицирован StopLoss отложенного ордера
   else if(this.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS)
     {
      string order=OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderPosCurrent();
      string sl="["+::DoubleToString(this.PriceStopLossBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceStopLoss(),this.m_digits)+"]";
      text=order+TextByLanguage(": модифицирован StopLoss: ",": modified StopLoss: ")+sl+magic;
      this.m_price=this.PriceStopLoss();
     }
//--- Модифицирован TakeProfit отложенного ордера
   else if(this.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_TAKE_PROFIT)
     {
      string order=OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderPosCurrent();
      string tp="["+::DoubleToString(this.PriceTakeProfitBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceTakeProfit(),this.m_digits)+"]";
      text=order+TextByLanguage(": модифицирован TakeProfit: ",": modified TakeProfit: ")+tp+magic;
      this.m_price=this.PriceTakeProfit();
     }
//--- Модифицирован StopLoss и TakeProfit отложенного ордера
   else if(this.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT)
     {
      string order=OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderPosCurrent();
      string sl="["+::DoubleToString(this.PriceStopLossBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceStopLoss(),this.m_digits)+"]";
      string tp="["+::DoubleToString(this.PriceTakeProfitBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceTakeProfit(),this.m_digits)+"]";
      text=order+TextByLanguage(": модифицирован StopLoss: ",": modified StopLoss: ")+sl+TextByLanguage(" и"," and")+" TakeProfit: "+tp+magic;
      this.m_price=this.PriceOpen();
     }
//--- Модифицирован StopLoss позиции
   else if(this.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS)
     {
      string order=PositionTypeDescription(this.TypePositionCurrent())+" #"+(string)this.TicketPositionCurrent();
      string sl="["+::DoubleToString(this.PriceStopLossBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceStopLoss(),this.m_digits)+"]";
      text=order+TextByLanguage(": модифицирован StopLoss: ",": modified StopLoss: ")+sl+magic;
      this.m_price=this.PriceStopLoss();
     }
//--- Модифицирован TakeProfit позиции
   else if(this.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT)
     {
      string order=PositionTypeDescription(this.TypePositionCurrent())+" #"+(string)this.TicketPositionCurrent();
      string tp="["+::DoubleToString(this.PriceTakeProfitBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceTakeProfit(),this.m_digits)+"]";
      text=order+TextByLanguage(": модифицирован TakeProfit: ",": modified TakeProfit: ")+tp+magic;
      this.m_price=this.PriceTakeProfit();
     }
//--- Модифицирован StopLoss и TakeProfit позиции
   else if(this.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS_TAKE_PROFIT)
     {
      string order=PositionTypeDescription(this.TypePositionCurrent())+" #"+(string)this.TicketPositionCurrent();
      string sl="["+::DoubleToString(this.PriceStopLossBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceStopLoss(),this.m_digits)+"]";
      string tp="["+::DoubleToString(this.PriceTakeProfitBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceTakeProfit(),this.m_digits)+"]";
      text=order+TextByLanguage(": модифицирован StopLoss: ",": modified StopLoss: ")+sl+TextByLanguage(" и"," and")+" TakeProfit: "+tp+magic;
      this.m_price=this.PriceOpen();
     }
   return head+this.Symbol()+" "+text;
  }
//+------------------------------------------------------------------+

Ahora, necesitamos definir en la clase de colección de eventos los eventos de modificación de las órdenes y posiciones ya existentes, crear un nuevo evento y añadirlo a la lista de colección de eventos.

Introducimos todas las mejoras necesarias en la clase CEventsCollection, en el archivo EventsCollection.mqh de la carpeta de la biblioteca \MQL5\Include\DoEasy\Collections.

Incluimos el archivo de la nueva clase de evento de la modificación.
En la sección privada de la clase, declaramos la variable de miembro de clase, una estructura para guardar los datos de ticks. Usaremos esta para obtener los datos sobre los últimos precios del evento de modificación.

//+------------------------------------------------------------------+
//|                                             EventsCollection.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Archivos de inclusión                                                 |
//+------------------------------------------------------------------+
#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"
#include "..\Objects\Events\EventModify.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;                // Объект-событие для поиска по свойству
   MqlTick           m_tick;                          // Структура последнего тика
   


Inicializamos la estructura del tick en el constructor de la clase:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CEventsCollection::CEventsCollection(void) : m_trade_event(TRADE_EVENT_NO_EVENT),m_trade_event_code(TRADE_EVENT_FLAG_NO_EVENT)
  {
   this.m_list_events.Clear();
   this.m_list_events.Sort(SORT_BY_EVENT_TIME_EVENT);
   this.m_list_events.Type(COLLECTION_EVENTS_ID);
   this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
   this.m_chart_id=::ChartID();
   ::ZeroMemory(this.m_tick);
  }
//+------------------------------------------------------------------+

En la séptima parte de la descripción, ya hicimos un método sobrecargado para crear un nuevo evento; ahora tenemos dos: un método para crear eventos al cambiar el número de órdenes y posiciones en la cuenta, y otro método que crea un nuevo evento al cambiar una orden o posición ya existente.
Debemos añadir algunos elementos al segundo, para que este pueda monitorear los eventos de modificación de órdenes y posiciones (en el séptimo artículo, el método procesaba solo el evento de activación de una orden StopLimit).
Añadimos varias líneas de código que procesan el evento de modificación de la orden o posición, y varias líneas para guardar la propiedad de la orden o posición hasta la modificación:

//+------------------------------------------------------------------+
//| Создаёт торговое событие в зависимости от типа изменения ордера  |
//+------------------------------------------------------------------+
void CEventsCollection::CreateNewEvent(COrderControl* order)
  {
   if(!::SymbolInfoTick(order.Symbol(),this.m_tick))
     {
      Print(DFUN,TextByLanguage("Не удалось получить текущие цены по символу события ","Failed to get current prices by event symbol "),order.Symbol());
      return;
     }
   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());
     }
//--- Модификация
   else
     {
      //--- Модифицирована цена отложенного ордера
      if(order.GetChangeType()==CHANGE_TYPE_ORDER_PRICE)
         this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_MODIFY+TRADE_EVENT_FLAG_PRICE;
      //--- Модифицирована цена отложенного ордера и его StopLoss
      else if(order.GetChangeType()==CHANGE_TYPE_ORDER_PRICE_STOP_LOSS)
         this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_MODIFY+TRADE_EVENT_FLAG_PRICE+TRADE_EVENT_FLAG_SL;
      //--- Модифицирована цена отложенного ордера и его TakeProfit
      else if(order.GetChangeType()==CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT)
         this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_MODIFY+TRADE_EVENT_FLAG_PRICE+TRADE_EVENT_FLAG_TP;
      //--- Модифицирована цена отложенного ордера, его StopLoss и TakeProfit
      else if(order.GetChangeType()==CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT)
         this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_MODIFY+TRADE_EVENT_FLAG_PRICE+TRADE_EVENT_FLAG_SL+TRADE_EVENT_FLAG_TP;
      //--- Модифицирован StopLoss отложенного ордера
      else if(order.GetChangeType()==CHANGE_TYPE_ORDER_STOP_LOSS)
         this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_MODIFY+TRADE_EVENT_FLAG_SL;
      //--- Модифицирован TakeProfit отложенного ордера
      else if(order.GetChangeType()==CHANGE_TYPE_ORDER_TAKE_PROFIT)
         this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_MODIFY+TRADE_EVENT_FLAG_TP;
      //--- Модифицирован StopLoss и TakeProfit отложенного ордера
      else if(order.GetChangeType()==CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT)
         this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_MODIFY+TRADE_EVENT_FLAG_SL+TRADE_EVENT_FLAG_TP;

      //--- Модифицирован StopLoss позиции
      else if(order.GetChangeType()==CHANGE_TYPE_POSITION_STOP_LOSS)
         this.m_trade_event_code=TRADE_EVENT_FLAG_POSITION_MODIFY+TRADE_EVENT_FLAG_SL;
      //--- Модифицирован TakeProfit позиции
      else if(order.GetChangeType()==CHANGE_TYPE_POSITION_TAKE_PROFIT)
         this.m_trade_event_code=TRADE_EVENT_FLAG_POSITION_MODIFY+TRADE_EVENT_FLAG_TP;
      //--- Модифицирован StopLoss и TakeProfit позиции
      else if(order.GetChangeType()==CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT)
         this.m_trade_event_code=TRADE_EVENT_FLAG_POSITION_MODIFY+TRADE_EVENT_FLAG_SL+TRADE_EVENT_FLAG_TP;
      
      //--- Создаём событие модификации
      event=new CEventModify(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,PositionTypeByOrderType((ENUM_ORDER_TYPE)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_PRICE_OPEN_BEFORE,order.PricePrev());            // Цена установки ордера до модификации
      event.SetProperty(EVENT_PROP_PRICE_SL_BEFORE,order.StopLossPrev());           // Цена StopLoss до модификации
      event.SetProperty(EVENT_PROP_PRICE_TP_BEFORE,order.TakeProfitPrev());         // Цена TakeProfit до модификации
      event.SetProperty(EVENT_PROP_PRICE_EVENT_ASK,this.m_tick.ask);                // Цена Ask в момент события
      event.SetProperty(EVENT_PROP_PRICE_EVENT_BID,this.m_tick.bid);                // Цена Bid в момент события
         
      event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                      // Магический номер ордера
      event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.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;
        }
     }
  }
//+------------------------------------------------------------------+

El procesamiento de las condiciones de los diferentes tipos de modificación es bastante sencillo y comprensible, y ha sido descrito en los comentarios al código. Dependiendo del tipo de cambio de la orden/posición, se crea un código de evento a partir del conjunto de banderas, y este código es enviado al constructor de clase CEventModify al crear un nuevo evento de modificación.

Queremos destacar que los bloques de código para guardar las nuevas propiedades de una orden/posición marcados en amarillo son añadidos en todos los métodos de guardado de las propiedades de la orden/posición de esta clase. Para no ocupar espacio aquí con líneas de código idénticas, omitiremos el análisis de estas. Se encuentran a disposición del lector en los archivos al final del artículo.

Ya tenemos todo listo para la simulación de los eventos de modificación de las órdenes y posiciones existentes.

Simulando los eventos de modificación de las órdenes y posiciones

Para realizar la simulación, necesitaremos completar el conjunto existente de botones del asesor de prueba del séptimo artículo.
Añadimos al conjunto de botones del asesor otros tres botones más, así como los manejadores de su pulsación: Set StopLoss, Set TakeProfit y Trailing All.
Los dos primeros botones establecerán el stop loss y el take profit de todas las órdenes y posiciones que carezcan de estos niveles; el tercer botón, tendrá dos posiciones (Act/Desact), es decir, al pulsar el mismo, permanecerá pulsado y comenzarán a funcionar las dos funciones de trailing. Como resultado, el asesor comenzará a realizar el trailing de los stops de todas las posiciones y a tirar de todas las órdenes pendientes activas tras el precio. Si pulsamos el botón de nuevo, los dos trailings se desactivarán.

Tomamos el asesor TestDoEasyPart07.mq5 de \MQL5\Experts\TestDoEasy\Part07 y los guardamos en la nueva carpeta \MQL5\Experts\TestDoEasy\Part08  con el nombre TestDoEasyPart08.mq5.

Añadimos a la enumeración de los botones tres nuevas constantes y cambiamos el número total de botones de 17 a 20 en la macrosustitución correspondiente:

//--- enums
enum ENUM_BUTTONS
  {
   BUTT_BUY,
   BUTT_BUY_LIMIT,
   BUTT_BUY_STOP,
   BUTT_BUY_STOP_LIMIT,
   BUTT_CLOSE_BUY,
   BUTT_CLOSE_BUY2,
   BUTT_CLOSE_BUY_BY_SELL,
   BUTT_SELL,
   BUTT_SELL_LIMIT,
   BUTT_SELL_STOP,
   BUTT_SELL_STOP_LIMIT,
   BUTT_CLOSE_SELL,
   BUTT_CLOSE_SELL2,
   BUTT_CLOSE_SELL_BY_BUY,
   BUTT_DELETE_PENDING,
   BUTT_CLOSE_ALL,
   BUTT_PROFIT_WITHDRAWAL,
   BUTT_SET_STOP_LOSS,   
   BUTT_SET_TAKE_PROFIT, 
   BUTT_TRAILING_ALL     
  };
#define TOTAL_BUTT   (20)

Añadimos a los parámetros de entrada las variables para indicar la distancia del nivel de StopLoss desde el precio, el salto de trailing, el número de puntos de beneficio para comenzar el trailing y el tamaño del StopLoss y el TakeProfit en puntos, para establecerlos al pulsar los botones correspondientes (los parámetros InpStopLoss y InpTakeProfit se usan para establcer los niveles de stop justo al abrir una posición/colocar una orden pendiente).
Vamos a añadir a la lista de variables globales las variables necesarias para guardar los valores de las variables de entrada nuevamente añadidas y la variable de la bandera que indica la actividad de las funciones de los trailings:

//--- input variables
input ulong    InpMagic             =  123;  // Magic number
input double   InpLots              =  0.1;  // Lots
input uint     InpStopLoss          =  50;   // StopLoss in points
input uint     InpTakeProfit        =  50;   // TakeProfit in points
input uint     InpDistance          =  50;   // Pending orders distance (points)
input uint     InpDistanceSL        =  50;   // StopLimit orders distance (points)
input uint     InpSlippage          =  0;    // Slippage in points
input double   InpWithdrawal        =  10;   // Withdrawal funds (in tester)
input uint     InpButtShiftX        =  40;   // Buttons X shift 
input uint     InpButtShiftY        =  10;   // Buttons Y shift 
input uint     InpTrailingStop      =  50;   // Trailing Stop (points)
input uint     InpTrailingStep      =  20;   // Trailing Step (points)
input uint     InpTrailingStart     =  0   // Trailing Start (points)
input uint     InpStopLossModify    =  20;   // StopLoss for modification (points)
input uint     InpTakeProfitModify  =  60;   // TakeProfit for modification (points)
//--- global variables
CEngine        engine;
CTrade         trade;
SDataButt      butt_data[TOTAL_BUTT];
string         prefix;
double         lot;
double         withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal);
ulong          magic_number;
uint           stoploss;
uint           takeprofit;
uint           distance_pending;
uint           distance_stoplimit;
uint           slippage;
bool           trailing_on;          
double         trailing_stop;       
double         trailing_step;       
uint           trailing_start;      
uint           stoploss_to_modify;  
uint           takeprofit_to_modify;
//+------------------------------------------------------------------+

Como el asesor es de prueba, al depurar la biblioteca, suceden con frecuencia situaciones en las que el programa se completa con un error crítico. En estas situaciones, todos los objetos gráficos (botones)construidos permanecerán en el gráfico, por lo que al iniciar de nuevo el asesor después de corregir el error que ha provocado el error crítico, el asesor no podrá construir los botones; así, deberemos iniciarlo de nuevo, para que este elimine primero los objetos existentes en el gráfico en su manejador OnDeinit(), y en el siguiente inicio ya pueda construir de nuevo los botones en el gráfico limpio.

Añadimos al manejador OnInit() del asesor la comprobación sobre la presencia de botones en el gráfico, establecemos los valores para las variables de las funciones de trailing y los niveles stop, y después de construir todos los botones, comprobamos la bandera de actividad del botón de trailing y activamos el botón si la bandera ha sido establecida.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Вызов данной функции выводит в журнал список констант перечисления, 
//--- заданного в файле DELib.mqh в строках 22 и 25, для проверки корректности констант
   //EnumNumbersTest();
//--- check for undeleted objects
   if(IsPresentObects(prefix))   
      ObjectsDeleteAll(0,prefix);
//--- set global variables
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0));
   magic_number=InpMagic;
   stoploss=InpStopLoss;
   takeprofit=InpTakeProfit;
   distance_pending=InpDistance;
   distance_stoplimit=InpDistanceSL;
   slippage=InpSlippage;
   trailing_stop=InpTrailingStop*Point();
   trailing_step=InpTrailingStep*Point();
   trailing_start=InpTrailingStart;      
   stoploss_to_modify=InpStopLossModify;    
   takeprofit_to_modify=InpTakeProfitModify;
//--- create buttons
   if(!CreateButtons(InpButtShiftX,InpButtShiftY))
      return INIT_FAILED;
//--- set button trailing
   ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on);
//--- setting trade parameters
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_NO);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Vamos a escribir una función para determinar la presencia en el gráfico del objeto gráfico con el prefijo establecido y una función para controlar el estado de los botones. Para que sea más cómodo leer el código, sacaremos este control del manejador OnTick() del asesor a una función aparte:

//+------------------------------------------------------------------+
//| Возвращает флаг наличия объекта с префиксом                      |
//+------------------------------------------------------------------+
bool IsPresentObects(const string object_prefix)
  {
   for(int i=ObjectsTotal(0)-1;i>=0;i--)
      if(StringFind(ObjectName(0,i,0),object_prefix)>WRONG_VALUE)
         return true;
   return false;
  }
//+------------------------------------------------------------------+
//| Контроль состояния кнопок                                        |
//+------------------------------------------------------------------+
void PressButtonsControl(void)
  {
   int total=ObjectsTotal(0);
   for(int i=0;i<total;i++)
     {
      string obj_name=ObjectName(0,i);
      if(StringFind(obj_name,prefix+"BUTT_")<0)
         continue;
      PressButtonEvents(obj_name);
     }
  }
//+------------------------------------------------------------------+

Vamos a introducir cambios en la función encargada de establecer el estado del objeto de botón:

//+------------------------------------------------------------------+
//| Устанавливает состояние кнопки                                   |
//+------------------------------------------------------------------+
void ButtonState(const string name,const bool state)
  {
   ObjectSetInteger(0,name,OBJPROP_STATE,state);
   if(name==butt_data[TOTAL_BUTT-1].name)
     {
      if(state)
         ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'220,255,240');
      else
         ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'240,240,240');
     }
  }
//+------------------------------------------------------------------+

Aquí:
establecemos el estado del botón
(activado/desactivado),
si se trata del último botón y
si este tiene el estado "activado"
, cambiamos el color del fondo del objeto de botón,
de lo contrario,
devolvemos al color del fondo el estado "desactivado".

Dado que tenemos tres nuevos botones, vamos a añadir a la función de creación del texto de los botones a partir de su nombre la conversión de los nombres de los nuevos objetos de botón a su texto:

//+------------------------------------------------------------------+
//| Преобразует перечисление в текст кнопки                          |
//+------------------------------------------------------------------+
string EnumToButtText(const ENUM_BUTTONS member)
  {
   string txt=StringSubstr(EnumToString(member),5);
   StringToLower(txt);
   StringReplace(txt,"set_take_profit","Set TakeProfit");
   StringReplace(txt,"set_stop_loss","Set StopLoss");    
   StringReplace(txt,"trailing_all","Trailing All");     
   StringReplace(txt,"buy","Buy");
   StringReplace(txt,"sell","Sell");
   StringReplace(txt,"_limit"," Limit");
   StringReplace(txt,"_stop"," Stop");
   StringReplace(txt,"close_","Close ");
   StringReplace(txt,"2"," 1/2");
   StringReplace(txt,"_by_"," by ");
   StringReplace(txt,"profit_","Profit ");
   StringReplace(txt,"delete_","Delete ");
   return txt;
  }
//+------------------------------------------------------------------+

Ahora, necesitamos procesar la pulsación de los tres nuevos botones. Para ello, añadimos a la función de procesamiento de la pulsación de los botones PressButtonEvents(), al final de la misma (después del bloque de código para el procesamiento de la pulsación del botón de retirada de fondos), las siguientes líneas de código:

      //--- Если нажата кнопка BUTT_PROFIT_WITHDRAWAL: Вывести средства со счёта
      if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL))
        {
         //--- Если программа запущена в тестере
         if(MQLInfoInteger(MQL_TESTER))
           {
            //--- Эмулируем вывод средств
            TesterWithdrawal(withdrawal);
           }
        }
      //--- Если нажата кнопка BUTT_SET_STOP_LOSS: Установить StopLoss всем ордерам и позициям, где его нету
      if(button==EnumToString(BUTT_SET_STOP_LOSS))
        {
         SetStopLoss();
        }
      //--- Если нажата кнопка BUTT_SET_TAKE_PROFIT: Установить TakeProfit всем ордерам и позициям, где его нету
      if(button==EnumToString(BUTT_SET_TAKE_PROFIT))
        {
         SetTakeProfit();
        }
      //--- Подождём 1/10 секунды
      Sleep(100);
      //--- "Отожмём" кнопку (если это не кнопка трейлинга)
      if(button!=EnumToString(BUTT_TRAILING_ALL))
         ButtonState(button_name,false);
      //--- Если нажата кнопка BUTT_TRAILING_ALL
      else
        {
         //--- Поставим цвет активной кнопки
         ButtonState(button_name,true);
         trailing_on=true;
        }
      //--- перерисуем чарт
      ChartRedraw();
     }
   //--- Вернём цвет неактивной кнопки (если это кнопка трейлинга)
   else if(button==EnumToString(BUTT_TRAILING_ALL))
     {
      ButtonState(button_name,false);
      trailing_on=false;
      //--- перерисуем чарт
      ChartRedraw();
     }
  }
//+------------------------------------------------------------------+

Como podemos ver, aquí se llaman dos nuevas funciones: SetStopLoss() y SetTakeProfit(), para establecer los niveles correspondientes de las órdenes y posiciones:

//+------------------------------------------------------------------+
//| Установка StopLoss всем ордерам и позициям                       |
//+------------------------------------------------------------------+
void SetStopLoss(void)
  {
   if(stoploss_to_modify==0)
      return;
//--- Установка StopLoss всем позициям, где его нету
   CArrayObj* list=engine.GetListMarketPosition();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_SL,0,EQUAL);
   if(list==NULL)
      return;
   int total=list.Total();
   for(int i=total-1;i>=0;i--)
     {
      COrder* position=list.At(i);
      if(position==NULL)
         continue;
      double sl=CorrectStopLoss(position.Symbol(),position.TypeByDirection(),0,stoploss_to_modify);
      trade.PositionModify(position.Ticket(),sl,position.TakeProfit());
     }
//--- Установка StopLoss всем отложенным ордерам, где его нету
   list=engine.GetListMarketPendings();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_SL,0,EQUAL);
   if(list==NULL)
      return;
   total=list.Total();
   for(int i=total-1;i>=0;i--)
     {
      COrder* order=list.At(i);
      if(order==NULL)
         continue;
      double sl=CorrectStopLoss(order.Symbol(),(ENUM_ORDER_TYPE)order.TypeOrder(),order.PriceOpen(),stoploss_to_modify);
      trade.OrderModify(order.Ticket(),order.PriceOpen(),sl,order.TakeProfit(),trade.RequestTypeTime(),trade.RequestExpiration(),order.PriceStopLimit());
     }
  }
//+------------------------------------------------------------------+
//| Установка TakeProfit всем ордерам и позициям                     |
//+------------------------------------------------------------------+
void SetTakeProfit(void)
  {
   if(takeprofit_to_modify==0)
      return;                 
//--- Установка TakeProfit всем позициям, где его нету
   CArrayObj* list=engine.GetListMarketPosition();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TP,0,EQUAL);
   if(list==NULL)
      return;
   int total=list.Total();
   for(int i=total-1;i>=0;i--)
     {
      COrder* position=list.At(i);
      if(position==NULL)
         continue;
      double tp=CorrectTakeProfit(position.Symbol(),position.TypeByDirection(),0,takeprofit_to_modify);
      trade.PositionModify(position.Ticket(),position.StopLoss(),tp);
     }
//--- Установка TakeProfit всем отложенным ордерам, где его нету
   list=engine.GetListMarketPendings();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TP,0,EQUAL);
   if(list==NULL)
      return;
   total=list.Total();
   for(int i=total-1;i>=0;i--)
     {
      COrder* order=list.At(i);
      if(order==NULL)
         continue;
      double tp=CorrectTakeProfit(order.Symbol(),(ENUM_ORDER_TYPE)order.TypeOrder(),order.PriceOpen(),takeprofit_to_modify);
      trade.OrderModify(order.Ticket(),order.PriceOpen(),order.StopLoss(),tp,trade.RequestTypeTime(),trade.RequestExpiration(),order.PriceStopLimit());
     }
  }
//+------------------------------------------------------------------+

Las funciones son bastante sencillas. Como ejemplo, vamos a analizar la colocación del TakeProfit para todas las órdenes y posiciones donde no ha sido establecido:

En primer lugar, vamos a comprobar qué tamaño en puntos se ha establecido para los niveles stop, y si es igual a cero, salimos de inmediato, no hay que cambiar nada.
A continuación, obtenemos la lista de las posiciones de mercado activas y la filtramos según un precio de TakeProfit igual a cero, es decir, como si la posición no tuviese TakeProfit.
Acto seguido, usando un ciclo en la lista final, obtenemos de esta las posiciones, calculamos para cada una de ellas el TakeProfit correcto con la ayuda de la función de servicio que analizamos en la cuarta parte de la descripción de la biblioteca y lo enviamos al método de modificación de la posición de la clase CTrade de la biblioteca estándar.
Para establecer el TakeProfit de las órdenes, obtenemos la lista de las órdenes pendientes ya activas y realizamos con esta las acciones descritas más arriba.

Solo queda escribir la función de trailing de los stops de las posiciones y los precios de colocación de las órdenes:

//+------------------------------------------------------------------+
//| Трал стопа позиции с наибольшей прибылью                         |
//+------------------------------------------------------------------+
void TrailingPositions(void)
  {
   MqlTick tick;
   if(!SymbolInfoTick(Symbol(),tick))
      return;
   double stop_level=StopLevel(Symbol(),2)*Point();
   //--- Получаем список всех открытых позиций
   CArrayObj* list=engine.GetListMarketPosition();
   //--- Выбираем из списка только позиции Buy
   CArrayObj* list_buy=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
   //--- Сортируем список по прибыли с учётом комиссии и свопа
   list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
   //--- Получаем индекс позиции Buy с наибольшей прибылью
   int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
   if(index_buy>WRONG_VALUE)
     {
      COrder* buy=list_buy.At(index_buy);
      if(buy!=NULL)
        {
         //--- Рассчитываем новый StopLoss
         double sl=NormalizeDouble(tick.bid-trailing_stop,Digits());
         //--- Если цена и отложенный от неё уровень StopLevel выше нового StopLoss (соблюдена дистанция по StopLevel)
         if(tick.bid-stop_level>sl) 
           {
            //--- Если новый уровень StopLoss выше, чем шаг трала, отложенный от текущего StopLoss
            if(buy.StopLoss()+trailing_step<sl)
              {
               //--- Если тралим при любой прибыли или прибыль позиции в пунктах больше значения начала трейлинга - модифицируем StopLoss
               if(trailing_start==0 || buy.ProfitInPoints()>(int)trailing_start)
                  trade.PositionModify(buy.Ticket(),sl,buy.TakeProfit());
              }
           }
        }
     }
   //--- Выбираем из списка только позиции Sell
   CArrayObj* list_sell=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
   //--- Сортируем список по прибыли с учётом комиссии и свопа
   list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
   //--- Получаем индекс позиции Sell с наибольшей прибылью
   int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
   if(index_sell>WRONG_VALUE)
     {
      COrder* sell=list_sell.At(index_sell);
      if(sell!=NULL)
        {
         //--- Рассчитываем новый StopLoss
         double sl=NormalizeDouble(tick.ask+trailing_stop,Digits());
         //--- Если цена и отложенный от неё уровень StopLevel ниже нового StopLoss (соблюдена дистанция по StopLevel)
         if(tick.ask+stop_level<sl) 
           {
            //--- Если новый уровень StopLoss ниже, чем шаг трала, отложенный от текущего StopLoss или у позиции ещё не установлен StopLoss
            if(sell.StopLoss()-trailing_step>sl || sell.StopLoss()==0)
              {
               //--- Если тралим при любой прибыли или прибыль позиции в пунктах больше значения начала трейлинга - модифицируем StopLoss
               if(trailing_start==0 || sell.ProfitInPoints()>(int)trailing_start)
                  trade.PositionModify(sell.Ticket(),sl,sell.TakeProfit());
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+
//| Трал самых дальних отложенных ордеров                            |
//+------------------------------------------------------------------+
void TrailingOrders(void)
  {
   MqlTick tick;
   if(!SymbolInfoTick(Symbol(),tick))
      return;
   double stop_level=StopLevel(Symbol(),2)*Point();
//--- Получаем список всех установленных ордеров
   CArrayObj* list=engine.GetListMarketPendings();
//--- Выбираем из списка только ордера в направлении Buy
   CArrayObj* list_buy=CSelect::ByOrderProperty(list,ORDER_PROP_DIRECTION,ORDER_TYPE_BUY,EQUAL);
   //--- Сортируем список по дистанции от цены в пунктах (по прибыли в пунктах)
   list_buy.Sort(SORT_BY_ORDER_PROFIT_PT);
   //--- Получаем индекс ордера в направлении Buy с наибольшей дистанцией
   int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_PT);
   if(index_buy>WRONG_VALUE)
     {
      COrder* buy=list_buy.At(index_buy);
      if(buy!=NULL)
        {
         //--- Если ордер установлен ниже цены (BuyLimit), и его необходимо "поднимать" за ценой
         if(buy.TypeOrder()==ORDER_TYPE_BUY_LIMIT)
           {
            //--- Рассчитываем новую цену установки ордера и стоп-уровни от новой цены
            double price=NormalizeDouble(tick.ask-trailing_stop,Digits());
            double sl=(buy.StopLoss()>0 ? NormalizeDouble(price-(buy.PriceOpen()-buy.StopLoss()),Digits()) : 0);
            double tp=(buy.TakeProfit()>0 ? NormalizeDouble(price+(buy.TakeProfit()-buy.PriceOpen()),Digits()) : 0);
            //--- Если рассчитанная цена ниже, чем дистанция StopLevel, отложенная от цены установки ордера Ask (соблюдена дистанция по StopLevel)
            if(price<tick.ask-stop_level) 
              {
               //--- Если рассчитанная цена выше, чем шаг трала, отложенный от цены установки ордера - модифицируем цену установки ордера
               if(price>buy.PriceOpen()+trailing_step)
                 {
                  trade.OrderModify(buy.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),buy.PriceStopLimit());
                 }
              }
           }
         //--- Если ордер установлен выше цены (BuyStop и BuyStopLimit), и его необходимо "опускать" за ценой
         else
           {
            //--- Рассчитываем новую цену установки ордера и стоп-уровни от новой цены
            double price=NormalizeDouble(tick.ask+trailing_stop,Digits());
            double sl=(buy.StopLoss()>0 ? NormalizeDouble(price-(buy.PriceOpen()-buy.StopLoss()),Digits()) : 0);
            double tp=(buy.TakeProfit()>0 ? NormalizeDouble(price+(buy.TakeProfit()-buy.PriceOpen()),Digits()) : 0);
            //--- Если рассчитанная цена выше, чем дистанция StopLevel, отложенная от цены установки ордера Ask (соблюдена дистанция по StopLevel)
            if(price>tick.ask+stop_level) 
              {
               //--- Если рассчитанная цена ниже, чем шаг трала, отложенный от цены установки ордера - модифицируем цену установки ордера
               if(price<buy.PriceOpen()-trailing_step)
                 {
                  trade.OrderModify(buy.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),(buy.PriceStopLimit()>0 ? price-distance_stoplimit*Point() : 0));
                 }
              }
           }
        }
     }
//--- Выбираем из списка только ордера в направлении Sell
   CArrayObj* list_sell=CSelect::ByOrderProperty(list,ORDER_PROP_DIRECTION,ORDER_TYPE_SELL,EQUAL);
   //--- Сортируем список по дистанции от цены в пунктах (по прибыли в пунктах)
   list_sell.Sort(SORT_BY_ORDER_PROFIT_PT);
   //--- Получаем индекс ордера в направлении Sell с наибольшей дистанцией
   int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_PT);
   if(index_sell>WRONG_VALUE)
     {
      COrder* sell=list_sell.At(index_sell);
      if(sell!=NULL)
        {
         //--- Если ордер установлен выше цены (SellLimit), и его необходимо "опускать" за ценой
         if(sell.TypeOrder()==ORDER_TYPE_SELL_LIMIT)
           {
            //--- Рассчитываем новую цену установки ордера и стоп-уровни от новой цены
            double price=NormalizeDouble(tick.bid+trailing_stop,Digits());
            double sl=(sell.StopLoss()>0 ? NormalizeDouble(price+(sell.StopLoss()-sell.PriceOpen()),Digits()) : 0);
            double tp=(sell.TakeProfit()>0 ? NormalizeDouble(price-(sell.PriceOpen()-sell.TakeProfit()),Digits()) : 0);
            //--- Если рассчитанная цена выше, чем дистанция StopLevel, отложенная от цены установки ордера Bid (соблюдена дистанция по StopLevel)
            if(price>tick.bid+stop_level) 
              {
               //--- Если рассчитанная цена ниже, чем шаг трала, отложенный от цены установки ордера - модифицируем цену установки ордера
               if(price<sell.PriceOpen()-trailing_step)
                 {
                  trade.OrderModify(sell.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),sell.PriceStopLimit());
                 }
              }
           }
         //--- Если ордер установлен ниже цены (SellStop и SellStopLimit), и его необходимо "поднимать" за ценой
         else
           {
            //--- Рассчитываем новую цену установки ордера и стоп-уровни от новой цены
            double price=NormalizeDouble(tick.bid-trailing_stop,Digits());
            double sl=(sell.StopLoss()>0 ? NormalizeDouble(price+(sell.StopLoss()-sell.PriceOpen()),Digits()) : 0);
            double tp=(sell.TakeProfit()>0 ? NormalizeDouble(price-(sell.PriceOpen()-sell.TakeProfit()),Digits()) : 0);
            //--- Если рассчитанная цена ниже, чем дистанция StopLevel, отложенная от цены установки ордера Bid (соблюдена дистанция по StopLevel)
            if(price<tick.bid-stop_level) 
              {
               //--- Если рассчитанная цена выше, чем шаг трала, отложенный от цены установки ордера - модифицируем цену установки ордера
               if(price>sell.PriceOpen()+trailing_step)
                 {
                  trade.OrderModify(sell.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),(sell.PriceStopLimit()>0 ? price+distance_stoplimit*Point() : 0));
                 }
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

Las funciones no contienen nada nuevo para nosotros, todas las acciones necesarias han sido descritas directamente en el código en sus comentarios, y el lector seguramente no tendrá preguntas al respecto al estudiar el código por sí mismo.

Dado que ahora tenemos tres botones más, en la función de creación del panel de botones se ha corregido el cálculo de las coordenadas de los botones (se puede ver en el listado final):
En el manejador OnTick(), implementamos la llamada de todas las funciones de trailing:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Инициализация последнего торгового события
   static ENUM_TRADE_EVENT last_event=WRONG_VALUE;
//--- Если работа в тестере
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer();
      PressButtonsControl();
     }
//--- Если последнее торговое событие изменилось
   if(engine.LastTradeEvent()!=last_event)
     {
      last_event=engine.LastTradeEvent();
     }
//--- Если установлен флаг трейлинга
   if(trailing_on)
     {
      TrailingPositions();
      TrailingOrders();   
     }
  }
//+------------------------------------------------------------------+

Aquí tenemos el listado completo del asesor de prueba:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart08.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
#include <Trade\Trade.mqh>
//--- enums
enum ENUM_BUTTONS
  {
   BUTT_BUY,
   BUTT_BUY_LIMIT,
   BUTT_BUY_STOP,
   BUTT_BUY_STOP_LIMIT,
   BUTT_CLOSE_BUY,
   BUTT_CLOSE_BUY2,
   BUTT_CLOSE_BUY_BY_SELL,
   BUTT_SELL,
   BUTT_SELL_LIMIT,
   BUTT_SELL_STOP,
   BUTT_SELL_STOP_LIMIT,
   BUTT_CLOSE_SELL,
   BUTT_CLOSE_SELL2,
   BUTT_CLOSE_SELL_BY_BUY,
   BUTT_DELETE_PENDING,
   BUTT_CLOSE_ALL,
   BUTT_PROFIT_WITHDRAWAL,
   BUTT_SET_STOP_LOSS,
   BUTT_SET_TAKE_PROFIT,
   BUTT_TRAILING_ALL
  };
#define TOTAL_BUTT   (20)
//--- structures
struct SDataButt
  {
   string      name;
   string      text;
  };
//--- input variables
input ulong    InpMagic             =  123;  // Magic number
input double   InpLots              =  0.1;  // Lots
input uint     InpStopLoss          =  50;   // StopLoss in points
input uint     InpTakeProfit        =  50;   // TakeProfit in points
input uint     InpDistance          =  50;   // Pending orders distance (points)
input uint     InpDistanceSL        =  50;   // StopLimit orders distance (points)
input uint     InpSlippage          =  0;    // Slippage in points
input double   InpWithdrawal        =  10;   // Withdrawal funds (in tester)
input uint     InpButtShiftX        =  40;   // Buttons X shift 
input uint     InpButtShiftY        =  10;   // Buttons Y shift 
input uint     InpTrailingStop      =  50;   // Trailing Stop (points)
input uint     InpTrailingStep      =  20;   // Trailing Step (points)
input uint     InpTrailingStart     =  0;    // Trailing Start (points)
input uint     InpStopLossModify    =  20;   // StopLoss for modification (points)
input uint     InpTakeProfitModify  =  60;   // TakeProfit for modification (points)
//--- global variables
CEngine        engine;
CTrade         trade;
SDataButt      butt_data[TOTAL_BUTT];
string         prefix;
double         lot;
double         withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal);
ulong          magic_number;
uint           stoploss;
uint           takeprofit;
uint           distance_pending;
uint           distance_stoplimit;
uint           slippage;
bool           trailing_on;
double         trailing_stop;
double         trailing_step;
uint           trailing_start;
uint           stoploss_to_modify;
uint           takeprofit_to_modify;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Вызов данной функции выводит в журнал список констант перечисления, 
//--- заданного в файле DELib.mqh в строках 22 и 25, для проверки корректности констант
   //EnumNumbersTest();
//--- check for undeleted objects
   if(IsPresentObects(prefix))
      ObjectsDeleteAll(0,prefix);
//--- set global variables
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0));
   magic_number=InpMagic;
   stoploss=InpStopLoss;
   takeprofit=InpTakeProfit;
   distance_pending=InpDistance;
   distance_stoplimit=InpDistanceSL;
   slippage=InpSlippage;
   trailing_stop=InpTrailingStop*Point();
   trailing_step=InpTrailingStep*Point();
   trailing_start=InpTrailingStart;
   stoploss_to_modify=InpStopLossModify;
   takeprofit_to_modify=InpTakeProfitModify;
//--- create buttons
   if(!CreateButtons(InpButtShiftX,InpButtShiftY))
      return INIT_FAILED;
//--- set button trailing
   ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on);
//--- setting trade parameters
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_NO);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- delete objects
   ObjectsDeleteAll(0,prefix);
   Comment("");
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Инициализация последнего торгового события
   static ENUM_TRADE_EVENT last_event=WRONG_VALUE;
//--- Если работа в тестере
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer();
      PressButtonsControl();
     }
//--- Если последнее торговое событие изменилось
   if(engine.LastTradeEvent()!=last_event)
     {
      last_event=engine.LastTradeEvent();
     }
//--- Если установлен флаг трейлинга
   if(trailing_on)
     {
      TrailingPositions();
      TrailingOrders();
     }
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   if(!MQLInfoInteger(MQL_TESTER))
      engine.OnTimer();
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   if(MQLInfoInteger(MQL_TESTER))
      return;
   if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,"BUTT_")>0)
     {
      PressButtonEvents(sparam);
     }
   if(id>=CHARTEVENT_CUSTOM)
     {
      ushort event=ushort(id-CHARTEVENT_CUSTOM);
      Print(DFUN,"id=",id,", event=",EnumToString((ENUM_TRADE_EVENT)event),", lparam=",lparam,", dparam=",DoubleToString(dparam,Digits()),", sparam=",sparam);
     } 
  }
//+------------------------------------------------------------------+
//| Возвращает флаг наличия объекта с префиксом                      |
//+------------------------------------------------------------------+
bool IsPresentObects(const string object_prefix)
  {
   for(int i=ObjectsTotal(0)-1;i>=0;i--)
      if(StringFind(ObjectName(0,i,0),object_prefix)>WRONG_VALUE)
         return true;
   return false;
  }
//+------------------------------------------------------------------+
//| Контроль состояния кнопок                                        |
//+------------------------------------------------------------------+
void PressButtonsControl(void)
  {
   int total=ObjectsTotal(0);
   for(int i=0;i<total;i++)
     {
      string obj_name=ObjectName(0,i);
      if(StringFind(obj_name,prefix+"BUTT_")<0)
         continue;
      PressButtonEvents(obj_name);
     }
  }
//+------------------------------------------------------------------+
//| Создаёт панель кнопок                                            |
//+------------------------------------------------------------------+
bool CreateButtons(const int shift_x=30,const int shift_y=0)
  {
   int h=18,w=84,offset=2;
   int cx=offset+shift_x,cy=offset+shift_y+(h+1)*(TOTAL_BUTT/2)+3*h+1;
   int x=cx,y=cy;
   int shift=0;
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      x=x+(i==7 ? w+2 : 0);
      if(i==TOTAL_BUTT-6) x=cx;
      y=(cy-(i-(i>6 ? 7 : 0))*(h+1));
      if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-6 ? w : w*2+2),h,butt_data[i].text,(i<4 ? clrGreen : i>6 && i<11 ? clrRed : clrBlue)))
        {
         Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text);
         return false;
        }
     }
   ChartRedraw(0);
   return true;
  }
//+------------------------------------------------------------------+
//| Создаёт кнопку                                                   |
//+------------------------------------------------------------------+
bool ButtonCreate(const string name,const int x,const int y,const int w,const int h,const string text,const color clr,const string font="Calibri",const int font_size=8)
  {
   if(ObjectFind(0,name)<0)
     {
      if(!ObjectCreate(0,name,OBJ_BUTTON,0,0,0)) 
        { 
         Print(DFUN,TextByLanguage("не удалось создать кнопку! Код ошибки=","Could not create button! Error code="),GetLastError()); 
         return false; 
        } 
      ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
      ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
      ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
      ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
      ObjectSetInteger(0,name,OBJPROP_XSIZE,w);
      ObjectSetInteger(0,name,OBJPROP_YSIZE,h);
      ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,font_size);
      ObjectSetString(0,name,OBJPROP_FONT,font);
      ObjectSetString(0,name,OBJPROP_TEXT,text);
      ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
      ObjectSetString(0,name,OBJPROP_TOOLTIP,"\n");
      ObjectSetInteger(0,name,OBJPROP_BORDER_COLOR,clrGray);
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//| Возвращает состояние кнопки                                      |
//+------------------------------------------------------------------+
bool ButtonState(const string name)
  {
   return (bool)ObjectGetInteger(0,name,OBJPROP_STATE);
  }
//+------------------------------------------------------------------+
//| Устанавливает состояние кнопки                                   |
//+------------------------------------------------------------------+
void ButtonState(const string name,const bool state)
  {
   ObjectSetInteger(0,name,OBJPROP_STATE,state);
   if(name==butt_data[TOTAL_BUTT-1].name)
     {
      if(state)
         ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'220,255,240');
      else
         ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'240,240,240');
     }
  }
//+------------------------------------------------------------------+
//| Преобразует перечисление в текст кнопки                          |
//+------------------------------------------------------------------+
string EnumToButtText(const ENUM_BUTTONS member)
  {
   string txt=StringSubstr(EnumToString(member),5);
   StringToLower(txt);
   StringReplace(txt,"set_take_profit","Set TakeProfit");
   StringReplace(txt,"set_stop_loss","Set StopLoss");
   StringReplace(txt,"trailing_all","Trailing All");
   StringReplace(txt,"buy","Buy");
   StringReplace(txt,"sell","Sell");
   StringReplace(txt,"_limit"," Limit");
   StringReplace(txt,"_stop"," Stop");
   StringReplace(txt,"close_","Close ");
   StringReplace(txt,"2"," 1/2");
   StringReplace(txt,"_by_"," by ");
   StringReplace(txt,"profit_","Profit ");
   StringReplace(txt,"delete_","Delete ");
   return txt;
  }
//+------------------------------------------------------------------+
//| Обработка нажатий кнопок                                         |
//+------------------------------------------------------------------+
void PressButtonEvents(const string button_name)
  {
   //--- Преобразуем имя кнопки в её строковый идентификатор
   string button=StringSubstr(button_name,StringLen(prefix));
   //--- Если кнопка в нажатом состоянии
   if(ButtonState(button_name))
     {
      //--- Если нажата кнопка BUTT_BUY: Открыть позицию Buy
      if(button==EnumToString(BUTT_BUY))
        {
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit);
         //--- Открываем позицию Buy
         trade.Buy(NormalizeLot(Symbol(),lot),Symbol(),0,sl,tp);
        }
      //--- Если нажата кнопка BUTT_BUY_LIMIT: Выставить BuyLimit
      else if(button==EnumToString(BUTT_BUY_LIMIT))
        {
         //--- Получаем корректную цену установки ордера относительно уровня StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending);
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit);
         //--- Устанавливаем ордер BuyLimit
         trade.BuyLimit(lot,price_set,Symbol(),sl,tp);
        }
      //--- Если нажата кнопка BUTT_BUY_STOP: Выставить BuyStop
      else if(button==EnumToString(BUTT_BUY_STOP))
        {
         //--- Получаем корректную цену установки ордера относительно уровня StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set,takeprofit);
         //--- Устанавливаем ордер BuyStop
         trade.BuyStop(lot,price_set,Symbol(),sl,tp);
        }
      //--- Если нажата кнопка BUTT_BUY_STOP_LIMIT: Выставить BuyStopLimit
      else if(button==EnumToString(BUTT_BUY_STOP_LIMIT))
        {
         //--- Получаем корректную цену установки ордера BuyStop относительно уровня StopLevel
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- Рассчитываем цену установки ордера BuyLimit относительно уровня установки BuyStop с учётом уровня StopLevel
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_stoplimit,price_set_stop);
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,takeprofit);
         //--- Устанавливаем ордер BuyStopLimit
         trade.OrderOpen(Symbol(),ORDER_TYPE_BUY_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
        }
      //--- Если нажата кнопка BUTT_SELL: Открыть позицию Sell
      else if(button==EnumToString(BUTT_SELL))
        {
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL,0,takeprofit);
         //--- Открываем позицию Sell
         trade.Sell(lot,Symbol(),0,sl,tp);
        }
      //--- Если нажата кнопка BUTT_SELL_LIMIT: Выставить SellLimit
      else if(button==EnumToString(BUTT_SELL_LIMIT))
        {
         //--- Получаем корректную цену установки ордера относительно уровня StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_pending);
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,takeprofit);
         //--- Устанавливаем ордер SellLimit
         trade.SellLimit(lot,price_set,Symbol(),sl,tp);
        }
      //--- Если нажата кнопка BUTT_SELL_STOP: Выставить SellStop
      else if(button==EnumToString(BUTT_SELL_STOP))
        {
         //--- Получаем корректную цену установки ордера относительно уровня StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set,takeprofit);
         //--- Устанавливаем ордер SellStop
         trade.SellStop(lot,price_set,Symbol(),sl,tp);
        }
      //--- Если нажата кнопка BUTT_SELL_STOP_LIMIT: Выставить SellStopLimit
      else if(button==EnumToString(BUTT_SELL_STOP_LIMIT))
        {
         //--- Получаем корректную цену установки ордера SellStop относительно уровня StopLevel
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- Рассчитываем цену установки ордера SellLimit относительно уровня установки SellStop с учётом уровня StopLevel
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_stoplimit,price_set_stop);
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,takeprofit);
         //--- Устанавливаем ордер SellStopLimit
         trade.OrderOpen(Symbol(),ORDER_TYPE_SELL_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
        }
      //--- Если нажата кнопка BUTT_CLOSE_BUY: Закрыть Buy с максимальной прибылью
      else if(button==EnumToString(BUTT_CLOSE_BUY))
        {
         //--- Получаем список всех открытых позиций
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Выбираем из списка только позиции Buy
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Сортируем список по прибыли с учётом комиссии и свопа
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Получаем индекс позиции Buy с наибольшей прибылью
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Получаем тикет позиции Buy и закрываем позицию по тикету
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- Если нажата кнопка BUTT_CLOSE_BUY2: Закрыть половину Buy с максимальной прибылью
      else if(button==EnumToString(BUTT_CLOSE_BUY2))
        {
         //--- Получаем список всех открытых позиций
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Выбираем из списка только позиции Buy
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Сортируем список по прибыли с учётом комиссии и свопа
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Получаем индекс позиции Buy с наибольшей прибылью
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Рассчитываем закрываемый объём и закрываем половину позиции Buy по тикету
               if(engine.IsHedge())
                  trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0));
               else
                  trade.Sell(NormalizeLot(position.Symbol(),position.Volume()/2.0));
              }
           }
        }
      //--- Если нажата кнопка BUTT_CLOSE_BUY_BY_SELL: Закрыть Buy с максимальной прибылью встречной Sell с максимальной прибылью
      else if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL))
        {
         //--- Получаем список всех открытых позиций
         CArrayObj* list_buy=engine.GetListMarketPosition();
         //--- Выбираем из списка только позиции Buy
         list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Сортируем список по прибыли с учётом комиссии и свопа
         list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Получаем индекс позиции Buy с наибольшей прибылью
         int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
         //--- Получаем список всех открытых позиций
         CArrayObj* list_sell=engine.GetListMarketPosition();
         //--- Выбираем из списка только позиции Sell
         list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Сортируем список по прибыли с учётом комиссии и свопа
         list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Получаем индекс позиции Sell с наибольшей прибылью
         int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
         if(index_buy>WRONG_VALUE && index_sell>WRONG_VALUE)
           {
            //--- Выбираем позицию Buy с наибольшей прибылью
            COrder* position_buy=list_buy.At(index_buy);
            //--- Выбираем позицию Sell с наибольшей прибылью
            COrder* position_sell=list_sell.At(index_sell);
            if(position_buy!=NULL && position_sell!=NULL)
              {
               //--- Закрываем позицию Buy встречной позицией Sell
               trade.PositionCloseBy(position_buy.Ticket(),position_sell.Ticket());
              }
           }
        }
      //--- Если нажата кнопка BUTT_CLOSE_SELL: Закрыть Sell с максимальной прибылью
      else if(button==EnumToString(BUTT_CLOSE_SELL))
        {
         //--- Получаем список всех открытых позиций
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Выбираем из списка только позиции Sell
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Сортируем список по прибыли с учётом комиссии и свопа
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Получаем индекс позиции Sell с наибольшей прибылью
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Получаем тикет позиции Sell и закрываем позицию по тикету
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- Если нажата кнопка BUTT_CLOSE_SELL2: Закрыть половину Sell с максимальной прибылью
      else if(button==EnumToString(BUTT_CLOSE_SELL2))
        {
         //--- Получаем список всех открытых позиций
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Выбираем из списка только позиции Sell
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Сортируем список по прибыли с учётом комиссии и свопа
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Получаем индекс позиции Sell с наибольшей прибылью
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Рассчитываем закрываемый объём и закрываем половину позиции Sell по тикету
               if(engine.IsHedge())
                  trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0));
               else
                  trade.Buy(NormalizeLot(position.Symbol(),position.Volume()/2.0));
              }
           }
        }
      //--- Если нажата кнопка BUTT_CLOSE_SELL_BY_BUY: Закрыть Sell с максимальной прибылью встречной Buy с максимальной прибылью
      else if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY))
        {
         //--- Получаем список всех открытых позиций
         CArrayObj* list_sell=engine.GetListMarketPosition();
         //--- Выбираем из списка только позиции Sell
         list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Сортируем список по прибыли с учётом комиссии и свопа
         list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Получаем индекс позиции Sell с наибольшей прибылью
         int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
         //--- Получаем список всех открытых позиций
         CArrayObj* list_buy=engine.GetListMarketPosition();
         //--- Выбираем из списка только позиции Buy
         list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Сортируем список по прибыли с учётом комиссии и свопа
         list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Получаем индекс позиции Buy с наибольшей прибылью
         int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
         if(index_sell>WRONG_VALUE && index_buy>WRONG_VALUE)
           {
            //--- Выбираем позицию Sell с наибольшей прибылью
            COrder* position_sell=list_sell.At(index_sell);
            //--- Выбираем позицию Buy с наибольшей прибылью
            COrder* position_buy=list_buy.At(index_buy);
            if(position_sell!=NULL && position_buy!=NULL)
              {
               //--- Закрываем позицию Sell встречной позицией Buy
               trade.PositionCloseBy(position_sell.Ticket(),position_buy.Ticket());
              }
           }
        }
      //--- Если нажата кнопка BUTT_CLOSE_ALL: Закрыть все позиции, начиная от позиции с наименьшим профитом
      else if(button==EnumToString(BUTT_CLOSE_ALL))
        {
         //--- Получаем список всех открытых позиций
         CArrayObj* list=engine.GetListMarketPosition();
         if(list!=NULL)
           {
            //--- Сортируем список по прибыли с учётом комиссии и свопа
            list.Sort(SORT_BY_ORDER_PROFIT_FULL);
            int total=list.Total();
            //--- В цикле от позиции с наименьшей прибылью
            for(int i=0;i<total;i++)
              {
               COrder* position=list.At(i);
               if(position==NULL)
                  continue;
               //--- закрываем каждую позицию по её тикету
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- Если нажата кнопка BUTT_DELETE_PENDING: Удалить первый отложенный ордер
      else if(button==EnumToString(BUTT_DELETE_PENDING))
        {
         //--- Получаем список всех ордеров
         CArrayObj* list=engine.GetListMarketPendings();
         if(list!=NULL)
           {
            //--- Сортируем список по времени установки
            list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
            int total=list.Total();
            //--- В цикле от позиции с наибольшим временем
            for(int i=total-1;i>=0;i--)
              {
               COrder* order=list.At(i);
               if(order==NULL)
                  continue;
               //--- удаяем ордер по его тикету
               trade.OrderDelete(order.Ticket());
              }
           }
        }
      //--- Если нажата кнопка BUTT_PROFIT_WITHDRAWAL: Вывести средства со счёта
      if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL))
        {
         //--- Если программа запущена в тестере
         if(MQLInfoInteger(MQL_TESTER))
           {
            //--- Эмулируем вывод средств
            TesterWithdrawal(withdrawal);
           }
        }
      //--- Если нажата кнопка BUTT_SET_STOP_LOSS: Установить StopLoss всем ордерам и позициям, где его нету
      if(button==EnumToString(BUTT_SET_STOP_LOSS))
        {
         SetStopLoss();
        }
      //--- Если нажата кнопка BUTT_SET_TAKE_PROFIT: Установить TakeProfit всем ордерам и позициям, где его нету
      if(button==EnumToString(BUTT_SET_TAKE_PROFIT))
        {
         SetTakeProfit();
        }
      //--- Подождём 1/10 секунды
      Sleep(100);
      //--- "Отожмём" кнопку (если это не кнопка трейлинга)
      if(button!=EnumToString(BUTT_TRAILING_ALL))
         ButtonState(button_name,false);
      //--- Если нажата кнопка BUTT_TRAILING_ALL
      else
        {
         //--- Поставим цвет активной кнопки
         ButtonState(button_name,true);
         trailing_on=true;
        }
      //--- перерисуем чарт
      ChartRedraw();
     }
   //--- Вернём цвет неактивной кнопки (если это кнопка трейлинга)
   else if(button==EnumToString(BUTT_TRAILING_ALL))
     {
      ButtonState(button_name,false);
      trailing_on=false;
      //--- перерисуем чарт
      ChartRedraw();
     }
  }
//+------------------------------------------------------------------+
//| Установка StopLoss всем ордерам и позициям                       |
//+------------------------------------------------------------------+
void SetStopLoss(void)
  {
   if(stoploss_to_modify==0)
      return;
//--- Установка StopLoss всем позициям, где его нету
   CArrayObj* list=engine.GetListMarketPosition();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_SL,0,EQUAL);
   if(list==NULL)
      return;
   int total=list.Total();
   for(int i=total-1;i>=0;i--)
     {
      COrder* position=list.At(i);
      if(position==NULL)
         continue;
      double sl=CorrectStopLoss(position.Symbol(),position.TypeByDirection(),0,stoploss_to_modify);
      trade.PositionModify(position.Ticket(),sl,position.TakeProfit());
     }
//--- Установка StopLoss всем отложенным ордерам, где его нету
   list=engine.GetListMarketPendings();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_SL,0,EQUAL);
   if(list==NULL)
      return;
   total=list.Total();
   for(int i=total-1;i>=0;i--)
     {
      COrder* order=list.At(i);
      if(order==NULL)
         continue;
      double sl=CorrectStopLoss(order.Symbol(),(ENUM_ORDER_TYPE)order.TypeOrder(),order.PriceOpen(),stoploss_to_modify);
      trade.OrderModify(order.Ticket(),order.PriceOpen(),sl,order.TakeProfit(),trade.RequestTypeTime(),trade.RequestExpiration(),order.PriceStopLimit());
     }
  }
//+------------------------------------------------------------------+
//| Установка TakeProfit всем ордерам и позициям                     |
//+------------------------------------------------------------------+
void SetTakeProfit(void)
  {
   if(takeprofit_to_modify==0)
      return;
//--- Установка TakeProfit всем позициям, где его нету
   CArrayObj* list=engine.GetListMarketPosition();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TP,0,EQUAL);
   if(list==NULL)
      return;
   int total=list.Total();
   for(int i=total-1;i>=0;i--)
     {
      COrder* position=list.At(i);
      if(position==NULL)
         continue;
      double tp=CorrectTakeProfit(position.Symbol(),position.TypeByDirection(),0,takeprofit_to_modify);
      trade.PositionModify(position.Ticket(),position.StopLoss(),tp);
     }
//--- Установка TakeProfit всем отложенным ордерам, где его нету
   list=engine.GetListMarketPendings();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TP,0,EQUAL);
   if(list==NULL)
      return;
   total=list.Total();
   for(int i=total-1;i>=0;i--)
     {
      COrder* order=list.At(i);
      if(order==NULL)
         continue;
      double tp=CorrectTakeProfit(order.Symbol(),(ENUM_ORDER_TYPE)order.TypeOrder(),order.PriceOpen(),takeprofit_to_modify);
      trade.OrderModify(order.Ticket(),order.PriceOpen(),order.StopLoss(),tp,trade.RequestTypeTime(),trade.RequestExpiration(),order.PriceStopLimit());
     }
  }
//+------------------------------------------------------------------+
//| Трал стопа позиции с наибольшей прибылью                         |
//+------------------------------------------------------------------+
void TrailingPositions(void)
  {
   MqlTick tick;
   if(!SymbolInfoTick(Symbol(),tick))
      return;
   double stop_level=StopLevel(Symbol(),2)*Point();
   //--- Получаем список всех открытых позиций
   CArrayObj* list=engine.GetListMarketPosition();
   //--- Выбираем из списка только позиции Buy
   CArrayObj* list_buy=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
   //--- Сортируем список по прибыли с учётом комиссии и свопа
   list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
   //--- Получаем индекс позиции Buy с наибольшей прибылью
   int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
   if(index_buy>WRONG_VALUE)
     {
      COrder* buy=list_buy.At(index_buy);
      if(buy!=NULL)
        {
         //--- Рассчитываем новый StopLoss
         double sl=NormalizeDouble(tick.bid-trailing_stop,Digits());
         //--- Если цена и отложенный от неё уровень StopLevel выше нового StopLoss (соблюдена дистанция по StopLevel)
         if(tick.bid-stop_level>sl) 
           {
            //--- Если новый уровень StopLoss выше, чем шаг трала, отложенный от текущего StopLoss
            if(buy.StopLoss()+trailing_step<sl)
              {
               //--- Если тралим при любой прибыли или прибыль позиции в пунктах больше значения начала трейлинга - модифицируем StopLoss
               if(trailing_start==0 || buy.ProfitInPoints()>(int)trailing_start)
                  trade.PositionModify(buy.Ticket(),sl,buy.TakeProfit());
              }
           }
        }
     }
   //--- Выбираем из списка только позиции Sell
   CArrayObj* list_sell=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
   //--- Сортируем список по прибыли с учётом комиссии и свопа
   list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
   //--- Получаем индекс позиции Sell с наибольшей прибылью
   int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
   if(index_sell>WRONG_VALUE)
     {
      COrder* sell=list_sell.At(index_sell);
      if(sell!=NULL)
        {
         //--- Рассчитываем новый StopLoss
         double sl=NormalizeDouble(tick.ask+trailing_stop,Digits());
         //--- Если цена и отложенный от неё уровень StopLevel ниже нового StopLoss (соблюдена дистанция по StopLevel)
         if(tick.ask+stop_level<sl) 
           {
            //--- Если новый уровень StopLoss ниже, чем шаг трала, отложенный от текущего StopLoss или у позиции ещё не установлен StopLoss
            if(sell.StopLoss()-trailing_step>sl || sell.StopLoss()==0)
              {
               //--- Если тралим при любой прибыли или прибыль позиции в пунктах больше значения начала трейлинга - модифицируем StopLoss
               if(trailing_start==0 || sell.ProfitInPoints()>(int)trailing_start)
                  trade.PositionModify(sell.Ticket(),sl,sell.TakeProfit());
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+
//| Трал самых дальних отложенных ордеров                            |
//+------------------------------------------------------------------+
void TrailingOrders(void)
  {
   MqlTick tick;
   if(!SymbolInfoTick(Symbol(),tick))
      return;
   double stop_level=StopLevel(Symbol(),2)*Point();
//--- Получаем список всех установленных ордеров
   CArrayObj* list=engine.GetListMarketPendings();
//--- Выбираем из списка только ордера в направлении Buy
   CArrayObj* list_buy=CSelect::ByOrderProperty(list,ORDER_PROP_DIRECTION,ORDER_TYPE_BUY,EQUAL);
   //--- Сортируем список по дистанции от цены в пунктах (по прибыли в пунктах)
   list_buy.Sort(SORT_BY_ORDER_PROFIT_PT);
   //--- Получаем индекс ордера в направлении Buy с наибольшей дистанцией
   int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_PT);
   if(index_buy>WRONG_VALUE)
     {
      COrder* buy=list_buy.At(index_buy);
      if(buy!=NULL)
        {
         //--- Если ордер установлен ниже цены (BuyLimit), и его необходимо "поднимать" за ценой
         if(buy.TypeOrder()==ORDER_TYPE_BUY_LIMIT)
           {
            //--- Рассчитываем новую цену установки ордера и стоп-уровни от новой цены
            double price=NormalizeDouble(tick.ask-trailing_stop,Digits());
            double sl=(buy.StopLoss()>0 ? NormalizeDouble(price-(buy.PriceOpen()-buy.StopLoss()),Digits()) : 0);
            double tp=(buy.TakeProfit()>0 ? NormalizeDouble(price+(buy.TakeProfit()-buy.PriceOpen()),Digits()) : 0);
            //--- Если рассчитанная цена ниже, чем дистанция StopLevel, отложенная от цены установки ордера Ask (соблюдена дистанция по StopLevel)
            if(price<tick.ask-stop_level) 
              {
               //--- Если рассчитанная цена выше, чем шаг трала, отложенный от цены установки ордера - модифицируем цену установки ордера
               if(price>buy.PriceOpen()+trailing_step)
                 {
                  trade.OrderModify(buy.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),buy.PriceStopLimit());
                 }
              }
           }
         //--- Если ордер установлен выше цены (BuyStop и BuyStopLimit), и его необходимо "опускать" за ценой
         else
           {
            //--- Рассчитываем новую цену установки ордера и стоп-уровни от новой цены
            double price=NormalizeDouble(tick.ask+trailing_stop,Digits());
            double sl=(buy.StopLoss()>0 ? NormalizeDouble(price-(buy.PriceOpen()-buy.StopLoss()),Digits()) : 0);
            double tp=(buy.TakeProfit()>0 ? NormalizeDouble(price+(buy.TakeProfit()-buy.PriceOpen()),Digits()) : 0);
            //--- Если рассчитанная цена выше, чем дистанция StopLevel, отложенная от цены установки ордера Ask (соблюдена дистанция по StopLevel)
            if(price>tick.ask+stop_level) 
              {
               //--- Если рассчитанная цена ниже, чем шаг трала, отложенный от цены установки ордера - модифицируем цену установки ордера
               if(price<buy.PriceOpen()-trailing_step)
                 {
                  trade.OrderModify(buy.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),(buy.PriceStopLimit()>0 ? price-distance_stoplimit*Point() : 0));
                 }
              }
           }
        }
     }
//--- Выбираем из списка только ордера в направлении Sell
   CArrayObj* list_sell=CSelect::ByOrderProperty(list,ORDER_PROP_DIRECTION,ORDER_TYPE_SELL,EQUAL);
   //--- Сортируем список по дистанции от цены в пунктах (по прибыли в пунктах)
   list_sell.Sort(SORT_BY_ORDER_PROFIT_PT);
   //--- Получаем индекс ордера в направлении Sell с наибольшей дистанцией
   int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_PT);
   if(index_sell>WRONG_VALUE)
     {
      COrder* sell=list_sell.At(index_sell);
      if(sell!=NULL)
        {
         //--- Если ордер установлен выше цены (SellLimit), и его необходимо "опускать" за ценой
         if(sell.TypeOrder()==ORDER_TYPE_SELL_LIMIT)
           {
            //--- Рассчитываем новую цену установки ордера и стоп-уровни от новой цены
            double price=NormalizeDouble(tick.bid+trailing_stop,Digits());
            double sl=(sell.StopLoss()>0 ? NormalizeDouble(price+(sell.StopLoss()-sell.PriceOpen()),Digits()) : 0);
            double tp=(sell.TakeProfit()>0 ? NormalizeDouble(price-(sell.PriceOpen()-sell.TakeProfit()),Digits()) : 0);
            //--- Если рассчитанная цена выше, чем дистанция StopLevel, отложенная от цены установки ордера Bid (соблюдена дистанция по StopLevel)
            if(price>tick.bid+stop_level) 
              {
               //--- Если рассчитанная цена ниже, чем шаг трала, отложенный от цены установки ордера - модифицируем цену установки ордера
               if(price<sell.PriceOpen()-trailing_step)
                 {
                  trade.OrderModify(sell.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),sell.PriceStopLimit());
                 }
              }
           }
         //--- Если ордер установлен ниже цены (SellStop и SellStopLimit), и его необходимо "поднимать" за ценой
         else
           {
            //--- Рассчитываем новую цену установки ордера и стоп-уровни от новой цены
            double price=NormalizeDouble(tick.bid-trailing_stop,Digits());
            double sl=(sell.StopLoss()>0 ? NormalizeDouble(price+(sell.StopLoss()-sell.PriceOpen()),Digits()) : 0);
            double tp=(sell.TakeProfit()>0 ? NormalizeDouble(price-(sell.PriceOpen()-sell.TakeProfit()),Digits()) : 0);
            //--- Если рассчитанная цена ниже, чем дистанция StopLevel, отложенная от цены установки ордера Bid (соблюдена дистанция по StopLevel)
            if(price<tick.bid-stop_level) 
              {
               //--- Если рассчитанная цена выше, чем шаг трала, отложенный от цены установки ордера - модифицируем цену установки ордера
               if(price>sell.PriceOpen()+trailing_step)
                 {
                  trade.OrderModify(sell.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),(sell.PriceStopLimit()>0 ? price+distance_stoplimit*Point() : 0));
                 }
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

Vamos a compilar el asesor.
Establecemos en sus parámetros los valores StopLoss in points y TaleProfit in points iguales a cero, para abrir las posiciones y esteblecer las órdenes pendientes desde el inicio sin niveles stop; asimismo, establecemos en los parámetros StopLoss for modification (points) y TakeProfit for modification (points) los valores 20 y 60 respectivamente (valores por defecto), estos niveles de StopLoss y TakeProfit serán establecidos al pulsar los botones.
Iniciamos el asesor en el simulador y colocamos las órdenes pendientes. A continuación, pulsamos por turno los botones para establecer el StopLoss y el TakeProfit: los niveles serán colocados, y las entradas correspondientes sobre ello se mostrarán en el diario. Después, activamos el trailing y observamos las órdenes. Estas se desplazan tras el precio, y en el diario se muestran las entradas correspondientes a estos eventos. Los niveles de StopLoss para las posiciones aparecidas como resultado de la activación de las órdenes seguirán al precio, y en el diario se mostrarán las entradas correspondientes a estos eventos.

Compensación:


Cobertura:


¿Qué es lo próximo?

En los próximos artículos, comenzaremos a introducir correcciones en la biblioteca para posibilitar la compatibilidad con MQL4 y, naturalmente, continuaremos desarrollando la biblioteca: aún nos queda mucho material interesante por explorar.

Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y los archivos del asesor de prueba. Puede descargarlo todo y ponerlo a prueba por sí mismo.
Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.

Volver al contenido

Artículos de esta serie:

Parte 1: Concepto y organización de datos.
Parte 2: Colecciones de las órdenes y transacciones históricas.
Parte 3: Colección de órdenes y posiciones de mercado, organización de la búsqueda.
Parte 4: Eventos comerciales. Concepto.
Parte 5: Clases y colección de eventos comerciales. Envío de eventos al programa.
Parte 6. Eventos en la cuenta con compensación.
Parte 7. Eventos de activación de órdenes StopLimit, preparación de la funcionalidad para el registro de los eventos de modificación de órdenes y posiciones.