Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte II): Colección de órdenes y transacciones históricas

Artyom Trishkin | 22 mayo, 2019

Contenido

En el artículo anterior, comenzamos a crear una gran biblioteca multiplataforma, cuyo cometido es facilitar la creación de programas para las plataformas MetaTrader 5 y MetaTrader 4. Creamos el objeto abstracto COrder, que es el objeto básico para guardar los datos de las órdenes y transacciones históricas, así como de las órdenes y posiciones de mercado.

En esta parte, vamos a continuar el desarrollo, creando todos los objetos necesarios para guardar los datos de la historia de la cuenta en la colección; además, proyectaremos la colección de órdenes y transacciones históricas, y realizaremos ciertas correcciones y adiciones en los objetos y enumeraciones creados.

Objetos de las órdenes y transacciones históricas

Nuestro objeto básico COrder comprende dentro de sí todos los datos de cualquier objeto de la cuenta, ya sea una orden de mercado (orden para ejecutar alguna acción), una orden pendiente, una transacción o una posición. Para que podamos operar libremente con todos estos objetos, separadamente unos de otros, vamos a crear usando como base la COrder abstracta varias clases que indicarán con exactitud la pertenencia de un objeto a su tipo.

En la lista de órdenes y transacciones históricas puede haber varios tipos de estos objetos: una orden pendiente eliminada, una orden de mercado colocada y una transacción (el resultado de la ejecución de una orden de mercado). Asimismo, podemos destacar otros dos tipos de objetos en MQL4: las operaciones de balance y crédito (En MQL5, estos datos se guardan en las propiedades de la transacción).

Vamos a crear una nueva clase CHistoryOrder en la carpeta Objects de la biblioteca.
Para ello, pulsamos el botón derecho sobre la carpeta Objects y elegimos el punto del menú "Nuevo archivo Ctrl+N". En el Wizard MQL5 que se abrirá, seleccionamos "Nueva clase" y pulsamos el botón "Continuar". En el campo con el nombre de la clase, introducimos CHistoryOrder (1), y en el campo para indicar la clase básica, introducimos el nombre de nuestra clase abstracta COrder (2) y pulsamos "Listo".


Después de ello, en la carpeta Objects se creará el archivo HistoryOrder.mqh (3). Lo abrimos:

//+------------------------------------------------------------------+
//|                                                 HistoryOrder.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHistoryOrder : public COrder
  {
private:

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

Por el momento, se trata simplemente de una plantilla de clase; si intentamos compilarla, aparecerán los cinco errores que ya conocemos: la nueva clase heredada de COrder no sabe nada sobre su padre. Añadimos el archivo de inclusión Order.mqh:

//+------------------------------------------------------------------+
//|                                                 HistoryOrder.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Order.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHistoryOrder : public COrder
  {
private:

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

Ahora, todo se compila.

La clase será bastante pequeña. Necesitamos definir los métodos de la clase padre COrder que retornan las banderas de soporte de las propiedades de la orden, así como definir la transmisión del ticket de la orden a la clase.

//+------------------------------------------------------------------+
//|                                                 HistoryOrder.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 "Order.mqh"
//+------------------------------------------------------------------+
//| History market order                                             |
//+------------------------------------------------------------------+
class CHistoryOrder : public COrder
  {
public:
   //--- Constructor
                     CHistoryOrder(const ulong ticket) : COrder(ORDER_STATUS_HISTORY_ORDER,ticket) {}
   //--- Supported integer order properties
   virtual bool      SupportProperty(ENUM_ORDER_PROP_INTEGER property);
  };
//+------------------------------------------------------------------+
//| Return 'true' if the order supports the passed property,         |
//| otherwise, return 'false'                                        |
//+------------------------------------------------------------------+
bool CHistoryOrder::SupportProperty(ENUM_ORDER_PROP_INTEGER property)
  {
   if(property==ORDER_PROP_TIME_EXP       || 
      property==ORDER_PROP_DEAL_ENTRY     || 
      property==ORDER_PROP_TIME_UPDATE    || 
      property==ORDER_PROP_TIME_UPDATE_MSC
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+

De esta forma, transmitimos el ticket de la orden elegida al constructor de clase, y transmitimos el estado de la orden (orden histórica) y su ticket al constructor protegido del objeto padre COrder.

Asimismo, hemos redefinido el método virtual de la clase padre, que retorna el soporte de las propiedades de tipo entero de la orden. Los métodos que retornan el soporte de las propiedades de tipo real y string los hemos dejado sin cambio: estos métodos de la clase padre siempre retornan true, así que consideraremos que la orden histórica soporta todas las propiedades de tipo real y string, y por eso no vamos a redefinirlas por el momento.

Comprobamos la propiedad en el método de soporte de propiedades de tipo entero de la orden, y si se trata del tiempo de expiración, la dirección de la transacción o la hora de cambio de la posición, retornamos false; las órdenes de mercado no dan soporte a estas propiedades. El resto de propiedades tienen soporte y se retorna true.

Por analogía, creamos la clase de la orden pendiente (eliminada) histórica CHistoryPending y la clase de la transacción histórica CHistoryDeal:

//+------------------------------------------------------------------+
//|                                               HistoryPending.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Order.mqh"
//+------------------------------------------------------------------+
//| Removed pending order                                            |
//+------------------------------------------------------------------+
class CHistoryPending : public COrder
  {
public:
   //--- Constructor
                     CHistoryPending(const ulong ticket) : COrder(ORDER_STATUS_HISTORY_PENDING,ticket) {}
   //--- Supported order properties (1) real, (2) integer
   virtual bool      SupportProperty(ENUM_ORDER_PROP_DOUBLE property);
   virtual bool      SupportProperty(ENUM_ORDER_PROP_INTEGER property);
  };
//+------------------------------------------------------------------+
//| Return 'true' if the order supports the passed property,         |
//| otherwise, return 'false'                                        |
//+------------------------------------------------------------------+
bool CHistoryPending::SupportProperty(ENUM_ORDER_PROP_INTEGER property)
  {
   if(property==ORDER_PROP_PROFIT_PT         ||
      property==ORDER_PROP_DEAL_ORDER        ||
      property==ORDER_PROP_DEAL_ENTRY        ||
      property==ORDER_PROP_TIME_UPDATE       ||
      property==ORDER_PROP_TIME_UPDATE_MSC   ||
      property==ORDER_PROP_TICKET_FROM       ||
      property==ORDER_PROP_TICKET_TO         ||
      property==ORDER_PROP_CLOSE_BY_SL       ||
      property==ORDER_PROP_CLOSE_BY_TP
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Return 'true' if the order supports the passed property,         |
//| otherwise, returns 'false'                                       |
//+------------------------------------------------------------------+
bool CHistoryPending::SupportProperty(ENUM_ORDER_PROP_DOUBLE property)
  {
   if(property==ORDER_PROP_COMMISSION  ||
      property==ORDER_PROP_SWAP        ||
      property==ORDER_PROP_PROFIT      ||
      property==ORDER_PROP_PROFIT_FULL ||
      property==ORDER_PROP_PRICE_CLOSE
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|                                                  HistoryDeal.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Order.mqh"
//+------------------------------------------------------------------+
//| Historical deal                                                  |
//+------------------------------------------------------------------+
class CHistoryDeal : public COrder
  {
public:
   //--- Constructor
                     CHistoryDeal(const ulong ticket) : COrder(ORDER_STATUS_DEAL,ticket) {}
   //--- Supported deal properties (1) real, (2) integer
   virtual bool      SupportProperty(ENUM_ORDER_PROP_DOUBLE property);
   virtual bool      SupportProperty(ENUM_ORDER_PROP_INTEGER property);
  };
//+------------------------------------------------------------------+
//| Return 'true' if the order supports the passed property,         |
//| otherwise, return 'false'                                        |
//+------------------------------------------------------------------+
bool CHistoryDeal::SupportProperty(ENUM_ORDER_PROP_INTEGER property)
  {
   if(property==ORDER_PROP_TIME_EXP          || 
      property==ORDER_PROP_PROFIT_PT         ||
      property==ORDER_PROP_POSITION_BY_ID    ||
      property==ORDER_PROP_TIME_UPDATE       ||
      property==ORDER_PROP_TIME_UPDATE_MSC   ||
      property==ORDER_PROP_STATE             ||
      (
       this.OrderType()==DEAL_TYPE_BALANCE &&
       (
        property==ORDER_PROP_POSITION_ID     ||
        property==ORDER_PROP_POSITION_BY_ID  ||
        property==ORDER_PROP_TICKET_FROM     ||
        property==ORDER_PROP_TICKET_TO       ||
        property==ORDER_PROP_DEAL_ORDER      ||
        property==ORDER_PROP_MAGIC           ||
        property==ORDER_PROP_TIME_CLOSE      ||
        property==ORDER_PROP_TIME_CLOSE_MSC  ||
        property==ORDER_PROP_CLOSE_BY_SL     ||
        property==ORDER_PROP_CLOSE_BY_TP
       )
      )
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
bool CHistoryDeal::SupportProperty(ENUM_ORDER_PROP_DOUBLE property)
  {
   if(property==ORDER_PROP_TP                || 
      property==ORDER_PROP_SL                || 
      property==ORDER_PROP_PRICE_CLOSE       ||
      property==ORDER_PROP_VOLUME_CURRENT    ||
      property==ORDER_PROP_PRICE_STOP_LIMIT  ||
      (
       this.OrderType()==DEAL_TYPE_BALANCE &&
       (
        property==ORDER_PROP_PRICE_OPEN      ||
        property==ORDER_PROP_COMMISSION      ||
        property==ORDER_PROP_SWAP            ||
        property==ORDER_PROP_VOLUME
       )
      )
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+

Ya hemos creado los tres objetos-orden sobre cuya base vamos a construir la colección de órdenes históricas. Todos ellos han sido heredados de la clase básica de la orden abstracta COrder, tienen sus propiedades, pero permiten retornar solo aquellas que son soportadas por este tipo de orden. Todos ellos se contrarán en una lista de colección: la colección de órdenes históricas de la cual vamos a obtener la información necesaria de la historia de la cuenta en cualquier composición y orden.

Conviene notar que no todas las propiedades soportadas y no soportadas se tienen en cuenta en los métodos SupportProperty() para la muestra de las propiedades de la orden en el diario de registro. En las transacciones, por ejemplo, se tienen en cuenta solo tres: la transacción de compra, la transacción de venta y la operación de balance.

No hay nada malo en ello, simplemente, las propiedades que por el momento no son tenidas en cuenta y no han sido registradas concretamenten en los métodos que retornan su soporte, siempre serán imprimidas. Podremos añadirlas a los métodos más tarde, para no imprimir aquellas propiedades que siempre retornan valores cero independientemente de la situación (esto significa que no tienen soporte).

Colecciones de las órdenes y transacciones históricas

Siempre resulta útil tener la historia de la cuenta a mano. Sí, el terminal la ofrece y nos da las herramientas para obetenerla en los programas. Pero, para nuestras necesidades, es imprescindible tener una lista propia que podamos clasificar y reorganizar según la solicitud para retornar a nuestros programas los datos deseados. Por consiguiente, es necesario comprobar en cada tick el cambio del anterior estado de la historia de la cuenta y, cuando este cambie, recalcular nuestra lista de órdenes y transacciones históricas. Por desgracia, reorganizar toda la historia en cada tick resulta demasiado laborioso. Por eso, crearemos solo una adición a nuestra lista de nuevos datos, mientras que los antiguos siguen estando calculados y guardados en la lista.

Vamos a crear la nueva clase CHistoryCollection en la carpeta Collections:

Pulsamos el botón derecho en la carpeta Collections y seleccionamos el punto "Nuevo archivo"; en la ventana del Wizard MQL, elegimos "Nueva clase" y pulsamos "Continuar". Introducimos el nombre de la clase CHistoryCollection, dejamos vacío el campo de la clase básica y pulsamos "Listo".


En la carpeta Collections, se creará el nuevo archivo HistoryCollection:

//+------------------------------------------------------------------+
//|                                            HistoryCollection.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHistoryCollection
  {
private:

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

Vamos a rellenarlo.

Para la lista, utilizaremos una lista dinámica de punteros a los ejemplares de los objetos de la biblioteca estándar CArrayObj. Lo incluimos en nuestro archivo y lo definimos de inmediato. (Por cierto, para hacerlo con mayor facilidad, podemos usar el menú contextual con el botón derecho del ratón)


Incluimos CArrayObj y definimos la lista de órdenes y transacciones históricas en la sección privada de la clase:

//+------------------------------------------------------------------+
//|                                            HistoryCollection.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
//+------------------------------------------------------------------+
//| Collection of historical orders and deals                        |
//+------------------------------------------------------------------+
class CHistoryCollection
  {
private:
   CArrayObj         m_list_all_orders;      // List of historical orders and deals

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

Vamos a necesitar guardar los índices de las últimas órdenes y transacciones de la lista de la historia del terminal añadidas a la colección; asimismo, será necesario conocer la diferencia entre el número anterior de órdenes y transacciones y el actual, por eso vamos a crear miembros privados de clase para guardarlos:

//+------------------------------------------------------------------+
//|                                            HistoryCollection.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
//+------------------------------------------------------------------+
//| Collection of historical orders and deals                        |
//+------------------------------------------------------------------+
class CHistoryCollection
  {
private:
   CArrayObj         m_list_all_orders;      // List of historical orders and deals
   int               m_index_order;          // Index of the last order added to the collection from the terminal history list (MQL4, MQL5)
   int               m_index_deal;           // Index of the last deal added to the collection from the terminal history list (MQL5)
   int               m_delta_order;          // Difference in the number of orders as compared to the past check
   int               m_delta_deal;           // Difference in the number of deals as compared to the past check
public:
                     CHistoryCollection();
                    ~CHistoryCollection();
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHistoryCollection::CHistoryCollection()
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHistoryCollection::~CHistoryCollection()
  {
  }
//+------------------------------------------------------------------+

En el primer inicio, todos los miembros de clase privados son redefinidos, y la historia se recalcula de nuevo. Para conseguir esto, solo debemos añadir en el constructor de la clase la lista de inicialización de los miembros de la clase y definir el criterio por defecto según el cual se clasificará la lista de colección.

Ya tenemos ahora el constructor por defecto, y antes de escribir su implementación, tenemos que crear la enumeración en la que se escribirán todos los posibles criterios de clasificación de las órdenes y transacciones en la lista de colección.
Pero primero vamos a ordenar las propiedades de tipo entero, real y string de la orden, para imprimirlas en el diario de registro de una forma más lógica. Abrimos el archivo Defines.mqh de la carpeta raíz de la biblioteca y ubicamos los miembros de la enumeración en el orden necesario:

//+------------------------------------------------------------------+
//| Order, deal, position integer properties                         |
//+------------------------------------------------------------------+
enum ENUM_ORDER_PROP_INTEGER
  {
   ORDER_PROP_TICKET = 0,                                   // Order ticket
   ORDER_PROP_MAGIC,                                        // Order magic number
   ORDER_PROP_TIME_OPEN,                                    // Open time (MQL5 Deal time)
   ORDER_PROP_TIME_CLOSE,                                   // Close time (MQL5 Execution or removal time - ORDER_TIME_DONE)
   ORDER_PROP_TIME_OPEN_MSC,                                // Open time in milliseconds (MQL5 Deal time in msc)
   ORDER_PROP_TIME_CLOSE_MSC,                               // Close time in milliseconds (MQL5 Execution or removal time - ORDER_TIME_DONE_MSC)
   ORDER_PROP_TIME_EXP,                                     // Order expiration date (for pending orders)
   ORDER_PROP_STATUS,                                       // Order status (from the ENUM_ORDER_STATUS enumeration)
   ORDER_PROP_TYPE,                                         // Order type (MQL5 deal type)
   ORDER_PROP_DIRECTION,                                    // Direction (Buy, Sell)
   ORDER_PROP_REASON,                                       // Deal/order/position reason or source
   ORDER_PROP_POSITION_ID,                                  // Position ID
   ORDER_PROP_POSITION_BY_ID,                               // Opposite position ID
   ORDER_PROP_DEAL_ORDER,                                   // Order a deal is based on
   ORDER_PROP_DEAL_ENTRY,                                   // Deal direction – IN, OUT or IN/OUT
   ORDER_PROP_TIME_UPDATE,                                  // Position change time in seconds
   ORDER_PROP_TIME_UPDATE_MSC,                              // Position change time in milliseconds
   ORDER_PROP_TICKET_FROM,                                  // Parent order ticket
   ORDER_PROP_TICKET_TO,                                    // Derived order ticket
   ORDER_PROP_PROFIT_PT,                                    // Profit in points
   ORDER_PROP_CLOSE_BY_SL,                                  // Flag of closing by StopLoss
   ORDER_PROP_CLOSE_BY_TP,                                  // Flag of closing by TakeProfit
  }; 
#define ORDER_PROP_INTEGER_TOTAL    (22)                    // Total number of integer properties
//+------------------------------------------------------------------+
//| Order, deal, position real properties                            |
//+------------------------------------------------------------------+
enum ENUM_ORDER_PROP_DOUBLE
  {
   ORDER_PROP_PRICE_OPEN = ORDER_PROP_INTEGER_TOTAL,        // Open price (MQL5 deal price)
   ORDER_PROP_PRICE_CLOSE,                                  // Close price
   ORDER_PROP_SL,                                           // StopLoss price
   ORDER_PROP_TP,                                           // TakeProfit price
   ORDER_PROP_PROFIT,                                       // Profit
   ORDER_PROP_COMMISSION,                                   // Commission
   ORDER_PROP_SWAP,                                         // Swap
   ORDER_PROP_VOLUME,                                       // Volume
   ORDER_PROP_VOLUME_CURRENT,                               // Unexecuted volume
   ORDER_PROP_PROFIT_FULL,                                  // Profit+commission+swap
   ORDER_PROP_PRICE_STOP_LIMIT,                             // Limit order price when StopLimit order is activated
  };
#define ORDER_PROP_DOUBLE_TOTAL     (11)                    // Total number of real properties
//+------------------------------------------------------------------+
//| Order, deal, position string properties                          |
//+------------------------------------------------------------------+
enum ENUM_ORDER_PROP_STRING
  {
   ORDER_PROP_SYMBOL = (ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL), // Order symbol
   ORDER_PROP_COMMENT,                                      // Order comment
   ORDER_PROP_EXT_ID                                        // Order ID in an external trading system
  };
#define ORDER_PROP_STRING_TOTAL     (3)                     // Total number of string properties
//+------------------------------------------------------------------+

Ahora vamos a añadir a este mismo archivo la enumeración con todos los tipos posibles de clasificación de órdenes y transacciones:

//+------------------------------------------------------------------+
//| Possible criteria of orders and deals sorting                    |
//+------------------------------------------------------------------+
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)
   SORT_BY_ORDER_TYPE            =  8,                      // Sort by order type
   SORT_BY_ORDER_REASON          =  10,                     // Sort by deal/order/position reason/source
   SORT_BY_ORDER_POSITION_ID     =  11,                     // Sort by position ID
   SORT_BY_ORDER_POSITION_BY_ID  =  12,                     // Sort by opposite position ID
   SORT_BY_ORDER_DEAL_ORDER      =  13,                     // Sort by order a deal is based on
   SORT_BY_ORDER_DEAL_ENTRY      =  14,                     // Sort by deal direction – IN, OUT or IN/OUT
   SORT_BY_ORDER_TIME_UPDATE     =  15,                     // Sort by position change time in seconds
   SORT_BY_ORDER_TIME_UPDATE_MSC =  16,                     // Sort by position change time in milliseconds
   SORT_BY_ORDER_TICKET_FROM     =  17,                     // Sort by parent order ticket
   SORT_BY_ORDER_TICKET_TO       =  18,                     // Sort by derived order ticket
   SORT_BY_ORDER_PROFIT_PT       =  19,                     // Sort by order profit in points
   SORT_BY_ORDER_CLOSE_BY_SL     =  20,                     // Sort by order closing by StopLoss flag
   SORT_BY_ORDER_CLOSE_BY_TP     =  21,                     // Sort by order closing by TakeProfit flag
   //--- Sort by real properties
   SORT_BY_ORDER_PRICE_OPEN      =  ORDER_PROP_INTEGER_TOTAL,// Sort by open price
   SORT_BY_ORDER_PRICE_CLOSE     =  23,                     // Sort by close price
   SORT_BY_ORDER_SL              =  24,                     // Sort by StopLoss price
   SORT_BY_ORDER_TP              =  25,                     // Sort by TakeProfit price
   SORT_BY_ORDER_PROFIT          =  26,                     // Sort by profit
   SORT_BY_ORDER_COMMISSION      =  27,                     // Sort by commission
   SORT_BY_ORDER_SWAP            =  28,                     // Sort by swap
   SORT_BY_ORDER_VOLUME          =  29,                     // Sort by volume
   SORT_BY_ORDER_VOLUME_CURRENT  =  30,                     // Sort by unexecuted volume
   SORT_BY_ORDER_PROFIT_FULL     =  31,                     // Sort by profit+commission+swap criterion
   SORT_BY_ORDER_PRICE_STOP_LIMIT=  32,                     // Sort by Limit order when StopLimit order is activated
   //--- Sort by string properties
   SORT_BY_ORDER_SYMBOL          =  ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL,// Sort by symbol
   SORT_BY_ORDER_COMMENT         =  34,                     // Sort by comment
   SORT_BY_ORDER_EXT_ID          =  35                      // Sort by order ID in an external trading system
  };
//+------------------------------------------------------------------+

Importante: los índices de los miembros de la clasificación deberá coincidir con los índices de los miembros de las propiedades, ya que, para buscar correctamente en la lista, esta debe estar clasificada según el mismo valor que es usado para realizar la búsqueda en esa lista.

Como hemos podido notar, en esta lista se ha omitido la propiedad de clasificación ORDER_PROP_DIRECTION, puesto que esta propiedad de servicio es utilizada para las necesidades de la biblioteca, al igual que sucede con otras propiedades personalizadas que hemos añadido anteriormente. Sin embargo, es posible que requieran de clasificación, porque han sido dejadas.

Ahora, vamos a escribir la implementación del constructor de la clase CHistoryCollection:

//+------------------------------------------------------------------+
//|                                            HistoryCollection.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\DELib.mqh"
//+------------------------------------------------------------------+
//| Collection of historical orders and deals                        |
//+------------------------------------------------------------------+
class CHistoryCollection
  {
private:
   CArrayObj         m_list_all_orders;      // List of historical orders and deals
   int               m_index_order;          // Index of the last order added to the collection from the terminal history list (MQL4, MQL5)
   int               m_index_deal;           // Index of the last deal added to the collection from the terminal history list (MQL5)
   int               m_delta_order;          // Difference in the number of orders as compared to the past check
   int               m_delta_deal;           // Difference in the number of deals as compared to the past check
public:
                     CHistoryCollection();
                    ~CHistoryCollection();
  };
//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CHistoryCollection::CHistoryCollection(void) : m_index_deal(0), 
                                               m_delta_deal(0), 
                                               m_index_order(0),
                                               m_delta_order(0) 
  {
   m_list_all_orders.Sort(SORT_BY_ORDER_TIME_CLOSE);
  }
//+------------------------------------------------------------------+

Vamos a analizar el código de arriba.
Puesto que en el constructor de la clase se usa el valor de la enumeración que se acaba de añadir, deberemos incluir el archivo Defines.mqh en el archivo de la clase.
En el primer artículo
, al preparar la clase básica de orden abstracta, creamos la biblioteca de funciones de servicio DELib.mqh, en la que incluimos el archivo Defines.mqh con todas las enumeraciones y macrosustituciones necesarias. Por eso, vamos a incluir precisamente la biblioteca de funciones de servicio.
En el constructor, en la lista de inicialización, se redefinen todos los índices y valores de las diferencias entre el número actual y el anterior, y después, en el cuerpo del constructor, se establece la lista de colección para la clasificación por defecto según la hora de cierre.

Ahora es el momento de trabajar en la recopilación de información desde la cuenta y guardarla en la lista de colección. Para ello, deberemos iterar en los ciclos por la historia de la cuenta e inscribir cada orden en la lista. Si el número de órdenes o transacciones ha cambiado con respecto a la anterior comprobación, deberemos establecer la bandera de evento comercial ocurrido. Esta bandera será necesaria para enviar mensajes sobre un nuevo evento ocurrido en la historia de la cuenta a un programa externo.
En la sección privada de la clase, declaramos la bandera de evento comercial, y en la pública, el método Refresh(), para actualizar la colección de la historia:

//+------------------------------------------------------------------+
//| Collection of historical orders and deals                        |
//+------------------------------------------------------------------+
class CHistoryCollection
  {
private:
   CArrayObj         m_list_all_orders;      // List of all historical orders and deals
   bool              m_is_trade_event;       // Trading event flag
   int               m_index_order;          // Index of the last order added to the collection from the terminal history list (MQL4, MQL5)
   int               m_index_deal;           // Index of the last deal added to the collection from the terminal history list (MQL5)
   int               m_delta_order;          // Difference in the number of orders as compared to the past check
   int               m_delta_deal;           // Difference in the number of deals as compared to the past check
public:
                     CHistoryCollection();
   //--- Update the list of orders, fill data on the number of new ones and set the trading event flag
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

Para implementar la actualización de la lista de órdenes de la colección, necesitaremos otra macrosustitución más para solicitar los datos históricos al completo. La función HistorySelect() se encargará de ello. A sus parámetros se transmiten las fechas de inicio y finalización de los datos requeridos. Para obtener la lista completa de toda la historia, debemos transmitir la fecha de inicio como 0, y la fecha de finalización como TimeCurrent(), pero, en este caso, podrían darse situaciones en las que no se retorne todo lo que hay en los datos históricos. Para evitar esta situación, tendremos que introducir, en lugar de TimeCurrent(), una fecha que supere la hora actual del servidor. Introduciremos ahí la fecha más alta posible: 31.12.3000 23:59:59. Esto también resulta cómodo porque en los símbolos personalizados puede existir esa fecha, y aun así la obtención de la historia funcionará.

Insertamos en el archivo Defines.mqh una nueva macrosustitución:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+
#define COUNTRY_LANG   ("Russian")              // Country language
#define DFUN           (__FUNCTION__+": ")      // "Function description"
#define END_TIME       (D'31.12.3000 23:59:59') // End time to request account history data
//+------------------------------------------------------------------+

Ahora, en lugar de introducir TimeCurrent() como hora final, vamos a introducir la macro END_TIME

Implementando la actualización de la lista de órdenes de la colección:

//+------------------------------------------------------------------+
//| Update the order list                                            |
//+------------------------------------------------------------------+
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;
         m_list_all_orders.InsertSort(order);
        }
      else
        {
         //--- Removed pending orders
         CHistoryPending *order=new CHistoryPending(::OrderTicket());
         if(order==NULL) continue;
         m_list_all_orders.InsertSort(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)
        {
         CHistoryOrder *order=new CHistoryOrder(order_ticket);
         if(order==NULL) continue;
         m_list_all_orders.InsertSort(order);
        }
      else
        {
         CHistoryPending *order=new CHistoryPending(order_ticket);
         if(order==NULL) continue;
         m_list_all_orders.InsertSort(order);
        }
     }
//--- 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;
      m_list_all_orders.InsertSort(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 
  }
//+------------------------------------------------------------------+

Comprobamos la pertenencia a MQL4 o MQL5. Vamos a analizar el código usando MQL5 como ejemplo, ya que es un poco más complicado.

Primero, solicitamos la historia completa de la cuenta y, si no lo hemos logrado, salimos hasta el tick siguiente. Después de solicitar la historia, se crearán dos listas: la lista de órdenes y la de transacciones.

Primero, iteramos en el ciclo por la lista completa de órdenes. El índice inicial, desde el que comenzará el ciclo, es el resultado del funcionamiento de este ciclo la vez anterior (en el primer inicio = 0). Esto nos permitirá desplazarnos solo por las órdenes nuevas que hayan aparecido tras la última comprobación, y no tener que iniciar un costoso ciclo por toda la historia.

A continuación, obtenemos el ticket de la orden y su tipo. Y según el resultado de la comrpobación del tipo de orden, creamos el nuevo objeto. O bien una orden de mercado histórica, o bien una orden pendiente eliminada, y las ubicamos en la lista de colección directamente de forma clasificada (ya hemos establecido anteriormente la clasificación según la hora de cierre).

Una vez haya finalizado el funcionemiento del ciclo, guardamos el nuevo índice de la orden, para comenzar desde él la próxima vez, y la diferencia entre el resultado del funcionamiento del ciclo anterior y el actual, que, en esencia, supone el número de órdenes añadidas de nuevo.
El ciclo de trabajo con las transacciones es exactamente igual, con la excepción de que en él no necesitamos dividir las transacciones por tipos, como hemos hecho con la lista de órdenes, sino que podemos introducir el objeto de transacción directamente en la lista de colección.

El ciclo en la versión del código MQL4 es prácticamente igual, salvo que allí se da el ciclo de toda la historia posible disponible en la historia de la cuenta. El usuario puede establecer esta en la pestaña "Historia" del terminal, por eso, aquí ya es el propio usuario el que debe preocuparse de que su historia esté disponible, por si su programa la requiriese. O bien se puede usar WinAPI para obtener la historia completa, lo cual se sale del marco de este artículo.

Cuando el funcionamiento de los ciclos ha finalizado, se comprueba el número de nuevas órdenes, y si el número es superior a cero, se establece la bandera de evento comercial ocurrido.

En esencia, hemos implementado en la clase de la colección histórica la obtención de datos que implementamos en la primera parte del asesor de prueba, con la excepción de que ahora se crean diferentes objetos de órdenes históricas, divididos conforme a su estado. Y ahora, para comprobar cómo funciona todo esto, vamos a necesitar obtener desde fuera (desde el programa que usará esta biblioteca, en este caso, desde el asesor de prueba) la lista de colección de órdenes históricas creada.

Para ello, vamos a añadir a la sección pública de la clase CHistoryCollection el método GetList() sin parámetros (en el siguiente artículo, se añadirán además los métodos de obtención de la lista, pero ya con parámetros):
//+------------------------------------------------------------------+
//| Collection of historical orders and deals                        |
//+------------------------------------------------------------------+
class CHistoryCollection
  {
private:
   CArrayObj         m_list_all_orders;      // List of all historical orders and deals
   bool              m_is_trade_event;       // Trading event flag
   int               m_index_order;          // Index of the last order added to the collection from the terminal history list (MQL4, MQL5)
   int               m_index_deal;           // Index of the last deal added to the collection from the terminal history list (MQL5)
   int               m_delta_order;          // Difference in the number of orders as compared to the past check
   int               m_delta_deal;           // Difference in the number of deals as compared to the past check
public:
   //--- Return the full collection list 'as is'
   CArrayObj*        GetList(void)           { return &m_list_all_orders;  }
//--- Constructor
                     CHistoryCollection();
   //--- Update the list of orders, fill data on the number of new ones and set the trading event flag
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

Como podemos ver el código, se retorna el puntero a la lista, y no el propio objeto. Y esto es suficiente para usar la lista en los programas. Pero esto será insuficiente para usar la biblioteca de forma rápida y cómoda, por lo que, más tarde (literalmente en el próximo artículo sobre la descripción de la biblioteca), vamos a organizar un sencillo acceso a los datos necesarios por solicitud. En los artículos siguientes, simplificaremos aún más el acceso, creando funciones especiales para trabajar con la biblioteca a través de ellas.

Pero ahora, vamos a comprobar cómo se rellena la lista Para ello, crearemos un pequeño asesor de prueba. Vamos a crear en la carpeta TestDoEasy, que creamos en la primera parte, la subcarpeta Part02; en ella se ubicará el archivo del segundo asesor de prueba, llamado TestDoEasyPart02, en el que incluiremos la colección creada:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart02.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Collections\HistoryCollection.mqh>
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

Como ahora en la lista se guardan objetos de diferente tipo heredados de un mismo padre COrder, vamos a implementar la muestra de las descripciones de los objetos seleccionados en el diario. Para ello, crearemos en los parámetros de entrada una enumeración con la selección de los tipos de las órdenes mostradas y un objeto de colección, desde el cual leeremos la información de la cuenta:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart02.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\Collections\HistoryCollection.mqh>
//--- enums
enum ENUM_TYPE_ORDERS
  {
   TYPE_ORDER_MARKET,   // Market orders
   TYPE_ORDER_PENDING,  // Pending orders
   TYPE_ORDER_DEAL      // Deals
  };
//--- input parameters
input ENUM_TYPE_ORDERS  InpOrderType   =  TYPE_ORDER_DEAL;  // Show type:
//--- global variables
CHistoryCollection history;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+

Ahora, exactamente igual que hicimos en el anterior asesor de prueba de la primera parte de la descripción de la biblioteca, en el manejador OnInit() calculamos la historia de la cuenta y mostramos la información sobre las órdenes en el diario en un ciclo:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- update history
   history.Refresh();
//--- get the pointer to the full collection list
   CArrayObj* list=history.GetList();
   if(list==NULL)
     {
      Print("Could not get collection list");
      return INIT_FAILED;
     }
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      //--- get order from the list
      COrder* order=list.At(i);
      if(order==NULL) continue;
      //--- if this is a deal
      if(order.Status()==ORDER_STATUS_DEAL && InpOrderType==TYPE_ORDER_DEAL)
         order.Print();
      //--- if this is a historical market order
      if(order.Status()==ORDER_STATUS_HISTORY_ORDER && InpOrderType==TYPE_ORDER_MARKET)
         order.Print();
      //--- if this is a removed pending order
      if(order.Status()==ORDER_STATUS_HISTORY_PENDING && InpOrderType==TYPE_ORDER_PENDING)
         order.Print();
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Aquí: creamos el puntero a la lista de colección y lo obtenemos desde CHistoryCollection, con la ayuda del método GetList() creado allí.
A continuación, obtenemos en el ciclo el objeto de orden de la lista, comprobamos su estado y su permiso de muestra en el diario en los ajustes del asesor.
Dependiendo del resultado de la comprobación, imprimimos los datos en el diario, o no lo hacemos.
Merece la pena destacar que, aunque obtenemos de la lista el objeto básico COrder, en el diario se muestran solo los datos que son inherentes a los herederos de esta orden básica: las órdenes de mercado, las órdenes pendientes y las transacciones en las que se han redefinido los métodos virtuales, que retornan la bandera que indica que la orden soporta solo las propiedades inherentes.

Compilamos e iniciamos el asesor. En el diario se muestran los datos de los tipos de órdenes y transacciones elegidos:

Si nos fijamos atentamente en los tipos de propiedades mostradas, veremos propiedades que no son típicas para las órdenes en MQL5: beneficio, swap, comisión, y beneficio en puntos.

Vamos a introducir algunas correcciones y adiciones en los objetos ya creados.

Añadimos al método SupportProperty(ENUM_ORDER_PROP_INTEGER property) de la clase CHistoryOrder la propiedad no soportada para MQL5, beneficio en puntos:

//+------------------------------------------------------------------+
//| Return 'true' if an order supports the passed property,          |
//| otherwise, return 'false'                                        |
//+------------------------------------------------------------------+
bool CHistoryOrder::SupportProperty(ENUM_ORDER_PROP_INTEGER property)
  {
   if(property==ORDER_PROP_TIME_EXP       || 
      property==ORDER_PROP_DEAL_ENTRY     || 
      property==ORDER_PROP_TIME_UPDATE    || 
      property==ORDER_PROP_TIME_UPDATE_MSC
      #ifdef __MQL5__                     ||
      property==ORDER_PROP_PROFIT_PT
      #endif                        
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+

y también añadimos un método virtual que retorna el soporte de propiedades de tipo real por parte de las órdenes:

//+------------------------------------------------------------------+
//| Historical market order                                          |
//+------------------------------------------------------------------+
class CHistoryOrder : public COrder
  {
public:
   //--- Constructor
                     CHistoryOrder(const ulong ticket) : COrder(ORDER_STATUS_HISTORY_ORDER,ticket) {}
   //--- Supported integer properties of an order
   virtual bool      SupportProperty(ENUM_ORDER_PROP_INTEGER property);
   //--- Supported real properties of an order
   virtual bool      SupportProperty(ENUM_ORDER_PROP_DOUBLE property);
  };
//+------------------------------------------------------------------+

y su implementación:

//+------------------------------------------------------------------+
//| Return 'true' if an order supports the passed property,          |
//| otherwise, return 'false'                                        |
//+------------------------------------------------------------------+
bool CHistoryOrder::SupportProperty(ENUM_ORDER_PROP_DOUBLE property)
  {
#ifdef __MQL5__
   if(property==ORDER_PROP_PROFIT      || 
      property==ORDER_PROP_PROFIT_FULL || 
      property==ORDER_PROP_SWAP        || 
      property==ORDER_PROP_COMMISSION  ||
      property==ORDER_PROP_PRICE_STOP_LIMIT
     ) return false;
#endif 
   return true;
  }
//+------------------------------------------------------------------+

Si se trata de MQL5, el beneficio, el swap, la comisión, el beneficio total y el precio de colocación de la orden StopLimit no tienen soporte. Para MQL4 y el resto de las propiedades de tipo real de la orden en MQL5, retornamos la bandera de soporte por parte de la orden de propiedades de tipo real.

Las órdenes en MQL5 tienen la propiedad ORDER_STATE, es decir, el estado de la orden definido en la enumeración ENUM_ORDER_STATE.
Vamos a añadirlo a la lista de propiedades de tipo entero de las órdenes, a la enumeración ENUM_ORDER_PROP_INTEGER en el archivo Defines.mqh:

//+------------------------------------------------------------------+
//| Order, deal, position integer properties                         |
//+------------------------------------------------------------------+
enum ENUM_ORDER_PROP_INTEGER
  {
   ORDER_PROP_TICKET = 0,                                   // Order ticket
   ORDER_PROP_MAGIC,                                        // Order magic number
   ORDER_PROP_TIME_OPEN,                                    // Open time (MQL5 Deal time)
   ORDER_PROP_TIME_CLOSE,                                   // Close time (MQL5 Execution or removal time - ORDER_TIME_DONE)
   ORDER_PROP_TIME_OPEN_MSC,                                // Open time in milliseconds (MQL5 Deal time in msc)
   ORDER_PROP_TIME_CLOSE_MSC,                               // Close time in milliseconds (MQL5 Execution or removal time - ORDER_TIME_DONE_MSC)
   ORDER_PROP_TIME_EXP,                                     // Order expiration date (for pending orders)
   ORDER_PROP_STATUS,                                       // Order status (from the ENUM_ORDER_STATUS enumeration)
   ORDER_PROP_TYPE,                                         // Order type (MQL5 deal type)
   ORDER_PROP_DIRECTION,                                    // Direction (Buy, Sell)
   ORDER_PROP_REASON,                                       // Deal/order/position reason or source
   ORDER_PROP_STATE,                                        // Order status (from the ENUM_ORDER_STATE enumeration)
   ORDER_PROP_POSITION_ID,                                  // Position ID
   ORDER_PROP_POSITION_BY_ID,                               // Opposite position ID
   ORDER_PROP_DEAL_ORDER,                                   // Order a deal is based on
   ORDER_PROP_DEAL_ENTRY,                                   // Deal direction – IN, OUT or IN/OUT
   ORDER_PROP_TIME_UPDATE,                                  // Position change time in seconds
   ORDER_PROP_TIME_UPDATE_MSC,                              // Position change time in milliseconds
   ORDER_PROP_TICKET_FROM,                                  // Parent order ticket
   ORDER_PROP_TICKET_TO,                                    // Derived order ticket
   ORDER_PROP_PROFIT_PT,                                    // Profit in points
   ORDER_PROP_CLOSE_BY_SL,                                  // Flag of closing by StopLoss
   ORDER_PROP_CLOSE_BY_TP,                                  // Flag of closing by TakeProfit
  }; 
#define ORDER_PROP_INTEGER_TOTAL    (23)                    // Total number of integer properties
//+------------------------------------------------------------------+

y a cambiar necesariamente el número de propiedades de tipo entero de la orden de 22 a 23 en la macrosustitución ORDER_PROP_INTEGER_TOTAL, que indica el número de propiedades de tipo entero de la orden y se usa para calcular la "dirección" exacta de propiedad necesaria de la orden.
En este mismo archivo, vamos a añadir una nueva propiedad a los posibles criterios de clasificación de las órdenes, para que podamos clasificar las órdenes según esta nueva propiedad, e implementar un cálculo más cómodo de los índices en el caso de que las enumeraciones cambien posteriormente:

//+------------------------------------------------------------------+
//| Possible criteria of orders and deals sorting                    |
//+------------------------------------------------------------------+
#define FIRST_DBL_PROP              (ORDER_PROP_INTEGER_TOTAL)
#define FIRST_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)
   SORT_BY_ORDER_TYPE            =  8,                      // Sort by order type
   SORT_BY_ORDER_REASON          =  10,                     // Sort by deal/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_DBL_PROP,         // Sort by open price
   SORT_BY_ORDER_PRICE_CLOSE     =  FIRST_DBL_PROP+1,       // Sort by close price
   SORT_BY_ORDER_SL              =  FIRST_DBL_PROP+2,       // Sort by StopLoss price
   SORT_BY_ORDER_TP              =  FIRST_DBL_PROP+3,       // Sort by TakeProfit price
   SORT_BY_ORDER_PROFIT          =  FIRST_DBL_PROP+4,       // Sort by profit
   SORT_BY_ORDER_COMMISSION      =  FIRST_DBL_PROP+5,       // Sort by commission
   SORT_BY_ORDER_SWAP            =  FIRST_DBL_PROP+6,       // Sort by swap
   SORT_BY_ORDER_VOLUME          =  FIRST_DBL_PROP+7,       // Sort by volume
   SORT_BY_ORDER_VOLUME_CURRENT  =  FIRST_DBL_PROP+8,       // Sort by unexecuted volume
   SORT_BY_ORDER_PROFIT_FULL     =  FIRST_DBL_PROP+9,       // Sort by profit+commission+swap criterion
   SORT_BY_ORDER_PRICE_STOP_LIMIT=  FIRST_DBL_PROP+10,      // Sort by Limit order when StopLimit order is activated
   //--- Sort by string properties
   SORT_BY_ORDER_SYMBOL          =  FIRST_STR_PROP,         // Sort by symbol
   SORT_BY_ORDER_COMMENT         =  FIRST_STR_PROP+1,       // Sort by comment
   SORT_BY_ORDER_EXT_ID          =  FIRST_STR_PROP+2        // Sort by order ID in an external trading system
  };
//+------------------------------------------------------------------+

En la sección protegida de la clase de orden abstracta COrder, en el archivo Order.mqh, declaramos el método OrderState(), que registra su estado en las propiedades de la orden a partir de la enumeración ENUM_ORDER_STATE:

protected:
   //--- Protected parametric constructor
                     COrder(ENUM_ORDER_STATUS order_status,const ulong ticket);

   //--- Get and return integer properties of a selected order from its parameters
   long              OrderMagicNumber(void)        const;
   long              OrderTicket(void)             const;
   long              OrderTicketFrom(void)         const;
   long              OrderTicketTo(void)           const;
   long              OrderPositionID(void)         const;
   long              OrderPositionByID(void)       const;
   long              OrderOpenTimeMSC(void)        const;
   long              OrderCloseTimeMSC(void)       const;
   long              OrderType(void)               const;
   long              OrderState(void)              const;
   long              OrderTypeByDirection(void)    const;
   long              OrderTypeFilling(void)        const;
   long              OrderTypeTime(void)           const;
   long              OrderReason(void)             const;
   long              DealOrder(void)               const;
   long              DealEntry(void)               const;
   bool              OrderCloseByStopLoss(void)    const;
   bool              OrderCloseByTakeProfit(void)  const;
   datetime          OrderOpenTime(void)           const;
   datetime          OrderCloseTime(void)          const;
   datetime          OrderExpiration(void)         const;
   datetime          PositionTimeUpdate(void)      const;
   datetime          PositionTimeUpdateMSC(void)   const;

y añadimos su implementación:

//+------------------------------------------------------------------+
//| Return the order status                                          |
//+------------------------------------------------------------------+
long COrder::OrderState(void) const
  {
#ifdef __MQL4__              
   return ORDER_STATE_FILLED;
#else
   long res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=::HistoryOrderGetInteger(m_ticket,ORDER_STATE); break;
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_STATE);                 break;
      case ORDER_STATUS_MARKET_ACTIVE     : 
      case ORDER_STATUS_DEAL              : 
      default                             : res=0;                                              break;
     }
   return res;
#endif
  }
//+------------------------------------------------------------------+

Para MQL4, por ahora vamos a retornar que la orden se ha ejecutado por completo. Para MQL5, o bien retornamos 0 (si es una transacción o una posición), o bien el estado de la orden (si se trata de una orden de mercado o una orden pendiente), dependiendo del estatus de la orden.

En la sección pública de la clase COrder, declaramos el método que retorna la descripción del estado de la orden:

//+------------------------------------------------------------------+
//| Descriptions of order object properties                          |
//+------------------------------------------------------------------+
   //--- Get description of an order's (1) integer, (2) real and (3) string property
   string            GetPropertyDescription(ENUM_ORDER_PROP_INTEGER property);
   string            GetPropertyDescription(ENUM_ORDER_PROP_DOUBLE property);
   string            GetPropertyDescription(ENUM_ORDER_PROP_STRING property);
   //--- Return order status name
   string            StatusDescription(void)    const;
   //---  Return order or position name
   string            TypeDescription(void)      const;
   //--- Return order status description
   string            StateDescription(void)     const;
   //--- Return deal direction name
   string            DealEntryDescription(void) const;
   //--- Return order/position direction type
   string            DirectionDescription(void) const;
   //--- Send description of order properties to the journal (full_prop=true - all properties, false - only supported ones)
   void              Print(const bool full_prop=false);

y su implementación:

//+------------------------------------------------------------------+
//| Return order status description                                  |
//+------------------------------------------------------------------+
string COrder::StateDescription(void) const
  {
   if(this.Status()==ORDER_STATUS_DEAL || this.Status()==ORDER_STATUS_MARKET_ACTIVE)
      return "";                       
   else switch(this.StateOrder())
     {
      case ORDER_STATE_STARTED         :  return TextByLanguage("Ордер проверен на корректность, но еще не принят брокером","Order checked for correctness, but not yet accepted by broker");
      case ORDER_STATE_PLACED          :  return TextByLanguage("Ордер принят","Order accepted");
      case ORDER_STATE_CANCELED        :  return TextByLanguage("Ордер снят клиентом","Order withdrawn by client");
      case ORDER_STATE_PARTIAL         :  return TextByLanguage("Ордер выполнен частично","Order filled partially");
      case ORDER_STATE_FILLED          :  return TextByLanguage("Ордер выполнен полностью","Order filled");
      case ORDER_STATE_REJECTED        :  return TextByLanguage("Ордер отклонен","Order rejected");
      case ORDER_STATE_EXPIRED         :  return TextByLanguage("Ордер снят по истечении срока его действия","Order withdrawn upon expiration");
      case ORDER_STATE_REQUEST_ADD     :  return TextByLanguage("Ордер в состоянии регистрации (выставление в торговую систему)","Order in state of registration (placing in trading system)");
      case ORDER_STATE_REQUEST_MODIFY  :  return TextByLanguage("Ордер в состоянии модификации","Order in state of modification.");
      case ORDER_STATE_REQUEST_CANCEL  :  return TextByLanguage("Ордер в состоянии удаления","Order in deletion state");
      default                          :  return TextByLanguage("Неизвестное состояние","Unknown state");
     }
  }
//+------------------------------------------------------------------+

Si se trata de una transacción o posición, retornamos una línea vacía, de lo contrario, comprobamos el estado de la orden y retornamos su descripción.

Añadimos a la implementación del método GetPropertyDescription(ENUM_ORDER_PROP_INTEGER property) el retorno de la descripción del estado de la orden:

//+------------------------------------------------------------------+
//| Return description of an order's integer property                |
//+------------------------------------------------------------------+
string COrder::GetPropertyDescription(ENUM_ORDER_PROP_INTEGER property)
  {
   return
     (
   //--- General properties
      property==ORDER_PROP_MAGIC             ?  TextByLanguage("Магик","Magic number")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TICKET            ?  TextByLanguage("Тикет","Ticket")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          " #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TICKET_FROM       ?  TextByLanguage("Тикет родительского ордера","Ticket of parent order")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          " #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TICKET_TO         ?  TextByLanguage("Тикет наследуемого ордера","Inherited order ticket")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          " #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TIME_OPEN         ?  TextByLanguage("Время открытия","Open time")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS)
         )  :
      property==ORDER_PROP_TIME_CLOSE        ?  TextByLanguage("Время закрытия","Close time")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS)
         )  :
      property==ORDER_PROP_TIME_EXP          ?  TextByLanguage("Дата экспирации","Expiration date")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          (this.GetProperty(property)==0     ?  TextByLanguage(": Не задана",": Not set") :
          ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS))
         )  :
      property==ORDER_PROP_TYPE              ?  TextByLanguage("Тип","Type")+": "+this.TypeDescription()                   :
      property==ORDER_PROP_DIRECTION         ?  TextByLanguage("Тип по направлению","Type by direction")+": "+this.DirectionDescription() :
      
      property==ORDER_PROP_REASON            ?  TextByLanguage("Причина","Reason")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+this.GetReasonDescription(this.GetProperty(property))
         )  :
      property==ORDER_PROP_POSITION_ID       ?  TextByLanguage("Идентификатор позиции","Position ID")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_DEAL_ORDER        ?  TextByLanguage("Сделка на основании ордера","Deal by order")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_DEAL_ENTRY        ?  TextByLanguage("Направление сделки","Deal direction")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+this.GetEntryDescription(this.GetProperty(property))
         )  :
      property==ORDER_PROP_POSITION_BY_ID    ?  TextByLanguage("Идентификатор встречной позиции","Opposite position ID")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TIME_OPEN_MSC     ?  TextByLanguage("Время открытия в милисекундах","Open time in milliseconds")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(string)this.GetProperty(property)+" > "+TimeMSCtoString(this.GetProperty(property))
         )  :
      property==ORDER_PROP_TIME_CLOSE_MSC    ?  TextByLanguage("Время закрытия в милисекундах","Close time in milliseconds")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(string)this.GetProperty(property)+" > "+TimeMSCtoString(this.GetProperty(property))
         )  :
      property==ORDER_PROP_TIME_UPDATE       ?  TextByLanguage("Время изменения позиции","Position change time")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(this.GetProperty(property)!=0 ? ::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS) : "0")
         )  :
      property==ORDER_PROP_TIME_UPDATE_MSC   ?  TextByLanguage("Время изменения позиции в милисекундах","Position change time in milliseconds")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(this.GetProperty(property)!=0 ? (string)this.GetProperty(property)+" > "+TimeMSCtoString(this.GetProperty(property)) : "0")
         )  :
      property==ORDER_PROP_STATE             ?  TextByLanguage("Состояние","Statе")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": \""+this.StateDescription()+"\""
         )  :
   //--- Additional property
      property==ORDER_PROP_STATUS            ?  TextByLanguage("Статус","Status")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": \""+this.StatusDescription()+"\""
         )  :
      property==ORDER_PROP_PROFIT_PT         ?  TextByLanguage("Прибыль в пунктах","Profit in points")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_CLOSE_BY_SL       ?  TextByLanguage("Закрытие по StopLoss","Close by StopLoss")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(this.GetProperty(property) ? TextByLanguage("Да","Yes") : TextByLanguage("Нет","No"))
         )  :
      property==ORDER_PROP_CLOSE_BY_TP       ?  TextByLanguage("Закрытие по TakeProfit","Close by TakeProfit")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(this.GetProperty(property) ? TextByLanguage("Да","Yes") : TextByLanguage("Нет","No"))
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

Y con esto, hemos finalizado las correcciones.

Conviene destacar que la biblioteca se está creando de forma paralela a la escritura de los artículos, y además es una versión beta, por lo que es posible que aparezcan diversas correcciones, cambios y adiciones.

¿Qué es lo próximo?

En el próximo artículo, crearemos una clase para seleccionar y filtrar cómodamente las órdenes, transacciones y posiciones de acuerdo con cualquiera de los criterios soportados; asimismo, crearemos una colección de órdenes de mercado y posiciones.

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

Volver al contenido