Biblioteca para desenvolvimento fácil e rápido de programas para a MetaTrader (parte V): Classes e coleções de eventos de negociação, envio de eventos para o programa

15 julho 2019, 09:26
Artyom Trishkin
0
1 029

Conteúdo

Reorganizando a estrutura da biblioteca

Nos artigos anteriores, 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 quarta parte, nós testamos o monitoramento de eventos de negociação na conta. Neste artigo, nós vamos desenvolver classes de eventos de negociação e colocá-los nas coleções de eventos. A partir daí, eles serão enviados ao objeto base da biblioteca Engine e ao gráfico do programa de controle.

Mas primeiro, vamos preparar o terreno para o desenvolvimento da estrutura da biblioteca.

Como nós teremos muitas coleções diferentes, e cada coleção terá seus próprios objetos inerentes apenas a essa coleção, parece razoável armazenar objetos para cada coleção em subpastas separadas.

Para fazer isso, vamos criar as pastas Orders e Events na subpasta Objetcs do diretório raíz da biblioteca DoEasy.
Mova todas as classes criadas anteriormente da pasta Objects para a de Orders, enquanto a pasta Events é para armazenar as classes de objetos de evento que nós vamos desenvolver neste artigo.
Além disso, realoque o arquivo Select.mqh de Collections para Services, já que nós vamos incluir ainda outra classe de serviço nele. A classe apresenta os métodos para o acesso rápido a quaisquer propriedades de qualquer objeto das coleções existentes e futuras, o que significa que ela deve estar localizada na pasta service das classes.

Depois de realocar o arquivo da classe CSelect e mover as classes de objeto order para o novo diretório, os endereços relativos dos arquivos necessários para sua compilação também são alterados. Portanto, vamos percorrer as listagens das classes movidas e substituir os endereços dos arquivos incluídos nelas:

No arquivo Order.mqh, substitua o caminho para incluir o arquivo de funções de serviço

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

para

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

No arquivo HistoryCollection.mqh, substitua os caminhos

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

 para

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

No arquivo MarketCollection.mqh, substitua os caminhos

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

para

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

Agora tudo deve ser compilado sem erros.

Como o número de coleções futuras é enorme, seria bom distinguir a propriedade da lista de coleção com base na CArrayObj para identificar a lista. Cada coleção apresenta um método que retorna o ponteiro para a lista completa de coleções. Se em algum lugar houver um método que receba uma determinada lista de uma determinada coleção, então, dentro desse método, nós precisamos identificar com precisão a lista passada ao método se ela pertence a uma coleção ou outra para evitar passar um sinalizador adicional indicando o tipo da lista passada para o método.

Felizmente, a biblioteca padrão já fornece a ferramenta necessária para isso na forma do método virtual Type() retornando o ID do objeto.
Por exemplo, para a CObject, o ID retornado é 0, enquanto que para a CArrayObj, o ID é 0x7778. Como o método é virtual, isso permite que os descendentes das classes tenham seus próprios métodos retornando IDs específicos.

Todas as nossas listas de coleções são baseadas na classe CArrayObj. Nós criaremos nossa própria classe CListObj que é um descendente da classe CArrayObj e o ID da lista é retornado em seu método virtual Type(). O ID em si é definido como uma constante no construtor da classe. Assim, nós continuaremos obtendo acesso às nossas coleções quanto ao objeto CArrayObj, mas agora cada lista terá seu próprio ID específico.

Primeiro, vamos definir as listas de coleção de IDs no arquivo Defines.mqh e adicionar a macro descrevendo a função com o número da linha de erro para exibir mensagens de depuração contendo uma string da qual esta mensagem é enviada para localizar o problema no código durante a depuração:

//+------------------------------------------------------------------+
//| Substituições de macro                                           |
//+------------------------------------------------------------------+
//--- Descreve a função com o número da linha de erro
#define DFUN_ERR_LINE            (__FUNCTION__+(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian" ? ", Page " : ", Line ")+(string)__LINE__+": ")
#define DFUN                     (__FUNCTION__+": ")        // "Descrição da função"
#define COUNTRY_LANG             ("Russian")                // Idioma do país
#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
#define COLLECTION_HISTORY_ID    (0x7778+1)                 // ID da lista de coleção do histórico
#define COLLECTION_MARKET_ID     (0x7778+2)                 // ID da lista de coleção à Mercado
#define COLLECTION_EVENTS_ID     (0x7778+3)                 // ID da lista de coleção de eventos
//+------------------------------------------------------------------+

Agora crie a classe CListObj no arquivo ListObj.mqh dentro da pasta Collections. A classe base para isso é a CArrayObj:

//+------------------------------------------------------------------+
//|                                                      ListObj.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. | 
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
//+------------------------------------------------------------------+
//| Classe das listas de coleções                                    |
//+------------------------------------------------------------------+
class CListObj : public CArrayObj
  {
private:
   int               m_type;                    // Tipo da lista
public:
   void              Type(const int type)       { this.m_type=type;     }
   virtual int       Type(void)           const { return(this.m_type);  }
                     CListObj()                 { this.m_type=0x7778;   }
  };
//+------------------------------------------------------------------+

Tudo o que temos a fazer aqui é declarar um membro da classe que contém o tipo lista, adicionar o método para definir o tipo lista e o método virtual para retorna-lo.
No construtor da classe, defina o tipo lista padrão igual ao da lista CArrajObj. Ele pode ser redefinido a partir do programa que realizou a chamada usando o método Type().

Agora nós precisamos herdar todas as listas de coleções da classe para poder atribuir um ID de busca separada a cada lista. Esse ID nos permitirá monitorar a propriedade da lista em qualquer método para o qual a lista será passada.

Abra o arquivo HistoryCollection.mqh, adicione a inclusão da classe CListObj e herde a classe CHistoryCollection de CListObj.

//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Orders\HistoryOrder.mqh"
#include "..\Objects\Orders\HistoryPending.mqh"
#include "..\Objects\Orders\HistoryDeal.mqh"
//+------------------------------------------------------------------+
//| Coleção do histórico de ordens e negócios                        |
//+------------------------------------------------------------------+
class CHistoryCollection : public CListObj
  {

No construtor da classe, defina o tipo lista de coleção do histórico, que nós especificamos como COLLECTION_HISTORY_ID no arquivo Defines.mqh:

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

Faça o mesmo para a classe CMarketCollection no arquivo MarketCollection.mqh:

//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Orders\MarketOrder.mqh"
#include "..\Objects\Orders\MarketPending.mqh"
#include "..\Objects\Orders\MarketPosition.mqh"
//+------------------------------------------------------------------+
//| Coleção de ordens e posições                                     |
//+------------------------------------------------------------------+
class CMarketCollection : public CListObj
  {

No construtor de classe, defina o tipo coleção à mercado, que nós especificamos no arquivo Defines.mqh como COLLECTION_MARKET_ID:

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

Agora, cada uma das listas de coleção tem seu ID simplificando a identificação das listas pelos seus tipos.



Como nós devemos adicionar novas coleções para trabalhar com os novos tipos de dados (incluindo a coleção de eventos da conta no presente artigo), nós usaremos as novas enumerações. Para evitar conflitos de nomes, nós precisamos substituir os nomes de algumas substituições de macro criadas anteriormente:

//+------------------------------------------------------------------+
//| Possíveis critérios de classificação de ordens e negócios        |
//+------------------------------------------------------------------+
#define FIRST_ORD_DBL_PROP          (ORDER_PROP_INTEGER_TOTAL)
#define FIRST_ORD_STR_PROP          (ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL)
enum ENUM_SORT_ORDERS_MODE
  {
   //--- Classifica por propriedades do tipo inteiro
   SORT_BY_ORDER_TICKET          =  0,                      // Classifica pelo ticket da ordem
   SORT_BY_ORDER_MAGIC           =  1,                      // Classifica pelo número mágico da ordem
   SORT_BY_ORDER_TIME_OPEN       =  2,                      // Classifica pelo horário de abertura da ordem
   SORT_BY_ORDER_TIME_CLOSE      =  3,                      // Classifica pelo horário de fechamento da ordem
   SORT_BY_ORDER_TIME_OPEN_MSC   =  4,                      // Classifica pelo horário de abertura da ordem em milissegundos
   SORT_BY_ORDER_TIME_CLOSE_MSC  =  5,                      // Classifica pelo horário de fechamento da ordem em milissegundos
   SORT_BY_ORDER_TIME_EXP        =  6,                      // Classifica pela data de expiração da ordem
   SORT_BY_ORDER_STATUS          =  7,                      // Classifica pelo estado da ordem (ordem à mercado/ordem pendente/negócio/saldo/ operação de crédito)
   SORT_BY_ORDER_TYPE            =  8,                      // Classifica pelo tipo da ordem
   SORT_BY_ORDER_REASON          =  10,                     // Classifica por ordem/posição/fonte
   SORT_BY_ORDER_STATE           =  11,                     // Classifica pelo estado da ordem
   SORT_BY_ORDER_POSITION_ID     =  12,                     // Classifica pelo ID da posição
   SORT_BY_ORDER_POSITION_BY_ID  =  13,                     // Classifica pelo ID da posição oposta
   SORT_BY_ORDER_DEAL_ORDER      =  14,                     // Classifica pela ordem que o negócio se baseou
   SORT_BY_ORDER_DEAL_ENTRY      =  15,                     // Classifica pela direção do negócio – IN, OUT ou IN/OUT
   SORT_BY_ORDER_TIME_UPDATE     =  16,                     // Classifica pelo horário de alteração da posição em segundos
   SORT_BY_ORDER_TIME_UPDATE_MSC =  17,                     // Classifica pelo horário de alteração da posição em milissegundos
   SORT_BY_ORDER_TICKET_FROM     =  18,                     // Classifica pelo ticket da ordem pai
   SORT_BY_ORDER_TICKET_TO       =  19,                     // Classifica pelo ticket da ordem derivada
   SORT_BY_ORDER_PROFIT_PT       =  20,                     // Classifica pelo lucro da ordem em pontos
   SORT_BY_ORDER_CLOSE_BY_SL     =  21,                     // Classifica pelo fechamento da ordem pela flag StopLoss
   SORT_BY_ORDER_CLOSE_BY_TP     =  22,                     // Classifica pelo fechamento da ordem pela flag TakeProfit
   //--- Classifica por propriedades do tipo real
   SORT_BY_ORDER_PRICE_OPEN      =  FIRST_ORD_DBL_PROP,     // Classifica pelo preço de abertura
   SORT_BY_ORDER_PRICE_CLOSE     =  FIRST_ORD_DBL_PROP+1,   // Classifica pelo preço do fechamento
   SORT_BY_ORDER_SL              =  FIRST_ORD_DBL_PROP+2,   // Classifica pelo preço de StopLoss
   SORT_BY_ORDER_TP              =  FIRST_ORD_DBL_PROP+3,   // Classifica pelo preço de TakeProfit
   SORT_BY_ORDER_PROFIT          =  FIRST_ORD_DBL_PROP+4,   // Classifica pelo lucro
   SORT_BY_ORDER_COMMISSION      =  FIRST_ORD_DBL_PROP+5,   // Classifica pela comissão
   SORT_BY_ORDER_SWAP            =  FIRST_ORD_DBL_PROP+6,   // Classifica pelo swap
   SORT_BY_ORDER_VOLUME          =  FIRST_ORD_DBL_PROP+7,   // Classifica pelo volume
   SORT_BY_ORDER_VOLUME_CURRENT  =  FIRST_ORD_DBL_PROP+8,   // Classifica pelo volume não executado
   SORT_BY_ORDER_PROFIT_FULL     =  FIRST_ORD_DBL_PROP+9,   // Classifica pelo critério de lucro+comissão+swap
   SORT_BY_ORDER_PRICE_STOP_LIMIT=  FIRST_ORD_DBL_PROP+10,  // Classifica pela ordem limitada quando a ordem StopLimit é ativada
   //--- Classifica pelas propriedades do tipo string
   SORT_BY_ORDER_SYMBOL          =  FIRST_ORD_STR_PROP,     // Classifica pelo símbolo
   SORT_BY_ORDER_COMMENT         =  FIRST_ORD_STR_PROP+1,   // Classifica pelo comentário
   SORT_BY_ORDER_EXT_ID          =  FIRST_ORD_STR_PROP+2    // Classifica pelo ID da ordem em um sistema de negociação externo
  };
//+------------------------------------------------------------------+

Já que atualmente nós estamos editando os arquivos Defines.mqh, vamos adicionar todas as enumerações necessárias para as classes de eventos e coleção de eventos da conta:

//+------------------------------------------------------------------+
//| Estado do evento                                                 |
//+------------------------------------------------------------------+
enum ENUM_EVENT_STATUS
  {
   EVENT_STATUS_MARKET_POSITION,                            // Evento de posição (abertura, abertura parcial, encerramento parcial, incremento do volume, reversão)
   EVENT_STATUS_MARKET_PENDING,                             // Evento de ordem pendente (colocação)
   EVENT_STATUS_HISTORY_PENDING,                            // Evento do histórico de ordem pendente (remoção)
   EVENT_STATUS_HISTORY_POSITION,                           // Evento do histórico de posição (encerramento)
   EVENT_STATUS_BALANCE,                                    // Evento de operação de saldo (saldo acumulado, retirada de fundos e eventos da enumeração ENUM_DEAL_TYPE)
  };
//+------------------------------------------------------------------+
//| Motivo do evento                                                 |
//+------------------------------------------------------------------+
enum ENUM_EVENT_REASON
  {
   EVENT_REASON_ACTIVATED_PENDING               =  0,       // Ativação da ordem pendente
   EVENT_REASON_ACTIVATED_PENDING_PARTIALLY     =  1,       // Ativação parcial da ordem pendente
   EVENT_REASON_CANCEL                          =  2,       // Cancelamento
   EVENT_REASON_EXPIRED                         =  3,       // Expiração da ordem
   EVENT_REASON_DONE                            =  4,       // Solicitação executada por completo
   EVENT_REASON_DONE_PARTIALLY                  =  5,       // Solicitação executada parcialmente
   EVENT_REASON_DONE_SL                         =  6,       // Encerramento por StopLoss
   EVENT_REASON_DONE_SL_PARTIALLY               =  7,       // Encerramento parcial por StopLoss
   EVENT_REASON_DONE_TP                         =  8,       // Encerramento por TakeProfit
   EVENT_REASON_DONE_TP_PARTIALLY               =  9,       // Encerramento parcial por TakeProfit
   EVENT_REASON_DONE_BY_POS                     =  10,      // Encerramento por uma posição oposta
   EVENT_REASON_DONE_PARTIALLY_BY_POS           =  11,      // Encerramento parcial por uma posição oposta
   EVENT_REASON_DONE_BY_POS_PARTIALLY           =  12,      // Encerramento uma posição oposta pelo volume parcial
   EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY =  13,      // Encerramento parcial de uma posição oposta pelo volume parcial
   //--- Constantes relacionadas ao tipo de negócio DEAL_TYPE_BALANCE da enumeração ENUM_DEAL_TYPE
   EVENT_REASON_BALANCE_REFILL                  =  14,      // Recarga de saldo
   EVENT_REASON_BALANCE_WITHDRAWAL              =  15,      // Saque de fundos da conta
   //--- A lista de constantes é relevante para a TRADE_EVENT_ACCOUNT_CREDIT da enumeração ENUM_TRADE_EVENT e alterada para +13 em relação a ENUM_DEAL_TYPE (EVENT_REASON_ACCOUNT_CREDIT-3)
   EVENT_REASON_ACCOUNT_CREDIT                  =  16,      // Crédito acumulado
   EVENT_REASON_ACCOUNT_CHARGE                  =  17,      // Cobranças adicionais
   EVENT_REASON_ACCOUNT_CORRECTION              =  18,      // Correção de entrada
   EVENT_REASON_ACCOUNT_BONUS                   =  19,      // Bônus acumulado
   EVENT_REASON_ACCOUNT_COMISSION               =  20,      // Comissões adicionais
   EVENT_REASON_ACCOUNT_COMISSION_DAILY         =  21,      // Comissão cobrada no fim da negociação do dia
   EVENT_REASON_ACCOUNT_COMISSION_MONTHLY       =  22,      // Comissão cobrada no fim da negociação do mês
   EVENT_REASON_ACCOUNT_COMISSION_AGENT_DAILY   =  23,      // Comissão do agente cobrada no fim da negociação do dia
   EVENT_REASON_ACCOUNT_COMISSION_AGENT_MONTHLY =  24,      // Comissão do agente cobrada no fim da negociação do mês
   EVENT_REASON_ACCOUNT_INTEREST                =  25,      // juros acumulado sobre o saldo livre
   EVENT_REASON_BUY_CANCELLED                   =  26,      // Compra cancelada
   EVENT_REASON_SELL_CANCELLED                  =  27,      // Venda cancelada
   EVENT_REASON_DIVIDENT                        =  28,      // Dividendos acumulados
   EVENT_REASON_DIVIDENT_FRANKED                =  29,      // Dividendos não tributáveis
   EVENT_REASON_TAX                             =  30       // Imposto
  };
#define REASON_EVENT_SHIFT    (EVENT_REASON_ACCOUNT_CREDIT-3)
//+------------------------------------------------------------------+
//| Propriedades de evento do tipo inteiro                           |
//+------------------------------------------------------------------+
enum ENUM_EVENT_PROP_INTEGER
  {
   EVENT_PROP_TYPE_EVENT = 0,                               // Tipo do evento da conta de negociação (da enumeração ENUM_TRADE_EVENT)
   EVENT_PROP_TIME_EVENT,                                   // Hora do evento em milissegundos
   EVENT_PROP_STATUS_EVENT,                                 // Estado do evento (da enumeração ENUM_EVENT_STATUS)
   EVENT_PROP_REASON_EVENT,                                 // Motivo do evento (da enumeração ENUM_EVENT_REASON)
   EVENT_PROP_TYPE_DEAL_EVENT,                              // Tipo do evento de negócio
   EVENT_PROP_TICKET_DEAL_EVENT,                            // Ticket do evento de negócio
   EVENT_PROP_TYPE_ORDER_EVENT,                             // Tipo de uma ordem, com base em qual evento de negociação está aberto (a ordem da última posição)
   EVENT_PROP_TICKET_ORDER_EVENT,                           // Ticket de uma ordem, com base em qual evento de negociação está aberto (a ordem da última posição)
   EVENT_PROP_TIME_ORDER_POSITION,                          // Hora de uma ordem, com base em qual evento de posição está aberto (a ordem da primeira posição)
   EVENT_PROP_TYPE_ORDER_POSITION,                          // Tipo de uma ordem, com base em qual evento de posição está aberto (a ordem da primeira posição)
   EVENT_PROP_TICKET_ORDER_POSITION,                        // Ticket de uma ordem, com base em qual evento de posição está aberto (a ordem da primeira posição)
   EVENT_PROP_POSITION_ID,                                  // ID da posição
   EVENT_PROP_POSITION_BY_ID,                               // ID da posição oposta
   EVENT_PROP_MAGIC_ORDER,                                  // Ordem/negócio/número mágico da posição
  }; 
#define EVENT_PROP_INTEGER_TOTAL (14)                       // Número total de propriedades do evento do tipo inteiro
//+------------------------------------------------------------------+
//| Propriedades de evento do tipo real                              |
//+------------------------------------------------------------------+
enum ENUM_EVENT_PROP_DOUBLE
  {
   EVENT_PROP_PRICE_EVENT = (EVENT_PROP_INTEGER_TOTAL),     // Preço de um evento que ocorreu
   EVENT_PROP_PRICE_OPEN,                                   // Ordem/negócio/preço de abertura da posição
   EVENT_PROP_PRICE_CLOSE,                                  // Ordem/negócio/preço de encerramento da posição
   EVENT_PROP_PRICE_SL,                                     // Ordem StopLoss/negócio/preço da posição
   EVENT_PROP_PRICE_TP,                                     // Ordem TakeProfit/negócio/preço da posição
   EVENT_PROP_VOLUME_INITIAL,                               // Volume solicitado
   EVENT_PROP_VOLUME_EXECUTED,                              // Volume executado
   EVENT_PROP_VOLUME_CURRENT,                               // Volume restante
   EVENT_PROP_PROFIT                                        // Lucro
  };
#define EVENT_PROP_DOUBLE_TOTAL  (9)                        // Número total de propriedades do evento do tipo real 
//+------------------------------------------------------------------+
//| Propriedades de evento do tipo string                            |
//+------------------------------------------------------------------+
enum ENUM_EVENT_PROP_STRING
  {
   EVENT_PROP_SYMBOL = (EVENT_PROP_INTEGER_TOTAL+EVENT_PROP_DOUBLE_TOTAL), // Símbolo da ordem
  };
#define EVENT_PROP_STRING_TOTAL     (1)                     // Número total de propriedades de evento do tipo string
//+------------------------------------------------------------------+
//| Possíveis critérios de ordenação de eventos                      |
//+------------------------------------------------------------------+
#define FIRST_EVN_DBL_PROP       (EVENT_PROP_INTEGER_TOTAL)
#define FIRST_EVN_STR_PROP       (EVENT_PROP_INTEGER_TOTAL+EVENT_PROP_DOUBLE_TOTAL)
enum ENUM_SORT_EVENTS_MODE
  {
   //--- Classifica por propriedades do tipo inteiro
   SORT_BY_EVENT_TYPE_EVENT            = 0,                    // Classifica pelo tipo do evento
   SORT_BY_EVENT_TIME_EVENT            = 1,                    // Classifica pela hora do evento
   SORT_BY_EVENT_STATUS_EVENT          = 2,                    // Classifica pelo estado do evento(da enumeração ENUM_EVENT_STATUS)
   SORT_BY_EVENT_REASON_EVENT          = 3,                    // Classifica pelo estado do evento (da enumeração ENUM_EVENT_REASON)
   SORT_BY_EVENT_TYPE_DEAL_EVENT       = 4,                    // Classifica pelo tipo do evento de negociação
   SORT_BY_EVENT_TICKET_DEAL_EVENT     = 5,                    // Classifica pelo ticket do evento de negociação
   SORT_BY_EVENT_TYPE_ORDER_EVENT      = 6,                    // Classifica pelo tipo de uma ordem, com base em qual evento de negociação está aberto (a ordem da última posição)
   SORT_BY_EVENT_TYPE_ORDER_POSITION   = 7,                    // Classifica pelo tipo de uma ordem, com base em qual evento de posição está aberto (a ordem da primeira posição)
   SORT_BY_EVENT_TICKET_ORDER_EVENT    = 8,                    // Classifica pelo ticket de uma ordem, com base em qual posição de negociação está aberto (a ordem da última posição)
   SORT_BY_EVENT_TICKET_ORDER_POSITION = 9,                    // Classifica pelo ticket de uma ordem, com base em qual posição de negociação está aberto (a ordem da última posição)
   SORT_BY_EVENT_POSITION_ID           = 10,                   // Classifica pelo ID da posição
   SORT_BY_EVENT_POSITION_BY_ID        = 11,                   // Classifica pela posição oposta
   SORT_BY_EVENT_MAGIC_ORDER           = 12,                   // Classifica pela Ordem/negócio/número mágico da posição
   SORT_BY_EVENT_TIME_ORDER_POSITION   = 13,                   // Classifica pelo horário de uma ordem, com base em qual posição de negociação está aberto (a ordem da primeira posição)
   //--- Classifica por propriedades do tipo real
   SORT_BY_EVENT_PRICE_EVENT        =  FIRST_EVN_DBL_PROP,     // Classifica pelo preço que um evento ocorreu
   SORT_BY_EVENT_PRICE_OPEN         =  FIRST_EVN_DBL_PROP+1,   // Classifica pelo preço de abertura da posição
   SORT_BY_EVENT_PRICE_CLOSE        =  FIRST_EVN_DBL_PROP+2,   // Classifica pelo preço de encerramento da posição
   SORT_BY_EVENT_PRICE_SL           =  FIRST_EVN_DBL_PROP+3,   // Classifica pelo preço do StopLoss da posição
   SORT_BY_EVENT_PRICE_TP           =  FIRST_EVN_DBL_PROP+4,   // Classifica pelo preço do TakeProfit da posição
   SORT_BY_EVENT_VOLUME_INITIAL     =  FIRST_EVN_DBL_PROP+5,   // Classifica pelo volume inicial
   SORT_BY_EVENT_VOLUME             =  FIRST_EVN_DBL_PROP+6,   // Classifica pelo volume atual
   SORT_BY_EVENT_VOLUME_CURRENT     =  FIRST_EVN_DBL_PROP+7,   // Classifica pelo volume restante
   SORT_BY_EVENT_PROFIT             =  FIRST_EVN_DBL_PROP+8,   // Classifica pelo lucro
   //--- Classifica pelas propriedades do tipo string
   SORT_BY_EVENT_SYMBOL             =  FIRST_EVN_STR_PROP      // Classifica pela ordem/posição/símbolo do negócio
  };
//+------------------------------------------------------------------+

Aqui, nós temos todos os possíveis estados do objeto de evento (semelhante aos estados de ordem descritos no primeiro artigo), motivos de ocorrência de eventos, todas as propriedades de eventos e critérios para ordenar os eventos para a busca pelas propriedades. Tudo isso já é familiar dos artigos anteriores. Nós sempre podemos voltar ao começo e refrescar a memória para esclarecimento.

Além do estado do evento fornecendo um dado geral do evento, o motivo do evento (ENUM_EVENT_REASON) já contém todos os detalhes sobre a origem específica do evento.
Por exemplo, se um evento tiver um estado de posição no mercado (EVENT_STATUS_MARKET_POSITION), o motivo da ocorrência do evento será especificado no campo do objeto EVENT_PROP_REASON_EVENT. Pode ser a execução de uma ordem pendente (EVENT_REASON_ACTIVATED_PENDING) ou a abertura de uma posição por uma ordem à mercado (EVENT_REASON_DONE). Além disso, as seguintes nuances também são consideradas: se uma posição for aberta parcialmente (nem todo o volume da ordem pendente ou à mercado foi executado), o motivo do evento é EVENT_REASON_ACTIVATED_PENDING_PARTIALLY ou EVENT_REASON_DONE_PARTIALLY etc.
Assim, um objeto do evento contém os dados inteiros do evento e uma ordem que o acionou. Além disso, os eventos históricos fornecem os dados sobre as duas ordens — a primeira ordem da posição e a ordem de encerramento da posição.
Assim, os dados sobre as ordens, negócios e posição no objeto de evento nos permitem monitorar toda a cadeia de eventos da posição dentro de toda o histórico de sua existência — da abertura ao encerramento.

As constantes de enumeração ENUM_EVENT_REASON estão localizadas e numeradas para que, quando o estado do evento for "deal", o tipo de negócio caia na enumeração ENUM_DEAL_TYPE no caso do tipo de negócio exceder o DEAL_TYPE_SELL. Assim, nós acabamos com os tipos de operações de saldo. A descrição da operação de saldo é enviada ao evento de motivo ao definir o tipo de negócio na classe preparada para a criação.
O mudança a ser adicionada ao tipo de negócio é calculado na substituição de macro #define REASON_EVENT_SHIFT. É necessário definir o tipo de operação de saldo na enumeração ENUM_EVENT_REASON.

Vamos adicionar as funções que retornam as descrições dos tipos de ordem, posição e negócio, bem como a função retornando o nome da posição dependendo do tipo da ordem que abriu ela. Todas as funções são adicionadas ao arquivo DELib.mqh localizado na biblioteca Services. Isso permite a saída conveniente das ordens, posições e ofertas.

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

Ao testar a classe de coleta de eventos, um problema muito desagradável foi descoberto: ao criar as listas de ordens e negócios no terminal usando a HistorySelect() e o acesso subsequente aos novos elementos das listas, eu descobri que as ordens não são listados pela ordem de ocorrência do evento, mas pelo tempo de colocação delas. Deixe-me explicar:

  1. abrir uma posição,
  2. colocar uma ordem pendente imediatamente
  3. encerrar parte de uma posição,
  4. aguardar até que uma ordem pendente seja ativada

Espera-se que a sequência dos eventos no histórico seja a seguinte:
abrir uma posição, colocar uma ordem, encerramento parcial, ativação da ordem — na ordem de conduzir as operações ao longo do tempo. Mas a sequência de eventos na ordem comum e no histórico de negócios é a seguinte:

  1. abrir uma posição
  2. colocar uma ordem
  3. ativação da ordem
  4. encerramento parcial

Em outras palavras, os históricos de ordens e negócios vivem suas próprias vidas dentro do terminal e não se correlacionam entre si, o que também é razoável, já que são duas listas com seus próprias históricos.

A classe de coleções de ordens e negócios é feita de tal forma que ao alterar qualquer uma das listas (ordens ou negócios), o último evento da conta é lido para não escanear constantemente o histórico, que é muito caro. Mas, considerando o que foi exposto acima e ao conduzir as operações de negociação, nós não monitoramos a sequência de ações. Nós simplesmente colocamos uma ordem e esperamos pela sua ativação. Depois de abrir uma posição, nós trabalhamos exclusivamente com ela. Nesse caso, todos os eventos devem estar localizados na ordem necessária, permitindo o seu monitoramento. No entanto, isso é insuficiente. Nós precisamos trabalhar em qualquer sequência, e o programa deve ser capaz de encontrar o evento certo e apontá-lo com precisão.

Com base no exposto acima, eu melhorei a classe de coleções do histórico de ordens e eventos. Agora, se um evento fora de ordem for exibido, a classe localizará a ordem necessária, criará seu objeto e o colocará na lista para ser o último, de modo que a classe de coleção de eventos sempre possa definir com precisão o último evento ocorrido.

Para implementar o recurso, vamos adicionar três novos métodos para a seção privada do histórico de ordens e da classe de coleção de negócios:

//--- Retorna a flag do objeto da ordem pelo seu tipo e ticket na lista do histórico de ordens e negócios
   bool              IsPresentOrderInList(const ulong order_ticket,const ENUM_ORDER_TYPE type);
//--- Retorna o tipo da ordem e ticket "perdidos"
   ulong             OrderSearch(const int start,ENUM_ORDER_TYPE &order_type);
//--- Cria o objeto da ordem e coloca-o na lista
   bool              CreateNewOrder(const ulong order_ticket,const ENUM_ORDER_TYPE order_type);

e sua implementação além do corpo da classe também.

O método retornando a flag da presença do objeto de ordem na lista pelo seu ticket e tipo:

//+-----------------------------------------------------------------------------+
//| Retorna a flag da presença do objeto de ordem na lista pelo tipo e ticket   |
//+-----------------------------------------------------------------------------+
bool CHistoryCollection::IsPresentOrderInList(const ulong order_ticket,const ENUM_ORDER_TYPE type)
  {
   CArrayObj* list=dynamic_cast<CListObj*>(&this.m_list_all_orders);
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,type,EQUAL);
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,order_ticket,EQUAL);
   return(list.Total()>0);
  }
//+------------------------------------------------------------------+

Cria o ponteiro para a lista usando o typecasting dinâmico (envia a lista CArrayObj para a classe CSelect, enquanto as listas de coleções são do tipo CListObj e são herdadas da CArrayObj)
Deixar apenas as ordens tendo o tipo passado para o método pela entrada.
Deixar apenas as ordens tendo o ticket passado para o método pela entrada.
Se tal ordem existir (a lista é maior que zero), retorne true.

O método retornando um tipo e um método de uma ordem, que não é o último da lista de terminais, mas não está na lista de coleções:

//+------------------------------------------------------------------+
//| Retorna o tipo da ordem e ticket "perdidos"                      |
//+------------------------------------------------------------------+
ulong CHistoryCollection::OrderSearch(const int start,ENUM_ORDER_TYPE &order_type)
  {
   ulong order_ticket=0;
   for(int i=start-1;i>=0;i--)
     {
      ulong ticket=::HistoryOrderGetTicket(i);
      if(ticket==0)
         continue;
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::HistoryOrderGetInteger(ticket,ORDER_TYPE);
      if(this.IsPresentOrderInList(ticket,type))
         continue;
      order_ticket=ticket;
      order_type=type;
     }
   return order_ticket;
  }
//+------------------------------------------------------------------+

O índice da última ordem é passado para a lista de ordens do terminal. Como o índice especifica a ordem já presente na coleção, o o loop de busca deve ser iniciado a partir da ordem anterior na lista (start-1).
Como a ordem necessária geralmente está localizada próximo ao final da lista, procura-se por uma ordem com um ticket e um tipo ausente na coleção no loop do final da lista usando o método IsPresentOrderInList(). Se a ordem estiver presente na coleção, verifica a próxima. Assim que houver uma ordem faltando na coleção, escrevemos o seu ticket e o tipo e devolvemos ao programa que realizou a chamada. O ticket é retornado pelo resultado do método, enquanto o tipo é retornado na variável através do link.

Já que agora nós precisamos criar os objetos de ordem em vários lugares dentro da classe (ao definir uma nova ordem e ao procurar por uma "perdida"), vamos fazer um método separado da criação de um objeto de ordem e colocá-lo na lista de coleções:

//+------------------------------------------------------------------+
//| Cria o objeto da ordem e coloca-o na lista                       |
//+------------------------------------------------------------------+
bool CHistoryCollection::CreateNewOrder(const ulong order_ticket,const ENUM_ORDER_TYPE order_type)
  {
   COrder* order=NULL;
   if(order_type==ORDER_TYPE_BUY)
     {
      order=new CHistoryOrder(order_ticket);
      if(order==NULL)
         return false;
     }
   else if(order_type==ORDER_TYPE_BUY_LIMIT)
     {
      order=new CHistoryPending(order_ticket);
      if(order==NULL)
         return false;
     }
   else if(order_type==ORDER_TYPE_BUY_STOP)
     {
      order=new CHistoryPending(order_ticket);
      if(order==NULL)
         return false;
     }
   else if(order_type==ORDER_TYPE_SELL)
     {
      order=new CHistoryOrder(order_ticket);
      if(order==NULL)
         return false;
     }
   else if(order_type==ORDER_TYPE_SELL_LIMIT)
     {
      order=new CHistoryPending(order_ticket);
      if(order==NULL)
         return false;
     }
   else if(order_type==ORDER_TYPE_SELL_STOP)
     {
      order=new CHistoryPending(order_ticket);
      if(order==NULL)
         return false;
     }
#ifdef __MQL5__
   else if(order_type==ORDER_TYPE_BUY_STOP_LIMIT)
     {
      order=new CHistoryPending(order_ticket);
      if(order==NULL)
         return false;
     }
   else if(order_type==ORDER_TYPE_SELL_STOP_LIMIT)
     {
      order=new CHistoryPending(order_ticket);
      if(order==NULL)
         return false;
     }
   else if(order_type==ORDER_TYPE_CLOSE_BY)
     {
      order=new CHistoryOrder(order_ticket);
      if(order==NULL)
         return false;
     }
#endif 
   if(this.m_list_all_orders.InsertSort(order))
      return true;
   else
     {
      delete order;
      return false;
     }
   return false;
  }
//+------------------------------------------------------------------+

Aqui tudo é simples e claro: o método recebe o ticket da ordem e o seu tipo, e um novo objeto de ordem é criado dependendo do tipo da ordem. Se o objeto não puder ser criado, é retornado false. Se o objeto for criado com sucesso, ele é colocado na coleção e é retornado true. Se ele não puder ser colocado na coleção, um objeto recém-criado será removido e é retornando false.

Vamos mudar o método Refresh() da classe de coleção, já que deve-se processar a "perda" de uma ordem necessária:

//+------------------------------------------------------------------+
//| Atualiza a lista de ordens e negócios                            |
//+------------------------------------------------------------------+
void CHistoryCollection::Refresh(void)
  {
#ifdef __MQL4__
   int total=::OrdersHistoryTotal(),i=m_index_order;
   for(; i<total; i++)
     {
      if(!::OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)) continue;
      ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)::OrderType();
      //--- Posições fechadas e operações de saldo/crédito
      if(order_type<ORDER_TYPE_BUY_LIMIT || order_type>ORDER_TYPE_SELL_STOP)
        {
         CHistoryOrder *order=new CHistoryOrder(::OrderTicket());
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
      else
        {
         //--- Ordens pendentes removidas
         CHistoryPending *order=new CHistoryPending(::OrderTicket());
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))this.m_list_all_orders.Type()
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
     }
//---
   int delta_order=i-m_index_order;
   this.m_index_order=i;
   this.m_delta_order=delta_order;
   this.m_is_trade_event=(this.m_delta_order!=0 ? true : false);
//--- __MQL5__
#else 
   if(!::HistorySelect(0,END_TIME)) return;
//--- Ordens
   int total_orders=::HistoryOrdersTotal(),i=m_index_order;
   for(; i<total_orders; i++)
     {
      ulong order_ticket=::HistoryOrderGetTicket(i);
      if(order_ticket==0) continue;
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::HistoryOrderGetInteger(order_ticket,ORDER_TYPE);
      if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL || type==ORDER_TYPE_CLOSE_BY)
        {
         //--- Se não houver uma ordem desse tipo e com este ticket na lista, cria um objeto de ordem e adiciona-o à lista
         if(!this.IsPresentOrderInList(order_ticket,type))
           {
            if(!this.CreateNewOrder(order_ticket,type))
               ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to list"));
           }
         //--- Essa ordem já está presente na lista, o que significa que a ordem necessária não é a última da lista do histórico. Vamos encontrá-la
         else
           {
            ENUM_ORDER_TYPE type_lost=WRONG_VALUE;
            ulong ticket_lost=this.OrderSearch(i,type_lost);
            if(ticket_lost>0 && !this.CreateNewOrder(ticket_lost,type_lost))
               ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to list"));
           }
        }
      else
        {
         //--- Se não houver uma ordem pendente deste tipo e com este ticket na lista, cria um objeto de ordem e adiciona-o à lista
         if(!this.IsPresentOrderInList(order_ticket,type))
           {
            if(!this.CreateNewOrder(order_ticket,type))
               ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to list"));
           }
         //--- Essa ordem já está presente na lista, o que significa que a ordem necessária não é a última da lista do histórico. Vamos encontrá-la
         else
           {
            ENUM_ORDER_TYPE type_lost=WRONG_VALUE;
            ulong ticket_lost=this.OrderSearch(i,type_lost);
            if(ticket_lost>0 && !this.CreateNewOrder(ticket_lost,type_lost))
               ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to list"));
           }
        }
     }
//--- salva o índice da última ordem adicionada e a diferença em comparação com a verificação anterior
   int delta_order=i-this.m_index_order;
   this.m_index_order=i;
   this.m_delta_order=delta_order;

//--- Negócios
   int total_deals=::HistoryDealsTotal(),j=m_index_deal;
   for(; j<total_deals; j++)
     {
      ulong deal_ticket=::HistoryDealGetTicket(j);
      if(deal_ticket==0) continue;
      CHistoryDeal *deal=new CHistoryDeal(deal_ticket);
      if(deal==NULL) continue;
      if(!this.m_list_all_orders.InsertSort(deal))
        {
         ::Print(DFUN,TextByLanguage("Не удалось добавить сделку в список","Could not add deal to list"));
         delete deal;
        }
     }
//--- salve o índice do último negócio adicionado e a diferença em comparação com a verificação anterior
   int delta_deal=j-this.m_index_deal;
   this.m_index_deal=j;
   this.m_delta_deal=delta_deal;
//--- Define a nova flag de evento no histórico
   this.m_is_trade_event=(this.m_delta_order+this.m_delta_deal);
#endif 
  }
//+------------------------------------------------------------------+

O bloco de tratamento de novas ordens para a MQL5 foi alterado no método. Todas as mudanças implementadas são descritas por comentários e destacados no texto da listagem.

Vamos adicionar a definição do método para procurar por ordens semelhantes na seção pública da classe COrder:

//--- Compara COrder por todas as propriedades (para procurar por objetos de evento iguais)
   bool              IsEqual(COrder* compared_order) const;

e sua implementação além do corpo da classe também:

//+------------------------------------------------------------------+
//| Compara os objetos da COrder por todas as propriedades           |
//+------------------------------------------------------------------+
bool COrder::IsEqual(COrder *compared_order) const
  {
   int beg=0, end=ORDER_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_ORDER_PROP_INTEGER prop=(ENUM_ORDER_PROP_INTEGER)i;
      if(this.GetProperty(prop)!=compared_order.GetProperty(prop)) return false; 
     }
   beg=end; end+=ORDER_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_ORDER_PROP_DOUBLE prop=(ENUM_ORDER_PROP_DOUBLE)i;
      if(this.GetProperty(prop)!=compared_order.GetProperty(prop)) return false; 
     }
   beg=end; end+=ORDER_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_ORDER_PROP_STRING prop=(ENUM_ORDER_PROP_STRING)i;
      if(this.GetProperty(prop)!=compared_order.GetProperty(prop)) return false; 
     }
   return true;
  }
//+------------------------------------------------------------------+

O método passa por todas as propriedades do objeto de ordem atual e pela ordem comparada passada para o método pelo ponteiro em um loop.
Assim que qualquer uma das propriedades da ordem atual não for igual à mesma propriedade da ordem comparada, é retornado false significando que as ordens não são iguais.


Classes de eventos

O estágio preparatório está completo. Vamos começar a criar as classes de objetos de eventos.

Nós faremos exatamente o mesmo que ao criar classes de ordens. Nós vamos desenvolver uma classe básica de eventos e cinco classes descendentes descritas por seus estados:

  • evento de abertura de posição,
  • evento de encerramento da posição,
  • evento de colocação da ordem pendente,
  • evento de remoção da ordem pendente,
  • evento de operação de saldo

Criamos uma nova classe CEvent, herdada da classe base CObject, na pasta Events que foi criada anteriormente no diretório da biblioteca Objects. No modelo de classe recém-criado, defina as inclusões necessárias do arquivo de funções de serviço, ordene as classes de coleção, bem como os membros e métodos privados e protegidos da classe:

//+------------------------------------------------------------------+
//|                                                        Event.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. | 
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessário para mql4
//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include <Object.mqh>
#include "\..\..\Services\DELib.mqh"
#include "..\..\Collections\HistoryCollection.mqh"
#include "..\..\Collections\MarketCollection.mqh"
//+------------------------------------------------------------------+
//| Classe de evento abstrata                                        |
//+------------------------------------------------------------------+
class CEvent : public CObject
  {
private:
   int               m_event_code;                                   // Código do evento
//--- Retorna o índice do array, As propriedades do evento do tipo (1) double e (2) string estão localizadas em
   int               IndexProp(ENUM_EVENT_PROP_DOUBLE property)const { return(int)property-EVENT_PROP_INTEGER_TOTAL;                         }
   int               IndexProp(ENUM_EVENT_PROP_STRING property)const { return(int)property-EVENT_PROP_INTEGER_TOTAL-EVENT_PROP_DOUBLE_TOTAL; }
protected:
   ENUM_TRADE_EVENT  m_trade_event;                                  // Evento de negociação
   long              m_chart_id;                                     // ID do gráfico do programa de controle
   int               m_digits_acc;                                   // Número de casas decimais da moeda da conta
   long              m_long_prop[EVENT_PROP_INTEGER_TOTAL];          // Proprieades do evento do tipo inteiro
   double            m_double_prop[EVENT_PROP_DOUBLE_TOTAL];         // Propriedades do evento do tipo real
   string            m_string_prop[EVENT_PROP_STRING_TOTAL];         // Propriedades do evento do tipo string
//--- retorna a presença da flag no evento de negociação
   bool              IsPresentEventFlag(const int event_code)  const { return (this.m_event_code & event_code)==event_code;            }

   //--- Construtor paramétrico protegido
                     CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket);
public:
//--- Construtor padrão
                     CEvent(void){;}
 
//--- Define as propriedades do tipo inteiro, (2) real e (3) string do evento
   void              SetProperty(ENUM_EVENT_PROP_INTEGER property,long value) { this.m_long_prop[property]=value;                      }
   void              SetProperty(ENUM_EVENT_PROP_DOUBLE property,double value){ this.m_double_prop[this.IndexProp(property)]=value;    }
   void              SetProperty(ENUM_EVENT_PROP_STRING property,string value){ this.m_string_prop[this.IndexProp(property)]=value;    }
//--- Retorna as propriedades do evento do tipo (1) inteiro, (2) real e (3) string do array de propriedades
   long              GetProperty(ENUM_EVENT_PROP_INTEGER property)      const { return this.m_long_prop[property];                     }
   double            GetProperty(ENUM_EVENT_PROP_DOUBLE property)       const { return this.m_double_prop[this.IndexProp(property)];   }
   string            GetProperty(ENUM_EVENT_PROP_STRING property)       const { return this.m_string_prop[this.IndexProp(property)];   }

//--- Retorna a flag do evento que suporta a propriedade
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property)        { return true; }
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property)         { return true; }
   virtual bool      SupportProperty(ENUM_EVENT_PROP_STRING property)         { return true; }

//--- Define o ID do gráfico do programa de controle
   void              SetChartID(const long id)                                { this.m_chart_id=id;                                    }
//--- Decodifica o código do evento e configura o evento de negociação, (2) retorna o evento de negociação
   void              SetTypeEvent(void);
   ENUM_TRADE_EVENT  TradeEvent(void)                                   const { return this.m_trade_event;                             }
//--- Envia o evento para o gráfico (implementação em classes descendentes)
   virtual void      SendEvent(void) {;}

//--- Compara os objetos CEvent por uma propriedade especificada (para classificar as listas por uma propriedade de objeto de evento especificada)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Compara os objetos CEvent por todas as propriedades (para procurar por objetos de evento iguais)
   bool              IsEqual(CEvent* compared_event) const;
//+--------------------------------------------------------------------+
//| Métodos de acesso simplificado às propriedades do objeto de evento |
//+--------------------------------------------------------------------+
//--- Retorna o (1) tipo do evento, (2) horário do evento em milissegundos, (3) estado do evento, (4) motivo do evento, (5) tipo de negócio, (6) ticket do negócio, 
//--- (7) tipo da ordem, com base no negócio que foi executado, (8) tipo da ordem que abriu uma posição, (9) ticket da última ordem que gerou a posição, 
//--- (10) ticket da primeira ordem que gerou a posição, (11) ID da posição, (12) ID da posição oposta, (13) número mágico, (14) horário da abertura de posição

   ENUM_TRADE_EVENT  TypeEvent(void)                                    const { return (ENUM_TRADE_EVENT)this.GetProperty(EVENT_PROP_TYPE_EVENT);     }
   long              TimeEvent(void)                                    const { return this.GetProperty(EVENT_PROP_TIME_EVENT);                       }
   ENUM_EVENT_STATUS Status(void)                                       const { return (ENUM_EVENT_STATUS)this.GetProperty(EVENT_PROP_STATUS_EVENT);  }
   ENUM_EVENT_REASON Reason(void)                                       const { return (ENUM_EVENT_REASON)this.GetProperty(EVENT_PROP_REASON_EVENT);  }
   long              TypeDeal(void)                                     const { return this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT);                  }
   long              TicketDeal(void)                                   const { return this.GetProperty(EVENT_PROP_TICKET_DEAL_EVENT);                }
   long              TypeOrderEvent(void)                               const { return this.GetProperty(EVENT_PROP_TYPE_ORDER_EVENT);                 }
   long              TypeOrderPosition(void)                            const { return this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION);              }
   long              TicketOrderEvent(void)                             const { return this.GetProperty(EVENT_PROP_TICKET_ORDER_EVENT);               }
   long              TicketOrderPosition(void)                          const { return this.GetProperty(EVENT_PROP_TICKET_ORDER_POSITION);            }
   long              PositionID(void)                                   const { return this.GetProperty(EVENT_PROP_POSITION_ID);                      }
   long              PositionByID(void)                                 const { return this.GetProperty(EVENT_PROP_POSITION_BY_ID);                   }
   long              Magic(void)                                        const { return this.GetProperty(EVENT_PROP_MAGIC_ORDER);                      }
   long              TimePosition(void)                                 const { return this.GetProperty(EVENT_PROP_TIME_ORDER_POSITION);              }
   
//--- Retorna (1) o preço em que o evento ocorreu, (2) preço de abertura, (3) preço de fechamento,
//--- (4) preço do StopLoss, (5) preço do TakeProfit, (6) lucro, (7) volume solicitado, (8), volume executado, (9) volume restante
   double            PriceEvent(void)                                   const { return this.GetProperty(EVENT_PROP_PRICE_EVENT);                      }
   double            PriceOpen(void)                                    const { return this.GetProperty(EVENT_PROP_PRICE_OPEN);                       }
   double            PriceClose(void)                                   const { return this.GetProperty(EVENT_PROP_PRICE_CLOSE);                      }
   double            PriceStopLoss(void)                                const { return this.GetProperty(EVENT_PROP_PRICE_SL);                         }
   double            PriceTakeProfit(void)                              const { return this.GetProperty(EVENT_PROP_PRICE_TP);                         }
   double            Profit(void)                                       const { return this.GetProperty(EVENT_PROP_PROFIT);                           }
   double            VolumeInitial(void)                                const { return this.GetProperty(EVENT_PROP_VOLUME_INITIAL);                   }
   double            VolumeExecuted(void)                               const { return this.GetProperty(EVENT_PROP_VOLUME_EXECUTED);                  }
   double            VolumeCurrent(void)                                const { return this.GetProperty(EVENT_PROP_VOLUME_CURRENT);                   }
   
//--- Retorna um símbolo
   string            Symbol(void)                                       const { return this.GetProperty(EVENT_PROP_SYMBOL);                           }
   
//+------------------------------------------------------------------+
//| Descrições de propriedades de objetos da ordem                   |
//+------------------------------------------------------------------+
//--- Retorna a descrição das propriedades do tipo (1) inteiro, (2) real e (3) string da ordem
   string            GetPropertyDescription(ENUM_EVENT_PROP_INTEGER property);
   string            GetPropertyDescription(ENUM_EVENT_PROP_DOUBLE property);
   string            GetPropertyDescription(ENUM_EVENT_PROP_STRING property);
//--- Retorna o (1) estado e o (2) tipo do evento
   string            StatusDescription(void)          const;
   string            TypeEventDescription(void)       const;
//--- Retorna o nome de uma (1) ordem/posição/negócio, (2) ordem pai, (3) posição
   string            TypeOrderDescription(void)       const;
   string            TypeOrderBasedDescription(void)  const;
   string            TypePositionDescription(void)    const;
//--- Retorna o nome do motivo do negócio/ordem/posição
   string            ReasonDescription(void)          const;

//--- Exibe (1) descrição das propriedades da ordem (full_prop=true - todas as propriedades, false - somente as suportadas),
//--- (2) mensagem de evento curta (implementação nas classes descendentes) no diário
   void              Print(const bool full_prop=false);
   virtual void      PrintShort(void) {;}
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CEvent::CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket) : m_event_code(event_code)
  {
   this.m_long_prop[EVENT_PROP_STATUS_EVENT]       =  event_status;
   this.m_long_prop[EVENT_PROP_TICKET_ORDER_EVENT] =  (long)ticket;
   this.m_digits_acc=(int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS);
   this.m_chart_id=::ChartID();
  }
//+------------------------------------------------------------------+

O construtor recebe o estado do evento, código de evento de negociação e o ticket da ordem ou negócio que desencadeou o evento.

Aqui, quase tudo é semelhante a um construtor protegido da classe COrder considerada anteriormente na primeira parte da descrição da biblioteca.

A diferença é que apenas duas propriedades do evento são preenchidas no construtor protegido da classe. Estes são o estado do evento e o ticket de uma ordem/negócio que acionou o evento. O tipo de evento é detectado e salvo no método da classe SetTypeEvent() com base no código de evento passado para o construtor. Todas as outras propriedades de evento são detectadas a partir do estado de ordens e negócios envolvidas no evento e definidas pelos métodos de classe correspondentes separadamente. Isso é feito porque os eventos devem ser detectados na classe de coleção de eventos, com o método definindo todas as propriedades para um evento recém-criado.

Nós consideramos o código do evento (m_event_code), bem como seu preenchimento e interpretação na quarta parte da descrição da biblioteca. Nós o movemos da classe CEngine já que ele foi colocado na classe base da biblioteca temporariamente para verificar o funcionamento dos eventos. Agora ele será calculado na classe de coleção de eventos e passado para o construtor da classe ao criar um objeto de evento.
O evento de negociação em si (m_trade_event) é compilado decodificando o código do evento no método SetTypeEvent(), nós já descrevemos o método de decodificação do código de evento no quarto artigo.
Nós precisamos do ID do gráfico do programa de controle ( m_chart_id) para enviar as mensagens personalizadas sobre os eventos para ele.
O número de casas decimais da moeda da conta ( m_digits_acc) é necessário para a exibição correta das mensagens sobre os eventos para o diário.

Os métodos de comparação Compare() e IsEqual() das propriedades de evento do objeto são bastante simples e transparentes. Nós consideramos o método Compare() na primeira parte da descrição da biblioteca. Ele foi semelhante ao método do objeto COrder. Em comparação com o primeiro método que compara dois objetos apenas por uma das propriedades, IsEqual() compara esses dois objetos por todos os campos. Se todos os campos dos dois objetos forem semelhantes (cada propriedade do objeto atual é igual à propriedade apropriada da comparação), os dois objetos são idênticos. O método verifica todas as propriedades dos dois objetos em um loop e retorna false assim que uma discrepância é detectada. Não há sentido em outras verificações, pois uma das propriedades do objeto não é mais igual à mesma propriedade do objeto comparado.

//+------------------------------------------------------------------+
//| Compara os objetos CEvent pela propriedade especificada          |
//+------------------------------------------------------------------+
int CEvent::Compare(const CObject *node,const int mode=0) const
  {
   const CEvent *event_compared=node;
//--- compara as propriedades inteiras de dois eventos
   if(mode<EVENT_PROP_INTEGER_TOTAL)
     {
      long value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_INTEGER)mode);
      long value_current=this.GetProperty((ENUM_EVENT_PROP_INTEGER)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- compara as propriedades inteiras de dois objetos
   if(mode<EVENT_PROP_DOUBLE_TOTAL+EVENT_PROP_INTEGER_TOTAL)
     {
      double value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_DOUBLE)mode);
      double value_current=this.GetProperty((ENUM_EVENT_PROP_DOUBLE)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- compara as propriedades de string de dois objetos
   else if(mode<EVENT_PROP_DOUBLE_TOTAL+EVENT_PROP_INTEGER_TOTAL+EVENT_PROP_STRING_TOTAL)
     {
      string value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_STRING)mode);
      string value_current=this.GetProperty((ENUM_EVENT_PROP_STRING)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
   return 0;
  }
//+------------------------------------------------------------------+
//| Compara os eventos CEvent por todas as propriedades              |
//+------------------------------------------------------------------+
bool CEvent::IsEqual(CEvent *compared_event) const
  {
   int beg=0, end=EVENT_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_INTEGER prop=(ENUM_EVENT_PROP_INTEGER)i;
      if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false; 
     }
   beg=end; end+=EVENT_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_DOUBLE prop=(ENUM_EVENT_PROP_DOUBLE)i;
      if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false; 
     }
   beg=end; end+=EVENT_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_STRING prop=(ENUM_EVENT_PROP_STRING)i;
      if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false; 
     }
   return true;
  }
//+------------------------------------------------------------------+

Vamos dar uma olhada mais de perto no método SetTypeEvent()

Todas as verificações e ações necessárias são definidas diretamente nos comentários do código:

//+------------------------------------------------------------------+
//| Decodifica o código do evento e define um evento de negociação   |
//+------------------------------------------------------------------+
void CEvent::SetTypeEvent(void)
  {
//--- Ordem pendente colocada (verifica a correspondência do código do evento, pois só pode haver uma flag aqui)
   if(this.m_event_code==TRADE_EVENT_FLAG_ORDER_PLASED)
     {
      this.m_trade_event=TRADE_EVENT_PENDING_ORDER_PLASED;
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
//--- Ordem pendente removida (verifica a correspondência do código do evento, pois só pode haver uma flag aqui)
   if(this.m_event_code==TRADE_EVENT_FLAG_ORDER_REMOVED)
     {
      this.m_trade_event=TRADE_EVENT_PENDING_ORDER_REMOVED;
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
//--- Posição aberta (Verifica a presença de várias flags no código do evento)
   if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_OPENED))
     {
      //--- Se a ordem pendente for ativada por um preço
      if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED))
        {
         //--- verifica a flag de encerramento parcial e define o evento de negociação "ordem pendente ativada" ou "ordem pendente parcialmente ativada"
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_PENDING_ORDER_ACTIVATED : TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
      //--- verifica a flag de abertura parcial e define o evento de negociação "Posição aberta" ou "Posição parcialmente aberta"
      this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_OPENED : TRADE_EVENT_POSITION_OPENED_PARTIAL);
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
//--- Posição encerrada (Verifica a presença de várias flags no código do evento)
   if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_CLOSED))
     {
      //--- se uma posição for encerrada por StopLoss
      if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL))
        {
         //--- verifica a flag de encerramento parcial e define o evento de negociação "Posição encerrada por StopLoss" ou "Posição parcialmente encerrada por StopLoss"
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_SL : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
      //--- se uma posição for encerrada por TakeProfit
      else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP))
        {
         //--- verifica a flag de encerramento parcial e define o evento de negociação "Posição encerrada por TakeProfit" ou "Posição parcialmente encerrada por TakeProfit"
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_TP : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
      //--- se uma posição é encerrada por uma oposta
      else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_BY_POS))
        {
         //--- verifica a flag de encerramento parcial e define o evento de negociação "Posição encerrada pelo lado oposto" ou "Posição parcialmente encerrada pelo lado oposto"
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_POS : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
      //--- Se uma posição foi encerrada
      else
        {
         //--- verifica a flag de encerramento parcial e define o evento de negociação "Posição encerrada" ou "Posição parcialmente encerrada"
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED : TRADE_EVENT_POSITION_CLOSED_PARTIAL);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
     }
//--- Operação de saldo na conta (esclarece o evento pelo tipo de negócio)
   if(this.m_event_code==TRADE_EVENT_FLAG_ACCOUNT_BALANCE)
     {
      //--- Inicializa um evento de negociação
      this.m_trade_event=TRADE_EVENT_NO_EVENT;
      //--- Obtém o tipo de negócio
      ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT);
      //--- se o negócio é uma operação de saldo
      if(deal_type==DEAL_TYPE_BALANCE)
        {
        //--- verifica o lucro do negócio e define um evento (depósito ou retirada de fundos)
         this.m_trade_event=(this.GetProperty(EVENT_PROP_PROFIT)>0 ? TRADE_EVENT_ACCOUNT_BALANCE_REFILL : TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL);
        }
      //--- Os tipos de operação de saldo restantes correspondem à enumeração ENUM_DEAL_TYPE, começando com DEAL_TYPE_CREDIT
      else if(deal_type>DEAL_TYPE_BALANCE)
        {
        //--- configura o evento
         this.m_trade_event=(ENUM_TRADE_EVENT)deal_type;
        }
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
  }
//+------------------------------------------------------------------+

Aqui tudo é simples: um código de evento é passado para o método e os sinalizadores de código de evento são verificados. Se o código tiver a flag marcada, o evento de negociação apropriado é definido. Como o código do evento pode ter várias flags, todos as flags possíveis para o evento são verificadas e o tipo de evento é definido a partir de sua combinação. Em seguida, o tipo de evento é adicionado à variável da classe apropriada e é inserido na propriedade do objeto de evento (EVENT_PROP_TYPE_EVENT).

Vamos dar uma olhada nos métodos de classe restantes:

//+------------------------------------------------------------------+
//| Retorna a descrição da propriedade do tipo inteiro de um evento  |
//+------------------------------------------------------------------+
string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_INTEGER property)
  {
   return
     (
      property==EVENT_PROP_TYPE_EVENT              ?  TextByLanguage("Тип события","Event type")+": "+this.TypeEventDescription()                                                       :
      property==EVENT_PROP_TIME_EVENT              ?  TextByLanguage("Время события","Time of event")+": "+TimeMSCtoString(this.GetProperty(property))                                    :
      property==EVENT_PROP_STATUS_EVENT            ?  TextByLanguage("Статус события","Status of event")+": \""+this.StatusDescription()+"\""                                             :
      property==EVENT_PROP_REASON_EVENT            ?  TextByLanguage("Причина события","Reason of event")+": "+this.ReasonDescription()                                                   :
      property==EVENT_PROP_TYPE_DEAL_EVENT         ?  TextByLanguage("Тип сделки","Deal's type")+": "+DealTypeDescription((ENUM_DEAL_TYPE)this.GetProperty(property))                     :
      property==EVENT_PROP_TICKET_DEAL_EVENT       ?  TextByLanguage("Тикет сделки","Deal's ticket")+" #"+(string)this.GetProperty(property)                                              :
      property==EVENT_PROP_TYPE_ORDER_EVENT        ?  TextByLanguage("Тип ордера события","Event's order type")+": "+OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(property))    :
      property==EVENT_PROP_TYPE_ORDER_POSITION     ?  TextByLanguage("Тип ордера позиции","Position's order type")+": "+OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(property)) :
      property==EVENT_PROP_TICKET_ORDER_POSITION   ?  TextByLanguage("Тикет первого ордера позиции","Position's first order ticket")+" #"+(string)this.GetProperty(property)              :
      property==EVENT_PROP_TICKET_ORDER_EVENT      ?  TextByLanguage("Тикет ордера события","Event's order ticket")+" #"+(string)this.GetProperty(property)                               :
      property==EVENT_PROP_POSITION_ID             ?  TextByLanguage("Идентификатор позиции","Position ID")+" #"+(string)this.GetProperty(property)                                       :
      property==EVENT_PROP_POSITION_BY_ID          ?  TextByLanguage("Идентификатор встречной позиции","Opposite position ID")+" #"+(string)this.GetProperty(property)                  :
      property==EVENT_PROP_MAGIC_ORDER             ?  TextByLanguage("Магический номер","Magic number")+": "+(string)this.GetProperty(property)                                           :
      property==EVENT_PROP_TIME_ORDER_POSITION     ?  TextByLanguage("Время открытия позиции","Position open time")+": "+TimeMSCtoString(this.GetProperty(property))                  :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Retorna a descrição da propriedade do tipo real de um evento     |
//+------------------------------------------------------------------+
string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_DOUBLE property)
  {
   int dg=(int)::SymbolInfoInteger(this.GetProperty(EVENT_PROP_SYMBOL),SYMBOL_DIGITS);
   int dgl=(int)DigitsLots(this.GetProperty(EVENT_PROP_SYMBOL));
   return
     (
      property==EVENT_PROP_PRICE_EVENT       ?  TextByLanguage("Цена события","Price at the time of event")+": "+::DoubleToString(this.GetProperty(property),dg) :
      property==EVENT_PROP_PRICE_OPEN        ?  TextByLanguage("Цена открытия","Open price")+": "+::DoubleToString(this.GetProperty(property),dg)                    :
      property==EVENT_PROP_PRICE_CLOSE       ?  TextByLanguage("Цена закрытия","Close price")+": "+::DoubleToString(this.GetProperty(property),dg)                   :
      property==EVENT_PROP_PRICE_SL          ?  TextByLanguage("Цена StopLoss","StopLoss price")+": "+::DoubleToString(this.GetProperty(property),dg)                :
      property==EVENT_PROP_PRICE_TP          ?  TextByLanguage("Цена TakeProfit","TakeProfit price")+": "+::DoubleToString(this.GetProperty(property),dg)            :
      property==EVENT_PROP_VOLUME_INITIAL    ?  TextByLanguage("Начальный объём","Initial volume")+": "+::DoubleToString(this.GetProperty(property),dgl)             :
      property==EVENT_PROP_VOLUME_EXECUTED   ?  TextByLanguage("Исполненный объём","Executed volume")+": "+::DoubleToString(this.GetProperty(property),dgl)          :
      property==EVENT_PROP_VOLUME_CURRENT    ?  TextByLanguage("Оставшийся объём","Remaining volume")+": "+::DoubleToString(this.GetProperty(property),dgl)          :
      property==EVENT_PROP_PROFIT            ?  TextByLanguage("Профит","Profit")+": "+::DoubleToString(this.GetProperty(property),this.m_digits_acc)                :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Retorna a descrição da propriedade do tipo string de um evento   |
//+------------------------------------------------------------------+
string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_STRING property)
  {
   return TextByLanguage("Символ","Symbol")+": \""+this.GetProperty(property)+"\"";
  }
//+------------------------------------------------------------------+
//| Retorna o nome do estado do evento                               |
//+------------------------------------------------------------------+
string CEvent::StatusDescription(void) const
  {
   ENUM_EVENT_STATUS status=(ENUM_EVENT_STATUS)this.GetProperty(EVENT_PROP_STATUS_EVENT);
   return
     (
      status==EVENT_STATUS_MARKET_PENDING    ?  TextByLanguage("Установлен отложенный ордер","Pending order placed") :
      status==EVENT_STATUS_MARKET_POSITION   ?  TextByLanguage("Открыта позиция","Position opened")                 :
      status==EVENT_STATUS_HISTORY_PENDING   ?  TextByLanguage("Удален отложенный ордер","Pending order removed")    :
      status==EVENT_STATUS_HISTORY_POSITION  ?  TextByLanguage("Закрыта позиция","Position closed")                  :
      status==EVENT_STATUS_BALANCE           ?  TextByLanguage("Балансная операция","Balance operation")             :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Retorna o nome do evento de negociação                           |
//+------------------------------------------------------------------+
string CEvent::TypeEventDescription(void) const
  {
   ENUM_TRADE_EVENT event=this.TypeEvent();
   return
     (
      event==TRADE_EVENT_PENDING_ORDER_PLASED            ?  TextByLanguage("Отложенный ордер установлен","Pending order placed")                                  :
      event==TRADE_EVENT_PENDING_ORDER_REMOVED           ?  TextByLanguage("Отложенный ордер удалён","Pending order removed")                                     :
      event==TRADE_EVENT_ACCOUNT_CREDIT                  ?  TextByLanguage("Начисление кредита","Credit")                                                         :
      event==TRADE_EVENT_ACCOUNT_CHARGE                  ?  TextByLanguage("Дополнительные сборы","Additional charge")                                            :
      event==TRADE_EVENT_ACCOUNT_CORRECTION              ?  TextByLanguage("Корректирующая запись","Correction")                                                  :
      event==TRADE_EVENT_ACCOUNT_BONUS                   ?  TextByLanguage("Перечисление бонусов","Bonus")                                                        :
      event==TRADE_EVENT_ACCOUNT_COMISSION               ?  TextByLanguage("Дополнительные комиссии","Additional commission")                                     :
      event==TRADE_EVENT_ACCOUNT_COMISSION_DAILY         ?  TextByLanguage("Комиссия, начисляемая в конце торгового дня","Daily commission")                      :
      event==TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY       ?  TextByLanguage("Комиссия, начисляемая в конце месяца","Monthly commission")                           :
      event==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY   ?  TextByLanguage("Агентская комиссия, начисляемая в конце торгового дня","Daily agent commission")      :
      event==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY ?  TextByLanguage("Агентская комиссия, начисляемая в конце месяца","Monthly agent commission")           :
      event==TRADE_EVENT_ACCOUNT_INTEREST                ?  TextByLanguage("Начисления процентов на свободные средства","Interest rate")                          :
      event==TRADE_EVENT_BUY_CANCELLED                   ?  TextByLanguage("Отмененная сделка покупки","Canceled buy deal")                                       :
      event==TRADE_EVENT_SELL_CANCELLED                  ?  TextByLanguage("Отмененная сделка продажи","Canceled sell deal")                                      :
      event==TRADE_EVENT_DIVIDENT                        ?  TextByLanguage("Начисление дивиденда","Dividend operations")                                          :
      event==TRADE_EVENT_DIVIDENT_FRANKED                ?  TextByLanguage("Начисление франкированного дивиденда","Franked (non-taxable) dividend operations")    :
      event==TRADE_EVENT_TAX                             ?  TextByLanguage("Начисление налога","Tax charges")                                                     :
      event==TRADE_EVENT_ACCOUNT_BALANCE_REFILL          ?  TextByLanguage("Пополнение средств на балансе","Balance refill")                                      :
      event==TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL      ?  TextByLanguage("Снятие средств с баланса","Withdrawals")                                              :
      event==TRADE_EVENT_PENDING_ORDER_ACTIVATED         ?  TextByLanguage("Отложенный ордер активирован ценой","Pending order activated")                        :
      event==TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL ?  TextByLanguage("Отложенный ордер активирован ценой частично","Pending order activated partially")     :
      event==TRADE_EVENT_POSITION_OPENED                 ?  TextByLanguage("Позиция открыта","Position opened")                                                  :
      event==TRADE_EVENT_POSITION_OPENED_PARTIAL         ?  TextByLanguage("Позиция открыта частично","Position opened partially")                               :
      event==TRADE_EVENT_POSITION_CLOSED                 ?  TextByLanguage("Позиция закрыта","Position closed")                                                   :
      event==TRADE_EVENT_POSITION_CLOSED_PARTIAL         ?  TextByLanguage("Позиция закрыта частично","Position closed partially")                                :
      event==TRADE_EVENT_POSITION_CLOSED_BY_POS          ?  TextByLanguage("Позиция закрыта встречной","Position closed by opposite position")                    :
      event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS  ?  TextByLanguage("Позиция закрыта встречной частично","Position closed partially by opposite position") :
      event==TRADE_EVENT_POSITION_CLOSED_BY_SL           ?  TextByLanguage("Позиция закрыта по StopLoss","Position closed by StopLoss")                           :
      event==TRADE_EVENT_POSITION_CLOSED_BY_TP           ?  TextByLanguage("Позиция закрыта по TakeProfit","Position closed by TakeProfit")                       :
      event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL   ?  TextByLanguage("Позиция закрыта частично по StopLoss","Position closed partially by StopLoss")        :
      event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP   ?  TextByLanguage("Позиция закрыта частично по TakeProfit","Position closed partially by TakeProfit")    :
      event==TRADE_EVENT_POSITION_REVERSED               ?  TextByLanguage("Разворот позиции","Position reversal")                                                :
      event==TRADE_EVENT_POSITION_VOLUME_ADD             ?  TextByLanguage("Добавлен объём к позиции","Added volume to position")                                 :
      TextByLanguage("Нет торгового события","No trade event")
     );   
  }
//+------------------------------------------------------------------+
//| Retorna o nome da ordem/posição/negócio                          |
//+------------------------------------------------------------------+
string CEvent::TypeOrderDescription(void) const
  {
   ENUM_EVENT_STATUS status=this.Status();
   return
     (
      status==EVENT_STATUS_MARKET_PENDING  || status==EVENT_STATUS_HISTORY_PENDING  ?  OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_EVENT))      :
      status==EVENT_STATUS_MARKET_POSITION || status==EVENT_STATUS_HISTORY_POSITION ?  PositionTypeDescription((ENUM_POSITION_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT)) :
      status==EVENT_STATUS_BALANCE  ?  DealTypeDescription((ENUM_DEAL_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT))  :  "Unknown"
     );
  }
//+------------------------------------------------------------------+
//| Retorna o nome da ordem pai                                      |
//+------------------------------------------------------------------+
string CEvent::TypeOrderBasedDescription(void) const
  {
   return OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION));
  }
//+------------------------------------------------------------------+
//| Retorna o nome da posição                                        |
//+------------------------------------------------------------------+
string CEvent::TypePositionDescription(void) const
  {
   ENUM_POSITION_TYPE type=PositionTypeByOrderType((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION));
   return PositionTypeDescription(type);
  }
//+------------------------------------------------------------------+
//| Retorna o nome do motivo do negócio/ordem/posição                |
//+------------------------------------------------------------------+
string CEvent::ReasonDescription(void) const
  {
   ENUM_EVENT_REASON reason=this.Reason();
   return 
     (
      reason==EVENT_REASON_ACTIVATED_PENDING                ?  TextByLanguage("Активирован отложенный ордер","Pending order activated")                           :
      reason==EVENT_REASON_ACTIVATED_PENDING_PARTIALLY      ?  TextByLanguage("Частичное срабатывание отложенного ордера","Pending order partially triggered")    :
      reason==EVENT_REASON_CANCEL                           ?  TextByLanguage("Отмена","Canceled")                                                                :
      reason==EVENT_REASON_EXPIRED                          ?  TextByLanguage("Истёк срок действия","Expired")                                                    :
      reason==EVENT_REASON_DONE                             ?  TextByLanguage("Запрос выполнен полностью","Request fully executed")                            :
      reason==EVENT_REASON_DONE_PARTIALLY                   ?  TextByLanguage("Запрос выполнен частично","Request partially executed")                         :
      reason==EVENT_REASON_DONE_SL                          ?  TextByLanguage("закрытие по StopLoss","Close by StopLoss triggered")                               :
      reason==EVENT_REASON_DONE_SL_PARTIALLY                ?  TextByLanguage("Частичное закрытие по StopLoss","Partial close by StopLoss triggered")             :
      reason==EVENT_REASON_DONE_TP                          ?  TextByLanguage("закрытие по TakeProfit","Close by TakeProfit triggered")                           :
      reason==EVENT_REASON_DONE_TP_PARTIALLY                ?  TextByLanguage("Частичное закрытие по TakeProfit","Partial close by TakeProfit triggered")         :
      reason==EVENT_REASON_DONE_BY_POS                      ?  TextByLanguage("Закрытие встречной позицией","Closed by opposite position")                        :
      reason==EVENT_REASON_DONE_PARTIALLY_BY_POS            ?  TextByLanguage("Частичное закрытие встречной позицией","Closed partially by opposite position")    :
      reason==EVENT_REASON_DONE_BY_POS_PARTIALLY            ?  TextByLanguage("Закрытие частью объёма встречной позиции","Closed by incomplete volume of opposite position") :
      reason==EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY  ?  TextByLanguage("Частичное закрытие частью объёма встречной позиции","Closed partially by incomplete volume of opposite position")  :
      reason==EVENT_REASON_BALANCE_REFILL                   ?  TextByLanguage("Пополнение баланса","Balance refill")                                              :
      reason==EVENT_REASON_BALANCE_WITHDRAWAL               ?  TextByLanguage("Снятие средств с баланса","Withdrawals from balance")                          :
      reason==EVENT_REASON_ACCOUNT_CREDIT                   ?  TextByLanguage("Начисление кредита","Credit")                                                      :
      reason==EVENT_REASON_ACCOUNT_CHARGE                   ?  TextByLanguage("Дополнительные сборы","Additional charge")                                         :
      reason==EVENT_REASON_ACCOUNT_CORRECTION               ?  TextByLanguage("Корректирующая запись","Correction")                                               :
      reason==EVENT_REASON_ACCOUNT_BONUS                    ?  TextByLanguage("Перечисление бонусов","Bonus")                                                     :
      reason==EVENT_REASON_ACCOUNT_COMISSION                ?  TextByLanguage("Дополнительные комиссии","Additional commission")                                  :
      reason==EVENT_REASON_ACCOUNT_COMISSION_DAILY          ?  TextByLanguage("Комиссия, начисляемая в конце торгового дня","Daily commission")                   :
      reason==EVENT_REASON_ACCOUNT_COMISSION_MONTHLY        ?  TextByLanguage("Комиссия, начисляемая в конце месяца","Monthly commission")                        :
      reason==EVENT_REASON_ACCOUNT_COMISSION_AGENT_DAILY    ?  TextByLanguage("Агентская комиссия, начисляемая в конце торгового дня","Daily agent commission")   :
      reason==EVENT_REASON_ACCOUNT_COMISSION_AGENT_MONTHLY  ?  TextByLanguage("Агентская комиссия, начисляемая в конце месяца","Monthly agent commission")        :
      reason==EVENT_REASON_ACCOUNT_INTEREST                 ?  TextByLanguage("Начисления процентов на свободные средства","Interest rate")                       :
      reason==EVENT_REASON_BUY_CANCELLED                    ?  TextByLanguage("Отмененная сделка покупки","Canceled buy deal")                                    :
      reason==EVENT_REASON_SELL_CANCELLED                   ?  TextByLanguage("Отмененная сделка продажи","Canceled sell deal")                                   :
      reason==EVENT_REASON_DIVIDENT                         ?  TextByLanguage("Начисление дивиденда","Dividend operations")                                       :
      reason==EVENT_REASON_DIVIDENT_FRANKED                 ?  TextByLanguage("Начисление франкированного дивиденда","Franked (non-taxable) dividend operations") :
      reason==EVENT_REASON_TAX                              ?  TextByLanguage("Начисление налога","Tax charges")                                                  :
      EnumToString(reason)
     );
  }
//+------------------------------------------------------------------+
//| Exibe as propriedades do evento no diário                        |
//+------------------------------------------------------------------+
void CEvent::Print(const bool full_prop=false)
  {
   ::Print("============= ",TextByLanguage("Начало списка параметров события: \"","Beginning of event parameter list: \""),this.StatusDescription(),"\" =============");
   int beg=0, end=EVENT_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_INTEGER prop=(ENUM_EVENT_PROP_INTEGER)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=EVENT_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_DOUBLE prop=(ENUM_EVENT_PROP_DOUBLE)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=EVENT_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_STRING prop=(ENUM_EVENT_PROP_STRING)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("================== ",TextByLanguage("Конец списка параметров: \"","End of parameter list: \""),this.StatusDescription(),"\" ==================\n");
  }
//+------------------------------------------------------------------+

A lógica de todos esses métodos é semelhante à dos métodos de saída de dados de ordem já descritos. Portanto, nós não nos concentraremos neles, pois tudo é bem simples e visualmente compreensível aqui.

A listagem completa da classe de eventos:

//+------------------------------------------------------------------+
//|                                                        Event.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. | 
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessário para mql4
//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include <Object.mqh>
#include "\..\..\Services\DELib.mqh"
#include "..\..\Collections\HistoryCollection.mqh"
#include "..\..\Collections\MarketCollection.mqh"
//+------------------------------------------------------------------+
//| Classe de evento abstrata                                        |
//+------------------------------------------------------------------+
class CEvent : public CObject
  {
private:
   int               m_event_code;                                   // Código de evento
//--- Retorna o índice do array, As propriedades do evento do tipo (1) double e (2) string estão localizadas em
   int               IndexProp(ENUM_EVENT_PROP_DOUBLE property)const { return(int)property-EVENT_PROP_INTEGER_TOTAL;                         }
   int               IndexProp(ENUM_EVENT_PROP_STRING property)const { return(int)property-EVENT_PROP_INTEGER_TOTAL-EVENT_PROP_DOUBLE_TOTAL; }
protected:
   ENUM_TRADE_EVENT  m_trade_event;                                  // Evento de negociação
   long              m_chart_id;                                     // ID do gráfico do programa de controle
   int               m_digits_acc;                                   // Número de casas decimais da moeda da conta
   long              m_long_prop[EVENT_PROP_INTEGER_TOTAL];          // Proprieades do evento do tipo inteiro
   double            m_double_prop[EVENT_PROP_DOUBLE_TOTAL];         // Propriedades do evento do tipo real
   string            m_string_prop[EVENT_PROP_STRING_TOTAL];         // Propriedades do evento do tipo string
//--- retorna a presença da flag no evento de negociação
   bool              IsPresentEventFlag(const int event_code)  const { return (this.m_event_code & event_code)==event_code;            }

   //--- Construtor paramétrico protegido
                     CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket);
public:
//--- Construtor padrão
                     CEvent(void){;}
 
//--- Define as propriedades do tipo inteiro, (2) real e (3) string do evento
   void              SetProperty(ENUM_EVENT_PROP_INTEGER property,long value) { this.m_long_prop[property]=value;                      }
   void              SetProperty(ENUM_EVENT_PROP_DOUBLE property,double value){ this.m_double_prop[this.IndexProp(property)]=value;    }
   void              SetProperty(ENUM_EVENT_PROP_STRING property,string value){ this.m_string_prop[this.IndexProp(property)]=value;    }
//--- Retorna as propriedades do evento do tipo (1) inteiro, (2) real e (3) string do array de propriedades
   long              GetProperty(ENUM_EVENT_PROP_INTEGER property)      const { return this.m_long_prop[property];                     }
   double            GetProperty(ENUM_EVENT_PROP_DOUBLE property)       const { return this.m_double_prop[this.IndexProp(property)];   }
   string            GetProperty(ENUM_EVENT_PROP_STRING property)       const { return this.m_string_prop[this.IndexProp(property)];   }

//--- Retorna a flag do evento que suporta a propriedade
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property)        { return true; }
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property)         { return true; }
   virtual bool      SupportProperty(ENUM_EVENT_PROP_STRING property)         { return true; }

//--- Define o ID do gráfico do programa de controle
   void              SetChartID(const long id)                                { this.m_chart_id=id;                                    }
//--- Decodifica o código do evento e configura o evento de negociação, (2) retorna o evento de negociação
   void              SetTypeEvent(void);
   ENUM_TRADE_EVENT  TradeEvent(void)                                   const { return this.m_trade_event;                             }
//--- Envia o evento para o gráfico (implementação em classes descendentes)
   virtual void      SendEvent(void) {;}

//--- Compara os objetos CEvent por uma propriedade especificada (para classificar as listas por uma propriedade de objeto de evento especificada)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Compara os objetos CEvent por todas as propriedades (para procurar por objetos de evento iguais)
   bool              IsEqual(CEvent* compared_event);
//+--------------------------------------------------------------------+
//| Métodos de acesso simplificado às propriedades do objeto de evento |
//+--------------------------------------------------------------------+
//--- Retorna o (1) tipo do evento, (2) horário do evento em milissegundos, (3) estado do evento, (4) motivo do evento, (5) tipo de negócio, (6) ticket do negócio, 
//--- (7) tipo da ordem, com base no negócio que foi executado, (8) tipo da ordem que abriu uma posição, (9) ticket da última ordem que gerou a posição, 
//--- (10) ticket da primeira ordem que gerou a posição, (11) ID da posição, (12) ID da posição oposta, (13) número mágico, (14) horário da abertura de posição

   ENUM_TRADE_EVENT  TypeEvent(void)                                    const { return (ENUM_TRADE_EVENT)this.GetProperty(EVENT_PROP_TYPE_EVENT);     }
   long              TimeEvent(void)                                    const { return this.GetProperty(EVENT_PROP_TIME_EVENT);                       }
   ENUM_EVENT_STATUS Status(void)                                       const { return (ENUM_EVENT_STATUS)this.GetProperty(EVENT_PROP_STATUS_EVENT);  }
   ENUM_EVENT_REASON Reason(void)                                       const { return (ENUM_EVENT_REASON)this.GetProperty(EVENT_PROP_REASON_EVENT);  }
   long              TypeDeal(void)                                     const { return this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT);                  }
   long              TicketDeal(void)                                   const { return this.GetProperty(EVENT_PROP_TICKET_DEAL_EVENT);                }
   long              TypeOrderEvent(void)                               const { return this.GetProperty(EVENT_PROP_TYPE_ORDER_EVENT);                 }
   long              TypeOrderPosition(void)                            const { return this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION);              }
   long              TicketOrderEvent(void)                             const { return this.GetProperty(EVENT_PROP_TICKET_ORDER_EVENT);               }
   long              TicketOrderPosition(void)                          const { return this.GetProperty(EVENT_PROP_TICKET_ORDER_POSITION);            }
   long              PositionID(void)                                   const { return this.GetProperty(EVENT_PROP_POSITION_ID);                      }
   long              PositionByID(void)                                 const { return this.GetProperty(EVENT_PROP_POSITION_BY_ID);                   }
   long              Magic(void)                                        const { return this.GetProperty(EVENT_PROP_MAGIC_ORDER);                      }
   long              TimePosition(void)                                 const { return this.GetProperty(EVENT_PROP_TIME_ORDER_POSITION);              }
   
//--- Retorna (1) o preço em que o evento ocorreu, (2) preço de abertura, (3) preço de fechamento,
//--- (4) preço do StopLoss, (5) preço do TakeProfit, (6) lucro, (7) volume solicitado, (8), volume executado, (9) volume restante
   double            PriceEvent(void)                                   const { return this.GetProperty(EVENT_PROP_PRICE_EVENT);                      }
   double            PriceOpen(void)                                    const { return this.GetProperty(EVENT_PROP_PRICE_OPEN);                       }
   double            PriceClose(void)                                   const { return this.GetProperty(EVENT_PROP_PRICE_CLOSE);                      }
   double            PriceStopLoss(void)                                const { return this.GetProperty(EVENT_PROP_PRICE_SL);                         }
   double            PriceTakeProfit(void)                              const { return this.GetProperty(EVENT_PROP_PRICE_TP);                         }
   double            Profit(void)                                       const { return this.GetProperty(EVENT_PROP_PROFIT);                           }
   double            VolumeInitial(void)                                const { return this.GetProperty(EVENT_PROP_VOLUME_INITIAL);                   }
   double            VolumeExecuted(void)                               const { return this.GetProperty(EVENT_PROP_VOLUME_EXECUTED);                  }
   double            VolumeCurrent(void)                                const { return this.GetProperty(EVENT_PROP_VOLUME_CURRENT);                   }
   
//--- Retorna um símbolo
   string            Symbol(void)                                       const { return this.GetProperty(EVENT_PROP_SYMBOL);                           }
   
//+------------------------------------------------------------------+
//| Descrições de propriedades de objetos da ordem                   |
//+------------------------------------------------------------------+
//--- Retorna a descrição das propriedades do tipo (1) inteiro, (2) real e (3) string da ordem
   string            GetPropertyDescription(ENUM_EVENT_PROP_INTEGER property);
   string            GetPropertyDescription(ENUM_EVENT_PROP_DOUBLE property);
   string            GetPropertyDescription(ENUM_EVENT_PROP_STRING property);
//--- Retorna o (1) estado e o (2) tipo do evento
   string            StatusDescription(void)          const;
   string            TypeEventDescription(void)       const;
//--- Retorna o nome de uma (1) ordem/posição/negócio, (2) ordem pai, (3) posição
   string            TypeOrderDescription(void)       const;
   string            TypeOrderBasedDescription(void)  const;
   string            TypePositionDescription(void)    const;
//--- Retorna o nome do motivo do negócio/ordem/posição
   string            ReasonDescription(void)          const;

//--- Exibe (1) descrição das propriedades da ordem (full_prop=true - todas as propriedades, false - somente as suportadas),
//--- (2) mensagem de evento curta (implementação nas classes descendentes) no diário
   void              Print(const bool full_prop=false);
   virtual void      PrintShort(void) {;}
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CEvent::CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket) : m_event_code(event_code)
  {
   this.m_long_prop[EVENT_PROP_STATUS_EVENT]       =  event_status;
   this.m_long_prop[EVENT_PROP_TICKET_ORDER_EVENT] =  (long)ticket;
   this.m_digits_acc=(int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS);
   this.m_chart_id=::ChartID();
  }
//+------------------------------------------------------------------+
//| Compara os objetos CEvent pela propriedade especificada          |
//+------------------------------------------------------------------+
int CEvent::Compare(const CObject *node,const int mode=0) const
  {
   const CEvent *event_compared=node;
//--- compara as propriedades inteiras de dois eventos
   if(mode<EVENT_PROP_INTEGER_TOTAL)
     {
      long value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_INTEGER)mode);
      long value_current=this.GetProperty((ENUM_EVENT_PROP_INTEGER)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- compara as propriedades reais de dois eventos
   if(mode<EVENT_PROP_DOUBLE_TOTAL+EVENT_PROP_INTEGER_TOTAL)
     {
      double value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_DOUBLE)mode);
      double value_current=this.GetProperty((ENUM_EVENT_PROP_DOUBLE)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- compara as propriedades de string de dois eventos
   else if(mode<EVENT_PROP_DOUBLE_TOTAL+EVENT_PROP_INTEGER_TOTAL+EVENT_PROP_STRING_TOTAL)
     {
      string value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_STRING)mode);
      string value_current=this.GetProperty((ENUM_EVENT_PROP_STRING)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
   return 0;
  }
//+------------------------------------------------------------------+
//| Compara os objetos CEvent por todas as suas propriedades         |
//+------------------------------------------------------------------+
bool CEvent::IsEqual(CEvent *compared_event)
  {
   int beg=0, end=EVENT_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_INTEGER prop=(ENUM_EVENT_PROP_INTEGER)i;
      if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false; 
     }
   beg=end; end+=EVENT_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_DOUBLE prop=(ENUM_EVENT_PROP_DOUBLE)i;
      if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false; 
     }
   beg=end; end+=EVENT_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_STRING prop=(ENUM_EVENT_PROP_STRING)i;
      if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false; 
     }
//---
   return true;
  }
//+------------------------------------------------------------------+
//| Decodifica o código do evento e define um evento de negociação   |
//+------------------------------------------------------------------+
void CEvent::SetTypeEvent(void)
  {
//--- Ordem pendente colocada (verifica a correspondência do código do evento, pois só pode haver uma flag aqui)
   if(this.m_event_code==TRADE_EVENT_FLAG_ORDER_PLASED)
     {
      this.m_trade_event=TRADE_EVENT_PENDING_ORDER_PLASED;
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
//--- Ordem pendente removida (verifica a correspondência do código do evento, pois só pode haver uma flag aqui)
   if(this.m_event_code==TRADE_EVENT_FLAG_ORDER_REMOVED)
     {
      this.m_trade_event=TRADE_EVENT_PENDING_ORDER_REMOVED;
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
//--- Posição aberta (Verifica a presença de várias flags no código do evento)
   if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_OPENED))
     {
      //--- Se a ordem pendente for ativada por um preço
      if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED))
        {
         //--- verifica a flag de encerramento parcial e define o evento de negociação "ordem pendente ativada" ou "ordem pendente parcialmente ativada"
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_PENDING_ORDER_ACTIVATED : TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
      //--- verifica a flag de abertura parcial e define o evento de negociação "Posição aberta" ou "Posição parcialmente aberta"
      this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_OPENED : TRADE_EVENT_POSITION_OPENED_PARTIAL);
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
//--- Posição encerrada (Verifica a presença de várias flags no código do evento)
   if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_CLOSED))
     {
      //--- se uma posição for encerrada por StopLoss
      if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL))
        {
         //--- verifica a flag de encerramento parcial e define o evento de negociação "Posição encerrada por StopLoss" ou "Posição parcialmente encerrada por StopLoss"
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_SL : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
      //--- se uma posição for encerrada por TakeProfit
      else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP))
        {
         //--- verifica a flag de encerramento parcial e define o evento de negociação "Posição encerrada por TakeProfit" ou "Posição parcialmente encerrada por TakeProfit"
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_TP : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
      //--- se uma posição é encerrada por uma oposta
      else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_BY_POS))
        {
         //--- verifica a flag de encerramento parcial e define o evento de negociação "Posição encerrada pelo lado oposto" ou "Posição parcialmente encerrada pelo lado oposto"
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_POS : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
      //--- Se uma posição foi encerrada
      else
        {
         //--- verifica a flag de encerramento parcial e define o evento de negociação "Posição encerrada" ou "Posição parcialmente encerrada"
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED : TRADE_EVENT_POSITION_CLOSED_PARTIAL);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
     }
//--- Operação de saldo na conta (esclarece o evento pelo tipo de negócio)
   if(this.m_event_code==TRADE_EVENT_FLAG_ACCOUNT_BALANCE)
     {
      //--- Inicializa um evento de negociação
      this.m_trade_event=TRADE_EVENT_NO_EVENT;
      //--- Obtém o tipo de negócio
      ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT);
      //--- se o negócio é uma operação de saldo
      if(deal_type==DEAL_TYPE_BALANCE)
        {
        //--- verifica o lucro do negócio e define um evento (depósito ou retirada de fundos)
         this.m_trade_event=(this.GetProperty(EVENT_PROP_PROFIT)>0 ? TRADE_EVENT_ACCOUNT_BALANCE_REFILL : TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL);
        }
      //--- Os tipos de operação de saldo restantes correspondem à enumeração ENUM_DEAL_TYPE, começando com DEAL_TYPE_CREDIT
      else if(deal_type>DEAL_TYPE_BALANCE)
        {
        //--- configura o evento
         this.m_trade_event=(ENUM_TRADE_EVENT)deal_type;
        }
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
  }
//+------------------------------------------------------------------+
//| Retorna a descrição da propriedade do tipo inteiro de um evento  |
//+------------------------------------------------------------------+
string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_INTEGER property)
  {
   return
     (
      property==EVENT_PROP_TYPE_EVENT              ?  TextByLanguage("Тип события","Event type")+": "+this.TypeEventDescription()                                                       :
      property==EVENT_PROP_TIME_EVENT              ?  TextByLanguage("Время события","Time of event")+": "+TimeMSCtoString(this.GetProperty(property))                                    :
      property==EVENT_PROP_STATUS_EVENT            ?  TextByLanguage("Статус события","Status of event")+": \""+this.StatusDescription()+"\""                                             :
      property==EVENT_PROP_REASON_EVENT            ?  TextByLanguage("Причина события","Reason of event")+": "+this.ReasonDescription()                                                   :
      property==EVENT_PROP_TYPE_DEAL_EVENT         ?  TextByLanguage("Тип сделки","Deal's type")+": "+DealTypeDescription((ENUM_DEAL_TYPE)this.GetProperty(property))                     :
      property==EVENT_PROP_TICKET_DEAL_EVENT       ?  TextByLanguage("Тикет сделки","Deal's ticket")+" #"+(string)this.GetProperty(property)                                              :
      property==EVENT_PROP_TYPE_ORDER_EVENT        ?  TextByLanguage("Тип ордера события","Event's order type")+": "+OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(property))    :
      property==EVENT_PROP_TYPE_ORDER_POSITION     ?  TextByLanguage("Тип ордера позиции","Position's order type")+": "+OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(property)) :
      property==EVENT_PROP_TICKET_ORDER_POSITION   ?  TextByLanguage("Тикет первого ордера позиции","Position's first order ticket")+" #"+(string)this.GetProperty(property)              :
      property==EVENT_PROP_TICKET_ORDER_EVENT      ?  TextByLanguage("Тикет ордера события","Event's order ticket")+" #"+(string)this.GetProperty(property)                               :
      property==EVENT_PROP_POSITION_ID             ?  TextByLanguage("Идентификатор позиции","Position ID")+" #"+(string)this.GetProperty(property)                                       :
      property==EVENT_PROP_POSITION_BY_ID          ?  TextByLanguage("Идентификатор встречной позиции","Opposite position's ID")+" #"+(string)this.GetProperty(property)                  :
      property==EVENT_PROP_MAGIC_ORDER             ?  TextByLanguage("Магический номер","Magic number")+": "+(string)this.GetProperty(property)                                           :
      property==EVENT_PROP_TIME_ORDER_POSITION     ?  TextByLanguage("Время открытия позиции","Position's opened time")+": "+TimeMSCtoString(this.GetProperty(property))                  :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Retorna a descrição da propriedade do tipo real de um evento     |
//+------------------------------------------------------------------+
string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_DOUBLE property)
  {
   int dg=(int)::SymbolInfoInteger(this.GetProperty(EVENT_PROP_SYMBOL),SYMBOL_DIGITS);
   int dgl=(int)DigitsLots(this.GetProperty(EVENT_PROP_SYMBOL));
   return
     (
      property==EVENT_PROP_PRICE_EVENT       ?  TextByLanguage("Цена события","Price at the time of event")+": "+::DoubleToString(this.GetProperty(property),dg) :
      property==EVENT_PROP_PRICE_OPEN        ?  TextByLanguage("Цена открытия","Open price")+": "+::DoubleToString(this.GetProperty(property),dg)                    :
      property==EVENT_PROP_PRICE_CLOSE       ?  TextByLanguage("Цена закрытия","Close price")+": "+::DoubleToString(this.GetProperty(property),dg)                   :
      property==EVENT_PROP_PRICE_SL          ?  TextByLanguage("Цена StopLoss","StopLoss price")+": "+::DoubleToString(this.GetProperty(property),dg)                :
      property==EVENT_PROP_PRICE_TP          ?  TextByLanguage("Цена TakeProfit","TakeProfit price")+": "+::DoubleToString(this.GetProperty(property),dg)            :
      property==EVENT_PROP_VOLUME_INITIAL    ?  TextByLanguage("Начальный объём","Initial volume")+": "+::DoubleToString(this.GetProperty(property),dgl)             :
      property==EVENT_PROP_VOLUME_EXECUTED   ?  TextByLanguage("Исполненный объём","Executed volume")+": "+::DoubleToString(this.GetProperty(property),dgl)          :
      property==EVENT_PROP_VOLUME_CURRENT    ?  TextByLanguage("Оставшийся объём","Remaining volume")+": "+::DoubleToString(this.GetProperty(property),dgl)          :
      property==EVENT_PROP_PROFIT            ?  TextByLanguage("Профит","Profit")+": "+::DoubleToString(this.GetProperty(property),this.m_digits_acc)                :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Retorna a descrição da propriedade do tipo string de um evento   |
//+------------------------------------------------------------------+
string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_STRING property)
  {
   return TextByLanguage("Символ","Symbol")+": \""+this.GetProperty(property)+"\"";
  }
//+------------------------------------------------------------------+
//| Retorna o nome do estado do evento                               |
//+------------------------------------------------------------------+
string CEvent::StatusDescription(void) const
  {
   ENUM_EVENT_STATUS status=(ENUM_EVENT_STATUS)this.GetProperty(EVENT_PROP_STATUS_EVENT);
   return
     (
      status==EVENT_STATUS_MARKET_PENDING    ?  TextByLanguage("Установлен отложенный ордер","Pending order placed") :
      status==EVENT_STATUS_MARKET_POSITION   ?  TextByLanguage("Открыта позиция","Position opened")                 :
      status==EVENT_STATUS_HISTORY_PENDING   ?  TextByLanguage("Удален отложенный ордер","Pending order removed")    :
      status==EVENT_STATUS_HISTORY_POSITION  ?  TextByLanguage("Закрыта позиция","Position closed")                  :
      status==EVENT_STATUS_BALANCE           ?  TextByLanguage("Балансная операция","Balance operation")             :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Retorna o nome do evento de negociação                           |
//+------------------------------------------------------------------+
string CEvent::TypeEventDescription(void) const
  {
   ENUM_TRADE_EVENT event=this.TypeEvent();
   return
     (
      event==TRADE_EVENT_PENDING_ORDER_PLASED            ?  TextByLanguage("Отложенный ордер установлен","Pending order placed")                                  :
      event==TRADE_EVENT_PENDING_ORDER_REMOVED           ?  TextByLanguage("Отложенный ордер удалён","Pending order removed")                                     :
      event==TRADE_EVENT_ACCOUNT_CREDIT                  ?  TextByLanguage("Начисление кредита","Credit")                                                         :
      event==TRADE_EVENT_ACCOUNT_CHARGE                  ?  TextByLanguage("Дополнительные сборы","Additional charge")                                            :
      event==TRADE_EVENT_ACCOUNT_CORRECTION              ?  TextByLanguage("Корректирующая запись","Correction")                                                  :
      event==TRADE_EVENT_ACCOUNT_BONUS                   ?  TextByLanguage("Перечисление бонусов","Bonus")                                                        :
      event==TRADE_EVENT_ACCOUNT_COMISSION               ?  TextByLanguage("Дополнительные комиссии","Additional commission")                                     :
      event==TRADE_EVENT_ACCOUNT_COMISSION_DAILY         ?  TextByLanguage("Комиссия, начисляемая в конце торгового дня","Daily commission")                      :
      event==TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY       ?  TextByLanguage("Комиссия, начисляемая в конце месяца","Monthly commission")                           :
      event==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY   ?  TextByLanguage("Агентская комиссия, начисляемая в конце торгового дня","Daily agent commission")      :
      event==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY ?  TextByLanguage("Агентская комиссия, начисляемая в конце месяца","Monthly agent commission")           :
      event==TRADE_EVENT_ACCOUNT_INTEREST                ?  TextByLanguage("Начисления процентов на свободные средства","Interest rate")                          :
      event==TRADE_EVENT_BUY_CANCELLED                   ?  TextByLanguage("Отмененная сделка покупки","Canceled buy deal")                                       :
      event==TRADE_EVENT_SELL_CANCELLED                  ?  TextByLanguage("Отмененная сделка продажи","Canceled sell deal")                                      :
      event==TRADE_EVENT_DIVIDENT                        ?  TextByLanguage("Начисление дивиденда","Dividend operations")                                          :
      event==TRADE_EVENT_DIVIDENT_FRANKED                ?  TextByLanguage("Начисление франкированного дивиденда","Franked (non-taxable) dividend operations")    :
      event==TRADE_EVENT_TAX                             ?  TextByLanguage("Начисление налога","Tax charges")                                                     :
      event==TRADE_EVENT_ACCOUNT_BALANCE_REFILL          ?  TextByLanguage("Пополнение средств на балансе","Balance refill")                                      :
      event==TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL      ?  TextByLanguage("Снятие средств с баланса","Withdrawals")                                              :
      event==TRADE_EVENT_PENDING_ORDER_ACTIVATED         ?  TextByLanguage("Отложенный ордер активирован ценой","Pending order activated")                        :
      event==TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL ?  TextByLanguage("Отложенный ордер активирован ценой частично","Pending order activated partially")     :
      event==TRADE_EVENT_POSITION_OPENED                 ?  TextByLanguage("Позиция открыта","Position opened")                                                  :
      event==TRADE_EVENT_POSITION_OPENED_PARTIAL         ?  TextByLanguage("Позиция открыта частично","Position opened partially")                               :
      event==TRADE_EVENT_POSITION_CLOSED                 ?  TextByLanguage("Позиция закрыта","Position closed")                                                   :
      event==TRADE_EVENT_POSITION_CLOSED_PARTIAL         ?  TextByLanguage("Позиция закрыта частично","Position closed partially")                                :
      event==TRADE_EVENT_POSITION_CLOSED_BY_POS          ?  TextByLanguage("Позиция закрыта встречной","Position closed by opposite position")                    :
      event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS  ?  TextByLanguage("Позиция закрыта встречной частично","Position closed partially by opposite position") :
      event==TRADE_EVENT_POSITION_CLOSED_BY_SL           ?  TextByLanguage("Позиция закрыта по StopLoss","Position closed by StopLoss")                           :
      event==TRADE_EVENT_POSITION_CLOSED_BY_TP           ?  TextByLanguage("Позиция закрыта по TakeProfit","Position closed by TakeProfit")                       :
      event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL   ?  TextByLanguage("Позиция закрыта частично по StopLoss","Position closed partially by StopLoss")        :
      event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP   ?  TextByLanguage("Позиция закрыта частично по TakeProfit","Position closed partially by TakeProfit")    :
      event==TRADE_EVENT_POSITION_REVERSED               ?  TextByLanguage("Разворот позиции","Position reversal")                                                :
      event==TRADE_EVENT_POSITION_VOLUME_ADD             ?  TextByLanguage("Добавлен объём к позиции","Added volume to position")                                 :
      TextByLanguage("Нет торгового события","No trade event")
     );   
  }
//+------------------------------------------------------------------+
//| Retorna o nome da ordem/posição/negócio                          |
//+------------------------------------------------------------------+
string CEvent::TypeOrderDescription(void) const
  {
   ENUM_EVENT_STATUS status=this.Status();
   return
     (
      status==EVENT_STATUS_MARKET_PENDING  || status==EVENT_STATUS_HISTORY_PENDING  ?  OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_EVENT))      :
      status==EVENT_STATUS_MARKET_POSITION || status==EVENT_STATUS_HISTORY_POSITION ?  PositionTypeDescription((ENUM_POSITION_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT)) :
      status==EVENT_STATUS_BALANCE  ?  DealTypeDescription((ENUM_DEAL_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT))  :  "Unknown"
     );
  }
//+------------------------------------------------------------------+
//| Retorna o nome da ordem pai                                      |
//+------------------------------------------------------------------+
string CEvent::TypeOrderBasedDescription(void) const
  {
   return OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION));
  }
//+------------------------------------------------------------------+
//| Retorna o nome da posição                                        |
//+------------------------------------------------------------------+
string CEvent::TypePositionDescription(void) const
  {
   ENUM_POSITION_TYPE type=PositionTypeByOrderType((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION));
   return PositionTypeDescription(type);
  }
//+------------------------------------------------------------------+
//| Retorna o nome do motivo do negócio/ordem/posição                |
//+------------------------------------------------------------------+
string CEvent::ReasonDescription(void) const
  {
   ENUM_EVENT_REASON reason=this.Reason();
   return 
     (
      reason==EVENT_REASON_ACTIVATED_PENDING                ?  TextByLanguage("Активирован отложенный ордер","Pending order activated")                           :
      reason==EVENT_REASON_ACTIVATED_PENDING_PARTIALLY      ?  TextByLanguage("Частичное срабатывание отложенного ордера","Pending order partially triggered")    :
      reason==EVENT_REASON_CANCEL                           ?  TextByLanguage("Отмена","Canceled")                                                                :
      reason==EVENT_REASON_EXPIRED                          ?  TextByLanguage("Истёк срок действия","Expired")                                                    :
      reason==EVENT_REASON_DONE                             ?  TextByLanguage("Запрос выполнен полностью","Request fully executed")                            :
      reason==EVENT_REASON_DONE_PARTIALLY                   ?  TextByLanguage("Запрос выполнен частично","Request partially executed")                         :
      reason==EVENT_REASON_DONE_SL                          ?  TextByLanguage("закрытие по StopLoss","Close by StopLoss triggered")                               :
      reason==EVENT_REASON_DONE_SL_PARTIALLY                ?  TextByLanguage("Частичное закрытие по StopLoss","Partial close by StopLoss triggered")             :
      reason==EVENT_REASON_DONE_TP                          ?  TextByLanguage("закрытие по TakeProfit","Close by TakeProfit triggered")                           :
      reason==EVENT_REASON_DONE_TP_PARTIALLY                ?  TextByLanguage("Частичное закрытие по TakeProfit","Partial close by TakeProfit triggered")         :
      reason==EVENT_REASON_DONE_BY_POS                      ?  TextByLanguage("Закрытие встречной позицией","Closed by opposite position")                        :
      reason==EVENT_REASON_DONE_PARTIALLY_BY_POS            ?  TextByLanguage("Частичное закрытие встречной позицией","Closed partially by opposite position")    :
      reason==EVENT_REASON_DONE_BY_POS_PARTIALLY            ?  TextByLanguage("Закрытие частью объёма встречной позиции","Closed by incomplete volume of opposite position") :
      reason==EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY  ?  TextByLanguage("Частичное закрытие частью объёма встречной позиции","Closed partially by incomplete volume of opposite position")  :
      reason==EVENT_REASON_BALANCE_REFILL                   ?  TextByLanguage("Пополнение баланса","Balance refill")                                              :
      reason==EVENT_REASON_BALANCE_WITHDRAWAL               ?  TextByLanguage("Снятие средств с баланса","Withdrawals from balance")                          :
      reason==EVENT_REASON_ACCOUNT_CREDIT                   ?  TextByLanguage("Начисление кредита","Credit")                                                      :
      reason==EVENT_REASON_ACCOUNT_CHARGE                   ?  TextByLanguage("Дополнительные сборы","Additional charge")                                         :
      reason==EVENT_REASON_ACCOUNT_CORRECTION               ?  TextByLanguage("Корректирующая запись","Correction")                                               :
      reason==EVENT_REASON_ACCOUNT_BONUS                    ?  TextByLanguage("Перечисление бонусов","Bonus")                                                     :
      reason==EVENT_REASON_ACCOUNT_COMISSION                ?  TextByLanguage("Дополнительные комиссии","Additional commission")                                  :
      reason==EVENT_REASON_ACCOUNT_COMISSION_DAILY          ?  TextByLanguage("Комиссия, начисляемая в конце торгового дня","Daily commission")                   :
      reason==EVENT_REASON_ACCOUNT_COMISSION_MONTHLY        ?  TextByLanguage("Комиссия, начисляемая в конце месяца","Monthly commission")                        :
      reason==EVENT_REASON_ACCOUNT_COMISSION_AGENT_DAILY    ?  TextByLanguage("Агентская комиссия, начисляемая в конце торгового дня","Daily agent commission")   :
      reason==EVENT_REASON_ACCOUNT_COMISSION_AGENT_MONTHLY  ?  TextByLanguage("Агентская комиссия, начисляемая в конце месяца","Monthly agent commission")        :
      reason==EVENT_REASON_ACCOUNT_INTEREST                 ?  TextByLanguage("Начисления процентов на свободные средства","Interest rate")                       :
      reason==EVENT_REASON_BUY_CANCELLED                    ?  TextByLanguage("Отмененная сделка покупки","Canceled buy deal")                                    :
      reason==EVENT_REASON_SELL_CANCELLED                   ?  TextByLanguage("Отмененная сделка продажи","Canceled sell deal")                                   :
      reason==EVENT_REASON_DIVIDENT                         ?  TextByLanguage("Начисление дивиденда","Dividend operations")                                       :
      reason==EVENT_REASON_DIVIDENT_FRANKED                 ?  TextByLanguage("Начисление франкированного дивиденда","Franked (non-taxable) dividend operations") :
      reason==EVENT_REASON_TAX                              ?  TextByLanguage("Начисление налога","Tax charges")                                                  :
      EnumToString(reason)
     );
  }
//+------------------------------------------------------------------+
//| Exibe as propriedades do evento no diário                        |
//+------------------------------------------------------------------+
void CEvent::Print(const bool full_prop=false)
  {
   ::Print("============= ",TextByLanguage("Начало списка параметров события: \"","Beginning of event parameter list: \""),this.StatusDescription(),"\" =============");
   int beg=0, end=EVENT_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_INTEGER prop=(ENUM_EVENT_PROP_INTEGER)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=EVENT_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_DOUBLE prop=(ENUM_EVENT_PROP_DOUBLE)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=EVENT_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_STRING prop=(ENUM_EVENT_PROP_STRING)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("================== ",TextByLanguage("Конец списка параметров: \"","End of parameter list: \""),this.StatusDescription(),"\" ==================\n");
  }
//+------------------------------------------------------------------+

A classe do evento base abstrato está pronta. Agora nós precisamos criar cinco classes descendentes que serão um evento indicando seu tipo: colocando uma ordem pendente, excluindo uma ordem pendente, abrindo uma posição, encerrando uma posição e uma operação de saldo.

Crie uma classe descendente com o estado de evento "Colocando uma ordem pendente".

Na pasta Events da biblioteca, crie um novo arquivo da classe CEventOrderPlased chamado de EventOrderPlased.mqh com a classe base CEvent e adicione todo as inclusões e métodos necessários para isso:

//+------------------------------------------------------------------+
//|                                             EventOrderPlased.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. | 
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include "Event.mqh"
//+------------------------------------------------------------------+
//| Evento de colocação de uma ordem pendente                        |
//+------------------------------------------------------------------+
class CEventOrderPlased : public CEvent
  {
public:
//--- Construtor
                     CEventOrderPlased(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_MARKET_PENDING,event_code,ticket) {}
//--- Propriedades da ordem do tipo (1) real e (2) inteiro suportadas
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
//--- (1) Exibe uma breve mensagem sobre o evento no diário, (2) Envia o evento para o gráfico
   virtual void      PrintShort(void);
   virtual void      SendEvent(void);
  };
//+------------------------------------------------------------------+

Passa o código do evento e o ticket de uma ordem ou negócio que acionou o evento para o construtor da classe e enviou o estado do evento "Colocando uma ordem pendente" EVENT_STATUS_MARKET_PENDING), o código do evento e o ticket de uma ordem ou negócio para a classe pai na lista de inicialização:

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

Nós já descrevemos os métodos que retornam as flags de um objeto que suportam certas propriedades - SupportProperty() na primeira parte da descrição da biblioteca. Tudo é o mesmo aqui:

//+------------------------------------------------------------------+
//| Retorna 'true' se o evento suportar a propriedade inteira        |
//| passada, caso contrário retorna 'false'                          |
//+------------------------------------------------------------------+
bool CEventOrderPlased::SupportProperty(ENUM_EVENT_PROP_INTEGER property)
  {
   if(property==EVENT_PROP_TYPE_DEAL_EVENT         ||
      property==EVENT_PROP_TICKET_DEAL_EVENT       ||
      property==EVENT_PROP_TYPE_ORDER_POSITION     ||
      property==EVENT_PROP_TICKET_ORDER_POSITION   ||
      property==EVENT_PROP_POSITION_ID             ||
      property==EVENT_PROP_POSITION_BY_ID          ||
      property==EVENT_PROP_TIME_ORDER_POSITION
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Retorna 'true' se o evento suportar a propriedade inteira        |
//| passada, real passada, caso contrário retorna 'false'            |
//+------------------------------------------------------------------+
bool CEventOrderPlased::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
  {
   if(property==EVENT_PROP_PRICE_CLOSE             ||
      property==EVENT_PROP_PROFIT
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+

O objeto de evento pai CEvent apresenta o método Print() que exibe os dados completos em todas as propriedades do objeto de evento suportado e o método virtual PrintShort(), permitindo exibir os dados necessários sobre evento no diário do terminal em duas linhas.
A implementação do método PrintShort() em cada classe descendente do objeto base do evento será individual, pois os eventos também são diferentes em suas origens:

//+------------------------------------------------------------------+
//| Exibe uma breve mensagem de evento no diário                     |
//+------------------------------------------------------------------+
void CEventOrderPlased::PrintShort(void)
  {
   string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   string sl=(this.PriceStopLoss()>0 ? ", sl "+::DoubleToString(this.PriceStopLoss(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
   string tp=(this.PriceTakeProfit()>0 ? ", tp "+::DoubleToString(this.PriceTakeProfit(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
   string vol=::DoubleToString(this.VolumeInitial(),DigitsLots(this.Symbol()));
   string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : "");
   string type=this.TypeOrderDescription()+" #"+(string)this.TicketOrderEvent();
   string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceOpen(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS));
   string txt=head+this.Symbol()+" "+vol+" "+type+price+sl+tp+magic;
   ::Print(txt);
  }
//+------------------------------------------------------------------+

Aqui nós fazemos o seguinte:

  • Criamos um cabeçalho de mensagem que consiste na descrição e hora do tipo de evento
  • Se a ordem tiver StopLoss, é criado uma linha com sua descrição, caso contrário, a string permanecerá vazia
  • Se a ordem tiver TakeProfit, é criado uma linha com sua descrição, caso contrário, a string permanecerá vazia
  • Criamos uma linha indicando o volume da ordem
  • Se a ordem tiver um número mágico, é criado uma linha com sua descrição, caso contrário, a string permanecerá vazia
  • Criamos uma linha especificando o tipo de ordem e o ticket
  • Criamos uma linha especificando o preço da ordem e o símbolo na qual a ordem é colocada
  • Criamos uma linha completa de todas as descrições acima
  • Exibimos a linha criada no diário

O método de envio de um evento personalizado para o gráfico é bem simples:

//+------------------------------------------------------------------+
//| Envia o evento para o gráfico                                    |
//+------------------------------------------------------------------+
void CEventOrderPlased::SendEvent(void)
  {
   this.PrintShort();
   ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.TicketOrderEvent(),this.PriceOpen(),this.Symbol());
  }
//+------------------------------------------------------------------+

Primeiro, uma breve mensagem sobre o evento é exibida no diário, então o evento personalizado EventChartCustom() é enviado para o gráfico especificado no ID do gráfico m_chart_id da classe de evento base CEvent.
Envia o evento m_trade_event para o ID do evento,
ticket da ordem — para o parâmetro do tipo long,
preço da ordem — para o parâmetro do tipo double
,
símbolo da ordem — para o parâmetro do tipo string.

A classe para exibir as mensagens permitindo que os usuários definam os níveis de mensagem para exibir apenas os dados necessários no diário deve ser desenvolvida no futuro. No atual estágio de desenvolvimento da biblioteca, todas as mensagens são exibidas por padrão.

Vamos considerar as listagem completa de outras classes de eventos.
Classe do evento "Remoção de ordem pendente":

//+------------------------------------------------------------------+
//|                                            EventOrderRemoved.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. | 
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include "Event.mqh"
//+------------------------------------------------------------------+
//| Evento de colocação de ordem pendente                            |
//+------------------------------------------------------------------+
class CEventOrderRemoved : public CEvent
  {
public:
//--- Construtor
                     CEventOrderRemoved(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_HISTORY_PENDING,event_code,ticket) {}
//--- Propriedades da ordem do tipo (1) real e (2) inteiro suportadas
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
//--- (1) Exibe uma breve mensagem sobre o evento no diário, (2) Envia o evento para o gráfico
   virtual void      PrintShort(void);
   virtual void      SendEvent(void);
  };
//+------------------------------------------------------------------+
//| Retorna 'true' se o evento suportar a propriedade inteira        |
//| passada, inteiro passada, caso contrário, retorna 'false'        |
//+------------------------------------------------------------------+
bool CEventOrderRemoved::SupportProperty(ENUM_EVENT_PROP_INTEGER property)
  {
   if(property==EVENT_PROP_TYPE_DEAL_EVENT         ||
      property==EVENT_PROP_TICKET_DEAL_EVENT       ||
      property==EVENT_PROP_TYPE_ORDER_POSITION     ||
      property==EVENT_PROP_TICKET_ORDER_POSITION   ||
      property==EVENT_PROP_TIME_ORDER_POSITION
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Retorna 'true' se o evento suportar a propriedade real passada,  |
//| caso contrário, retorna 'false'                                  |
//+------------------------------------------------------------------+
bool CEventOrderRemoved::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
  {
   return(property==EVENT_PROP_PROFIT ? false : true);
  }
//+------------------------------------------------------------------+
//| Exibe uma breve mensagem de evento no diário                     |
//+------------------------------------------------------------------+
void CEventOrderRemoved::PrintShort(void)
  {
   string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   string sl=(this.PriceStopLoss()>0 ? ", sl "+::DoubleToString(this.PriceStopLoss(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
   string tp=(this.PriceTakeProfit()>0 ? ", tp "+::DoubleToString(this.PriceTakeProfit(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
   string vol=::DoubleToString(this.VolumeInitial(),DigitsLots(this.Symbol()));
   string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : "");
   string type=this.TypeOrderDescription()+" #"+(string)this.TicketOrderEvent();
   string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceOpen(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS));
   string txt=head+this.Symbol()+" "+vol+" "+type+price+sl+tp+magic;
   ::Print(txt);
  }
//+------------------------------------------------------------------+
//| Envia o evento para o gráfico                                    |
//+------------------------------------------------------------------+
void CEventOrderRemoved::SendEvent(void)
  {
   this.PrintShort();
   ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.TicketOrderEvent(),this.PriceOpen(),this.Symbol());
  }
//+------------------------------------------------------------------+

Classe de evento "Abertura de posição":

//+------------------------------------------------------------------+
//|                                            EventPositionOpen.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. | 
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include "Event.mqh"
//+------------------------------------------------------------------+
//| Evento abertura de posição                                       |
//+------------------------------------------------------------------+
class CEventPositionOpen : public CEvent
  {
public:
//--- Construtor
                     CEventPositionOpen(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_MARKET_POSITION,event_code,ticket) {}
//--- Propriedades da ordem do tipo (1) real e (2) inteiro suportadas
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
//--- (1) Exibe uma breve mensagem sobre o evento no diário, (2) Envia o evento para o gráfico
   virtual void      PrintShort(void);
   virtual void      SendEvent(void);
  };
//+------------------------------------------------------------------+
//| Retorna 'true' se o evento suportar a propriedade inteira        |
//| passada, inteiro passada, caso contrário, retorna 'false'        |
//+------------------------------------------------------------------+
bool CEventPositionOpen::SupportProperty(ENUM_EVENT_PROP_INTEGER property)
  {
   return(property==EVENT_PROP_POSITION_BY_ID ? false : true);
  }
//+------------------------------------------------------------------+
//| Retorna 'true' se a ordem suportar a propriedade real passada,   |
//| caso contrário, retorna 'false'                                  |
//+------------------------------------------------------------------+
bool CEventPositionOpen::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
  {
   if(property==EVENT_PROP_PRICE_CLOSE ||
      property==EVENT_PROP_PROFIT
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Exibe uma breve mensagem de evento no diário                     |
//+------------------------------------------------------------------+
void CEventPositionOpen::PrintShort(void)
  {
   string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   string order=(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED) ? " #"+(string)this.TicketOrderPosition() : "");
   string activated=(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED) ? TextByLanguage(" активацией ордера "," by ")+this.TypeOrderBasedDescription() : "");
   string sl=(this.PriceStopLoss()>0 ? ", sl "+::DoubleToString(this.PriceStopLoss(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
   string tp=(this.PriceTakeProfit()>0 ? ", tp "+::DoubleToString(this.PriceTakeProfit(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
   string vol=::DoubleToString(this.VolumeInitial(),DigitsLots(this.Symbol()));
   string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : "");
   string type=this.TypePositionDescription()+" #"+(string)this.PositionID();
   string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceOpen(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS));
   string txt=head+this.Symbol()+" "+vol+" "+type+activated+order+price+sl+tp+magic;
   ::Print(txt);
  }
//+------------------------------------------------------------------+
//| Envia o evento para o gráfico                                    |
//+------------------------------------------------------------------+
void CEventPositionOpen::SendEvent(void)
  {
   this.PrintShort();
   ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.PositionID(),this.PriceOpen(),this.Symbol());
  }
//+------------------------------------------------------------------+

Classe de evento "Encerramento da posição":

//+------------------------------------------------------------------+
//|                                           EventPositionClose.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. | 
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include "Event.mqh"
//+------------------------------------------------------------------+
//| Evento de abertura de posição                                    |
//+------------------------------------------------------------------+
class CEventPositionClose : public CEvent
  {
public:
//--- Construtor
                     CEventPositionClose(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_HISTORY_POSITION,event_code,ticket) {}
//--- Propriedades da ordem do tipo (1) real e (2) inteiro suportadas
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
//--- (1) Exibe uma breve mensagem sobre o evento no diário, (2) Envia o evento para o gráfico
   virtual void      PrintShort(void);
   virtual void      SendEvent(void);
  };
//+------------------------------------------------------------------+
//| Retorna 'true' se o evento suportar a propriedade inteira        |
//| passada, inteiro passada, caso contrário, retorna 'false'        |
//+------------------------------------------------------------------+
bool CEventPositionClose::SupportProperty(ENUM_EVENT_PROP_INTEGER property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| Retorna 'true' se o evento suportar a propriedade real passada,  |
//| caso contrário, retorna 'false'                                  |
//+------------------------------------------------------------------+
bool CEventPositionClose::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| Exibe uma breve mensagem sobre o evento no diário                |
//+------------------------------------------------------------------+
void CEventPositionClose::PrintShort(void)
  {
   string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   string opposite=(this.IsPresentEventFlag(TRADE_EVENT_FLAG_BY_POS) ? " by "+this.TypeOrderDescription()+" #"+(string)this.PositionByID() : "");
   string vol=::DoubleToString(this.VolumeExecuted(),DigitsLots(this.Symbol()));
   string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : "");
   string type=this.TypePositionDescription()+" #"+(string)this.PositionID()+opposite;
   string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceClose(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS));
   string profit=TextByLanguage(", профит: ",", profit: ")+::DoubleToString(this.Profit(),this.m_digits_acc)+" "+::AccountInfoString(ACCOUNT_CURRENCY);
   string txt=head+this.Symbol()+" "+vol+" "+type+price+magic+profit;
   ::Print(txt);
  }
//+------------------------------------------------------------------+
//| Envia o evento para o gráfico                                    |
//+------------------------------------------------------------------+
void CEventPositionClose::SendEvent(void)
  {
   this.PrintShort();
   ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.PositionID(),this.PriceClose(),this.Symbol());
  }
//+------------------------------------------------------------------+

Classe de evento "Operação de saldo":

//+------------------------------------------------------------------+
//|                                        EventBalanceOperation.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. | 
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include "Event.mqh"
//+------------------------------------------------------------------+
//| Evento de abertura de posição                                    |
//+------------------------------------------------------------------+
class CEventBalanceOperation : public CEvent
  {
public:
//--- Construtor
                     CEventBalanceOperation(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_BALANCE,event_code,ticket) {}
//--- Propriedades da ordem do tipo (1) real e (2) inteiro suportadas
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
   virtual bool      SupportProperty(ENUM_EVENT_PROP_STRING property);
//--- (1) Exibe uma breve mensagem sobre o evento no diário, (2) Envia o evento para o gráfico
   virtual void      PrintShort(void);
   virtual void      SendEvent(void);
  };
//+------------------------------------------------------------------+
//| Retorna 'true' se o evento suportar a propriedade inteira        |
//| passada, inteiro passada, caso contrário, retorna 'false'        |
//+------------------------------------------------------------------+
bool CEventBalanceOperation::SupportProperty(ENUM_EVENT_PROP_INTEGER property)
  {
   if(property==EVENT_PROP_TYPE_ORDER_EVENT        ||
      property==EVENT_PROP_TYPE_ORDER_POSITION     ||
      property==EVENT_PROP_TICKET_ORDER_EVENT      ||
      property==EVENT_PROP_TICKET_ORDER_POSITION   ||
      property==EVENT_PROP_POSITION_ID             ||
      property==EVENT_PROP_POSITION_BY_ID          ||
      property==EVENT_PROP_POSITION_ID             ||
      property==EVENT_PROP_MAGIC_ORDER             ||
      property==EVENT_PROP_TIME_ORDER_POSITION
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Retorna 'true' se o evento suportar a propriedade real passada,  |
//| caso contrário, retorna 'false'                                  |
//+------------------------------------------------------------------+
bool CEventBalanceOperation::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
  {
   return(property==EVENT_PROP_PROFIT ? true : false);
  }
//+------------------------------------------------------------------+
//| Retorna 'true' se o evento suportar a propriedade string passada,|
//| caso contrário, retorna 'false'                                  |
//+------------------------------------------------------------------+
bool CEventBalanceOperation::SupportProperty(ENUM_EVENT_PROP_STRING property)
  {
   return false;
  }
//+------------------------------------------------------------------+
//| Exibe uma breve mensagem sobre o evento no diário                |
//+------------------------------------------------------------------+
void CEventBalanceOperation::PrintShort(void)
  {
   string head="- "+this.StatusDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   ::Print(head+this.TypeEventDescription()+": "+::DoubleToString(this.Profit(),this.m_digits_acc)+" "+::AccountInfoString(ACCOUNT_CURRENCY));
  }
//+------------------------------------------------------------------+
//| Envia o evento para o gráfico                                    |
//+------------------------------------------------------------------+
void CEventBalanceOperation::SendEvent(void)
  {
   this.PrintShort();
   ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.TypeEvent(),this.Profit(),::AccountInfoString(ACCOUNT_CURRENCY));
  }
//+------------------------------------------------------------------+

Como nós podemos ver nas listagens, as classes são diferentes apenas pelo número de propriedades suportadas, o estado enviado ao construtor da classe pai e os métodos PrintShort(), já que cada evento tem seus próprios recursos que devem ser refletidos no diário. Tudo isso pode ser entendido a partir das listagens dos métodos, e você pode analisá-las por conta própria, portanto, não faz sentido insistir nelas. Vamos passar para o desenvolvimento da classe de coleção de eventos.

Coleção de eventos de negociação

Na quarta parte da descrição da biblioteca, nós testamos os eventos de conta definidos e sua exibição no diário e no EA. No entanto, nós conseguimos acompanhar apenas o evento mais recente. Além disso, toda a funcionalidade estava localizada na classe base da biblioteca CEngine.
A decisão correta é colocar tudo em uma classe separada e processar todos os eventos que ocorrem nela.

Para conseguir isso, eu desenvolvi os objetos de evento. Agora nós precisamos fazer com que a classe manipule qualquer número de eventos ocorridos simultaneamente. Afinal, pode haver uma situação em que as ordens pendentes são removidas ou colocadas, ou várias posições são encerradas simultaneamente em um único loop.
O princípio que nós já testamos em tais situações nos daria apenas o evento mais recente dos vários realizados de uma só vez. Eu acho que isso é incorreto. Portanto, vamos criar a classe que salva todos os eventos que ocorreram de uma só vez na lista de coleções de eventos. Além disso, no futuro, será possível usar os métodos dessas classes para percorrer o histórico da conta e recriar tudo o que aconteceu nela desde que ela foi aberta.

Na DoEasy\Collections, crie o novo arquivo da classe CEventsCollection chamado de EventsCollection.mqh. A classe CListObj deve ser criada como sendo a base.

Em seguida, preencha o modelo de classe recém-criado com todas as inclusões, membros e métodos necessários:

//+------------------------------------------------------------------+
//|                                             EventsCollection.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. | 
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Orders\Order.mqh"
#include "..\Objects\Events\EventBalanceOperation.mqh"
#include "..\Objects\Events\EventOrderPlaced.mqh"
#include "..\Objects\Events\EventOrderRemoved.mqh"
#include "..\Objects\Events\EventPositionOpen.mqh"
#include "..\Objects\Events\EventPositionClose.mqh"
//+------------------------------------------------------------------+
//| Colecão de eventos de conta                                      |
//+------------------------------------------------------------------+
class CEventsCollection : public CListObj
  {
private:
   CListObj          m_list_events;                   // Lista de eventos
   bool              m_is_hedge;                      // Flag da conta hedge
   long              m_chart_id;                      // ID do gráfico do programa de controle
   ENUM_TRADE_EVENT  m_trade_event;                   // Evento da conta de negociação
   CEvent            m_event_instance;                // Objeto de evento para buscar pela propriedade
   
//--- Cria um evento de negociação dependendo do estado da ordem
   void              CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market);
//--- Seleciona e retorna a lista de ordens pendentes do mercado
   CArrayObj*        GetListMarketPendings(CArrayObj* list);
//--- Seleciona e retorna a lista do histórico de (1) ordens pendentes removidos, (2) negócios, (3) todas as ordens de encerramento 
   CArrayObj*        GetListHistoryPendings(CArrayObj* list);
   CArrayObj*        GetListDeals(CArrayObj* list);
   CArrayObj*        GetListCloseByOrders(CArrayObj* list);
//--- Seleciona e retorna a lista de (1) todas as ordens da posição pelo seu ID, (2) todos os negócios da posição pelo seu ID
//--- (3) todos os negócios de entrada no mercado pelo ID da posição, (4) todas os negócios de encerramento de mercado por ID da posição
   CArrayObj*        GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsInByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsOutByPosID(CArrayObj* list,const ulong position_id);
//--- Retorna o volume total de todos os negócios (1) IN, (2) OUT da posição pelo seu ID
   double            SummaryVolumeDealsInByPosID(CArrayObj* list,const ulong position_id);
   double            SummaryVolumeDealsOutByPosID(CArrayObj* list,const ulong position_id);
//--- Retorna a (1) primeira, (2) última e (3) a ordem de encerramento da lista de todas as ordens da posição, (4) uma ordem por ticket
   COrder*           GetFirstOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetLastOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetCloseByOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetOrderByTicket(CArrayObj* list,const ulong order_ticket);
//--- Retorna a flag da presença do objeto de evento na lista de eventos
   bool              IsPresentEventInList(CEvent* compared_event);
   
public:
//--- Seleciona os eventos da coleção com o horário dentro do intervalo de begin_time até end_time
   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0);
//--- Retorna a lista completa de coleções de eventos "como ela está"
   CArrayObj        *GetList(void)                                                                       { return &this.m_list_events;                                           }
//--- Retorna a lista pelas propriedades de (1) inteiro, (2) real e (3) string selecionadas que atendem ao critério comparado
   CArrayObj        *GetList(ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
//--- Atualiza a lista de eventos
   void              Refresh(CArrayObj* list_history,
                             CArrayObj* list_market,
                             const bool is_history_event,
                             const bool is_market_event,
                             const int  new_history_orders,
                             const int  new_market_pendings,
                             const int  new_market_positions,
                             const int  new_deals);
//--- Define o ID do gráfico do programa de controle
   void              SetChartID(const long id)        { this.m_chart_id=id;         }
//--- Retorna o último evento de negociação na conta
   ENUM_TRADE_EVENT  GetLastTradeEvent(void)    const { return this.m_trade_event;  }
//--- Redefine o último evento de negociação
   void              ResetLastTradeEvent(void)        { this.m_trade_event=TRADE_EVENT_NO_EVENT;   }
//--- Construtor
                     CEventsCollection(void);
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CEventsCollection::CEventsCollection(void) : m_trade_event(TRADE_EVENT_NO_EVENT)
  {
   this.m_list_events.Clear();
   this.m_list_events.Sort(SORT_BY_EVENT_TIME_EVENT);
   this.m_list_events.Type(COLLECTION_EVENTS_ID);
   this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
   this.m_chart_id=::ChartID();
  }
//+------------------------------------------------------------------+

Redefine o evento de negociação na lista de inicialização do construtor da classe,
limpa a lista de coleções no corpo do construtor,
define sua classificação pelo horário do evento
,
define o ID da lista de coleções de eventos,
define a flag da conta hedge e
define o ID do gráfico do programa de controle como o gráfico atual
.

Vamos considerar os métodos necessários para a classe funcionar.

Os seguintes membros da classe são declarados na seção privada da classe:

   CListObj          m_list_events;                   // Lista de eventos
   bool              m_is_hedge;                      // Flag da conta Hedge
   long              m_chart_id;                      // ID do gráfico do programa de controle
   ENUM_TRADE_EVENT  m_trade_event;                   // Evento da conta de negociação
   CEvent            m_event_instance;                // Objeto de evento para buscar pela propriedade

A lista de eventos m_list_events é baseada na CListObj. Ele armazenará os eventos que ocorrem na conta desde o lançamento do programa. Além disso, nós vamos usá-la para receber o número necessário de vários eventos que ocorreram de uma só vez.
A flag da conta hedge m_is_hedge é usada para salvar e receber o tipo da conta. O valor da flag (tipo de conta) define o bloco de manipulação dos eventos que ocorrem no gráfico
O ID do gráfico do programa de controle m_chart_id recebe os eventos personalizados que ocorrem na conta. O ID é enviado para os objetos de evento e volta ao gráfico. O ID pode ser definido no programa de controle usando o método criado para essa finalidade.
O evento de negociação m_trade_event armazena o último evento ocorrido na conta.
O objeto de evento m_event_instance para busca por uma propriedade — um objeto de amostra especial para uso interno no método que retorna a lista de eventos com as datas especificadas no intervalo de busca de start até end. Nós já analisamos um método similar na terceira parte da descrição da biblioteca ao discutir como organizar a busca nas listas por diferentes critérios.

Aqui na seção privada, você pode ver os métodos necessários para a operação da classe:

//--- Cria um evento de negociação dependendo do estado da ordem
   void              CreateNewEvent(COrder* order,CArrayObj* list_history);
//--- Seleciona e retorna a lista de ordens pendentes do mercado
   CArrayObj*        GetListMarketPendings(CArrayObj* list);
//--- Seleciona e retorna a lista do histórico de (1) ordens, (2) ordens pendentes removidas, 
//--- (3) negócios, (4) todas as ordens da posição pelo seu ID, (5) todos os negócios da posição pelo seu ID
//--- (6) todos os negócios de entrada pelo ID da posição, (7) todos os negócios de encerramento pelo ID da posição
//--- (7) todas as ordens de encerramento
   CArrayObj*        GetListHistoryOrders(CArrayObj* list);
   CArrayObj*        GetListHistoryPendings(CArrayObj* list);
   CArrayObj*        GetListDeals(CArrayObj* list);
   CArrayObj*        GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsInByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsOutByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllCloseByOrders(CArrayObj* list);
//--- Retorna o volume total de todos os negócios (1) IN, (2) OUT pelo seu ID
   double            SummaryVolumeDealsInByPosID(CArrayObj* list,const ulong position_id);
   double            SummaryVolumeDealsOutByPosID(CArrayObj* list,const ulong position_id);
//--- Retorna a (1) primeira, (2) última e (3) a ordem de encerramento da lista de todas as ordens da posição, (4) uma ordem por ticket
   COrder*           GetFirstOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetLastOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetCloseByOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetOrderByTicket(CArrayObj* list,const ulong order_ticket);
//--- Retorna a flag da presença do objeto de evento na lista de eventos
   bool              IsPresentEventInList(CEvent* compared_event);

O método CreateNewEvent() que cria um evento de negociação dependendo do estado da ordem é usado no método Refresh() da classe principal. Nós vamos considerá-lo ao discutir o método Refresh().

Os métodos para receber as listas de vários tipos de ordens são bem simples — a seleção por uma propriedade especificada foi discutida na terceira parte da descrição da biblioteca. Aqui nós mencionaremos brevemente alguns métodos que consistem de várias iterações de seleção pelas propriedades necessárias.

O método de receber a lista de ordens pendentes do mercado:

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

O tipo da lista passada para o método é verificado primeiro. Se não for uma lista de coleção de mercado, a mensagem de erro é exibida e a lista vazia é retornada.

Em seguida, as ordens com o estado "Ordem pendente de mercado" são selecionadas da lista passada para o método e a lista obtida é retornada.

Os métodos para receber as listas de ordens pendentes removidas, negócios e ordens de encerramento colocadas ao encerrar uma posição por uma oposta:

//+------------------------------------------------------------------+
//| Seleciona somente as ordens pendentes removidas da lista         |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListHistoryPendings(CArrayObj* list)
  {
   if(list.Type()!=COLLECTION_HISTORY_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of the history collection"));
      return NULL;
     }
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_PENDING,EQUAL);
   return list_orders;
  }
//+------------------------------------------------------------------+
//| Seleciona apenas os negócios da lista                            |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListDeals(CArrayObj* list)
  {
   if(list.Type()!=COLLECTION_HISTORY_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of the history collection"));
      return NULL;
     }
   CArrayObj* list_deals=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
   return list_deals;
  }
//+------------------------------------------------------------------+
//|  Retorna a lista de todas as ordens CloseBy de encerramento da lista|
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListCloseByOrders(CArrayObj *list)
  {
   if(list.Type()!=COLLECTION_HISTORY_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of the history collection"));
      return NULL;
     }
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,ORDER_TYPE_CLOSE_BY,EQUAL);
   return list_orders;
  }
//+------------------------------------------------------------------+

Assim como ao retornar a lista de ordens pendentes ativas:

O tipo da lista é verificado e, se não for a coleção do histórico, a mensagem é exibida e NULL é retornado.
Em seguida, as ordens com os estados "Ordem pendente removida" e "Negócio" são selecionados da lista passada para o método ou as ordens são selecionadas pelo tipo ORDER_TYPE_CLOSE_BY, dependendo do método, e a lista obtida é retornada.

O método para obter uma lista de todas as ordens pertencentes a uma posição pelo seu ID:

//+------------------------------------------------------------------+
//|  Retorna a lista de todas as ordens da posição pelo seu ID       |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id)
  {
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_ID,position_id,EQUAL);
   list_orders=CSelect::ByOrderProperty(list_orders,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,NO_EQUAL);
   return list_orders;
  }
//+------------------------------------------------------------------+

Primeiro, usando a lista passada para o método, forma-se uma lista separada de todos os objetos com um ponteiro para o ID da posição passado para o método pelo seu parâmetro.
Em seguida, todos os negócios são removidos da lista obtida, e a lista final é retornada ao programa de chamada. O resultado retornado pelo método pode ser NULL, portanto, nós devemos verificar o que o método retornou no programa de chamada.

O método para obter uma lista de todas os negócios pertencentes a uma posição pelo seu ID:

//+------------------------------------------------------------------+
//| Retorna a lista de todos os negócios da posição pelo seu ID      |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListAllDealsByPosID(CArrayObj *list,const ulong position_id)
  {
   if(list.Type()!=COLLECTION_HISTORY_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of the history collection"));
      return NULL;
     }
   CArrayObj* list_deals=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_ID,position_id,EQUAL);
   list_deals=CSelect::ByOrderProperty(list_deals,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
   return list_deals;
  }
//+------------------------------------------------------------------+

O tipo da lista é verificado primeiro e, se não for o a coleção do histórico, a mensagem é exibida e NULL é retornado.
Em seguida, usando a lista passada para o método, é formado uma lista separada de todos os objetos com um ponteiro para o ID da posição passada para o método pelo seu parâmetro.
Depois disso, apenas os negócios são deixados na lista obtida, e a lista final é retornada ao programa de chamada. O resultado retornado pelo método pode ser NULL, portanto, nós devemos verificar o que o método retornou no programa de chamada.

O método para obter uma lista de todos os negócios de entrada no mercado pertencentes a uma posição pelo seu ID:

//+------------------------------------------------------------------+
//| Retorna a lista de todos os negócios de entrada do mercado(IN)   |
//| pelo ID da posição                                               |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListAllDealsInByPosID(CArrayObj *list,const ulong position_id)
  {
   CArrayObj* list_deals=this.GetListAllDealsByPosID(list,position_id);
   list_deals=CSelect::ByOrderProperty(list_deals,ORDER_PROP_DEAL_ENTRY,DEAL_ENTRY_IN,EQUAL);
   return list_deals;
  }
//+------------------------------------------------------------------+

Primeiro, usando a lista passada para o método, forma-se uma lista separada de todos os negócios com um ponteiro para o ID da posição passado para o método pelo seu parâmetro.
Em seguida, apenas os negócios do tipo DEAL_ENTRY_IN são deixados na lista obtida, e a lista final é retornada ao programa de chamada. O resultado retornado pelo método pode ser NULL, portanto, nós devemos verificar o que o método retornou no programa de chamada.

O método para obter uma lista de todos os negócios de saída de mercado pertencentes a uma posição pelo seu ID:

//+------------------------------------------------------------------+
//| Retorna a lista de todas os negócios de saída do mercado (OUT)   |
//| pelo ID da posição                                               |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListAllDealsOutByPosID(CArrayObj *list,const ulong position_id)
  {
   CArrayObj* list_deals=this.GetListAllDealsByPosID(list,position_id);
   list_deals=CSelect::ByOrderProperty(list_deals,ORDER_PROP_DEAL_ENTRY,DEAL_ENTRY_OUT,EQUAL);
   return list_deals;
  }
//+------------------------------------------------------------------+

Primeiro, usando a lista passada para o método, forma-se uma lista separada de todos os negócios com um ponteiro para o ID da posição passado para o método pelo seu parâmetro.
Em seguida, apenas os negócios do tipo DEAL_ENTRY_OUT são deixados na lista obtida, e a lista final é retornada ao programa de chamada. O resultado retornado pelo método pode ser NULL, portanto, nós devemos verificar o que o método retornou no programa de chamada.

O método retornando o volume total de todos os negócios de uma posição de entrada no mercado pelo seu ID:

//+------------------------------------------------------------------+
//| Retorna o volume total de todas os negócios IN da posição        |
//| pelo seu ID                                                      |
//+------------------------------------------------------------------+
double CEventsCollection::SummaryVolumeDealsInByPosID(CArrayObj *list,const ulong position_id)
  {
   double vol=0.0;
   CArrayObj* list_in=this.GetListAllDealsInByPosID(list,position_id);
   if(list_in==NULL)
      return 0;
   for(int i=0;i<list_in.Total();i++)
     {
      COrder* deal=list_in.At(i);
      if(deal==NULL)
         continue;
      vol+=deal.Volume();
     }
   return vol;
  }
//+------------------------------------------------------------------+

Primeiro, receber a lista de todas os negócios de entrada da posição, então soma-se os volumes de todas os negócios em um loop. O volume resultante é retornado para o programa de chamada. Se a lista passada para o método estiver vazia ou não for uma lista de coleção do histórico, o método retornará zero.

O método retornando o volume total de todos os negócios de saída da posição pelo seu ID:

//+--------------------------------------------------------------------+
//| Retorna o volume total de todos os negócios OUT da posição pelo seu|
//| ID (a participação no fechamento por uma posição oposta é considerada)|
//+--------------------------------------------------------------------+
double CEventsCollection::SummaryVolumeDealsOutByPosID(CArrayObj *list,const ulong position_id)
  {
   double vol=0.0;
   CArrayObj* list_out=this.GetListAllDealsOutByPosID(list,position_id);
   if(list_out!=NULL)
     {
      for(int i=0;i<list_out.Total();i++)
        {
         COrder* deal=list_out.At(i);
         if(deal==NULL)
            continue;
         vol+=deal.Volume();
        }
     }
   CArrayObj* list_by=this.GetListCloseByOrders(list);
   if(list_by!=NULL)
     {
      for(int i=0;i<list_by.Total();i++)
        {
         COrder* order=list_by.At(i);
         if(order==NULL)
            continue;
         if(order.PositionID()==position_id || order.PositionByID()==position_id)
           {
            vol+=order.Volume();
           }
        }
     }
   return vol;
  }
//+------------------------------------------------------------------+

Se parte de uma posição, cujo ID foi passado para o método, participou no fechamento de outra posição (como uma posição oposta), ou parte da posição foi fechada por uma posição oposta, isso não é considerado nos negócios da posição. Em vez disso, ele é considerado no campo de propriedade ORDER_PROP_POSITION_BY_ID da última ordem de encerramento da posição. Portanto, esse método tem duas buscas para o volume de encerramento — por negócios e por ordens de encerramento.

Primeiro, recebemos a lista de todos os negócios da posição de saída do mercado, então soma-se os volumes de todos os negócios em um loop.
Em seguida, recebemos a lista de todos as ordens de encerramento presente na lista do histórico, usamos o loop para verificar o pertencimento da ordem selecionada para a posição cujo ID foi passado para o método. Se a ordem selecionada participou do fechamento de uma posição, seu volume é adicionado ao total.
O volume resultante é retornado para o programa de chamada. Se a lista passada para o método estiver vazia ou não for uma lista de coleção do histórico, o método retornará zero.

O método retornando a primeira ordem de abertura pelo seu ID:

//+------------------------------------------------------------------+
//| Retorna a primeira ordem da lista de todos as ordens da posição  |
//+------------------------------------------------------------------+
COrder* CEventsCollection::GetFirstOrderFromList(CArrayObj* list,const ulong position_id)
  {
   CArrayObj* list_orders=this.GetListAllOrdersByPosID(list,position_id);
   if(list_orders==NULL || list_orders.Total()==0) return NULL;
   list_orders.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list_orders.At(0);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

Primeiro, recebemos a lista de todos as ordens da posição. A lista obtida é ordenada pelo horário de abertura e seu primeiro elemento é levado em consideração. Ele será usado como a primeira ordem da posição. A ordem obtida é retornada ao programa de chamada. Se as listas estiverem vazias, o método retornará NULL.

O método retornando a última ordem da posição pelo seu ID:

//+------------------------------------------------------------------+
//| Retorna a última ordem da lista de todos as ordens da posição    |
//+------------------------------------------------------------------+
COrder* CEventsCollection::GetLastOrderFromList(CArrayObj* list,const ulong position_id)
  {
   CArrayObj* list_orders=this.GetListAllOrdersByPosID(list,position_id);
   if(list_orders==NULL || list_orders.Total()==0) return NULL;
   list_orders.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list_orders.At(list_orders.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

Primeiro, recebemos a lista de todos as ordens da posição. A lista obtida é ordenada pelo horário de abertura e seu último elemento é levado em consideração. Ele será usado como a última ordem da posição. A ordem obtida é retornada ao programa de chamada. Se as listas estiverem vazias, o método retornará NULL.

O método que retorna a última ordem de encerramento pelo seu ID (ordem do tipo ORDER_TYPE_CLOSE_BY):

//+------------------------------------------------------------------+
//| Retorna a última ordem de encerramento                           |
//| da lista de todas as ordens da posição                           |
//+------------------------------------------------------------------+
COrder* CEventsCollection::GetCloseByOrderFromList(CArrayObj *list,const ulong position_id)
  {
   CArrayObj* list_orders=this.GetListAllOrdersByPosID(list,position_id);
   list_orders=CSelect::ByOrderProperty(list_orders,ORDER_PROP_TYPE,ORDER_TYPE_CLOSE_BY,EQUAL);
   if(list_orders==NULL || list_orders.Total()==0) return NULL;
   list_orders.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list_orders.At(list_orders.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

Uma vez que os encerramentos parciais são possíveis ao encerrar uma posição pela sua oposta, e os volumes das duas posições opostas podem ser desiguais, a ordem de fechamento pode não ser a única dentro das ordens de posição. Portanto, o método procura por tais ordens e retorna a última delas — é a última ordem que acionou um evento.

Primeiro, recebemos a lista de todos as ordens da posição. Então, da lista obtida, recebemos a lista contendo apenas as ordens de encerramento (do tipo ORDER_TYPE_CLOSE_BY). A lista obtida de tal maneira é ordenada pelo horário de abertura e seu último elemento é levado em consideração. Ela será usada como a última ordem de encerramento. A ordem obtida é retornada ao programa de chamada. Se as listas estiverem vazias, o método retornará NULL.

Ao encerrar por uma posição oposta, pode haver situações em que a biblioteca vê dois eventos idênticos: duas posições são encerradas e apenas uma delas tem uma ordem de encerramento, além disso, temos dois negócios. Portanto, para não duplicar o mesmo evento na coleção, nós devemos primeiro verificar a presença do mesmo evento na lista de coleção de eventos e, se não estiver lá, adicionamos o evento à lista.

O método retornando uma ordem pelo ticket:

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

Primeiro, criamos apenas a lista de ordens, então ordenamos a lista pelo ticket passado pelo parâmetro do método. Como resultado, nós retornamos NULL (quando não há nenhuma ordem com tal ticket), ou o número do ticket.

O método que retorna a presença do evento na lista é usado para verificar se o evento está na lista:

//+------------------------------------------------------------------+
//| Retorna a flag da presença do objeto de evento na lista de eventos|
//+------------------------------------------------------------------+
bool CEventsCollection::IsPresentEventInList(CEvent *compared_event)
  {
   int total=this.m_list_events.Total();
   if(total==0)
      return false;
   for(int i=total-1;i>=0;i--)
     {
      CEvent* event=this.m_list_events.At(i);
      if(event==NULL)
         continue;
      if(event.IsEqual(compared_event))
         return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

O ponteiro para o objeto de evento comparado é passado para o método. Se a lista de coleção estiver vazia, é retornado 'false' imediatamente o que significa que não existe tal evento na lista. Depois disso, o próximo evento é retirado da lista em um loop e é comparado com o evento passado para o método usando o método IsEqual() da clase abstrata CEvent. Se o método retornar 'true', esse objeto de evento estará presente na lista de coleções de eventos. Tendo completado o loop ou atingido o último método de string, significa que não há evento na lista, retornando 'false'.

Declare os métodos na seção pública da classe:

public:
//--- Seleciona os eventos da coleção com o horário dentro do intervalo de begin_time até end_time
   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0);
//--- Retorna a lista completa de coleções de eventos "como ela está"
   CArrayObj        *GetList(void)                                                                       { return &this.m_list_events;                                           }
//--- Retorna a lista pelas propriedades de (1) inteiro, (2) real e (3) string selecionadas que atendem ao critério comparado
   CArrayObj        *GetList(ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
//--- Atualiza a lista de eventos
   void              Refresh(CArrayObj* list_history,
                             CArrayObj* list_market,
                             const bool is_history_event,
                             const bool is_market_event,
                             const int  new_history_orders,
                             const int  new_market_pendings,
                             const int  new_market_positions,
                             const int  new_deals);
//--- Define o ID do gráfico do programa de controle
   void              SetChartID(const long id)        { this.m_chart_id=id;         }
//--- Retorna o último evento de negociação na conta
   ENUM_TRADE_EVENT  GetLastTradeEvent(void)    const { return this.m_trade_event;  }
//--- Redefine o último evento de negociação
   void              ResetLastTradeEvent(void)        { this.m_trade_event=TRADE_EVENT_NO_EVENT;   }
//--- Construtor
                     CEventsCollection(void);

Eu descrevi os métodos para receber a lista completa, as listas por um intervalo de datas e por propriedades inteiras, reais e de string selecionadas na terceira parte da descrição da biblioteca. Aqui, eu vou mostrar apenas a listagem desses métodos para que você possa analisá-los por conta própria.

O método para receber a lista de eventos no intervalo de datas especificado:

//+------------------------------------------------------------------+
//| Seleciona os eventos da coleção com a hora                       |
//| dentro do intervalo de begin_time até end_time                   |
//+------------------------------------------------------------------+
CArrayObj *CEventsCollection::GetListByTime(const datetime begin_time=0,const datetime end_time=0)
  {
   CArrayObj *list=new CArrayObj();
   if(list==NULL)
     {
      ::Print(DFUN+TextByLanguage("Ошибка создания временного списка","Error creating temporary list"));
      return NULL;
     }
   datetime begin=begin_time,end=(end_time==0 ? END_TIME : end_time);
   if(begin_time>end_time) begin=0;
   list.FreeMode(false);
   ListStorage.Add(list);
   //---
   this.m_event_instance.SetProperty(EVENT_PROP_TIME_EVENT,begin);
   int index_begin=this.m_list_events.SearchGreatOrEqual(&m_event_instance);
   if(index_begin==WRONG_VALUE)
      return list;
   this.m_event_instance.SetProperty(EVENT_PROP_TIME_EVENT,end);
   int index_end=this.m_list_events.SearchLessOrEqual(&m_event_instance);
   if(index_end==WRONG_VALUE)
      return list;
   for(int i=index_begin; i<=index_end; i++)
      list.Add(this.m_list_events.At(i));
   return list;
  }
//+------------------------------------------------------------------+

O método principal a ser chamado a partir do objeto da biblioteca base quando qualquer um dos eventos ocorre é o Refresh().
Atualmente, o método funciona em contas hedge para a MQL5.

O método recebe os ponteiros para as listas de coleções do histórico e de ordens de mercado, negócios e posições, bem como os dados sobre o número de ordens recém-criadas ou removidas, posições abertas e encerradas e novos negócios.
Dependendo da lista alterada, o número necessário de ordens ou negócios é feito de acordo com o número de ordens/posições/negócios em um loop, e é chamado para cada um deles o método CreateNewEvent() para criar os eventos e colocá-los na lista de coleções.
Assim, o novo método de criação do evento é chamado para qualquer evento ocorrido, enquanto o evento é colocado na lista de coleções e o programa de chamada é notificado de todos os eventos enviando uma mensagem personalizada para o gráfico do programa de chamada.

A variável membro de classe m_trade_event recebe o valor do último evento ocorrido. O método público GetLastTradeEvent() retorna o valor do último evento de negociação. Há também o método para redefinir o último evento de negociação (semelhante a GetLastError() e ResetLastError()).
Além disso, há métodos que retornam a lista de coleções de eventos total, por um intervalo de horário e por critérios especificados. O programa de chamada sempre sabe que um evento ou vários eventos ocorreram, e é possível solicitar a lista de todos esses eventos em uma quantidade necessária e manipulá-lo de acordo com a lógica do programa interno.

Vamos considerar as listagens dos métodos Refresh() e CreateNewEvent().

O método de atualizar a lista de coleta de eventos:

//+------------------------------------------------------------------+
//| Atualiza a lista de eventos                                      |
//+------------------------------------------------------------------+
void CEventsCollection::Refresh(CArrayObj* list_history,
                                CArrayObj* list_market,
                                const bool is_history_event,
                                const bool is_market_event,
                                const int  new_history_orders,
                                const int  new_market_pendings,
                                const int  new_market_positions,
                                const int  new_deals)
  {
//--- Sai se as listas estiverem vazias
   if(list_history==NULL || list_market==NULL)
      return;
//--- No caso de uma conta hedging
   if(this.m_is_hedge)
     {
      //--- Se o evento estiver no ambiente de mercado
      if(is_market_event)
        {
         //--- se o número de ordens pendentes colocadas aumentar
         if(new_market_pendings>0)
           {
            //--- Recebe a lista das ordens pendentes recém-colocadas
            CArrayObj* list=this.GetListMarketPendings(list_market);
            if(list!=NULL)
              {
               //--- Ordena a nova lista por horário de colocação da ordem
               list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); 
               //--- Pega o número de ordens igual ao número de recém-colocadas do final da lista em um loop (os últimos N eventos)
               int total=list.Total(), n=new_market_pendings;
               for(int i=total-1; i>=0 && n>0; i--,n--)
                 {
                  //--- Recebe uma ordem da lista, se esta for uma ordem pendente, define um evento de negociação
                  COrder* order=list.At(i);
                  if(order!=NULL && order.Status()==ORDER_STATUS_MARKET_PENDING)
                     this.CreateNewEvent(order,list_history,list_market);
                 }
              }
           }
        }
      //--- Se o evento estiver no histórico da conta
      if(is_history_event)
        {
         //--- Se o número de ordens do histórico aumentou
         if(new_history_orders>0)
           {
            //--- Recebe somente a lista de ordens pendentes removidas
            CArrayObj* list=this.GetListHistoryPendings(list_history);
            if(list!=NULL)
              {
               //--- Ordena a nova lista por horário de remoção da ordem
               list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC);
               //--- Pega o número de ordens igual ao número de recém-removidas do final da lista em um loop (os últimos N eventos)
               int total=list.Total(), n=new_history_orders;
               for(int i=total-1; i>=0 && n>0; i--,n--)
                 {
                  //--- Recebe uma ordem da lista. Se esta for uma ordem pendente removida, define um evento de negociação
                  COrder* order=list.At(i);
                  if(order!=NULL && order.Status()==ORDER_STATUS_HISTORY_PENDING)
                     this.CreateNewEvent(order,list_history,list_market);
                 }
              }
           }
         //--- Se o número de negócios aumentou
         if(new_deals>0)
           {
            //--- Recebe apenas a lista de negócios
            CArrayObj* list=this.GetListDeals(list_history);
            if(list!=NULL)
              {
               //--- Ordena a nova lista por horário do negócio
               list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
               //--- Pega o número de negócios igual ao número de novos negócios do final da lista em um loop (os últimos N eventos)
               int total=list.Total(), n=new_deals;
               for(int i=total-1; i>=0 && n>0; i--,n--)
                 {
                  //--- Recebe uma negócio da lista e define um evento de negociação
                  COrder* order=list.At(i);
                  if(order!=NULL)
                     this.CreateNewEvent(order,list_history,list_market);
                 }
              }
           }
        }
     }
   //--- No caso de uma conta netting
   else
     {
      
     }
  }  
//+------------------------------------------------------------------+

A listagem dos métodos simples contém todas as condições e ações necessárias quando essas condições são atendidas. Eu acredito que tudo esteja bem transparente aqui. Atualmente, os eventos são tratados apenas em conta hedge.

Vamos considerar o método para criar um novo evento:

//+------------------------------------------------------------------+
//| Cria um evento de negociação, dependendo do estado da ordem      |
//+------------------------------------------------------------------+
void CEventsCollection::CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market)
  {
   int trade_event_code=TRADE_EVENT_FLAG_NO_EVENT;
   ENUM_ORDER_STATUS status=order.Status();
//--- Ordem pendente colocada
   if(status==ORDER_STATUS_MARKET_PENDING)
     {
      trade_event_code=TRADE_EVENT_FLAG_ORDER_PLASED;
      CEvent* event=new CEventOrderPlased(trade_event_code,order.Ticket());
      if(event!=NULL)
        {
         event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());                       // Horário do evento
         event.SetProperty(EVENT_PROP_REASON_EVENT,EVENT_REASON_DONE);                       // Motivo do evento (da enumeração ENUM_EVENT_REASON)
         event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());                    // Tipo do negócio do evento
         event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());                     // Ticket do negócio do evento
         event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder());                   // Tipo da ordem do evento
         event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder());                // Tipo da ordem do evento
         event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket());                    // Ticket da ordem do evento
         event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());                 // Ticket da ordem
         event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());                       // ID da posição
         event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID());                  // ID da posição oposta
         event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                            // Magic number da ordem
         event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpenMSC());              // Horário da ordem
         event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());                        // Preço de um evento que ocorreu
         event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());                         // Preço da ordem colocada
         event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());                       // Preço de encerramento da ordem
         event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());                            // Preço da ordem StopLoss
         event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());                          // Preço da ordem TakeProfit
         event.SetProperty(EVENT_PROP_VOLUME_INITIAL,order.Volume());                        // Volume solicitado
         event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume()-order.VolumeCurrent()); // Volume executado
         event.SetProperty(EVENT_PROP_VOLUME_CURRENT,order.VolumeCurrent());                 // Volume restante (não executado)
         event.SetProperty(EVENT_PROP_PROFIT,order.Profit());                                // Lucro
         event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                                // Símbolo da ordem
         //--- Define o ID do gráfico do programa de controle, decodifica o código do evento e define o tipo do evento
         event.SetChartID(this.m_chart_id);
         event.SetTypeEvent();
         //--- Adiciona o objeto de evento se ele não estiver na lista
         if(!this.IsPresentEventInList(event))
           {
            this.m_list_events.InsertSort(event);
            //--- Envia uma mensagem sobre o evento e define o valor do último evento de negociação
            event.SendEvent();
            this.m_trade_event=event.TradeEvent();
           }
         //--- Se o evento já estiver presente na lista, remove um novo objeto de evento e exibe uma mensagem de depuração
         else
           {
            ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
            delete event;
           }
        }
     }
//--- Ordem pendente removida
   if(status==ORDER_STATUS_HISTORY_PENDING)
     {
      trade_event_code=TRADE_EVENT_FLAG_ORDER_REMOVED;
      CEvent* event=new CEventOrderRemoved(trade_event_code,order.Ticket());
      if(event!=NULL)
        {
         ENUM_EVENT_REASON reason=
           (
            order.State()==ORDER_STATE_CANCELED ? EVENT_REASON_CANCEL :
            order.State()==ORDER_STATE_EXPIRED  ? EVENT_REASON_EXPIRED : EVENT_REASON_DONE
           );
         event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeCloseMSC());             // Horário do evento
         event.SetProperty(EVENT_PROP_REASON_EVENT,reason);                         // Motivo do evento (da enumeração ENUM_EVENT_REASON)
         event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());           // Tipo da ordem do evento
         event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());            // Ticket do negócio do evento
         event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder());          // Tipo de uma ordem que acionou um evento de negócio (a ordem da última posição)
         event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder());       // Tipo de uma ordem que acionou um negócio da posição (a ordem da primeira posição)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket());           // Ticket de uma ordem, com base em qual evento de negociação está aberto (a ordem da última posição)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());        // Ticket de uma ordem, com base em qual negócio de posição está aberto (a ordem da primeira posição)
         event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());              // ID da posição
         event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID());         // ID da posição oposta
         event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                   // Magic number da ordem
         event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpenMSC());     // Hora de uma ordem, com base em qual negócio da posição está aberto (a ordem da primeira posição)
         event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());               // Preço do evento
         event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());                // Preço da ordem colocada
         event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());              // Preço de encerramento da ordem
         event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());                   // Preço da ordem StopLoss
         event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());                 // Preço da ordem TakeProfit
         event.SetProperty(EVENT_PROP_VOLUME_INITIAL,order.Volume());               // Volume solicitado
         event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume()-order.VolumeCurrent()); // Volume executado
         event.SetProperty(EVENT_PROP_VOLUME_CURRENT,order.VolumeCurrent());        // Volume restante (não executado)
         event.SetProperty(EVENT_PROP_PROFIT,order.Profit());                       // Lucro
         event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                       // Símbolo da ordem
         //--- Define o ID do gráfico do programa de controle, decodifica o código do evento e define o tipo do evento
         event.SetChartID(this.m_chart_id);
         event.SetTypeEvent();
         //--- Adiciona o objeto de evento se ele não estiver na lista
         if(!this.IsPresentEventInList(event))
           {
            this.m_list_events.InsertSort(event);
            //--- Envia uma mensagem sobre o evento e define o valor do último evento de negociação
            event.SendEvent();
            this.m_trade_event=event.TradeEvent();
           }
         //--- Se o evento já estiver presente na lista, remove um novo objeto de evento e exibe uma mensagem de depuração
         else
           {
            ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
            delete event;
           }
        }
     }
//--- Posição aberta (__MQL4__)
   if(status==ORDER_STATUS_MARKET_POSITION)
     {
      trade_event_code=TRADE_EVENT_FLAG_POSITION_OPENED;
      CEvent* event=new CEventPositionOpen(trade_event_code,order.Ticket());
      if(event!=NULL)
        {
         event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpen());              // Horário do evento
         event.SetProperty(EVENT_PROP_REASON_EVENT,EVENT_REASON_DONE);           // Motivo do evento (da enumeração ENUM_EVENT_REASON)
         event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());        // Tipo do negócio do evento
         event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());         // Ticket do negócio do evento
         event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder());       // Tipo de uma ordem que acionou um evento de negócio (a ordem da última posição)
         event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder());    // Tipo de uma ordem, com base em qual negócio de posição está aberto (a ordem da primeira posição)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket());        // Ticket de uma ordem, com base em qual evento de negociação está aberto (a ordem da última posição)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());     // Ticket de uma ordem, com base em qual negócio de posição está aberto (a ordem da primeira posição)
         event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());           // ID da posição
         event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID());      // ID da posição oposta
         event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                // Magic number da ordem/negócio/posição
         event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpen());     // Horário de uma ordem, com base em qual negócio da posição está aberto (a ordem da primeira posição)
         event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());            // Preço do evento
         event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());             // Preço de abertura da ordem/negócio/posição
         event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());           // Preço de encerramento da ordem/negócio/posição
         event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());                // Preço do StopLoss da posição
         event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());              // Preço do TakeProfit da posição
         event.SetProperty(EVENT_PROP_VOLUME_INITIAL,order.Volume());            // Volume solicitado
         event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume());           // Volume executado
         event.SetProperty(EVENT_PROP_VOLUME_CURRENT,order.VolumeCurrent());     // Volume restante (não executado)
         event.SetProperty(EVENT_PROP_PROFIT,order.Profit());                    // Lucro
         event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                    // Símbolo da ordem
         //--- Define o ID do gráfico do programa de controle, decodifica o código do evento e define o tipo do evento
         event.SetChartID(this.m_chart_id);
         event.SetTypeEvent();
         //--- Adiciona o objeto de evento se ele não estiver na lista
         if(!this.IsPresentEventInList(event))
           {
            this.m_list_events.InsertSort(event);
            //--- Envia uma mensagem sobre o evento e define o valor do último evento de negociação
            event.SendEvent();
            this.m_trade_event=event.TradeEvent();
           }
         //--- Se o evento já estiver presente na lista, remove um novo objeto de evento e exibe uma mensagem de depuração
         else
           {
            ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
            delete event;
           }
        }
     }
//--- Novo negócio (__MQL5__)
   if(status==ORDER_STATUS_DEAL)
     {
      //--- Nova operação de saldo
      if((ENUM_DEAL_TYPE)order.TypeOrder()>DEAL_TYPE_SELL)
        {
         trade_event_code=TRADE_EVENT_FLAG_ACCOUNT_BALANCE;
         CEvent* event=new CEventBalanceOperation(trade_event_code,order.Ticket());
         if(event!=NULL)
           {
            ENUM_EVENT_REASON reason=
              (
               (ENUM_DEAL_TYPE)order.TypeOrder()==DEAL_TYPE_BALANCE ? (order.Profit()>0 ? EVENT_REASON_BALANCE_REFILL : EVENT_REASON_BALANCE_WITHDRAWAL) :
               (ENUM_EVENT_REASON)(order.TypeOrder()+REASON_EVENT_SHIFT)
              );
            event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());           // Horário do evento
            event.SetProperty(EVENT_PROP_REASON_EVENT,reason);                      // Motivo do evento (da enumeração ENUM_EVENT_REASON)
            event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());        // Tipo do negócio do evento
            event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());         // Ticket do negócio do evento
            event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder());       // Tipo de uma ordem que acionou um evento de negócio (a ordem da última posição)
            event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder());    // Tipo de uma ordem, com base em qual negócio de posição está aberto (a ordem da primeira posição)
            event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket());        // Ticket de uma ordem, com base em qual evento de negociação está aberto (a ordem da última posição)
            event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());     // Ticket de uma ordem, com base em qual negócio de posição está aberto (a ordem da primeira posição)
            event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());           // ID da posição
            event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID());      // ID da posição oposta
            event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                // Magic number da ordem/negócio/posição
            event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpenMSC());  // Horário de uma ordem, com base em qual negócio da posição está aberto (a ordem da primeira posição)
            event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());            // Preço do evento
            event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());             // Preço de abertura da ordem/negócio/posição
            event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());           // Preço de encerramento da ordem/negócio/posição
            event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());                // Preço do StopLoss do negócio
            event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());              // Preço do TakeProfit do negócio
            event.SetProperty(EVENT_PROP_VOLUME_INITIAL,order.Volume());            // Volume solicitado
            event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume());           // Volume executado
            event.SetProperty(EVENT_PROP_VOLUME_CURRENT,order.VolumeCurrent());     // Volume restante (não executado)
            event.SetProperty(EVENT_PROP_PROFIT,order.Profit());                    // Lucro
            event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                    // Símbolo da ordem
            //--- Define o ID do gráfico do programa de controle, decodifica o código do evento e define o tipo do evento
            event.SetChartID(this.m_chart_id);
            event.SetTypeEvent();
            //--- Adiciona o objeto de evento se ele não estiver na lista
            if(!this.IsPresentEventInList(event))
              {
               //--- Envia uma mensagem sobre o evento e define o valor do último evento de negociação
               this.m_list_events.InsertSort(event);
               event.SendEvent();
               this.m_trade_event=event.TradeEvent();
              }
            //--- Se o evento já estiver presente na lista, remove um novo objeto de evento e exibe uma mensagem de depuração
            else
              {
               ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
               delete event;
              }
           }
        }
      //--- Se este não for uma operação de saldo
      else
        {
         //--- Entrada no mercado
         if(order.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_IN)
           {
            trade_event_code=TRADE_EVENT_FLAG_POSITION_OPENED;
            int reason=EVENT_REASON_DONE;
            //--- Busca por todas os negócios das posições na direção de sua abertura e conta o seu volume total
            double volume_in=this.SummaryVolumeDealsInByPosID(list_history,order.PositionID());
            //--- Pega as primeiras e últimas ordens da posição da lista de todas as ordens da posição
            ulong order_ticket=order.GetProperty(ORDER_PROP_DEAL_ORDER);
            COrder* order_first=this.GetOrderByTicket(list_history,order_ticket);
            COrder* order_last=this.GetLastOrderFromList(list_history,order.PositionID());
            //--- Se não houver a última ordem, a primeira e a última ordem da posição se coincidem
            if(order_last==NULL)
               order_last=order_first;
            if(order_first!=NULL)
              {
               //--- Se o volume da ordem for aberto parcialmente, isso é uma execução parcial
               if(this.SummaryVolumeDealsInByPosID(list_history,order.PositionID())<order_first.Volume())
                 {
                  trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                  reason=EVENT_REASON_DONE_PARTIALLY;
                 }
               //--- Se uma ordem de abertura é uma pendente, a ordem pendente é ativada
               if(order_first.TypeOrder()>ORDER_TYPE_SELL && order_first.TypeOrder()<ORDER_TYPE_CLOSE_BY)
                 {
                  trade_event_code+=TRADE_EVENT_FLAG_ORDER_ACTIVATED;
                  //--- Se uma ordem for executada parcialmente, define a execução parcial da ordem como um motivo do evento
                  reason=
                    (this.SummaryVolumeDealsInByPosID(list_history,order.PositionID())<order_first.Volume() ? 
                     EVENT_REASON_ACTIVATED_PENDING_PARTIALLY : 
                     EVENT_REASON_ACTIVATED_PENDING
                    );
                 }
               CEvent* event=new CEventPositionOpen(trade_event_code,order.PositionID());
               if(event!=NULL)
                 {
                  event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());                 // Horário do evento (horário de abertura de posição)
                  event.SetProperty(EVENT_PROP_REASON_EVENT,reason);                            // Motivo do evento (da enumeração ENUM_EVENT_REASON)
                  event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());              // Tipo do negócio do evento
                  event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());               // Ticket do negócio do evento
                  event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order_first.TypeOrder());    // Tipo de uma ordem, com base em qual negócio de posição está aberto (a ordem da primeira posição)
                  event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order_first.Ticket());     // Ticket de uma ordem, com base em qual evento de negociação está aberto (a ordem da última posição)
                  event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order_last.TypeOrder());        // Tipo de uma ordem que acionou um evento de negócio (a ordem da última posição)
                  event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order_last.Ticket());         // Ticket de uma ordem, com base em qual evento de negociação está aberto (a ordem da última posição)
                  event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());                 // ID da posição
                  event.SetProperty(EVENT_PROP_POSITION_BY_ID,order_last.PositionByID());       // ID da posição oposta
                  event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                      // Magic number da ordem/negócio/posição 
                  event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order_first.TimeOpenMSC());  // Horário de uma ordem, com base em qual negócio da posição está aberto (a ordem da primeira posição)
                  event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());                  // Preço do evento (preço de abertura da posição)
                  event.SetProperty(EVENT_PROP_PRICE_OPEN,order_first.PriceOpen());             // Preço de abertura da ordem (preço de abertura da posição)
                  event.SetProperty(EVENT_PROP_PRICE_CLOSE,order_last.PriceClose());            // Preço de encerramento da ordem (preço de encerramento da última ordem da posição)
                  event.SetProperty(EVENT_PROP_PRICE_SL,order_first.StopLoss());                // Preço do StopLoss (preço do StopLoss da posição)
                  event.SetProperty(EVENT_PROP_PRICE_TP,order_first.TakeProfit());              // Preço do TakeProfit (preço do TakeProfit da posição)
                  event.SetProperty(EVENT_PROP_VOLUME_INITIAL,order_first.Volume());            // Volume solicitado
                  event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,volume_in);                      // Volume executado
                  event.SetProperty(EVENT_PROP_VOLUME_CURRENT,order_first.Volume()-volume_in);  // Volume restante (não executado)
                  event.SetProperty(EVENT_PROP_PROFIT,order.ProfitFull());                      // Lucro
                  event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                          // Símbolo da ordem
                  //--- Define o ID do gráfico do programa de controle, decodifica o código do evento e define o tipo do evento
                  event.SetChartID(this.m_chart_id);
                  event.SetTypeEvent();
                  //--- Adiciona o objeto de evento se ele não estiver na lista
                  if(!this.IsPresentEventInList(event))
                    {
                     this.m_list_events.InsertSort(event);
                     //--- Envia uma mensagem sobre o evento e define o valor do último evento de negociação
                     event.SendEvent();
                     this.m_trade_event=event.TradeEvent();
                    }
                  //--- Se o evento já estiver presente na lista, remove um novo objeto de evento e exibe uma mensagem de depuração
                  else
                    {
                     ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
                     delete event;
                    }
                 }
              }
           }
         //--- Saída do mercado
         else if(order.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT)
           {
            trade_event_code=TRADE_EVENT_FLAG_POSITION_CLOSED;
            int reason=EVENT_REASON_DONE;
            //--- Pega as primeiras e últimas ordens da posição da lista de todas as ordens da posição
            COrder* order_first=this.GetFirstOrderFromList(list_history,order.PositionID());
            COrder* order_last=this.GetLastOrderFromList(list_history,order.PositionID());
            if(order_first!=NULL && order_last!=NULL)
              {
               //--- Busca por todas os negócios da posição na direção de sua abertura e fechamento e conta o seu volume total
               double volume_in=this.SummaryVolumeDealsInByPosID(list_history,order.PositionID());
               double volume_out=this.SummaryVolumeDealsOutByPosID(list_history,order.PositionID());
               //--- Calcula o volume atual da posição encerrada
               int dgl=(int)DigitsLots(order.Symbol());
               double volume_current=::NormalizeDouble(volume_in-volume_out,dgl);
               //--- Se o volume da ordem for encerrada parcialmente, isso é uma execução parcial
               if(volume_current>0)
                 {
                  trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                 }
               //--- Se a ordem de encerramento for executada parcialmente, define a execução parcial da ordem de encerramento como um motivo do evento
               if(order_last.VolumeCurrent()>0)
                 {
                  reason=EVENT_REASON_DONE_PARTIALLY;
                 }
               //--- Se a flag de encerramento estiver definido como StopLoss para a ordem de encerramento de uma posição, o encerramento será executado pelo StopLoss
               //--- Se uma ordem de StopLoss for executada parcialmente, define a execução parcial da ordem StopLoss como a razão do evento
               if(order_last.IsCloseByStopLoss())
                 {
                  trade_event_code+=TRADE_EVENT_FLAG_SL;
                  reason=(order_last.VolumeCurrent()>0 ? EVENT_REASON_DONE_SL_PARTIALLY : EVENT_REASON_DONE_SL);
                 }
               //--- Se a flag de encerramento estiver definido como TakeProfit para a ordem de encerramento de uma posição, o encerramento será executado pelo TakeProfit
               //--- Se uma ordem de TakeProfit for executada parcialmente, define a execução parcial da ordem TakeProfit como a razão do evento
               else if(order_last.IsCloseByTakeProfit())
                 {
                  trade_event_code+=TRADE_EVENT_FLAG_TP;
                  reason=(order_last.VolumeCurrent()>0 ? EVENT_REASON_DONE_TP_PARTIALLY : EVENT_REASON_DONE_TP);
                 }
               //---
               CEvent* event=new CEventPositionClose(trade_event_code,order.PositionID());
               if(event!=NULL)
                 {
                  event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());                 // Horário do evento (horário de encerramento da posição)
                  event.SetProperty(EVENT_PROP_REASON_EVENT,reason);                            // Motivo do evento (da enumeração ENUM_EVENT_REASON)
                  event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());              // Tipo do negócio do evento
                  event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());               // Ticket do negócio do evento
                  event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order_first.TypeOrder());    // Tipo de uma ordem, com base em qual negócio de posição está aberto (a ordem da primeira posição)
                  event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order_last.TypeOrder());        // Tipo de uma ordem que acionou um evento de negócio (a ordem da última posição)
                  event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order_first.Ticket());     // Ticket de uma ordem, com base em qual negócio de posição está aberto (a ordem da primeira posição)
                  event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order_last.Ticket());         // Ticket de uma ordem, com base em qual evento de negociação está aberto (a ordem da última posição)
                  event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());                 // ID da posição
                  event.SetProperty(EVENT_PROP_POSITION_BY_ID,order_last.PositionByID());       // ID da posição oposta
                  event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                      // Magic number da ordem/negócio/posição
                  event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order_first.TimeOpenMSC());  // Horário de uma ordem, com base em qual negócio da posição está aberto (a ordem da primeira posição)
                  event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());                  // Preço do evento (preço de encerramento da posição)
                  event.SetProperty(EVENT_PROP_PRICE_OPEN,order_first.PriceOpen());             // Preço de abertura da ordem (preço de abertura da posição)
                  event.SetProperty(EVENT_PROP_PRICE_CLOSE,order_last.PriceClose());            // Preço de encerramento da ordem (preço de encerramento da última ordem da posição)
                  event.SetProperty(EVENT_PROP_PRICE_SL,order_first.StopLoss());                // Preço do StopLoss (preço do StopLoss da posição)
                  event.SetProperty(EVENT_PROP_PRICE_TP,order_first.TakeProfit());              // Preço do TakeProfit (preço do TakeProfit da posição)
                  event.SetProperty(EVENT_PROP_VOLUME_INITIAL,volume_in);                       // Volume inicial
                  event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume());                 // Volume encerrado
                  event.SetProperty(EVENT_PROP_VOLUME_CURRENT,volume_in-volume_out);            // Volume restante (não executado)
                  event.SetProperty(EVENT_PROP_PROFIT,order.ProfitFull());                      // Lucro
                  event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                          // Símbolo da ordem
                  //--- Define o ID do gráfico do programa de controle, decodifica o código do evento e define o tipo do evento
                  event.SetChartID(this.m_chart_id);
                  event.SetTypeEvent();
                  //--- Adiciona o objeto de evento se ele não estiver na lista
                  if(!this.IsPresentEventInList(event))
                    {
                     this.m_list_events.InsertSort(event);
                     //--- Envia uma mensagem sobre o evento e define o valor do último evento de negociação
                     event.SendEvent();
                     this.m_trade_event=event.TradeEvent();
                    }
                  //--- Se o evento já estiver presente na lista, remove um novo objeto de evento e exibe uma mensagem de depuração
                  else
                    {
                     ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
                     delete event;
                    }
                 }
              }
           }
         //--- Posição oposta
         else if(order.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT_BY)
           {
            trade_event_code=TRADE_EVENT_FLAG_POSITION_CLOSED;
            int reason=EVENT_REASON_DONE_BY_POS;
            //--- Pega as primeiras e últimas ordens da posição da lista de todas as ordens da posição
            COrder* order_first=this.GetFirstOrderFromList(list_history,order.PositionID());
            COrder* order_close=this.GetCloseByOrderFromList(list_history,order.PositionID());
            if(order_first!=NULL && order_close!=NULL)
              {
               //--- Adiciona a flag para encerrar por uma posição oposta
               trade_event_code+=TRADE_EVENT_FLAG_BY_POS;
               //--- Busca por todas os negócios da posição na direção de sua abertura e fechamento e conta o seu volume total
               double volume_in=this.SummaryVolumeDealsInByPosID(list_history,order.PositionID());
               double volume_out=this.SummaryVolumeDealsOutByPosID(list_history,order.PositionID());//+order_close.Volume();
               //--- Calcula o volume atual da posição encerrada
               int dgl=(int)DigitsLots(order.Symbol());
               double volume_current=::NormalizeDouble(volume_in-volume_out,dgl);
               //--- Busca por todos os negócios da posição oposta na direção de sua abertura e fechamento e conta o seu volume total
               double volume_opp_in=this.SummaryVolumeDealsInByPosID(list_history,order_close.PositionByID());
               double volume_opp_out=this.SummaryVolumeDealsOutByPosID(list_history,order_close.PositionByID());//+order_close.Volume();
               //--- Calcula o volume atual da posição oposta
               double volume_opp_current=::NormalizeDouble(volume_opp_in-volume_opp_out,dgl);
               //--- Se o volume da posição encerrada é encerrada parcialmente, isso é uma execução parcial
               if(volume_current>0 || order_close.VolumeCurrent()>0)
                 {
                  //--- Adiciona a flag de execução parcial
                  trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                  //--- Se o volume da posição oposta é encerrada parcialmente, isso é uma execução parcial pela parte do volume da posição oposta
                  reason=(volume_opp_current>0 ? EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY : EVENT_REASON_DONE_PARTIALLY_BY_POS);
                 }
               //--- Se o volume da posição oposta é encerrada completamente, isso é um encerramento pela parte do volume da posição oposta
               else
                 {
                  if(volume_opp_current>0)
                    {
                     reason=EVENT_REASON_DONE_BY_POS_PARTIALLY;
                    }
                 }
               CEvent* event=new CEventPositionClose(trade_event_code,order.PositionID());
               if(event!=NULL)
                 {
                  event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());                 // Horário do evento
                  event.SetProperty(EVENT_PROP_REASON_EVENT,reason);                            // Motivo do evento (da enumeração ENUM_EVENT_REASON)
                  event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());              // Tipo do negócio do evento
                  event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());               // Ticket do negócio do evento
                  event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order_close.TypeOrder());       // Tipo de uma ordem que acionou um evento de negócio (a ordem da última posição)
                  event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order_close.Ticket());        // Ticket de uma ordem, com base em qual evento de negociação está aberto (a ordem da última posição)
                  event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order_first.TimeOpenMSC());  // Horário de uma ordem, com base em qual negócio da posição está aberto (a ordem da primeira posição)
                  event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order_first.TypeOrder());    // Tipo de uma ordem, com base em qual negócio de posição está aberto (a ordem da primeira posição)
                  event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order_first.Ticket());     // Ticket de uma ordem, com base em qual negócio de posição está aberto (a ordem da primeira posição)
                  event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());                 // ID da posição
                  event.SetProperty(EVENT_PROP_POSITION_BY_ID,order_close.PositionByID());      // ID da posição oposta
                  event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                      // Magic number da ordem/negócio/posição
                  event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());                  // Preço do evento
                  event.SetProperty(EVENT_PROP_PRICE_OPEN,order_first.PriceOpen());             // Preço de abertura da ordem/negócio/posição
                  event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());                 // Preço de encerramento da ordem/negócio/posição
                  event.SetProperty(EVENT_PROP_PRICE_SL,order_first.StopLoss());                // Preço do StopLoss (preço do StopLoss da posição)
                  event.SetProperty(EVENT_PROP_PRICE_TP,order_first.TakeProfit());              // Preço do TakeProfit (preço do TakeProfit da posição)
                  event.SetProperty(EVENT_PROP_VOLUME_INITIAL,::NormalizeDouble(volume_in,dgl));// Volume inicial
                  event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume());                 // Volume encerrado
                  event.SetProperty(EVENT_PROP_VOLUME_CURRENT,volume_current);                  // Volume restante (não executado)
                  event.SetProperty(EVENT_PROP_PROFIT,order.ProfitFull());                      // Lucro
                  event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                          // Símbolo da ordem
                  //--- Define o ID do gráfico do programa de controle, decodifica o código do evento e define o tipo do evento
                  event.SetChartID(this.m_chart_id);
                  event.SetTypeEvent();
                  //--- Adiciona o objeto de evento se ele não estiver na lista
                  if(!this.IsPresentEventInList(event))
                    {
                     this.m_list_events.InsertSort(event);
                     //--- Envia uma mensagem sobre o evento e define o valor do último evento de negociação
                     event.SendEvent();
                     this.m_trade_event=event.TradeEvent();
                    }
                  //--- Se o evento já estiver presente na lista, remove um novo objeto de evento e exibe uma mensagem de depuração
                  else
                    {
                     ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
                     delete event;
                    }
                 }
              }
           }
         //--- Reversão
         else if(order.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_INOUT)
           {
            //--- Reversão da posição
            Print(DFUN,"Position reversal");
            order.Print();
           }
        }
     }
  }
//+------------------------------------------------------------------+

O método acabou por ser bastante extenso. Portanto, todas as descrições das verificações necessárias e ações correspondentes são fornecidas diretamente na listagem.

O método verifica o estado de uma ordem passada e todos os componentes necessários do evento ocorrido, dependendo de seu tipo (ordem pendente colocada, ordem pendente removida, negócio). Um novo evento é criado e preenchido com os dados correspondentes à ordem e ao tipo de evento, enquanto o evento é colocado na coleção de eventos e, finalmente, uma mensagem sobre esse evento é enviada ao gráfico do programa de controle e a variável que armazena o tipo do último evento que ocorreu é preenchida.

A classe de coleção de eventos está pronta. Agora nós precisamos incluí-lo no objeto base da biblioteca.

Depois de criar a classe de coleção de eventos, algumas coisas que nós fizemos na quarta parte da classe de objeto base CEngine para monitorar os eventos são redundantes, portanto, o objeto base deve ser revisado.

  1. Remova a variável privada m_trade_event_code da classe armazenando o código de estado do evento de negociação.
  2. Remova os métodos privados:
    1. método SetTradeEvent() para decodificar o código do evento,
    2. método IsTradeEventFlag() retornando a presença da flag em um evento de negociação,
    3. os métodos WorkWithHedgeCollections() e WorkWithNettoCollections() para trabalhar com as coleções de hedging e netting
    4. e o método TradeEventCode() para retornar o código de evento de negociação

Adicione a inclusão do arquivo da classe de coleção de eventos de negociação para o corpo da classe, declare o objeto da coleção de eventos, adicione o método TradeEventsControl() para trabalhar com os eventos para a seção privada da classe, altere o nome do método GetListHistoryDeals() para GetListDeals() na seção pública. Os negócios estão sempre localizadas na coleção do histórico, portanto, não é necessário mencionar explicitamente a coleção no nome do método. Vamos mudar a implementação do método para redefinir o último evento de negociação: já que agora nós recebemos o último evento da classe de coleção de eventos e o método de redefinir o último evento está presente dentro da classe, nós simplesmente precisamos chamar o método de mesmo nome da classe de coleção do evento no método ResetLastTradeEvent() da classe.

//+------------------------------------------------------------------+
//|                                                       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"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Services\TimerCounter.mqh"
//+------------------------------------------------------------------+
//| Classe base da Biblioteca                                        |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // Coleção do histórico de ordens e negócios
   CMarketCollection    m_market;                        // Coleção de ordens à mercado e negócios
   CEventsCollection    m_events;                        // Colecão de eventos
   CArrayObj            m_list_counters;                 // Lista dos contadores do timer
   bool                 m_first_start;                   // Flag da primeira execução
   bool                 m_is_hedge;                      // Flag da conta hedge
   bool                 m_is_market_trade_event;         // Flag do evento de negociação da conta
   bool                 m_is_history_trade_event;        // Flag do evento do histórico de negociação
   ENUM_TRADE_EVENT     m_acc_trade_event;               // Evento de negociação da conta
//--- Retorna o índice do contador por id
   int                  CounterIndex(const int id) const;
//--- Retorna (1) a primeira flag de ativação, (2) a presença da flag em um evento de negociação
   bool                 IsFirstStart(void);
//--- Trabalhando com eventos
   void                 TradeEventsControl(void);
//--- Retorna a última (1) ordem pendente, (2) ordem à mercado, (3) última posição, (4) posição pelo ticket
   COrder*              GetLastMarketPending(void);
   COrder*              GetLastMarketOrder(void);
   COrder*              GetLastPosition(void);
   COrder*              GetPosition(const ulong ticket);
//--- Retorna a última (1) ordem pendente removida, (2) ordem do histórico, (3) ordem do histórico pelo seu ticket (ordem à mercado ou pendente)
   COrder*              GetLastHistoryPending(void);
   COrder*              GetLastHistoryOrder(void);
   COrder*              GetHistoryOrder(const ulong ticket);
//--- Retorna a (1) primeira e a (2) última ordem do histórico da lista de todos as ordens de posição, (3) o último negócio
   COrder*              GetFirstOrderPosition(const ulong position_id);
   COrder*              GetLastOrderPosition(const ulong position_id);
   COrder*              GetLastDeal(void);
public:
   //--- Retorna a lista de (1) posições, (2) ordens pendentes e (3) ordens à mercado
   CArrayObj*           GetListMarketPosition(void);
   CArrayObj*           GetListMarketPendings(void);
   CArrayObj*           GetListMarketOrders(void);
   //--- Retorna a lista do histórico de (1) ordens, (2) ordens pendentes removidas, (3) negócios, (4) todas as ordens pelo id da posição
   CArrayObj*           GetListHistoryOrders(void);
   CArrayObj*           GetListHistoryPendings(void);
   CArrayObj*           GetListDeals(void);
   CArrayObj*           GetListAllOrdersByPosID(const ulong position_id);
//--- Redefine o último evento de negociação
   void                 ResetLastTradeEvent(void)                       { this.m_events.ResetLastTradeEvent(); }
//--- Retorna o (1) código do evento de negociação e (2) a flag da conta hedge
   ENUM_TRADE_EVENT     LastTradeEvent(void)                      const { return this.m_acc_trade_event;       }
   bool                 IsHedge(void)                             const { return this.m_is_hedge;              }
//--- Cria o timer da conta
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- Timer
   void                 OnTimer(void);
//--- Construtor/destrutor
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+

No construtor da classe CEngine, adicione a manipulação do resultado do desenvolvimento do timer em milissegundos. Se ele não for criado, exiba uma mensagem apropriada no diário. Em seguida, nós vamos desenvolver a classe para manipular certos erros, definir as flags visíveis por um programa baseado na biblioteca e processar as situações de erro.

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

No timer da classe, chame o método TradeEventsControl() após o timer da coleção de ordens, negócios e posições "despausar".

//+------------------------------------------------------------------+
//| Timer da CEngine                                                 |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
//--- Timer das coleções do histórico de ordens, negócios, ordens de mercado e posições
   int index=this.CounterIndex(COLLECTION_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      //--- Se "despausado", trabalha com os eventos de coleções
      if(counter!=NULL && counter.IsTimeDone())
        {
         this.TradeEventsControl();
        }
     }
  }
//+------------------------------------------------------------------+

Vamos melhorar o método retornando uma ordem do histórico pelo ticket. Como a lista de coleções do histórico pode conter ordens pendentes, ordens à mercado ativadas e ordens agindo como encerradas ao ser encerrada por uma posição oposta, nós precisamos considerar todos os tipos de ordens.

Para fazer isso, primeiro, busque por uma ordem pelo ticket na lista de ordens de mercado e de encerramento. Se a lista estiver vazia, busque por uma ordem pendente removida com o mesmo ticket. Se a lista não contiver a ordem, será retornado NULL. Caso contrário, o programa retorna o primeiro elemento da lista onde a ordem foi encontrada. Se não conseguir receber a ordem da lista, será retornado NULL.

//+------------------------------------------------------------------+
//| Retorna a ordem do histórico pelo seu ticket                     |
//+------------------------------------------------------------------+
COrder* CEngine::GetHistoryOrder(const ulong ticket)
  {
   CArrayObj* list=this.GetListHistoryOrders();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,(long)ticket,EQUAL);
   if(list==NULL || list.Total()==0)
     {
      list=this.GetListHistoryPendings();
      list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,(long)ticket,EQUAL);
      if(list==NULL) return NULL;
     }
   COrder* order=list.At(0);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

Vamos implementar o método TradeEventsControl() para trabalhar com eventos da conta:

//+------------------------------------------------------------------+
//| Verifica os eventos de negociação                                |
//+------------------------------------------------------------------+
void CEngine::TradeEventsControl(void)
  {
//--- Inicializa o código e as flags dos eventos de negociação
   this.m_is_market_trade_event=false;
   this.m_is_history_trade_event=false;
//--- Atualiza as listas 
   this.m_market.Refresh();
   this.m_history.Refresh();
//--- Ações durante a primeira execução
   if(this.IsFirstStart())
     {
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      return;
     }
//--- Verifica as alterações no estado do mercado e no histórico da conta 
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();

//--- Em caso de qualquer evento, envia as listas, as flags e o número de novas ordens e negócios para a coleção de eventos e atualize ela
   if(this.m_is_history_trade_event || this.m_is_market_trade_event)
     {
      this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),
                            this.m_is_history_trade_event,this.m_is_market_trade_event,
                            this.m_history.NewOrders(),this.m_market.NewPendingOrders(),
                            this.m_market.NewMarketOrders(),this.m_history.NewDeals());
      //--- Obtém o último evento de negociação da conta
      this.m_acc_trade_event=this.m_events.GetLastTradeEvent();
     }
  }

Esse método é muito menor em comparação com o seu predecessor WorkWithHedgeCollections() da quarta parte da descrição da biblioteca.

O método é simples e não requer explicações. O código contém todos os comentários, permitindo que você entenda sua lógica simples.

Aqui está uma lista completa da classe CEngine atualizada:

//+------------------------------------------------------------------+
//|                                                       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"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Services\TimerCounter.mqh"
//+------------------------------------------------------------------+
//| Classe base da Biblioteca                                        |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // Coleção do histórico de ordens e negócios
   CMarketCollection    m_market;                        // Coleção de ordens à mercado e negócios
   CEventsCollection    m_events;                        // Coleção de eventos
   CArrayObj            m_list_counters;                 // Lista dos contadores do timer
   bool                 m_first_start;                   // Flag da primeira execução
   bool                 m_is_hedge;                      // Flag da conta hedge
   bool                 m_is_market_trade_event;         // Flag do evento de negociação da conta
   bool                 m_is_history_trade_event;        // Flag do evento do histórico de negociação
   ENUM_TRADE_EVENT     m_acc_trade_event;               // Evento de negociação da conta
//--- Retorna o índice do contador por id
   int                  CounterIndex(const int id) const;
//--- Retorna (1) a primeira flag de ativação, (2) a presença da flag em um evento de negociação
   bool                 IsFirstStart(void);
//--- Trabalhando com eventos
   void                 TradeEventsControl(void);
//--- Retorna a última (1) ordem pendente, (2) ordem à mercado, (3) última posição, (4) posição pelo ticket
   COrder*              GetLastMarketPending(void);
   COrder*              GetLastMarketOrder(void);
   COrder*              GetLastPosition(void);
   COrder*              GetPosition(const ulong ticket);
//--- Retorna a última (1) ordem pendente removida, (2) ordem do histórico, (3) ordem do histórico pelo seu ticket (ordem à mercado ou pendente)
   COrder*              GetLastHistoryPending(void);
   COrder*              GetLastHistoryOrder(void);
   COrder*              GetHistoryOrder(const ulong ticket);
//--- Retorna a (1) primeira e a (2) última ordem do histórico da lista de todos as ordens de posição, (3) o último negócio
   COrder*              GetFirstOrderPosition(const ulong position_id);
   COrder*              GetLastOrderPosition(const ulong position_id);
   COrder*              GetLastDeal(void);
public:
   //--- Retorna a lista de (1) posições, (2) ordens pendentes e (3) ordens à mercado
   CArrayObj*           GetListMarketPosition(void);
   CArrayObj*           GetListMarketPendings(void);
   CArrayObj*           GetListMarketOrders(void);
   //--- Retorna a lista do histórico de (1) ordens, (2) ordens pendentes removidas, (3) negócios, (4) todas as ordens pelo id da posição
   CArrayObj*           GetListHistoryOrders(void);
   CArrayObj*           GetListHistoryPendings(void);
   CArrayObj*           GetListDeals(void);
   CArrayObj*           GetListAllOrdersByPosID(const ulong position_id);
//--- Redefine o último evento de negociação
   void                 ResetLastTradeEvent(void)                       { this.m_events.ResetLastTradeEvent(); }
//--- Retorna o (1) código do evento de negociação e (2) a flag da conta hedge
   ENUM_TRADE_EVENT     LastTradeEvent(void)                      const { return this.m_acc_trade_event;       }
   bool                 IsHedge(void)                             const { return this.m_is_hedge;              }
//--- Cria o timer da conta
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- Timer
   void                 OnTimer(void);
//--- Construtor/destrutor
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+
//| Construtor CEngine                                               |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),m_acc_trade_event(TRADE_EVENT_NO_EVENT)
  {
   ::ResetLastError();
   if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
      Print(DFUN,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError());
   this.m_list_counters.Sort();
   this.m_list_counters.Clear();
   this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE);
   this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
  }
//+------------------------------------------------------------------+
//| Destrutor CEngine                                                |
//+------------------------------------------------------------------+
CEngine::~CEngine()
  {
   ::EventKillTimer();
  }
//+------------------------------------------------------------------+
//| Timer da CEngine                                                 |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
//--- Timer das coleções do histórico de ordens, negócios, ordens de mercado e posições
   int index=this.CounterIndex(COLLECTION_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      //--- Se "despausado", trabalha com os eventos de coleções
      if(counter!=NULL && counter.IsTimeDone())
        {
         this.TradeEventsControl();
        }
     }
  }
//+------------------------------------------------------------------+
//| 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;
     }
  }
//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+
//| Verifica os eventos de negociação                                |
//+------------------------------------------------------------------+
void CEngine::TradeEventsControl(void)
  {
//--- Inicialize o código e as flags do evento de negociação
   this.m_is_market_trade_event=false;
   this.m_is_history_trade_event=false;
//--- Atualiza as listas 
   this.m_market.Refresh();
   this.m_history.Refresh();
//--- Ações durante a primeira execução
   if(this.IsFirstStart())
     {
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      return;
     }
//--- Verifica as alterações no estado de mercado e no histórico da conta 
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();

//--- Em caso de qualquer evento, envia as listas, as flags e o número de novas ordens e negócios para a coleção de eventos e atualize ela
   if(this.m_is_history_trade_event || this.m_is_market_trade_event)
     {
      this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),
                            this.m_is_history_trade_event,this.m_is_market_trade_event,
                            this.m_history.NewOrders(),this.m_market.NewPendingOrders(),
                            this.m_market.NewMarketOrders(),this.m_history.NewDeals());
      //--- Obtém o último evento de negociação da conta
      this.m_acc_trade_event=this.m_events.GetLastTradeEvent();
     }
  }
//+------------------------------------------------------------------+
//| Retorna a lista de posições                                      |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListMarketPosition(void)
  {
   CArrayObj* list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_POSITION,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Retorna a lista de ordens pendentes                              |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListMarketPendings(void)
  {
   CArrayObj* list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_PENDING,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Retorna a lista de ordens à mercado                              |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListMarketOrders(void)
  {
   CArrayObj* list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_ORDER,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Retorna a lista de ordens do histórico                           |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListHistoryOrders(void)
  {
   CArrayObj* list=this.m_history.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_ORDER,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Retorna a lista de ordens pendentes removidas                    |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListHistoryPendings(void)
  {
   CArrayObj* list=this.m_history.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_PENDING,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Retorna a lista de negócios                                      |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListDeals(void)
  {
   CArrayObj* list=this.m_history.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//|  Retorna a lista de todas as ordens da posição                   |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListAllOrdersByPosID(const ulong position_id)
  {
   CArrayObj* list=this.GetListHistoryOrders();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_ID,position_id,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Retorna a última posição                                         |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastPosition(void)
  {
   CArrayObj* list=this.GetListMarketPosition();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Retorna a posição pelo ticket                                    |
//+------------------------------------------------------------------+
COrder* CEngine::GetPosition(const ulong ticket)
  {
   CArrayObj* list=this.GetListMarketPosition();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,ticket,EQUAL);
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TICKET);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Retorna o último negócio                                         |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastDeal(void)
  {
   CArrayObj* list=this.GetListDeals();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Retorna a última ordem pendente                                  |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastMarketPending(void)
  {
   CArrayObj* list=this.GetListMarketPendings();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Retorna a última ordem pendente do histórico                     |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastHistoryPending(void)
  {
   CArrayObj* list=this.GetListHistoryPendings();
   if(list==NULL) return NULL;
   list.Sort(#ifdef __MQL5__ SORT_BY_ORDER_TIME_OPEN_MSC #else SORT_BY_ORDER_TIME_CLOSE_MSC #endif);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Retorna a última ordem à mercado                                 |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastMarketOrder(void)
  {
   CArrayObj* list=this.GetListMarketOrders();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Retorna a última ordem à mercado do histórico                    |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastHistoryOrder(void)
  {
   CArrayObj* list=this.GetListHistoryOrders();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Retorna a ordem do histórico pelo seu ticket                     |
//+------------------------------------------------------------------+
COrder* CEngine::GetHistoryOrder(const ulong ticket)
  {
   CArrayObj* list=this.GetListHistoryOrders();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,(long)ticket,EQUAL);
   if(list==NULL || list.Total()==0)
     {
      list=this.GetListHistoryPendings();
      list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,(long)ticket,EQUAL);
      if(list==NULL) return NULL;
     }
   COrder* order=list.At(0);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Retorna a primeira ordem à mercado do histórico                  |
//| da lista de todas as ordens da posição                           |
//+------------------------------------------------------------------+
COrder* CEngine::GetFirstOrderPosition(const ulong position_id)
  {
   CArrayObj* list=this.GetListAllOrdersByPosID(position_id);
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN);
   COrder* order=list.At(0);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Retorna a última ordem à mercado do histórico                    |
//| da lista de todas as ordens da posição                           |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastOrderPosition(const ulong position_id)
  {
   CArrayObj* list=this.GetListAllOrdersByPosID(position_id);
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

Testando os processos de definição, manipulação e recebimento de eventos

Agora nós estamos prontos para trabalhar com os eventos. É hora de preparar o EA para testar e lidar com as descrições de eventos e enviá-las para o programa de controle.

Na pasta do terminal \MQL5\Experts\TestDoEasy, crie a pasta Part05 e copie o EA TestDoEasyPart04.mq5 da parte anterior para um novo nome: TestDoEasyPart05.mq5

Altere o seu manipulador de eventos OnChartEvent() para receber os eventos personalizados:

//+------------------------------------------------------------------+
//| Função ChartEvent                                                |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   if(MQLInfoInteger(MQL_TESTER))
      return;
   if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,"BUTT_")>0)
     {
      PressButtonEvents(sparam);
     }
   if(id>=CHARTEVENT_CUSTOM)
     {
      ushort event=ushort(id-CHARTEVENT_CUSTOM);
      Print(DFUN,"id=",id,", event=",EnumToString((ENUM_TRADE_EVENT)event),", lparam=",lparam,", dparam=",DoubleToString(dparam,Digits()),", sparam=",sparam);
     } 
  }
//+------------------------------------------------------------------+

Aqui, se o ID do evento exceder ou for igual ao ID do evento personalizado, recebemos o código do evento passado da biblioteca pelos descendentes da classe CEvent. Ao enviar um evento personalizado pela função EventChartCustom() especificada no parâmetro da função custom_event_id (aquele em que nós escrevemos nosso evento), o valor da constante CHARTEVENT_CUSTOM (igual a 1000) da enumeração ENUM_CHART_EVENT é adicionada ao valor do evento. Portanto, para recuperar o valor do evento, basta subtrair o valor CHARTEVENT_CUSTOM do ID do evento. Depois disso, nós exibimos os dados do evento no diário do terminal.
Os seguintes dados são exibidos: o ID, descrição do evento na forma do valor da enumeração ENUM_TRADE_EVENT, valor lparam armazenando a ordem ou o ticket da posição, o valor dparam armazenando o preço do evento e o valor sparam — símbolo de uma ordem ou uma posição participando do evento ou nome da moeda da conta, caso o evento seja uma operação de saldo.
Por exemplo:

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

Além disso, nós precisamos corrigir o lote calculado para o encerramento parcial. Ele estava incorreto nas versões anteriores dos EAs de teste, uma vez que o valor do volume da posição não executado (VolumeCurrent()) foi usado para o cálculo do lote. Ele é sempre igual a zero no testador ao abrir uma posição, pois o testador não simula aberturas parciais. Assim, o valor mínimo do lote foi tomado para o encerramento, uma vez que a função de cálculo do lote sempre ajustou para zero o valor do lote menos aceitável.

Vamos encontrar as strings onde o lote para encerramento parcial é calculado e substituir VolumeCurrent() por Volume():

               //--- Calcula o volume de encerramento e encerra a metade da posição de Compra pelo ticket
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0));

               //--- Calcula o volume de encerramento e encerra a metade da posição de Venda pelo ticket
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0));

Apenas dois lugares no código — encerrando a metade da posição de Compra e encerrando a metade da posição de Venda.

Além disso, adicione o deslocamento do botão pelos eixos X e Y às entradas do EA para a localização mais conveniente dos conjuntos de botões no gráfico do testador visual (eu desloquei os botões para a direita para ver os tickets da ordem e posição no visualizador, pois eles poderiam estar ocultos pelos botões):

//--- variáveis de entrada
input ulong    InpMagic       =  123;  // Número mágico
input double   InpLots        =  0.1;  // Lotes
input uint     InpStopLoss    =  50;   // StopLoss em pontos
input uint     InpTakeProfit  =  50;   // TakeProfit em pontos
input uint     InpDistance    =  50;   // Distância das ordens pendentes (pontos)
input uint     InpDistanceSL  =  50;   // Distância das ordens StopLimit (pontos)
input uint     InpSlippage    =  0;    // Desvio em pontos
input double   InpWithdrawal  =  10;   // Retirada de fundos (no testador)
input uint     InpButtShiftX  =  40;   // Deslocamento X dos botões 
input uint     InpButtShiftY  =  10;   // Deslocamento Y dos botões 
//--- variáveis globais

Vamos mudar um pouco o código da função de criação do botão:

//+------------------------------------------------------------------+
//| Cria o painel de botões                                          |
//+------------------------------------------------------------------+
bool CreateButtons(const int shift_x=30,const int shift_y=0)
  {
   int h=18,w=84,offset=2;
   int cx=offset+shift_x,cy=offset+shift_y+(h+1)*(TOTAL_BUTT/2)+2*h+1;
   int x=cx,y=cy;
   int shift=0;
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      x=x+(i==7 ? w+2 : 0);
      if(i==TOTAL_BUTT-3) x=cx;
      y=(cy-(i-(i>6 ? 7 : 0))*(h+1));
      if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-3 ? w : w*2+2),h,butt_data[i].text,(i<4 ? clrGreen : i>6 && i<11 ? clrRed : clrBlue)))
        {
         Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text);
         return false;
        }
     }
   ChartRedraw(0);
   return true;
  }
//+------------------------------------------------------------------+

e implementar a chamada da função no manipulador OnInit() do EA:

//--- cria os botões
   if(!CreateButtons(InpButtShiftX,InpButtShiftY))
      return INIT_FAILED;
//--- configurando os parâmetros de negociação

O código completo do EA de teste é fornecido abaixo:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart05.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
#include <Trade\Trade.mqh>
//--- enums
enum ENUM_BUTTONS
  {
   BUTT_BUY,
   BUTT_BUY_LIMIT,
   BUTT_BUY_STOP,
   BUTT_BUY_STOP_LIMIT,
   BUTT_CLOSE_BUY,
   BUTT_CLOSE_BUY2,
   BUTT_CLOSE_BUY_BY_SELL,
   BUTT_SELL,
   BUTT_SELL_LIMIT,
   BUTT_SELL_STOP,
   BUTT_SELL_STOP_LIMIT,
   BUTT_CLOSE_SELL,
   BUTT_CLOSE_SELL2,
   BUTT_CLOSE_SELL_BY_BUY,
   BUTT_DELETE_PENDING,
   BUTT_CLOSE_ALL,
   BUTT_PROFIT_WITHDRAWAL
  };
#define TOTAL_BUTT   (17)
//--- estruturas
struct SDataButt
  {
   string      name;
   string      text;
  };
//--- variáveis de entrada
input ulong    InpMagic       =  123;  // Número mágico
input double   InpLots        =  0.1;  // Lotes
input uint     InpStopLoss    =  50;   // StopLoss em pontos
input uint     InpTakeProfit  =  50;   // TakeProfit em pontos
input uint     InpDistance    =  50;   // Distância das ordens pendentes (pontos)
input uint     InpDistanceSL  =  50;   // Distância das ordens StopLimit (pontos)
input uint     InpSlippage    =  0;    // Desvio em pontos
input double   InpWithdrawal  =  10;   // Retirada de fundos (no testador)
input uint     InpButtShiftX  =  40;   // Deslocamento X dos botões 
input uint     InpButtShiftY  =  10;   // Deslocamento Y dos botões 
//--- variáveis globais
CEngine        engine;
CTrade         trade;
SDataButt      butt_data[TOTAL_BUTT];
string         prefix;
double         lot;
double         withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal);
ulong          magic_number;
uint           stoploss;
uint           takeprofit;
uint           distance_pending;
uint           distance_stoplimit;
uint           slippage;
//+------------------------------------------------------------------+
//| Função de inicialização do Expert                                |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Verifica o tipo da conta
   if(!engine.IsHedge())
     {
      Alert(TextByLanguage("Ошибка. Счёт должен быть хеджевым","Error. Account must be hedge"));
      return INIT_FAILED;
     }
//--- define as variáveis globais
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0));
   magic_number=InpMagic;
   stoploss=InpStopLoss;
   takeprofit=InpTakeProfit;
   distance_pending=InpDistance;
   distance_stoplimit=InpDistanceSL;
   slippage=InpSlippage;
//--- cria os botões
   if(!CreateButtons(InpButtShiftX,InpButtShiftY))
      return INIT_FAILED;
//--- configurando os parâmetros de negociação
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_NO);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Função de desinicialização do Expert                             |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- remove os objetos
   ObjectsDeleteAll(0,prefix);
   Comment("");
  }
//+------------------------------------------------------------------+
//| Função Tick do Expert                                            |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   static ENUM_TRADE_EVENT last_event=WRONG_VALUE;
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer();
      int total=ObjectsTotal(0);
      for(int i=0;i<total;i++)
        {
         string obj_name=ObjectName(0,i);
         if(StringFind(obj_name,prefix+"BUTT_")<0)
            continue;
         PressButtonEvents(obj_name);
        }
     }
   if(engine.LastTradeEvent()!=last_event)
     {
      Comment("\nLast trade event: ",EnumToString(engine.LastTradeEvent()));
      last_event=engine.LastTradeEvent();
     }
  }
//+------------------------------------------------------------------+
//| Funçao timer                                                     |
//+------------------------------------------------------------------+
void OnTimer()
  {
   if(!MQLInfoInteger(MQL_TESTER))
      engine.OnTimer();
  }
//+------------------------------------------------------------------+
//| Função ChartEvent                                                |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   if(MQLInfoInteger(MQL_TESTER))
      return;
   if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,"BUTT_")>0)
     {
      PressButtonEvents(sparam);
     }
   if(id>=CHARTEVENT_CUSTOM)
     {
      ushort event=ushort(id-CHARTEVENT_CUSTOM);
      Print(DFUN,"id=",id,", event=",EnumToString((ENUM_TRADE_EVENT)event),", lparam=",lparam,", dparam=",DoubleToString(dparam,Digits()),", sparam=",sparam);
     } 
  }
//+------------------------------------------------------------------+
//| Cria o painel de botões                                          |
//+------------------------------------------------------------------+
bool CreateButtons(const int shift_x=30,const int shift_y=0)
  {
   int h=18,w=84,offset=2;
   int cx=offset+shift_x,cy=offset+shift_y+(h+1)*(TOTAL_BUTT/2)+2*h+1;
   int x=cx,y=cy;
   int shift=0;
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      x=x+(i==7 ? w+2 : 0);
      if(i==TOTAL_BUTT-3) x=cx;
      y=(cy-(i-(i>6 ? 7 : 0))*(h+1));
      if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-3 ? w : w*2+2),h,butt_data[i].text,(i<4 ? clrGreen : i>6 && i<11 ? clrRed : clrBlue)))
        {
         Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text);
         return false;
        }
     }
   ChartRedraw(0);
   return true;
  }
//+------------------------------------------------------------------+
//| Cria o botão                                                     |
//+------------------------------------------------------------------+
bool ButtonCreate(const string name,const int x,const int y,const int w,const int h,const string text,const color clr,const string font="Calibri",const int font_size=8)
  {
   if(ObjectFind(0,name)<0)
     {
      if(!ObjectCreate(0,name,OBJ_BUTTON,0,0,0)) 
        { 
         Print(DFUN,TextByLanguage("не удалось создать кнопку! Код ошибки=","Could not create button! Error code="),GetLastError()); 
         return false; 
        } 
      ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
      ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
      ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
      ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
      ObjectSetInteger(0,name,OBJPROP_XSIZE,w);
      ObjectSetInteger(0,name,OBJPROP_YSIZE,h);
      ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,font_size);
      ObjectSetString(0,name,OBJPROP_FONT,font);
      ObjectSetString(0,name,OBJPROP_TEXT,text);
      ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
      ObjectSetString(0,name,OBJPROP_TOOLTIP,"\n");
      ObjectSetInteger(0,name,OBJPROP_BORDER_COLOR,clrGray);
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//| Retorna o estado do botão                                        |
//+------------------------------------------------------------------+
bool ButtonState(const string name)
  {
   return (bool)ObjectGetInteger(0,name,OBJPROP_STATE);
  }
//+------------------------------------------------------------------+
//| Define o estado do botão                                         |
//+------------------------------------------------------------------+
void ButtonState(const string name,const bool state)
  {
   ObjectSetInteger(0,name,OBJPROP_STATE,state);
  }
//+------------------------------------------------------------------+
//| Transforma a enumeração em texto do botão                        |
//+------------------------------------------------------------------+
string EnumToButtText(const ENUM_BUTTONS member)
  {
   string txt=StringSubstr(EnumToString(member),5);
   StringToLower(txt);
   StringReplace(txt,"buy","Buy");
   StringReplace(txt,"sell","Sell");
   StringReplace(txt,"_limit"," Limit");
   StringReplace(txt,"_stop"," Stop");
   StringReplace(txt,"close_","Close ");
   StringReplace(txt,"2"," 1/2");
   StringReplace(txt,"_by_"," by ");
   StringReplace(txt,"profit_","Profit ");
   StringReplace(txt,"delete_","Delete ");
   return txt;
  }
//+------------------------------------------------------------------+
//| Manipa os pressionamentos de botões                              |
//+------------------------------------------------------------------+
void PressButtonEvents(const string button_name)
  {
   //--- Converte o nome do botão em seu ID de string
   string button=StringSubstr(button_name,StringLen(prefix));
   //--- Se o botão foi pressionado
   if(ButtonState(button_name))
     {
      //--- Se o botão BUTT_BUY for pressionado: Abre uma posição de Compra
      if(button==EnumToString(BUTT_BUY))
        {
         //--- Obtém os preços corretos de StopLoss e TakeProfit em relação ao StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit);
         //--- Abre uma posição de Compra
         trade.Buy(NormalizeLot(Symbol(),lot),Symbol(),0,sl,tp);
        }
      //--- Se o botão BUTT_BUY_LIMIT for pressionado: Coloca a BuyLimit
      else if(button==EnumToString(BUTT_BUY_LIMIT))
        {
         //--- Obtém o preço correto de posicionamento da ordem em relação ao StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending);
         //--- Obtém os preços corretos de StopLoss e TakeProfit em relação ao nível de colocação da ordem, considerando o StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit);
         //--- Define a ordem BuyLimit
         trade.BuyLimit(lot,price_set,Symbol(),sl,tp);
        }
      //--- Se o botão BUTT_BUY_STOP for pressionado: Coloca a BuyStop
      else if(button==EnumToString(BUTT_BUY_STOP))
        {
         //--- Obtém o preço correto de posicionamento da ordem em relação ao StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- Obtém os preços corretos de StopLoss e TakeProfit em relação ao nível de colocação da ordem, considerando o StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set,takeprofit);
         //--- Define a ordem BuyStop
         trade.BuyStop(lot,price_set,Symbol(),sl,tp);
        }
      //--- Se o botão BUTT_BUY_STOP_LIMIT for pressionado: Coloca a BuyStopLimit
      else if(button==EnumToString(BUTT_BUY_STOP_LIMIT))
        {
         //--- Obtém o preço correto do BuyStop relativo ao StopLevel
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- Calcula o preço da ordem BuyLimit relativo ao nível de colocação da BuyStop considerando o StopLevel
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_stoplimit,price_set_stop);
         //--- Obtém os preços corretos de StopLoss e TakeProfit em relação ao nível de colocação da ordem, considerando o StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,takeprofit);
         //--- Define a ordem BuyStopLimit
         trade.OrderOpen(Symbol(),ORDER_TYPE_BUY_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
        }
      //--- Se o botão BUTT_SELL for pressionado: Abre uma posição de Venda
      else if(button==EnumToString(BUTT_SELL))
        {
         //--- Obtém os preços corretos de StopLoss e TakeProfit em relação ao StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL,0,takeprofit);
         //--- Abre uma posição de Venda
         trade.Sell(lot,Symbol(),0,sl,tp);
        }
      //--- Se o botão BUTT_SELL_LIMIT for pressionado: Coloca uma SellLimit
      else if(button==EnumToString(BUTT_SELL_LIMIT))
        {
         //--- Obtém o preço correto de posicionamento da ordem em relação ao StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_pending);
         //--- Obtém os preços corretos de StopLoss e TakeProfit em relação ao nível de colocação da ordem, considerando o StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,takeprofit);
         //--- Coloca a ordem SellLimit
         trade.SellLimit(lot,price_set,Symbol(),sl,tp);
        }
      //--- Se o botão BUTT_SELL_STOP for pressionado: Coloca a SellStop
      else if(button==EnumToString(BUTT_SELL_STOP))
        {
         //--- Obtém o preço correto de posicionamento da ordem em relação ao StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- Obtém os preços corretos de StopLoss e TakeProfit em relação ao nível de colocação da ordem, considerando o StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set,takeprofit);
         //--- Coloca a ordem SellStop
         trade.SellStop(lot,price_set,Symbol(),sl,tp);
        }
      //--- Se o botão BUTT_SELL_STOP_LIMIT for pressionado: Coloca a SellStopLimit
      else if(button==EnumToString(BUTT_SELL_STOP_LIMIT))
        {
         //--- Obtém o preço correto da ordem SellStop em relação ao StopLevel
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- Calcula o preço da ordem SellLimit relativo ao nível de SellStop considerando o StopLevel
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_stoplimit,price_set_stop);
         //--- Obtém os preços corretos de StopLoss e TakeProfit em relação ao nível de colocação da ordem, considerando o StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,takeprofit);
         //--- Coloca a ordem SellStopLimit
         trade.OrderOpen(Symbol(),ORDER_TYPE_SELL_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
        }
      //--- Se o botão BUTT_CLOSE_BUY for pressionado: Encerra a Compra com o lucro máximo
      else if(button==EnumToString(BUTT_CLOSE_BUY))
        {
         //--- Obtém a lista de todas as posições em aberto
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Seleciona apenas as posições de compra da lista
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Ordena a lista por lucro considerando a comissão e swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Obtém o índice da posição de Compra com o lucro máximo
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Obtém o ticket da posição de Compra e encerra a posição pelo ticket
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- Se o botão BUTT_CLOSE_BUY2 for pressionado: Encerra a metade da Compra com o lucro máximo
      else if(button==EnumToString(BUTT_CLOSE_BUY2))
        {
         //--- Obtém a lista de todas as posições em aberto
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Seleciona apenas as posições de compra da lista
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Ordena a lista por lucro considerando a comissão e swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Obtém o índice da posição de Compra com o lucro máximo
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Calcula o volume de encerramento e encerra a metade da posição de Compra pelo ticket
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0));
              }
           }
        }
      //--- Se o botão BUTT_CLOSE_BUY_BY_SELL for pressionado: Encerra a Compra com o lucro máximo pela Venda oposta com lucro máximo
      else if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL))
        {
         //--- Obtém a lista de todas as posições em aberto
         CArrayObj* list_buy=engine.GetListMarketPosition();
         //--- Seleciona apenas as posições de compra da lista
         list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Ordena a lista por lucro considerando a comissão e swap
         list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Obtém o índice da posição de Compra com o lucro máximo
         int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
         //--- Obtém a lista de todas as posições em aberto
         CArrayObj* list_sell=engine.GetListMarketPosition();
         //--- Seleciona apenas as posições de Venda da lista
         list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Ordena a lista por lucro considerando a comissão e swap
         list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Obtém o índice da posição de Venda com o lucro máximo
         int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
         if(index_buy>WRONG_VALUE && index_sell>WRONG_VALUE)
           {
            //--- Seleciona a posição de Compra com o lucro máximo
            COrder* position_buy=list_buy.At(index_buy);
            //--- Seleciona a posição de Venda com o lucro máximo
            COrder* position_sell=list_sell.At(index_sell);
            if(position_buy!=NULL && position_sell!=NULL)
              {
               //--- Encerra a posição de Compra pela de Venda oposta
               trade.PositionCloseBy(position_buy.Ticket(),position_sell.Ticket());
              }
           }
        }
      //--- Se o botão BUTT_CLOSE_SELL for pressionado: Encerra a Venda com o lucro máximo
      else if(button==EnumToString(BUTT_CLOSE_SELL))
        {
         //--- Obtém a lista de todas as posições em aberto
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Seleciona apenas as posições de Venda da lista
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Ordena a lista por lucro considerando a comissão e swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Obtém o índice da posição de Venda com o lucro máximo
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Obtém o ticket de venda e encerra a posição pelo ticket
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- Se o botão BUTT_CLOSE_SELL2 for pressionado: Encerra metade da Venda com o lucro máximo
      else if(button==EnumToString(BUTT_CLOSE_SELL2))
        {
         //--- Obtém a lista de todas as posições em aberto
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Seleciona apenas as posições de Venda da lista
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Ordena a lista por lucro considerando a comissão e swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Obtém o índice da posição de Venda com o lucro máximo
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Calcula o volume de encerramento e encerra a metade da posição de Venda pelo ticket
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0));
              }
           }
        }
      //--- Se o botão BUTT_CLOSE_SELL_BY_BUY for pressionado: Encerra a Venda com o lucro máximo pela Compra oposta com o lucro máximo
      else if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY))
        {
         //--- Obtém a lista de todas as posições em aberto
         CArrayObj* list_sell=engine.GetListMarketPosition();
         //--- Seleciona apenas as posições de Venda da lista
         list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Ordena a lista por lucro considerando a comissão e swap
         list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Obtém o índice da posição de Venda com o lucro máximo
         int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
         //--- Obtém a lista de todas as posições em aberto
         CArrayObj* list_buy=engine.GetListMarketPosition();
         //--- Seleciona apenas as posições de compra da lista
         list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Ordena a lista por lucro considerando a comissão e swap
         list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Obtém o índice da posição de Compra com o lucro máximo
         int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
         if(index_sell>WRONG_VALUE && index_buy>WRONG_VALUE)
           {
            //--- Seleciona a posição de Venda com o lucro máximo
            COrder* position_sell=list_sell.At(index_sell);
            //--- Seleciona a posição de Compra com o lucro máximo
            COrder* position_buy=list_buy.At(index_buy);
            if(position_sell!=NULL && position_buy!=NULL)
              {
               //--- Encerra a posição de Venda pela de Compra oposta
               trade.PositionCloseBy(position_sell.Ticket(),position_buy.Ticket());
              }
           }
        }
      //--- Se o BUTT_CLOSE_ALL for pressionado: Encerra todas as posições começando com aquela com o menor lucro
      else if(button==EnumToString(BUTT_CLOSE_ALL))
        {
         //--- Obtém a lista de todas as posições em aberto
         CArrayObj* list=engine.GetListMarketPosition();
         if(list!=NULL)
           {
            //--- Ordena a lista por lucro considerando a comissão e swap
            list.Sort(SORT_BY_ORDER_PROFIT_FULL);
            int total=list.Total();
            //--- No loop da posição com o menor lucro
            for(int i=0;i<total;i++)
              {
               COrder* position=list.At(i);
               if(position==NULL)
                  continue;
               //--- encerra cada posição pelo seu ticket
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- Se o botão BUTT_DELETE_PENDING for pressionado: Remove a primeira ordem pendente
      else if(button==EnumToString(BUTT_DELETE_PENDING))
        {
         //--- Obtém a lista de todas as ordens
         CArrayObj* list=engine.GetListMarketPendings();
         if(list!=NULL)
           {
            //--- Ordena a lista por horário de colocação
            list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
            int total=list.Total();
            //--- No loop da posição com a maior quantidade de tempo
            for(int i=total-1;i>=0;i--)
              {
               COrder* order=list.At(i);
               if(order==NULL)
                  continue;
               //--- remove a ordem pelo ticket
               trade.OrderDelete(order.Ticket());
              }
           }
        }
      //--- Se o botão BUTT_PROFIT_WITHDRAWAL for pressionado: Retira os fundos da conta
      if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL))
        {
         //--- Se o programa é iniciado no testador
         if(MQLInfoInteger(MQL_TESTER))
           {
            //--- Emula a retirada de fundos
            TesterWithdrawal(withdrawal);
           }
        }
      //--- Aguarda 1/10 de segundo
      Sleep(100);
      //--- "Despressiona" o botão e redesenha o gráfico
      ButtonState(button_name,false);
      ChartRedraw();
     }
  }
//+------------------------------------------------------------------+

Agora nós podemos compilar o EA e lançá-lo no testador. Ao clicar nos botões, são exibidas no diário do testador duas linhas de mensagens curtas sobre os eventos da conta que ocorrem.


As entradas do manipulador de eventos do EA não são exibidas no diário, pois elas funcionam fora do testador. Se clicar nos botões do EA em uma conta demo, três linhas serão exibidas no diário do terminal: duas linhas do método para exibir as mensagens curtas da classe CEvent e outra — do manipulador OnChartEvent() do EA.

Abaixo está uma amostra exibindo uma mensagem no diário quando uma ordem pendente é colocada e removida:

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

Qual é o próximo?

No próximo artigo, nós vamos começar a adicionar a funcionalidade para trabalhar com as contas netting da MetaTrader 5.

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

Artigos anteriores da série:

Parte 1. Conceito, gerenciamento de dados.
Parte 2. Coleção do histórico de ordens e negócios.
Parte 3 Coleção de ordens e posições de mercado, busca e ordenação.
Parte 4 Eventos de negociação. Conceito.


Traduzido do russo pela MetaQuotes Software Corp.
Artigo original: https://www.mql5.com/ru/articles/6211

Arquivos anexados |
MQL5.zip (73.98 KB)
Implementado OLAP na negociação (Parte 2): Visualizando resultados da análise interativa de dados multidimensionais Implementado OLAP na negociação (Parte 2): Visualizando resultados da análise interativa de dados multidimensionais

O artigo discute diversos aspectos da criação de interfaces gráficas interativas de programas MQL projetados para processamento analítico online (OLAP) do histórico de contas e de relatórios de negociação. Para obter um resultado visual, são usadas janelas maximizadas e escaláveis, uma disposição adaptável de controles de borracha e um novo 'controle' para exibir diagramas. Com base nisso, é implementada uma GUI com a possibilidade de escolher indicadores ao longo dos eixos de coordenadas, funções de agregação, tipos de gráficos e classificações.

Criando interfaces gráficas baseadas no .Net Framework e C# (Parte 2): elementos gráficos adicionais Criando interfaces gráficas baseadas no .Net Framework e C# (Parte 2): elementos gráficos adicionais

O artigo é uma continuação lógica da publicação anterior "Criando interfaces gráficas para EAs e indicadores baseados no .Net Framework e C#" e introduz os leitores a novos elementos gráficos para criar interfaces gráficas.

Estimando o índice de funcionalidade, o expoente de Hurst e a possibilidade de prever séries temporais financeiras Estimando o índice de funcionalidade, o expoente de Hurst e a possibilidade de prever séries temporais financeiras

A busca e o estudo do comportamento fractal de dados financeiros implica que, por trás do comportamento aparentemente caótico de séries temporais econômicas, estão ocultos e operam mecanismos estáveis que governam a conduta coletiva dos participantes. Na bolsa de valores, essa mecânica pode levar ao surgimento de uma dinâmica de preços que determina e descreve as propriedades específicas das séries de preços. Na negociação, seria interessante ter indicadores que pudessem estimar os parâmetros de fractalidade de maneira efetiva e estável, numa escala e num intervalo de tempo que fossem uteis na prática.

Métodos para medir a velocidade do movimento de preços Métodos para medir a velocidade do movimento de preços

Existem diferentes abordagens para estudar e analisar o mercado, mas, há dois principais, nomeadamente a técnica e a fundamental. No primeiro caso, acontece a coleta, o processamento e o estudo de quaisquer dados numéricos e de características relacionadas ao mercado: preços, volumes e assim por diante. No segundo caso, acorre a análise de eventos e de notícias que, por sua vez, afetam direta ou indiretamente os mercados. O artigo discute métodos para medir a velocidade do movimento de preços e o estudo de estratégias de negociação com base neles.