English Русский 中文 Deutsch 日本語 Português
Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte III): Colección de órdenes y posiciones de mercado, búsqueda y filtrado

Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte III): Colección de órdenes y posiciones de mercado, búsqueda y filtrado

MetaTrader 5Ejemplos | 10 junio 2019, 10:26
866 0
Artyom Trishkin
Artyom Trishkin

Contenido

Organizando la búsqueda
El objeto básico Engine como base de la biblioteca
Objetos de órdenes y posiciones de mercado activas
Colección de órdenes y posiciones de mercado activas
Qué es lo próximo

En la primera parte del presente ciclo de artículos, comenzamos a crear una gran biblioteca multiplataforma cuyo objetivo es facilitar la creación de programas para las plataformas MetaTrader 5 y MetaTrader 4.
En la segunda parte, continuamos el desarrollo de la biblioteca e hicimos una colección de órdenes y transacciones históricas.


En esta parte, vamos a crear una clase para seleccionar y filtrar cómodamente órdenes, transacciones, y posiciones en las listas de colecciones; también crearemos un objeto básico de la biblioteca, llamado Engine, y añadiremos a la biblioteca una colección de órdenes y posiciones de mercado.

En estos momentos, ya se insinúa una cierta estructura de guardado de datos, a la que nos ceñiremos en lo sucesivo para crear colecciones de diferentes tipos de objetos:

Para guardar y gestionar las colecciones, así como para el intercambio de datos entre el programa y la biblioteca, se creará el objeto único Engine, que será el objeto básico de toda la biblioteca, y al que recurrirán para obtener datos los programas creados sobre la base de la biblioteca. Asimismo, en él se acumulará todo el proceso de automatización del funcionamiento de la biblioteca.

Organizando la búsqueda

Para usar los datos de las colecciones de la biblioteca de forma fácil y simple, vamos a crear una búsqueda, clasificación y entrega de datos según la solicitud que nos resulten cómodas. Para ello, crearemos una clase especial y la llamaremos CSelect.
Todas las solicitudes de datos pasarán por ella.

Bien, en la carpeta de la biblioteca Collections, creamos la nueva clase CSelect. No es necesario crear la clase básica. Una vez finalice el funcionamiento del Wizard MQL, en la carpeta Collections se creará el nuevo archivo Select.mqh:

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

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

Para realizar la búsqueda, necesitaremos establecer todos sus modos. Para ello, en el archivo Defines.mqh, creamos una enumeración que describa los modos de comparación de los objetos durante la búsqueda:

//+------------------------------------------------------------------+
//|                                                      Defines.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"
//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+
#define COUNTRY_LANG   ("Russian")              // Country language
#define DFUN           (__FUNCTION__+": ")      // "Function description"
#define END_TIME       (D'31.12.3000 23:59:59') // Final data for requesting account history data
//+------------------------------------------------------------------+
//| Search                                                           |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Search data                                                      |
//+------------------------------------------------------------------+
enum ENUM_COMPARER_TYPE
  {
   EQUAL,                                                   // Equal
   MORE,                                                    // More
   LESS,                                                    // Less
   NO_EQUAL,                                                // Not equal
   EQUAL_OR_MORE,                                           // Equal or more
   EQUAL_OR_LESS                                            // Equal or less
  };
//+------------------------------------------------------------------+

En el archivo de la clase CSelect, incluimos desde la biblioteca estándar la clase de lista de punteros dinámicos a los ejemplares de los objetos, la clase COrder, y junto con ella, la biblioteca de funciones de servicio DELib.mqh (y con ella, el archivo Defines.mqh). Asimismo, declaramos el objeto de almacenamiento especial, disponible para toda la biblioteca a nivel global. En él ubicaremos las copias de las listas creadas durante la clasificación. Si no fijamos al objeto de almacenamiento las listas creadas de nuevo, tendremos que elimnarlas obligatoriamente después de estas dejen de ser necesarias. Y esto supone un gasto de recursos innecesario para monitorear dónde, cuándo y en qué circunstancias hemos necesitado esta o aquella lista, lo cual puede provocar una pérdida de la cadena lógica y fugas de memoria debido a los objetos no eliminados. Así, si los fijamos a la lista, será el subsistema del terminal el que los monitoree, y eliminandolo siempre a tiempo tanto la propia lista de almacenamiento, como su contenido.

Para realizar la comparación, es necesario crear el método siguiente: declaramos un método estático de plantilla para comparar las dos magnitudes.
//+------------------------------------------------------------------+
//|                                                       Select.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>
#include "..\Objects\Order.mqh"
//+------------------------------------------------------------------+
//| Storage list                                                     |
//+------------------------------------------------------------------+
CArrayObj   ListStorage; // Storage object for storing sorted collection lists
//+------------------------------------------------------------------+
//| Class for sorting objects meeting the criterion                  |
//+------------------------------------------------------------------+
class CSelect
  {
private:
   //--- Method for comparing two values
   template<typename T>
   static bool       CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode);
public:

  };
//+------------------------------------------------------------------+

Implementación del método de comparación:

//+------------------------------------------------------------------+
//| Method of comparing two values                                   |
//+------------------------------------------------------------------+
template<typename T>
bool CSelect::CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode)
  {
   return
     (
      mode==EQUAL && value1==value2          ?  true  :
      mode==NO_EQUAL && value1!=value2       ?  true  :
      mode==MORE && value1>value2            ?  true  :
      mode==LESS && value1<value2            ?  true  :
      mode==EQUAL_OR_MORE && value1>=value2  ?  true  :
      mode==EQUAL_OR_LESS && value1<=value2  ?  true  :  false
     );
  }
//+------------------------------------------------------------------+

Al método se transmiten dos valores de un mismo tipo y modo, según el cual es necesario realizar la comparación.
A continuación, se realiza una comparación simple dependiendo del método de comparación (igual/diferente/mayor/menor/mayor o igual/menor o igual), y se retorna el resultado.

Ahora, vamos a crear varios métodos para realizar la búsqueda de la lista. En la sección pública de la clase CSelect, declaramos tres métodos estáticos para buscar órdenes según el criterio establecido:

//+------------------------------------------------------------------+
//| Class for sorting objects meeting the criterion                  |
//+------------------------------------------------------------------+
class CSelect
  {
private:
   //--- Two values comparison method
   template<typename T>
   static bool       CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode);
public:
   //--- Return the list of orders with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
  };
//+------------------------------------------------------------------+

Vamos a implementarlos de inmediato fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Return the list of orders having one of the integer              |
//| properties meeting a specified criterion                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   int total=list_source.Total();
   for(int i=0; i<total; i++)
     {
      COrder *order=list_source.At(i);
      if(!order.SupportProperty(property)) continue;
      long order_prop=order.GetProperty(property);
      if(CompareValues(order_prop,value,mode)) list.Add(order);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Return the list of orders having one of the real                 |
//| properties meeting a specified criterion                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   for(int i=0; i<list_source.Total(); i++)
     {
      COrder *order=list_source.At(i);
      if(!order.SupportProperty(property)) continue;
      double order_prop=order.GetProperty(property);
      if(CompareValues(order_prop,value,mode)) list.Add(order);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Return the list of orders having one of the string               |
//| properties meeting a specified criterion                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   for(int i=0; i<list_source.Total(); i++)
     {
      COrder *order=list_source.At(i);
      if(!order.SupportProperty(property)) continue;
      string order_prop=order.GetProperty(property);
      if(CompareValues(order_prop,value,mode)) list.Add(order);
     }
   return list;
  }
//+------------------------------------------------------------------+

Vamos a ver un ejemplo de búsqueda según criterios de línea:

  • Al método se le transmite un puntero a la lista de colección, la propiedad según la cual componemos una nueva lista que cumpla con el criterio de búsqueda y el modo de comparación.
  • A continuación, se comprueba la validez de la lista y se retorna NULL en el caso de que no sea válida.
  • Si se ha superado la comprobación, creamos un nuevo objeto de lista y establecemos para este la bandera de control manual de la memoria. si no hacemos esto, al eliminar este objeto de lista, se eliminarán también todos los punteros a los objetos de orden guardados en la colección, lo cual no se debe dar en ninguún caso. El lector podrá encontrar más información en la guía de ayuda sobre la lista dinámica de punteros, concretamente, sobre este método.
  • A continuación, añadimos la lista de almacenamiento nuevamente creada y comenzamos a clasifcar los objetos en un ciclo:
    • obtenemos una orden de la lista, si la orden no soporta la propiedad establecida en la búsqueda, se omite y se elige la siguiente.
    • Después, la propiedad de la orden  se compara con la propiedad transmitida para la comparación según el modo establecido (mayor/menor/igual, etcétera), y si cumple el criterio de comparación, esta orden se inscribe en la lista creada nuevamente.
    • Cuando el ciclo termina su funcionamiento, esta lista se retorna al programa que ha realizado al solicitud.

Vamos a añadir seis métodos más para la búsqueda y el retorno del índice de la orden con el valor máximo y mínimo de la propiedad establecida:

//+------------------------------------------------------------------+
//| Class for sorting objects meeting the criterion                  |
//+------------------------------------------------------------------+
class CSelect
  {
private:
   //--- Two values comparison method
   template<typename T>
   static bool       CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode);
public:
   //--- Return the list of orders with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Return the order index with the maximum value of the (1) integer, (2) real and (3) string properties
   static int        FindOrderMax(CArrayObj* list_source,ENUM_ORDER_PROP_INTEGER property);
   static int        FindOrderMax(CArrayObj* list_source,ENUM_ORDER_PROP_DOUBLE property); 
   static int        FindOrderMax(CArrayObj* list_source,ENUM_ORDER_PROP_STRING property); 
   //--- Return the order index with the minimum value of the (1) integer, (2) real and (3) string properties
   static int        FindOrderMin(CArrayObj* list_source,ENUM_ORDER_PROP_INTEGER property);
   static int        FindOrderMin(CArrayObj* list_source,ENUM_ORDER_PROP_DOUBLE property); 
   static int        FindOrderMin(CArrayObj* list_source,ENUM_ORDER_PROP_STRING property); 
  };
//+------------------------------------------------------------------+

y su implementación:

//+------------------------------------------------------------------+
//| Return the order index in the list                               |
//| with the maximum integer property value                          |
//+------------------------------------------------------------------+
int CSelect::FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   COrder *max_order=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      COrder *order=list_source.At(i);
      long order1_prop=order.GetProperty(property);
      max_order=list_source.At(index);
      long order2_prop=max_order.GetProperty(property);
      if(CompareValues(order1_prop,order2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Return the order index in the list                               |
//| with the maximum real property value                             |
//+------------------------------------------------------------------+
int CSelect::FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   COrder *max_order=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      COrder *order=list_source.At(i);
      double order1_prop=order.GetProperty(property);
      max_order=list_source.At(index);
      double order2_prop=max_order.GetProperty(property);
      if(CompareValues(order1_prop,order2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Return the order index in the list                               |
//| with the maximum string property value                           |
//+------------------------------------------------------------------+
int CSelect::FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   COrder *max_order=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      COrder *order=list_source.At(i);               
      string order1_prop=order.GetProperty(property);
      max_order=list_source.At(index);                   
      string order2_prop=max_order.GetProperty(property);
      if(CompareValues(order1_prop,order2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+

Vamos a ver un ejemplo de búsqueda del índice con valores de línea mínimos:

  • al método se le transmite un puntero a la lista de colección y la propiedad para la búsqueda de la orden con el valor máximo de esta propiedad.
  • A continuación, se comprueba la validez de la lista y se retorna WRONG_VALUE (-1) en caso de que no sea válida.
  • Declaramos el índice de la orden con el valor máximo, lo inicializamos con un cero y creamos un objeto de orden vacío en el que iremos registrando los valores para la comparación.
  • Después, iteramos en el ciclo por la lista de colección de la segunda orden:
    • Obtenemos el valor buscado de la orden con el índice del ciclo, obtenemos el valor buscado de la orden con el índice index, comparamos los dos valores obtenidos y, si el valor buscado de la primera orden (con el índice del ciclo) es superior al valor buscado de la segunda orden (con el índice index), asignamos a la variable index el valor del índice de la orden con el mayor valor.
    • Cuando finaliza el ciclo, la variable index tendrá el índice de la orden con el valor buscado máximo. Lo retornamos al programa que realizado la solicitud.

Los métodos que retornan el índice de la orden con el valor mínimo de la propiedad establecida están construidos de forma análoga:

//+------------------------------------------------------------------+
//| Return the order index in the list                               |
//| with the minimum integer property value                          |
//+------------------------------------------------------------------+
int CSelect::FindOrderMin(CArrayObj* list_source,ENUM_ORDER_PROP_INTEGER property)
  {
   int index=0;
   COrder* min_order=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++){
      COrder* order=list_source.At(i);
      long order1_prop=order.GetProperty(property);
      min_order=list_source.At(index);
      long order2_prop=min_order.GetProperty(property);
      if(CompareValues(order1_prop,order2_prop,LESS)) index=i;
      }
   return index;
  }
//+------------------------------------------------------------------+
//| Return the order index in the list                               |
//| with the minimum real property value                             |
//+------------------------------------------------------------------+
int CSelect::FindOrderMin(CArrayObj* list_source,ENUM_ORDER_PROP_DOUBLE property)
  {
   int index=0;
   COrder* min_order=NULL;
   int total=list_source.Total();
   if(total== 0) return WRONG_VALUE;
   for(int i=1; i<total; i++){
      COrder* order=list_source.At(i);
      double order1_prop=order.GetProperty(property);
      min_order=list_source.At(index);
      double order2_prop=min_order.GetProperty(property);
      if(CompareValues(order1_prop,order2_prop,LESS)) index=i;
      }
   return index;
  }
//+------------------------------------------------------------------+
//| Return the order index in the list                               |
//| with the minimum string property value                           |
//+------------------------------------------------------------------+
int CSelect::FindOrderMin(CArrayObj* list_source,ENUM_ORDER_PROP_STRING property)
  {
   int index=0;
   COrder* min_order=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++){
      COrder* order=list_source.At(i);
      string order1_prop=order.GetProperty(property);
      min_order=list_source.At(index);
      string order2_prop=min_order.GetProperty(property);
      if(CompareValues(order1_prop,order2_prop,LESS)) index=i;
      }
   return index;
  }
//+------------------------------------------------------------------+

Ahora podemos añadir a la clase de la colección de órdenes históricas la clasificación de la lista de la colección según el tiempo y los criterios establecidos.

Priemro, añadimos al archivo Defines.mqh las variantes de clasificación por tiempo:

//+------------------------------------------------------------------+
//|                                                      Defines.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"

//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+
#define COUNTRY_LANG   ("Russian")              // Country language
#define DFUN           (__FUNCTION__+": ")      // "Function description"
#define END_TIME       (D'31.12.3000 23:59:59') // Final data for requesting account history data
//+------------------------------------------------------------------+
//| Search                                                           |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Search and sorting data                                          |
//+------------------------------------------------------------------+
enum ENUM_COMPARER_TYPE
  {
   EQUAL,                                                   // Equal
   MORE,                                                    // More
   LESS,                                                    // Less
   NO_EQUAL,                                                // Not equal
   EQUAL_OR_MORE,                                           // Equal or more
   EQUAL_OR_LESS                                            // Equal or less
  };
//+------------------------------------------------------------------+
//| Possible options of selecting by time                            |
//+------------------------------------------------------------------+
enum ENUM_SELECT_BY_TIME
  {
   SELECT_BY_TIME_OPEN,                                     // By open time
   SELECT_BY_TIME_CLOSE,                                    // By close time
   SELECT_BY_TIME_OPEN_MSC,                                 // By open time in milliseconds
   SELECT_BY_TIME_CLOSE_MSC,                                // By close time in milliseconds
  };
//+------------------------------------------------------------------+

Debemos incluir en el archivo HistoryCollection.mqh la clase CSelect. Para ello, sustituimos la línea de inclusión de las funciones de servicio por la línea de inclusión del archivo de la clase CSelect:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\DELib.mqh"
#include "..\Objects\HistoryOrder.mqh"
#include "..\Objects\HistoryPending.mqh"
#include "..\Objects\HistoryDeal.mqh"
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "Select.mqh"
#include "..\Objects\HistoryOrder.mqh"
#include "..\Objects\HistoryPending.mqh"
#include "..\Objects\HistoryDeal.mqh"
//+------------------------------------------------------------------+

Ahora, en lugar del archivo con las funciones de servicio, hemos incluido el archivo de la clase CSelect. El asunto es que hemos incluido Order.mqh en el archivo Select.mqh, mientras que en Order.mqh ya hemos incluido el archivo de las funciones de servicio.

Declaramos en la sección pública de la clase CHistoryCollection el método de selección de órdenes de la colección según la hora seleccionada en un intervalo de fechas, mientras que a la sección privada añadimos el objeto de orden abstracta COrder, que consistirá en una orden de ejemplo para la búsqueda de valores:
//+------------------------------------------------------------------+
//| Collection of historical orders and deals                        |
//+------------------------------------------------------------------+
class CHistoryCollection
  {
private:
   CArrayObj         m_list_all_orders;      // List of all historical orders and deals
   COrder            m_order_instance;       // Order object to search by a property
   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;  }
   //--- Select orders from the collection with time from begin_time to end_time
   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0,
                                   const ENUM_SELECT_BY_TIME select_time_mode=SELECT_BY_TIME_CLOSE);
   //--- Constructor
                     CHistoryCollection();
   //--- Update the list of orders, fill data on the number of new ones and set the trading event flag
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

Antes de implementar el método de selección de órdenes de la colección en un intervalo de fechas, añadimos a la clase de orden abstracta COrder en el archivo Order.mqh los métodos para establecer las propiedades de tipo entero, real y string (en este caso, el método de adición de una propiedad de tipo entero será necesario para inscribir los parámetros en la orden de ejemplo de los datos temporales):

public:
   //--- Set (1) integer, (2) real and (3) string order properties
   void              SetProperty(ENUM_ORDER_PROP_INTEGER property,long value) { m_long_prop[property]=value;                     }
   void              SetProperty(ENUM_ORDER_PROP_DOUBLE property,long value)  { m_long_prop[property]=value                    }
   void              SetProperty(ENUM_ORDER_PROP_STRING property,long value)  { m_long_prop[property]=value                    }
   //--- Return (1) integer, (2) real and (3) string order properties from the properties array
   long              GetProperty(ENUM_ORDER_PROP_INTEGER property)      const { return m_long_prop[property];                    }
   double            GetProperty(ENUM_ORDER_PROP_DOUBLE property)       const { return m_double_prop[this.IndexProp(property)];  }
   string            GetProperty(ENUM_ORDER_PROP_STRING property)       const { return m_string_prop[this.IndexProp(property)];  }

   //--- Return the flag of the order supporting the property
   virtual bool      SupportProperty(ENUM_ORDER_PROP_INTEGER property)        { return true; }
   virtual bool      SupportProperty(ENUM_ORDER_PROP_DOUBLE property)         { return true; }
   virtual bool      SupportProperty(ENUM_ORDER_PROP_STRING property)         { return true; }

   //--- Compare COrder objects with one another by all possible properties
   virtual int       Compare(const CObject *node,const int mode=0) const;

//+------------------------------------------------------------------+

En el archivo HistoryCollection.mqh, añadimos la implementación del método de selección de órdenes de la colección en el intervalo de fechas:

//+------------------------------------------------------------------+
//| Select orders from the collection                                |
//| from begin_time to end_time                                      |
//+------------------------------------------------------------------+
CArrayObj *CHistoryCollection::GetListByTime(const datetime begin_time=0,const datetime end_time=0,
                                             const ENUM_SELECT_BY_TIME select_time_mode=SELECT_BY_TIME_CLOSE)
  {
   ENUM_ORDER_PROP_INTEGER property=
     (
      select_time_mode==SELECT_BY_TIME_CLOSE       ?  ORDER_PROP_TIME_CLOSE      : 
      select_time_mode==SELECT_BY_TIME_OPEN        ?  ORDER_PROP_TIME_OPEN       :
      select_time_mode==SELECT_BY_TIME_CLOSE_MSC   ?  ORDER_PROP_TIME_CLOSE_MSC  : 
      ORDER_PROP_TIME_OPEN_MSC
     );

   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);
   //---
   m_order_instance.SetProperty(property,begin);
   int index_begin=m_list_all_orders.SearchGreatOrEqual(&m_order_instance);
   if(index_begin==WRONG_VALUE)
      return list;
   m_order_instance.SetProperty(property,end);
   int index_end=m_list_all_orders.SearchLessOrEqual(&m_order_instance);
   if(index_end==WRONG_VALUE)
      return list;
   for(int i=index_begin; i<=index_end; i++)
      list.Add(m_list_all_orders.At(i));
   return list;
  }
//+------------------------------------------------------------------+

¿Qué obtenemos como resultado?:

  • se transmiten al método la hora de comienzo del intervalo de la historia necesaria, la hora de finalización del intervalo de la historia necesaria y el modo de selección de fechas del intervalo de la enumeración ENUM_SELECT_BY_TIME.
  • Establecemos la propiedad necesaria para la búsqueda y la comparación en las propiedades de las órdenes, en función de la hora conforme a la cual se ha clasificado la lista: si se ha clasificado por hora de apertura, la propiedad para la búsqueda también será la hora de apertura; si se ha clasificado por hora de cierre, en las órdenes se comparará su hora de cierre, etcétera.
  • A continuación, se crea una nueva lista, en la que se ubicarán las órdenes correspondientes al criterio del intervalo de fechas, y que se retornará más tarde al programa que ha realizado la llamada.
  • Acto seguido, se comprueban las fechas de inicio y finalización del intervalo.
  • Si se ha transmitido un cero como fecha de finalización del intervalo, se definirá para esta la fecha máxima en el futuro; después se comprueba la fecha de inicio del intervalo, y si es superior a la fecha de finalización del intervalo, la fecha más antigua se definirá como inicio del intervalo. En este caso, si establecemos la fecha de forma incorrecta, se proporcionará una lista desde el incio de la historia de la cuenta hasta la fecha de finalización del intervalo. Se considera inicio del intervalo la fecha más antigua, y finalización del intervalo, la fecha situada más cerca de la hora actual.
  • A continuación, a la lista nuevamente creada se le asigna la bandera de control manual de la memoria(se habló de ello más arriba) y la lista se fija al objeto de almacenamiento.
  • Después, en la orden de ejemplo se registra el valor de la fecha inicial para la búsqueda en la lista de colección y se realiza la búsqueda del índice de la orden desde la fecha inicial con la ayuda del método SearchGreatOrEqual(), heradado mediante la clase COrder de la clase padre CObject.
  • Si no se ha encontrado el índice, significa que no hay órdenes posteriores a la fecha definida: se retorna una lista vacía.
  • A continuación, deberemos hacer lo mismo para buscar la fecha de finalización: se registra en el ejemplo la fecha de finalización y se realiza la búsqueda del índice la orden con la fecha final con la ayuda del método SearchLessOrEqual(). Si el índice no se encuentra, significa que no hay órdenes con una fecha anterior a la buscada, por lo que se retorna una lista vacía.
  • A continuación, en el ciclo desde el índice de la orden con la fecha inicial hasta el índice de la orden con la fecha final se añaden a la lista todas las órdenes que se encuentran en este intervalo y se retorna la lista rellenada al programa que realizado la llamada.

Ahora, añadimos a la sección pública los métodos que retornan la lista de la propiedad de tipo entero, real o string elegida que cumpla con el criterio a comparar:

//+------------------------------------------------------------------+
//| Collection of historical orders and deals                        |
//+------------------------------------------------------------------+
class CHistoryCollection
  {
private:
   CArrayObj         m_list_all_orders;      // List of all historical orders and deals
   COrder            m_order_instance;       // Order object to search by a property
   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;  }
   //--- Select orders from the collection with time from begin_time to end_time
   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0,
                                   const ENUM_SELECT_BY_TIME select_time_mode=SELECT_BY_TIME_CLOSE);
   //--- Return the list by selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   CArrayObj*        GetList(ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); }
   CArrayObj*        GetList(ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); }
   CArrayObj*        GetList(ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); }
   //--- Constructor
                     CHistoryCollection();
   //--- Update the list of orders, fill data on the number of new ones and set the trading event flag
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

Se transmite al método la propiedad de orden buscada, el valor con el que se debe comparar el modo compración (mayor/menor/igual/diferente/mayor o igual/menor o igual). A continuación, con la ayuda de los métodos de la clase CSelect anteriormente descritos, se retorna una lista filtrada según las propiedades, valores y métodos de compración necesarios.

Vamos a poner a prueba la obtención de las listas necesarias con métodos diferentes.

Vamos a tomar el asesor de prueba de la segunda parte, TestDoEasyPart02.mq5, y guardarlo en la carpeta MQL5\Experts\TestDoEasy, en la nueva subcarpeta Part03 con el nombre TestDoEasyPart03_1.mq5. Después, añadimos a sus parámetros input la selección del inicio y la finalización del intervalo de fechas y cambiamos el código en el manejador OnInit(), donde solicitaremos la historia en el intervalo de fechas:

//+------------------------------------------------------------------+
//|                                           TestDoEasyPart03_1.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:
input datetime          InpTimeBegin   =  0;                // Start date of required range
input datetime          InpTimeEnd     =  END_TIME;         // End date of required range  
//--- global variables
CHistoryCollection history;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- update history
   history.Refresh();
//--- get collection list in the date range
   CArrayObj* list=history.GetListByTime(InpTimeBegin,InpTimeEnd,SELECT_BY_TIME_CLOSE);
   if(list==NULL)
     {
      Print("Could not get collection list");
      return INIT_FAILED;
     }
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the 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);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

Ahora, en lugar de la lista completa, obtenemos una lista seleccionada según el intervalo de fechas establecido con el método GetListByTime(). Compilamos e iniciamos el asesor con los ajustes por defecto. En el diario de registro se mostrarán todas las transacciones de la historia de la cuenta:

Pulsamos F7 e introducimos en los ajustes la fecha final del intervalo necesario. Lo hemos hecho de la forma siguiente: entramos en la cuenta, vimos cuándo se realizó la última recarga y la fecha de la siguiente transacción (la primera tras la recarga de la cuenta)

y elegimos un intervalo tal que la primera transacción no entre en él: del 2018.01.22 al 2018.02.01.
Como resultado, en el diario solo ha entrado una transacción, la recarga de saldo:

Ahora, guardamos el asesor TestDoEasyPart03_1.mq5 con el nombre TestDoEasyPart03_2mq5. Guardamos los parámetros de entrada y cambiamos el método de obtención de los datos sobre transacciones:

//+------------------------------------------------------------------+
//|                                           TestDoEasyPart03_2.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>
//--- global variables
CHistoryCollection history;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- update history
   history.Refresh();
//--- receive only deals from the collection list
   CArrayObj* list=history.GetList(ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
//--- sort the obtained list by balance operations
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,DEAL_TYPE_BALANCE,EQUAL);
   if(list==NULL)
     {
      Print("Could not get collection list");
      return INIT_FAILED;
     }
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the order from the list
      COrder* order=list.At(i);
      if(order==NULL) continue;
      order.Print();
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

En primer lugar, obtenemos la lista de todas las transacciones (destacamos de la lista el estado de las órdenes con el tipo de transacción); a continuación, filtramos la lista obtenida según el tipo de "operación de balance". En ambos casos, usamos el modo de comparación "Igual".
Como resultado, en el diario se muestra solo una transacción, la operación de balance "Ingreso de saldo":

En comparación con el ejemplo anterior, donde tuvimos que mirar en el terminal en la pestaña con la historia de la cuenta el intervalo de transacciones para mostrar la operación de balance, en este ejemplo la hemos obtenido de inmediato, filtrando la lista según los criterios necesarios.

Los otros métodos de obtención de información necesaria se basan en el mismo principio. Por ejemplo, para obtener el propio "recarga de saldo", podemos encontrar el índice de la transacción con el mayor beneficio, y ya de paso, el índice de la transacción con el menor beneficio.
Ejemplo en el asesor de prueba TestDoEasyPart03_3.mq5:

//+------------------------------------------------------------------+
//|                                           TestDoEasyPart03_3.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>
//--- global variables
CHistoryCollection history;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- update history
   history.Refresh();
//--- receive only deals from the collection list
   CArrayObj* list=history.GetList(ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
   if(list==NULL)
     {
      Print(TextByLanguage("Не удалось получить список","Could not get list"));
      return INIT_FAILED;
     }
//--- Get the index of the most profitable deal (first balance replenishment)
   int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT);
   if(index!=WRONG_VALUE)
     {
      //--- get the deal from the list by index
      COrder* order=list.At(index);
      if(order!=NULL)
         order.Print();
     }
   else
      Print(TextByLanguage("Не найден индекс ордера с максимальным значением профита","Order index with maximum profit value not found"));
//--- Get the index of the least profitable deal
   index=CSelect::FindOrderMin(list,ORDER_PROP_PROFIT);
   if(index!=WRONG_VALUE)
     {
      //--- get the deal from the list by index
      COrder* order=list.At(index);
      if(order!=NULL)
         order.Print();
     }
   else
      Print(TextByLanguage("Не найден индекс ордера с минимальным значением профита","Order index with minimum profit value not found"));
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

Cuando finalice su funcionamiento, en el diario se mostrarán dos transacciones: la que tiene el mayor beneficio (recarga de saldo) y la que tiene el menor benficio.


El objeto básico Engine como base de la biblioteca

Un programa personalizado que funcione conjuntamente con la biblioteca debe controlar los datos en la biblioteca y recibir datos de la misma. Para ello, resulta más cómodo que se trate de una clase única que se incluya en el programa, y en la que se acumulen todas las posibles acciones sobre la interrelación conjunta de la biblioteca y el programa. Y, naturalemente, es necesario que esta clase asuma todas las posibles acciones de servicio que funcionen en segundo plano y no requieran por parte del usuario ninguna acción laboriosa. Por eso, vamos a crear una clase básica que ejercerá como base de la biblioteca, a la que llamaremos Engine.

En la carpeta raíz de la biblioteca creamos una nueva clase CEngine, que usa como base la clase básica CObject, y añadimos a ella la clase de colección histórica:

//+------------------------------------------------------------------+
//|                                                       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"
//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
//--- Collection of historical orders and deals
   CHistoryCollection   m_history;
public:
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+
//| CEngine constructor                                              |
//+------------------------------------------------------------------+
CEngine::CEngine()
  {
  }
//+------------------------------------------------------------------+
//| CEngine destructor                                               |
//+------------------------------------------------------------------+
CEngine::~CEngine()
  {
  }
//+------------------------------------------------------------------+

Todas las acciones que hemos realizado con la colección de órdenes históricas en los asesores de prueba han tenido lugar en el manejador OnInit(), es decir, se han ejecutado solo una vez al inicializar el asesor, recompilarlo o cambiar sus parámetros. Esto ha sido suficiente para una comprobación rápida, pero no es tolerable en un programa de trabajo. Así que vamos a comenzar a poner orden.

En primer lugar, creamos el manejador OnTimer() en la sección pública de la clase, y, fuera del cuerpo de la clase, su compilación; en ella se actualizarán todas las colecciones:
//+------------------------------------------------------------------+
//|                                                       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"
//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
//--- Collection of historical orders and deals
   CHistoryCollection   m_history;
public:
//--- Timer
   void                 OnTimer(void);
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+
//| CEngine constructor                                              |
//+------------------------------------------------------------------+
CEngine::CEngine()
  {
  }
//+------------------------------------------------------------------+
//| CEngine destructor                                               |
//+------------------------------------------------------------------+
CEngine::~CEngine()
  {
  }
//+------------------------------------------------------------------+
//| CEngine timer                                                    |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
   
  }
//+------------------------------------------------------------------+

Vamos a crear una clase-contador de servicio (temporizador), ya que es totalmente posible que para un evento se requiera un retraso del temporizador, mientras que para otro sea necesario otro completamente distinto. Esta clase debe encargarse de contar el retraso necesario; para cada uno de los contadores del temporizador se declarará su propio ejemplar del contador.

En primer lugar, añadimos al archivo Defines.mqh las nuevas macrosustituciones, en las que escribimos la frecuencia del temporizador general de la biblioteca en milisegundos y la pausa del contador del temporizador de las colecciones en milisegundos, también añadimos el salto de incremento del contador del temporizador de las colecciones, así como el identificador del contador del temporizador de actualización de las órdenes y transacciones históricas (puede haber varios contadores en el proceso de desarrollo de la biblioteca, y cada uno de ellos debe tener su propio identificador)
//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+
#define COUNTRY_LANG             ("Russian")                // Country language
#define DFUN                     (__FUNCTION__+": ")        // "Function description"
#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
//+------------------------------------------------------------------+

En la carpeta raíz de la biblioteca, creamos la nueva carpeta Services, y en ella, la nueva clase CTimerCounter. Y trasladamos de inmediato a esta carpeta nuestro archivo de funciones de servicio DELib.mqh, esa ubicación será ideal para él.

A continuación, después de trasladar DELib.mqh a la nueva carpeta, en el archivo Order.mqh cambiamos la dirección del archivo de las funciones de servicio:

en lugar de la ruta

//+------------------------------------------------------------------+
//|                                                        Order.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 "..\DELib.mqh"
//+------------------------------------------------------------------+

escribimos la ruta:

//+------------------------------------------------------------------+
//|                                                        Order.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"
//+------------------------------------------------------------------+

Ahora, vamos a analizar la clase del contador del temporizador. La clase es sencilla, así que podemos estudiar directamente su lista y analizar su funcionamiento:

//+------------------------------------------------------------------+
//|                                                 TimerCounter.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 <Object.mqh>
#include "DELib.mqh"
//+------------------------------------------------------------------+
//| Timer counter class                                              |
//+------------------------------------------------------------------+
class CTimerCounter : public CObject
  {
private:  
   int               m_counter_id;   
   ulong             m_counter;      
   ulong             m_counter_step; 
   ulong             m_counter_pause;
public:
   //--- Return the waiting completion flag
   bool              IsTimeDone(void);
   //--- Set the counter parameters
   void              SetParams(const ulong step,const ulong pause)         { this.m_counter_step=step; this.m_counter_pause=pause;  }
   //--- Return th counter id
   virtual  int      Type(void)                                      const { return this.m_counter_id;                              }
   //--- Compare counter objects
   virtual int       Compare(const CObject *node,const int mode=0)   const;
   //--- Constructor
                     CTimerCounter(const int id);
  };
//+------------------------------------------------------------------+
//| CTimerCounter constructor                                        |
//+------------------------------------------------------------------+
CTimerCounter::CTimerCounter(const int id) : m_counter(0),m_counter_step(16),m_counter_pause(16)
  {
   this.m_counter_id=id;
  }
//+------------------------------------------------------------------+
//| CTimerCounter returns the pause completion flag                  |
//+------------------------------------------------------------------+
bool CTimerCounter::IsTimeDone(void)
  {
   if(this.m_counter>=ULONG_MAX)
      this.m_counter=0;
   if(this.m_counter<this.m_counter_pause)
     {
      this.m_counter+=this.m_counter_step;
      return false;
     }
   this.m_counter=0;
   return true;
  }
//+------------------------------------------------------------------+
//| Compare CTimerCounter objects by id                              |
//+------------------------------------------------------------------+
int CTimerCounter::Compare(const CObject *node,const int mode=0) const
  {
   const CTimerCounter *counter_compared=node;
   int value_compared=counter_compared.Type();
   int value_current=this.Type();
   return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
   return 0;
  }
  
//+------------------------------------------------------------------+

Como hemos movido el archivo DELib.mqh a la misma carpeta en la que se ubica la clase-contador, también debemos incluirlo directamente en esta misma carpeta. Al archivo DELib.mqh está conectado Defines.mqh, así que la clase verá todas las macrosustituciones.

  • En la sección privada se declaran cuatro variables miembros de la clase: el identificador del temporizador, el contador del temporizador, el salto de incremento del temporizador y la pausa.
  • En la sección pública, se encuentra el método de definición de los parámetros del contador necesarios, al que se transmite el salto y la pausa del temporizador, donde sus valores son asignados de inmediato a las variables miembros de la clase.
  • En la lista de inicialización del constructor de la clase, se definen los parámetros por defecto del contador del temporizador (0), el salto del temporizador (16) y la pausa del temporizador (16). Estos parámetros de salto y pausa permiten funcionar al temporizador sin retrasos mientras espera que se alcance el valor de duración de la pausa.
  • En el cuerpo del constructor de la clase, asignamos al identificador del contador el valor transmitido por el parámetro de entrada.

La construcción del método transmitido por la bandera de finalización de la pausa es muy sencilla:

  • Primero se comprueba si se ha desbordado la variable-contador, y si el valor ha superado el máximo posible, el contador se pone a cero.
  • A continuación, se comparan los valores del contador del temporizador y la pausa, y si el valor del contador es menor que el valor de la pausa, al valor del contador se le añade el valor del salto de incremento y se retorna false.
  • Si el valor del contador ha superado o es igual al valor de la pausa, el contador se pone a cero y se retorna el evento de finalización del tiempo de espera del contador.

El método que retorna el identificador del contador Type() se ha hecho virtual. En la clase CObject existe un método virtual que retorna el tipo de objeto:

   //--- method of identifying the object
   virtual int       Type(void)                                    const { return(0);      }

Se supone que en las clases descendientes este método será redefinido y retornará el identificador del objeto descendiente de la clase CObject (para el propio CObject se retorna un tipo=0). Aquí usamos precisamente esta posibilidad y retornamos el identificador del contador redefiniendo este método virtual:

   virtual  int      Type(void)                                    const { return this.m_counter_id; }

El método virtual de comparación de dos objetos-contadores es sencillo:

//+------------------------------------------------------------------+
//| Compare CTimerCounter objects by id                              |
//+------------------------------------------------------------------+
int CTimerCounter::Compare(const CObject *node,const int mode=0) const
  {
   const CTimerCounter *counter_compared=node;
   int value_compared=counter_compared.Type();
   int value_current=this.Type();
   return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
   return 0;
  }
//+------------------------------------------------------------------+

Obtenemos el enlace al objeto-fuente, tomamos de la fuente su id y tomamos la id del contador actual; después se retorna el resultado de la comparación simple sobre mayor/menor/igual.


Seguimos rellenando la clase CEngine. Incluimos la clase de contador del temporizador en el archivo Engine.mqh:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Collections\HistoryCollection.mqh"
#include "Services\TimerCounter.mqh"
//+------------------------------------------------------------------+

Añadimos a la sección privada el objeto de lista de los contadores del temporizador, el método que retorna el índice del contador en la lista según su identificador; por otra parte, añadimos a la sección pública el método de creación del contador (para que el método esté disponible desde el exterior y sea posible crear en los programas contadores personalizados).

En el constructor de la clase, inicializamos el temporizador de milisegundos, ponemos la bandera de lista clasificada y creamos el contador del temporizador de actualización de la colección de órdenes y transacciones históricas. En el destructor de la clase, definimos la destrucción del temporizador:
//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
//--- List of timer counters
   CArrayObj            m_list_counters;           
//--- Collection of historical orders and deals
   CHistoryCollection   m_history;
//--- Return the counter index by id
   int                  CounterIndex(const int id) const;
public:
//--- Create the timer counter
   void                 CreateCounter(const int counter_id,const ulong frequency,const ulong pause);
//--- Timer
   void                 OnTimer(void);
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+
//| CEngine constructor                                              |
//+------------------------------------------------------------------+
CEngine::CEngine()
  {
   ::EventSetMillisecondTimer(TIMER_FREQUENCY);
   this.m_list_counters.Sort();
   this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_MIN_PAUSE);
  }
//+------------------------------------------------------------------+
//| CEngine destructor                                               |
//+------------------------------------------------------------------+
CEngine::~CEngine()
  {
   ::EventKillTimer();
  }
//+------------------------------------------------------------------+

Implementando el método que retorna el índice del contador según su identificador:

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

Como difícilmente habrá muchos contadores, hemos organizado una iteración sencillísima con búsqueda y comparación de valores. Si se ha localizado un contador con estos identificadores en la lista, se retorna su índice en la lista, en caso contrario, se retorna -1.

Vamos a analizar el método de creación del contador del temporizador:

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

En primer lugar, comprobamos el identificador del contador transmitido al método, y si esta id ya existe, mostramos un mensaje sobre ello en el diario y salimos del método: el contador con esa id ya ha sido creado.
Ya que la búsqueda puede realizarse solo en una lista clasificada, establecemos en la lista la bandera de clasficación, creamos un nuevo objeto-contador, comprobamos que su creación haya tenido éxito y establecemos las propiedades del contador necesarias.

A continuación, se realiza la búsqueda de exactamente ese mismo contador en la lista, y si no se encuentra allí, el nuevo contador se añade a la lista.

En caso contario, se forma un mensaje que incluya todos los parámetros y se muestra en el diario. Acto seguido, el objeto-contador es eliminado, ya que no es necesario: tenemos uno exactamente igual.

Debemos destacar que la última comprobación de la coincidencia de todos los parámetros del contador nuevamente creado con el ya existente ya no es necesaria en este momento, la comprobación de la id al inicio del método no nos deja crear un objeto con el mismo identificador que hay en la lista. La hemos dejado para posibles cambios futuros.

Para que la clase CEngine sepa cuándo es necesario procesar una situación comercial, debe tener conocimiento de los cambios sucedidos en el número de órdenes y transacciones en la historia. Para ello, vamos a añadir a la clase CHistoryCollection métodos que retornen el número de órdenes y transacciones nuevamente aparecidas:

//+------------------------------------------------------------------+
//| Collection of historical orders and deals                        |
//+------------------------------------------------------------------+
class CHistoryCollection
  {
private:
   CArrayObj         m_list_all_orders;      // List of all historical orders and deals
   COrder            m_order_instance;       // Order object to search by a property
   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:
   //--- Select orders from the collection with time from begin_time to end_time
   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0,
                                   const ENUM_SELECT_BY_TIME select_time_mode=SELECT_BY_TIME_CLOSE);
   //--- Return the full collection list 'as is'
   CArrayObj        *GetList(void)                                                                       { return &m_list_all_orders;                                            }
   //--- Return the list by selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   CArrayObj        *GetList(ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
   //--- Return the number of (1) new orders and (2) new deals
   int               NewOrders(void)                                                                     { return m_delta_order; }
   int               NewDeals(void                                                                     { return m_delta_deal;  }
   
   //--- Constructor
                     CHistoryCollection();
   //--- Update the list of orders, fill data on the number of new ones and set the trading event flag
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

Los métodos retornan los valores de las variables-miembros de clase correspondientes.

Ahora, ya podemos comprobar en CEngine su estado en el temporizador de la clase:

//+------------------------------------------------------------------+
//| CEngine timer                                                    |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
   //--- Timer of the historical orders and deals collection
   int index=this.CounterIndex(COLLECTION_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter.IsTimeDone())
        {
         this.m_history.Refresh();
         if(this.m_history.NewOrders()>0)
           {
            Print(DFUN,TextByLanguage("Изменилось количество исторических ордеров: NewOrders=","Number of historical orders changed: NewOrders="),this.m_history.NewOrders());
           }
         if(this.m_history.NewDeals()>0)
           {
            Print(DFUN,TextByLanguage("Изменилось количество сделок: NewDeals=","Number of deals changed: NewDeals="),this.m_history.NewOrders());
           }
        }
     }
  }
//+------------------------------------------------------------------+

Obtenemos el índice del contador del temporizador de la colección de órdenes y transacciones históricas, obtenemos el puntero al contador del temporizador según su índice, comprobamos el evento de finalización del tiempo de retraso del temporizador y actualizamos la colección (solo la última orden o transacción añadida, si las ha habido).

Si ha cambiado el número de órdenes históricas, mostramos en el diario un mensaje sobre ello, y hacemos exactamente lo mismo con las transacciones.

Para realizar la comprobación, crearemos un asesor sencillo. En la carpeta del terminal Experts\TestDoEasy\Part3, creamos un asesor con el nombre TestDoEasyPart03_4.mq5 con temporizador. Para crear la plantilla del asesor con temporizador, en la segunda página del Wizard MQL, establecemos la bandera en la casilla de verificación del temporizador:

Pulsamos "Siguiente" hasta que termine el funcionamiento del Wizard. Como resultado, hemos creado una plantilla de asesor vacía, en la que incluiremos el archivo principal de la biblioteca; crearemos el objeto de clase de la biblioteca y en el temporizador del asesor llamaremos el temporizador de la biblioteca.

//+------------------------------------------------------------------+
//|                                           TestDoEasyPart03_4.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>
//--- global variables
CEngine        engine;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   engine.OnTimer();
  }
//+------------------------------------------------------------------+

Eso es todo lo que necesitamos hacer en el asesor para obtener los datos sobre los cambios en la historia de la cuenta.

Si ahora iniciamos el asesor y
colocamos una orden pendiente, y después la eliminamos, en el diario aparecerá una entrada sobre el cambio en el número de órdenes históricas.
Si abrimos una posición, en el diario aparecerán dos entradas:

  1. sobre el cambio en el número de órdenes (se ha colocado una orden de mercado de apertura) y
  2. sobre el cambio en el número de transacciones (la orden de mercado se ha activado y ha generado la transacción "Entrar en el mercado").

Ahora, si cerramos una posición, en el diario aparecerán dos entradas:

  1. sobre la aparición de una orden de mercado de cierre y
  2. sobre la aparición de una nueva transacción (la orden de mercado de cierre se ha activado y ha generado la transacción "salir del mercado")

Con el primer inicio, en el diaro se mostrarán mensajes sobre los cambios de las órdenes y transacciones en la historia de la cuenta. Esto sucede porque, en el primer inicio, la biblioteca lee toda la historia al completo, por eso, la diferencia entre el valor cero del número de órdenes y transacciones (ya que la biblioteca en el primer inicio no sabe nada sobre ellas) y el valor calculado del número de todos las órdenes y transacciones durante el ciclo completo de la historia de la cuenta será igual a su cantidad total. Esto es muy incómodo, además de innecesario. Esto significa que merece la pena deshacerse de los mensajes falsos sobre el cambio en la cantidad. Podemos hacerlo de dos maneras:

  1. no mostrar nada en absoluto en el diario en el primer inicio
  2. mostrar los mensajes sobre el estado de la cuenta en el primer inicio
Puesto que la selección y la muestra de las estadísticas necesarias serán implementadas en los siguientes apartados de la descripción, utilizaremos la primera opción, "no mostrar nada en absoluto en el diario en el primer inicio", e implementaremos un "obturador" para el primer inicio:

En la sección privada de la clase CEngine, añadimos la bandera de primer inicio y un método para comprobar y resetear esta bandera:

//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // Collection of historical orders and deals
   CArrayObj            m_list_counters;                 // List of timer counters
   bool                 m_first_start;                   // First launch flag
//--- Return the counter index by id
   int                  CounterIndex(const int id) const;
//--- Return the first launch flag
   bool                 IsFirstStart(void);
public:
//--- Create the timer counter
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- Timer
   void                 OnTimer(void);
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+

e implementamos de inmediato fuera del cuerpo de la clase el método de comprobación y reseteo del primer inicio:

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

Aquí todo es muy sencillo: si la bandera se ha establecido, la reseteamos y retornamos true, de lo contrario, retornamos false.
Ahora, en el constructor de la clase, debemos definir la bandera en la lista de inicialización, para que siempre esté lista para actuar en el primer inicio.

//+------------------------------------------------------------------+
//| CEngine constructor                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true)
  {
   ::EventSetMillisecondTimer(TIMER_FREQUENCY);
   this.m_list_counters.Sort();
   this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE);
  }
//+------------------------------------------------------------------+

Ya está. Ahora, al darse el primer inicio del programa, no aparecerá el innecesario evento relacionado con el primer cálculo de la historia completa en la historia de la cuenta.

Podemos comprobarlo iniciando el asesor de prueba desde la ubicación: MQL5\Experts\TestDoEasy\Part2\TestDoEasyPart03_4.mq5 y asegurándonos de que en el primer inicio, en el diario de expertos no se mostrarán mensajes sobre la adición de órdenes y transacciones a la historia de la cuenta.

Objetos de órdenes y posiciones de mercado activas

En esta etapa, merece la pena que nos detengamos temporalmente en la adición de la funcionalidad a la clase CEngine y la implementación de la segunda mitad del trabajo con la cuenta: los objetos y colecciones de órdenes y posiciones de mercado. Y, ya después de crearlos, podremos pasar a la funcionalidad del objeto básico Engine, ya que dicha funcionalidad tocará tanto la historia de la cuenta como su estado actual.

En la carpeta de la biblioteca Objects, creamos la nueva clase CMarketPosition, basada en la orden abstracta de la biblioteca COrder: este será objeto de posición de mercado:

Después de pulsar el botón "Listo", se creará una plantilla de clase con el nombre MarketPosition.mqh, a la que nosotros añadiremos de inmediato la inclusión de la clase COrder:

//+------------------------------------------------------------------+
//|                                               MarketPosition.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"
//+------------------------------------------------------------------+
//| Market position                                                  |
//+------------------------------------------------------------------+
class CMarketPosition : public COrder
  {
private:

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

Modificamos el constructor de tal forma que se transmita a este el ticket de la posición; en su lista de inicialización definimos para la clase padre (COrder) el estado "posición de mercado" y enviamos el ticket a él. Declaramos tres métodos virtuales que retornan la bandera de soporte de propiedades de tipo entero, real y string por parte de la posición:

//+------------------------------------------------------------------+
//| Market position                                                  |
//+------------------------------------------------------------------+
class CMarketPosition : public COrder
  {
public:
   //--- Constructor
                     CMarketPosition(const ulong ticket=0) : COrder(ORDER_STATUS_MARKET_POSITION,ticket) {}
   //--- Supported position properties (1) real, (2) integer
   virtual bool      SupportProperty(ENUM_ORDER_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_ORDER_PROP_DOUBLE property);
   virtual bool      SupportProperty(ENUM_ORDER_PROP_STRING property);
  };
//+------------------------------------------------------------------+

e implementamos estos métodos fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Return 'true' if the position supports the passed                |
//| integer property, otherwise return 'false'                       |
//+------------------------------------------------------------------+
bool CMarketPosition::SupportProperty(ENUM_ORDER_PROP_INTEGER property)
  {
   if(property==ORDER_PROP_TIME_CLOSE     || 
      property==ORDER_PROP_TIME_CLOSE_MSC ||
      property==ORDER_PROP_TIME_EXP       ||
      property==ORDER_PROP_POSITION_BY_ID ||
      property==ORDER_PROP_DEAL_ORDER     ||
      property==ORDER_PROP_DEAL_ENTRY     ||
      property==ORDER_PROP_CLOSE_BY_SL    ||
      property==ORDER_PROP_CLOSE_BY_TP
     #ifdef __MQL5__                      ||
      property==ORDER_PROP_TICKET_FROM    ||
      property==ORDER_PROP_TICKET_TO
     #endif 
     ) return false;
   return true;
}
//+------------------------------------------------------------------+
//| Return 'true' if the position supports the passed                |
//| real property, otherwise return 'false'                          |
//+------------------------------------------------------------------+
bool CMarketPosition::SupportProperty(ENUM_ORDER_PROP_DOUBLE property)
  {
   if(property==ORDER_PROP_PRICE_CLOSE || property==ORDER_PROP_PRICE_STOP_LIMIT) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Return 'true' if the position supports the passed                |
//| string property, otherwise return 'false'                        |
//+------------------------------------------------------------------+
bool CMarketPosition::SupportProperty(ENUM_ORDER_PROP_STRING property)
  {
   if(property==ORDER_PROP_EXT_ID) return false;
   return true;
  }
//+------------------------------------------------------------------+

Aquí todo tiene lugar de forma análoga a la creación de los objetos de las órdenes y transacciones históricas, cuyo proceso de creación se ha descrito en la segunda parte de la descripción de la biblioteca.

Ahora, creamos de manera análoga el objeto de orden de mercado pendiente. Creamos la nueva clase CMarketPending - basada en la orden abstracta de la biblioteca COrder - en la carpeta Objects, y añadimos a la clase los cambios de la plantilla de clase creada por el Wizard MQL, que ya conocemos:

//+------------------------------------------------------------------+
//|                                                MarketPending.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"
//+------------------------------------------------------------------+
//| Market pending order                                             |
//+------------------------------------------------------------------+
class CMarketPending : public COrder
  {
public:
   //--- Constructor
                     CMarketPending(const ulong ticket=0) : COrder(ORDER_STATUS_MARKET_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                   |
//| integer property, otherwise, return 'false'                      |
//+------------------------------------------------------------------+
bool CMarketPending::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_CLOSE        ||
      property==ORDER_PROP_TIME_CLOSE_MSC    ||
      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                   |
//| real property, otherwise, return 'false'                         |
//+------------------------------------------------------------------+
bool CMarketPending::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;
  }
//+------------------------------------------------------------------+

Aquí, transmitimos el estado "orden pendiente" en el constructor de la clase, en su lista de inicialización en la COrder básica.

Con ello, ya hemos finalizado la creación de los objetos que necesitamos para crear la colección de órdenes y posiciones de mercado.

Colección de órdenes y posiciones de mercado activas

Al crear una colección de órdenes y transacciones históricas, hemos respetado una norma: no tiene sentido comprobar constantemente toda la historia. Hemos añadido a una lista creada una sola vez las órdenes y transacciones solo cuando cambiaba su número. Al usar la lista de posiciones de mercado, debemos tener en cuenta otra regla totalmente distinta: la lista debe ser actual en cada tick.

Para ello:

  1. debemos monitorear el cambio del número de órdenes pendientes, el cambio del número de posiciones activas para las cuentas de cobertura (ya que en las cuentas de compensación solo hay una posición), el cambio en el volumen de la posición (si ha aumentado o disminuido en la posición de compensación, o el cierre parcial de una de las posiciones de cobertura),
  2. debemos actualizar obligatoriamente en cada tick los datos de cada una de las posiciones de cobertura o de la posición de compensación existentes, para tener siempre datos actuales sobre es estado de las posiciones.

Por consiguiente, aquí deberemos recordar las magnitudes indicadas en el tick anterior, para compararlas con el estado de estos mismos datos en el tcik actual, y, de haber cambios, actualizar o crear de nuevo la lista completa. Por suerte, la lista no es grande, y crearla de nuevo no supondrá mucho tiempo.

Bien, vamos a comenzar. En la carpeta de la biblioteca Collections, creamos la nueva clase CMarketCollection. Para ello, clicamos con el botón derecho en la carpeta Collection y elegimos "Nuevo archivo". En el Wizard MQL que se abrirá, seleccionamos "Nueva clase" y pulsamos "Continuar".

Introducimos el nombre de la clase CMarketCollection y pulsamos el botón "Listo"; se creará una plantilla de clase con el nombre MarketCollection.mqh:

//+------------------------------------------------------------------+
//|                                             MarketCollection.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"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CMarketCollection
  {
private:

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

Vamos a rellenarla.

En primer lugar, incluimos todas las clases que hemos preparado, y que serán necesarias para implementar la colección de órdenes y posiciones de mercado y realizar la selección y la búsqueda según esta:

//+------------------------------------------------------------------+
//|                                             MarketCollection.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>
#include "Select.mqh"
#include "..\Objects\MarketPending.mqh" 
#include "..\Objects\MarketPosition.mqh"
//+------------------------------------------------------------------+

En la sección privada de la clase, creamos una estructura en la que escribiremos las variables para guardar los valores de todas las magnitudes monitoreadas sobre las que hablamos anteriormente (número de órdenes, posiciones, etcétera), y creamos dos variables miembros de clase con el tipo de esta estructura para guardar los datos actuales y pasados:

//+------------------------------------------------------------------+
//|                                             MarketCollection.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>
#include "Select.mqh"
#include "..\Objects\MarketPending.mqh" 
#include "..\Objects\MarketPosition.mqh"
//+------------------------------------------------------------------+
//| Collection of historical orders and deals                        |
//+------------------------------------------------------------------+
class CMarketCollection
  {
private:
   struct MqlDataCollection
     {
      long           hash_sum_acc;           // Hash sum of all orders and positions on the account
      int            total_pending;          // Number of pending orders on the account
      int            total_positions;        // Number of positions on the account
      double         total_volumes;          // Total volume of orders and positions on the account
     };
   MqlDataCollection m_struct_curr_market;   // Current data on market orders and positions on the account
   MqlDataCollection m_struct_prev_market;   // Previous data on market orders and positions on the account
public:
                     CMarketCollection();
                    ~CMarketCollection();
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CMarketCollection::CMarketCollection()
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CMarketCollection::~CMarketCollection()
  {
  }
//+------------------------------------------------------------------+

Vamos a detenernos con mayor detalle en la suma hash en la la estructura.
Para que podamos determinar con exactitud los cambios en la cuenta, bastará con saber el número de órdenes y posiciones: una orden pendiente puede ser eliminada, y en este caso, cambiará el número total de órdenes y posiciones en la cuenta. Pero... una orden pendiente puede activarse y convertirse en posición. En este caso, la suma total de las órdenes y posiciones cambiará (para las cuentas de cobertura y MQL4): el número de posiciones ha aumentado, pero el número de órdenes ha disminuido, y como resultado, el número total no ha cambiado. Esto no nos vale.

Veamos el ticket. La adición/eliminación de una orden pendiente cambiará la suma total de los tickets en la cuenta, mientras que la activación de una orden pendiente no cambiará la cantidad total de tickets en la cuenta.

Echemos un vistazo al volumen total. da igual si hemos colocado o eliminado una orden: el volumen total en la cuenta ha cambiado; abramos, cerremos o cambiemos una posición, el volumen total cambiará. Podría valer, pero, una vez más, la activación de una orden pendiente no cambiará el volumen total.

Esto significa que analizaremos otra propiedad de la posición, su hora de cambio en milisegundos: la apertura de una nueva posición cambiará el tiempo total de cambio de la posición, el cierre parcial cambiará el tiempo de cambio de la posición, y la adición de volumen en una cuenta de compensación cambiará el tiempo total de cambio de la posición.

¿Y de todo esto, qué nos ayudará a determinar de forma unívoca que ha habido un cambio en la cuenta? El ticket + el tiempo de cambio de la posición. Vamos a comprobarlo:

  • Apertura de una posición — la suma de los tickets ha cambiado + la suma de tiempo de cambio de la posición ha cambiado hay cambio
  • Cierre de una posición — la suma de los tickets ha cambiado + la suma de tiempo de cambio de la posición ha cambiado hay cambio
  • Colocación de una orden pendiente — la suma de los tickets ha cambiado + la suma de tiempo de cambio de la posición no ha cambiado hay cambio
  • Eliminación de una orden pendiente — la suma de los tickets ha cambiado + la suma de tiempo de cambio de la posición no ha cambiado <s4>hay cambio</s4>
  • Activación de una orden pendiente — la suma de los tickets no ha cambiado + la suma de tiempo de cambio de la posición ha cambiado hay cambio
  • Cierre parcial de una posición — la suma de los tickets ha cambiado + la suma de tiempo de cambio de la posición ha cambiado hay cambio
  • Adición de volumen a la posición — la suma de los tickets no ha cambiado + la suma de tiempo de cambio de la posición ha cambiado hay cambio
De esta forma, para la suma hash usaremos el ticket + el tiempo de cambio de la posición en milisegundos.

En la sección privada de la clase, creamos una lista dinámica de punteros a los objetos, se tratará de una lista de colección de órdenes pendientes y posiciones de mercado. Asimismo, crearemos dos banderas: la bandera de evento comercial en la cuenta y la bandera sobre el cambio sucedido en el volumen de la posición, para que resulte más sencillo identificar el evento comercial en la clase CEngine. Y tres variables miembros de clase para guardar la magnitud del cambio del volumen, el número de nuevas posiciones y órdenes pendientes.
Declaramos en la sección pública el método para actualizar la lista de colección, y escribimos fuera del cuerpo de la clase la implementación del constructor de la clase:

//+------------------------------------------------------------------+
//| Collection of market orders and positions                        |
//+------------------------------------------------------------------+
class CMarketCollection
  {
private:
   struct MqlDataCollection
     {
      long           hash_sum_acc;           // Hash sum of all orders and positions on the account
      int            total_pending;          // Number of pending orders on the account
      int            total_positions;        // Number of positions on the account
      double         total_volumes;          // Total volume of orders and positions on the account
     };
   MqlDataCollection m_struct_curr_market;   // Current data on market orders and positions on the account
   MqlDataCollection m_struct_prev_market;   // Previous data on market orders and positions on the account
   CArrayObj         m_list_all_orders;      // List of pending orders and positions on the account
   bool              m_is_trade_event;       // Trading event flag
   bool              m_is_change_volume;     // Total volume change flag
   double            m_change_volume_value;  // Total volume change value
   int               m_new_positions;        // Number of new positions
   int               m_new_pendings;         // Number of new pending orders
public:
   //--- Constructor
                     CMarketCollection(void);
   //--- Update the list of pending orders and positions
   void              Refresh(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CMarketCollection::CMarketCollection(void) : m_is_trade_event(false),m_is_change_volume(false),m_change_volume_value(0)
  {
   m_list_all_orders.Sort(SORT_BY_ORDER_TIME_OPEN);
   ::ZeroMemory(this.m_struct_prev_market);
   this.m_struct_prev_market.hash_sum_acc=WRONG_VALUE;
  }
//+------------------------------------------------------------------+

En la lista de inicialización del constructor de la clase, redifinimos las banderas de evento comercial y cambio de volumen de la posición y reseteamos la magnitud de cambio de volumen.
En el cuerpo del constructor, establecemos la clasificación de la lista de órdenes y posiciones de mercado según la hora de apertura, reseteamos todas las variables de la estructura del estado pasado de la cuenta, excepto la suma hash anterior: a esta le asignamos el valor -1 (para identificar el primer inicio).

Añadimos a la sección privada de la clase el método de guardado de los datos actuales recopilados de la cuenta en la estructura de datos pasados, para la posterior comprobación de los cambios en el número de órdenes y posiciones en la cuenta. A la sección pública de la cuenta, añadimos los tres métodos que retornan el número de nuevas órdenes pendientes, el número de nuevas posiciones y el método que retorna la bandera de evento comercial sucedido en la cuenta:

//+------------------------------------------------------------------+
//| Collection of market orders and positions                        |
//+------------------------------------------------------------------+
class CMarketCollection
  {
private:
   struct MqlDataCollection
     {
      long           hash_sum_acc;           // Hash sum of all orders and positions on the account
      int            total_pending;          // Number of pending orders on the account
      int            total_positions;        // Number of positions on the account
      double         total_volumes;          // Total volume of orders and positions on the account
     };
   MqlDataCollection m_struct_curr_market;   // Current data on market orders and positions on the account
   MqlDataCollection m_struct_prev_market;   // Previous data on market orders and positions on the account
   CArrayObj         m_list_all_orders;      // List of pending orders and positions on the account
   bool              m_is_trade_event;       // Trading event flag
   bool              m_is_change_volume;     // Total volume change flag
   double            m_change_volume_value;  // Total volume change value
   int               m_new_positions;        // Number of new positions
   int               m_new_pendings;         // Number of new pending orders
   //--- Save the current values of the account data status as previous ones
   void              SavePrevValues(void)             { this.m_struct_prev_market=this.m_struct_curr_market;   }
public:
   //--- Return the number of (1) new pending orders, (2) new positions, (3) occurred trading event flag
   int               NewOrders(void)    const         { return this.m_new_pendings;                            }
   int               NewPosition(void)  const         { return this.m_new_positions;                           }
   bool              IsTradeEvent(void) const         { return this.m_is_trade_event;                          }
   //--- Constructor
                     CMarketCollection(void);
   //--- Update the list of pending orders and positions
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

Vamos a implementar el método de actualización del estado de mercado actual:

//+------------------------------------------------------------------+
//| Update the order list                                            |
//+------------------------------------------------------------------+
void CMarketCollection::Refresh(void)
  {
   ::ZeroMemory(this.m_struct_curr_market);
   this.m_is_trade_event=false;            
   this.m_is_change_volume=false;          
   this.m_new_pendings=0;                  
   this.m_new_positions=0;                 
   this.m_change_volume_value=0;           
   m_list_all_orders.Clear();              
#ifdef __MQL4__
   int total=::OrdersTotal();
   for(int i=0; i<total; i++)
     {
      if(!::OrderSelect(i,SELECT_BY_POS)) continue;
      long ticket=::OrderTicket();
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderType();
      if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL)
        {
         CMarketPosition *position=new CMarketPosition(ticket);
         if(position==NULL) continue;
         if(this.m_list_all_orders.InsertSort(position))
           {
            this.m_struct_market.hash_sum_acc+=ticket;
            this.m_struct_market.total_volumes+=::OrderLots();
            this.m_struct_market.total_positions++;
           }
         else
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить позицию в список","Failed to add position to list"));
            delete position;
           }
        }
      else
        {
         CMarketPending *order=new CMarketPending(ticket);
         if(order==NULL) continue;
         if(this.m_list_all_orders.InsertSort(order))
           {
            this.m_struct_market.hash_sum_acc+=ticket;
            this.m_struct_market.total_volumes+=::OrderLots();
            this.m_struct_market.total_pending++;
           }
         else
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
     }
//--- MQ5
#else    
//--- Positions
   int total_positions=::PositionsTotal();
   for(int i=0; i<total_positions; i++)
     {
      ulong ticket=::PositionGetTicket(i);
      if(ticket==0) continue;
      CMarketPosition *position=new CMarketPosition(ticket);
      if(position==NULL) continue;
      if(this.m_list_all_orders.InsertSort(position))
        {
         this.m_struct_curr_market.hash_sum_acc+=(long)::PositionGetInteger(POSITION_TIME_UPDATE_MSC);
         this.m_struct_curr_market.total_volumes+=::PositionGetDouble(POSITION_VOLUME);
         this.m_struct_curr_market.total_positions++;
        }
      else
        {
         ::Print(DFUN,TextByLanguage("Не удалось добавить позицию в список","Failed to add position to list"));
         delete position;
        }
     }
//--- Orders
   int total_orders=::OrdersTotal();
   for(int i=0; i<total_orders; i++)
     {
      ulong ticket=::OrderGetTicket(i);
      if(ticket==0) continue;
      CMarketPending *order=new CMarketPending(ticket);
      if(order==NULL) continue;
      if(this.m_list_all_orders.InsertSort(order))
        {
         this.m_struct_curr_market.hash_sum_acc+=(long)ticket;
         this.m_struct_curr_market.total_volumes+=::OrderGetDouble(ORDER_VOLUME_INITIAL);
         this.m_struct_curr_market.total_pending++;
        }
      else
        {
         ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
         delete order;
        }
     }
#endif 
//--- First launch
   if(this.m_struct_prev_market.hash_sum_acc==WRONG_VALUE)
     {
      this.SavePrevValues();                              
     }                                                    
//--- If the hash sum of all orders and positions changed
   if(this.m_struct_curr_market.hash_sum_acc!=this.m_struct_prev_market.hash_sum_acc)
     {
      this.m_new_pendings=this.m_struct_curr_market.total_pending-this.m_struct_prev_market.total_pending;
      this.m_new_positions=this.m_struct_curr_market.total_positions-this.m_struct_prev_market.total_positions;
      this.m_change_volume_value=::NormalizeDouble(this.m_struct_curr_market.total_volumes-this.m_struct_prev_market.total_volumes,4);
      this.m_is_change_volume=(this.m_change_volume_value!=0 ? true : false);
      this.m_is_trade_event=true;
      this.SavePrevValues();
     }
  }
//+------------------------------------------------------------------+

Antes de analizar el método, haremos una pequeña digresión: puesto que necesitamos constantemente datos actuales de todas las órdenes y posiciones de mercado, podemos hacer algo sencillo: limpiaremos la lista en cada tick y la rellenaremos con los datos del entorno de mercado. O podemos rellenar una vez la lista y cambiar solo aquellos datos que han podido variar. Si reflexionamos un poco, veremos que resulta más rápido cambiar solo los datos que han variado. Pero, vamos a necesitar para ello:

  1. iterar por la lista de órdenes de mercado y las posiciones del terminal, rellenar con ellas la lista de la biblioteca,
  2. iterar en cada tick por la lista de órdenes y posiciones de mercado del terminal, tomar los datos que han cambiado, buscar en la lista de la biblioteca las órdenes y posiciones con el mismo ticket y actualizar los datos existentes,
  3. si la orden ha sido eliminada o la posición ha sido cerrada, las eliminamos de la lista de la biblioteca.

Esto parece más laborioso que simplemente limpiar la lista de la biblioteca y rellenarla en un ciclo por las órdenes de mercado y las posiciones del terminal.

Partiendo de dichas ideas, vamos a hacer precisamente eso: limpiar la lista y rellenarla de nuevo. Claro que nada nos impide comprobar también el método de búsqueda y actualización de datos en la lista de la biblioteca ya disponible. Pero esto ya sería después, en el estadio de trabajo con la biblioteca y la depuración de su rendimiento: siguiendo el principio de partir de lo sencillo hacia lo complicado.

Ahora, vamos a ver cómo está construido el método de actualización de la lista de colección de las órdenes y posiciones de mercado.

En inicio del método, se resetea la estructura de datos de mercado actuales, se resetean las banderas de evento la magnitud de cambio del volumen, todas las variables sobre el número de órdenes y posiciones, y finalmente se limpia la lista de colección.

Después, se comrpueba la pertenencia a MQL4 o a MQL5.
Puesto que en esta etapa estamos haciendo el código en MQL5, vamos a analizar precisamente este:

En MQL5, a diferencia de MQL4, las órdenes y posiciones se guardan en listas diferentes.
Por eso, en primer lugar, obtenemos el número total de posiciones en la cuenta, después iteramos en el ciclo por todas las posiciones del terminal, elegimos el ticket de la próxima posición, creamos un objeto de posición y lo añadimos a la lista de colección de órdenes activas y posiciones de la biblioteca.

Exactamente de la misma forma añadimos todas las órdenes pendientes disponibles en la cuenta en este momento. Solo para las órdenes pendientes, obtenemos el número total de órdenes en la cuenta e iteramos en el ciclo por la lista de órdenes del terminal, obteniendo el ticket de la orden y añadiendo el objeto de orden a la lista de colección de órdenes activas y posiciones de la biblioteca.

Tras finalizar ambos ciclos, en la lista de colección de órdenes y posiciones activas de la biblioteca se contendrán los objetos de las órdenes y posiciones disponibles en la cuenta en este momento. A continuación, se comprueba la bandera del primer inicio (aquí actúa como bandera el valor "pasado" de la suma hash igual a -1) y, si se trata del primer inicio, los valores de todas las magnitudes "pasadas" se copian a la estrutura que guarda estos valores con la ayuda del método SavePrevValues(). Si se trata del primer inicio, el valor de la pasada suma hash se compara con el valor de la suma hash actual, calculado durante la iteración de los ciclos de recopilación de datos de la cuenta en la lista de colección, y si la pasada suma hash no es igual a la actual, significa que ha habido un cambio en la cuenta.

En este caso, se registra en la variable que guarda el nuevo número de órdenes en la cuenta la diferencia entre el número pasado de órdenes y el actual,
en la variable que guarda el nuevo número de posiciones en la cuenta, se registra la diferencia el número actual de posiciones y el pasado,
se guarda el valor en el que ha cambiado el volumen total de la cuenta,
se establece la bandera de cambio de volumen,
la bandera de evento comercial sucedido,
y finalmente, se registran los nuevos valores en la estructura de "nuevos" valores con la ayuda del método SavePrevValues(), para su posterior comprobación.

El método SavePrevValues() simplemente copia la estructura con los valores actuales en la estructura con los valores pasados.

Para comprobar el funcionamiento del método de actualización de la lista de órdenes y posiciones de mercado y su trabajo conjunto con el método de actualización de la lista de órdenes y transacciones históricas, usaremos el último asesor de prueba de la carpeta Part03, con el nombre TestDoEasyPart03_4.mq5:

//+------------------------------------------------------------------+
//|                                           TestDoEasyPart03_4.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>
//--- global variables
CEngine        engine;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   engine.OnTimer();
  }
//+------------------------------------------------------------------+

Para que podamos ver los cambios realizados en cuanto a la adición y el monitoreo de la colección de órdenes y posiciones de mercado, vamos a escribir en el manejador del evento "Temporizador" de la clase CEngine estas líneas:

//+------------------------------------------------------------------+
//| CEngine timer                                                    |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
   //--- Timer of the collections of historical orders and deals, as well as of market orders and positions
   int index=this.CounterIndex(COLLECTION_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter!=NULL && counter.IsTimeDone())
        {
         //--- Update the lists 
         this.m_market.Refresh(); 
         this.m_history.Refresh();
         //--- First launch actions
         if(this.IsFirstStart())
           {
            return;
           }
         //--- Check the market status change
         if(this.m_market.IsTradeEvent())
           {
            Print(DFUN,TextByLanguage("Новое торговое событие на счёте","New trading event on account"));
           }
         //--- Check the account history change
         if(this.m_history.IsTradeEvent())
           {
            Print(DFUN,TextByLanguage("Новое торговое событие в истории счёта","New trading event in account history"));
           }
        }
     }
  }
//+------------------------------------------------------------------+

Aquí todo es muy sencillo. En primer lugar, se comprueba la finalización de la espera en el contador del temporizador de la colección, y si la pausa ha finalizado, se actualizan las listas de órdenes y posiciones de mercado y de órdenes y transacciones históricas. En el primer inicio no se requiere ninguna acción por nuestra parte. A continuación, al obtener la bandera de evento sucedido en la cuenta, solo tenemos que mostrar en el diario un mensaje sobre ello, y hacer exactamente lo mismo al obtener la bandera de evento en la historia de la cuenta: mostrar un mensaje sobre ello en el diario.

Compilamos el asesor de prueba y lo iniciamos. Ahora, si abrimos una posición, en el diario se mostrarán dos entradas:

2019.02.28 17:36:24.678 CEngine::OnTimer: New trading event on the account
2019.02.28 17:36:24.678 CEngine::OnTimer: New trading event in the account history

La primera de ellas, nos indica que ha sucedido un evento comercial en la cuenta; la segunda, que se ha añadido un nuevo evento a la historia de la cuenta. En este caso, el evento comercial en la cuenta es el aumento del número de posiciones en 1, mientras que el nuevo evento en la historia de la cuenta es la aparición de una nueva orden de mercado de apertura y una nueva transacción de "entrada en el mercado".

Si cerramos ahora la posición, en el diario aparecerán las mismas dos entradas, pero en esta ocasión darán una información distinta: el evento comercial en la cuenta es la reducción del número de posiciones en 1, y el nuevo evento en la historia de la cuenta es la adición de una orden de mercado de cierre y una nueva transacción de "salida del mercado".

¿Qué es lo próximo?

En el siguiente artículo, continuaremos con el desarrollo del elemento principal de la biblioteca CEngine, e implementaremos el procesamiento de los eventos entrantes de la colección y el envío de estos al programa.

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


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

Archivos adjuntos |
MQL5.zip (36.13 KB)
Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte IV): Eventos comerciales Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte IV): Eventos comerciales
En los anteriores artículos, comenzamos a crear una gran biblioteca multiplataforma, cuyo objetivo es facilitar la escritura de programas para las plataformas MetaTrader 5 y MetaTrader 4. Ya disponemos de una colección de órdenes y transacciones históricas, y órdenes y posiciones de mercado, así como de una clase para seleccionar y filtrar órdenes cómodamente. En esta parte, vamos a continuar desarrollando el objeto básico, además de enseñar a la biblioteca Engine a monitorear los eventos comerciales en la cuenta.
Integración de MetaTrader 5 y Python: recibiendo y enviando datos Integración de MetaTrader 5 y Python: recibiendo y enviando datos
En nuestra época, el procesamiento de datos requiere un extenso instrumental y muchas veces no se limita al entorno protegido (sandbox) de alguna determinada aplicación. Existen los lenguajes de programación especializados y universalmente reconocidos para procesar y analizar los datos, para la estadística y el aprendizaje automático. Python es el líder en este campo. En este artículo, se describe un ejemplo de la integración de MetaTrader 5 y Python a través de los sockets, así como, la obtención de las cotizaciones por medio de la API del terminal.
Indicadores MTF como herramienta de análisis técnico Indicadores MTF como herramienta de análisis técnico
La mayoría de nosotros estamos de acuerdo en que el proceso de análisis de la situación actual de mercado comienza por el estudio de los marcos temporales mayores del gráfico. Esto sucede hasta que pasemos al gráfico en el que realizamos las transacciones. Esta opción de análisis es una de las condiciones del comercio exitoso, indispensable para lograr un enfoque profesional de dicha tarea. En este artículo, vamos a hablar sobre los indicadores de marco temporal múltiple y los métodos de creación de los mismos. Mostraremos ejemplos de código en MQL5, realizaremos una valoración de los puntos fuertes y débiles de cada versión, y también ofreceremos un nuevo enfoque respecto a los indicadores que usan el modo MTF.
Extrayendo datos estructurados de las páginas HTML usando los selectores CSS Extrayendo datos estructurados de las páginas HTML usando los selectores CSS
En este artículo, se describe un método universal para analizar y convertir los datos de documentos HTML basados en los selectores CSS. Ahora, en MQL tenemos disponibles los informes comerciales y del Simulador de Estrategias, los calendarios económicos preferibles, señales públicas y monitoreo de cuentas, fuentes adicionales de las cotizaciones en línea.