Biblioteca para desenvolvimento fácil e rápido de programas para a MetaTrader (parte III). Coleção de ordens e posições de mercado, busca e ordenação

Artyom Trishkin | 15 maio, 2019

Conteúdo

Organizando a busca
O objeto base Engine como o núcleo da biblioteca
Objetos de ordens e posições de mercado ativas
Coleção de ordens e posições de mercado ativas
Qual é o próximo

Na primeira parte da série de artigos, nós começamos a criar uma grande biblioteca multi-plataforma simplificando o desenvolvimento de programas para as plataformas MetaTrader 5 e MetaTrader 4.
Na segunda parte, nós retomamos o desenvolvimento da biblioteca e implementamos a coleção do histórico de ordens e negócios.


Aqui, nós vamos criar uma classe para uma seleção e ordenação de maneira conveniente das ordens, negócios e posições em forma de listas de coleções, implementar o objeto base da biblioteca chamado Engine e adicionar uma coleção de ordens e posições de mercado para a biblioteca.

No momento, uma certa estrutura de armazenamento de dados já está surgindo. Nós vamos aderir a ela ao criar as coleções de vários tipos de objetos:


Um único objeto Engine será criado para armazenar e gerenciar as coleções, bem como para a troca de dados entre o programa e a biblioteca. O engine deve se tornar o objeto base de toda a biblioteca. Os programas baseados na biblioteca se referem a ele para a obtenção dos dados. Além disso, ele server para acumular toda a automação da biblioteca.

Organizando a busca

Para o uso fácil e conveniente de dados das coleções da biblioteca, nós vamos implementar de maneira adequada a busca de dados, ordenação e exibição mediante solicitação. Para conseguir isso, vamos criar uma classe especial e nomeá-la de CSelect.
Todas as solicitações de dados devem passar por ela.

Na pasta da biblioteca Collections, crie a nova classe CSelect. Não há necessidade para definir a classe base. Após a conclusão da operação do Assistente MQL, o novo arquivo Select.mqh é gerado na pasta Collections:

//+------------------------------------------------------------------+
//|                                                       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"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CSelect
  {
private:

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

Para realizar a busca, defina todos os seus modos. Para fazer isso, crie a enumeração descrevendo os modos de comparação dos objetos durante a busca. A enumeração deve ser criada no arquivo Defines.mqh:

//+------------------------------------------------------------------+
//|                                                      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"
//+------------------------------------------------------------------+
//| Substituições de macro                                           |
//+------------------------------------------------------------------+
#define COUNTRY_LANG   ("Russian")              // Idioma do país
#define DFUN           (__FUNCTION__+": ")      // "Descrição da função"
#define END_TIME       (D'31.12.3000 23:59:59') // Hora de término para solicitar os dados do histórico da conta
//+------------------------------------------------------------------+
//| Busca                                                            |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Busca os dados                                                   |
//+------------------------------------------------------------------+
enum ENUM_COMPARER_TYPE
  {
   EQUAL,                                                   // Igual
   MORE,                                                    // Maior
   LESS,                                                    // Menor
   NO_EQUAL,                                                // Diferente
   EQUAL_OR_MORE,                                           // Maior ou igual
   EQUAL_OR_LESS                                            // Menor ou igual
  };
//+------------------------------------------------------------------+

No arquivo da classe CSelect, da biblioteca padrão, inclua a classe da lista de ponteiros dinâmicos para as instâncias de objetos, a classe COrder e a biblioteca de funções de serviço DELib.mqh (junto com o arquivo Defines.mqh). Além disso, declare um objeto de armazenamento especial disponível para toda a biblioteca à nível global. Ela serve para armazenar as cópias das listas criadas durante a ordenação. Se as listas recém-criadas não estiverem anexadas a um objeto de armazenamento, elas deverão ser excluídas após não haver mais necessidade delas. Isso significa que nós precisamos alocar recursos extras no monitoramento onde, quando e por que precisamos de uma determinada lista que pode causar uma perda de uma cadeia lógica e vazamentos de memória devido aos objetos não excluídos. Se nós anexarmos à lista, o monitoramento é realizado pelo subsistema do terminal, que sempre exclui a lista de armazenamento e seu conteúdo em tempo de execução.

Para realizarmos uma comparação, nós precisamos criar esse método. Vamos declarar um método estático do modelo para a comparação dos dois valores.
//+------------------------------------------------------------------+
//|                                                       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"
//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\Objects\Order.mqh"
//+------------------------------------------------------------------+
//| Lista de armazenamento                                           |
//+------------------------------------------------------------------+
CArrayObj   ListStorage; // Objeto de armazenamento para armazenar as listas de coleções ordenadas
//+------------------------------------------------------------------+
//| Classe para ordenar os objetos que atendem ao critério           |
//+------------------------------------------------------------------+
class CSelect
  {
private:
   //--- Método para a comparação de dois valores
   template<typename T>
   static bool       CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode);
public:

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

Implementação do método de comparação:

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

Dois valores do mesmo tipo e o modo da comparação são passados para o método.
Em seguida, é feita uma comparação simples, dependendo do método aplicado (igual/diferente/maior/menor/maior ou igual/menor ou igual) e o resultado é retornado.

Agora vamos criar vários métodos para a busca da lista. Na seção pública da classe CSelect, declare três métodos estáticos para a busca de uma ordem por um determinado critério:

//+------------------------------------------------------------------+
//| Classe para ordenar os objetos que atendem ao critério           |
//+------------------------------------------------------------------+
class CSelect
  {
private:
   //--- Método de comparação de dois valores
   template<typename T>
   static bool       CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode);
public:
   //--- Retorna a lista de ordens com uma das propriedades de (1) inteiro, (2) real e (3) string que atendem a um critério especificado
   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 implementá-los imediatamente fora do corpo da classe:

//+------------------------------------------------------------------+
//| Retorna a lista de ordens contendo as propriedades de um inteiro |
//| que atendam a um critério em específico                          |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Retorna a lista de ordens contendo as propriedades de um nº real |
//| que atendam a um critério em específico                          |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Retorna a lista de ordens contendo as propriedades de uma string |
//| que atendam a um critério em específico                          |
//+------------------------------------------------------------------+
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 dar uma olhada mais de perto usando a busca por critérios de string como exemplo:

Adicione outros seis métodos para busca e retorno do índice de ordens com o valor máximo e mínimo propriedade especificada:

//+------------------------------------------------------------------+
//| Classe para ordenar os objetos que atendem ao critério           |
//+------------------------------------------------------------------+
class CSelect
  {
private:
   //--- Método de comparação de dois valores
   template<typename T>
   static bool       CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode);
public:
   //--- Retorna a lista de ordens com uma das propriedades de (1) inteiro, (2) real e (3) string que atendem a um critério especificado
   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);
   //--- Retorna o índice da ordem com o valor máximo das propriedades de (1) inteiro, (2) real e (3) string
   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); 
   //--- Retorna o índice da ordem com o valor mínimo das propriedades de (1) inteiro, (2) real e (3) string
   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); 
  };
//+------------------------------------------------------------------+

e sua implementação:

//+------------------------------------------------------------------+
//| Retorna o índice da ordem na lista                               |
//| com o valor máximo da propriedade inteira                        |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Retorna o índice da ordem na lista                               |
//| com o valor máximo da propriedade real                           |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Retorna o índice da ordem na lista                               |
//| com o valor máximo da propriedade da string                      |
//+------------------------------------------------------------------+
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 dar uma olhada mais de perto usando a busca pelo índice de ordens com o valor máximo da string:

Os métodos que retornam o índice das ordens com o menor valor da propriedade especificada são organizados de maneira semelhante:

//+------------------------------------------------------------------+
//| Retorna o índice da ordem na lista                               |
//| com o valor mínimo da propriedade inteira                        |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Retorna o índice da ordem na lista                               |
//| com o valor mínimo da propriedade real                           |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Retorna o índice da ordem na lista                               |
//| com o valor mínimo da propriedade da string                      |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

Agora nós podemos adicionar a ordenação da lista de coleções por horário e critérios especificados à classe da coleção do histórico de ordens.

Primeiro, adicione as opções de ordenação por hora para o arquivo Defines.mqh:

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

//+------------------------------------------------------------------+
//| Substituições de macro                                           |
//+------------------------------------------------------------------+
#define COUNTRY_LANG   ("Russian")              // Idioma do país
#define DFUN           (__FUNCTION__+": ")      // "Descrição da função"
#define END_TIME       (D'31.12.3000 23:59:59') // Hora de término para solicitar os dados do histórico da conta
//+------------------------------------------------------------------+
//| Busca                                                            |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Busca e ordenação dos dados                                      |
//+------------------------------------------------------------------+
enum ENUM_COMPARER_TYPE
  {
   EQUAL,                                                   // Igual
   MORE,                                                    // Maior
   LESS,                                                    // Menor
   NO_EQUAL,                                                // Diferente
   EQUAL_OR_MORE,                                           // Maior ou igual
   EQUAL_OR_LESS                                            // Menor ou igual
  };
//+------------------------------------------------------------------+
//| Possíveis opções de seleção por horário                          |
//+------------------------------------------------------------------+
enum ENUM_SELECT_BY_TIME
  {
   SELECT_BY_TIME_OPEN,                                     // Pelo horário de abertura
   SELECT_BY_TIME_CLOSE,                                    // Pelo horário de fechamento
   SELECT_BY_TIME_OPEN_MSC,                                 // Pelo horário de abertura em milissegundos
   SELECT_BY_TIME_CLOSE_MSC,                                // Pelo horário de fechamento em milissegundos
  };
//+------------------------------------------------------------------+

Inclua a classe CSelect no arquivo HistoryCollection.mqh. Para fazer isso, substitua a linha de inclusão das funções de serviço com a inclusão do arquivo da classe CSelect 1:

//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\DELib.mqh"
#include "..\Objects\HistoryOrder.mqh"
#include "..\Objects\HistoryPending.mqh"
#include "..\Objects\HistoryDeal.mqh"
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "Select.mqh"
#include "..\Objects\HistoryOrder.mqh"
#include "..\Objects\HistoryPending.mqh"
#include "..\Objects\HistoryDeal.mqh"
//+------------------------------------------------------------------+

Agora, nós incluímos o arquivo da classe CSelect em vez do arquivo de funções de serviço. Nós incluímos o arquivo Order.mqh para Select.mqh, enquanto o arquivo de funções de serviço já está incluído no arquivo Order.mqh.

Na seção pública da classe CHistoryCollection, declare o método de seleção de ordens da coleção pelo horário especificado no intervalo de datas, enquanto na seção privada, adicione a classe abstrata COrder que serve como uma ordem de exemplo para a busca de valores:
//+------------------------------------------------------------------+
//| Coleção do histórico de ordens e negócios                        |
//+------------------------------------------------------------------+
class CHistoryCollection
  {
private:
   CArrayObj         m_list_all_orders;      // Lista de todo o histórico de ordens e negócios
   COrder            m_order_instance;       // Objeto de ordem para buscar por uma propriedade
   bool              m_is_trade_event;       // Flag do evento de negociação
   int               m_index_order;          // Índice da última ordem adicionada à coleção da lista do histórico do terminal (MQL4, MQL5)
   int               m_index_deal;           // Índice do último negócio adicionado à coleção da lista do histórico do terminal (MQL5)
   int               m_delta_order;          // Diferença no número de ordens em comparação com a verificação passada
   int               m_delta_deal;           // Diferença no número de negócios em comparação com a verificação passada
public:
   //--- Retorna a lista completa da coleção 'como está'
   CArrayObj        *GetList(void) { return &m_list_all_orders;  }
   //--- Seleciona as ordens da coleção com o horário de begin_time até 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);
   //--- Construtor
                     CHistoryCollection();
   //--- Atualiza a lista de ordens, preenche os dados sobre o número de novas ordens e configura a flag do evento de negociação
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

Antes de implementar o método de seleção de ordens da coleção dentro do intervalo de datas, adicione o métodos de colocação de propriedades inteiras, reais e de string para a classe abstrata COrder no arquivo Order.mqh (nesse caso, os métodos de escrita da propriedade inteira são necessários para adicionar os parâmetros de dados de horário da amostra):

public:
   //--- Define as propriedades de (1) inteiro, (2) real e (3) da ordem da string
   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                    }
   //--- Retorna as propriedades do tipo (1) inteiro, (2) real e (3) string do array de propriedades
   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)];  }

   //--- Retorna a flag da ordem que suporta a propriedade
   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; }

   //--- Compara os objetos da linha entre si por todas as propriedades possíveis
   virtual int       Compare(const CObject *node,const int mode=0) const;

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

No arquivo HistoryCollection.mqh, implemente o método de seleção de ordens da coleção dentro do intervalo de datas:

//+------------------------------------------------------------------+
//| Seleciona as ordens da coleção                                   |
//| de begin_time para 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;
  }
//+------------------------------------------------------------------+

Então, o que nós temos aqui?

Na seção pública, declare os métodos retornando a lista pelo método selecionado, as propriedades dos inteiros, reais e de strings que atendem ao critério comparado:

//+------------------------------------------------------------------+
//| Coleção do histórico de ordens e negócios                        |
//+------------------------------------------------------------------+
class CHistoryCollection
  {
private:
   CArrayObj         m_list_all_orders;      // Lista de todo o histórico de ordens e negócios
   COrder            m_order_instance;       // Objeto de ordem para buscar por uma propriedade
   bool              m_is_trade_event;       // Flag do evento de negociação
   int               m_index_order;          // Índice da última ordem adicionada à coleção da lista do histórico do terminal (MQL4, MQL5)
   int               m_index_deal;           // Índice do último negócio adicionado à coleção da lista do histórico do terminal (MQL5)
   int               m_delta_order;          // Diferença no número de ordens em comparação com a verificação passada
   int               m_delta_deal;           // Diferença no número de negócios em comparação com a verificação passada
public:
   //--- Retorna a lista completa da coleção 'como está'
   CArrayObj        *GetList(void) { return &m_list_all_orders;  }
   //--- Seleciona as ordens da coleção com o horário de begin_time até 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);
   //--- Retorna a lista pelas propriedades de (1) inteiro, (2) real e (3) string selecionadas que atendem ao critério comparado
   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); }
   //--- Construtor
                     CHistoryCollection();
   //--- Atualiza a lista de ordens, preenche os dados sobre o número de novas ordens e configura a flag do evento de negociação
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

O método recebe a propriedade da ordem alvo, o valor para comparar e a comparação do modo (maior/menor/igual/diferente/maior ou igual/ menor ou igual). A lista classificada pelas propriedades, valores e método de comparação necessários é retornada com a ajuda dos métodos da classe CSelect descritos anteriormente.

Vamos testar o recebimento das listas necessárias usando diferentes métodos.

Use o EA de teste TestDoEasyPart02.mq5 da segunda parte e salve-o na nova subpasta Part3 de MQL5\Experts\TestDoEasy sob o nome de TestDoEasyPart03_1.mq5. Adicione a seleção do início e fim do período aos seus parâmetros de entrada e altere o código no manipulador OnInit() onde vamos solicitar o histórico no período:

//+------------------------------------------------------------------+
//|                                           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,   // Ordens à mercado
   TYPE_ORDER_PENDING,  // Ordens pendentes
   TYPE_ORDER_DEAL      // Negócios
  };
//--- parâmetros de entrada
input ENUM_TYPE_ORDERS  InpOrderType   =  TYPE_ORDER_DEAL;  // Tipo da exibição:
input datetime          InpTimeBegin   =  0;                // Data de início do intervalo necessário
input datetime          InpTimeEnd     =  END_TIME;         // Data final do intervalo necessário  
//--- variáveis globais
CHistoryCollection history;
//+------------------------------------------------------------------+
//| Função de inicialização do Expert                                |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- atualiza o histórico
   history.Refresh();
//--- obtém a lista de coleção no período
   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++)
     {
      //--- obtém a ordem da lista
      COrder* order=list.At(i);
      if(order==NULL) continue;
      //--- se isso é um negócio
      if(order.Status()==ORDER_STATUS_DEAL && InpOrderType==TYPE_ORDER_DEAL)
         order.Print();
      //--- se esta for o histórico da ordem à mercado
      if(order.Status()==ORDER_STATUS_HISTORY_ORDER && InpOrderType==TYPE_ORDER_MARKET)
         order.Print();
      //--- se esta for uma ordem pendente removida
      if(order.Status()==ORDER_STATUS_HISTORY_PENDING && InpOrderType==TYPE_ORDER_PENDING)
         order.Print();
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Função de desinicialização do Expert                             |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Função Tick do Expert                                            |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

Agora, em vez da lista completa, nós obtemos a lista selecionada no intervalo de datas especificadas usando o método GetListByTime(). Compile e inicie o EA com as configurações padrão. Todas as ordens para todo o histórico da conta são exibidas no diário:


Pressione F7 e defina a data final do intervalo necessário nas configurações. Pessoalmente, eu entrei no histórico da conta, determinado quando ocorrer uma reposição, defini a data do próximo negócio (o primeiro negócio que ocorreu após a reposição da conta)


e selecionado o intervalo para que o primeiro negócio fosse fora dele: 22.01.2018 - 01.02.2018.
Como resultado, apenas um negócio (reposição da conta) foi exibida no diário:


Agora vamos salvar o EA TestDoEasyPart03_1.mq5 sob o nome TestDoEasyPart03_2mq5. Remova as entradas e altere a forma como os dados dos negócios são recebidos:

//+------------------------------------------------------------------+
//|                                           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>
//--- variáveis globais
CHistoryCollection history;
//+------------------------------------------------------------------+
//| Função de inicialização do Expert                                |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- atualiza o histórico
   history.Refresh();
//--- recebe apenas ordens da lista de coleções
   CArrayObj* list=history.GetList(ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
//--- ordena a lista obtida pelas operações de saldo
   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++)
     {
      //--- obtém a ordem da lista
      COrder* order=list.At(i);
      if(order==NULL) continue;
      order.Print();
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Função de desinicialização do Expert                             |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Função Tick do Expert                                            |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

Primeiro, obtenha a lista de todas as ordens (realce os estados da ordem do tipo do negócio da lista) e ordene a lista obtida pelo tipo "operação de saldo". O modo de comparação Igual é usado em ambos os casos.
Como resultado, somente a operação "operação de saldo" é exibido no diário:


Enquanto no exemplo anterior, nós tínhamos que examinar o intervalo de negócios na guia do histórico de contas do terminal para exibir a operação de saldo, aqui nós o obtivemos imediatamente depois de classificar a lista pelos critérios exigidos.

Quaisquer outras formas de obter os dados necessários seguem o mesmo princípio. Por exemplo, para obter o mesmo "operação de saldo", nós podemos encontrar os índices dos negócios mais e menos lucrativos.
Exemplo no EA de teste 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>
//--- variáveis globais
CHistoryCollection history;
//+------------------------------------------------------------------+
//| Função de inicialização do Expert                                |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- atualiza o histórico
   history.Refresh();
//--- recebe apenas ordens da lista de coleções
   CArrayObj* list=history.GetList(ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
   if(list==NULL)
     {
      Print(TextByLanguage("Не удалось получить список","Could not get list"));
      return INIT_FAILED;
     }
//--- Obtém o índice do negócio mais lucrativo (primeira operação de saldo)
   int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT);
   if(index!=WRONG_VALUE)
     {
      //--- obtém o negócio da lista pelo índice
      COrder* order=list.At(index);
      if(order!=NULL)
         order.Print();
     }
   else
      Print(TextByLanguage("Не найден индекс ордера с максимальным значением профита","Order index with maximum profit value not found"));
//--- Obtém o índice do negócio menos lucrativo
   index=CSelect::FindOrderMin(list,ORDER_PROP_PROFIT);
   if(index!=WRONG_VALUE)
     {
      //--- obtém o negócio da lista pelo índice
      COrder* order=list.At(index);
      if(order!=NULL)
         order.Print();
     }
   else
      Print(TextByLanguage("Не найден индекс ордера с минимальным значением профита","Order index with minimum profit value not found"));
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Função de desinicialização do Expert                             |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Função Tick do Expert                                            |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

Após a conclusão, dois negócios são exibidos no diário — o que tem o maior lucro (operação de saldo) e aquele com o menor lucro.



O objeto base Engine como o núcleo da biblioteca

Um programa personalizado trabalhando em conjunto com a biblioteca deve enviar os dados para a biblioteca e receber os dados dela. Para conseguir isso, é mais conveniente ter uma única classe conectando-se ao programa e acumulando todas as ações possíveis para comunicação entre a biblioteca e o programa. Claro, a classe deve assumir todas as funções de serviço possíveis que funcionarão em segundo plano e não exigirão ações custosas do usuário. Portanto, nós vamos criar uma classe básica como base da biblioteca e nomeá-la de Engine.

Na pasta raiz da biblioteca, crie uma nova classe CEngine com base na classe CObject e inclua a classe de coleção do histórico para ela:

//+------------------------------------------------------------------+
//|                                                       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"
//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include "Collections\HistoryCollection.mqh"
//+------------------------------------------------------------------+
//| Classe base da Biblioteca                                        |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
//--- Coleção do histórico de ordens e negócios
   CHistoryCollection   m_history;
public:
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+
//| Construtor CEngine                                               |
//+------------------------------------------------------------------+
CEngine::CEngine()
  {
  }
//+------------------------------------------------------------------+
//| Destrutor CEngine                                                |
//+------------------------------------------------------------------+
CEngine::~CEngine()
  {
  }
//+------------------------------------------------------------------+

Todas as ações que nós realizamos enquanto trabalhamos na coleção do histórico de ordens nos EAs de testes que foram realizadas no bloco OnInit(). Em outras palavras, eles foram executados apenas uma vez ao iniciar o EA, recompile-o ou altere seus parâmetros. Isso é suficiente para uma verificação rápida, mas não é aceitável o funcionamento do programa. Então, vamos começar a colocar tudo em ordem.

Primeiro, crie o manipulador OnTimer() na seção pública da classe, bem como sua implementação fora do corpo da classe para atualizar todas as coleções:
//+------------------------------------------------------------------+
//|                                                       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"
//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include "Collections\HistoryCollection.mqh"
//+------------------------------------------------------------------+
//| Classe base da Biblioteca                                        |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
//--- Coleção do histórico de ordens e negócios
   CHistoryCollection   m_history;
public:
//--- Timer
   void                 OnTimer(void);
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+
//| Construtor CEngine                                               |
//+------------------------------------------------------------------+
CEngine::CEngine()
  {
  }
//+------------------------------------------------------------------+
//| Destrutor CEngine                                                |
//+------------------------------------------------------------------+
CEngine::~CEngine()
  {
  }
//+------------------------------------------------------------------+
//| Timer da CEngine                                                 |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
   
  }
//+------------------------------------------------------------------+

Vamos fazer a classe de serviço (contador do timer), pois é bem provável que diferentes atrasos no timer serão necessários para diferentes eventos. A classe deve contar o tempo de atraso necessário e uma instância do contador separada deve ser declarada para cada um dos contadores do timer.

Primeiro, adicione novas substituições de macro ao arquivo Define.mqh. Especificamos a frequência do temporizador da biblioteca e o a pausa do contador do timer de coleções em milissegundos, incremento do contador do timer das coleções e o ID do histórico de ordens e o contador do timer atualizado dos negócios neles (ao desenvolver a biblioteca, você pode precisar de vários contadores que exigem IDs individuais)
//+------------------------------------------------------------------+
//| Substituições de macro                                           |
//+------------------------------------------------------------------+
#define COUNTRY_LANG             ("Russian")                // Idioma do país
#define DFUN                     (__FUNCTION__+": ")        // "Descrição da função"
#define END_TIME                 (D'31.12.3000 23:59:59')   // Hora de término para solicitar os dados do histórico da conta
#define TIMER_FREQUENCY          (16                      // Frequência mínima do timer da biblioteca em milissegundos
#define COLLECTION_PAUSE         (250)                      // pausa do timer da coleção de ordens e negócios em milissegundos
#define COLLECTION_COUNTER_STEP  (16                      // Incremento do contador do timer da coleção de ordens e negócios
#define COLLECTION_COUNTER_ID    (1)                        // ID do contador do timer da coleção de ordens e negócios
//+------------------------------------------------------------------+

Na pasta raiz da biblioteca, crie a nova pasta Services, bem como a nova classe CTimerCounter nela. Mova o arquivo de funções de serviço DELib.mqh para esta pasta imediatamente. Este é o lugar certo para isso.

Depois de mover a DELib.mqh para a nova pasta, altere o caminho do arquivo de funções de serviço no arquivo Order.mqh:

substitua o caminho

//+------------------------------------------------------------------+
//|                                                        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    // Necessário para mql4
//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include <Object.mqh>
#include "..\DELib.mqh"
//+------------------------------------------------------------------+

com o caminho:

//+------------------------------------------------------------------+
//|                                                        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    // Necessário para mql4
//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include <Object.mqh>
#include "..\Services\DELib.mqh"
//+------------------------------------------------------------------+

Agora vamos considerar a classe do contador do timer. A classe é simples. Então, vamos analisá-lo e nos concentrar em sua operação:

//+------------------------------------------------------------------+
//|                                                 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"
//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include <Object.mqh>
#include "DELib.mqh"
//+------------------------------------------------------------------+
//| Classe do contador do Timer                                      |
//+------------------------------------------------------------------+
class CTimerCounter : public CObject
  {
private:  
   int               m_counter_id;   
   ulong             m_counter;      
   ulong             m_counter_step; 
   ulong             m_counter_pause;
public:
   //--- Retorna a flag de conclusão em espera
   bool              IsTimeDone(void);
   //--- Define os parâmetros do contador
   void              SetParams(const ulong step,const ulong pause)         { this.m_counter_step=step; this.m_counter_pause=pause;  }
   //--- Retorna o id do contador
   virtual  int      Type(void)                                      const { return this.m_counter_id;                              }
   //--- Compara os objetos do contador
   virtual int       Compare(const CObject *node,const int mode=0)   const;
   //--- Construtor
                     CTimerCounter(const int id);
  };
//+------------------------------------------------------------------+
//| Construtor CTimerCounter                                         |
//+------------------------------------------------------------------+
CTimerCounter::CTimerCounter(const int id) : m_counter(0),m_counter_step(16),m_counter_pause(16)
  {
   this.m_counter_id=id;
  }
//+------------------------------------------------------------------+
//| CTimerCounter retorna a flag de conclusão da pausa               |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Compara os objetos CTimerCounter pelo 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 nós movemos o DELib.mqh para a mesma pasta em que a classe do contador está localizada, nós devemos incluí-lo diretamente da mesma pasta. Defines.mqh está incluído no DELib.mqh, o que significa que a classe verá todas as substituições de macro.

O método que retorna a flag de conclusão da pausa é organizado de uma maneira simples:

O método retornando o ID do contador Type() é do tipo virtual. Na classe CObject, há um método virtual retornando o tipo do objeto:

   //--- método para identificar o objeto
   virtual int       Type(void)                                    const { return(0);      }

Supõe-se que esse método deve ser redefinido nas classes descendentes e retornar o ID do objeto da classe descendente CObject (o tipo = 0 é retornado para a próprio CObject). Vamos usar essa oportunidade e retornar o ID do contador redefinindo o método virtual:

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

O método virtual de comparação de dois objetos contadores é simples:

//+------------------------------------------------------------------+
//| Compara os objetos CTimerCounter pelo 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;
  }
//+------------------------------------------------------------------+

Obtenha uma ligação para o objeto de origem, pegue seu ID e pegue o ID do contador atual. Em seguida, retorne o resultado de uma simples comparação por maior/menor/igual.


Vamos continuar a preencher a classe CEngine. Inclua a classe do contador do temporizador no arquivo Engine.mqh:

//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include "Collections\HistoryCollection.mqh"
#include "Services\TimerCounter.mqh"
//+------------------------------------------------------------------+

Adicione o objeto de lista dos contadores do timer, assim como o método retornando o índice do contador na lista por seu ID para a seção privada, e o método de criação do contador para o público (para que o método seja acessível a partir do exterior, e é possível criar contadores personalizados nos programas).

No construtor da classe, inicialize o timer em milissegundos, defina o flag da lista ordenada e crie o contador do timer de atualização da coleção do histórico de ordens e negócios. Defina a destruição do timer no destrutor da classe:
//+------------------------------------------------------------------+
//| Classe base da Biblioteca                                        |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
//--- Lista de contadores do timer
   CArrayObj            m_list_counters;           
//--- Coleção do histórico de ordens e negócios
   CHistoryCollection   m_history;
//--- Retorna o índice do contador por id
   int                  CounterIndex(const int id) const;
public:
//--- Cria o contador do timer
   void                 CreateCounter(const int counter_id,const ulong frequency,const ulong pause);
//--- Timer
   void                 OnTimer(void);
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+
//| Construtor CEngine                                               |
//+------------------------------------------------------------------+
CEngine::CEngine()
  {
   ::EventSetMillisecondTimer(TIMER_FREQUENCY);
   this.m_list_counters.Sort();
   this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_MIN_PAUSE);
  }
//+------------------------------------------------------------------+
//| Destrutor CEngine                                                |
//+------------------------------------------------------------------+
CEngine::~CEngine()
  {
   ::EventKillTimer();
  }
//+------------------------------------------------------------------+

Implementando o método retornando o índice do contador pelo seu ID:

//+------------------------------------------------------------------+
//| Retorna o índice do contador na lista pelo 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 dificilmente haverá inúmeros contadores, eu organizei a enumeração mais simples com a busca e comparação dos valores. Se o contador com este ID for encontrado na lista, o sistema retornará seu índice na lista, caso contrário, o sistema retornará -1.

Vamos considerar o método de criação do contador do timer:

//+------------------------------------------------------------------+
//| Cria o contador do timer                                         |
//+------------------------------------------------------------------+
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;
     }
  }
//+------------------------------------------------------------------+

Primeiro, verifique o ID do contador passado para o método. Se esse ID já estiver presente, exiba a mensagem apropriada no diário e saia do método — o contador com esse ID já existe.
Já que a busca só pode ser conduzida numa lista ordenada, a flag de ordenação está definida para a lista, um novo objeto do contador é criado, é verificado se a sua criação foi bem sucedida e as propriedades requeridas do contador estão definidas.

Depois disso, a busca pelo mesmo contador é realizada na lista. Se não for encontrado, o novo contador é adicionado à lista.

Caso contrário, a mensagem contendo todos os parâmetros é formada e exibida no diário. Então o o objeto contador é removido desde que nós já temos um semelhante.

A última verificação para todos os parâmetros do contador recém-criado que corresponde àquele já existente é atualmente redundante — a verificação do ID no início do método impede a criação de um objeto com um ID já presente na lista. Eu deixei para possíveis mudanças futuras.

Para permitir que a classe CEngine saiba quando lidar com a situação de negociação, ela deve estar ciente das mudanças ocorridas no número do histórico de ordens e negócios. Para fazer isso, adicione os métodos que retornam o número de novas ordens e negócios que apareceram na lista para a classe CHistoryCollection:

//+------------------------------------------------------------------+
//| Coleção do histórico de ordens e negócios                        |
//+------------------------------------------------------------------+
class CHistoryCollection
  {
private:
   CArrayObj         m_list_all_orders;      // Lista de todo o histórico de ordens e negócios
   COrder            m_order_instance;       // Objeto de ordem para buscar por uma propriedade
   bool              m_is_trade_event;       // Flag do evento de negociação
   int               m_index_order;          // Índice da última ordem adicionada à coleção da lista do histórico do terminal (MQL4, MQL5)
   int               m_index_deal;           // Índice do último negócio adicionado à coleção da lista do histórico do terminal (MQL5)
   int               m_delta_order;          // Diferença no número de ordens em comparação com a verificação passada
   int               m_delta_deal;           // Diferença no número de negócios em comparação com a verificação passada
public:
   //--- Seleciona as ordens da coleção com o horário de begin_time até 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);
   //--- Retorna a lista completa da coleção 'como está'
   CArrayObj        *GetList(void)                                                                       { return &m_list_all_orders;                                            }
   //--- Retorna a lista pelas propriedades de (1) inteiro, (2) real e (3) string selecionadas que atendem ao critério comparado
   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);  }
   //--- Retorna o número de (1) novas ordens e (2) novos negócios
   int               NewOrders(void)                                                                     { return m_delta_order; }
   int               NewDeals(void                                                                     { return m_delta_deal;  }
   
   //--- Construtor
                     CHistoryCollection();
   //--- Atualiza a lista de ordens, preenche os dados sobre o número de novas ordens e configura a flag do evento de negociação
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

Os métodos simplesmente retornam os valores das variáveis membro da classe apropriada.

Agora na CEngine, nós podemos verificar o seu estado na classe timer:

//+------------------------------------------------------------------+
//| Timer da CEngine                                                 |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
   //--- Timer da coleção do histórico de ordens e negócios
   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());
           }
        }
     }
  }
//+------------------------------------------------------------------+

Obtenha o índice do contador do timer da coleção do histórico de ordens e negócios, obtenha o ponteiro para o contador do timer pelo seu índice, verifique a conclusão do tempo de atraso do timer e atualize a coleção (somente a última ordem ou negócio adicionado, se houver).

Se o número do histórico de ordens foi alterado, exiba a mensagem no diário. O mesmo vale para os negócios.

Vamos fazer um EA simples para verificação. Em Experts\TestDoEasy\Part3, crie o EA com o timer chamado TestDoEasyPart03_4.mq5. Para criar o modelo do EA com o timer, marque a OnTimer na segunda página do Assistente MQL:


Clique em Avançar até a conclusão da operação do Assistente. Como resultado, um modelo do EA vazio é criado. Inclua o arquivo da biblioteca principal a ele, crie o objeto da classe da biblioteca e chame o timer da biblioteca no EA.

//+------------------------------------------------------------------+
//|                                           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>
//--- variáveis globais
CEngine        engine;
//+------------------------------------------------------------------+
//| Função de inicialização do Expert                                |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Função de desinicialização do Expert                             |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Função Tick do Expert                                            |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Funçao timer                                                     |
//+------------------------------------------------------------------+
void OnTimer()
  {
   engine.OnTimer();
  }
//+------------------------------------------------------------------+

Isso é tudo o que é necessário a ser feito no EA para obter dados sobre as alterações no histórico da conta.

Se nós lançarmos o EA agora, coloque uma ordem pendente e exclua-o, uma entrada referente à alteração do número do histórico de ordens aparecerá no diário.
Se nós abrirmos uma posição, duas entradas aparecerão no diário:

  1. uma relativa à alteração do número de ordens (a ordem à mercado aberta foi enviada) e
  2. outra relativa a mudança no número de negócios (a ordem à mercado foi ativada e gerou o negócio "Market Entry").

Se nós fecharmos uma posição, duas entradas aparecerão no diário novamente:

  1. sobre a aparência da ordem de encerramento à mercado
  2. sobre o surgimento do novo negócio (a ordem de encerramento à mercado foi ativada e gerou o acordo "Market exit")

Durante a primeira execução, as mensagens sobre as alterações de ordens e negócios no histórico da conta são exibidas no diário. Isso acontece porque a biblioteca lê todo o histórico durante a primeira execução, portanto, a diferença entre o número zero de ordens e negócios (a biblioteca não sabe nada sobre elas durante a primeira execução) e o número de todos as ordens e negócios calculados durante o loop completo do histórico da conta será igual ao seu número completo. Isso é inconveniente e desnecessário. Portanto, nós precisamos eliminar essas mensagens falsas de alteração do número. Nós podemos fazer isso de duas maneiras:

  1. exibir nada no diário durante a primeira execução
  2. exibir as mensagens do estado da conta durante a primeira execução
Como nós vamos implementar a coleta e a exibição das estatísticas necessárias nas partes subsequentes da descrição, vamos usar a primeira opção por enquanto (não exibindo nada no diário durante a primeira execução) e apenas fazer um esboço para a primeira execução:

Na seção privada da classe CEngine, adicione a flag da primeira execução e o método que verifica e redefine a flag:

//+------------------------------------------------------------------+
//| Classe base da Biblioteca                                        |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // Coleção do histórico de ordens e negócios
   CArrayObj            m_list_counters;                 // Lista dos contadores do timer
   bool                 m_first_start;                   // Flag da primeira execução
//--- Retorna o índice do contador por id
   int                  CounterIndex(const int id) const;
//--- Retorna a flag da primeira execução
   bool                 IsFirstStart(void);
public:
//--- Cria o contador do timer
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- Timer
   void                 OnTimer(void);
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+

e adicione a implementação do método de verificação e redefina a flag da primeira execução além do corpo da classe:

//+------------------------------------------------------------------+
//| Retorna a flag da primeira execução, redefine o sinalizador      |
//+------------------------------------------------------------------+
bool CEngine::IsFirstStart(void)
  {
   if(this.m_first_start)
     {
      this.m_first_start=false;
      return true;             
     }
   return false;
  }
//+------------------------------------------------------------------+

Tudo é muito simples aqui: se a flag estiver definida, redefina-o e retorne 'true', caso contrário retorne 'false'.
Agora nós precisamos definir a flag na lista de inicialização, para que permaneça sempre pronto para ser ativado.

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

Tudo está definido. De agora em diante, nenhum evento desnecessário do histórico da conta relacionado ao primeiro cálculo do histórico aparecerá durante a execução do primeiro programa.

Nós podemos verificar isso iniciando o teste do EA em MQL5\Experts\TestDoEasy\Part2\TestDoEasyPart03_4.mq5 e garantindo que nenhuma mensagem sobre a adição de ordens e negócios ao histórico da conta apareça no diário Experts durante a primeira execução.

Objetos de ordens e posições de mercado ativas

Acredito que agora é hora de parar temporariamente de adicionar a funcionalidade à classe CEngine e começar a implementar os objetos e a coleção de ordens e posições de mercado. Após a conclusão da implementação, nós continuaremos desenvolvendo a funcionalidade do objeto base de Engine, já que essa funcionalidade afeta o histórico da conta e seu estado atual.

Na pasta Objects da biblioteca, crie a nova classe CMarketPosition com base na classe abstrata COrder da biblioteca — este é um objeto da posição de mercado:


Depois de clicar em Concluir, um modelo da classe chamado MarketPosition.mqh é criado. Vamos adicionar a inclusão da classe COrder para ele imediatamente:

//+------------------------------------------------------------------+
//|                                               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"
//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include "Order.mqh"
//+------------------------------------------------------------------+
//| Posição no mercado                                               |
//+------------------------------------------------------------------+
class CMarketPosition : public COrder
  {
private:

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

Altere o construtor para que o ticket da posição seja passado para ele, defina o estado da "posição no mercado" para a classe pai (COrder) na sua lista de inicialização e envie o ticket para ele. Declare três métodos virtuais retornando a flag suportando as propriedades inteiras, reais e de string pela posição:

//+------------------------------------------------------------------+
//| Posição no mercado                                               |
//+------------------------------------------------------------------+
class CMarketPosition : public COrder
  {
public:
   //--- Construtor
                     CMarketPosition(const ulong ticket=0) : COrder(ORDER_STATUS_MARKET_POSITION,ticket) {}
   //--- Propriedades de posição suportadas (1) real, (2) inteiro
   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 adicione a implementação desses métodos fora do corpo da classe:

//+------------------------------------------------------------------+
//| Retorna 'true' se a posição suportar a propriedade               |
//| inteira passada, caso contrário retorna '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;
}
//+------------------------------------------------------------------+
//| Retorna 'true' se a posição suportar a propriedade               |
//| real passada, caso contrário retorna '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;
  }
//+------------------------------------------------------------------+
//| Retorna 'true' se a posição suportar a propriedade               |
//| de string passada, caso contrário retorna 'false'                |
//+------------------------------------------------------------------+
bool CMarketPosition::SupportProperty(ENUM_ORDER_PROP_STRING property)
  {
   if(property==ORDER_PROP_EXT_ID) return false;
   return true;
  }
//+------------------------------------------------------------------+

Tudo aqui é semelhante a criação de objetos do histórico de ordens e negócios discutido na segunda parte da descrição da biblioteca.

Agora vamos criar um objeto de ordem pendente de maneira semelhante. Vamos criar uma nova classe CMarketPending com base na ordem abstrata da biblioteca COrder na pasta Objects e inserir as mudanças já familiares do modelo de classe criado pelo Assistente MQL para ela:

//+------------------------------------------------------------------+
//|                                                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"
//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include "Order.mqh"
//+------------------------------------------------------------------+
//| Ordem pendente                                                   |
//+------------------------------------------------------------------+
class CMarketPending : public COrder
  {
public:
   //--- Construtor
                     CMarketPending(const ulong ticket=0) : COrder(ORDER_STATUS_MARKET_PENDING,ticket) {}
   //--- Propriedades das ordens suportadas do tipo (1) real, (2) inteiro
   virtual bool      SupportProperty(ENUM_ORDER_PROP_DOUBLE property);
   virtual bool      SupportProperty(ENUM_ORDER_PROP_INTEGER property);
  };
//+------------------------------------------------------------------+
//| Retorna 'true' se a ordem suportar a propriedade                 |
//| inteiro passada, caso contrário, retorna '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;
  }
//+------------------------------------------------------------------+
//| Retorna 'true' se a ordem suportar a propriedade                 |
//| real passada, caso contrário, retorna '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;
  }
//+------------------------------------------------------------------+

Passe o estado da "ordem pendente" para a COrder base na lista de inicialização do construtor da classe.

Nós completamos o desenvolvimento dos objetos necessários para criar a coleção de ordens e posições de mercado.

Coleção de ordens e posições de mercado ativas

Ao criar a coleção do histórico de ordens e negócios, nós seguimos a regra de que não há sentido em verificar constantemente todo o histórico. Então, nós adicionamos novas ordens e negócios à lista única criada anteriormente somente quando os números forem alterados. Ao usar a lista de posições, uma regra completamente diferente deve ser mantida em mente — a lista de cada tick deve ser relevante.

Para alcançar isto:

  1. acompanhe as alterações no número de ordens pendentes, no número de posições ativas para contas hedge (já que existe apenas uma posição nas contas netting) e no volume da posição (aumento ou diminuição do volume na posição da conta netting, encerramento parcial de uma das posições da conta hedging),
  2. certifique-se de atualizar os dados em cada posição existente da conta hedging ou de uma netting a cada tick para sempre ter dados do estado da posição relevantes.

Lembre-se dos valores especificados no último tick para compará-los com os mesmos dados no atual e atualizar a posição ou recriar a lista inteira novamente se houver alterações. Felizmente, a lista não é grande e sua recriação não leva muito tempo.

Vamos começar. Na pasta Collections da biblioteca, crie a nova classe CMarketCollection. Para fazer isso, clique com o botão direito do mouse na pasta Collection e selecione "Novo arquivo". No Assistente MQL recém-aberto, selecione "Nova classe" e clique em Avançar.


Digite o nome da classe CMarketCollection e clique em Concluir. O modelo de classe MarketCollection.mqh é criado:

//+------------------------------------------------------------------+
//|                                             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 preenchê-lo.

Primeiro, inclua todas as classes preparadas, bem como as que são necessárias para implementar a coleção de ordens e posições de mercado e de busca:

//+------------------------------------------------------------------+
//|                                             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"
//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "Select.mqh"
#include "..\Objects\MarketPending.mqh" 
#include "..\Objects\MarketPosition.mqh"
//+------------------------------------------------------------------+

Na seção privada da classe, crie a estrutura, especifique as variáveis para armazenar todos os valores rastreados mencionados anteriormente (número de ordens e posições, etc.) e crie duas variáveis membro de classe com este tipo de estrutura para armazenar os dados atuais e anterior:

//+------------------------------------------------------------------+
//|                                             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"
//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "Select.mqh"
#include "..\Objects\MarketPending.mqh" 
#include "..\Objects\MarketPosition.mqh"
//+------------------------------------------------------------------+
//| Coleção do histórico de ordens e negócios                        |
//+------------------------------------------------------------------+
class CMarketCollection
  {
private:
   struct MqlDataCollection
     {
      long           hash_sum_acc;           // Soma hash de todos as ordens e posições na conta
      int            total_pending;          // Número de ordens pendentes na conta
      int            total_positions;        // Número de posições na conta
      double         total_volumes;          // Volume total de ordens e posições na conta
     };
   MqlDataCollection m_struct_curr_market;   // Dados atuais sobre ordens e posições de mercado na conta
   MqlDataCollection m_struct_prev_market;   // Dados anteriores sobre ordens e posições de mercado na conta
public:
                     CMarketCollection();
                    ~CMarketCollection();
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CMarketCollection::CMarketCollection()
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CMarketCollection::~CMarketCollection()
  {
  }
//+------------------------------------------------------------------+

Vamos nos debruçar sobre a soma hash na estrutura.
O número de ordens e posições é insuficiente se quisermos definir com precisão um evento da conta ocorrido. Uma ordem pendente pode ser removida alterando o número total de ordens e posições na conta. Por outro lado, uma ordem pendente pode ser ativada e se tornar uma posição. Nesse caso, a soma total de ordens e posições permanece inalterada (para contas hedge e MQL4) — o número de posições aumenta, mas o número de ordens diminui. Como resultado, o número total permanece o mesmo. Isso não é adequado para nós.

Vamos considerar o ticket. Adicionar/remover uma ordem pendente altera a soma total de tickets na conta, enquanto a ativação de uma ordem pendente não altera a soma total de tickets na conta.

Vamos considerar o volume total. Colocar/remover uma ordem pendente — o volume total da conta foi alterado, abrindo, fechando ou modificando uma posição — o volume total da conta foi alterado. Essa opção parece se ajustar, mas a ativação de ordens pendentes não altera o volume total.

Então, vamos dar uma olhada em outra propriedade da posição — tempo de sua mudança em milissegundos: a abertura de uma nova posição altera o tempo total de alteração da posição, o fechamento parcial altera o tempo de alteração da posição e a adição do volume em uma conta hedging altera o tempo total de alteração da posição.

Quais são as opções mais adequadas para a definição precisa de uma alteração da conta ocorrida? TIcket + tempo de alteração da posição. Vamos verificar:

  • Abertura de uma posição — soma dos tickets mudou + a soma do horário de alteração da posição mudou há uma mudança
  • Encerramento de uma posição — a soma dos ticket mudou + a soma do horário de alteração da posição mudou há uma mudança
  • Colocação de uma ordem pendente — a soma dos tickets mudou + a soma do horário de alteração da posição não mudouhá uma mudança
  • Remoção de uma ordem pendente — a soma dos tickets mudou + a soma do horário de alteração da posição não mudou há uma mudança
  • Acionamento de uma ordem pendente — a soma dos tickets não mudou + a soma do horário de alteração da posição mudou há uma mudança
  • Encerramento parcial de uma posição — a soma dos tickets mudou + a soma do horário de alteração da posição mudou há uma mudança
  • Aumento do volume de uma posição — a soma dos tickets não mudou + a soma do horário de alteração da posição mudou há uma mudança
Assim, nós usaremos o ticket + ao horário de alteração da posição em milissegundos para a soma hash.

Na seção privada da classe, crie a lista dinâmica de ponteiros para os objetos para ser usado como uma lista de coleção de ordens pendentes e posições. Além disso, crie duas flags: flag do evento de negociação na conta e a flag indicando a ocorrência de uma alteração no volume da posição para a identificação simplificada de um evento de negociação na classe CEngine, bem como três variáveis membro da classe para definir o valor do volume alterado, número de novas posições e ordens pendentes.
Na seção pública, declare o método para atualizar a lista de coleções e escreva a implementação do construtor de classe fora do corpo da classe:

//+------------------------------------------------------------------+
//| Coleção de ordens e posições                                     |
//+------------------------------------------------------------------+
class CMarketCollection
  {
private:
   struct MqlDataCollection
     {
      long           hash_sum_acc;           // Soma hash de todos as ordens e posições na conta
      int            total_pending;          // Número de ordens pendentes na conta
      int            total_positions;        // Número de posições na conta
      double         total_volumes;          // Volume total de ordens e posições na conta
     };
   MqlDataCollection m_struct_curr_market;   // Dados atuais sobre as ordens e posições de mercado na conta
   MqlDataCollection m_struct_prev_market;   // Dados anteriores sobre ordens e posições de mercado na conta
   CArrayObj         m_list_all_orders;      // Lista de ordens pendentes e posições na conta
   bool              m_is_trade_event;       // Flag do evento de negociação
   bool              m_is_change_volume;     // Flag da alteração do volume total
   double            m_change_volume_value;  // Valor da alteração do volume total
   int               m_new_positions;        // Número de novas posições
   int               m_new_pendings;         // Número de novas ordens pendentes
public:
   //--- Construtor
                     CMarketCollection(void);
   //--- Atualiza a lista de ordens pendentes e posições
   void              Refresh(void);
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

Redefina as flags do evento de negociação e da alteração do volume da posição e redefina o valor da alteração do volume na lista de inicialização do construtor de classe.
No corpo do construtor, defina a ordenação da lista de posições e ordens à mercado pelo horário de abertura, redefina todas as estruturas de variáveis do estado anterior da conta exceto para a soma hash anterior defina -1 para ela (para identificar o primeiro lançamento).

Adicione o método para salvar os dados da conta atualmente coletados na estrutura de dados anterior para a seção privada da classe para a verificação subseqüente de alterações no número de ordens e posições na conta. Adicione os três métodos retornando o número de novas ordens pendentes, o número de novas posições e o método que retorna a flag da ocorrência de um evento de negociação na conta para a seção pública da classe:

//+------------------------------------------------------------------+
//| Coleção de ordens e posições                                     |
//+------------------------------------------------------------------+
class CMarketCollection
  {
private:
   struct MqlDataCollection
     {
      long           hash_sum_acc;           // Soma hash de todos as ordens e posições na conta
      int            total_pending;          // Número de ordens pendentes na conta
      int            total_positions;        // Número de posições na conta
      double         total_volumes;          // Volume total de ordens e posições na conta
     };
   MqlDataCollection m_struct_curr_market;   // Dados atuais sobre as ordens e posições de mercado na conta
   MqlDataCollection m_struct_prev_market;   // Dados anteriores sobre ordens e posições de mercado na conta
   CArrayObj         m_list_all_orders;      // Lista de ordens pendentes e posições na conta
   bool              m_is_trade_event;       // Flag do evento de negociação
   bool              m_is_change_volume;     // Flag de alteração do volume total
   double            m_change_volume_value;  // Valor alterado do volume total
   int               m_new_positions;        // Número de novas posições
   int               m_new_pendings;         // Número de novas ordens pendentes
   //--- Salva os valores atuais do estado dos dados da conta como sendo o anterior
   void              SavePrevValues(void)             { this.m_struct_prev_market=this.m_struct_curr_market;   }
public:
   //--- Retorna o número de (1) novas ordens pendentes, (2) novas posições, (3) flag de ocorrência do evento de negociação
   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;                          }
   //--- Construtor
                     CMarketCollection(void);
   //--- Atualiza a lista de ordens pendentes e posições
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

Vamos implementar o método de atualização do estado atual do mercado:

//+------------------------------------------------------------------+
//| Atualiza a lista order                                           |
//+------------------------------------------------------------------+
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    
//--- Posições
   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;
        }
     }
//--- Ordens
   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 
//--- Primeiro lançamento
   if(this.m_struct_prev_market.hash_sum_acc==WRONG_VALUE)
     {
      this.SavePrevValues();                              
     }                                                    
//--- Se a soma hash de todas as ordens e posições mudou
   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 analisar o método, vamos fazer uma pequena digressão: uma vez que nós precisamos constantemente de dados relevantes em todas as ordens e posições de mercado, nós podemos limpar a lista a cada tick e preenchê-la com os dados do ambiente de mercado. Alternativamente, nós podemos preencher a lista uma vez e alterar apenas os dados que podem ser alterados. À primeira vista, parece que seria mais rápido alterar apenas os dados alterados. Mas para fazer isso, nós precisamos:

  1. percorrer a lista de ordens e posições do terminal, e preencher a lista de bibliotecas com eles,
  2. a cada tick, percorrer a lista de ordens e posições do terminal, obter os dados em modificação, procurar as ordens e posições com o mesmo ticket na lista da biblioteca e atualizar os dados existentes,
  3. Se uma ordem for removida ou uma posição for encerrada, remova-a da lista da biblioteca.

Isso parece mais caro do que simplesmente limpar a lista da biblioteca e preenchê-la por ordens e posições de mercado do terminal em um loop.

Portanto, vamos seguir uma maneira mais simples: limpar a lista e preenchê-la novamente. Naturalmente, nada nos impede de tentar o método que envolve a busca e a atualização dos dados na lista da biblioteca já existente. Vamos experimentá-lo ao trabalhar com a biblioteca e melhorar sua velocidade de execução de (maneira "simples à complexa").

Agora vamos ver como o método de atualização da lista de coleções de ordens e posições de mercado é organizado.

No início do método, a estrutura dos dados de mercado atuais, flags de eventos, valor do volume alterado, bem como todas as variáveis relativas ao número de ordens e posições são redefinidas e a lista de coleções é limpa.

Então, é conduzido a verificação para a referência à MQL4 ou MQL5.
Como neste estágio nós preparamos o código em MQL5, vamos dar uma olhada na versão MQL5:

Ao contrário do MQL4, em MQL5, as ordens e posições são armazenadas em listas diferentes.
Portanto, obtenha o número total de posições na conta, em seguida, mova ao longo de todas as posições do terminal em um loop, selecione o ticket da próxima posição, crie um objeto da posição e adicione-o à lista de coleções de ordens e posições ativas da biblioteca.

Da mesma forma, adicione todas ordens pendentes presentes atualmente na conta. Obtenha o número total de ordens na conta somente para ordens pendentes e mova ao longo da lista de ordens do terminal em um loop recebendo um ticket da ordem e adicionando um objeto da ordem para a lista de coleções das ordens e posições ativas da biblioteca.

Após a conclusão dos dois loops, a lista de coleções das ordens e posições ativas da biblioteca conterá os objetos de ordens e posições presentes atualmente na conta. Em seguida, verifique a primeira flag de lançamento (o valor da soma hash "anterior" igual a -1 é usado como flag aqui). Se esse é o primeiro lançamento, os valores de todos os valores "antigos" são copiados para a estrutura que armazena esses valores usando o método SavePrevValues(). Se esta não for a primeira execução, o valor da soma hash anterior será comparada com o valor da soma atual calculada na iteração dos loops da coleta de dados da conta para a lista de coleções. Se a soma hash anterior não for igual à atual, ocorrerá uma alteração na conta

Nesse caso, a diferença entre o número atual e a anterior das ordens é definida na variável que armazena o novo número de ordens da conta, enquanto a diferença entre o número atual e o anterior de posições é definida na variável que armazena o novo número de posições da conta. Salve o valor, pelo qual o volume total da conta foi alterado, defina a flag de alteração do volume, assim como a flag da ocorrência de um evento de negociação, e finalmente, adicione os novos valores à estrutura dos valores "anteriores" usando o método SavePrevValues() para uma verificação posterior.

O método SavePrevValues() simplesmente copia a estrutura com os valores atuais para a estrutura contendo os anteriores.

Para verificar a operação do método de atualização da lista de ordens e posições de mercado e seu trabalho conjunto com o método de atualização da lista do histórico de ordens e de negócios, use o último EA de teste da pasta Part03 chamada 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>
//--- variáveis globais
CEngine        engine;
//+------------------------------------------------------------------+
//| Função de inicialização do Expert                                |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Função de desinicialização do Expert                             |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 
   
  }
//+------------------------------------------------------------------+
//| Função Tick do Expert                                            |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Funçao timer                                                     |
//+------------------------------------------------------------------+
void OnTimer()
  {
   engine.OnTimer();
  }
//+------------------------------------------------------------------+

Para ver as alterações implementadas ao adicionar e rastrear as coleções de ordens e posições de mercado, adicione as seguintes sequências ao manipulador de eventos Timer da classe CEngine:

//+------------------------------------------------------------------+
//| Timer da CEngine                                                 |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
   //--- Timer das coleções do histórico de ordens e negócios, bem como da ordens e posições de mercado
   int index=this.CounterIndex(COLLECTION_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter!=NULL && counter.IsTimeDone())
        {
         //--- Atualiza as listas 
         this.m_market.Refresh(); 
         this.m_history.Refresh();
         //--- Primeiras ações de lançamento
         if(this.IsFirstStart())
           {
            return;
           }
         //--- Verifica se o estado do mercado se alterou
         if(this.m_market.IsTradeEvent())
           {
            Print(DFUN,TextByLanguage("Новое торговое событие на счёте","New trading event on account"));
           }
         //--- Verifica se o histórico da conta se alterou
         if(this.m_history.IsTradeEvent())
           {
            Print(DFUN,TextByLanguage("Новое торговое событие в истории счёта","New trading event in account history"));
           }
        }
     }
  }
//+------------------------------------------------------------------+

Tudo é simples aqui. A conclusão da espera no contador do timer da coleção é verificada primeiro. Se a pausa acabou, as listas de ordens e posições de mercado são atualizadas, bem como as do histórico de ordens e negócios. Nenhuma ação é necessária ainda durante o primeiro lançamento. Ao receber uma flag de ocorrência de um evento da conta, exiba o método apropriado no diário. O mesmo é feito ao receber a flag de evento no histórico da conta.

Compile o EA de teste e inicie-o. Agora, se nós abrirmos uma posição, duas entradas aparecerão no diário:

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

A primeira indica que um evento de negociação ocorreu em uma conta, enquanto a segunda informa sobre a adição de um novo evento ao histórico da conta. Nesse caso, um evento de negociação na conta consiste em aumentar o número de posições por 1, enquanto um novo evento no histórico da conta significa o aparecimento de uma nova ordem de abertura à mercado e um novo negócio — "market entry".

Se nós fecharmos agora uma posição, as mesmas duas entradas aparecem no diário, mas agora as leituras são diferentes: um evento de negociação na conta está diminuindo o número de posições por 1, enquanto um novo evento no histórico da conta está adicionando uma única nova ordem de encerramento à mercado e um único novo negócio — "market exit".

Qual é o próximo?

No próximo artigo, nós continuaremos o desenvolvimento do elemento principal da biblioteca (a classe CEngine) e implementaremos a manipulação de eventos provenientes da coleção e o envio deles para o programa.

Todos os arquivos da versão atual da biblioteca estão anexados abaixo, juntamente com os arquivos do EA de teste para você testar e fazer o download.
Deixe suas perguntas, comentários e sugestões nos comentários.

Voltar ao conteúdo