Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte V): Clases y colección de eventos comerciales, envío de eventos al programa

19 julio 2019, 16:06
Artyom Trishkin
0
321

Contenido

Reorganizando la estructura de la biblioteca

En anteriores artículos comenzamos a crear una gran biblioteca multiplataforma cuyo objetivo es simplificar la escritura de programas para las plataformas MetaTrader 5 y MetaTrader 4. En la cuarta parte, hemos puesto a prueba el seguimiento de eventos comerciales en la cuenta. En esta parte, vamos a crear las clases de los eventos comerciales y a colocarlas en la colección de eventos desde la que serán enviadas al objeto básico de la biblioteca Engine y al gráfico del programa de control.

Pero, en primer lugar, vamos a preparar el terreno para el desarrollo posterior de la estructura de la biblioteca.

Dado que existirán multitud de diferentes colecciones, y a cada colección le corresponderán sus propias colecciones de objetos, inherentes solo a ellas, nos parece adecuado guardar los objetos para cada colección en su propia subcarpeta.

Para ello, en el directorio raíz de la biblioteca DoEasy, en su subdirectorio Objects, vamos a crear dos carpetas: Orders y Events.
Colocamos en la carpeta Orders todas las clases de la carpeta Objects que hemos creado anteriormente. Asimismo, vamos a necesitar la carpeta Events para guardar las clases de objetos de evento que crearemos hoy.
Además, vamos a pasar el archivo Select.mqh de la carpeta Collections a la carpeta Services, ya que planeamos más adelante conectar a dicho archivo otra clase de servicio con los métodos de acceso rápido a cualquier propiedad de cualquier objeto de las colecciones disponibles y futuras, y esto significa que su lugar precisamente se encontrará en la carpeta de clases de servicio.

Puesto que, después de trasladar las clases de los objetos de órdenes al nuevo directorio y trasladar el archivo de la clase CSelect, también han cambiado sus direcciones relativas, necesarias para compilar sus archivos, vamos a iterar por la lista de las clases desplazadas y cambiar en ellas las direcciones de los archivos de inclusión:

En el archivo Order.mqh, cambiamos la ruta de conexión del archivo de las funciones de servicio de

#include "..\Services\DELib.mqh" 

a

#include "..\..\Services\DELib.mqh"

En el archivo HistoryCollection.mqh, cambiamos las rutas

#include "Select.mqh"
#include "..\Objects\HistoryOrder.mqh"
#include "..\Objects\HistoryPending.mqh"
#include "..\Objects\HistoryDeal.mqh"

 a

#include "..\Services\Select.mqh"
#include "..\Objects\Orders\HistoryOrder.mqh"
#include "..\Objects\Orders\HistoryPending.mqh"
#include "..\Objects\Orders\HistoryDeal.mqh"

En el archivo MarketCollection.mqh, cambiamos las rutas

#include "Select.mqh"
#include "..\Objects\MarketOrder.mqh"
#include "..\Objects\MarketPending.mqh"
#include "..\Objects\MarketPosition.mqh"

a

#include "..\Services\Select.mqh"
#include "..\Objects\Orders\MarketOrder.mqh"
#include "..\Objects\Orders\MarketPending.mqh"
#include "..\Objects\Orders\MarketPosition.mqh"

Ahora, todo debería compilarse sin errores.

Ya que planeamos crear muchas colecciones, sería deseable tener la posibilidad de diferenciar de alguna forma la pertenencia de una lista de colecciones creada sobre la base de CArrayObj, para poder identificar dicha lista. Y es que en cada colección existe un método que retorna el puntero a la lista completa de la colección. Si en algún lugar hay un método que reciba una lista determinada de una colección determinada, entonces, dentro de este método, deberemos poder identificar con exactitud la lista transmitida al método según su pertenencia a una colección u otra, para evitar transmitir al método una bandera adicional que indique el tipo de la lista pasada al método.

Por fortuna, la biblioteca estándar ya ofrece la herramienta necesaria para ello, el método virtual Type(), que retorna la ID del objeto.Bien, para CObject, se retorna el identificador 0, y para CArrayObj, el identificador 0x7778. Puesto que el método se ha hecho virtual, esto da la posibilidad a los herederos de la clase de tener un método propio que retorne su propio identificador.

Haremos de esta forma: ya sabemos que todas las listas de colecciones las hemos creado usando como base la clase CArrayObj, así que vamos a crear nuestras propia clase CListObj, que será heredera de la clase CArrayObj, y en su método virtual Type(), se retornará el identificador de esta lista. El propio identificador lo estableceremos mediante la constante en el constructor de la clase. De esta forma, continuaremos accediendo a nuestras colecciones como al objeto CArrayObj, pero ahora, cada lista tendrá su propio identificador concreto.

En primer lugar, registraremos los identificadores de las listas de colección que necesitamos en el archivo Defines.mqh. Y de paso, añadiremos la macro "descripción de la función con el número de línea del error", para mostrar los mensajes de depuración con la indicación de la línea desde la que se envía este mensaje, y así localizar el lugar problemático en el código al realizar la depuración:

//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+
//--- Describe the function with the error line number
#define DFUN_ERR_LINE            (__FUNCTION__+(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian" ? ", Page " : ", Line ")+(string)__LINE__+": ")
#define DFUN                     (__FUNCTION__+": ")        // "Function description"
#define COUNTRY_LANG             ("Russian")                // Country language
#define END_TIME                 (D'31.12.3000 23:59:59')   // End date for requesting account history data
#define TIMER_FREQUENCY          (16)                       // Minimal frequency of the library timer in milliseconds
#define COLLECTION_PAUSE         (250)                      // Orders and deals collection timer pause in milliseconds
#define COLLECTION_COUNTER_STEP  (16)                       // Increment of the orders and deals collection timer counter
#define COLLECTION_COUNTER_ID    (1)                        // Orders and deals collection timer counter ID
#define COLLECTION_HISTORY_ID    (0x7778+1)                 // Historical collection list ID
#define COLLECTION_MARKET_ID     (0x7778+2)                 // Market collection list ID
#define COLLECTION_EVENTS_ID     (0x7778+3)                 // Events collection list ID
//+------------------------------------------------------------------+

Ahora, en la carpeta Collections, creamos la clase CListObj en el archivo ListObj.mqh. La clase básica para ella, será la clase CArrayObj:

//+------------------------------------------------------------------+
//|                                                      ListObj.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
//+------------------------------------------------------------------+
//| Collection lists class                                           |
//+------------------------------------------------------------------+
class CListObj : public CArrayObj
  {
private:
   int               m_type;                    // List type
public:
   void              Type(const int type)       { this.m_type=type;     }
   virtual int       Type(void)           const { return(this.m_type);  }
                     CListObj()                 { this.m_type=0x7778;   }
  };
//+------------------------------------------------------------------+

Todo lo que tenemos que hacer es declarar el miembro de clase que contiene el tipo de lista, añadir el método para determinar el tipo de lista y el método virtual de retorno del tipo de lista.
En el constructor de la clase, establecemos el tipo de lista, por defecto, es igual al tipo de lista de CArrajObj. A continuación, podremos reasignarlo desde el programa que realiza la llamada, con la ayuda del método Type().

Ahora necesitamos heredar todas las listas de las colecciones de esta clase, para poder así asignar una ID de búsqueda por separado a cada lista. Esa identificación nos permitirá rastrear una propiedad de la lista en cualquier método al que se pase la lista.

Abrimos el archivo HistoryCollection.mqh y añadimos la conexión de la clase CListObj y heredamos la clase CHistoryCollection de la clase CListObj.

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Orders\HistoryOrder.mqh"
#include "..\Objects\Orders\HistoryPending.mqh"
#include "..\Objects\Orders\HistoryDeal.mqh"
//+------------------------------------------------------------------+
//| Collection of historical orders and deals                        |
//+------------------------------------------------------------------+
class CHistoryCollection : public CListObj
  {

En el constructor de la clase, establecemos la lista de colección histórica de su tipo, que indicamos en el archivo Defines.mqh como COLLECTION_HISTORY_ID:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CHistoryCollection::CHistoryCollection(void) : m_index_deal(0),m_delta_deal(0),m_index_order(0),m_delta_order(0),m_is_trade_event(false)
  {
   this.m_list_all_orders.Sort(#ifdef __MQL5__ SORT_BY_ORDER_TIME_OPEN #else SORT_BY_ORDER_TIME_CLOSE #endif );
   this.m_list_all_orders.Clear();
   this.m_list_all_orders.Type(COLLECTION_HISTORY_ID);
  }
//+------------------------------------------------------------------+

Realizamos las mismas acciones con la clase CMarketCollection en el archivo MarketCollection.mqh:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Orders\MarketOrder.mqh"
#include "..\Objects\Orders\MarketPending.mqh"
#include "..\Objects\Orders\MarketPosition.mqh"
//+------------------------------------------------------------------+
//| Collection of market orders and positions                        |
//+------------------------------------------------------------------+
class CMarketCollection : public CListObj
  {

En el constructor de la clase, establecemos la lista de colección de mercado de su tipo, que indicamos en el archivo Defines.mqh como COLLECTION_HISTORY_ID:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CMarketCollection::CMarketCollection(void) : m_is_trade_event(false),m_is_change_volume(false),m_change_volume_value(0)
  {
   this.m_list_all_orders.Sort(SORT_BY_ORDER_TIME_OPEN);
   this.m_list_all_orders.Clear();
   ::ZeroMemory(this.m_struct_prev_market);
   this.m_struct_prev_market.hash_sum_acc=WRONG_VALUE;
   this.m_list_all_orders.Type(COLLECTION_MARKET_ID);
  }
//+------------------------------------------------------------------+

Ahora, las lista de colección disponen cada una de su identificador, que puede ayudarnos posteriormente a reconocer las listas según su tipo.



Dado que vamos a añadir nuevas colecciones para trabajar con los nuevos tipos de datos, y concretamente, hoy vamos añadir una colección de eventos en la cuenta, usaremos nuevas enumeraciones. Para evitar un conflicto de nombres, necesitamos cambiar los nombres de ciertas macros creadas anteriormente:

//+------------------------------------------------------------------+
//| Possible criteria of orders and deals sorting                    |
//+------------------------------------------------------------------+
#define FIRST_ORD_DBL_PROP          (ORDER_PROP_INTEGER_TOTAL)
#define FIRST_ORD_STR_PROP          (ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL)
enum ENUM_SORT_ORDERS_MODE
  {
   //--- Sort by integer properties
   SORT_BY_ORDER_TICKET          =  0,                      // Sort by order ticket
   SORT_BY_ORDER_MAGIC           =  1,                      // Sort by order magic number
   SORT_BY_ORDER_TIME_OPEN       =  2,                      // Sort by order open time
   SORT_BY_ORDER_TIME_CLOSE      =  3,                      // Sort by order close time
   SORT_BY_ORDER_TIME_OPEN_MSC   =  4,                      // Sort by order open time in milliseconds
   SORT_BY_ORDER_TIME_CLOSE_MSC  =  5,                      // Sort by order close time in milliseconds
   SORT_BY_ORDER_TIME_EXP        =  6,                      // Sort by order expiration date
   SORT_BY_ORDER_STATUS          =  7,                      // Sort by order status (market order/pending order/deal/balance, credit operation)
   SORT_BY_ORDER_TYPE            =  8,                      // Sort by order type
   SORT_BY_ORDER_REASON          =  10,                     // Sort by order/position reason/source
   SORT_BY_ORDER_STATE           =  11,                     // Sort by order status
   SORT_BY_ORDER_POSITION_ID     =  12,                     // Sort by position ID
   SORT_BY_ORDER_POSITION_BY_ID  =  13,                     // Sort by opposite position ID
   SORT_BY_ORDER_DEAL_ORDER      =  14,                     // Sort by order a deal is based on
   SORT_BY_ORDER_DEAL_ENTRY      =  15,                     // Sort by deal direction – IN, OUT or IN/OUT
   SORT_BY_ORDER_TIME_UPDATE     =  16,                     // Sort by position change time in seconds
   SORT_BY_ORDER_TIME_UPDATE_MSC =  17,                     // Sort by position change time in milliseconds
   SORT_BY_ORDER_TICKET_FROM     =  18,                     // Sort by parent order ticket
   SORT_BY_ORDER_TICKET_TO       =  19,                     // Sort by derived order ticket
   SORT_BY_ORDER_PROFIT_PT       =  20,                     // Sort by order profit in points
   SORT_BY_ORDER_CLOSE_BY_SL     =  21,                     // Sort by order closing by StopLoss flag
   SORT_BY_ORDER_CLOSE_BY_TP     =  22,                     // Sort by order closing by TakeProfit flag
   //--- Sort by real properties
   SORT_BY_ORDER_PRICE_OPEN      =  FIRST_ORD_DBL_PROP,     // Sort by open price
   SORT_BY_ORDER_PRICE_CLOSE     =  FIRST_ORD_DBL_PROP+1,   // Sort by close price
   SORT_BY_ORDER_SL              =  FIRST_ORD_DBL_PROP+2,   // Sort by StopLoss price
   SORT_BY_ORDER_TP              =  FIRST_ORD_DBL_PROP+3,   // Sort by TakeProfit price
   SORT_BY_ORDER_PROFIT          =  FIRST_ORD_DBL_PROP+4,   // Sort by profit
   SORT_BY_ORDER_COMMISSION      =  FIRST_ORD_DBL_PROP+5,   // Sort by commission
   SORT_BY_ORDER_SWAP            =  FIRST_ORD_DBL_PROP+6,   // Sort by swap
   SORT_BY_ORDER_VOLUME          =  FIRST_ORD_DBL_PROP+7,   // Sort by volume
   SORT_BY_ORDER_VOLUME_CURRENT  =  FIRST_ORD_DBL_PROP+8,   // Sort by unexecuted volume
   SORT_BY_ORDER_PROFIT_FULL     =  FIRST_ORD_DBL_PROP+9,   // Sort by profit+commission+swap criterion
   SORT_BY_ORDER_PRICE_STOP_LIMIT=  FIRST_ORD_DBL_PROP+10,  // Sort by Limit order when StopLimit order is activated
   //--- Sort by string properties
   SORT_BY_ORDER_SYMBOL          =  FIRST_ORD_STR_PROP,     // Sort by symbol
   SORT_BY_ORDER_COMMENT         =  FIRST_ORD_STR_PROP+1,   // Sort by comment
   SORT_BY_ORDER_EXT_ID          =  FIRST_ORD_STR_PROP+2    // Sort by order ID in an external trading system
  };
//+------------------------------------------------------------------+

Pero, dado que vamos a corregir ahora el archivo Defines.mqh, añadiremos directamente al mismo todas las enumeraciones necesarias para las clases de eventos y la colección de eventos de la cuenta:

//+------------------------------------------------------------------+
//| Event status                                                     |
//+------------------------------------------------------------------+
enum ENUM_EVENT_STATUS
  {
   EVENT_STATUS_MARKET_POSITION,                            // Market position event (opening, partial opening, partial closing, adding volume, reversal)
   EVENT_STATUS_MARKET_PENDING,                             // Market pending order event (placing)
   EVENT_STATUS_HISTORY_PENDING,                            // Historical pending order event (removal)
   EVENT_STATUS_HISTORY_POSITION,                           // Historical position event (closing)
   EVENT_STATUS_BALANCE,                                    // Balance operation event (accruing balance, withdrawing funds and events from the ENUM_DEAL_TYPE enumeration)
  };
//+------------------------------------------------------------------+
//| Event reason                                                     |
//+------------------------------------------------------------------+
enum ENUM_EVENT_REASON
  {
   EVENT_REASON_ACTIVATED_PENDING               =  0,       // Pending order activation
   EVENT_REASON_ACTIVATED_PENDING_PARTIALLY     =  1,       // Pending order partial activation
   EVENT_REASON_CANCEL                          =  2,       // Cancelation
   EVENT_REASON_EXPIRED                         =  3,       // Order expiration
   EVENT_REASON_DONE                            =  4,       // Request executed in full
   EVENT_REASON_DONE_PARTIALLY                  =  5,       // Request executed partially
   EVENT_REASON_DONE_SL                         =  6,       // Closing by StopLoss
   EVENT_REASON_DONE_SL_PARTIALLY               =  7,       // Partial closing by StopLoss
   EVENT_REASON_DONE_TP                         =  8,       // Closing by TakeProfit
   EVENT_REASON_DONE_TP_PARTIALLY               =  9,       // Partial closing by TakeProfit
   EVENT_REASON_DONE_BY_POS                     =  10,      // Closing by an opposite position
   EVENT_REASON_DONE_PARTIALLY_BY_POS           =  11,      // Partial closing by an opposite position
   EVENT_REASON_DONE_BY_POS_PARTIALLY           =  12,      // Closing an opposite position by a partial volume
   EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY =  13,      // Partial closing of an opposite position by a partial volume
   //--- Constants related to DEAL_TYPE_BALANCE deal type from the ENUM_DEAL_TYPE enumeration
   EVENT_REASON_BALANCE_REFILL                  =  14,      // Refilling the balance
   EVENT_REASON_BALANCE_WITHDRAWAL              =  15,      // Withdrawing funds from the account
   //--- List of constants is relevant to TRADE_EVENT_ACCOUNT_CREDIT from the ENUM_TRADE_EVENT enumeration and shifted to +13 relative to ENUM_DEAL_TYPE (EVENT_REASON_ACCOUNT_CREDIT-3)
   EVENT_REASON_ACCOUNT_CREDIT                  =  16,      // Accruing credit
   EVENT_REASON_ACCOUNT_CHARGE                  =  17,      // Additional charges
   EVENT_REASON_ACCOUNT_CORRECTION              =  18,      // Correcting entry
   EVENT_REASON_ACCOUNT_BONUS                   =  19,      // Accruing bonuses
   EVENT_REASON_ACCOUNT_COMISSION               =  20,      // Additional commissions
   EVENT_REASON_ACCOUNT_COMISSION_DAILY         =  21,      // Commission charged at the end of a trading day
   EVENT_REASON_ACCOUNT_COMISSION_MONTHLY       =  22,      // Commission charged at the end of a trading month
   EVENT_REASON_ACCOUNT_COMISSION_AGENT_DAILY   =  23,      // Agent commission charged at the end of a trading day
   EVENT_REASON_ACCOUNT_COMISSION_AGENT_MONTHLY =  24,      // Agent commission charged at the end of a month
   EVENT_REASON_ACCOUNT_INTEREST                =  25,      // Accruing interest on free funds
   EVENT_REASON_BUY_CANCELLED                   =  26,      // Canceled buy deal
   EVENT_REASON_SELL_CANCELLED                  =  27,      // Canceled sell deal
   EVENT_REASON_DIVIDENT                        =  28,      // Accruing dividends
   EVENT_REASON_DIVIDENT_FRANKED                =  29,      // Accruing franked dividends
   EVENT_REASON_TAX                             =  30       // Tax
  };
#define REASON_EVENT_SHIFT    (EVENT_REASON_ACCOUNT_CREDIT-3)
//+------------------------------------------------------------------+
//| Event's integer properties                                       |
//+------------------------------------------------------------------+
enum ENUM_EVENT_PROP_INTEGER
  {
   EVENT_PROP_TYPE_EVENT = 0,                               // Account trading event type (from the ENUM_TRADE_EVENT enumeration)
   EVENT_PROP_TIME_EVENT,                                   // Event time in milliseconds
   EVENT_PROP_STATUS_EVENT,                                 // Event status (from the ENUM_EVENT_STATUS enumeration)
   EVENT_PROP_REASON_EVENT,                                 // Event reason (from the ENUM_EVENT_REASON enumeration)
   EVENT_PROP_TYPE_DEAL_EVENT,                              // Deal event type
   EVENT_PROP_TICKET_DEAL_EVENT,                            // Deal event ticket
   EVENT_PROP_TYPE_ORDER_EVENT,                             // Type of an order, based on which a deal event is opened (the last position order)
   EVENT_PROP_TICKET_ORDER_EVENT,                           // Ticket of an order, based on which a deal event is opened (the last position order)
   EVENT_PROP_TIME_ORDER_POSITION,                          // Time of an order, based on which a position deal is opened (the first position order)
   EVENT_PROP_TYPE_ORDER_POSITION,                          // Type of an order, based on which a position deal is opened (the first position order)
   EVENT_PROP_TICKET_ORDER_POSITION,                        // Ticket of an order, based on which a position deal is opened (the first position order)
   EVENT_PROP_POSITION_ID,                                  // Position ID
   EVENT_PROP_POSITION_BY_ID,                               // Opposite position ID
   EVENT_PROP_MAGIC_ORDER,                                  // Order/deal/position magic number
  }; 
#define EVENT_PROP_INTEGER_TOTAL (14)                       // Total number of integer event properties
//+------------------------------------------------------------------+
//| Event's real properties                                          |
//+------------------------------------------------------------------+
enum ENUM_EVENT_PROP_DOUBLE
  {
   EVENT_PROP_PRICE_EVENT = (EVENT_PROP_INTEGER_TOTAL),     // Price an event occurred at
   EVENT_PROP_PRICE_OPEN,                                   // Order/deal/position open price
   EVENT_PROP_PRICE_CLOSE,                                  // Order/deal/position close price
   EVENT_PROP_PRICE_SL,                                     // StopLoss order/deal/position price
   EVENT_PROP_PRICE_TP,                                     // TakeProfit Order/deal/position
   EVENT_PROP_VOLUME_INITIAL,                               // Requested volume
   EVENT_PROP_VOLUME_EXECUTED,                              // Executed volume
   EVENT_PROP_VOLUME_CURRENT,                               // Remaining volume
   EVENT_PROP_PROFIT                                        // Profit
  };
#define EVENT_PROP_DOUBLE_TOTAL  (9)                        // Total number of event's real properties
//+------------------------------------------------------------------+
//| Event's string properties                                        |
//+------------------------------------------------------------------+
enum ENUM_EVENT_PROP_STRING
  {
   EVENT_PROP_SYMBOL = (EVENT_PROP_INTEGER_TOTAL+EVENT_PROP_DOUBLE_TOTAL), // Order symbol
  };
#define EVENT_PROP_STRING_TOTAL     (1)                     // Total number of event's string properties
//+------------------------------------------------------------------+
//| Possible event sorting criteria                                  |
//+------------------------------------------------------------------+
#define FIRST_EVN_DBL_PROP       (EVENT_PROP_INTEGER_TOTAL)
#define FIRST_EVN_STR_PROP       (EVENT_PROP_INTEGER_TOTAL+EVENT_PROP_DOUBLE_TOTAL)
enum ENUM_SORT_EVENTS_MODE
  {
   //--- Sort by integer properties
   SORT_BY_EVENT_TYPE_EVENT            = 0,                    // Sort by event type
   SORT_BY_EVENT_TIME_EVENT            = 1,                    // Sort by event time
   SORT_BY_EVENT_STATUS_EVENT          = 2,                    // Sort by event status (from the ENUM_EVENT_STATUS enumeration)
   SORT_BY_EVENT_REASON_EVENT          = 3,                    // Sort by event reason (from the ENUM_EVENT_REASON enumeration)
   SORT_BY_EVENT_TYPE_DEAL_EVENT       = 4,                    // Sort by deal event type
   SORT_BY_EVENT_TICKET_DEAL_EVENT     = 5,                    // Sort by deal event ticket
   SORT_BY_EVENT_TYPE_ORDER_EVENT      = 6,                    // Sort by type of an order, based on which a deal event is opened (the last position order)
   SORT_BY_EVENT_TYPE_ORDER_POSITION   = 7,                    // Sort by type of an order, based on which a position deal is opened (the first position order)
   SORT_BY_EVENT_TICKET_ORDER_EVENT    = 8,                    // Sort by a ticket of an order, based on which a deal event is opened (the last position order)
   SORT_BY_EVENT_TICKET_ORDER_POSITION = 9,                    // Sort by a ticket of an order, based on which a position deal is opened (the first position order)
   SORT_BY_EVENT_POSITION_ID           = 10,                   // Sort by position ID
   SORT_BY_EVENT_POSITION_BY_ID        = 11,                   // Sort by opposite position ID
   SORT_BY_EVENT_MAGIC_ORDER           = 12,                   // Sort by order/deal/position magic number
   SORT_BY_EVENT_TIME_ORDER_POSITION   = 13,                   // Sort by time of an order, based on which a position deal is opened (the first position order)
   //--- Sort by real properties
   SORT_BY_EVENT_PRICE_EVENT        =  FIRST_EVN_DBL_PROP,     // Sort by a price an event occurred at
   SORT_BY_EVENT_PRICE_OPEN         =  FIRST_EVN_DBL_PROP+1,   // Sort by position open price
   SORT_BY_EVENT_PRICE_CLOSE        =  FIRST_EVN_DBL_PROP+2,   // Sort by position close price
   SORT_BY_EVENT_PRICE_SL           =  FIRST_EVN_DBL_PROP+3,   // Sort by position's StopLoss price
   SORT_BY_EVENT_PRICE_TP           =  FIRST_EVN_DBL_PROP+4,   // Sort by position's TakeProfit price
   SORT_BY_EVENT_VOLUME_INITIAL     =  FIRST_EVN_DBL_PROP+5,   // Sort by initial volume
   SORT_BY_EVENT_VOLUME             =  FIRST_EVN_DBL_PROP+6,   // Sort by the current volume
   SORT_BY_EVENT_VOLUME_CURRENT     =  FIRST_EVN_DBL_PROP+7,   // Sort by remaining volume
   SORT_BY_EVENT_PROFIT             =  FIRST_EVN_DBL_PROP+8,   // Sort by profit
   //--- Sort by string properties
   SORT_BY_EVENT_SYMBOL             =  FIRST_EVN_STR_PROP      // Sort by order/position/deal symbol
  };
//+------------------------------------------------------------------+

Aquí se enumeran todos los posibles estados de los objetos de evento (por analogía con los estados de las órdenes que analizamos en el primer artículo); los motivos de la aparición de los eventos para la búsqueda por propiedades, todo esto ya lo conocemos por los artículos anteriores y ha sido detalladamente descrito. Para precisar datos, siempre podemos regresar al principio y refrescar la información en la memoria.

Aparte del estado del evento, que nos ofrece información solo de carácter general sobre un evento, en el motivo del evento (ENUM_EVENT_REASON) ya se contiene toda la información aclaratoria sobre la procedencia de un evento concreto.
Por ejemplo, si el evento tiene el estado de posición de mercado (EVENT_STATUS_MARKET_POSITION), el motivo de la aparición de este evento se indica en el campo del objeto EVENT_PROP_REASON_EVENT, y puede ser tanto la activación de una orden pendiente (EVENT_REASON_ACTIVATED_PENDING), como la apertura de una posición por una orden de mercado (EVENT_REASON_DONE). En este caso, además, se tienen en cuenta los siguientes matices: es decir, si ha sucedido la apertura parcial de la posición (no se ha ejecutado el volumen completo de una orden de mercado o pendiente), entonces los motivos del evento serán ya EVENT_REASON_ACTIVATED_PENDING_PARTIALLY o EVENT_REASON_DONE_PARTIALLY, etc.
De esta forma, en el objeto de evento se incluirá toda la información tanto sobre el propio evento, como sobre la orden cuya activación ha propiciado la aparición de dicho evento. Además, en los eventos históricos se dispondrá de información sobre dos órdenes: sobre la primera orden de la posición y sobre la orden que cierra la misma.
De esta manera, disponiendo de los datos sobre las órdenes, las transacciones y la propia posición en el objeto de evento, siempre podremos monitorear la cadena completa de eventos sucedidos en la posición durante su historia completa: desde la apertura hasta el cierre.

Las constantes de la enumeración ENUM_EVENT_REASON están ubicadas y enumeradas de tal forma que, al darse el estado de evento "transacción", el tipo de transacción entre en el valor de la enumeración ENUM_DEAL_TYPE, en el caso de que el tipo de transacción sea superior a DEAL_TYPE_SELL. De esta forma, entramos en los tipos de operaciones de balance, y en el motivo del evento entrará la descripción de la operación de balance al determinar el tipo de transacción en la clase preparada para la creación.
En la macrosustitución #define REASON_EVENT_SHIFT se calcula el desplazamiento que añadiremos al tipo de transacción para indicar el tipo de operación de balance en la enumeración ENUM_EVENT_REASON.

Para que resulte cómodo mostrar las descripciones de las órdenes, posiciones y transacciones, en el archivo DELib.mqh, que se encuentra en la carpeta de la biblioteca Services, vamos a añadir funciones que retornan las descripciones de los tipos de órdenes, posiciones y transacciones, así como una función que retorna la denominación de la posición dependiendo del tipo de orden abierta:

//+------------------------------------------------------------------+
//| Return the order name                                            |
//+------------------------------------------------------------------+
string OrderTypeDescription(const ENUM_ORDER_TYPE type)
  {
   string pref=(#ifdef __MQL5__ "Market order" #else "Position" #endif );
   return
     (
      type==ORDER_TYPE_BUY_LIMIT       ?  "Buy Limit"                                                 :
      type==ORDER_TYPE_BUY_STOP        ?  "Buy Stop"                                                  :
      type==ORDER_TYPE_SELL_LIMIT      ?  "Sell Limit"                                                :
      type==ORDER_TYPE_SELL_STOP       ?  "Sell Stop"                                                 :
   #ifdef __MQL5__
      type==ORDER_TYPE_BUY_STOP_LIMIT  ?  "Buy Stop Limit"                                            :
      type==ORDER_TYPE_SELL_STOP_LIMIT ?  "Sell Stop Limit"                                           :
      type==ORDER_TYPE_CLOSE_BY        ?  TextByLanguage("Закрывающий ордер","Order for closing by")  :  
   #else 
      type==ORDER_TYPE_BALANCE         ?  TextByLanguage("Балансовая операция","Balance operation")   :
      type==ORDER_TYPE_CREDIT          ?  TextByLanguage("Кредитная операция","Credit operation")     :
   #endif 
      type==ORDER_TYPE_BUY             ?  pref+" Buy"                                                 :
      type==ORDER_TYPE_SELL            ?  pref+" Sell"                                                :  
      TextByLanguage("Неизвестный тип ордера","Unknown order type")
     );
  }
//+------------------------------------------------------------------+
//| Return the position name                                         |
//+------------------------------------------------------------------+
string PositionTypeDescription(const ENUM_POSITION_TYPE type)
  {
   return
     (
      type==POSITION_TYPE_BUY    ? "Buy"  :
      type==POSITION_TYPE_SELL   ? "Sell" :  
      TextByLanguage("Неизвестный тип позиции","Unknown position type")
     );
  }
//+------------------------------------------------------------------+
//| Return the deal name                                             |
//+------------------------------------------------------------------+
string DealTypeDescription(const ENUM_DEAL_TYPE type)
  {
   return
     (
      type==DEAL_TYPE_BUY                       ?  TextByLanguage("Сделка на покупку","Buy deal") :
      type==DEAL_TYPE_SELL                      ?  TextByLanguage("Сделка на продажу","Sell deal") :
      type==DEAL_TYPE_BALANCE                   ?  TextByLanguage("Балансовая операция","Balance operation") :
      type==DEAL_TYPE_CREDIT                    ?  TextByLanguage("Начисление кредита","Credit") :
      type==DEAL_TYPE_CHARGE                    ?  TextByLanguage("Дополнительные сборы","Additional charge") :
      type==DEAL_TYPE_CORRECTION                ?  TextByLanguage("Корректирующая запись","Correction") :
      type==DEAL_TYPE_BONUS                     ?  TextByLanguage("Перечисление бонусов","Bonus") :
      type==DEAL_TYPE_COMMISSION                ?  TextByLanguage("Дополнительные комиссии","Additional comissions") :
      type==DEAL_TYPE_COMMISSION_DAILY          ?  TextByLanguage("Комиссия, начисляемая в конце торгового дня","Daily commission") :
      type==DEAL_TYPE_COMMISSION_MONTHLY        ?  TextByLanguage("Комиссия, начисляемая в конце месяца","Monthly commission") :
      type==DEAL_TYPE_COMMISSION_AGENT_DAILY    ?  TextByLanguage("Агентская комиссия, начисляемая в конце торгового дня","Daily agent commission") :
      type==DEAL_TYPE_COMMISSION_AGENT_MONTHLY  ?  TextByLanguage("Агентская комиссия, начисляемая в конце месяца","Monthly agent commission") :
      type==DEAL_TYPE_INTEREST                  ?  TextByLanguage("Начисления процентов на свободные средства","Agency commission charged at the end of month") :
      type==DEAL_TYPE_BUY_CANCELED              ?  TextByLanguage("Отмененная сделка покупки","Canceled buy transaction") :
      type==DEAL_TYPE_SELL_CANCELED             ?  TextByLanguage("Отмененная сделка продажи","Canceled sell transaction") :
      type==DEAL_DIVIDEND                       ?  TextByLanguage("Начисление дивиденда","Dividend operations") :
      type==DEAL_DIVIDEND_FRANKED               ?  TextByLanguage("Начисление франкированного дивиденда","Franked (non-taxable) dividend operations") :
      type==DEAL_TAX                            ?  TextByLanguage("Начисление налога","Tax charges") : 
      TextByLanguage("Неизвестный тип сделки","Unknown deal type")
     );
  }
//+------------------------------------------------------------------+
//| Return the position type by the order type                       |
//+------------------------------------------------------------------+
ENUM_POSITION_TYPE PositionTypeByOrderType(ENUM_ORDER_TYPE type_order)
  {
   if(
      type_order==ORDER_TYPE_BUY             ||
      type_order==ORDER_TYPE_BUY_LIMIT       ||
      type_order==ORDER_TYPE_BUY_STOP
   #ifdef __MQL5__                           ||
      type_order==ORDER_TYPE_BUY_STOP_LIMIT
   #endif 
     ) return POSITION_TYPE_BUY;
   else if(
      type_order==ORDER_TYPE_SELL            ||
      type_order==ORDER_TYPE_SELL_LIMIT      ||
      type_order==ORDER_TYPE_SELL_STOP
   #ifdef __MQL5__                           ||
      type_order==ORDER_TYPE_SELL_STOP_LIMIT
   #endif 
     ) return POSITION_TYPE_SELL;
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+

Avanzando un poco. Al poner a prueba la clase de la colección de eventos, hemos detectado un problema bastante desagradable: al crear en el terminal listas de órdenes y transacciones con la ayuda de HistorySelect() y acceder posteriormente a los nuevos elementos de las listas, hemos notado que las órdenes no entran en la lista según el orden de aparición, sino según su hora de colocación. Expliquémoslo:

  1. abrimos una posición,
  2. después colocamos de inmediato una orden pendiente,
  3. a continuación, cerramos parte de la posición
  4. esperamos la activación de la orden pendiente

En principio, y como era de esperar, el orden de secuencia de estos eventos en la historia debería haber sido así:
apertura de posición, colocación de la orden, cierre parcial, activación de la orden: todo ordenado por tiempo según el orden de ejecución de las operaciones. Pero ha resultado de otra forma, y el orden de la secuencia de estos eventos en la historia general de transacciones es el siguiente:

  1. se abre una posición
  2. se coloca una orden
  3. la orden se activa
  4. se da un cierre parcial

Es decir, la historia de órdenes y la historia de transacciones en el terminal viven su propia vida, y no tienen ninugna correlación entre ellas, lo que también resulta lógico: dos listas, cada una con su historia.

Nuestra clase de la colección de órdenes y transacciones está hecha de tal forma que al cambiar cualquiera de las listas, ya sea la de órdenes o la de transacciones, se calcula el último evento en la cuenta, para no escanear constantemente la historia, lo cual no es muy práctico. Pero considerando lo anterior, y al realizar operaciones comerciales, no monitoreamos la secuencia de acciones, simplemente colocamos una orden y esperamos su activación. Una vez abrimos una posición, trabajamos exclusivamente con ella. En ese caso, todos los eventos deben ubicarse en el orden necesario para permitir su rastreo. Sin embargo, esto no basta. Necesitamos trabajar en cualquier secuencia, y el programa debe ser capaz de localizar el evento correcto e indicarlo con precisión.

Partiendo de lo anterior, hemos mejorado la clase de colección de eventos y órdenes históricos. Ahora, si aparece un evento, pero se encuentra fuera del orden secuencial de aparición según el tiempo, la clase encontrará el orden necesario, creará su objeto y lo colocará en la lista como el último, de forma que la clase de colección de eventos siempre pueda definir con precisión el último evento ocurrido.

Para implementar la función, vamos a añadir tres nuevos métodos a la sección privada de la clase de colección de órdenes y transacciones históricos:

//--- Return the flag of the order object by its type and ticket in the list of historical orders and deals
   bool              IsPresentOrderInList(const ulong order_ticket,const ENUM_ORDER_TYPE type);
//--- Return the "lost" order type and ticket
   ulong             OrderSearch(const int start,ENUM_ORDER_TYPE &order_type);
//--- Create the order object and place it to the list
   bool              CreateNewOrder(const ulong order_ticket,const ENUM_ORDER_TYPE order_type);

y su implementación fuera del cuerpo.

Método que retorna la bandera de presencia de un objeto de orden en la lista según su ticket y tipo:

//+-----------------------------------------------------------------------------+
//| Return the flag of the order object presence in the list by type and ticket |
//+-----------------------------------------------------------------------------+
bool CHistoryCollection::IsPresentOrderInList(const ulong order_ticket,const ENUM_ORDER_TYPE type)
  {
   CArrayObj* list=dynamic_cast<CListObj*>(&this.m_list_all_orders);
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,type,EQUAL);
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,order_ticket,EQUAL);
   return(list.Total()>0);
  }
//+------------------------------------------------------------------+

CReamos el puntero a la lista con la ayuda de la conversión dinámica de tipos (enviamos a la clase CSelect la lista CArrayObj, mientras que las listas de colección tienen el tipo CListObj, son herederas de CArrayObj)
Dejamos en la lista solo las órdenes con el tipo transmitido al método con el parámetro de entrada.
Dejamos en la lista solo la orden con el ticket transmitido al método con el parámetro de entrada.
Si esa orden existe (la lista tiene un tamaño superior a cero, retornamos true)

Método que retorna el tipo y el ticket de una orden que no se ubique en el último lugar de la lista del terminal, pero no se encuentre en la lista de colección:

//+------------------------------------------------------------------+
//| Return the "lost" order's type and ticket                        |
//+------------------------------------------------------------------+
ulong CHistoryCollection::OrderSearch(const int start,ENUM_ORDER_TYPE &order_type)
  {
   ulong order_ticket=0;
   for(int i=start-1;i>=0;i--)
     {
      ulong ticket=::HistoryOrderGetTicket(i);
      if(ticket==0)
         continue;
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::HistoryOrderGetInteger(ticket,ORDER_TYPE);
      if(this.IsPresentOrderInList(ticket,type))
         continue;
      order_ticket=ticket;
      order_type=type;
     }
   return order_ticket;
  }
//+------------------------------------------------------------------+

Al método se transmite el índice de la última orden en la lista de órdenes del terminal. Dado que el índice hace referencia a una orden que ya existe en la colección, el ciclo de búsqueda se debe comenzar desde la anterior orden en la lista (start-1).
Dado que el orden necesario generalmente comienza desde el final de la lista (ya que el orden buscado se encontrará más cerca del final de la lista), buscaremos una orden con un ticket y un tipo que esté ausente en la colección con la ayuda del método IsPresentOrderInList (). Si la orden está presente en la colección, comprobamos la siguiente. En cuanto veamos una orden que no está presente en la colección, registramos su ticket y tipo y los retornamos al programa que ha realizado la llamada: el ticket con el resultado del método, y el tipo en la variable según el enlace.

Ya que ahora necesitamos crear objetos de orden en varios lugares dentro de la clase (cuando definimos una nueva orden y al encontrar una "extraviada"), vamos a hacer un método que cree un objeto de orden y lo coloque en la lista de colección:

//+------------------------------------------------------------------+
//| Create an order object and place it to the list                  |
//+------------------------------------------------------------------+
bool CHistoryCollection::CreateNewOrder(const ulong order_ticket,const ENUM_ORDER_TYPE order_type)
  {
   COrder* order=NULL;
   if(order_type==ORDER_TYPE_BUY)
     {
      order=new CHistoryOrder(order_ticket);
      if(order==NULL)
         return false;
     }
   else if(order_type==ORDER_TYPE_BUY_LIMIT)
     {
      order=new CHistoryPending(order_ticket);
      if(order==NULL)
         return false;
     }
   else if(order_type==ORDER_TYPE_BUY_STOP)
     {
      order=new CHistoryPending(order_ticket);
      if(order==NULL)
         return false;
     }
   else if(order_type==ORDER_TYPE_SELL)
     {
      order=new CHistoryOrder(order_ticket);
      if(order==NULL)
         return false;
     }
   else if(order_type==ORDER_TYPE_SELL_LIMIT)
     {
      order=new CHistoryPending(order_ticket);
      if(order==NULL)
         return false;
     }
   else if(order_type==ORDER_TYPE_SELL_STOP)
     {
      order=new CHistoryPending(order_ticket);
      if(order==NULL)
         return false;
     }
#ifdef __MQL5__
   else if(order_type==ORDER_TYPE_BUY_STOP_LIMIT)
     {
      order=new CHistoryPending(order_ticket);
      if(order==NULL)
         return false;
     }
   else if(order_type==ORDER_TYPE_SELL_STOP_LIMIT)
     {
      order=new CHistoryPending(order_ticket);
      if(order==NULL)
         return false;
     }
   else if(order_type==ORDER_TYPE_CLOSE_BY)
     {
      order=new CHistoryOrder(order_ticket);
      if(order==NULL)
         return false;
     }
#endif 
   if(this.m_list_all_orders.InsertSort(order))
      return true;
   else
     {
      delete order;
      return false;
     }
   return false;
  }
//+------------------------------------------------------------------+

Aquí todo es simple y claro: el método recibe el ticket y el tipo de la orden y, dependiendo del tipo de la misma, se crea un nuevo objeto de orden. Si el objeto no se ha podido crear, se retorna false inmediatamente. Si el objeto se ha creado con éxito, se coloca en la colección y se retorna true. Si no ha sido posible colocarlo en la colección, se eliminará el objeto recién creado y se retornará false.

Cambiamos el método de la clase de colección Refresh(), puesto que debemos procesar el hecho de la "pérdida" de la orden necesaria:

//+------------------------------------------------------------------+
//| Update the list of orders and deals                              |
//+------------------------------------------------------------------+
void CHistoryCollection::Refresh(void)
  {
#ifdef __MQL4__
   int total=::OrdersHistoryTotal(),i=m_index_order;
   for(; i<total; i++)
     {
      if(!::OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)) continue;
      ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)::OrderType();
      //--- Closed positions and balance/credit operations
      if(order_type<ORDER_TYPE_BUY_LIMIT || order_type>ORDER_TYPE_SELL_STOP)
        {
         CHistoryOrder *order=new CHistoryOrder(::OrderTicket());
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
      else
        {
         //--- Removed pending orders
         CHistoryPending *order=new CHistoryPending(::OrderTicket());
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))this.m_list_all_orders.Type()
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
     }
//---
   int delta_order=i-m_index_order;
   this.m_index_order=i;
   this.m_delta_order=delta_order;
   this.m_is_trade_event=(this.m_delta_order!=0 ? true : false);
//--- __MQL5__
#else 
   if(!::HistorySelect(0,END_TIME)) return;
//--- Orders
   int total_orders=::HistoryOrdersTotal(),i=m_index_order;
   for(; i<total_orders; i++)
     {
      ulong order_ticket=::HistoryOrderGetTicket(i);
      if(order_ticket==0) continue;
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::HistoryOrderGetInteger(order_ticket,ORDER_TYPE);
      if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL || type==ORDER_TYPE_CLOSE_BY)
        {
         //--- If there is no order of this type and with this ticket in the list, create an order object and add it to the list
         if(!this.IsPresentOrderInList(order_ticket,type))
           {
            if(!this.CreateNewOrder(order_ticket,type))
               ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to list"));
           }
         //--- Such an order is already present in the list, which means the necessary order is not the last one in the history list. Let's find it
         else
           {
            ENUM_ORDER_TYPE type_lost=WRONG_VALUE;
            ulong ticket_lost=this.OrderSearch(i,type_lost);
            if(ticket_lost>0 && !this.CreateNewOrder(ticket_lost,type_lost))
               ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to list"));
           }
        }
      else
        {
         //--- If there is no pending order of this type and with this ticket in the list, create an order object and add it to the list
         if(!this.IsPresentOrderInList(order_ticket,type))
           {
            if(!this.CreateNewOrder(order_ticket,type))
               ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to list"));
           }
         //--- Such an order is already present in the list, which means the necessary order is not the last one in the history list. Let's find it
         else
           {
            ENUM_ORDER_TYPE type_lost=WRONG_VALUE;
            ulong ticket_lost=this.OrderSearch(i,type_lost);
            if(ticket_lost>0 && !this.CreateNewOrder(ticket_lost,type_lost))
               ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to list"));
           }
        }
     }
//--- save the index of the last added order and the difference as compared to the previous check
   int delta_order=i-this.m_index_order;
   this.m_index_order=i;
   this.m_delta_order=delta_order;

//--- Deals
   int total_deals=::HistoryDealsTotal(),j=m_index_deal;
   for(; j<total_deals; j++)
     {
      ulong deal_ticket=::HistoryDealGetTicket(j);
      if(deal_ticket==0) continue;
      CHistoryDeal *deal=new CHistoryDeal(deal_ticket);
      if(deal==NULL) continue;
      if(!this.m_list_all_orders.InsertSort(deal))
        {
         ::Print(DFUN,TextByLanguage("Не удалось добавить сделку в список","Could not add deal to list"));
         delete deal;
        }
     }
//--- save the index of the last added deal and the difference as compared to the previous check
   int delta_deal=j-this.m_index_deal;
   this.m_index_deal=j;
   this.m_delta_deal=delta_deal;
//--- Set the new event flag in history
   this.m_is_trade_event=(this.m_delta_order+this.m_delta_deal);
#endif 
  }
//+------------------------------------------------------------------+

En el método se ha cambiado el bloque de nuevas órdenes para MQL5. Todos los cambios realizados se describen con comentarios y se destacan en el texto de la lista.

Para buscar órdenes iguales, añadimos a la sección pública de la clase COrder la definición del método:

//--- Compare COrder by all properties (to search for equal event objects)
   bool              IsEqual(COrder* compared_order) const;

y su implementación fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Compare COrder objects by all properties                         |
//+------------------------------------------------------------------+
bool COrder::IsEqual(COrder *compared_order) const
  {
   int beg=0, end=ORDER_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_ORDER_PROP_INTEGER prop=(ENUM_ORDER_PROP_INTEGER)i;
      if(this.GetProperty(prop)!=compared_order.GetProperty(prop)) return false; 
     }
   beg=end; end+=ORDER_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_ORDER_PROP_DOUBLE prop=(ENUM_ORDER_PROP_DOUBLE)i;
      if(this.GetProperty(prop)!=compared_order.GetProperty(prop)) return false; 
     }
   beg=end; end+=ORDER_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_ORDER_PROP_STRING prop=(ENUM_ORDER_PROP_STRING)i;
      if(this.GetProperty(prop)!=compared_order.GetProperty(prop)) return false; 
     }
   return true;
  }
//+------------------------------------------------------------------+

El método itera en el ciclo por todas las propiedades del objeto de orden actual y la orden comparada, transmitida con el puntero al método.
En cuanto se encuentra cualquiera de las propiedades de la orden actual que no sea igual a la misma propiedad de la orden comparada, se retorna false: las órdenes no son iguales.


Clases de eventos

La etapa preparatoria ha finalizado. Vamosa a proceder a la creación de las clases de los objetos de eventos.

Haremos lo mismo que al crear las clases de orden. Vamos a crear una clase de evento básica y cinco clases herederas descritas por sus estados:

  • evento de apertura de posición,
  • evento de cierre de posición,
  • evento de colocación de una orden pendiente
  • evento de eliminación de una orden pendiente
  • evento de una operación de balance

En la carpeta Events, creada por nosotros anteriormente en el directorio de la biblioteca Objects, creamos una nueva clase CEvent, heredada de la clase básica CObject. En la plantilla de clase nuevamente creada, registramos las inclusiones necesarias del archivo de las funciones de servicio, las clases de las colecciones de órdenes, así como los miembros privados y protegidos y los métodos de clase:

//+------------------------------------------------------------------+
//|                                                        Event.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Object.mqh>
#include "\..\..\Services\DELib.mqh"
#include "..\..\Collections\HistoryCollection.mqh"
#include "..\..\Collections\MarketCollection.mqh"
//+------------------------------------------------------------------+
//| Abstract event class                                             |
//+------------------------------------------------------------------+
class CEvent : public CObject
  {
private:
   int               m_event_code;                                   // Event code
//--- Return the index of the array the event's (1) double and (2) string properties are located at
   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;                                  // Trading event
   long              m_chart_id;                                     // Control program chart ID
   int               m_digits_acc;                                   // Number of decimal places for the account currency
   long              m_long_prop[EVENT_PROP_INTEGER_TOTAL];          // Event integer properties
   double            m_double_prop[EVENT_PROP_DOUBLE_TOTAL];         // Event real properties
   string            m_string_prop[EVENT_PROP_STRING_TOTAL];         // Event string properties
//--- return the flag presence in the trading event
   bool              IsPresentEventFlag(const int event_code)  const { return (this.m_event_code & event_code)==event_code;            }

   //--- Protected parametric constructor
                     CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket);
public:
//--- Default constructor
                     CEvent(void){;}
 
//--- Set event's (1) integer, (2) real and (3) string properties
   void              SetProperty(ENUM_EVENT_PROP_INTEGER property,long value) { this.m_long_prop[property]=value;                      }
   void              SetProperty(ENUM_EVENT_PROP_DOUBLE property,double value){ this.m_double_prop[this.IndexProp(property)]=value;    }
   void              SetProperty(ENUM_EVENT_PROP_STRING property,string value){ this.m_string_prop[this.IndexProp(property)]=value;    }
//--- Return the event's (1) integer, (2) real and (3) string properties from the property array
   long              GetProperty(ENUM_EVENT_PROP_INTEGER property)      const { return this.m_long_prop[property];                     }
   double            GetProperty(ENUM_EVENT_PROP_DOUBLE property)       const { return this.m_double_prop[this.IndexProp(property)];   }
   string            GetProperty(ENUM_EVENT_PROP_STRING property)       const { return this.m_string_prop[this.IndexProp(property)];   }

//--- Return the flag of the event supporting the property
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property)        { return true; }
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property)         { return true; }
   virtual bool      SupportProperty(ENUM_EVENT_PROP_STRING property)         { return true; }

//--- Set the control program chart ID
   void              SetChartID(const long id)                                { this.m_chart_id=id;                                    }
//--- Decode the event code and set the trading event, (2) return the trading event
   void              SetTypeEvent(void);
   ENUM_TRADE_EVENT  TradeEvent(void)                                   const { return this.m_trade_event;                             }
//--- Send the event to the chart (implementation in descendant classes)
   virtual void      SendEvent(void) {;}

//--- Compare CEvent objects by a specified property (to sort the lists by a specified event object property)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Compare CEvent objects by all properties (to search for equal event objects)
   bool              IsEqual(CEvent* compared_event) const;
//+------------------------------------------------------------------+
//| Methods of simplified access to event object properties          |
//+------------------------------------------------------------------+
//--- Return (1) event type, (2) event time in milliseconds, (3) event status, (4) event reason, (5) deal type, (6) deal ticket, 
//--- (7) order type, based on which a deal was executed, (8) position opening order type, (9) position last order ticket, 
//--- (10) position first order ticket, (11) position ID, (12) opposite position ID, (13) magic number, (14) position open time

   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);  }
   long              TypeDeal(void)                                     const { return this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT);                  }
   long              TicketDeal(void)                                   const { return this.GetProperty(EVENT_PROP_TICKET_DEAL_EVENT);                }
   long              TypeOrderEvent(void)                               const { return this.GetProperty(EVENT_PROP_TYPE_ORDER_EVENT);                 }
   long              TypeOrderPosition(void)                            const { return this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION);              }
   long              TicketOrderEvent(void)                             const { return this.GetProperty(EVENT_PROP_TICKET_ORDER_EVENT);               }
   long              TicketOrderPosition(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              TimePosition(void)                                 const { return this.GetProperty(EVENT_PROP_TIME_ORDER_POSITION);              }
   
//--- Return (1) the price the event occurred at, (2) open price, (3) close price,
//--- (4) StopLoss price, (5) TakeProfit price, (6) profit, (7) requested volume, (8), executed volume, (9) remaining volume
   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            VolumeInitial(void)                                const { return this.GetProperty(EVENT_PROP_VOLUME_INITIAL);                   }
   double            VolumeExecuted(void)                               const { return this.GetProperty(EVENT_PROP_VOLUME_EXECUTED);                  }
   double            VolumeCurrent(void)                                const { return this.GetProperty(EVENT_PROP_VOLUME_CURRENT);                   }
   
//--- Return a symbol
   string            Symbol(void)                                       const { return this.GetProperty(EVENT_PROP_SYMBOL);                           }
   
//+------------------------------------------------------------------+
//| Descriptions of order object properties                          |
//+------------------------------------------------------------------+
//--- Return description of the order's (1) integer, (2) real and (3) string properties
   string            GetPropertyDescription(ENUM_EVENT_PROP_INTEGER property);
   string            GetPropertyDescription(ENUM_EVENT_PROP_DOUBLE property);
   string            GetPropertyDescription(ENUM_EVENT_PROP_STRING property);
//--- Return the event's (1) status and (2) type
   string            StatusDescription(void)          const;
   string            TypeEventDescription(void)       const;
//--- Return the name of an (1) order/position/deal, (2) parent order, (3) position
   string            TypeOrderDescription(void)       const;
   string            TypeOrderBasedDescription(void)  const;
   string            TypePositionDescription(void)    const;
//--- Return the name of the deal/order/position reason
   string            ReasonDescription(void)          const;

//--- Display (1) description of order properties (full_prop=true - all properties, false - only supported ones),
//--- (2) short event message (implementation in the class descendants) in the journal
   void              Print(const bool full_prop=false);
   virtual void      PrintShort(void) {;}
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CEvent::CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket) : m_event_code(event_code)
  {
   this.m_long_prop[EVENT_PROP_STATUS_EVENT]       =  event_status;
   this.m_long_prop[EVENT_PROP_TICKET_ORDER_EVENT] =  (long)ticket;
   this.m_digits_acc=(int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS);
   this.m_chart_id=::ChartID();
  }
//+------------------------------------------------------------------+

Al constructor se transmiten el estado del evento, el código del evento comercial y el ticket de la orden o transacción que ha provocado la aparición del evento.

Aquí todo es prácticamente igual que en el constructor protegido de la clase COrder que analizamos anteriormente en la primera parte de la descripción de la biblioteca.

La úncia diferencia es que en el constructor protegido de la clase se rellenan solo dos propiedades de evento: el estado del evento y el ticket de la orden o transacción que ha ocasionado la aparición del evento. el tipo de evento se detecta y guarda en el método de la clase SetTypeEvent(), partiendo del código de evento transmitido al constructor; el resto de propiedades de evento se detectan a partir del estado de las órdenes y transacciones que han participado en este evento, y son establecidas aparte por los métodos de clase correspondientes. Se ha hecho de esta forma para que los eventos sean detectados en la clase de colección de eventos. Allí se encontrará un método que establecerá todas las propiedades para el evento creado de nuevo.

El código de evento (m_event_code), así como su rellenado e interpretación fueron analizados por nosotros en la cuarta parte de la descripción de la biblioteca. Lo trasladamos desde la clase CEngine hacia aquí, ya que en la clase básica de la biblioteca era solo temporal, y solo para comprobar el concepto del trabajo con eventos. Ahora se calculará en la clase de colección de eventos y se transmitirá al constructor de esta clase al crear el objeto de evento.
El propio evento comercial (m_trade_event) se compondrá descodificando el código de evento en el método SetTypeEvent(); ya analizamos este método de desencriptado del código de evento en el cuarto artículo.
Necesitamos el identificador del gráfico del programa de control ( m_chart_id) para enviar al mismo los mensajes de usuario sobre los eventos.
Necesitamos el número de decimales tras la coma para la divisa de la cuenta ( m_digits_acc) para mostrar correctamente los mensajes sobre eventos en el diario de registro.

Los métodos de comparación de las propiedades del objeto de evento Compare() y IsEqual() son bastante sencillos y transparentes. Ya analizamos el método Compare() en la primera parte de la descripción de la biblioteca, y es exactamente igual que en el objeto COrder; el método IsEqual(), a diferencia del primer método (que compara dos objetos solo de acuerdo con una de sus propiedades), compara estos dos objetos conforme a todos sus campos. Si todos los campos en los dos objetos son iguales (cada propiedad del objeto actual es igual a la propiedad correspondiente del objeto comparado), ambos objetos serán idénticos. El método comprueba en el ciclo todas las propiedades de los dos objetos, y en cuanto encuentra una desigualdad en las propiedades, retorna de inmediato false (no tiene sentido seguir comprobando); uno de las propiedades del objeto ya no es igual a la misma propiedad del objeto comparado.

//+------------------------------------------------------------------+
//| Compare CEvent objects by a specified property                   |
//+------------------------------------------------------------------+
int CEvent::Compare(const CObject *node,const int mode=0) const
  {
   const CEvent *event_compared=node;
//--- compare integer properties of two events
   if(mode<EVENT_PROP_INTEGER_TOTAL)
     {
      long value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_INTEGER)mode);
      long value_current=this.GetProperty((ENUM_EVENT_PROP_INTEGER)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- compare integer properties of two objects
   if(mode<EVENT_PROP_DOUBLE_TOTAL+EVENT_PROP_INTEGER_TOTAL)
     {
      double value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_DOUBLE)mode);
      double value_current=this.GetProperty((ENUM_EVENT_PROP_DOUBLE)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- compare string properties of two objects
   else if(mode<EVENT_PROP_DOUBLE_TOTAL+EVENT_PROP_INTEGER_TOTAL+EVENT_PROP_STRING_TOTAL)
     {
      string value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_STRING)mode);
      string value_current=this.GetProperty((ENUM_EVENT_PROP_STRING)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
   return 0;
  }
//+------------------------------------------------------------------+
//| Compare CEvent events by all properties                          |
//+------------------------------------------------------------------+
bool CEvent::IsEqual(CEvent *compared_event) const
  {
   int beg=0, end=EVENT_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_INTEGER prop=(ENUM_EVENT_PROP_INTEGER)i;
      if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false; 
     }
   beg=end; end+=EVENT_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_DOUBLE prop=(ENUM_EVENT_PROP_DOUBLE)i;
      if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false; 
     }
   beg=end; end+=EVENT_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_STRING prop=(ENUM_EVENT_PROP_STRING)i;
      if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false; 
     }
   return true;
  }
//+------------------------------------------------------------------+

Vamos a analizar con más detalle el método SetTypeEvent()

Aquí, directamente en los comentarios del código, se registran todas las comprobaciones y acciones necesarias:

//+------------------------------------------------------------------+
//| Decode the event code and set a trading event                    |
//+------------------------------------------------------------------+
void CEvent::SetTypeEvent(void)
  {
//--- Pending order placed (check for matching the event code since there can only be one flag here)
   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;
     }
//--- Pending order removed (check for matching the event code since there can only be one flag here)
   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;
     }
//--- Position opened (Check the presence of multiple flags in the event code)
   if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_OPENED))
     {
      //--- If the pending order is activated by a price
      if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED))
        {
         //--- check the partial closure flag and set the "pending order activated" or "pending order partially activated" trading event
         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;
        }
      //--- check the partial opening flag and set the "Position opened" or "Position partially opened" trading event
      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;
     }
//--- Position closed (Check the presence of multiple flags in the event code)
   if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_CLOSED))
     {
      //--- if a position is closed by StopLoss
      if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL))
        {
         //--- check the partial closing flag and set the "Position closed by StopLoss" or "Position partially closed by StopLoss" trading event
         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;
        }
      //--- if a position is closed by TakeProfit
      else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP))
        {
         //--- check the partial closure flag and set the "Position closed by TakeProfit" or "Position partially closed by TakeProfit" trading event
         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;
        }
      //--- if a position is closed by an opposite one
      else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_BY_POS))
        {
         //--- check the partial closure flag and set the "Position closed by opposite one" or "Position partially closed by opposite one" trading event
         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;
        }
      //--- If a position is closed
      else
        {
         //--- check the partial closure flag and set the "Position closed" or "Position partially closed" trading event
         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;
        }
     }
//--- Balance operation on the account (clarify the event by deal type)
   if(this.m_event_code==TRADE_EVENT_FLAG_ACCOUNT_BALANCE)
     {
      //--- Initialize a trading event
      this.m_trade_event=TRADE_EVENT_NO_EVENT;
      //--- Take a deal type
      ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT);
      //--- if a deal is a balance operation
      if(deal_type==DEAL_TYPE_BALANCE)
        {
        //--- check the deal profit and set an event (funds deposit or withdrawal)
         this.m_trade_event=(this.GetProperty(EVENT_PROP_PROFIT)>0 ? TRADE_EVENT_ACCOUNT_BALANCE_REFILL : TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL);
        }
      //--- The remaining balance operation types match the ENUM_DEAL_TYPE enumeration starting from DEAL_TYPE_CREDIT
      else if(deal_type>DEAL_TYPE_BALANCE)
        {
        //--- set the event
         this.m_trade_event=(ENUM_TRADE_EVENT)deal_type;
        }
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
  }
//+------------------------------------------------------------------+

Aquí todo es simple: transmitimos al método el código de evento, y después se comprueban las banderas del código de evento. Si existe en el código la bandera comprobada, se establece el evento comercial correspondiente. Dado que puede haber varias banderas en el código, se comprueban todas las banderas posibles para este evento, y se determina el tipo de evento partiendo de sus combinaciones. A continuación, el tipo de evento se registra en la variable de clase correspondiente y se introduce en la propiedad del objeto de evento (EVENT_PROP_TYPE_EVENT).

Vamos a echar un vistazo a los demás métodos de clase:

//+------------------------------------------------------------------+
//| Return the description of the event's integer property           |
//+------------------------------------------------------------------+
string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_INTEGER property)
  {
   return
     (
      property==EVENT_PROP_TYPE_EVENT              ?  TextByLanguage("Тип события","Event 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 ID")+" #"+(string)this.GetProperty(property)                  :
      property==EVENT_PROP_MAGIC_ORDER             ?  TextByLanguage("Магический номер","Magic number")+": "+(string)this.GetProperty(property)                                           :
      property==EVENT_PROP_TIME_ORDER_POSITION     ?  TextByLanguage("Время открытия позиции","Position open time")+": "+TimeMSCtoString(this.GetProperty(property))                  :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Return the description of the event's real 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 event")+": "+::DoubleToString(this.GetProperty(property),dg) :
      property==EVENT_PROP_PRICE_OPEN        ?  TextByLanguage("Цена открытия","Open price")+": "+::DoubleToString(this.GetProperty(property),dg)                    :
      property==EVENT_PROP_PRICE_CLOSE       ?  TextByLanguage("Цена закрытия","Close price")+": "+::DoubleToString(this.GetProperty(property),dg)                   :
      property==EVENT_PROP_PRICE_SL          ?  TextByLanguage("Цена StopLoss","StopLoss price")+": "+::DoubleToString(this.GetProperty(property),dg)                :
      property==EVENT_PROP_PRICE_TP          ?  TextByLanguage("Цена TakeProfit","TakeProfit price")+": "+::DoubleToString(this.GetProperty(property),dg)            :
      property==EVENT_PROP_VOLUME_INITIAL    ?  TextByLanguage("Начальный объём","Initial volume")+": "+::DoubleToString(this.GetProperty(property),dgl)             :
      property==EVENT_PROP_VOLUME_EXECUTED   ?  TextByLanguage("Исполненный объём","Executed volume")+": "+::DoubleToString(this.GetProperty(property),dgl)          :
      property==EVENT_PROP_VOLUME_CURRENT    ?  TextByLanguage("Оставшийся объём","Remaining volume")+": "+::DoubleToString(this.GetProperty(property),dgl)          :
      property==EVENT_PROP_PROFIT            ?  TextByLanguage("Профит","Profit")+": "+::DoubleToString(this.GetProperty(property),this.m_digits_acc)                :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Return the description of the event's string property            |
//+------------------------------------------------------------------+
string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_STRING property)
  {
   return TextByLanguage("Символ","Symbol")+": \""+this.GetProperty(property)+"\"";
  }
//+------------------------------------------------------------------+
//| Return the event status name                                     |
//+------------------------------------------------------------------+
string CEvent::StatusDescription(void) const
  {
   ENUM_EVENT_STATUS status=(ENUM_EVENT_STATUS)this.GetProperty(EVENT_PROP_STATUS_EVENT);
   return
     (
      status==EVENT_STATUS_MARKET_PENDING    ?  TextByLanguage("Установлен отложенный ордер","Pending order placed") :
      status==EVENT_STATUS_MARKET_POSITION   ?  TextByLanguage("Открыта позиция","Position opened")                 :
      status==EVENT_STATUS_HISTORY_PENDING   ?  TextByLanguage("Удален отложенный ордер","Pending order removed")    :
      status==EVENT_STATUS_HISTORY_POSITION  ?  TextByLanguage("Закрыта позиция","Position closed")                  :
      status==EVENT_STATUS_BALANCE           ?  TextByLanguage("Балансная операция","Balance operation")             :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Return the trading event name                                    |
//+------------------------------------------------------------------+
string CEvent::TypeEventDescription(void) const
  {
   ENUM_TRADE_EVENT event=this.TypeEvent();
   return
     (
      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 opened")                                                  :
      event==TRADE_EVENT_POSITION_OPENED_PARTIAL         ?  TextByLanguage("Позиция открыта частично","Position opened 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               ?  TextByLanguage("Разворот позиции","Position reversal")                                                :
      event==TRADE_EVENT_POSITION_VOLUME_ADD             ?  TextByLanguage("Добавлен объём к позиции","Added volume to position")                                 :
      TextByLanguage("Нет торгового события","No trade event")
     );   
  }
//+------------------------------------------------------------------+
//| Return the name of the order/position/deal                       |
//+------------------------------------------------------------------+
string CEvent::TypeOrderDescription(void) const
  {
   ENUM_EVENT_STATUS status=this.Status();
   return
     (
      status==EVENT_STATUS_MARKET_PENDING  || status==EVENT_STATUS_HISTORY_PENDING  ?  OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_EVENT))      :
      status==EVENT_STATUS_MARKET_POSITION || status==EVENT_STATUS_HISTORY_POSITION ?  PositionTypeDescription((ENUM_POSITION_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT)) :
      status==EVENT_STATUS_BALANCE  ?  DealTypeDescription((ENUM_DEAL_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT))  :  "Unknown"
     );
  }
//+------------------------------------------------------------------+
//| Return the name of the parent order                              |
//+------------------------------------------------------------------+
string CEvent::TypeOrderBasedDescription(void) const
  {
   return OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION));
  }
//+------------------------------------------------------------------+
//| Return the position name                                         |
//+------------------------------------------------------------------+
string CEvent::TypePositionDescription(void) const
  {
   ENUM_POSITION_TYPE type=PositionTypeByOrderType((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION));
   return PositionTypeDescription(type);
  }
//+------------------------------------------------------------------+
//| Return the name of the deal/order/position reason                |
//+------------------------------------------------------------------+
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_CANCEL                           ?  TextByLanguage("Отмена","Canceled")                                                                :
      reason==EVENT_REASON_EXPIRED                          ?  TextByLanguage("Истёк срок действия","Expired")                                                    :
      reason==EVENT_REASON_DONE                             ?  TextByLanguage("Запрос выполнен полностью","Request fully executed")                            :
      reason==EVENT_REASON_DONE_PARTIALLY                   ?  TextByLanguage("Запрос выполнен частично","Request partially executed")                         :
      reason==EVENT_REASON_DONE_SL                          ?  TextByLanguage("закрытие по StopLoss","Close by StopLoss triggered")                               :
      reason==EVENT_REASON_DONE_SL_PARTIALLY                ?  TextByLanguage("Частичное закрытие по StopLoss","Partial close by StopLoss triggered")             :
      reason==EVENT_REASON_DONE_TP                          ?  TextByLanguage("закрытие по TakeProfit","Close by TakeProfit triggered")                           :
      reason==EVENT_REASON_DONE_TP_PARTIALLY                ?  TextByLanguage("Частичное закрытие по TakeProfit","Partial 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 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)
     );
  }
//+------------------------------------------------------------------+
//| Display the event properties in the journal                      |
//+------------------------------------------------------------------+
void CEvent::Print(const bool full_prop=false)
  {
   ::Print("============= ",TextByLanguage("Начало списка параметров события: \"","Beginning of event parameter list: \""),this.StatusDescription(),"\" =============");
   int beg=0, end=EVENT_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_INTEGER prop=(ENUM_EVENT_PROP_INTEGER)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=EVENT_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_DOUBLE prop=(ENUM_EVENT_PROP_DOUBLE)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=EVENT_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_STRING prop=(ENUM_EVENT_PROP_STRING)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("================== ",TextByLanguage("Конец списка параметров: \"","End of parameter list: \""),this.StatusDescription(),"\" ==================\n");
  }
//+------------------------------------------------------------------+

La lógica de todos estos métodos repite la de los métodos de representación de información sobre las órdenes, que ya hemos visto, por eso, no vamos a acentuar la atención sobre ellos, todo resulta bastante sencillo y visual.

Aquí tenemos el listado completo de la clase de evento:

//+------------------------------------------------------------------+
//|                                                        Event.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Object.mqh>
#include "\..\..\Services\DELib.mqh"
#include "..\..\Collections\HistoryCollection.mqh"
#include "..\..\Collections\MarketCollection.mqh"
//+------------------------------------------------------------------+
//| Abstract event class                                             |
//+------------------------------------------------------------------+
class CEvent : public CObject
  {
private:
   int               m_event_code;                                   // Event code
//--- Return the index of the array the event's (1) double and (2) string properties are located at
   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;                                  // Trading event
   long              m_chart_id;                                     // Control program chart ID
   int               m_digits_acc;                                   // Number of decimal places for the account currency
   long              m_long_prop[EVENT_PROP_INTEGER_TOTAL];          // Event integer properties
   double            m_double_prop[EVENT_PROP_DOUBLE_TOTAL];         // Event real properties
   string            m_string_prop[EVENT_PROP_STRING_TOTAL];         // Event string properties
//--- return the flag presence in the trading event
   bool              IsPresentEventFlag(const int event_code)  const { return (this.m_event_code & event_code)==event_code;            }

   //--- Protected parametric constructor
                     CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket);
public:
//--- Default constructor
                     CEvent(void){;}
 
//--- Set event's (1) integer, (2) real and (3) string properties
   void              SetProperty(ENUM_EVENT_PROP_INTEGER property,long value) { this.m_long_prop[property]=value;                      }
   void              SetProperty(ENUM_EVENT_PROP_DOUBLE property,double value){ this.m_double_prop[this.IndexProp(property)]=value;    }
   void              SetProperty(ENUM_EVENT_PROP_STRING property,string value){ this.m_string_prop[this.IndexProp(property)]=value;    }
//--- Return the event's (1) integer, (2) real and (3) string properties from the property array
   long              GetProperty(ENUM_EVENT_PROP_INTEGER property)      const { return this.m_long_prop[property];                     }
   double            GetProperty(ENUM_EVENT_PROP_DOUBLE property)       const { return this.m_double_prop[this.IndexProp(property)];   }
   string            GetProperty(ENUM_EVENT_PROP_STRING property)       const { return this.m_string_prop[this.IndexProp(property)];   }

//--- Return the flag of the event supporting the property
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property)        { return true; }
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property)         { return true; }
   virtual bool      SupportProperty(ENUM_EVENT_PROP_STRING property)         { return true; }

//--- Set the control program chart ID
   void              SetChartID(const long id)                                { this.m_chart_id=id;                                    }
//--- Decode the event code and set the trading event, (2) return the trading event
   void              SetTypeEvent(void);
   ENUM_TRADE_EVENT  TradeEvent(void)                                   const { return this.m_trade_event;                             }
//--- Send the event to the chart (implementation in the class descendants)
   virtual void      SendEvent(void) {;}

//--- Compare CEvent objects by a specified property (to sort the lists by a specified event object property)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Compare CEvent objects by all properties (to search for equal event objects)
   bool              IsEqual(CEvent* compared_event);
//+------------------------------------------------------------------+
//| Methods of simplified access to event object properties          |
//+------------------------------------------------------------------+
//--- Return (1) event type, (2) event time in milliseconds, (3) event status, (4) event reason, (5) deal type, (6) deal ticket, 
//--- (7) order type, based on which a deal was executed, (8) position opening order type, (9) position last order ticket, 
//--- (10) position first order ticket, (11) position ID, (12) opposite position ID, (13) magic number, (14) position open time

   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);  }
   long              TypeDeal(void)                                     const { return this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT);                  }
   long              TicketDeal(void)                                   const { return this.GetProperty(EVENT_PROP_TICKET_DEAL_EVENT);                }
   long              TypeOrderEvent(void)                               const { return this.GetProperty(EVENT_PROP_TYPE_ORDER_EVENT);                 }
   long              TypeOrderPosition(void)                            const { return this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION);              }
   long              TicketOrderEvent(void)                             const { return this.GetProperty(EVENT_PROP_TICKET_ORDER_EVENT);               }
   long              TicketOrderPosition(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              TimePosition(void)                                 const { return this.GetProperty(EVENT_PROP_TIME_ORDER_POSITION);              }
   
//--- Return (1) the price the event occurred at, (2) open price, (3) close price,
//--- (4) StopLoss price, (5) TakeProfit price, (6) profit, (7) requested volume, (8), executed volume, (9) remaining volume
   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            VolumeInitial(void)                                const { return this.GetProperty(EVENT_PROP_VOLUME_INITIAL);                   }
   double            VolumeExecuted(void)                               const { return this.GetProperty(EVENT_PROP_VOLUME_EXECUTED);                  }
   double            VolumeCurrent(void)                                const { return this.GetProperty(EVENT_PROP_VOLUME_CURRENT);                   }
   
//--- Return a symbol
   string            Symbol(void)                                       const { return this.GetProperty(EVENT_PROP_SYMBOL);                           }
   
//+------------------------------------------------------------------+
//| Descriptions of order object properties                          |
//+------------------------------------------------------------------+
//--- Return description of the order's (1) integer, (2) real and (3) string properties
   string            GetPropertyDescription(ENUM_EVENT_PROP_INTEGER property);
   string            GetPropertyDescription(ENUM_EVENT_PROP_DOUBLE property);
   string            GetPropertyDescription(ENUM_EVENT_PROP_STRING property);
//--- Return the event's (1) status and (2) type
   string            StatusDescription(void)          const;
   string            TypeEventDescription(void)       const;
//--- Return the name of an (1) order/position/deal, (2) parent order, (3) position
   string            TypeOrderDescription(void)       const;
   string            TypeOrderBasedDescription(void)  const;
   string            TypePositionDescription(void)    const;
//--- Return the name of the deal/order/position reason
   string            ReasonDescription(void)          const;

//--- Display (1) description of order properties (full_prop=true - all properties, false - only supported ones),
//--- (2) short event message (implementation in the class descendants) in the journal
   void              Print(const bool full_prop=false);
   virtual void      PrintShort(void) {;}
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CEvent::CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket) : m_event_code(event_code)
  {
   this.m_long_prop[EVENT_PROP_STATUS_EVENT]       =  event_status;
   this.m_long_prop[EVENT_PROP_TICKET_ORDER_EVENT] =  (long)ticket;
   this.m_digits_acc=(int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS);
   this.m_chart_id=::ChartID();
  }
//+------------------------------------------------------------------+
//| Compare CEvent objects by a specified property                   |
//+------------------------------------------------------------------+
int CEvent::Compare(const CObject *node,const int mode=0) const
  {
   const CEvent *event_compared=node;
//--- compare integer properties of two events
   if(mode<EVENT_PROP_INTEGER_TOTAL)
     {
      long value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_INTEGER)mode);
      long value_current=this.GetProperty((ENUM_EVENT_PROP_INTEGER)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- compare real properties of two events
   if(mode<EVENT_PROP_DOUBLE_TOTAL+EVENT_PROP_INTEGER_TOTAL)
     {
      double value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_DOUBLE)mode);
      double value_current=this.GetProperty((ENUM_EVENT_PROP_DOUBLE)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- compare string properties of two events
   else if(mode<EVENT_PROP_DOUBLE_TOTAL+EVENT_PROP_INTEGER_TOTAL+EVENT_PROP_STRING_TOTAL)
     {
      string value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_STRING)mode);
      string value_current=this.GetProperty((ENUM_EVENT_PROP_STRING)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
   return 0;
  }
//+------------------------------------------------------------------+
//| Compare CEvent objects by all properties                         |
//+------------------------------------------------------------------+
bool CEvent::IsEqual(CEvent *compared_event)
  {
   int beg=0, end=EVENT_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_INTEGER prop=(ENUM_EVENT_PROP_INTEGER)i;
      if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false; 
     }
   beg=end; end+=EVENT_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_DOUBLE prop=(ENUM_EVENT_PROP_DOUBLE)i;
      if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false; 
     }
   beg=end; end+=EVENT_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_STRING prop=(ENUM_EVENT_PROP_STRING)i;
      if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false; 
     }
//---
   return true;
  }
//+------------------------------------------------------------------+
//| Decode the event code and set a trading event                    |
//+------------------------------------------------------------------+
void CEvent::SetTypeEvent(void)
  {
//--- Pending order placed (check for matching the event code since there can only be one flag here)
   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;
     }
//--- Pending order removed (check for matching the event code since there can only be one flag here)
   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;
     }
//--- Position opened (Check the presence of multiple flags in the event code)
   if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_OPENED))
     {
      //--- If the pending order is activated by a price
      if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED))
        {
         //--- check the partial closure flag and set the "pending order activated" or "pending order partially activated" trading event
         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;
        }
      //--- check the partial opening flag and set the "Position opened" or "Position partially opened" trading event
      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;
     }
//--- Position closed (Check the presence of multiple flags in the event code)
   if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_CLOSED))
     {
      //--- if a position is closed by StopLoss
      if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL))
        {
         //--- check the partial closing flag and set the "Position closed by StopLoss" or "Position partially closed by StopLoss" trading event
         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;
        }
      //--- if a position is closed by TakeProfit
      else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP))
        {
         //--- check the partial closure flag and set the "Position closed by TakeProfit" or "Position partially closed by TakeProfit" trading event
         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;
        }
      //--- if a position is closed by an opposite one
      else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_BY_POS))
        {
         //--- check the partial closure flag and set the "Position closed by opposite one" or "Position partially closed by opposite one" trading event
         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;
        }
      //--- If a position is closed
      else
        {
         //--- check the partial closure flag and set the "Position closed" or "Position partially closed" trading event
         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;
        }
     }
//--- Balance operation on the account (clarify the event by deal type)
   if(this.m_event_code==TRADE_EVENT_FLAG_ACCOUNT_BALANCE)
     {
      //--- Initialize a trading event
      this.m_trade_event=TRADE_EVENT_NO_EVENT;
      //--- Take a deal type
      ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT);
      //--- if a deal is a balance operation
      if(deal_type==DEAL_TYPE_BALANCE)
        {
        //--- check the deal profit and set an event (funds deposit or withdrawal)
         this.m_trade_event=(this.GetProperty(EVENT_PROP_PROFIT)>0 ? TRADE_EVENT_ACCOUNT_BALANCE_REFILL : TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL);
        }
      //--- The remaining balance operation types match the ENUM_DEAL_TYPE enumeration starting with DEAL_TYPE_CREDIT
      else if(deal_type>DEAL_TYPE_BALANCE)
        {
        //--- set the event
         this.m_trade_event=(ENUM_TRADE_EVENT)deal_type;
        }
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
  }
//+------------------------------------------------------------------+
//| Return the description of the event's integer property           |
//+------------------------------------------------------------------+
string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_INTEGER property)
  {
   return
     (
      property==EVENT_PROP_TYPE_EVENT              ?  TextByLanguage("Тип события","Event 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_TIME_ORDER_POSITION     ?  TextByLanguage("Время открытия позиции","Position's opened time")+": "+TimeMSCtoString(this.GetProperty(property))                  :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Return the description of the event's real 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 event")+": "+::DoubleToString(this.GetProperty(property),dg) :
      property==EVENT_PROP_PRICE_OPEN        ?  TextByLanguage("Цена открытия","Open price")+": "+::DoubleToString(this.GetProperty(property),dg)                    :
      property==EVENT_PROP_PRICE_CLOSE       ?  TextByLanguage("Цена закрытия","Close price")+": "+::DoubleToString(this.GetProperty(property),dg)                   :
      property==EVENT_PROP_PRICE_SL          ?  TextByLanguage("Цена StopLoss","StopLoss price")+": "+::DoubleToString(this.GetProperty(property),dg)                :
      property==EVENT_PROP_PRICE_TP          ?  TextByLanguage("Цена TakeProfit","TakeProfit price")+": "+::DoubleToString(this.GetProperty(property),dg)            :
      property==EVENT_PROP_VOLUME_INITIAL    ?  TextByLanguage("Начальный объём","Initial volume")+": "+::DoubleToString(this.GetProperty(property),dgl)             :
      property==EVENT_PROP_VOLUME_EXECUTED   ?  TextByLanguage("Исполненный объём","Executed volume")+": "+::DoubleToString(this.GetProperty(property),dgl)          :
      property==EVENT_PROP_VOLUME_CURRENT    ?  TextByLanguage("Оставшийся объём","Remaining volume")+": "+::DoubleToString(this.GetProperty(property),dgl)          :
      property==EVENT_PROP_PROFIT            ?  TextByLanguage("Профит","Profit")+": "+::DoubleToString(this.GetProperty(property),this.m_digits_acc)                :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Return the description of the event's string property            |
//+------------------------------------------------------------------+
string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_STRING property)
  {
   return TextByLanguage("Символ","Symbol")+": \""+this.GetProperty(property)+"\"";
  }
//+------------------------------------------------------------------+
//| Return the event status name                                     |
//+------------------------------------------------------------------+
string CEvent::StatusDescription(void) const
  {
   ENUM_EVENT_STATUS status=(ENUM_EVENT_STATUS)this.GetProperty(EVENT_PROP_STATUS_EVENT);
   return
     (
      status==EVENT_STATUS_MARKET_PENDING    ?  TextByLanguage("Установлен отложенный ордер","Pending order placed") :
      status==EVENT_STATUS_MARKET_POSITION   ?  TextByLanguage("Открыта позиция","Position opened")                 :
      status==EVENT_STATUS_HISTORY_PENDING   ?  TextByLanguage("Удален отложенный ордер","Pending order removed")    :
      status==EVENT_STATUS_HISTORY_POSITION  ?  TextByLanguage("Закрыта позиция","Position closed")                  :
      status==EVENT_STATUS_BALANCE           ?  TextByLanguage("Балансная операция","Balance operation")             :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Return the trading event name                                    |
//+------------------------------------------------------------------+
string CEvent::TypeEventDescription(void) const
  {
   ENUM_TRADE_EVENT event=this.TypeEvent();
   return
     (
      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 opened")                                                  :
      event==TRADE_EVENT_POSITION_OPENED_PARTIAL         ?  TextByLanguage("Позиция открыта частично","Position opened 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               ?  TextByLanguage("Разворот позиции","Position reversal")                                                :
      event==TRADE_EVENT_POSITION_VOLUME_ADD             ?  TextByLanguage("Добавлен объём к позиции","Added volume to position")                                 :
      TextByLanguage("Нет торгового события","No trade event")
     );   
  }
//+------------------------------------------------------------------+
//| Return the name of the order/position/deal                       |
//+------------------------------------------------------------------+
string CEvent::TypeOrderDescription(void) const
  {
   ENUM_EVENT_STATUS status=this.Status();
   return
     (
      status==EVENT_STATUS_MARKET_PENDING  || status==EVENT_STATUS_HISTORY_PENDING  ?  OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_EVENT))      :
      status==EVENT_STATUS_MARKET_POSITION || status==EVENT_STATUS_HISTORY_POSITION ?  PositionTypeDescription((ENUM_POSITION_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT)) :
      status==EVENT_STATUS_BALANCE  ?  DealTypeDescription((ENUM_DEAL_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT))  :  "Unknown"
     );
  }
//+------------------------------------------------------------------+
//| Return the name of the parent order                              |
//+------------------------------------------------------------------+
string CEvent::TypeOrderBasedDescription(void) const
  {
   return OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION));
  }
//+------------------------------------------------------------------+
//| Return the position name                                         |
//+------------------------------------------------------------------+
string CEvent::TypePositionDescription(void) const
  {
   ENUM_POSITION_TYPE type=PositionTypeByOrderType((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION));
   return PositionTypeDescription(type);
  }
//+------------------------------------------------------------------+
//| Return the name of the deal/order/position reason                |
//+------------------------------------------------------------------+
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_CANCEL                           ?  TextByLanguage("Отмена","Canceled")                                                                :
      reason==EVENT_REASON_EXPIRED                          ?  TextByLanguage("Истёк срок действия","Expired")                                                    :
      reason==EVENT_REASON_DONE                             ?  TextByLanguage("Запрос выполнен полностью","Request fully executed")                            :
      reason==EVENT_REASON_DONE_PARTIALLY                   ?  TextByLanguage("Запрос выполнен частично","Request partially executed")                         :
      reason==EVENT_REASON_DONE_SL                          ?  TextByLanguage("закрытие по StopLoss","Close by StopLoss triggered")                               :
      reason==EVENT_REASON_DONE_SL_PARTIALLY                ?  TextByLanguage("Частичное закрытие по StopLoss","Partial close by StopLoss triggered")             :
      reason==EVENT_REASON_DONE_TP                          ?  TextByLanguage("закрытие по TakeProfit","Close by TakeProfit triggered")                           :
      reason==EVENT_REASON_DONE_TP_PARTIALLY                ?  TextByLanguage("Частичное закрытие по TakeProfit","Partial 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 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)
     );
  }
//+------------------------------------------------------------------+
//| Display the event properties to the journal                      |
//+------------------------------------------------------------------+
void CEvent::Print(const bool full_prop=false)
  {
   ::Print("============= ",TextByLanguage("Начало списка параметров события: \"","Beginning of event parameter list: \""),this.StatusDescription(),"\" =============");
   int beg=0, end=EVENT_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_INTEGER prop=(ENUM_EVENT_PROP_INTEGER)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=EVENT_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_DOUBLE prop=(ENUM_EVENT_PROP_DOUBLE)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=EVENT_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_STRING prop=(ENUM_EVENT_PROP_STRING)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("================== ",TextByLanguage("Конец списка параметров: \"","End of parameter list: \""),this.StatusDescription(),"\" ==================\n");
  }
//+------------------------------------------------------------------+

La clase del evento básico abstracto está preparada. Ahora necesitamos crear cinco clases herederas, que constituirán un evento que indica su tipo: colocación de una orden pendiente, eliminación de una orden pendiente, apertura de posición, cierre de posición y operación de balance.

Creamos una clase heredera que tenga el estado de evento "Colocar una orden pendiente".

En la carpeta de la biblioteca Events, creamos un nuevo archivo de clase CEventOrderPlased con el nombre EventOrderPlased.mqh con la clase básica CEvent y registramos en ella todas las inclusiones y métodos necesarios:

//+------------------------------------------------------------------+
//|                                             EventOrderPlased.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Event.mqh"
//+------------------------------------------------------------------+
//| Placing a pending order event                                    |
//+------------------------------------------------------------------+
class CEventOrderPlased : public CEvent
  {
public:
//--- Constructor
                     CEventOrderPlased(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_MARKET_PENDING,event_code,ticket) {}
//--- Supported (1) real and (2) integer order properties
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
//--- (1) Display a brief message about the event in the journal, (2) Send the event to the chart
   virtual void      PrintShort(void);
   virtual void      SendEvent(void);
  };
//+------------------------------------------------------------------+

Transmitmos al constructor de clase el código de evento y el ticket de la orden o transacción que ha provocado el evento; en la lista de inicialización enviamos a la clase padre el estado de evento " Colocar una orden pendiente" (EVENT_STATUS_MARKET_PENDING), el código de evento y el ticket de la orden o transacción:

CEventOrderPlased(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_MARKET_PENDING,event_code,ticket) {}

Ya hemos analizado en la primera parte de la descripción de la biblioteca los métodos que retornan las banderas de soporte por parte del objeto de estas u otras propiedades SupportProperty(), aquí todo es exactamente igual:

//+------------------------------------------------------------------+
//| Return 'true' if the event supports the passed                   |
//| integer property, otherwise return 'false'                       |
//+------------------------------------------------------------------+
bool CEventOrderPlased::SupportProperty(ENUM_EVENT_PROP_INTEGER property)
  {
   if(property==EVENT_PROP_TYPE_DEAL_EVENT         ||
      property==EVENT_PROP_TICKET_DEAL_EVENT       ||
      property==EVENT_PROP_TYPE_ORDER_POSITION     ||
      property==EVENT_PROP_TICKET_ORDER_POSITION   ||
      property==EVENT_PROP_POSITION_ID             ||
      property==EVENT_PROP_POSITION_BY_ID          ||
      property==EVENT_PROP_TIME_ORDER_POSITION
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Return 'true' if the event supports the passed                   |
//| real property, otherwise return 'false'                          |
//+------------------------------------------------------------------+
bool CEventOrderPlased::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
  {
   if(property==EVENT_PROP_PRICE_CLOSE             ||
      property==EVENT_PROP_PROFIT
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+

En el objeto de evento padre CEvent, existe el método Print(), que muestra información completa sobre todas las propiedades soportadas de este objeto de evento; además, existe el método virtual PrintShort(), que permite mostrar en dos líneas suficiente información sobre un evento en el diario del terminal.
La implementación del método PrintShort() en cada clase heredera del objeto de evento básico será individual, ya que los eventos también se ubican según su procedencia:

//+------------------------------------------------------------------+
//| Display a brief event message in the journal                     |
//+------------------------------------------------------------------+
void CEventOrderPlased::PrintShort(void)
  {
   string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   string sl=(this.PriceStopLoss()>0 ? ", sl "+::DoubleToString(this.PriceStopLoss(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
   string tp=(this.PriceTakeProfit()>0 ? ", tp "+::DoubleToString(this.PriceTakeProfit(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
   string vol=::DoubleToString(this.VolumeInitial(),DigitsLots(this.Symbol()));
   string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : "");
   string type=this.TypeOrderDescription()+" #"+(string)this.TicketOrderEvent();
   string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceOpen(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS));
   string txt=head+this.Symbol()+" "+vol+" "+type+price+sl+tp+magic;
   ::Print(txt);
  }
//+------------------------------------------------------------------+

Aquí:

  • Creamos el encabezado del mensaje, que constará de la descripción del tipo de evento y la hora del mismo
  • Si se ha establecido StopLoss para la orden, creamos una línea con su descripción, de lo contrario, la línea quedará vacía
  • Si se ha establecido TakeProfit para la orden, creamos una línea con su descripción, de lo contrario, la línea quedará vacía
  • Creamos una línea que indique el volumen de la orden
  • Si se ha establecido el número mágico para la orden, creamos una línea con su descripción, de lo contrario, la línea quedará vacía
  • Creamos una línea que indique el tipo de la orden y su ticket
  • Creamos una línea que indique el precio de colocación de la orden y el símbolo en el que se coloca la misma
  • Creamos una línea completa a partir de todas las descripciones hechas anteriormente
  • Mostramos la línea creada en el diario de registro

El método para enviar un evento de usuario al gráfico es muy sencillo:

//+------------------------------------------------------------------+
//| Send the event to the chart                                      |
//+------------------------------------------------------------------+
void CEventOrderPlased::SendEvent(void)
  {
   this.PrintShort();
   ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.TicketOrderEvent(),this.PriceOpen(),this.Symbol());
  }
//+------------------------------------------------------------------+

Primero se muestra un mensaje breve sobre el evento en el diario, después se envía el evento de usuario EventChartCustom() el gráfico indicado en el identificador del gráfico m_chart_id en la clase de evento básica CEvent.
Enviamos el evento m_trade_event al identificador de evento
al parámetro del tipo long, el ticket de la orden,
al parámetro del tipo double
, el precio de colocación de la orden,
al parámetro del tipo string , el símbolo de la orden.

Querríamos destacar que más adelante se creará una clase para mostrar mensajes, donde se podrán indicar niveles de mensajes que permiten mostrar solo la información esencial en el diario. Ahora (en la etapa de construcción de la biblioteca), todos los mensajes se muestran por defecto.

Vamos a analizar los listados completos de las demás clases de evento.
Clase de evento "Eliminación de una orden pendiente":

//+------------------------------------------------------------------+
//|                                            EventOrderRemoved.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Event.mqh"
//+------------------------------------------------------------------+
//| Event of placing a pending order                                 |
//+------------------------------------------------------------------+
class CEventOrderRemoved : public CEvent
  {
public:
//--- Constructor
                     CEventOrderRemoved(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_HISTORY_PENDING,event_code,ticket) {}
//--- Supported (1) real and (2) integer order properties
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
//--- (1) Display a brief message about the event in the journal, (2) Send the event to the chart
   virtual void      PrintShort(void);
   virtual void      SendEvent(void);
  };
//+------------------------------------------------------------------+
//| Return 'true' if the event supports the passed                   |
//| integer property, otherwise, return 'false'                      |
//+------------------------------------------------------------------+
bool CEventOrderRemoved::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_TIME_ORDER_POSITION
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Return 'true' if the event supports the passed                   |
//| real property, otherwise, return 'false'                         |
//+------------------------------------------------------------------+
bool CEventOrderRemoved::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
  {
   return(property==EVENT_PROP_PROFIT ? false : true);
  }
//+------------------------------------------------------------------+
//| Display a brief event message in the journal                     |
//+------------------------------------------------------------------+
void CEventOrderRemoved::PrintShort(void)
  {
   string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   string sl=(this.PriceStopLoss()>0 ? ", sl "+::DoubleToString(this.PriceStopLoss(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
   string tp=(this.PriceTakeProfit()>0 ? ", tp "+::DoubleToString(this.PriceTakeProfit(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
   string vol=::DoubleToString(this.VolumeInitial(),DigitsLots(this.Symbol()));
   string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : "");
   string type=this.TypeOrderDescription()+" #"+(string)this.TicketOrderEvent();
   string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceOpen(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS));
   string txt=head+this.Symbol()+" "+vol+" "+type+price+sl+tp+magic;
   ::Print(txt);
  }
//+------------------------------------------------------------------+
//| Send the event to the chart                                      |
//+------------------------------------------------------------------+
void CEventOrderRemoved::SendEvent(void)
  {
   this.PrintShort();
   ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.TicketOrderEvent(),this.PriceOpen(),this.Symbol());
  }
//+------------------------------------------------------------------+

Clase de evento "Apertura de posición":

//+------------------------------------------------------------------+
//|                                            EventPositionOpen.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Event.mqh"
//+------------------------------------------------------------------+
//| Position opening event                                           |
//+------------------------------------------------------------------+
class CEventPositionOpen : public CEvent
  {
public:
//--- Constructor
                     CEventPositionOpen(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_MARKET_POSITION,event_code,ticket) {}
//--- Supported (1) real and (2) integer order properties
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
//--- (1) Display a brief message about the event in the journal, (2) Send the event to the chart
   virtual void      PrintShort(void);
   virtual void      SendEvent(void);
  };
//+------------------------------------------------------------------+
//| Return 'true' if the event supports the passed                   |
//| integer property, otherwise, return 'false'                      |
//+------------------------------------------------------------------+
bool CEventPositionOpen::SupportProperty(ENUM_EVENT_PROP_INTEGER property)
  {
   return(property==EVENT_PROP_POSITION_BY_ID ? false : true);
  }
//+------------------------------------------------------------------+
//| Return 'true' if the order supports the passed                   |
//| real property, otherwise, return 'false'                         |
//+------------------------------------------------------------------+
bool CEventPositionOpen::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
  {
   if(property==EVENT_PROP_PRICE_CLOSE ||
      property==EVENT_PROP_PROFIT
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Display a brief event message in the journal                     |
//+------------------------------------------------------------------+
void CEventPositionOpen::PrintShort(void)
  {
   string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   string order=(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED) ? " #"+(string)this.TicketOrderPosition() : "");
   string activated=(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED) ? TextByLanguage(" активацией ордера "," by ")+this.TypeOrderBasedDescription() : "");
   string sl=(this.PriceStopLoss()>0 ? ", sl "+::DoubleToString(this.PriceStopLoss(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
   string tp=(this.PriceTakeProfit()>0 ? ", tp "+::DoubleToString(this.PriceTakeProfit(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
   string vol=::DoubleToString(this.VolumeInitial(),DigitsLots(this.Symbol()));
   string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : "");
   string type=this.TypePositionDescription()+" #"+(string)this.PositionID();
   string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceOpen(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS));
   string txt=head+this.Symbol()+" "+vol+" "+type+activated+order+price+sl+tp+magic;
   ::Print(txt);
  }
//+------------------------------------------------------------------+
//| Send the event to the chart                                      |
//+------------------------------------------------------------------+
void CEventPositionOpen::SendEvent(void)
  {
   this.PrintShort();
   ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.PositionID(),this.PriceOpen(),this.Symbol());
  }
//+------------------------------------------------------------------+

Clase de evento "Cierre de posición":

//+------------------------------------------------------------------+
//|                                           EventPositionClose.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Event.mqh"
//+------------------------------------------------------------------+
//| Position opening event                                           |
//+------------------------------------------------------------------+
class CEventPositionClose : public CEvent
  {
public:
//--- Constructor
                     CEventPositionClose(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_HISTORY_POSITION,event_code,ticket) {}
//--- Supported (1) real and (2) integer order properties
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
//--- (1) Display a brief message about the event in the journal, (2) Send the event to the chart
   virtual void      PrintShort(void);
   virtual void      SendEvent(void);
  };
//+------------------------------------------------------------------+
//| Return 'true' if the event supports the passed                   |
//| integer property, otherwise, return 'false'                      |
//+------------------------------------------------------------------+
bool CEventPositionClose::SupportProperty(ENUM_EVENT_PROP_INTEGER property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| Return 'true' if the event supports the passed                   |
//| real property, otherwise, return 'false'                         |
//+------------------------------------------------------------------+
bool CEventPositionClose::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| Display a brief message about the event in the journal           |
//+------------------------------------------------------------------+
void CEventPositionClose::PrintShort(void)
  {
   string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   string opposite=(this.IsPresentEventFlag(TRADE_EVENT_FLAG_BY_POS) ? " by "+this.TypeOrderDescription()+" #"+(string)this.PositionByID() : "");
   string vol=::DoubleToString(this.VolumeExecuted(),DigitsLots(this.Symbol()));
   string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : "");
   string type=this.TypePositionDescription()+" #"+(string)this.PositionID()+opposite;
   string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceClose(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS));
   string profit=TextByLanguage(", профит: ",", profit: ")+::DoubleToString(this.Profit(),this.m_digits_acc)+" "+::AccountInfoString(ACCOUNT_CURRENCY);
   string txt=head+this.Symbol()+" "+vol+" "+type+price+magic+profit;
   ::Print(txt);
  }
//+------------------------------------------------------------------+
//| Send the event to the chart                                      |
//+------------------------------------------------------------------+
void CEventPositionClose::SendEvent(void)
  {
   this.PrintShort();
   ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.PositionID(),this.PriceClose(),this.Symbol());
  }
//+------------------------------------------------------------------+

Clase de evento "Operación de balance":

//+------------------------------------------------------------------+
//|                                        EventBalanceOperation.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Event.mqh"
//+------------------------------------------------------------------+
//| Position opening event                                           |
//+------------------------------------------------------------------+
class CEventBalanceOperation : public CEvent
  {
public:
//--- Constructor
                     CEventBalanceOperation(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_BALANCE,event_code,ticket) {}
//--- Supported (1) real and (2) integer order properties
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
   virtual bool      SupportProperty(ENUM_EVENT_PROP_STRING property);
//--- (1) Display a brief message about the event in the journal, (2) Send the event to the chart
   virtual void      PrintShort(void);
   virtual void      SendEvent(void);
  };
//+------------------------------------------------------------------+
//| Return 'true' if the event supports the passed                   |
//| integer property, otherwise, return 'false'                      |
//+------------------------------------------------------------------+
bool CEventBalanceOperation::SupportProperty(ENUM_EVENT_PROP_INTEGER property)
  {
   if(property==EVENT_PROP_TYPE_ORDER_EVENT        ||
      property==EVENT_PROP_TYPE_ORDER_POSITION     ||
      property==EVENT_PROP_TICKET_ORDER_EVENT      ||
      property==EVENT_PROP_TICKET_ORDER_POSITION   ||
      property==EVENT_PROP_POSITION_ID             ||
      property==EVENT_PROP_POSITION_BY_ID          ||
      property==EVENT_PROP_POSITION_ID             ||
      property==EVENT_PROP_MAGIC_ORDER             ||
      property==EVENT_PROP_TIME_ORDER_POSITION
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Return 'true' if the event supports the passed                   |
//| real property, otherwise, return 'false'                         |
//+------------------------------------------------------------------+
bool CEventBalanceOperation::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
  {
   return(property==EVENT_PROP_PROFIT ? true : false);
  }
//+------------------------------------------------------------------+
//| Return 'true' if the event supports the passed                   |
//| string property, otherwise, return 'false'                       |
//+------------------------------------------------------------------+
bool CEventBalanceOperation::SupportProperty(ENUM_EVENT_PROP_STRING property)
  {
   return false;
  }
//+------------------------------------------------------------------+
//| Display a brief message about the event in the journal           |
//+------------------------------------------------------------------+
void CEventBalanceOperation::PrintShort(void)
  {
   string head="- "+this.StatusDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   ::Print(head+this.TypeEventDescription()+": "+::DoubleToString(this.Profit(),this.m_digits_acc)+" "+::AccountInfoString(ACCOUNT_CURRENCY));
  }
//+------------------------------------------------------------------+
//| Send the event to the chart                                      |
//+------------------------------------------------------------------+
void CEventBalanceOperation::SendEvent(void)
  {
   this.PrintShort();
   ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.TypeEvent(),this.Profit(),::AccountInfoString(ACCOUNT_CURRENCY));
  }
//+------------------------------------------------------------------+

Como podemos ver por los listados, las clases se distinguen solo por el conjunto de propiedades soportadas, por el estado enviado al constructor de la clase padre y por los métodos PrintShort(), puesto que cada evento tiene sus particularidades que se deben reflejar en el diario de registro. Todo lo mencionado se comprende bien a partir de los listados de métodos, y podrá analizar estos por sí mismo, por eso, no vamos a focalizar la atención sobre ellos: vamos a pasar a la creación de la clase de colección de eventos.

Colección de eventos comerciales

En la cuarta parte de la biblioteca pusimos a prueba la definición de eventos en la cuenta y la muestra de estos en el diario de registro y el asesor. Pero solo pudimos monitorear el último evento sucedido; además, todo estaba organizado en la clase básica de la biblioteca CEngine.
La solución correcta consistiráen sacar todos a una clase aparte y procesar en la misma todos los eventos que sucedan.

Para estos objetivos hemos creado los objetos de evento. Ahora, debemos crear una clase que procese cualquier número de eventos sucedidos simultáneamente. Y es que puede darse la situación en que, por ejemplo, se eliminen o coloquen en un ciclo órdenes pendientes, o bien se cierren varias posiciones al mismo tiempo.
El principio que pusimos a prueba, en dichas situaciones, solo nos daba el último evento de varios realizados de una sola vez. Consideramos que esto es inexacto. Por eso, vamos a a crear una clase que guardará en la lista de colección de eventos todos los eventos que han sucedido de una sola vez. Asimismo, en el futuro será posible (con la ayuda de los métodos de estas clases) pasar por la historia de la cuenta y recrear todo lo que ha sucedido en ella desde el momento de su apertura.

En la carpeta DoEasy\Collections, creamos un nuevo archivo de la clase CEventsCollection con el nombre EventsCollection.mqh. Tenemos que hacer que CListObj sea la clase básica.

Rellenamos directamente la plantilla nuevamente creada con todas las inclusiones, miembros y métodos necesarios:

//+------------------------------------------------------------------+
//|                                             EventsCollection.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#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"
//+------------------------------------------------------------------+
//| Collection of account events                                     |
//+------------------------------------------------------------------+
class CEventsCollection : public CListObj
  {
private:
   CListObj          m_list_events;                   // List of events
   bool              m_is_hedge;                      // Hedge account flag
   long              m_chart_id;                      // Control program chart ID
   ENUM_TRADE_EVENT  m_trade_event;                   // Account trading event
   CEvent            m_event_instance;                // Event object for searching by property
   
//--- Create a trading event depending on the order status
   void              CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market);
//--- Select and return the list of market pending orders
   CArrayObj*        GetListMarketPendings(CArrayObj* list);
//--- Select and return the list of historical (1) removed pending orders, (2) deals, (3) all closing orders 
   CArrayObj*        GetListHistoryPendings(CArrayObj* list);
   CArrayObj*        GetListDeals(CArrayObj* list);
   CArrayObj*        GetListCloseByOrders(CArrayObj* list);
//--- Select and return the list of (1) all position orders by its ID, (2) all deal positions by its ID
//--- (3) all market entry deals by position ID, (4) all market exit deals by position ID
   CArrayObj*        GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsInByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsOutByPosID(CArrayObj* list,const ulong position_id);
//--- Return the total volume of all deals (1) IN, (2) OUT of the position by its ID
   double            SummaryVolumeDealsInByPosID(CArrayObj* list,const ulong position_id);
   double            SummaryVolumeDealsOutByPosID(CArrayObj* list,const ulong position_id);
//--- Return the (1) first, (2) last and (3) closing order from the list of all position order, (4) an order by ticket
   COrder*           GetFirstOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetLastOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetCloseByOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetOrderByTicket(CArrayObj* list,const ulong order_ticket);
//--- Return the flag of the event object presence in the event list
   bool              IsPresentEventInList(CEvent* compared_event);
   
public:
//--- Select events from the collection with time within the range from begin_time to end_time
   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0);
//--- Return the full event collection list "as is"
   CArrayObj        *GetList(void)                                                                       { return &this.m_list_events;                                           }
//--- Return the list by selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   CArrayObj        *GetList(ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
//--- Update the list of events
   void              Refresh(CArrayObj* list_history,
                             CArrayObj* list_market,
                             const bool is_history_event,
                             const bool is_market_event,
                             const int  new_history_orders,
                             const int  new_market_pendings,
                             const int  new_market_positions,
                             const int  new_deals);
//--- Set the control program chart ID
   void              SetChartID(const long id)        { this.m_chart_id=id;         }
//--- Return the last trading event on the account
   ENUM_TRADE_EVENT  GetLastTradeEvent(void)    const { return this.m_trade_event;  }
//--- Reset the last trading event
   void              ResetLastTradeEvent(void)        { this.m_trade_event=TRADE_EVENT_NO_EVENT;   }
//--- Constructor
                     CEventsCollection(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CEventsCollection::CEventsCollection(void) : m_trade_event(TRADE_EVENT_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();
  }
//+------------------------------------------------------------------+

Reseteamos el evento comercial en el constructor de la clase, en su lista de inicialización
en el cuerpo del constructor, limpiamos la lista de colección,
definimos su clasificación de acuerdo con la hora del evento
,
indicamos el identificador de la lista de colección del evento,
establecemos la bandera de la cuenta con el tipo de cobertura y
definimos el identificador del gráfico del programa de control como el gráfico actual
.

Vamos a analizar los métodos necesarios para trabajar con la clase.

En la sección privada de la clase se declaran los miembros de clase:

   CListObj          m_list_events;                   // Event list
   bool              m_is_hedge;                      // Hedge account flag
   long              m_chart_id;                      // Control program chart ID
   ENUM_TRADE_EVENT  m_trade_event;                   // Account trading event
   CEvent            m_event_instance;                // Event object for searching by property

La lista de eventos m_list_eventses una lista basada en CListObj. En esta añadiremos y guardaremos los eventos que suceden en la cuenta desde el momento de inicio del programa. Y desde esta recibiremos el número necesario de eventos múltiples sucedidos de una sola vez.
La bandera de cuenta con el tipo cobertura m_is_hedgenos será necesaria para guardar y obtener el tipo de cuenta. Del valor de la bandera (del tipo de cuenta) dependerá qué bloque procesará los eventos sucedidos en la cuenta
El identificador del gráfico del programa de controlm_chart_ides el identificador del gráfico al que se enviarán los eventos de usuario sobre lo que sucede en la cuenta. Este identificador se enviará a los objetos de evento, y ya desde ellos se enviarán los eventos al gráfico. Es posible establecer el identificador del programa de control mediante un método creado con este objetivo.
Evento comercial en la cuenta m_trade_event: en esta variable guardaremos el último evento sucedido en la cuenta.
El objeto de evento para la búsqueda según la propiedad m_event_instancees un objeto de muestra especial para el uso interno en el método, que retorna la lista de eventos con las fechas establecidas de inicio y finalización para el intervalo de búsqueda. Ya analizamos exactamente el mismo método en la tercera parte de la descripción de la biblioteca, al discutir la organización de la búsqueda en las listas según diferentes criterios.

Aquí mismo, en la sección privada, se ubican los métodos necesarios para el funcionamiento de la clase:

//--- Create a trading event depending on the order status
   void              CreateNewEvent(COrder* order,CArrayObj* list_history);
//--- Select and return the list of market pending orders
   CArrayObj*        GetListMarketPendings(CArrayObj* list);
//--- Select and return the list of historical (1) orders, (2) removed pending orders, 
//--- (3) deals, (4) all position orders by its ID, (5) all position deals by its ID
//--- (6) all market entry deals by position ID, (7) all market exit deals by position ID
//--- (7) all closing orders
   CArrayObj*        GetListHistoryOrders(CArrayObj* list);
   CArrayObj*        GetListHistoryPendings(CArrayObj* list);
   CArrayObj*        GetListDeals(CArrayObj* list);
   CArrayObj*        GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsInByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsOutByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllCloseByOrders(CArrayObj* list);
//--- Return the total volume of all deals (1) IN, (2) OUT position by its ID
   double            SummaryVolumeDealsInByPosID(CArrayObj* list,const ulong position_id);
   double            SummaryVolumeDealsOutByPosID(CArrayObj* list,const ulong position_id);
//--- Return the (1) first, (2) last and (3) closing order from the list of all position order, (4) an order by ticket
   COrder*           GetFirstOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetLastOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetCloseByOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetOrderByTicket(CArrayObj* list,const ulong order_ticket);
//--- Return the flag of the event object presence in the event list
   bool              IsPresentEventInList(CEvent* compared_event);

El método CreateNewEvent(), que crea un evento comercial dependiendo del estado de la orden, se usa en el método principal de la clase Refresh(). Lo veremos más tarde, cuando analicemos el método Refresh()

Los métodos de obtención de listas de distintos tipos de órdenes son bastante sencillos: el principio de funcionamiento de la selección según una propiedad establecida se discutió en la tercera parte de la descripción de la biblioteca, por lo que aquí solo recordaremos brevemente que ciertos métodos constan de varias iteraciones de selección según las propiedades necesarias.

Método de obtención de la lista de órdenes pendientes de mercado:

//+------------------------------------------------------------------+
//| Select only market pending orders from the list                  |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListMarketPendings(CArrayObj* list)
  {
   if(list.Type()!=COLLECTION_MARKET_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком рыночной коллекции","Error. List is not a list of market collection"));
      return NULL;
     }
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_PENDING,EQUAL);
   return list_orders;
  }
//+------------------------------------------------------------------+

Primero se comprueba el tipo de la lista transmitida al método, y si no es una lista de colección de mercado, se mostrará un mensaje sobre el error y se retornará una lista vacía.

A continuación, las órdenes con el estado "Orden pendiente de mercado" son elegidas de la lista transmitida al método y se retorna la lista obtenida.

Métodos de obtención de las listas de órdenes pendientes eliminadas, transacciones y órdenes de cierre, colocadas al cerrar una posición con la opuesta:

//+------------------------------------------------------------------+
//| Select only removed pending orders from the list                 |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListHistoryPendings(CArrayObj* list)
  {
   if(list.Type()!=COLLECTION_HISTORY_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of the history collection"));
      return NULL;
     }
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_PENDING,EQUAL);
   return list_orders;
  }
//+------------------------------------------------------------------+
//| Select only deals from the list                                  |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListDeals(CArrayObj* list)
  {
   if(list.Type()!=COLLECTION_HISTORY_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of the history collection"));
      return NULL;
     }
   CArrayObj* list_deals=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
   return list_deals;
  }
//+------------------------------------------------------------------+
//|  Return the list of all closing CloseBy orders from the list     |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListCloseByOrders(CArrayObj *list)
  {
   if(list.Type()!=COLLECTION_HISTORY_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of the history collection"));
      return NULL;
     }
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,ORDER_TYPE_CLOSE_BY,EQUAL);
   return list_orders;
  }
//+------------------------------------------------------------------+

Aquí todo es igual que en el retorno de la lista de órdenes pendientes activas:

Se compara el tipo de lista, y si la lista no es una lista de colección histórica, se muestra un mensaje y se retorna NULL.
A continuación, las órdenes con el estado "Orden pendiente eliminada", "Transacción" son elegidas de la lista transmitida al método, o se seleccionan órdenes según el tipo ORDER_TYPE_CLOSE_BY dependiendo del método, y se retorna la lista obtenida.

Método de obtención de la lista con todas las órdenes pertenecientes a una posición según su identificador:

//+------------------------------------------------------------------+
//|  Return the list of all position orders by its ID                |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id)
  {
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_ID,position_id,EQUAL);
   list_orders=CSelect::ByOrderProperty(list_orders,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,NO_EQUAL);
   return list_orders;
  }
//+------------------------------------------------------------------+

En primer lugar, usando la lista pasada al método, seleccionamos en una lista aparte todos los objetos en los que hay un puntero al identificador de la posición transmitido al método por su parámetro,
A continuación, se eliminan todas las transacciones de la lista obtenida. La lista definitiva se retorna al programa que ha realizado la llamada. El resultado retornado por el método puede ser NULL, por eso, debemos comprobar en el programa que ha realizado la llamada qué nos ha retornado este método.

Método de obtención de la lista con todas las transacciones pertenecientes a una posición según su identificador:

//+------------------------------------------------------------------+
//| Return the list of all position deals by its ID                  |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListAllDealsByPosID(CArrayObj *list,const ulong position_id)
  {
   if(list.Type()!=COLLECTION_HISTORY_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of the history collection"));
      return NULL;
     }
   CArrayObj* list_deals=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_ID,position_id,EQUAL);
   list_deals=CSelect::ByOrderProperty(list_deals,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
   return list_deals;
  }
//+------------------------------------------------------------------+

Primero se compara el tipo de lista, y si la lista no es una lista de colección histórica, se muestra un mensaje sobre ello y se retorna NULL.
A continuación, usando la lista pasada al método, seleccionamos en una lista aparte todos los objetos en los que hay un puntero al identificador de la posición transmitido al método por su parámetro,
Acto seguido, en la la lista obtenida, se dejan solo las transacciones. La lista definitiva se retorna al programa que ha realizado la llamada. El resultado retornado por el método puede ser NULL, por eso, debemos comprobar en el programa que ha realizado la llamada qué nos ha retornado este método.

Método de obtención de la lista con todas las transacciones de entrada en el mercado, pertenecientes a una posición según su identificador:

//+------------------------------------------------------------------+
//| Return the list of all market entry deals (IN)                   |
//| by position ID                                                   |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListAllDealsInByPosID(CArrayObj *list,const ulong position_id)
  {
   CArrayObj* list_deals=this.GetListAllDealsByPosID(list,position_id);
   list_deals=CSelect::ByOrderProperty(list_deals,ORDER_PROP_DEAL_ENTRY,DEAL_ENTRY_IN,EQUAL);
   return list_deals;
  }
//+------------------------------------------------------------------+

En primer lugar, usando la lista pasada al método, seleccionamos en una lista aparte todas las transacciones en las que hay un puntero al identificador de la posición transmitido al método por su parámetro,
Acto seguido, en la la lista obtenida, se dejan solo las transacciones con el tipo DEAL_ENTRY_IN. La lista definitiva se retorna al programa que ha realizado la llamada. El resultado retornado por el método puede ser NULL, por eso, debemos comprobar en el programa que ha realizado la llamada qué nos ha retornado este método.

Método de obtención de la lista con todas las transacciones de salida del mercado, pertenecientes a una posición según su identificador:

//+------------------------------------------------------------------+
//| Return the list of all market exit deals (OUT)                   |
//| by position ID                                                   |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListAllDealsOutByPosID(CArrayObj *list,const ulong position_id)
  {
   CArrayObj* list_deals=this.GetListAllDealsByPosID(list,position_id);
   list_deals=CSelect::ByOrderProperty(list_deals,ORDER_PROP_DEAL_ENTRY,DEAL_ENTRY_OUT,EQUAL);
   return list_deals;
  }
//+------------------------------------------------------------------+

En primer lugar, usando la lista pasada al método, seleccionamos en una lista aparte todas las transacciones en las que hay un puntero al identificador de la posición transmitido al método por su parámetro,
Acto seguido, en la la lista obtenida, se dejan solo las transacciones con el tipo DEAL_ENTRY_OUT. La lista definitiva se retorna al programa que ha realizado la llamada. El resultado retornado por el método puede ser NULL, por eso, debemos comprobar en el programa que ha realizado la llamada qué nos ha retornado este método.

Método que retorna el volumen sumado de todas las transacciones de una posición de entrada en el mercado, según su identificador:

//+------------------------------------------------------------------+
//| Return the total volume of all deals of IN position              |
//| by its ID                                                        |
//+------------------------------------------------------------------+
double CEventsCollection::SummaryVolumeDealsInByPosID(CArrayObj *list,const ulong position_id)
  {
   double vol=0.0;
   CArrayObj* list_in=this.GetListAllDealsInByPosID(list,position_id);
   if(list_in==NULL)
      return 0;
   for(int i=0;i<list_in.Total();i++)
     {
      COrder* deal=list_in.At(i);
      if(deal==NULL)
         continue;
      vol+=deal.Volume();
     }
   return vol;
  }
//+------------------------------------------------------------------+

Primero obtenemos la lista con todas las transacciones de entrada en el mercado, a continuación, sumamos en el ciclo los volúmenes de todas las transacciones. Luego retornamos el volumen resultante al programa que ha realizado la llamada. Si la lista transmitida al método está vacía (o bien se transmitido una lista que no es de colección histórica), el método retornará cero.

Método que retorna el volumen sumado de todas las transacciones de una posición de salida del mercado, según su identificador:

//+--------------------------------------------------------------------+
//| Return the total volume of all deals of OUT position by its        |
//| ID (participation in closing by an opposite position is considered)|
//+--------------------------------------------------------------------+
double CEventsCollection::SummaryVolumeDealsOutByPosID(CArrayObj *list,const ulong position_id)
  {
   double vol=0.0;
   CArrayObj* list_out=this.GetListAllDealsOutByPosID(list,position_id);
   if(list_out!=NULL)
     {
      for(int i=0;i<list_out.Total();i++)
        {
         COrder* deal=list_out.At(i);
         if(deal==NULL)
            continue;
         vol+=deal.Volume();
        }
     }
   CArrayObj* list_by=this.GetListCloseByOrders(list);
   if(list_by!=NULL)
     {
      for(int i=0;i<list_by.Total();i++)
        {
         COrder* order=list_by.At(i);
         if(order==NULL)
            continue;
         if(order.PositionID()==position_id || order.PositionByID()==position_id)
           {
            vol+=order.Volume();
           }
        }
     }
   return vol;
  }
//+------------------------------------------------------------------+

Aquí se tiene en cuenta el hecho siguiente: si parte de la posición cuyo identificador se ha transmitido al método ha participado en el cierre de otra posición (cierre con opuesta), o bien parte de esta posición se ha cerrado con una posición opuesta, en las transacciones de esta posición no se reflejará este hecho, se reflejará en la última orden de cierre de la posición, en el campo de su propiedad ORDER_PROP_POSITION_BY_ID. Por eso, este método tiene dos búsquedas de volúmenes cerrados: según la transacción y según las órdenes de cierre.

Primero obtenemos la lista con todas las transacciones de salida del mercado, a continuación, sumamos en el ciclo los volúmenes de todas las transacciones.
A continuación, obtenemos la lista de todas las órdenes de cierre existentes en la lista histórica, y comprobamos en el ciclo la pertenencia de la orden destacada a la posición cuyo identificador se ha tansmitido al método. Si la orden seleccionada ha participado en el cierre de la posición, su volumen se añadirá al volumen total.
Luego retornamos el volumen resultante al programa que ha realizado la llamada. Si la lista transmitida al método está vacía (o bien se transmitido una lista que no es de colección histórica), el método retornará cero.

Método que retorna la primera orden (de apertura) de una posición según su identificador:

//+------------------------------------------------------------------+
//| Return the first order from the list of all position orders      |
//+------------------------------------------------------------------+
COrder* CEventsCollection::GetFirstOrderFromList(CArrayObj* list,const ulong position_id)
  {
   CArrayObj* list_orders=this.GetListAllOrdersByPosID(list,position_id);
   if(list_orders==NULL || list_orders.Total()==0) return NULL;
   list_orders.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list_orders.At(0);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

En primer lugar, obtenemos la lista de todas las órdenes de la posición. Clasificamos la lista obtenida según la hora de apertura y tomamos el primer elemento de la lista, él será la primera orden de la posición. Luego retornamos la orden obtenida al programa que ha realizado la llamada. Si las listas están vacías, el método retorna NULL.

Método que retorna la última orden de una posición según su identificador:

//+------------------------------------------------------------------+
//| Return the last order from the list of all position orders       |
//+------------------------------------------------------------------+
COrder* CEventsCollection::GetLastOrderFromList(CArrayObj* list,const ulong position_id)
  {
   CArrayObj* list_orders=this.GetListAllOrdersByPosID(list,position_id);
   if(list_orders==NULL || list_orders.Total()==0) return NULL;
   list_orders.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list_orders.At(list_orders.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

En primer lugar, obtenemos la lista de todas las órdenes de la posición. Clasificamos la lista obtenida según la hora de apertura y tomamos el último elemento de la lista, él será la última orden de la posición. Luego retornamos la orden obtenida al programa que ha realizado la llamada. Si las listas están vacías, el método retorna NULL.

Método que retorna la última orden de cierre de una posición según su identificador (orden con el tipo ORDER_TYPE_CLOSE_BY):

//+------------------------------------------------------------------+
//| Return the last closing order                                    |
//| from the list of all position orders                             |
//+------------------------------------------------------------------+
COrder* CEventsCollection::GetCloseByOrderFromList(CArrayObj *list,const ulong position_id)
  {
   CArrayObj* list_orders=this.GetListAllOrdersByPosID(list,position_id);
   list_orders=CSelect::ByOrderProperty(list_orders,ORDER_PROP_TYPE,ORDER_TYPE_CLOSE_BY,EQUAL);
   if(list_orders==NULL || list_orders.Total()==0) return NULL;
   list_orders.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list_orders.At(list_orders.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

Dado que los cierres parciales son posibles cuando se cierran con una posición opuesta, y los volúmenes de las dos posiciones opuestas pueden ser desiguales, la orden de cierre podría no ser la única dentro de las órdenes de la posición. Por ello, el método busca tales las órdenes y retorna la última de ellas: se trata de la última orden que provoca el evento.

En primer lugar, obtenemos la lista de todas las órdenes de la posición. A continuación, usando la lista obtenida, obtenemos una lista que contiene solo las órdenes de cierre (con el tipo ORDER_TYPE_CLOSE_BY). Clasificamos la lista obtenida de esta forma según la hora de apertura y tomamos el último elemento de la lista, él será la última orden de cierre de la posición. Luego retornamos la orden obtenida al programa que ha realizado la llamada. Si las listas están vacías, el método retorna NULL.

Al cerrar con una orden opuesta, a veces sucede que la biblioteca ve dos eventos idénticos: esto es así porque se cierran dos posiciones, y solo una de ellas tiene entre sus componentes una orden de cierre; además tenemos dos transacciones. Por eso, para que no se dé la situación en la que se duplica un mismo evento en la colección, deberemos primero comprobar la presencia de ese mismo evento idéntico en la lista de colección de eventos, y si todavía no se encuentra allí, añadir el evento a la lista.

Método que retorna una orden según el ticket:

//+------------------------------------------------------------------+
//| Return the order by ticket                                       |
//+------------------------------------------------------------------+
COrder* CEventsCollection::GetOrderByTicket(CArrayObj *list,const ulong order_ticket)
  {
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,NO_EQUAL);
   list_orders=CSelect::ByOrderProperty(list_orders,ORDER_PROP_TICKET,order_ticket,EQUAL);
   if(list_orders==NULL || list_orders.Total()==0) return NULL;
   COrder* order=list_orders.At(0);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

Primero creamos una lista solo de órdenes, después filtramos la lista según el ticket transmitido por el parámetro del método. Como resultado, retornaremos, o bien NULL (si no hay una orden con ese ticket), o bien el número del ticket.

Para comprobar la presencia de un evento en la lista, se usa un método que retona la presencia de un evento en la lista:

//+------------------------------------------------------------------+
//| Return the flag of the event object presence in the event list   |
//+------------------------------------------------------------------+
bool CEventsCollection::IsPresentEventInList(CEvent *compared_event)
  {
   int total=this.m_list_events.Total();
   if(total==0)
      return false;
   for(int i=total-1;i>=0;i--)
     {
      CEvent* event=this.m_list_events.At(i);
      if(event==NULL)
         continue;
      if(event.IsEqual(compared_event))
         return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

El puntero al objeto de evento comparado se transmite al método. Si la lista de colecciones está vacía, se retorna de inmediato false, no existe tal elemento en la lista. A continuación, en el ciclo por la lista de la colección de eventos, se toma el siguiente evento de la lista y se compara con el evento transmitido al método con la ayuda del método IsEqual() del evento abstracto CEvent. Si el método retorna true, significa que existe este objeto de evento en la lista de colección de eventos, así que retornamos true. Si el ciclo ha finalizado o hemos llegado a la última línea del método, significa que el evento no está en la lista, y retornamos false.

Declaramos los métodos en la sección pública de la clase:

public:
//--- Select events from the collection with time within the range from begin_time to end_time
   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0);
//--- Return the full event collection list "as is"
   CArrayObj        *GetList(void)                                                                       { return &this.m_list_events;                                           }
//--- Return the list by selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   CArrayObj        *GetList(ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
//--- Update the list of events
   void              Refresh(CArrayObj* list_history,
                             CArrayObj* list_market,
                             const bool is_history_event,
                             const bool is_market_event,
                             const int  new_history_orders,
                             const int  new_market_pendings,
                             const int  new_market_positions,
                             const int  new_deals);
//--- Set the control program chart ID
   void              SetChartID(const long id)        { this.m_chart_id=id;         }
//--- Return the last trading event on the account
   ENUM_TRADE_EVENT  GetLastTradeEvent(void)    const { return this.m_trade_event;  }
//--- Reset the last trading event
   void              ResetLastTradeEvent(void)        { this.m_trade_event=TRADE_EVENT_NO_EVENT;   }
//--- Constructor
                     CEventsCollection(void);

En la tercera parte de la descripción de la biblioteca analizamos los métodos de obtención de la lista completa, de las listas según un intervalo de fechas, y según las propiedades de tipo entero, real y string seleccionadas; aquí vamos a limitarnos a mostrar el listado de estos métodos para el análisis propio.

Método de obtención de una lista de eventos en el intervalo de fechas establecido:

//+------------------------------------------------------------------+
//| Select events from the collection with the time                  |
//| within the range from begin_time to end_time                     |
//+------------------------------------------------------------------+
CArrayObj *CEventsCollection::GetListByTime(const datetime begin_time=0,const datetime end_time=0)
  {
   CArrayObj *list=new CArrayObj();
   if(list==NULL)
     {
      ::Print(DFUN+TextByLanguage("Ошибка создания временного списка","Error creating temporary list"));
      return NULL;
     }
   datetime begin=begin_time,end=(end_time==0 ? END_TIME : end_time);
   if(begin_time>end_time) begin=0;
   list.FreeMode(false);
   ListStorage.Add(list);
   //---
   this.m_event_instance.SetProperty(EVENT_PROP_TIME_EVENT,begin);
   int index_begin=this.m_list_events.SearchGreatOrEqual(&m_event_instance);
   if(index_begin==WRONG_VALUE)
      return list;
   this.m_event_instance.SetProperty(EVENT_PROP_TIME_EVENT,end);
   int index_end=this.m_list_events.SearchLessOrEqual(&m_event_instance);
   if(index_end==WRONG_VALUE)
      return list;
   for(int i=index_begin; i<=index_end; i++)
      list.Add(this.m_list_events.At(i));
   return list;
  }
//+------------------------------------------------------------------+

El método principal que se llamará desde el objeto básico de la biblioteca al darse cualquiera de los eventos será el método Refresh().
En este momento, se ha implementado el funcionamiento de este método en las cuentas de cobertura para MQL5.

El método recibe los punteros a las listas de colecciones de órdenes históricas y de mercado, y también de transacciones y posiciones, así como los datos sobre el número de órdenes recién aparecidas o eliminadas, las posiciones abiertas o cerradas y las nuevas transacciones.
Dependiendo de la lista en la que hayan sucedido los cambios, se toma en el ciclo en el número de nuevas órdenes/posiciones/transacciones el número necesario de órdenes o transacciones, y se llama para cada una de ellas el método de creación de eventos CreateNewEvent() y la inclusión del evento en la lista de colección.
De esta forma, para cualquiera de los eventos sucedidos se llamará el método de creación de un nuevo evento, y este se ubicará en la lista de colección, mientras que el programa que realiza la llamada será notificado sobre todos los eventos mediante el envío de mensajes de usuario al gráfico del programa que ha realizado la llamada.

Asimismo, en la clase, la variable de miembro de clase m_trade_event es rellenada con el valor del último evento sucedido. Además existe el método público GetLastTradeEvent(), que retorna el valor del último evento comercial, y también el método que resetea el último evento comercial (por analogía con GetLastError() y ResetLastError()).
Como adición, existen métodos que retornan la lista de colección de los eventos, tanto completa, como según el intervalo temporal y los criterios establecidos. De esta forma, el programa que realiza la llamada siempre sabrá que ha sucedido un evento en la cuenta, ya sea solo uno o varios en un paquete; en este caso, además, será posible solicitar la lista de todos estos eventos en el número que necesitemos, además de procesarlos de acuerdo con la lógica implementada en el programa.

Vamos a ver el listado de los métodos Refresh() y CreateNewEvent().

Método de actualización de la lista de colección de eventos:

//+------------------------------------------------------------------+
//| Update the event list                                            |
//+------------------------------------------------------------------+
void CEventsCollection::Refresh(CArrayObj* list_history,
                                CArrayObj* list_market,
                                const bool is_history_event,
                                const bool is_market_event,
                                const int  new_history_orders,
                                const int  new_market_pendings,
                                const int  new_market_positions,
                                const int  new_deals)
  {
//--- Exit if the lists are empty
   if(list_history==NULL || list_market==NULL)
      return;
//--- In case of a hedging account
   if(this.m_is_hedge)
     {
      //--- If the event is in the market environment
      if(is_market_event)
        {
         //--- if the number of placed pending orders increased
         if(new_market_pendings>0)
           {
            //--- Receive the list of the newly placed pending orders
            CArrayObj* list=this.GetListMarketPendings(list_market);
            if(list!=NULL)
              {
               //--- Sort the new list by order placement time
               list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); 
               //--- Take the number of orders equal to the number of newly placed ones from the end of the list in a loop (the last N events)
               int total=list.Total(), n=new_market_pendings;
               for(int i=total-1; i>=0 && n>0; i--,n--)
                 {
                  //--- Receive an order from the list, if this is a pending order, set a trading event
                  COrder* order=list.At(i);
                  if(order!=NULL && order.Status()==ORDER_STATUS_MARKET_PENDING)
                     this.CreateNewEvent(order,list_history,list_market);
                 }
              }
           }
        }
      //--- If the event is in the account history
      if(is_history_event)
        {
         //--- If the number of historical orders increased
         if(new_history_orders>0)
           {
            //--- Receive the list of removed pending orders only
            CArrayObj* list=this.GetListHistoryPendings(list_history);
            if(list!=NULL)
              {
               //--- Sort the new list by order removal time
               list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC);
               //--- Take the number of orders equal to the number of newly removed ones from the end of the list in a loop (the last N events)
               int total=list.Total(), n=new_history_orders;
               for(int i=total-1; i>=0 && n>0; i--,n--)
                 {
                  //--- Receive an order from the list. If this is a removed pending order, set a trading event
                  COrder* order=list.At(i);
                  if(order!=NULL && order.Status()==ORDER_STATUS_HISTORY_PENDING)
                     this.CreateNewEvent(order,list_history,list_market);
                 }
              }
           }
         //--- If the number of deals increased
         if(new_deals>0)
           {
            //--- Receive the list of deals only
            CArrayObj* list=this.GetListDeals(list_history);
            if(list!=NULL)
              {
               //--- Sort the new list by deal time
               list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
               //--- Take the number of deals equal to the number of new ones from the end of the list in a loop (the last N events)
               int total=list.Total(), n=new_deals;
               for(int i=total-1; i>=0 && n>0; i--,n--)
                 {
                  //--- Receive a deal from the list and set a trading event
                  COrder* order=list.At(i);
                  if(order!=NULL)
                     this.CreateNewEvent(order,list_history,list_market);
                 }
              }
           }
        }
     }
   //--- In case of a netting account
   else
     {
      
     }
  }  
//+------------------------------------------------------------------+

Para no que no tengamos que detenernos en la descripción de un método tan sencillo, veremos que directamente en su listado se describen todas las condiciones y acciones necesarias, donde se encuentran estas condiciones. Todo parece bastante claro. Una observación: por el momento, solo se ha implementado el procesamiento de eventos en la cuenta de cobertura.

Vamos a analizar el método de creación de un nuevo evento:

//+------------------------------------------------------------------+
//| Create a trading event depending on the order status             |
//+------------------------------------------------------------------+
void CEventsCollection::CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market)
  {
   int trade_event_code=TRADE_EVENT_FLAG_NO_EVENT;
   ENUM_ORDER_STATUS status=order.Status();
//--- Pending order placed
   if(status==ORDER_STATUS_MARKET_PENDING)
     {
      trade_event_code=TRADE_EVENT_FLAG_ORDER_PLASED;
      CEvent* event=new CEventOrderPlased(trade_event_code,order.Ticket());
      if(event!=NULL)
        {
         event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());                       // Event time
         event.SetProperty(EVENT_PROP_REASON_EVENT,EVENT_REASON_DONE);                       // Event reason (from the ENUM_EVENT_REASON enumeration)
         event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());                    // Event deal type
         event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());                     // Event order ticket
         event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder());                   // Event order type
         event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder());                // Event order type
         event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket());                    // Event order ticket
         event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());                 // Order ticket
         event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());                       // Position ID
         event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID());                  // Opposite position ID
         event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                            // Order magic number
         event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpenMSC());              // Order time
         event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());                        // Price the event occurred at
         event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());                         // Order placement price
         event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());                       // Order closure price
         event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());                            // StopLoss order price
         event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());                          // TakeProfit order price
         event.SetProperty(EVENT_PROP_VOLUME_INITIAL,order.Volume());                        // Requested volume
         event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume()-order.VolumeCurrent()); // Executed volume
         event.SetProperty(EVENT_PROP_VOLUME_CURRENT,order.VolumeCurrent());                 // Remaining (unexecuted) volume
         event.SetProperty(EVENT_PROP_PROFIT,order.Profit());                                // Profit
         event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                                // Order symbol
         //--- Set control program chart ID, decode the event code and set the event type
         event.SetChartID(this.m_chart_id);
         event.SetTypeEvent();
         //--- Add the event object if it is not in the list
         if(!this.IsPresentEventInList(event))
           {
            this.m_list_events.InsertSort(event);
            //--- Send a message about the event and set the value of the last trading event
            event.SendEvent();
            this.m_trade_event=event.TradeEvent();
           }
         //--- If the event is already present in the list, remove a new event object and display a debugging message
         else
           {
            ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
            delete event;
           }
        }
     }
//--- Pending order removed
   if(status==ORDER_STATUS_HISTORY_PENDING)
     {
      trade_event_code=TRADE_EVENT_FLAG_ORDER_REMOVED;
      CEvent* event=new CEventOrderRemoved(trade_event_code,order.Ticket());
      if(event!=NULL)
        {
         ENUM_EVENT_REASON reason=
           (
            order.State()==ORDER_STATE_CANCELED ? EVENT_REASON_CANCEL :
            order.State()==ORDER_STATE_EXPIRED  ? EVENT_REASON_EXPIRED : EVENT_REASON_DONE
           );
         event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeCloseMSC());             // Event time
         event.SetProperty(EVENT_PROP_REASON_EVENT,reason);                         // Event reason (from the ENUM_EVENT_REASON reason)
         event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());           // Event order type
         event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());            // Event order ticket
         event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder());          // Type of an order that triggered an event deal (the last position order)
         event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder());       // Type of an order that triggered a position deal (the first position order)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket());           // Ticket of an order, based on which an event deal is opened (the last position order)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());        // Ticket of an order, based on which a position deal is opened (the first position order)
         event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());              // Position ID
         event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID());         // Opposite position ID
         event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                   // Order magic number
         event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpenMSC());     // Time of an order, based on which a position deal is opened (the first position order)
         event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());               // Event price
         event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());                // Order open price
         event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());              // Order close price
         event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());                   // StopLoss order price
         event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());                 // TakeProfit order price
         event.SetProperty(EVENT_PROP_VOLUME_INITIAL,order.Volume());               // Requested volume
         event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume()-order.VolumeCurrent()); // Executed volume
         event.SetProperty(EVENT_PROP_VOLUME_CURRENT,order.VolumeCurrent());        // Remaining (unexecuted) volume
         event.SetProperty(EVENT_PROP_PROFIT,order.Profit());                       // Profit
         event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                       // Order symbol
         //--- Set the control program chart ID, decode the event code and set the event type
         event.SetChartID(this.m_chart_id);
         event.SetTypeEvent();
         //--- Add the event object if it is not present on the list
         if(!this.IsPresentEventInList(event))
           {
            this.m_list_events.InsertSort(event);
            //--- Send a message about the event and set the last trading event value
            event.SendEvent();
            this.m_trade_event=event.TradeEvent();
           }
         //--- If the event is already in the list, remove the new event object and display the debugging message
         else
           {
            ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
            delete event;
           }
        }
     }
//--- Position opened (__MQL4__)
   if(status==ORDER_STATUS_MARKET_POSITION)
     {
      trade_event_code=TRADE_EVENT_FLAG_POSITION_OPENED;
      CEvent* event=new CEventPositionOpen(trade_event_code,order.Ticket());
      if(event!=NULL)
        {
         event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpen());              // Event time
         event.SetProperty(EVENT_PROP_REASON_EVENT,EVENT_REASON_DONE);           // Event reason (from the ENUM_EVENT_REASON enumeration)
         event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());        // Event deal type
         event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());         // Event deal ticket
         event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder());       // Type of an order, based on which an event deal is opened (the last position order)
         event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder());    // Type of an order, based on which a position deal is opened (the first position order)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket());        // Ticket of an order, based on which an event deal is opened (the last position order)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());     // Ticket of an order, based on which a position deal is opened (the first position order)
         event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());           // Position ID
         event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID());      // Opposite position ID
         event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                // Order/deal/position magic number
         event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpen());     // Time of an order, based on which a position deal is opened (the first position order)
         event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());            // Event price
         event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());             // Order/deal/position open price
         event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());           // Order/deal/position close price
         event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());                // StopLoss position price
         event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());              // TakeProfit position price
         event.SetProperty(EVENT_PROP_VOLUME_INITIAL,order.Volume());            // Requested volume
         event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume());           // Executed volume
         event.SetProperty(EVENT_PROP_VOLUME_CURRENT,order.VolumeCurrent());     // Remaining (unexecuted) volume
         event.SetProperty(EVENT_PROP_PROFIT,order.Profit());                    // Profit
         event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                    // Order symbol
         //--- Set the control program chart ID, decode the event code and set the event type
         event.SetChartID(this.m_chart_id);
         event.SetTypeEvent();
         //--- Add the event object if it is not present in the list
         if(!this.IsPresentEventInList(event))
           {
            this.m_list_events.InsertSort(event);
            //--- Send the message about the event and set the value of the last trading event
            event.SendEvent();
            this.m_trade_event=event.TradeEvent();
           }
         //--- If the event is already present in the list, remove the new event object and display the debugging message
         else
           {
            ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
            delete event;
           }
        }
     }
//--- New deal (__MQL5__)
   if(status==ORDER_STATUS_DEAL)
     {
      //--- New balance operation
      if((ENUM_DEAL_TYPE)order.TypeOrder()>DEAL_TYPE_SELL)
        {
         trade_event_code=TRADE_EVENT_FLAG_ACCOUNT_BALANCE;
         CEvent* event=new CEventBalanceOperation(trade_event_code,order.Ticket());
         if(event!=NULL)
           {
            ENUM_EVENT_REASON reason=
              (
               (ENUM_DEAL_TYPE)order.TypeOrder()==DEAL_TYPE_BALANCE ? (order.Profit()>0 ? EVENT_REASON_BALANCE_REFILL : EVENT_REASON_BALANCE_WITHDRAWAL) :
               (ENUM_EVENT_REASON)(order.TypeOrder()+REASON_EVENT_SHIFT)
              );
            event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());           // Event time
            event.SetProperty(EVENT_PROP_REASON_EVENT,reason);                      // Event reason (from the ENUM_EVENT_REASON enumeration)
            event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());        // Event deal type
            event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());         // Event order ticket
            event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder());       // Type of an order that triggered an event deal (the last position order)
            event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder());    // Type of an order, based on which a position deal is opened (the first position order)
            event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket());        // Ticket of an order, based on which an event deal is opened (the last position order)
            event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());     // Ticket of an order, based on which a position deal is opened (the first position order)
            event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());           // Position ID
            event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID());      // Opposite position ID
            event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                // Order/deal/position magic number
            event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpenMSC());  // Time of an order, based on which a position deal is opened (the first position order)
            event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());            // Event price
            event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());             // Order/deal/position open price
            event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());           // Order/deal/position close price
            event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());                // StopLoss deal price
            event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());              // TakeProfit deal price
            event.SetProperty(EVENT_PROP_VOLUME_INITIAL,order.Volume());            // Requested volume
            event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume());           // Executed volume
            event.SetProperty(EVENT_PROP_VOLUME_CURRENT,order.VolumeCurrent());     // Remaining (unexecuted) volume
            event.SetProperty(EVENT_PROP_PROFIT,order.Profit());                    // Profit
            event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                    // Order symbol
            //--- Set the control program chart ID, decode the event code and set the event type
            event.SetChartID(this.m_chart_id);
            event.SetTypeEvent();
            //--- Add the event object if it is not in the list
            if(!this.IsPresentEventInList(event))
              {
               //--- Send a message about the event and set the last trading event value
               this.m_list_events.InsertSort(event);
               event.SendEvent();
               this.m_trade_event=event.TradeEvent();
              }
            //--- If the event is already in the list, remove the new event object and display the debugging message
            else
              {
               ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
               delete event;
              }
           }
        }
      //--- If this is not a balance operation
      else
        {
         //--- Market entry
         if(order.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_IN)
           {
            trade_event_code=TRADE_EVENT_FLAG_POSITION_OPENED;
            int reason=EVENT_REASON_DONE;
            //--- Look for all position deals in the direction of its opening and count its total volume
            double volume_in=this.SummaryVolumeDealsInByPosID(list_history,order.PositionID());
            //--- Take the first and last position orders from the list of all position orders
            ulong order_ticket=order.GetProperty(ORDER_PROP_DEAL_ORDER);
            COrder* order_first=this.GetOrderByTicket(list_history,order_ticket);
            COrder* order_last=this.GetLastOrderFromList(list_history,order.PositionID());
            //--- If there is no last order, the first and last position orders coincide
            if(order_last==NULL)
               order_last=order_first;
            if(order_first!=NULL)
              {
               //--- If the order volume is opened partially, this is a partial execution
               if(this.SummaryVolumeDealsInByPosID(list_history,order.PositionID())<order_first.Volume())
                 {
                  trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                  reason=EVENT_REASON_DONE_PARTIALLY;
                 }
               //--- If an opening order is a pending one, the pending order is activated
               if(order_first.TypeOrder()>ORDER_TYPE_SELL && order_first.TypeOrder()<ORDER_TYPE_CLOSE_BY)
                 {
                  trade_event_code+=TRADE_EVENT_FLAG_ORDER_ACTIVATED;
                  //--- If an order is executed partially, set the partial order execution as an event reason
                  reason=
                    (this.SummaryVolumeDealsInByPosID(list_history,order.PositionID())<order_first.Volume() ? 
                     EVENT_REASON_ACTIVATED_PENDING_PARTIALLY : 
                     EVENT_REASON_ACTIVATED_PENDING
                    );
                 }
               CEvent* event=new CEventPositionOpen(trade_event_code,order.PositionID());
               if(event!=NULL)
                 {
                  event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());                 // Event time (position open time)
                  event.SetProperty(EVENT_PROP_REASON_EVENT,reason);                            // Event reason (from the ENUM_EVENT_REASON enumeration)
                  event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());              // Event deal type
                  event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());               // Event deal ticket
                  event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order_first.TypeOrder());    // Type of an order, based on which a position deal is opened (the first position order)
                  event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order_first.Ticket());     // Ticket of an order, based on which an event deal is opened (the last position order)
                  event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order_last.TypeOrder());        // Type of an order, based on which an event deal is opened (the last position order)
                  event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order_last.Ticket());         // Ticket of an order, based on which an event deal is opened (the last position order)
                  event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());                 // Position ID
                  event.SetProperty(EVENT_PROP_POSITION_BY_ID,order_last.PositionByID());       // Opposite position ID
                  event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                      // Order/deal/position magic number 
                  event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order_first.TimeOpenMSC());  // Time of an order, based on which a position deal is opened (the first position order)
                  event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());                  // Event price (position open price)
                  event.SetProperty(EVENT_PROP_PRICE_OPEN,order_first.PriceOpen());             // Order open price (position opening order price)
                  event.SetProperty(EVENT_PROP_PRICE_CLOSE,order_last.PriceClose());            // Order close price (position last order close price)
                  event.SetProperty(EVENT_PROP_PRICE_SL,order_first.StopLoss());                // StopLoss price (Position order StopLoss price)
                  event.SetProperty(EVENT_PROP_PRICE_TP,order_first.TakeProfit());              // TakeProfit price (Position order TakeProfit price)
                  event.SetProperty(EVENT_PROP_VOLUME_INITIAL,order_first.Volume());            // Requested volume
                  event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,volume_in);                      // Executed volume
                  event.SetProperty(EVENT_PROP_VOLUME_CURRENT,order_first.Volume()-volume_in);  // Remaining (unexecuted) volume
                  event.SetProperty(EVENT_PROP_PROFIT,order.ProfitFull());                      // Profit
                  event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                          // Order symbol
                  //--- Set the control program chart ID, decode the event code and set the event type
                  event.SetChartID(this.m_chart_id);
                  event.SetTypeEvent();
                  //--- Add the event object if it is not on the list
                  if(!this.IsPresentEventInList(event))
                    {
                     this.m_list_events.InsertSort(event);
                     //--- Send a message about the event and set the last trading event value
                     event.SendEvent();
                     this.m_trade_event=event.TradeEvent();
                    }
                  //--- If the event is already in the list, remove the new event object and display the debugging message
                  else
                    {
                     ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
                     delete event;
                    }
                 }
              }
           }
         //--- Market exit
         else if(order.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT)
           {
            trade_event_code=TRADE_EVENT_FLAG_POSITION_CLOSED;
            int reason=EVENT_REASON_DONE;
            //--- Take the first and last position orders from the list of all position orders
            COrder* order_first=this.GetFirstOrderFromList(list_history,order.PositionID());
            COrder* order_last=this.GetLastOrderFromList(list_history,order.PositionID());
            if(order_first!=NULL && order_last!=NULL)
              {
               //--- Look for all position deals in the direction of its opening and closing and count their total volume
               double volume_in=this.SummaryVolumeDealsInByPosID(list_history,order.PositionID());
               double volume_out=this.SummaryVolumeDealsOutByPosID(list_history,order.PositionID());
               //--- Calculate the current volume of the closed position
               int dgl=(int)DigitsLots(order.Symbol());
               double volume_current=::NormalizeDouble(volume_in-volume_out,dgl);
               //--- If the order volume is closed partially, this is a partial execution
               if(volume_current>0)
                 {
                  trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                 }
               //--- If the closing order is executed partially, set the closing order partial execution as an event reason
               if(order_last.VolumeCurrent()>0)
                 {
                  reason=EVENT_REASON_DONE_PARTIALLY;
                 }
               //--- If the closing flag is set to StopLoss for a position's closing order, then closing is performed by StopLoss
               //--- If a StopLoss order is executed partially, set partial StopLoss order execution as the event reason
               if(order_last.IsCloseByStopLoss())
                 {
                  trade_event_code+=TRADE_EVENT_FLAG_SL;
                  reason=(order_last.VolumeCurrent()>0 ? EVENT_REASON_DONE_SL_PARTIALLY : EVENT_REASON_DONE_SL);
                 }
               //--- If the closing flag is set to TakeProfit for a position's closing order, then closing is performed by TakeProfit
               //--- If a TakeProfit order is executed partially, set partial TakeProfit order execution as the event reason
               else if(order_last.IsCloseByTakeProfit())
                 {
                  trade_event_code+=TRADE_EVENT_FLAG_TP;
                  reason=(order_last.VolumeCurrent()>0 ? EVENT_REASON_DONE_TP_PARTIALLY : EVENT_REASON_DONE_TP);
                 }
               //---
               CEvent* event=new CEventPositionClose(trade_event_code,order.PositionID());
               if(event!=NULL)
                 {
                  event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());                 // Event time (position closing time)
                  event.SetProperty(EVENT_PROP_REASON_EVENT,reason);                            // Event reason (from the ENUM_EVENT_REASON enumeration)
                  event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());              // Event deal type
                  event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());               // Event deal ticket
                  event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order_first.TypeOrder());    // Type of an order, based on which a position deal is opened (the first position order)
                  event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order_last.TypeOrder());        // Type of an order, based on which an event deal is opened (the last position order)
                  event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order_first.Ticket());     // Ticket of an order, based on which a position deal is opened (the first position order)
                  event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order_last.Ticket());         // Ticket of an order, based on which an event deal is opened (the last position order)
                  event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());                 // Position ID
                  event.SetProperty(EVENT_PROP_POSITION_BY_ID,order_last.PositionByID());       // Opposite position ID
                  event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                      // Order/deal/position magic number
                  event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order_first.TimeOpenMSC());  // Time of an order, based on which a position deal is opened (the first position order)
                  event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());                  // Event price (position closing price)
                  event.SetProperty(EVENT_PROP_PRICE_OPEN,order_first.PriceOpen());             // Order open price (position opening order price)
                  event.SetProperty(EVENT_PROP_PRICE_CLOSE,order_last.PriceClose());            // Order close price (position last order closing price)
                  event.SetProperty(EVENT_PROP_PRICE_SL,order_first.StopLoss());                // StopLoss price (Position order StopLoss price)
                  event.SetProperty(EVENT_PROP_PRICE_TP,order_first.TakeProfit());              // TakeProfit price (Position order TakeProfit price)
                  event.SetProperty(EVENT_PROP_VOLUME_INITIAL,volume_in);                       // Initial volume
                  event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume());                 // Closed volume
                  event.SetProperty(EVENT_PROP_VOLUME_CURRENT,volume_in-volume_out);            // Remaining (current) volume
                  event.SetProperty(EVENT_PROP_PROFIT,order.ProfitFull());                      // Profit
                  event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                          // Order symbol
                  //--- Set the control program chart ID, decode the event code and set the event type
                  event.SetChartID(this.m_chart_id);
                  event.SetTypeEvent();
                  //--- Add the event object if it is not on the list
                  if(!this.IsPresentEventInList(event))
                    {
                     this.m_list_events.InsertSort(event);
                     //--- Send a message about the event and set the last trading event value
                     event.SendEvent();
                     this.m_trade_event=event.TradeEvent();
                    }
                  //--- If the event is already in the list, remove the new event object and display the debugging message
                  else
                    {
                     ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
                     delete event;
                    }
                 }
              }
           }
         //--- Opposite position
         else if(order.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT_BY)
           {
            trade_event_code=TRADE_EVENT_FLAG_POSITION_CLOSED;
            int reason=EVENT_REASON_DONE_BY_POS;
            //--- Take the first and last position orders from the list of all position orders
            COrder* order_first=this.GetFirstOrderFromList(list_history,order.PositionID());
            COrder* order_close=this.GetCloseByOrderFromList(list_history,order.PositionID());
            if(order_first!=NULL && order_close!=NULL)
              {
               //--- Add the flag of closing by an opposite position
               trade_event_code+=TRADE_EVENT_FLAG_BY_POS;
               //--- Look for all closed position deals in the direction of its opening and closing and count their total volume
               double volume_in=this.SummaryVolumeDealsInByPosID(list_history,order.PositionID());
               double volume_out=this.SummaryVolumeDealsOutByPosID(list_history,order.PositionID());//+order_close.Volume();
               //--- Calculate the current volume of the closed position
               int dgl=(int)DigitsLots(order.Symbol());
               double volume_current=::NormalizeDouble(volume_in-volume_out,dgl);
               //--- Look for all opposite position deals in the direction of its opening and closing and calculate their total volume
               double volume_opp_in=this.SummaryVolumeDealsInByPosID(list_history,order_close.PositionByID());
               double volume_opp_out=this.SummaryVolumeDealsOutByPosID(list_history,order_close.PositionByID());//+order_close.Volume();
               //--- Calculate the current volume of the opposite position
               double volume_opp_current=::NormalizeDouble(volume_opp_in-volume_opp_out,dgl);
               //--- If the closed position volume is closed partially, this is a partial closing
               if(volume_current>0 || order_close.VolumeCurrent()>0)
                 {
                  //--- Add the partial closing flag
                  trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                  //--- If the opposite position is closed partially, there is a partial closing by the part of the opposite position volume
                  reason=(volume_opp_current>0 ? EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY : EVENT_REASON_DONE_PARTIALLY_BY_POS);
                 }
               //--- If the position volume is closed in full and there is a partial execution by the opposite one, there is a closing by the part of the opposite position volume
               else
                 {
                  if(volume_opp_current>0)
                    {
                     reason=EVENT_REASON_DONE_BY_POS_PARTIALLY;
                    }
                 }
               CEvent* event=new CEventPositionClose(trade_event_code,order.PositionID());
               if(event!=NULL)
                 {
                  event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());                 // Event time
                  event.SetProperty(EVENT_PROP_REASON_EVENT,reason);                            // Event reason (from the ENUM_EVENT_REASON enumeration)
                  event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());              // Event deal type
                  event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());               // Event deal ticket
                  event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order_close.TypeOrder());       // Type of an order, based on which an event deal is opened (the last position order)
                  event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order_close.Ticket());        // Ticket of an order, based on which an event deal is opened (the last position order)
                  event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order_first.TimeOpenMSC());  // Time of an order, based on which a position deal is opened (the first position order)
                  event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order_first.TypeOrder());    // Type of an order, based on which a position deal is opened (the first position order)
                  event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order_first.Ticket());     // Ticket of an order, based on which a position deal is opened (the first position order)
                  event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());                 // Position ID
                  event.SetProperty(EVENT_PROP_POSITION_BY_ID,order_close.PositionByID());      // Opposite position ID
                  event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                      // Order/deal/position magic number
                  event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());                  // Event price
                  event.SetProperty(EVENT_PROP_PRICE_OPEN,order_first.PriceOpen());             // Order/deal/position open price
                  event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());                 // Order/deal/position close price
                  event.SetProperty(EVENT_PROP_PRICE_SL,order_first.StopLoss());                // StopLoss price (Position order StopLoss price)
                  event.SetProperty(EVENT_PROP_PRICE_TP,order_first.TakeProfit());              // TakeProfit price (Position order TakeProfit price)
                  event.SetProperty(EVENT_PROP_VOLUME_INITIAL,::NormalizeDouble(volume_in,dgl));// Initial volume
                  event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume());                 // Closed volume
                  event.SetProperty(EVENT_PROP_VOLUME_CURRENT,volume_current);                  // Remaining (current) volume
                  event.SetProperty(EVENT_PROP_PROFIT,order.ProfitFull());                      // Profit
                  event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                          // Order symbol
                  //--- Set the control program chart ID, decode the event code and set the event type
                  event.SetChartID(this.m_chart_id);
                  event.SetTypeEvent();
                  //--- Add the event object if it is not in the list
                  if(!this.IsPresentEventInList(event))
                    {
                     this.m_list_events.InsertSort(event);
                     //--- Send a message about the event and set the value of the last trading event
                     event.SendEvent();
                     this.m_trade_event=event.TradeEvent();
                    }
                  //--- If the event is already present in the list, remove the new event object and display the debugging message
                  else
                    {
                     ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
                     delete event;
                    }
                 }
              }
           }
         //--- Reversal
         else if(order.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_INOUT)
           {
            //--- Position reversal
            Print(DFUN,"Position reversal");
            order.Print();
           }
        }
     }
  }
//+------------------------------------------------------------------+

El método ha resultado bastante voluminoso. Por ello, todas las descripciones de las comprobaciones necesarias en él, así como las acciones correspondientes a las mismas, se han escrito directamente en el listado.

La esencia del funcionamiento de este método se resume en lo siguiente: se comprueba el estado de la orden transmitida al método, y dependiendo de la orden que sea (orden pendiente colocada, orden pendiente eliminada, o bien transacción), se comprueban todos los componentes necesarios del evento sucedido; luego se crea un nuevo evento y se rellena con datos que se correspondan con el tipo de orden y evento; a continuación, el evento se coloca en la colección de eventos, y finalmente, se envía un mensaje sobre este evento al gráfico del programa de control y se rellena la variable que guarda el tipo del último evento sucedido.

La clase de colección de eventos está lista. Ahora, necesitamos incluirla en el objeto básico de la biblioteca.

Después de crear la clase de colección de eventos, ya no necesitamos algunas cosas que hicimos en la cuarta parte, para monitorear eventos en la clase del objeto básico CEngine. Por ello, merece la pena revisar el objeto básico.

  1. Vamos a eliminar del listado la variable de miembro privada de clase m_trade_event_code, que guarda el código del estado del evento comercial.
  2. Eliminamos los siguientes métodos privados:
    1. el método de desencriptado del código de un evento, SetTradeEvent(),
    2. el método que retorna la presencia de una bandera en un evento comercial, IsTradeEventFlag(),
    3. los métodos de trabajo con las colecciones de cobertura y compensación, WorkWithHedgeCollections() y WorkWithNettoCollections()
    4. y los métodos que retornan el código del evento comercial, TradeEventCode()

Añadimos al cuerpo de la clase la inclusión del archivo de la clase de la colección de eventos comerciales, declaramos el objeto de colección de eventos, añadimos a la sección privada de la clase el método para trabajar con eventos TradeEventsControl(); luego, en la sección pública cambiamos el nombre del método GetListHistoryDeals() a GetListDeals(), las transacciones siempre se ubican en la colección histórica, por lo que indicar explícitamente la colección en el nombre del método parece redundante. Cambiamos la implementación del método de reseteo del último evento comercial: como ahora recibimos el último evento desde la clase de colección de eventos, y dentro de esta clase existe el método de reseteo del último evento, solo necesitamos llamar en esta clase, en el método ResetLastTradeEvent(), el método homónimo desde la clase de colección de eventos.

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Collections\HistoryCollection.mqh"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Services\TimerCounter.mqh"
//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // Collection of historical orders and deals
   CMarketCollection    m_market;                        // Collection of market orders and deals
   CEventsCollection    m_events;                        // Collection of events
   CArrayObj            m_list_counters;                 // List of timer counters
   bool                 m_first_start;                   // First launch flag
   bool                 m_is_hedge;                      // Hedge account flag
   bool                 m_is_market_trade_event;         // Account trading event flag
   bool                 m_is_history_trade_event;        // Account history trading event flag
   ENUM_TRADE_EVENT     m_acc_trade_event;               // Account trading event
//--- Return the counter index by id
   int                  CounterIndex(const int id) const;
//--- Return (1) the first launch flag, (2) flag presence in a trading event
   bool                 IsFirstStart(void);
//--- Working with events
   void                 TradeEventsControl(void);
//--- Return the last (1) market pending order, (2) market order, (3) last position, (4) position by ticket
   COrder*              GetLastMarketPending(void);
   COrder*              GetLastMarketOrder(void);
   COrder*              GetLastPosition(void);
   COrder*              GetPosition(const ulong ticket);
//--- Return the last (1) removed pending order, (2) historical market order, (3) historical order (market or pending one) by its ticket
   COrder*              GetLastHistoryPending(void);
   COrder*              GetLastHistoryOrder(void);
   COrder*              GetHistoryOrder(const ulong ticket);
//--- Return the (1) first and the (2) last historical market orders from the list of all position orders, (3) the last deal
   COrder*              GetFirstOrderPosition(const ulong position_id);
   COrder*              GetLastOrderPosition(const ulong position_id);
   COrder*              GetLastDeal(void);
public:
   //--- Return the list of market (1) positions, (2) pending orders and (3) market orders
   CArrayObj*           GetListMarketPosition(void);
   CArrayObj*           GetListMarketPendings(void);
   CArrayObj*           GetListMarketOrders(void);
   //--- Return the list of historical (1) orders, (2) removed pending orders, (3) deals, (4) all position market orders by its id
   CArrayObj*           GetListHistoryOrders(void);
   CArrayObj*           GetListHistoryPendings(void);
   CArrayObj*           GetListDeals(void);
   CArrayObj*           GetListAllOrdersByPosID(const ulong position_id);
//--- Reset the last trading event
   void                 ResetLastTradeEvent(void)                       { this.m_events.ResetLastTradeEvent(); }
//--- Return the (1) last trading event and (2) hedge account flag
   ENUM_TRADE_EVENT     LastTradeEvent(void)                      const { return this.m_acc_trade_event;       }
   bool                 IsHedge(void)                             const { return this.m_is_hedge;              }
//--- Create the timer account
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- Timer
   void                 OnTimer(void);
//--- Constructor/destructor
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+

En el constructor de la clase CEngine, añadimos el procesamiento del resultado de la creación de un temporizador de milisegundos, y si no ha sido creado, mostramos un mensaje sobre ello en el diario. A continuación, vamos a crear la clase de procesamiento de ciertos errores, a establecer las banderas que verá el programa que funciona usando como base la biblioetca, y también a procesar las situaciones erróneas.

//+------------------------------------------------------------------+
//| CEngine constructor                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),m_acc_trade_event(TRADE_EVENT_NO_EVENT)
  {
   ::ResetLastError();
   if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
      Print(DFUN,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError());
   this.m_list_counters.Sort();
   this.m_list_counters.Clear();
   this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE);
   this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
  }
//+------------------------------------------------------------------+

Solo tenemos que llamar al método TradeEventsControl() en el temporizador de la clase después de que finalice la pausa del temporizador de las colecciones de órdenes, transacciones y posiciones.

//+------------------------------------------------------------------+
//| CEngine timer                                                    |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
//--- Timer of historical orders, deals, market orders and positions collections
   int index=this.CounterIndex(COLLECTION_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      //--- If unpaused, work with the collections events
      if(counter!=NULL && counter.IsTimeDone())
        {
         this.TradeEventsControl();
        }
     }
  }
//+------------------------------------------------------------------+

Vamos a mejorar el método que retorna una orden según el ticket, ya que en la lista de la colección histórica pueden encontrarse tanto órdenes pendientes como órdenes de mercado activadas y órdenes de cierre al cerrar una posición con otra opuesta. Por eso, debemos tener en cuenta todos los tipos de órdenes.

Para ello, primero buscaremos la orden según el ticket en la lista de órdenes de mercado y de cierre, y si la lista ha resultado vacía, buscaremos una orden pendiente eliminada con el mismo ticket. Si la orden tampoco se encuentra en esta lista, se retornará NULL. En caso contrario, se retornará el elemento de la lista en el que se ha encontrado la orden. Si tampoco logramos obtener la orden de la lista, se retornará NULL.

//+------------------------------------------------------------------+
//| Return historical order by its ticket                            |
//+------------------------------------------------------------------+
COrder* CEngine::GetHistoryOrder(const ulong ticket)
  {
   CArrayObj* list=this.GetListHistoryOrders();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,(long)ticket,EQUAL);
   if(list==NULL || list.Total()==0)
     {
      list=this.GetListHistoryPendings();
      list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,(long)ticket,EQUAL);
      if(list==NULL) return NULL;
     }
   COrder* order=list.At(0);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

Vamos a implementar el método para trabajar con los eventos de la cuenta, TradeEventsControl():

//+------------------------------------------------------------------+
//| Check trading events                                             |
//+------------------------------------------------------------------+
void CEngine::TradeEventsControl(void)
  {
//--- Initialize the code and flags of trading events
   this.m_is_market_trade_event=false;
   this.m_is_history_trade_event=false;
//--- Update the lists 
   this.m_market.Refresh();
   this.m_history.Refresh();
//--- Actions during the first launch
   if(this.IsFirstStart())
     {
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      return;
     }
//--- Check the changes in the market state and account history 
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();

//--- In case of any event, send the lists, flags and the number of new orders and deals to the event collection and update it
   if(this.m_is_history_trade_event || this.m_is_market_trade_event)
     {
      this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),
                            this.m_is_history_trade_event,this.m_is_market_trade_event,
                            this.m_history.NewOrders(),this.m_market.NewPendingOrders(),
                            this.m_market.NewMarketOrders(),this.m_history.NewDeals());
      //--- Get the last account trading event
      this.m_acc_trade_event=this.m_events.GetLastTradeEvent();
     }
  }

El método ha resultado mucho más breve que su predecesor WorkWithHedgeCollections(), de la cuarta parte de la descripción de la biblioteca.

El método es sencillo y no requiere de aclaraciones, tanto más que el código incluye comentarios: podrá comprender con facilidad su sencilla lógica.

Vamos a mostrar el listado completo de la clase CEngine actualizada:

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Collections\HistoryCollection.mqh"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Services\TimerCounter.mqh"
//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // Collection of historical orders and deals
   CMarketCollection    m_market;                        // Collection of market orders and deals
   CEventsCollection    m_events;                        // Collection of events
   CArrayObj            m_list_counters;                 // List of timer counters
   bool                 m_first_start;                   // First launch flag
   bool                 m_is_hedge;                      // Hedge account flag
   bool                 m_is_market_trade_event;         // Account trading event flag
   bool                 m_is_history_trade_event;        // Account history trading event flag
   ENUM_TRADE_EVENT     m_acc_trade_event;               // Account trading event
//--- Return the counter index by id
   int                  CounterIndex(const int id) const;
//--- Return (1) the first launch flag, (2) flag presence in a trading event
   bool                 IsFirstStart(void);
//--- Working with events
   void                 TradeEventsControl(void);
//--- Return the last (1) market pending order, (2) market order, (3) last position, (4) position by ticket
   COrder*              GetLastMarketPending(void);
   COrder*              GetLastMarketOrder(void);
   COrder*              GetLastPosition(void);
   COrder*              GetPosition(const ulong ticket);
//--- Return the last (1) removed pending order, (2) historical market order, (3) historical order (market or pending one) by its ticket
   COrder*              GetLastHistoryPending(void);
   COrder*              GetLastHistoryOrder(void);
   COrder*              GetHistoryOrder(const ulong ticket);
//--- Return the (1) first and the (2) last historical market orders from the list of all position orders, (3) the last deal
   COrder*              GetFirstOrderPosition(const ulong position_id);
   COrder*              GetLastOrderPosition(const ulong position_id);
   COrder*              GetLastDeal(void);
public:
   //--- Return the list of market (1) positions, (2) pending orders and (3) market orders
   CArrayObj*           GetListMarketPosition(void);
   CArrayObj*           GetListMarketPendings(void);
   CArrayObj*           GetListMarketOrders(void);
   //--- Return the list of historical (1) orders, (2) removed pending orders, (3) deals, (4) all position market orders by its id
   CArrayObj*           GetListHistoryOrders(void);
   CArrayObj*           GetListHistoryPendings(void);
   CArrayObj*           GetListDeals(void);
   CArrayObj*           GetListAllOrdersByPosID(const ulong position_id);
//--- Reset the last trading event
   void                 ResetLastTradeEvent(void)                       { this.m_events.ResetLastTradeEvent(); }
//--- Return the (1) last trading event and (2) hedge account flag
   ENUM_TRADE_EVENT     LastTradeEvent(void)                      const { return this.m_acc_trade_event;       }
   bool                 IsHedge(void)                             const { return this.m_is_hedge;              }
//--- Create the timer account
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- Timer
   void                 OnTimer(void);
//--- Constructor/destructor
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+
//| CEngine constructor                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),m_acc_trade_event(TRADE_EVENT_NO_EVENT)
  {
   ::ResetLastError();
   if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
      Print(DFUN,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError());
   this.m_list_counters.Sort();
   this.m_list_counters.Clear();
   this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE);
   this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
  }
//+------------------------------------------------------------------+
//| CEngine destructor                                               |
//+------------------------------------------------------------------+
CEngine::~CEngine()
  {
   ::EventKillTimer();
  }
//+------------------------------------------------------------------+
//| CEngine timer                                                    |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
//--- Timer of historical orders, deals, market orders and positions collections
   int index=this.CounterIndex(COLLECTION_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      //--- If unpaused, work with the collections events
      if(counter!=NULL && counter.IsTimeDone())
        {
         this.TradeEventsControl();
        }
     }
  }
//+------------------------------------------------------------------+
//| Create the timer counter                                         |
//+------------------------------------------------------------------+
void CEngine::CreateCounter(const int id,const ulong step,const ulong pause)
  {
   if(this.CounterIndex(id)>WRONG_VALUE)
     {
      ::Print(TextByLanguage("Ошибка. Уже создан счётчик с идентификатором ","Error. Already created counter with id "),(string)id);
      return;
     }
   m_list_counters.Sort();
   CTimerCounter* counter=new CTimerCounter(id);
   if(counter==NULL)
      ::Print(TextByLanguage("Не удалось создать счётчик таймера ","Failed to create timer counter "),(string)id);
   counter.SetParams(step,pause);
   if(this.m_list_counters.Search(counter)==WRONG_VALUE)
      this.m_list_counters.Add(counter);
   else
     {
      string t1=TextByLanguage("Ошибка. Счётчик с идентификатором ","Error. Counter with ID ")+(string)id;
      string t2=TextByLanguage(", шагом ",", step ")+(string)step;
      string t3=TextByLanguage(" и паузой "," and pause ")+(string)pause;
      ::Print(t1+t2+t3+TextByLanguage(" уже существует"," already exists"));
      delete counter;
     }
  }
//+------------------------------------------------------------------+
//| Return the counter index in the list by id                       |
//+------------------------------------------------------------------+
int CEngine::CounterIndex(const int id) const
  {
   int total=this.m_list_counters.Total();
   for(int i=0;i<total;i++)
     {
      CTimerCounter* counter=this.m_list_counters.At(i);
      if(counter==NULL) continue;
      if(counter.Type()==id) 
         return i;
     }
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+
//| Return the first launch flag, reset the flag                     |
//+------------------------------------------------------------------+
bool CEngine::IsFirstStart(void)
  {
   if(this.m_first_start)
     {
      this.m_first_start=false;
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//| Check trading events                                             |
//+------------------------------------------------------------------+
void CEngine::TradeEventsControl(void)
  {
//--- Initialize trading event code and flags
   this.m_is_market_trade_event=false;
   this.m_is_history_trade_event=false;
//--- Update the lists 
   this.m_market.Refresh();
   this.m_history.Refresh();
//--- Actions during the first launch
   if(this.IsFirstStart())
     {
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      return;
     }
//--- Check the changes in the market status and account history 
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();

//--- If there is any event, send the lists, the flags and the number of new orders and deals to the event collection, and update it
   if(this.m_is_history_trade_event || this.m_is_market_trade_event)
     {
      this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),
                            this.m_is_history_trade_event,this.m_is_market_trade_event,
                            this.m_history.NewOrders(),this.m_market.NewPendingOrders(),
                            this.m_market.NewMarketOrders(),this.m_history.NewDeals());
      //--- Receive the last account trading event
      this.m_acc_trade_event=this.m_events.GetLastTradeEvent();
     }
  }
//+------------------------------------------------------------------+
//| Return the list of market positions                              |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListMarketPosition(void)
  {
   CArrayObj* list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_POSITION,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Return the list of market pending orders                         |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListMarketPendings(void)
  {
   CArrayObj* list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_PENDING,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Return the list of market orders                                 |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListMarketOrders(void)
  {
   CArrayObj* list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_ORDER,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Return the list of historical orders                             |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListHistoryOrders(void)
  {
   CArrayObj* list=this.m_history.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_ORDER,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Return the list of removed pending orders                        |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListHistoryPendings(void)
  {
   CArrayObj* list=this.m_history.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_PENDING,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Return the list of deals                                         |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListDeals(void)
  {
   CArrayObj* list=this.m_history.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//|  Return the list of all position orders                          |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListAllOrdersByPosID(const ulong position_id)
  {
   CArrayObj* list=this.GetListHistoryOrders();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_ID,position_id,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Return the last position                                         |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastPosition(void)
  {
   CArrayObj* list=this.GetListMarketPosition();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Return position by ticket                                        |
//+------------------------------------------------------------------+
COrder* CEngine::GetPosition(const ulong ticket)
  {
   CArrayObj* list=this.GetListMarketPosition();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,ticket,EQUAL);
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TICKET);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Return the last deal                                             |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastDeal(void)
  {
   CArrayObj* list=this.GetListDeals();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Return the last market pending order                             |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastMarketPending(void)
  {
   CArrayObj* list=this.GetListMarketPendings();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Return the last historical pending order                         |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastHistoryPending(void)
  {
   CArrayObj* list=this.GetListHistoryPendings();
   if(list==NULL) return NULL;
   list.Sort(#ifdef __MQL5__ SORT_BY_ORDER_TIME_OPEN_MSC #else SORT_BY_ORDER_TIME_CLOSE_MSC #endif);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Return the last market order                                     |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastMarketOrder(void)
  {
   CArrayObj* list=this.GetListMarketOrders();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Return the last historical market order                          |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastHistoryOrder(void)
  {
   CArrayObj* list=this.GetListHistoryOrders();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Return historical order by its ticket                            |
//+------------------------------------------------------------------+
COrder* CEngine::GetHistoryOrder(const ulong ticket)
  {
   CArrayObj* list=this.GetListHistoryOrders();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,(long)ticket,EQUAL);
   if(list==NULL || list.Total()==0)
     {
      list=this.GetListHistoryPendings();
      list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,(long)ticket,EQUAL);
      if(list==NULL) return NULL;
     }
   COrder* order=list.At(0);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Return the first historical market order                         |
//| from the list of all position orders                             |
//+------------------------------------------------------------------+
COrder* CEngine::GetFirstOrderPosition(const ulong position_id)
  {
   CArrayObj* list=this.GetListAllOrdersByPosID(position_id);
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN);
   COrder* order=list.At(0);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Return the last historical market order                          |
//| from the list of all position orders                             |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastOrderPosition(const ulong position_id)
  {
   CArrayObj* list=this.GetListAllOrdersByPosID(position_id);
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

Poniendo a prueba la definición, el procesamiento y la obtención de eventos

Ya está todo listo para trabajar con los eventos, así que podemos preparar el asesor para simular la definición de eventos, su procesamiento y su envío al programa de control.

En el directorio donde se ubica el terminal\MQL5\Experts\TestDoEasy, creamos la carpeta Part05 y copiamos en la misma el asesor de la parte anterior TestDoEasyPart04.mq5 con un nuevo nombre: TestDoEasyPart05.mq5

En su procesador de eventos OnChartEvent(), introducimos los cambios para obtener los eventos de usuario:

//+------------------------------------------------------------------+
//| 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);
     } 
  }
//+------------------------------------------------------------------+

Aquí: si el identificador del evento es superior o igual al identificador del evento de usuario, obtenemos el código de evento transmitido desde la biblioteca por los herederos de la clase CEvent. Dado que, al enviar un evento de usuario con la función EventChartCustom() indicada en el parámetro de la función custom_event_id (en él registramos nuestro evento) se añade al valor de nuestro evento el valor de la constante CHARTEVENT_CUSTOM (su valor es igual a 1000) de la enumeración ENUM_CHART_EVENT, para obtener de vuelta el valor de nuestro evento, deberemos simplemente restar del identificador del evento (id) el valor de CHARTEVENT_CUSTOM. Después, solo tendremos que mostrar los datos sobre el evento en el diario del terminal.
Se imprimirán: el identificador como está, la descripción del evento como denominación del valor de la enumeración ENUM_TRADE_EVENT, el valor lparam, donde se guarda el ticket de la orden o posición, el valor dparam, donde se guarda el precio al que sucedió el evento, y el valor sparam, el símbolo de la orden o posición que ha participado en el evento, o bien la denominación de la divisa de la cuenta, si el evento es una operación de balance.
Por ejemplo, así:

2019.04.06 03:19:54.442 OnChartEvent: id=1001, event=TRADE_EVENT_PENDING_ORDER_PLASED, lparam=375419507, dparam=1.14562, sparam=EURUSD

Asimismo, es necesario corregir en el asesor el lote calculado para el cierre parcial, pues en las versiones anteriores de los asesores de prueba había un error: al calcular el lote, se tomaba el valor del volumen no ejecutado de la posición (VolumeCurrent()), dicho valor es siempre cero en el simulador al abrir una posición, dado que el simulador no modela aperturas parciales. Por consiguiente, se tomaba el valor mínimo de lote para el cierre, ya que la función de cálculo de lote siempre corregía el cero por el valor mínimo permitido para el lote.

Vamos a localizar en el código las líneas donde tiene lugar el cálculo para el cierre parcial, luego, sustituimos VolumeCurrent() por Volume():

               //--- Calculate the closing volume and close half of the Buy position by ticket
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0));
               //--- Calculate the closing volume and close half of the Sell position by ticket
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0));

Solo hay dos sitios en el código: el cierre de la mitad de una posición Buy y el cierre de la mitad de una posición Sell.

Asimsimo, vamos a añadir a los parámetros de entrada del asesor el desplazamiento de los botones en los ejes X e Y, para que la ubicación del conjunto de botones en el gráfico del simulador visual resulte más cómoda (hemos tenido que desplazar los botones más a la derecha, para ver en el visualizador los tickets de las órdenes y posiciones que podrían quedar ocultas por los botones):

//--- 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 
//--- global variables

Vamos a cambiar ligeramente el código de la función de creación de botones:

//+------------------------------------------------------------------+
//| Create the buttons panel                                         |
//+------------------------------------------------------------------+
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)+2*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-3) x=cx;
      y=(cy-(i-(i>6 ? 7 : 0))*(h+1));
      if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-3 ? 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;
  }
//+------------------------------------------------------------------+

y la llamada de esta función en el manejador OnInit() del asesor:

//--- create buttons
   if(!CreateButtons(InpButtShiftX,InpButtShiftY))
      return INIT_FAILED;
//--- setting trade parameters

Aquí tenemos el código completo del asesor de prueba:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart05.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/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
  };
#define TOTAL_BUTT   (17)
//--- 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 
//--- 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;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Check account type
   if(!engine.IsHedge())
     {
      Alert(TextByLanguage("Ошибка. Счёт должен быть хеджевым","Error. Account must be hedge"));
      return INIT_FAILED;
     }
//--- 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;
//--- create buttons
   if(!CreateButtons(InpButtShiftX,InpButtShiftY))
      return INIT_FAILED;
//--- 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();
      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);
        }
     }
   if(engine.LastTradeEvent()!=last_event)
     {
      Comment("\nLast trade event: ",EnumToString(engine.LastTradeEvent()));
      last_event=engine.LastTradeEvent();
     }
  }
//+------------------------------------------------------------------+
//| 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);
     } 
  }
//+------------------------------------------------------------------+
//| Create the buttons panel                                         |
//+------------------------------------------------------------------+
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)+2*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-3) x=cx;
      y=(cy-(i-(i>6 ? 7 : 0))*(h+1));
      if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-3 ? 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;
  }
//+------------------------------------------------------------------+
//| Create the button                                                |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Return the button status                                         |
//+------------------------------------------------------------------+
bool ButtonState(const string name)
  {
   return (bool)ObjectGetInteger(0,name,OBJPROP_STATE);
  }
//+------------------------------------------------------------------+
//| Set the button status                                            |
//+------------------------------------------------------------------+
void ButtonState(const string name,const bool state)
  {
   ObjectSetInteger(0,name,OBJPROP_STATE,state);
  }
//+------------------------------------------------------------------+
//| Transform enumeration into the button text                       |
//+------------------------------------------------------------------+
string EnumToButtText(const ENUM_BUTTONS member)
  {
   string txt=StringSubstr(EnumToString(member),5);
   StringToLower(txt);
   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;
  }
//+------------------------------------------------------------------+
//| Handle pressing the buttons                                      |
//+------------------------------------------------------------------+
void PressButtonEvents(const string button_name)
  {
   //--- Convert the button name into its string ID
   string button=StringSubstr(button_name,StringLen(prefix));
   //--- If the button is pressed
   if(ButtonState(button_name))
     {
      //--- If the BUTT_BUY button is pressed: Open Buy position
      if(button==EnumToString(BUTT_BUY))
        {
         //--- Get the correct StopLoss and TakeProfit prices relative to StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit);
         //--- Open Buy position
         trade.Buy(NormalizeLot(Symbol(),lot),Symbol(),0,sl,tp);
        }
      //--- If the BUTT_BUY_LIMIT button is pressed: Set BuyLimit
      else if(button==EnumToString(BUTT_BUY_LIMIT))
        {
         //--- Get the correct order placement price relative to StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending);
         //--- Get the correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit);
         //--- Set BuyLimit order
         trade.BuyLimit(lot,price_set,Symbol(),sl,tp);
        }
      //--- If the BUTT_BUY_STOP button is pressed: Set BuyStop
      else if(button==EnumToString(BUTT_BUY_STOP))
        {
         //--- Get the correct order placement price relative to StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- Get the correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set,takeprofit);
         //--- Set BuyStop order
         trade.BuyStop(lot,price_set,Symbol(),sl,tp);
        }
      //--- If the BUTT_BUY_STOP_LIMIT button is pressed: Set BuyStopLimit
      else if(button==EnumToString(BUTT_BUY_STOP_LIMIT))
        {
         //--- Get the correct BuyStop price relative to StopLevel
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- Calculate BuyLimit order price relative to BuyStop placement level considering StopLevel
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_stoplimit,price_set_stop);
         //--- Get correct StopLoss and TakeProfit prices relative to order placement level considering 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);
         //--- Set BuyStopLimit order
         trade.OrderOpen(Symbol(),ORDER_TYPE_BUY_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
        }
      //--- If the BUTT_SELL button is pressed: Open Sell position
      else if(button==EnumToString(BUTT_SELL))
        {
         //--- Get the correct StopLoss and TakeProfit prices relative to StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL,0,takeprofit);
         //--- Open Sell position
         trade.Sell(lot,Symbol(),0,sl,tp);
        }
      //--- If the BUTT_SELL_LIMIT button is pressed: Set SellLimit
      else if(button==EnumToString(BUTT_SELL_LIMIT))
        {
         //--- Get the correct order placement price relative to StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_pending);
         //--- Get the correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,takeprofit);
         //--- Set SellLimit order
         trade.SellLimit(lot,price_set,Symbol(),sl,tp);
        }
      //--- If the BUTT_SELL_STOP button is pressed: Set SellStop
      else if(button==EnumToString(BUTT_SELL_STOP))
        {
         //--- Get the correct order placement price relative to StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- Get the correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set,takeprofit);
         //--- Set SellStop order
         trade.SellStop(lot,price_set,Symbol(),sl,tp);
        }
      //--- If the BUTT_SELL_STOP_LIMIT button is pressed: Set SellStopLimit
      else if(button==EnumToString(BUTT_SELL_STOP_LIMIT))
        {
         //--- Get the correct SellStop order price relative to StopLevel
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- Calculate SellLimit order price relative to SellStop level considering StopLevel
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_stoplimit,price_set_stop);
         //--- Get the correct StopLoss and TakeProfit prices relative to the order placement level considering 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);
         //--- Set SellStopLimit order
         trade.OrderOpen(Symbol(),ORDER_TYPE_SELL_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
        }
      //--- If the BUTT_CLOSE_BUY button is pressed: Close Buy with the maximum profit
      else if(button==EnumToString(BUTT_CLOSE_BUY))
        {
         //--- Get the list of all open positions
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Select only Buy positions from the list
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Buy position with the maximum profit
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Get the Buy position ticket and close the position by the ticket
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- If the BUTT_CLOSE_BUY2 button is pressed: Close the half of Buy with the maximum profit
      else if(button==EnumToString(BUTT_CLOSE_BUY2))
        {
         //--- Get the list of all open positions
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Select only Buy positions from the list
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Buy position with the maximum profit
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Calculate the closed volume and close the half of the Buy position by the ticket
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0));
              }
           }
        }
      //--- If the BUTT_CLOSE_BUY_BY_SELL button is pressed: Close Buy with the maximum profit by the opposite Sell with the maximum profit
      else if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL))
        {
         //--- Get the list of all open positions
         CArrayObj* list_buy=engine.GetListMarketPosition();
         //--- Select only Buy positions from the list
         list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Buy position with the maximum profit
         int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
         //--- Get the list of all open positions
         CArrayObj* list_sell=engine.GetListMarketPosition();
         //--- Select only Sell positions from the list
         list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Sell position with the maximum profit
         int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
         if(index_buy>WRONG_VALUE && index_sell>WRONG_VALUE)
           {
            //--- Select the Buy position with the maximum profit
            COrder* position_buy=list_buy.At(index_buy);
            //--- Select the Sell position with the maximum profit
            COrder* position_sell=list_sell.At(index_sell);
            if(position_buy!=NULL && position_sell!=NULL)
              {
               //--- Close the Buy position by the opposite Sell one
               trade.PositionCloseBy(position_buy.Ticket(),position_sell.Ticket());
              }
           }
        }
      //--- If the BUTT_CLOSE_SELL button is pressed: Close Sell with the maximum profit
      else if(button==EnumToString(BUTT_CLOSE_SELL))
        {
         //--- Get the list of all open positions
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Select only Sell positions from the list
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Sell position with the maximum profit
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Get the Sell position ticket and close the position by the ticket
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- If the BUTT_CLOSE_SELL2 button is pressed: Close half of the Sell with the maximum profit
      else if(button==EnumToString(BUTT_CLOSE_SELL2))
        {
         //--- Get the list of all open positions
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Select only Sell positions from the list
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Sell position with the maximum profit
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Calculate the closed volume and close the half of the Sell position by the ticket
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0));
              }
           }
        }
      //--- If the BUTT_CLOSE_SELL_BY_BUY button is pressed: Close Sell with the maximum profit by the opposite Buy with the maximum profit
      else if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY))
        {
         //--- Get the list of all open positions
         CArrayObj* list_sell=engine.GetListMarketPosition();
         //--- Select only Sell positions from the list
         list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Sell position with the maximum profit
         int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
         //--- Get the list of all open positions
         CArrayObj* list_buy=engine.GetListMarketPosition();
         //--- Select only Buy positions from the list
         list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Sort the list by profit considering commission and swap
         list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Get the index of the Buy position with the maximum profit
         int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
         if(index_sell>WRONG_VALUE && index_buy>WRONG_VALUE)
           {
            //--- Select the Sell position with the maximum profit
            COrder* position_sell=list_sell.At(index_sell);
            //--- Select the Buy position with the maximum profit
            COrder* position_buy=list_buy.At(index_buy);
            if(position_sell!=NULL && position_buy!=NULL)
              {
               //--- Close the Sell position by the opposite Buy one
               trade.PositionCloseBy(position_sell.Ticket(),position_buy.Ticket());
              }
           }
        }
      //--- If the BUTT_CLOSE_ALL is pressed: Close all positions starting with the one with the least profit
      else if(button==EnumToString(BUTT_CLOSE_ALL))
        {
         //--- Get the list of all open positions
         CArrayObj* list=engine.GetListMarketPosition();
         if(list!=NULL)
           {
            //--- Sort the list by profit considering commission and swap
            list.Sort(SORT_BY_ORDER_PROFIT_FULL);
            int total=list.Total();
            //--- In the loop from the position with the least profit
            for(int i=0;i<total;i++)
              {
               COrder* position=list.At(i);
               if(position==NULL)
                  continue;
               //--- close each position by its ticket
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- If the BUTT_DELETE_PENDING button is pressed: Remove the first pending order
      else if(button==EnumToString(BUTT_DELETE_PENDING))
        {
         //--- Get the list of all orders
         CArrayObj* list=engine.GetListMarketPendings();
         if(list!=NULL)
           {
            //--- Sort the list by placement time
            list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
            int total=list.Total();
            //--- In the loop from the position with the most amount of time
            for(int i=total-1;i>=0;i--)
              {
               COrder* order=list.At(i);
               if(order==NULL)
                  continue;
               //--- delete the order by its ticket
               trade.OrderDelete(order.Ticket());
              }
           }
        }
      //--- If the BUTT_PROFIT_WITHDRAWAL button is pressed: Withdraw funds from the account
      if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL))
        {
         //--- If the program is launched in the tester
         if(MQLInfoInteger(MQL_TESTER))
           {
            //--- Emulate funds withdrawal
            TesterWithdrawal(withdrawal);
           }
        }
      //--- Wait for 1/10 of a second
      Sleep(100);
      //--- "Unpress" the button and redraw the chart
      ButtonState(button_name,false);
      ChartRedraw();
     }
  }
//+------------------------------------------------------------------+

Ahora, ya podemos compilar el asesor e iniciarlo en el simulador. Al pulsar los botones, en el diario del simulador se mostrarán breves mensajes de dos líneas sobre los eventos sucedidos en la cuenta.


Las entradas del manejador de eventos del asesor no se mostrarán en el diario, estas funcionan fuera del simulador. Si clicamos en los botones del asesor en una cuenta demo, en el diario del terminal se mostrarán tres líneas: dos líneas del método de muestra de mensajes breves de la clase CEvent y una línea del manejador OnChartEvent() del asesor.

Ejemplo de muestra de información en el diario del asesor al colocar y eliminar una orden pendiente:

- Pending order placed: 2019.04.05 23:19:55.248 -                                                              
EURUSD 0.10 Sell Limit #375419507 at price 1.14562                                                             
OnChartEvent: id=1001, event=TRADE_EVENT_PENDING_ORDER_PLASED, lparam=375419507, dparam=1.14562, sparam=EURUSD 
- Pending order removed: 2019.04.05 23:19:55.248 -                                                             
EURUSD 0.10 Sell Limit #375419507 at price 1.14562                                                             
OnChartEvent: id=1002, event=TRADE_EVENT_PENDING_ORDER_REMOVED, lparam=375419507, dparam=1.14562, sparam=EURUSD

¿Qué es lo próximo?

En el siguiente artículo, comenzaremos a añadir la funcionalidad necesaria para trabajar con las cuentas de compensación de MetaTrader 5.

Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y los archivos de los asesores de prueba. El lector podrá descargar y poner a prueba todo por sí mismo.
Si tiene cualquier duda, observación o sugerencia, podrá formularla en los comentarios al artículo.

Volver al contenido

Artículos de esta serie:

Parte 1. Concepto, organización de datos.
Parte 2. Colección de ó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.


Traducción del ruso hecha por MetaQuotes Software Corp.
Artículo original: https://www.mql5.com/ru/articles/6211

Archivos adjuntos |
MQL5.zip (73.98 KB)
Cómo visualizar la historia del comercio multidivisa en informes con formato HTML y CSV Cómo visualizar la historia del comercio multidivisa en informes con formato HTML y CSV

Como sabemos, MetaTrader 5 ofrece la posibilidad de realizar simulaciones multidivisa desde su aparición. Esta función tiene mucha demanda entre la mayoría de los tráders, pero, por desgracia, no es tan universal como querríamos. En el presente artículo, ofrecemos varios programas para trazar gráficos con la ayuda de objetos gráficos usando como base la historia comercial de informes en los formatos HTML y CSV. El comercio con varios instrumentos puede analizarse paralelamente en varias subventanas, o en una sola ventana con la ayuda de la alternancia dinámica a una orden del usuario.

Aplicando OLAP en el trading (parte 1): Fundamentos del análisis corriente de datos multidimensionales Aplicando OLAP en el trading (parte 1): Fundamentos del análisis corriente de datos multidimensionales

En este artículo, se describen los principios básicos de la construcción del framework para el procesamiento analítico en línea (OLAP en inglés), su implementación en MQL en el ambiente de MetaTrader, usando el procesamiento del historial de trading de la cuenta como ejemplo.

Implementando OLAP en la negociación (Parte 2): Visualización de los resultados del análisis interactivo de los datos multidimensionales Implementando OLAP en la negociación (Parte 2): Visualización de los resultados del análisis interactivo de los datos multidimensionales

En este artículo, se consideran diversos aspectos del desarrollo de la interfaz gráfica interactiva de un programa MQL diseñado para el procesamiento analítico en línea (OLAP) del historial de la cuenta y de los informes comerciales. Para obtener un resultado visual, se usan las ventanas maximizadas y de escala, una disposición adaptable de los controles «de goma» y un nuevo control para mostrar diagramas. A base de eso, fue implementado GUI con una selección de indicadores a lo largo de los ejes de coordenadas, funciones agregadas, tipos de los gráficos y ordenaciones.

Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte VI): Eventos en la cuenta con compensación Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte VI): Eventos en la cuenta con compensación

En anteriores artículos comenzamos a crear una gran biblioteca multiplataforma cuyo objetivo es simplificar la escritura de programas para las plataformas MetaTrader 5 y MetaTrader 4. En la quinta parte, hemos creado las clases de los eventos comerciales y la colección de eventos desde donde se envían los eventos a la objeto de la biblioteca Engine y al gráfico del programa de control. En esta parte de la descripción, vamos a añadir la posibilidad de trabajar con la biblioteca en las cuentas de tipo compensación.