Biblioteca para o desenvolvimento fácil e rápido de programas para a MetaTrader (parte IV): eventos de negociação

30 maio 2019, 10:23
Artyom Trishkin
0
1 619

Conteúdo

No primeiro artigo, nós começamos a criar uma grande biblioteca multi-plataforma simplificando o desenvolvimento de programas para a MetaTrader 5 e MetaTrader 4. Nos artigos subsequentes, nós continuamos o desenvolvimento da biblioteca e completamos concluímos o objeto base da biblioteca Engine e a coleção de ordens e posições de mercado. Neste artigo, nós continuaremos desenvolvendo o objeto base e ensinando-o a identificar os eventos de negociação na conta.

Passagem de eventos de negociação para o programa

Se nós voltarmos para o EA de teste criado no final do terceiro artigo, nós podemos ver que a biblioteca é capaz de definir os eventos de negociação que acontecem na conta. No entanto, nós devemos dividir com precisão todos os eventos que ocorrem pelo seu tipo e enviá-los para o programa usando utilizando a biblioteca.
Para fazer isso, nós devemos escrever o método que define os eventos e o que define os tipos de eventos.

Vamos pensar sobre quais eventos de negociação nós precisamos identificar:

  • uma ordem pendente pode ser colocada,
  • uma ordem pendente pode ser removida,
  • uma ordem pendente pode ser executada gerando uma posição,
  • uma ordem pendente pode ser parcialmente executada gerando uma posição,
  • uma posição pode ser aberta,
  • uma posição pode ser encerrada,
  • uma posição pode ser aberta parcialmente,
  • uma posição pode ser encerrada parcialmente,
  • uma posição pode ser encerrada por uma outra posição oposta,
  • uma posição pode ser encerrada parcialmente por uma outra posição oposta,
  • uma conta pode ser recarregada,
  • fundos podem ser retirados de uma conta,
  • operações de saldo podem ocorrer na conta
    eventos que ainda não foram monitorados:
  • uma ordem pendente pode ser modificada (alterando o preço de ativação, adição/remoção/alteração dos níveis de StopLoss e TakeProfit)
  • uma posição pode ser modificada (adição/remoção/alteração dos níveis StopLoss e TakeProfit)

Com base no que foi descrito acima, você precisa decidir como identificar um evento sem ambiguidade. É melhor dividir imediatamente a solução de acordo com os tipos de conta:

Hedging:

  1. aumento do número de ordens pendentes - significa adicionar uma ordem pendente (evento no ambiente de mercado)
  2. Houve redução do número de ordens pendentes:
    1. aumento do número de posições - significa a execução de uma ordem pendente (evento no ambiente de mercado e do histórico)
    2. o número de posições não aumentou - significa a remoção de uma ordem pendente (evento no ambiente de mercado)
  3. Não houve redução do número de ordens pendentes:
    1. aumento do número de posições - significa abrir uma nova posição (evento no ambiente de mercado e histórico)
    2. redução do número de posições - significa encerrar uma posição (evento no mercado e ambiente histórico)
    3. o número de posições permaneceu inalterado, mas acompanhou a redução do volume - significa o encerramento parcial de uma posição (evento no ambiente histórico)

Netting:

  1. aumento do número de ordens pendentes - significa adicionar uma ordem pendente
  2. Houve redução do número de ordens pendentes:
    1. aumento do número de posições - significa a execução de uma ordem pendente
    2. o número de posições permaneceu inalterado, mas foi acompanhado da alteração do horário de modificação da posição e volume inalterado - significa a execução de uma ordem pendente e o aumento do volume da posição
    3. redução do número de posições - significa encerrar uma posição
  3. Não houve redução do número de ordens pendentes:
    1. aumento do número de posições - significa abrir uma nova posição
    2. redução do número de posições - significa encerrar uma posição
    3. o número de posições permaneceu inalterado, mas foi acompanhado da alteração do horário de modificação da posição e aumento do volume - significa adicionar volume à posição
    4. o número de posições permaneceu inalterado, mas foi acompanhado da alteração do horário de modificação da posição e a redução do volume - significa o encerramento parcial da posição

Para identificar os eventos de negociação, nós precisamos saber a conta que o programa está em execução. Adicione a flag do tipo de conta hedge para a seção privada da classe CEngine, defina o tipo de conta no construtor de classe e escreva o resultado para essa variável de flag:

//+------------------------------------------------------------------+
//| 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
   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
//--- Retorna o índice do contador por id
   int                  CounterIndex(const int id) const;
//--- Retorna a flag da primeira execução
   bool                 IsFirstStart(void);
public:
//--- Cria o contador do timer
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- Timer
   void                 OnTimer(void);
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+
//| Construtor CEngine                                               |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true)
  {
   ::EventSetMillisecondTimer(TIMER_FREQUENCY);
   this.m_list_counters.Sort();
   this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE);
   this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
  }
//+------------------------------------------------------------------+

Durante a primeira execução, ao construir o objeto de classe, o tipo de conta em que o programa é iniciado é definido em seu construtor. Consequentemente, os métodos de definição de eventos de negociação são imediatamente atribuídos a uma conta hedge ou netting.

Depois de definir um evento de negociação, nós precisamos armazenar o seu código. É para permanecer constante até o próximo evento. Assim, o programa sempre pode definir o último evento em uma conta. Um código de evento consistirá em um conjunto de flags. Cada flag irá descrever um evento específico. Por exemplo, um evento de encerramento de posição pode ser dividido em determinados subconjuntos, caracterizando-o com mais precisão:

  1. encerrado totalmente
  2. encerrado parcialmente
  3. encerrado por uma posição oposta
  4. encerrado pelo stop loss
  5. encerrado pelo take profit
  6. etc.

Todos esses atributos são inerentes ao evento "encerramento de posição", o que significa que um código de evento deve conter todos esses dados. Para construir um evento usando as flags, vamos criar duas novas enumerações no arquivo Defines.mqh da pasta raiz da biblioteca (flag de eventos de negociação e possíveis eventos de negociação) na conta que vamos monitorar:

//+------------------------------------------------------------------+
//| Lista de flags de eventos de negociação na conta                 |
//+------------------------------------------------------------------+
enum ENUM_TRADE_EVENT_FLAGS
  {
   TRADE_EVENT_FLAG_NO_EVENT        =  0,                   // Nenhum evento
   TRADE_EVENT_FLAG_ORDER_PLASED    =  1,                   // Ordem pendente colocada
   TRADE_EVENT_FLAG_ORDER_REMOVED   =  2,                   // Ordem pendente removida
   TRADE_EVENT_FLAG_ORDER_ACTIVATED =  4,                   // Ordem pendente ativada pelo preço
   TRADE_EVENT_FLAG_POSITION_OPENED =  8,                   // Posição aberta
   TRADE_EVENT_FLAG_POSITION_CLOSED =  16,                  // Posição encerrada
   TRADE_EVENT_FLAG_ACCOUNT_BALANCE =  32,                  // Operação de saldo (classificado como um tipo de negócio)
   TRADE_EVENT_FLAG_PARTIAL         =  64,                  // Execução parcial
   TRADE_EVENT_FLAG_BY_POS          =  128,                 // Executado pela posição oposta
   TRADE_EVENT_FLAG_SL              =  256,                 // Executado pelo StopLoss
   TRADE_EVENT_FLAG_TP              =  512                  // Executado pelo TakeProfit
  };
//+------------------------------------------------------------------+
//| Lista de eventos de negociação possíveis na conta                |
//+------------------------------------------------------------------+
enum ENUM_TRADE_EVENT
  {
   TRADE_EVENT_NO_EVENT,                                    // Nenhum evento de negociação
   TRADE_EVENT_PENDING_ORDER_PLASED,                        // Ordem pendente colocada
   TRADE_EVENT_PENDING_ORDER_REMOVED,                       // Ordem pendente removida
//--- membros da enumeração que correspondem aos membros da enumeração ENUM_DEAL_TYPE
   TRADE_EVENT_ACCOUNT_CREDIT,                              // Crédito
   TRADE_EVENT_ACCOUNT_CHARGE,                              // Cobrança adicional
   TRADE_EVENT_ACCOUNT_CORRECTION,                          // Correção de entrada
   TRADE_EVENT_ACCOUNT_BONUS,                               // Bônus
   TRADE_EVENT_ACCOUNT_COMISSION,                           // Comissões adicionais
   TRADE_EVENT_ACCOUNT_COMISSION_DAILY,                     // Comissão cobrada no fim do dia
   TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY,                   // Comissão cobrada no fim do mês
   TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY,               // Comissão do agente cobrada no fim da negociação do dia
   TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY,             // Comissão do agente cobrada no fim da negociação do mês
   TRADE_EVENT_ACCOUNT_INTEREST,                            // Taxa de juros dobre o saldo livre
   TRADE_EVENT_BUY_CANCELLED,                               // Negócio de compra cancelado
   TRADE_EVENT_SELL_CANCELLED,                              // Negócio de venda cancelado
   TRADE_EVENT_DIVIDENT,                                    // Acréscimo de dividendos
   TRADE_EVENT_DIVIDENT_FRANKED,                            // Acréscimo de dividendos não tributáveis
   TRADE_EVENT_TAX,                                         // Cálculo do imposto
//--- membros da enumeração relacionados ao tipo de negócio DEAL_TYPE_BALANCE da enumeração ENUM_DEAL_TYPE
   TRADE_EVENT_ACCOUNT_BALANCE_REFILL,                      // Recarga do saldo da conta
   TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL,                  // Saque de fundos da conta
//---
   TRADE_EVENT_PENDING_ORDER_ACTIVATED,                     // Ordem pendente executada pelo preço
   TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL,             // Ordem pendente executada parcialmente pelo preço
   TRADE_EVENT_POSITION_OPENED,                             // Posição aberta
   TRADE_EVENT_POSITION_OPENED_PARTIAL,                     // Posição aberta parcialmente
   TRADE_EVENT_POSITION_CLOSED,                             // Posição encerrada
   TRADE_EVENT_POSITION_CLOSED_PARTIAL,                     // Posição parcialmente encerrada
   TRADE_EVENT_POSITION_CLOSED_BY_POS,                      // Posição encerrada por uma posição oposta
   TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS,              // Posição parcialmente encerrada por uma posição oposta
   TRADE_EVENT_POSITION_CLOSED_BY_SL,                       // Posição encerrada pelo StopLoss
   TRADE_EVENT_POSITION_CLOSED_BY_TP,                       // Posição encerrada pelo TakeProfit
   TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL,               // Posição parcialmente encerrada pelo StopLoss
   TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP,               // Posição parcialmente encerrada pelo TakeProfit
   TRADE_EVENT_POSITION_REVERSED,                           // Reversão da posição (netting)
   TRADE_EVENT_POSITION_VOLUME_ADD                          // Aumento do volume da posição (netting)
  };
//+------------------------------------------------------------------+

Aqui, nós devemos fazer alguns esclarecimentos sobre a enumeração ENUM_TRADE_EVENT.
Já que alguns eventos de negociação não requerem nenhum programa ou intervenção humana (cobrança de comissões, taxas, bônus, etc.), nós pegaremos esses dados do tipo negócio (a enumeraçãoENUM_DEAL_TYPE) na MQL5. Para simplificar o monitoramento do evento mais tarde, nós precisamos fazer com que nossos eventos correspondam ao valor da enumeração ENUM_DEAL_TYPE.
Nós vamos dividir a operação de saldo em dois eventos: recarregamento de saldo da conta e retirada de fundos. Outros eventos da enumeração do tipo negócio, a partir de DEAL_TYPE_CREDIT, tem os mesmos valores que na enumeração ENUM_DEAL_TYPE, exceto para compra e venda (DEAL_TYPE_BUY e DEAL_TYPE_SELL) que não estão relacionados às operações de saldo.

Vamos melhorar a classe de coleções de ordens e posições de mercado.
Nós vamos adicionar uma ordem de modelo para a seção privada da classe CMarketCollection para executar uma busca pelas propriedades da ordem especificada, enquanto a seção pública da classe receberá os métodos para obter a lista completa de ordens e posições, a lista de ordens e posições, que foram selecionadas por um intervalo de tempo especificado e a lista de ordens e posições retornadas, que foram selecionadas por um critério especificado de propriedades inteiras, reais e de string de uma ordem ou uma posição. Isso nos permitirá receber as listas necessárias de ordens e posições de mercado da coleção (como foi feito para a coleção de ordens históricas e negócios na parte 2 e parte 3 da descrição da biblioteca).

//+------------------------------------------------------------------+
//| Coleção de ordens e posições                                     |
//+------------------------------------------------------------------+
class CMarketCollection
  {
private:
   struct MqlDataCollection
     {
      long           hash_sum_acc;           // Soma hash de todos as ordens e posições na conta
      int            total_pending;          // Número de ordens pendentes na conta
      int            total_positions;        // Número de posições na conta
      double         total_volumes;          // Volume total de ordens e posições na conta
     };
   MqlDataCollection m_struct_curr_market;   // Dados atuais sobre as ordens e posições de mercado na conta
   MqlDataCollection m_struct_prev_market;   // Dados anteriores sobre ordens e posições de mercado na conta
   CArrayObj         m_list_all_orders;      // Lista de ordens pendentes e posições na conta
   COrder            m_order_instance;       // Objeto ordem para buscar pela propriedade
   bool              m_is_trade_event;       // Flag do evento de negociação
   bool              m_is_change_volume;     // Flag de alteração do volume total
   double            m_change_volume_value;  // Valor alterado do volume total
   int               m_new_positions;        // Número de novas posições
   int               m_new_pendings;         // Número de novas ordens pendentes
   //--- Salva os valores atuais do estado dos dados da conta como sendo o anterior
   void              SavePrevValues(void)                                                                { this.m_struct_prev_market=this.m_struct_curr_market;                  }
public:
   //--- Retorna a lista de todos as ordens pendentes e posições abertas
   CArrayObj*        GetList(void)                                                                       { return &m_list_all_orders;                                            }
   //--- Retorna a lista de ordens e posições com um horário de abertura de begin_time até end_time
   CArrayObj*        GetListByTime(const datetime begin_time=0,const datetime end_time=0);
   //--- Retorna a lista de ordens e posições pela propriedade selecionada (1) double, (2) inteiro e (3) string ajustando uma condição comparada
   CArrayObj*        GetList(ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
   CArrayObj*        GetList(ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
   CArrayObj*        GetList(ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
   //--- Retorna o número de (1) novas ordens pendentes, (2) novas posições, (3) ocorrências da flag de evento de negociação, (4) o volume alterado
   int               NewOrders(void)                                                            const    { return this.m_new_pendings;                                           }
   int               NewPosition(void)                                                          const    { return this.m_new_positions;                                          }
   bool              IsTradeEvent(void)                                                         const    { return this.m_is_trade_event;                                         }
   double            ChangedVolumeValue(void)                                                   const    { return this.m_change_volume_value;                                    }
   //--- Construtor
                     CMarketCollection(void);
   //--- Atualiza a lista de ordens pendentes e posições
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

Implementa o método para selecionar as ordens e posições pelo horário além do corpo da classe:

//+------------------------------------------------------------------------+
//| Seleciona as ordens ou posições de mercado da coleção com o horário    |
//| entre o intervalo de begin_time até end_time                           |
//+------------------------------------------------------------------------+
CArrayObj* CMarketCollection::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);
   list.FreeMode(false);
   ListStorage.Add(list);
   m_order_instance.SetProperty(ORDER_PROP_TIME_OPEN,begin);
   int index_begin=m_list_all_orders.SearchGreatOrEqual(&m_order_instance);
   if(index_begin==WRONG_VALUE)
      return list;
   m_order_instance.SetProperty(ORDER_PROP_TIME_OPEN,end);
   int index_end=m_list_all_orders.SearchLessOrEqual(&m_order_instance);
   if(index_end==WRONG_VALUE)
      return list;
   for(int i=index_begin; i<=index_end; i++)
      list.Add(m_list_all_orders.At(i));
   return list;
  }
//+------------------------------------------------------------------+

O método é quase idêntico àquele que seleciona o histórico de ordens e negócios pelo horário que descrevemos na Parte 3. Releia a descrição na seção apropriada do terceiro artigo, se necessário. A diferença entre este método e aquele da classe de coleção do histórico é que nós não selecionamos o horário que as ordens são selecionadas. As ordens e posições de mercado possuem apenas o horário de abertura.

Além disso, altere o construtor da classe de coleção do histórico de ordens e negócios. O sistema de ordens em MQL5 não possui um conceito de horário de encerramento — todas as ordens e negócios são organizados em listas de acordo com o seu horário de colocação (ou horário de abertura de acordo com o sistema de ordens em MQL4). Para fazer isso, altere a linha que define a classificação de direção na lista de coleções do histórico de ordens e negócios no construtor da classe CHistoryCollection:

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

Já em MQL5, todas as ordens e negócios na coleção do histórico de ordens e negócios serão ordenados pelo horário de colocação por padrão, enquanto em MQL4, eles serão ordenados pelo horário de encerramento definido nas propriedades da ordem.

Agora vamos passar para outro recurso do sistema de ordens em MQL5. Ao encerrar uma posição por uma posição oposta, uma ordem de encerramento especial do tipo ORDER_TYPE_CLOSE_BY é colocada, já para o encerramento de uma posição por uma ordem de stop, é colocado em seu lugar uma ordem à mercado para encerrar a posição.
A fim de considerar as ordens à mercado, nós tivemos que adicionar mais uma propriedade aos métodos de receber e retornar as propriedades inteiras da classe base order (usando o recebimento e retorno do número mágico da ordem como exemplo):

//+------------------------------------------------------------------+
//| Retorna o número mágico                                          |
//+------------------------------------------------------------------+
long COrder::OrderMagicNumber() const
  {
#ifdef __MQL4__
   return ::OrderMagicNumber();
#else
   long res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_POSITION   : res=::PositionGetInteger(POSITION_MAGIC);           break;
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_MAGIC);                 break;
      case ORDER_STATUS_DEAL              : res=::HistoryDealGetInteger(m_ticket,DEAL_MAGIC);   break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=::HistoryOrderGetInteger(m_ticket,ORDER_MAGIC); break;
      default                             : res=0;                                              break;
     }
   return res;
#endif
  }
//+------------------------------------------------------------------+

Eu fiz tais alterações (ou as que correspondem logicamente ao método) em todos os métodos de recebimento e retorno das propriedades inteiras da classe base order onde esse estado realmente precisa ser levado em consideração. Como esse estado existe, crie uma nova classe da ordem à mercado CMarketOrder na pasta Objects da biblioteca para armazenar esses tipos de ordens. A classe é completamente idêntica ao restante dos objetos do histórico de ordens e negócios criados anteriormente, assim, eu fornecerei abaixo apenas a sua inclusão:

//+------------------------------------------------------------------+
//|                                                  MarketOrder.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Arquivos de inclusão                                             |
//+------------------------------------------------------------------+
#include "Order.mqh"
//+------------------------------------------------------------------+
//| Ordem à mercado                                                  |
//+------------------------------------------------------------------+
class CMarketOrder : public COrder
  {
public:
   //--- Construtor
                     CMarketOrder(const ulong ticket=0) : COrder(ORDER_STATUS_MARKET_ORDER,ticket) {}
   //--- Propriedades das ordens suportadas do tipo (1) real, (2) inteiro
   virtual bool      SupportProperty(ENUM_ORDER_PROP_DOUBLE property);
   virtual bool      SupportProperty(ENUM_ORDER_PROP_INTEGER property);
  };
//+------------------------------------------------------------------+
//| Retorna 'true' se a ordem suportar a propriedade                 |
//| inteiro passada, caso contrário, retorna 'false'                 |
//+------------------------------------------------------------------+
bool CMarketOrder::SupportProperty(ENUM_ORDER_PROP_INTEGER property)
  {
   if(property==ORDER_PROP_TIME_EXP          || 
      property==ORDER_PROP_DEAL_ENTRY        || 
      property==ORDER_PROP_TIME_UPDATE       || 
      property==ORDER_PROP_TIME_UPDATE_MSC   ||
      property==ORDER_PROP_PROFIT_PT         ||
      property==ORDER_PROP_TIME_CLOSE        ||
      property==ORDER_PROP_TIME_CLOSE_MSC    ||
      property==ORDER_PROP_TICKET_FROM       ||
      property==ORDER_PROP_TICKET_TO
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Retorna 'true' se a ordem suportar a propriedade                 |
//| real passada, caso contrário, retorna 'false'                    |
//+------------------------------------------------------------------+
bool CMarketOrder::SupportProperty(ENUM_ORDER_PROP_DOUBLE property)
  {
   if(property==ORDER_PROP_PROFIT            || 
      property==ORDER_PROP_PROFIT_FULL       || 
      property==ORDER_PROP_SWAP              || 
      property==ORDER_PROP_COMMISSION        ||
      property==ORDER_PROP_PRICE_CLOSE       ||
      property==ORDER_PROP_SL                ||
      property==ORDER_PROP_TP                ||
      property==ORDER_PROP_PRICE_STOP_LIMIT
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+

No arquivo Defines.mqh da biblioteca, nós vamos escrever o estado da ordem à mercado:

//+------------------------------------------------------------------+
//| Abstração do tipo de ordem (estado)                              |
//+------------------------------------------------------------------+
enum ENUM_ORDER_STATUS
  {
   ORDER_STATUS_MARKET_PENDING,                             // ordem pendente
   ORDER_STATUS_MARKET_ORDER,                               // ordem à mercado
   ORDER_STATUS_MARKET_POSITION,                            // posição de mercado
   ORDER_STATUS_HISTORY_ORDER,                              // Histórico de ordens de mercado
   ORDER_STATUS_HISTORY_PENDING,                            // Ordem pendente removida
   ORDER_STATUS_BALANCE,                                    // Operação Saldo
   ORDER_STATUS_CREDIT,                                     // Operação Crédito
   ORDER_STATUS_DEAL,                                       // Negócio
   ORDER_STATUS_UNKNOWN                                     // Estado desconhecido
  };
//+------------------------------------------------------------------+

Agora, no bloco para adicionar as ordens à lista (método Refresh() da classe CMarketCollection para atualizar a lista de ordens e posições de mercado), você deve implementar a verificação do tipo da ordem. Dependendo do tipo, adicione um objeto de ordem à mercado ou um objeto de ordem pendente para a lista de coleções:

//+------------------------------------------------------------------+
//| Atualiza a lista order                                           |
//+------------------------------------------------------------------+
void CMarketCollection::Refresh(void)
  {
   ::ZeroMemory(this.m_struct_curr_market);
   this.m_is_trade_event=false;
   this.m_is_change_volume=false;
   this.m_new_pendings=0;
   this.m_new_positions=0;
   this.m_change_volume_value=0;
   m_list_all_orders.Clear();
#ifdef __MQL4__
   int total=::OrdersTotal();
   for(int i=0; i<total; i++)
     {
      if(!::OrderSelect(i,SELECT_BY_POS)) continue;
      long ticket=::OrderTicket();
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderType();
      if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL)
        {
         CMarketPosition *position=new CMarketPosition(ticket);
         if(position==NULL) continue;
         if(this.m_list_all_orders.InsertSort(position))
           {
            this.m_struct_market.hash_sum_acc+=ticket;
            this.m_struct_market.total_volumes+=::OrderLots();
            this.m_struct_market.total_positions++;
           }
         else
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить позицию в список","Failed to add position to list"));
            delete position;
           }
        }
      else
        {
         CMarketPending *order=new CMarketPending(ticket);
         if(order==NULL) continue;
         if(this.m_list_all_orders.InsertSort(order))
           {
            this.m_struct_market.hash_sum_acc+=ticket;
            this.m_struct_market.total_volumes+=::OrderLots();
            this.m_struct_market.total_pending++;
           }
         else
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
     }
//--- MQ5
#else 
//--- Posições
   int total_positions=::PositionsTotal();
   for(int i=0; i<total_positions; i++)
     {
      ulong ticket=::PositionGetTicket(i);
      if(ticket==0) continue;
      CMarketPosition *position=new CMarketPosition(ticket);
      if(position==NULL) continue;
      if(this.m_list_all_orders.InsertSort(position))
        {
         this.m_struct_curr_market.hash_sum_acc+=(long)::PositionGetInteger(POSITION_TIME_UPDATE_MSC);
         this.m_struct_curr_market.total_volumes+=::PositionGetDouble(POSITION_VOLUME);
         this.m_struct_curr_market.total_positions++;
        }
      else
        {
         ::Print(DFUN,TextByLanguage("Не удалось добавить позицию в список","Failed to add position to list"));
         delete position;
        }
     }
//--- Ordens
   int total_orders=::OrdersTotal();
   for(int i=0; i<total_orders; i++)
     {
      ulong ticket=::OrderGetTicket(i);
      if(ticket==0) continue;
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderGetInteger(ORDER_TYPE);
      if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL)
        {
         CMarketOrder *order=new CMarketOrder(ticket);
         if(order==NULL) continue;
         if(this.m_list_all_orders.InsertSort(order))
           {
            this.m_struct_curr_market.hash_sum_acc+=(long)ticket;
            this.m_struct_curr_market.total_market++;
           }
         else
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить маркет-ордер в список","Failed to add market order to list"));
            delete order;
           }
        }
      else
        {
         CMarketPending *order=new CMarketPending(ticket);
         if(order==NULL) continue;
         if(this.m_list_all_orders.InsertSort(order))
           {
            this.m_struct_curr_market.hash_sum_acc+=(long)ticket;
            this.m_struct_curr_market.total_volumes+=::OrderGetDouble(ORDER_VOLUME_INITIAL);
            this.m_struct_curr_market.total_pending++;
           }
         else
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить отложенный ордер в список","Failed to add pending order to list"));
            delete order;
           }
        }
     }
#endif 
//--- Primeiro lançamento
   if(this.m_struct_prev_market.hash_sum_acc==WRONG_VALUE)
     {
      this.SavePrevValues();
     }
//--- Se a soma hash de todas as ordens e posições mudou
   if(this.m_struct_curr_market.hash_sum_acc!=this.m_struct_prev_market.hash_sum_acc)
     {
      this.m_new_market=this.m_struct_curr_market.total_market-this.m_struct_prev_market.total_market;
      this.m_new_pendings=this.m_struct_curr_market.total_pending-this.m_struct_prev_market.total_pending;
      this.m_new_positions=this.m_struct_curr_market.total_positions-this.m_struct_prev_market.total_positions;
      this.m_change_volume_value=::NormalizeDouble(this.m_struct_curr_market.total_volumes-this.m_struct_prev_market.total_volumes,4);
      this.m_is_change_volume=(this.m_change_volume_value!=0 ? true : false);
      this.m_is_trade_event=true;
      this.SavePrevValues();
     }
  }
//+------------------------------------------------------------------+

Para poder considerar as ordens de encerramento do tipo ORDER_TYPE_CLOSE_BY, adicione este tipo de ordem ao bloco que define o tipo de ordem no método Refresh() de atualização da lista do histórico ordens e negócios da classe CHistoryCollection para que essas ordens sejam incluídas na coleção. Sem isso, o objeto base da biblioteca CEngine não consegue definir que uma posição foi encerrada por uma outra oposta:

//+------------------------------------------------------------------+
//| 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))
           {
            ::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)
        {
         CHistoryOrder *order=new CHistoryOrder(order_ticket);
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
      else
        {
         CHistoryPending *order=new CHistoryPending(order_ticket);
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
     }
//--- 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;
      this.m_list_all_orders.InsertSort(deal);
     }
//--- salva 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 
  }
//+------------------------------------------------------------------+

Ao testar a classe CEngine para definir os eventos que ocorrem na conta, eu detectei e corrigi algumas pequenas falhas nos métodos de serviço. Não há sentido em descrevê-las aqui, uma vez que elas não afetam o desempenho, enquanto sua descrição desvia a atenção do desenvolvimento de uma importante funcionalidade da biblioteca. Todas as alterações já foram feitas nas listagens das classes. Você pode vê-los nos arquivos da biblioteca anexados abaixo.


Vamos continuar nosso trabalho na definição de eventos.

Após a depuração da definição dos eventos de negociação, todos os eventos ocorridos serão compactados em uma única variável membro da classe feita como um conjunto de flags. O método de leitura de dados da variável para decompor o seu valor em componentes que caracterizam um evento específico será criado posteriormente.

Adicione a variável membro da classe para armazenar o código do evento de negociação, os métodos de verificação de um evento de negociação para as contas hedge e netting e os métodos que retornam os objetos order necessários para a seção privada da classe CEngine.
Na seção pública, declare os métodos retornando as listas de posições e ordens pendentes, histórico de ordens e negócios, o método retornando um código do evento de negociação da variável m_trade_event_code e o método retornando a flag da conta hedge.

//+------------------------------------------------------------------+
//| 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
   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
   int                  m_trade_event_code;              // Código de estado do evento de negociação da conta
//--- Retorna o índice do contador por id
   int                  CounterIndex(const int id) const;
//--- Retorna a flag da primeira execução
   bool                 IsFirstStart(void);
//--- Trabalhando com coleções (1) hedging e (2) netting
   void                 WorkWithHedgeCollections(void);
   void                 WorkWithNettoCollections(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
   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*           GetListHistoryDeals(void);                       
   CArrayObj*           GetListAllOrdersByPosID(const ulong position_id);
//--- Retorna o (1) código do evento de negociação e (2) a flag da conta hedge
   int                  TradeEventCode(void)             const { return this.m_trade_event_code;   }
   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();
  };
//+------------------------------------------------------------------+

Inicializa o código do evento de negociação na lista de inicialização do construtor da classe.

//+------------------------------------------------------------------+
//| Construtor CEngine                                               |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),m_trade_event_code(TRADE_EVENT_FLAG_NO_EVENT)
  {
   ::EventSetMillisecondTimer(TIMER_FREQUENCY);
   this.m_list_counters.Sort();
   this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE);
   this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
  }
//+------------------------------------------------------------------+

Implementa os métodos declarados fora do corpo da classe:

//+------------------------------------------------------------------+
//| 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::GetListHistoryDeals(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.GetListHistoryDeals();
   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 à mercado 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) return NULL;
   list.Sort(SORT_BY_ORDER_TICKET);
   COrder* order=list.At(list.Total()-1);
   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);
  }
//+------------------------------------------------------------------+

Vamos ver como as listas são obtidas usando o seguinte exemplo:

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

Tudo é bem simples e conveniente: recebemos a lista completa das posições da coleção de ordens e posições de mercado usando o método da coleção GetList(). Após isso, selecione uma ordem com o estado "position" dele usando o método de seleção de ordens por uma propriedade especificada da classe CSelect. O método foi descrito no terceiro artigo da descrição da biblioteca. Retorna a lista obtida.
A lista pode estar vazia (NULL), portanto, o resultado retornado por este método deve ser verificado no programa que o chamou.

Vamos ver como receber a ordem desejada usando o seguinte exemplo:

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

Primeiro, recebemos a lista de posições usando o método GetListMarketPosition() descrito acima. Se a lista estiver vazia, é retornado NULL. Em seguida, a lista por horário de abertura em milissegundos é ordenada (uma vez que vamos receber a última posição em aberto, a lista deve ser ordenada por horário) e então, selecionado a última ordem. Como resultado, nós retornamos a ordem obtida da lista para o programa que fez a chamada.
O resultado da busca de uma ordem retornada pelo método pode ser vazio (a ordem não foi encontrada) e ser igual a NULL. Portanto, verifique o resultado obtido para NULL antes de acessá-lo.

Como você pode ver, tudo é rápido e fácil. Nós podemos construir qualquer método para receber os dados de qualquer lista de coleções existente, bem como os que nós criaremos no futuro. Isso nos proporciona maior flexibilidade no uso de tais listas.
Os métodos para receber as listas são colocados na seção da classe pública, permitindo-nos receber quaisquer dados das listas em programas personalizados, como foi feito com os métodos considerados acima.
Os métodos para receber as ordens desejadas estão ocultos na seção privada. Eles são necessários apenas na classe CEngine para as necessidades internas — em particular, para a obtenção dos dados sobre as últimas ordens, negócios e posições. Em programas personalizados, nós podemos desenvolver as funções personalizadas para receber as ordens específicas pelas propriedades especificadas (os exemplos são exibidos acima).
Para obter facilmente quaisquer dados das coleções, nós criaremos, eventualmente, uma ampla gama de funções para os usuários finais.
Isso será feito nos artigos finais dedicados ao trabalho com as coleções de ordens.

Agora vamos implementar o método para verificar os eventos de negociação.
Atualmente, ele funciona apenas em contas hedging para a MQL5. Após isso, eu vou desenvolver os métodos para verificar os eventos de negociação para as contas netting para MQL5 e MQL4. Para testar a operação do método, ele apresenta a exibição da verificação e dos resultados do evento. Essa funcionalidade deve ser removida posteriormente, pois essa função deve ser preenchida por outro método que será criado após a depuração do método de verificação de eventos de negociação na conta.

//+------------------------------------------------------------------+
//| Verificação dos eventos de negociação (hedging)                  |
//+------------------------------------------------------------------+
void CEngine::WorkWithHedgeCollections(void)
  {
//--- Inicialização do código de flags e eventos de negociação
   this.m_trade_event_code=TRADE_EVENT_FLAG_NO_EVENT;
   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 a dinâmica do estado de mercado e o histórico da conta
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();
//---
#ifdef __MQL4__

#else // MQL5
//--- Se um evento é apenas em ordens e posições de mercado
   if(this.m_is_market_trade_event && !this.m_is_history_trade_event)
     {
      Print(DFUN,TextByLanguage("Новое торговое событие на счёте","New trading event on account"));
      //--- Se o número de ordens pendentes aumentou
      if(this.m_market.NewPendingOrders()>0)
        {
         //--- Adiciona a flag para instalar uma ordem pendente
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_PLASED;
         string text=TextByLanguage("Установлен отложенный ордер: ","Pending order placed: ");
         //--- Pega a última ordem pendente
         COrder* order=this.GetLastMarketPending();
         if(order!=NULL)
           {
            //--- adiciona o ticket da ordem à mensagem
            text+=order.TypeDescription()+" #"+(string)order.Ticket();
           }
         //--- Adiciona a mensagem ao diário
         Print(DFUN,text);
        }
      //--- Se o número de ordens à mercado aumentou
      if(this.m_market.NewMarketOrders()>0)
        {
         //--- não adiciona a flag de evento
         //--- ...
         string text=TextByLanguage("Выставлен маркет-ордер: ","Market order placed: ");
         //--- Pega a última ordem à mercado
         COrder* order=this.GetLastMarketOrder();
         if(order!=NULL)
           {
            //--- adiciona o ticket da ordem à mensagem
            text+=order.TypeDescription()+" #"+(string)order.Ticket();
           }
         //--- Adiciona a mensagem ao diário
         Print(DFUN,text);
        }
     }
   
//--- Se um evento é apenas no histórico de ordens e negócios
   else if(this.m_is_history_trade_event && !this.m_is_market_trade_event)
     {
      Print(DFUN,TextByLanguage("Новое торговое событие в истории счёта","New trading event in account history"));
      //--- Se um novo negócio aparecer
      if(this.m_history.NewDeals()>0)
        {
         //--- Adiciona a flag de evento do saldo da conta
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ACCOUNT_BALANCE;
         string text=TextByLanguage("Новая сделка: ","New deal: ");
         //--- Pega o último negócio
         COrder* deal=this.GetLastDeal();
         if(deal!=NULL)
           {
            //--- adiciona sua descrição ao texto
            text+=deal.TypeDescription();
            //--- se o negócio é uma operação de saldo
            if((ENUM_DEAL_TYPE)deal.GetProperty(ORDER_PROP_TYPE)==DEAL_TYPE_BALANCE)
              {
              //--- verifica o lucro do negócio e adiciona o evento (adicionando ou retirando fundos) à mensagem
               text+=(deal.Profit()>0 ? TextByLanguage(": Пополнение счёта: ",": Account Recharge: ") : TextByLanguage(": Вывод средств: ",": Withdrawal: "))+::DoubleToString(deal.Profit(),(int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS));
              }
           }
         //--- Exibe a mensagem no diário
         Print(DFUN,text);
        }
     }
   
//--- Se os eventos estiverem no histórico de ordens à mercado e posições
   else if(this.m_is_market_trade_event && this.m_is_history_trade_event)
     {
      Print(DFUN,TextByLanguage("Новые торговые события на счёте и в истории счёта","New trading events on account and in account history"));
      
      //--- Se o número de ordens pendentes diminuiu e nenhum novo negócio apareceu
      if(this.m_market.NewPendingOrders()<0 && this.m_history.NewDeals()==0)
        {
         //--- Adiciona o sinalizador para remover uma ordem pendente
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_REMOVED;
         string text=TextByLanguage("Удалён отложенный ордер: ","Removed pending order: ");
         //--- Pega a última ordem pendente do histórico
         COrder* order=this.GetLastHistoryPending();
         if(order!=NULL)
           {
            //--- adiciona o ticket à mensagem apropriada
            text+=order.TypeDescription()+" #"+(string)order.Ticket();
           }
         //--- Exibe a mensagem no diário
         Print(DFUN,text);
        }
      
      //--- Se houver um novo negócio e uma nova ordem no histórico
      if(this.m_history.NewDeals()>0 && this.m_history.NewOrders()>0)
        {
         //--- Pega o último negócio
         COrder* deal=this.GetLastDeal();
         if(deal!=NULL)
           {
            //--- No caso de um negócio de entrada à mercado
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_IN)
              {
               //--- Adiciona o flag de abertura de posição
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_OPENED;
               string text=TextByLanguage("Открыта позиция: ","Position opened: ");
               //--- Se o número de ordens pendentes diminuiu
               if(this.m_market.NewPendingOrders()<0)
                 {
                  //--- Adiciona a flag de ativação da ordem pendente
                  this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_ACTIVATED;
                  text=TextByLanguage("Сработал отложенный ордер: ","Pending order activated: ");
                 }
               //--- Pega o ticket da ordem na ordem
               ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(order_ticket);
               if(order!=NULL)
                 {
                  //--- Se o volume atual da ordem exceder zero
                  if(order.VolumeCurrent()>0)
                    {
                     //--- Adiciona a flag de execução parcial
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                     text=TextByLanguage("Частично открыта позиция: ","Position partially open: ");
                    }
                  //--- adiciona a direção da ordem à mensagem
                  text+=order.DirectionDescription();
                 }
               //--- adiciona o ticket da posição do negócio à mensagem e exibe a mensagem no diário
               text+=" #"+(string)deal.PositionID();
               Print(DFUN,text);
              }
            
            //--- No caso de um negócio de saída à mercado
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT)
              {
               //--- Adiciona a flag de encerramento de posição
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED;
               string text=TextByLanguage("Закрыта позиция: ","Position closed: ");
               //--- Pega o ticket da ordem do negócio
               ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(order_ticket);
               if(order!=NULL)
                 {
                  //--- Se a posição do negócio ainda estiver ativa no mercado
                  COrder* pos=this.GetPosition(deal.PositionID());
                  if(pos!=NULL)
                    {
                     //--- Adiciona a flag de execução parcial
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                     text=TextByLanguage("Частично закрыта позиция: ","Partially closed position: ");
                    }
                  //--- Caso contrário, se a posição estiver totalmente encerrada
                  else
                    {
                     //--- Se a ordem tiver a flag de encerramento por StopLoss
                     if(order.IsCloseByStopLoss())
                       {
                        //--- Adicione a flag para encerrar por StopLoss
                        this.m_trade_event_code+=TRADE_EVENT_FLAG_SL;
                        text=TextByLanguage("Позиция закрыта по StopLoss: ","Position closed by StopLoss: ");
                       }
                     //--- Se a ordem tiver a flag de encerramento por TakeProfit
                     if(order.IsCloseByTakeProfit())
                       {
                        //--- Adiciona o sinalizador para encerrar por TakeProfit
                        this.m_trade_event_code+=TRADE_EVENT_FLAG_TP;
                        text=TextByLanguage("Позиция закрыта по TakeProfit: ","Position closed by TakeProfit: ");
                       }
                    }
                  //--- adiciona a direção da ordem inversa à mensagem:
                  //--- encerra a ordem de Compra para a posição vendida e encerra a ordem de venda para a posição de compra,
                  //--- portanto, inverte a direção da ordem para a descrição correta de uma posição fechada
                  text+=(order.DirectionDescription()=="Sell" ? "Buy " : "Sell ");
                 }
               //--- adiciona o ticket da posição do negócio e exibe a mensagem no diário
               text+="#"+(string)deal.PositionID();
               Print(DFUN,text);
              }
            
            //--- Ao encerrar por uma posição oposta
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT_BY)
              {
               //--- Adiciona a flag de encerramento de posição
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED;
               //--- Adiciona a flag para encerrar por uma posição oposta
               this.m_trade_event_code+=TRADE_EVENT_FLAG_BY_POS;
               string text=TextByLanguage("Позиция закрыта встречной: ","Position closed by opposite position: ");
               //--- Pega a ordem do negócio
               ulong ticket_from=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(ticket_from);
               if(order!=NULL)
                 {
                  //--- adiciona a direção da ordem inversa à mensagem:
                  //--- encerra a ordem de Compra para a posição vendida e encerra a ordem de venda para a posição de compra,
                  //--- portanto, inverte a direção da ordem para a descrição correta de uma posição fechada
                  text+=(order.DirectionDescription()=="Sell" ? "Buy" : "Sell");
                  text+=" #"+(string)order.PositionID()+TextByLanguage(" закрыта позицией #"," closed by position #")+(string)order.PositionByID();
                  //--- Se a posição da ordem ainda estiver presente no mercado
                  COrder* pos=this.GetPosition(order.PositionID());
                  if(pos!=NULL)
                    {
                     //--- Adiciona a flag de execução parcial
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                     text+=TextByLanguage(" частично"," partially");
                    }
                 }
               //--- Exibe a mensagem no diário
               Print(DFUN,text);
              }
           //--- fim do último bloco de processamento do negócio
           }
        }
     }
#endif 
  }
//+------------------------------------------------------------------+

O método é simples, mas bem amplo. Portanto, o código apresenta todas as verificações e ações correspondentes, permitindo-nos ver o que acontece dentro do método de forma mais conveniente. Por enquanto, o método implementa a verificação da conta hedge em MQL5. Na verdade, tudo se resume a verificar o número de novas ordens e negócios do histórico da conta ou no mercado. Para a exibição no diário, os dados são retirados da última posição, do último negócio, da última ordem ou da última ordem do negócio — tudo isso é feito para exibir os dados no diário para verificar a execução do código. Esta funcionalidade será posteriormente removida do código e substituída por um único método a ser usado nos programas que trabalham com a biblioteca.

Teste do processamento dos eventos de negociação

Vamos criar um EA de teste para verificar o método que define os eventos de negociação na conta.
Vamos implementar o conjunto de botões para gerenciar os novos eventos.
O conjunto de ações necessárias e os botões correspondentes são os seguintes:

  • Abrir uma posição de Compra
  • Colocar uma ordem pendente BuyLimit
  • Colocar uma ordem pendente BuyStop
  • Colocar uma ordem pendente BuyStopLimit
  • Encerrar uma posição de Compra
  • Encerrar metade da posição de Compra
  • Encerrar uma posição de Compra por uma posição oposta de Venda
  • Abrir uma posição de Venda
  • Colocar uma ordem pendente SellLimit
  • Colocar uma ordem pendente SellStop
  • Colocar uma ordem pendente SellStopLimit
  • Encerrar uma posição de Venda
  • Encerrar metade da posição de Venda
  • Encerrar uma posição de Venda por uma posição oposta de Compra
  • Encerrar todas as posições
  • Retirar fundos da conta

O conjunto de entradas é o seguinte:

  • Magic number - número mágico
  • Lots - volume das posições em aberto
  • StopLoss in points
  • TakeProfit in points
  • Pending orders distance (points)
  • StopLimit orders distance (points)
    Uma ordem StopLimit é colocada como uma ordem de stop a uma distância do preço definido pelo valor da Pending orders distance.
    Assim que o preço atinge a ordem especificada e o ativa, esse nível de preço é usado para colocar uma ordem limitada à distância do preço definido pelo valor da StopLimit orders distance.
  • Slippage in points
  • Withdrawal funds (in tester)) - retirada de fundos da conta no testador

Nós vamos precisar das funções para calcular os valores corretos para definir os preços para a colocação das ordens em relação ao StopLevel, volume das ordens de stop e posição. Neste estágio, vamos adicionar as funções à biblioteca de funções de serviço no arquivo DELIB.mqh, desde que ainda não tenha classes de negociação e classes de símbolos:

//+------------------------------------------------------------------+
//| Retorna o lote mínimo de símbolos                                |
//+------------------------------------------------------------------+
double MinimumLots(const string symbol_name) 
  { 
   return SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MIN);
  }
//+------------------------------------------------------------------+
//| Retorna o lote máximo de símbolos                                |
//+------------------------------------------------------------------+
double MaximumLots(const string symbol_name) 
  { 
   return SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MAX);
  }
//+------------------------------------------------------------------+
//| Retorna o passo de alteração do lote de símbolo                  |
//+------------------------------------------------------------------+
double StepLots(const string symbol_name) 
  { 
   return SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_STEP);
  }
//+------------------------------------------------------------------+
//| Retorna o lote normalizado                                       |
//+------------------------------------------------------------------+
double NormalizeLot(const string symbol_name, double order_lots) 
  {
   double ml=SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MIN);
   double mx=SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MAX);
   double ln=NormalizeDouble(order_lots,int(ceil(fabs(log(ml)/log(10)))));
   return(ln<ml ? ml : ln>mx ? mx : ln);
  }
//+------------------------------------------------------------------+
//| Retorna o StopLoss correto em relação ao StopLevel               |
//+------------------------------------------------------------------+
double CorrectStopLoss(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double stop_loss,const int spread_multiplier=2)
  {
   if(stop_loss==0) return 0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT);
   double price=(order_type==ORDER_TYPE_BUY ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : order_type==ORDER_TYPE_SELL ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price_set);
   return
     (order_type==ORDER_TYPE_BUY       || 
      order_type==ORDER_TYPE_BUY_LIMIT || 
      order_type==ORDER_TYPE_BUY_STOP
      #ifdef __MQL5__                  ||
      order_type==ORDER_TYPE_BUY_STOP_LIMIT
      #endif ? 
      NormalizeDouble(fmin(price-lv*pt,stop_loss),dg) :
      NormalizeDouble(fmax(price+lv*pt,stop_loss),dg)
     );
  }
//+------------------------------------------------------------------+
//| Retorna o StopLoss correto em relação ao StopLevel               |
//+------------------------------------------------------------------+
double CorrectStopLoss(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const int stop_loss,const int spread_multiplier=2)
  {
   if(stop_loss==0) return 0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT);
   double price=(order_type==ORDER_TYPE_BUY ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : order_type==ORDER_TYPE_SELL ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price_set);
   return
     (order_type==ORDER_TYPE_BUY       || 
      order_type==ORDER_TYPE_BUY_LIMIT || 
      order_type==ORDER_TYPE_BUY_STOP
      #ifdef __MQL5__                  ||
      order_type==ORDER_TYPE_BUY_STOP_LIMIT
      #endif ?
      NormalizeDouble(fmin(price-lv*pt,price-stop_loss*pt),dg) :
      NormalizeDouble(fmax(price+lv*pt,price+stop_loss*pt),dg)
     );
  }
//+------------------------------------------------------------------+
//| Retorna o TakeProfit correto em relação ao StopLevel             |
//+------------------------------------------------------------------+
double CorrectTakeProfit(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double take_profit,const int spread_multiplier=2)
  {
   if(take_profit==0) return 0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT);
   double price=(order_type==ORDER_TYPE_BUY ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : order_type==ORDER_TYPE_SELL ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price_set);
   return
     (order_type==ORDER_TYPE_BUY       || 
      order_type==ORDER_TYPE_BUY_LIMIT || 
      order_type==ORDER_TYPE_BUY_STOP
      #ifdef __MQL5__                  ||
      order_type==ORDER_TYPE_BUY_STOP_LIMIT
      #endif ?
      NormalizeDouble(fmax(price+lv*pt,take_profit),dg) :
      NormalizeDouble(fmin(price-lv*pt,take_profit),dg)
     );
  }
//+------------------------------------------------------------------+
//| Retorna o TakeProfit correto em relação ao StopLevel             |
//+------------------------------------------------------------------+
double CorrectTakeProfit(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const int take_profit,const int spread_multiplier=2)
  {
   if(take_profit==0) return 0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT);
   double price=(order_type==ORDER_TYPE_BUY ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : order_type==ORDER_TYPE_SELL ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price_set);
   return
     (order_type==ORDER_TYPE_BUY       || 
      order_type==ORDER_TYPE_BUY_LIMIT || 
      order_type==ORDER_TYPE_BUY_STOP
      #ifdef __MQL5__                  ||
      order_type==ORDER_TYPE_BUY_STOP_LIMIT
      #endif ?
      ::NormalizeDouble(::fmax(price+lv*pt,price+take_profit*pt),dg) :
      ::NormalizeDouble(::fmin(price-lv*pt,price-take_profit*pt),dg)
     );
  }
//+------------------------------------------------------------------+
//| Retorna o preço para a colocação correta da ordem                |
//| relativo ao StopLevel                                            |
//+------------------------------------------------------------------+
double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double price=0,const int spread_multiplier=2)
  {
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   switch(order_type)
     {
      case ORDER_TYPE_BUY_LIMIT        :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg);
      case ORDER_TYPE_BUY_STOP         :  
      case ORDER_TYPE_BUY_STOP_LIMIT   :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg);
      case ORDER_TYPE_SELL_LIMIT       :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg);
      case ORDER_TYPE_SELL_STOP        :  
      case ORDER_TYPE_SELL_STOP_LIMIT  :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg);
      default                          :  Print(DFUN,TextByLanguage("Не правильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0;
     }
  }
//+------------------------------------------------------------------+
//| Retorna o preço para a colocação correta da ordem                |
//| relativo ao StopLevel                                            |
//+------------------------------------------------------------------+
double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const int distance_set,const double price=0,const int spread_multiplier=2)
  {
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   switch(order_type)
     {
      case ORDER_TYPE_BUY_LIMIT        :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg);
      case ORDER_TYPE_BUY_STOP         :  
      case ORDER_TYPE_BUY_STOP_LIMIT   :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg);
      case ORDER_TYPE_SELL_LIMIT       :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg);
      case ORDER_TYPE_SELL_STOP        :  
      case ORDER_TYPE_SELL_STOP_LIMIT  :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg);
      default                          :  Print(DFUN,TextByLanguage("Не правильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0;
     }
  }
//+------------------------------------------------------------------+
//| Verifica o nível de stop em pontos em relação ao StopLevel       |
//+------------------------------------------------------------------+
bool CheckStopLevel(const string symbol_name,const int stop_in_points,const int spread_multiplier)
  {
   return(stop_in_points>=StopLevel(symbol_name,spread_multiplier));
  }
//+------------------------------------------------------------------+
//| Retorna o StopLevel em pontos                                    |
//+------------------------------------------------------------------+
int StopLevel(const string symbol_name,const int spread_multiplier)
  {
   int spread=(int)SymbolInfoInteger(symbol_name,SYMBOL_SPREAD);
   int stop_level=(int)SymbolInfoInteger(symbol_name,SYMBOL_TRADE_STOPS_LEVEL);
   return(stop_level==0 ? spread*spread_multiplier : stop_level);
  }
//+------------------------------------------------------------------+ 

As funções simplesmente calculam os valores corretos para que eles não violem as limitações definidas no servidor. Além do símbolo, a função de cálculo do StopLevel também recebe o multiplicador de spread. Isso é feito porque se o StopLevel no servidor estiver definido como zero, isso significa um nível flutuante e, para calcular o StopLevel, nós devemos usar um valor de spread multiplicado por um determinado número (geralmente 2, mas 3 também é possível). Esse multiplicador é passado para a função, permitindo-nos codificá-lo nas configurações do EA ou calculá-lo.

Por uma questões de economia de nosso tempo, nós não iremos escrever as funções de negociação personalizadas. Em vez disso, nós vamos usar a classes de negociação da biblioteca padrão, ou seja, a classe CTrade para executar as operações de negociação.
Para criar os botões do gráfico de trabalho, nós vamos implementar uma enumeração com os seus membros para definição dos nomes, rótulos e valores dos botões para verificar um determinado botão que foi pressionado.

Na pasta MQL5\Experts\TestDoEasy\Part04\, crie o novo EA chamado TestDoEasy04.mqh (verifique os manipuladores de eventos OnTimer e OnChartEvent no Assistente MQL ao criar o EA):


Após a criação do modelo do EA pelo Assistente MQL, inclua a biblioteca customizada e a biblioteca padrão da classe de negociação nele. Além disso, adicione os parâmetros de entrada:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart04.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_CLOSE_ALL,
   BUTT_PROFIT_WITHDRAWAL
  };
#define TOTAL_BUTT   (16)
//--- 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)
//--- 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;
//+------------------------------------------------------------------+

Aqui nós incluímos o objeto principal da biblioteca CEngine e a classe de negociação CTrade.
Em seguida, crie a enumeração especificando todos os botões necessários.
A sequência de métodos d enumeração é importante, pois define a ordem de criação dos botões e sua localização no gráfico.

Após isso, declare a estrutura para armazenar o nome do objeto gráfico do botão e o texto a ser inserido no botão.
No bloco de entradas, defina todas as variáveis do parâmetro do EA listados acima. No bloco de variáveis globais do EA, declare o objeto da biblioteca, objeto da classe de negociação, array de estruturas do botão e as variáveis que devem ser atribuído aos valores de entrada no manipulador da OnInit():

//+------------------------------------------------------------------+
//| 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())  
      return INIT_FAILED;
//---
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_ERRORS);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

No manipulador da OnInit(), verificamos o tipo de conta e, se não for do tipo hedging, informamos isso e saímos do programa com um erro.
Em seguida, é definido o prefixo de nomes de objetos (para que o EA seja capaz de reconhecer seus objetos) e preenchido o array de estruturas com os dados do botão em um loop pelo número de botões.
O nome do objeto de botão é definido como uma representação do prefixo+string da enumeração ENUM_BUTTONS correspondente ao índice do loop, enquanto o texto do botão é compilado, transformando a representação dos caracteres da enumeração correspondente ao índice do loop usando a função EnumToButtText().

O lote das posições em abertas e das ordens colocadas é calculado mais para frente. Como metade das posições está encerrada, o lote de uma posição aberta deve ser pelo menos o dobro do lote mínimo. Portanto, o lote máximo é retirado de dois:
1) inserido nos parâmetros de entrada, 2) lote mínimo multiplicado por dois na linha fmax(InpLots,MinimumLots(Symbol())*2.0), o valor obtido do lote é normalizado e atribuído à variável global lot. Como resultado, se o lote inserido pelo usuário nos parâmetros de entrada for menor que o dobro do lote mínimo, será usado o dobro do lote mínimo. Caso contrário, será aplicado o lote inserido pelo usuário.

As entradas restantes são atribuídas às variáveis globais apropriadas e a função CreateButtons() é chamada para criar os botões do array de estrutura já preenchido com os dados do botão durante a etapa anterior. Se a criação do botão usando a função ButtonCreate() falhar, a mensagem de erro é exibida e a a operação do programa termina com um erro de inicialização.

Finalmente, a classe CTrade é inicializada:

//---
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_ERRORS);
//---
  • Define o desvio em pontos
  • define o número mágico,
  • define o tipo de execução da ordem de acordo com as configurações do símbolo atual,
  • define o modo de cálculo da margem de acordo com as configurações da conta atual
  • coloca o nível de registro das mensagens para exibir apenas as mensagens de erro no diário
    (o modo de registro completo é ativado automaticamente no testador).

No manipulador OnDeinit(), implemente a remoção de todos os botões pelo prefixo do nome do objeto:

//+------------------------------------------------------------------+
//| Função de desinicialização do Expert                             |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- remove os objetos
   ObjectsDeleteAll(0,prefix);
  }
//+------------------------------------------------------------------+

Agora vamos decidir sobre o timer da biblioteca e a ordem de inicialização do manipulador de eventos do EA.

  • Se o EA não for iniciado no testador, o timer da biblioteca deve ser iniciado a partir do timer do EA, enquanto o manipulador de eventos trabalha no modo normal.
  • Se o EA for iniciado no testador, o timer da biblioteca deve ser iniciado a partir do manipulador OnTick() do EA. O evento de pressionamento do botão também é monitorado na OnTick().

Manipuladores OnTick(), OnTimer() e OnChartEvent() do EA:

//+------------------------------------------------------------------+
//| Função Tick do Expert                                            |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   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);
     }
  }
//+------------------------------------------------------------------+
//| 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);
     }  
  }
//+------------------------------------------------------------------+

Na OnTick()

  • Verifica onde o EA é iniciado. Se ele foi iniciado no testador, chame o manipulador OnTimer() da biblioteca.
  • Em seguida, verifica o nome do objeto por todos os objetos do gráfico atual no loop. Se ele corresponder ao nome de um botão, o manipulador do pressionamento do botão é chamado.

Na OnTimer()

  • Verifica onde o EA é iniciado. Se ele não foi iniciado no testador, chame o manipulador OnTimer() da biblioteca.

Na OnChartEvent()

  • Verifica onde o EA é iniciado. Se ele foi iniciado no testador, sai do manipulador.
  • Em seguida, o ID do evento é verificado e, se esse for o evento de clique em um objeto gráfico e o nome do objeto contiver um texto pertencente aos botões, o manipulador de pressionamento do botão correspondente é chamado.

Função CreateButtons():

//+------------------------------------------------------------------+
//| Cria o painel de botões                                          |
//+------------------------------------------------------------------+
bool CreateButtons(void)
  {
   int h=18,w=84,offset=10;
   int cx=offset,cy=offset+(h+1)*(TOTAL_BUTT/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-2) x=cx;
      y=(cy-(i-(i>6 ? 7 : 0))*(h+1));
      if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-2 ? 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;
  }
//+------------------------------------------------------------------+

Nesta função, tudo se resume a calcular as coordenadas e cores de um botão em um loop pelo número de membros da enumeração ENUM_BUTTONS. As coordenadas e a cor são calculadas com base no índice do loop indicando o número do membro da enumeração ENUM_BUTTONS. Após o cálculo das coordenadas x e y, a função de criação do botão com as coordenadas e valores da cor calculados no loop é chamada.

Função EnumToButtText():

//+------------------------------------------------------------------+
//| Converte a enumeração no 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 ");
   return txt;
  }
//+------------------------------------------------------------------+

Tudo é simples aqui: a função recebe o membro da enumeração e converte-o em uma string removendo o texto desnecessário. Em seguida, todos os caracteres da string obtida são convertidos em minúsculas e todas as entradas inadequadas são substituídas com aquelas necessárias.
A string de enumeração de entrada convertida no texto é finalmente retornada.

As funções para criação do botão, colocação e obtenção de seu estado:

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

Tudo é simples e claro aqui, portanto, nenhuma explicação é necessária.

A função para manipular os botões pressionados:

//+------------------------------------------------------------------+
//| Manipulação dos botões pressionados                              |
//+------------------------------------------------------------------+
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 uma BuyLimit
      else if(button==EnumToString(BUTT_BUY_LIMIT))
        {
         //--- Obtém o posicionamento correto 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 de colocação da ordem 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 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 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 colocação da ordem relativo 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 é pressionado: encerra 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.VolumeCurrent()/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 as posições de Venda somente 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 posição oposta de Venda
               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.VolumeCurrent()/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();
         //--- Obtém apenas as posições de Venda na 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 posição de Compra oposta
               trade.PositionCloseBy(position_sell.Ticket(),position_buy.Ticket());
              }
           }
        }
      //--- Se o botã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))
        {
         //--- Recebe 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_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();
     }
  }
//+------------------------------------------------------------------+

A função é bem grande, mas simples: ela recebe o nome do objeto do botão a ser convertido em um ID de string. O estado do botão é verificado em seguida e, se for pressionado, a ID da string é verificada. A ramificação if-else apropriada é executada calculando todos os níveis e aplicando um ajuste necessário para não violar a limitação do StopLevel. O método correspondente da classe de negociação é executado.
Todas as explicações são escritas diretamente nas linhas de comentários do código.

Para o EA de teste, nós fizemos as verificações mínimas necessárias, ignorando todas as outras verificações importantes para uma conta real. Atualmente, o mais importante para nós é verificar a operação da biblioteca, em vez de desenvolver um EA para trabalhar com ele em contas reais.

A listagem completa do EA de teste:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart04.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_CLOSE_ALL,
   BUTT_PROFIT_WITHDRAWAL
  };
#define TOTAL_BUTT   (16)
//--- 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)
//--- 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())
      return INIT_FAILED;
//--- define 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);
  }
//+------------------------------------------------------------------+
//| Função Tick do Expert                                            |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   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.TradeEventCode()!=TRADE_EVENT_FLAG_NO_EVENT)
     {
      
      Print(DFUN,EnumToString((ENUM_TRADE_EVENT_FLAGS)engine.TradeEventCode()));
     }
   engine.TradeEventCode();
  }
//+------------------------------------------------------------------+
//| 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);
     }  
  }
//+------------------------------------------------------------------+
//| Cria o painel de botões                                          |
//+------------------------------------------------------------------+
bool CreateButtons(void)
  {
   int h=18,w=84,offset=10;
   int cx=offset,cy=offset+(h+1)*(TOTAL_BUTT/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-2) x=cx;
      y=(cy-(i-(i>6 ? 7 : 0))*(h+1));
      if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-2 ? 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 ");
   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.VolumeCurrent()/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.VolumeCurrent()/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_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();
     }
  }
//+------------------------------------------------------------------+

O código parece bem longo... No entanto, isso é apenas o começo, pois tudo deve ser simplificado para os usuários finais. A maioria das ações que realizamos aqui deve ser ocultada no código da biblioteca, enquanto o mecanismo mais amigável de interação com a biblioteca deve ser introduzido.

Vamos iniciar o EA no testador e experimentar os botões:

Tudo está ativado corretamente e o diário recebe as mensagens sobre os eventos ocorridos.

Atualmente, o último evento é sempre fixo. Em outras palavras, se nós fecharmos várias posições simultaneamente, apenas a última posição dentre as várias fechadas se encontrará no evento. O fechamento em massa pode ser monitorado pelo número de novos negócios ou ordens do histórico. É possível, então, obter a lista de todas as posições encerradas recentemente por seu número e definir o seu conjunto inteiro. Vamos desenvolver uma classe de coleção de eventos separada para isso. Isso nos permitirá ter acesso constante a todos os eventos ocorridos no programa.

Atualmente, todas as mensagens de evento no diário de teste são exibidas no método CEngine::WorkWithHedgeCollections() do objeto base da biblioteca, e nós precisamos que o programa personalizado conheça os códigos de evento para "entender" o que aconteceu na conta. Isso nos permitirá formar a lógica de resposta do programa, dependendo do evento. Para testar a capacidade de conseguir isso, nós criaremos dois métodos no objeto base da biblioteca. Um método é armazenar o código do último evento, enquanto o outro é decodificar esse código consistindo em um conjunto de sinalizadores de evento.
No próximo artigo, nós criaremos uma classe completa para trabalhar com os eventos da conta.

No corpo da classe CEngine, defina o método de decodificação do código de evento e defina o código de evento de negociação da conta, o método de verificar a presença de uma flag de evento no código do evento, o método para receber o último evento de negociação do programa que realizou a chamada, bem como o método de redefinição do valor do último evento de negociação:

//+------------------------------------------------------------------+
//| 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
   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
   int                  m_trade_event_code;              // Código do estado do evento de negociação da conta
   ENUM_TRADE_EVENT     m_acc_trade_event;               // Evento de negociação da conta
//--- Decodifica o código do evento e define o evento de negociação na conta
   void                 SetTradeEvent(void);
//--- Retorna o índice do contador por id
   int                  CounterIndex(const int id) const;
//--- Retorna a (1) primeira flag de ativação, (2) presença da flag no evento de negociação
   bool                 IsFirstStart(void);
   bool                 IsTradeEventFlag(const int event_code)    const { return (this.m_trade_event_code&event_code)==event_code;  }
//--- Trabalhar com as coleções de (1) hedging, (2) netting
   void                 WorkWithHedgeCollections(void);
   void                 WorkWithNettoCollections(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
   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*           GetListHistoryDeals(void);
   CArrayObj*           GetListAllOrdersByPosID(const ulong position_id);
//--- Redefine o último evento de negociação
   void                 ResetLastTradeEvent(void)                       { this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;  }
//--- Retorna o (1) último evento de negociação, (2) o código de evento de negociação, (3) a flag de conta de hedge
   ENUM_TRADE_EVENT     LastTradeEvent(void)                      const { return this.m_acc_trade_event;                }
   int                  TradeEventCode(void)                      const { return this.m_trade_event_code;               }
   bool                 IsHedge(void)                             const { return this.m_is_hedge;                       }
//--- Cria o contador do timer
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- Timer
   void                 OnTimer(void);
//--- Construtor/destrutor
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+

Além do corpo da classe, escrevemos o método para decodificar um evento de negociação (escrevemos todos os esclarecimentos diretamente no código):

//+------------------------------------------------------------------+
//| Decodifica o código do evento e define um evento de negociação   |
//+------------------------------------------------------------------+
void CEngine::SetTradeEvent(void)
  {
//--- Nenhum evento de negociação. Exit
   if(this.m_trade_event_code==TRADE_EVENT_FLAG_NO_EVENT)
      return;
//--- A ordem pendente está definida (verifica se o código do evento é correspondido, pois pode haver apenas uma flag aqui)
   if(this.m_trade_event_code==TRADE_EVENT_FLAG_ORDER_PLASED)
     {
      this.m_acc_trade_event=TRADE_EVENT_PENDING_ORDER_PLASED;
      Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
      return;
     }
//--- A ordem pendente é removida (verifica se o código do evento é correspondido, pois pode haver apenas uma flag aqui)
   if(this.m_trade_event_code==TRADE_EVENT_FLAG_ORDER_REMOVED)
     {
      this.m_acc_trade_event=TRADE_EVENT_PENDING_ORDER_REMOVED;
      Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
      return;
     }
//--- A posição é aberta (verifique se há várias flags no código do evento)
   if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_POSITION_OPENED))
     {
      //--- Se esta ordem pendente for ativada pelo preço
      if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED))
        {
         this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_PENDING_ORDER_ACTIVATED : TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL);
         Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
         return;
        }
      this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_OPENED : TRADE_EVENT_POSITION_OPENED_PARTIAL);
      Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
      return;
     }
//--- A posição está encerrada (verifica se há várias flags no código do evento)
   if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_POSITION_CLOSED))
     {
      //--- se a posição for encerrada por StopLoss
      if(this.IsTradeEventFlag(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 encerrada parcialmente por StopLoss"
         this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_SL : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL);
         Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
         return;
        }
      //--- se a posição for encerrada por TakeProfit
      else if(this.IsTradeEventFlag(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 encerrada parcialmente por TakeProfit"
         this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_TP : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP);
         Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
         return;
        }
      //--- se a posição for encerrada por uma oposto
      else if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_BY_POS))
        {
         //--- verifica a flag de encerramento parcial e define o evento "Posição encerrada por uma oposta" ou "Posição encerrada parcialmente por uma oposta"
         this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_POS : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS);
         Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
         return;
        }
      //--- Se a posição estiver encerrada
      else
        {
         //--- verifica a flag de encerramento parcial e define o evento "Posição encerrada" ou "Posição encerrada parcialmente"
         this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED : TRADE_EVENT_POSITION_CLOSED_PARTIAL);
         Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
         return;
        }
     }
//--- Operação de saldo na conta (esclarece o evento pelo tipo de negócio)
   if(this.m_trade_event_code==TRADE_EVENT_FLAG_ACCOUNT_BALANCE)
     {
      //--- Inicializa o evento de negociação
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      //--- Pega o último negócio
      COrder* deal=this.GetLastDeal();
      if(deal!=NULL)
        {
         ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)deal.GetProperty(ORDER_PROP_TYPE);
         //--- se o negócio for uma operação de saldo
         if(deal_type==DEAL_TYPE_BALANCE)
           {
           //--- verifica o lucro do negócio e define o evento (depósito ou retirada de fundos)
            this.m_acc_trade_event=(deal.Profit()>0 ? TRADE_EVENT_ACCOUNT_BALANCE_REFILL : TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL);
           }
         //--- Os tipos de operação de saldo restante correspondem à enumeração ENUM_DEAL_TYPE que começa com DEAL_TYPE_CREDIT
         else if(deal_type>DEAL_TYPE_BALANCE)
           {
           //--- configura o evento
            this.m_acc_trade_event=(ENUM_TRADE_EVENT)deal_type;
           }
        }
      Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
      return;
     }
  }
//+------------------------------------------------------------------+

Adiciona a chamada do método de codificação do evento de negociação e remove a exibição de descrições de eventos no diário no final do método WorkWithHedgeCollections() verificando e criando um código de evento de negociação:

//+------------------------------------------------------------------+
//| Verificação dos eventos de negociação (hedging)                  |
//+------------------------------------------------------------------+
void CEngine::WorkWithHedgeCollections(void)
  {
//--- Inicialize o código e as flags do evento de negociação
   this.m_trade_event_code=TRADE_EVENT_FLAG_NO_EVENT;
   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 o estado do mercado e as mudanças 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();
//---
#ifdef __MQL4__

#else // MQL5
//--- Se um evento se referir apenas a ordens à mercado e posições
   if(this.m_is_market_trade_event && !this.m_is_history_trade_event)
     {
      //--- Se o número de ordens pendentes aumentou
      if(this.m_market.NewPendingOrders()>0)
        {
         //--- Adiciona a flag de colocação da ordem pendente
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_PLASED;
        }
      //--- Se o número de ordens à mercado aumentou
      if(this.m_market.NewMarketOrders()>0)
        {
         //--- não adiciona a flag de evento
         //--- ...
        }
     }
   
//--- Se um evento se referir apenas as ordens do negócios do histórico
   else if(this.m_is_history_trade_event && !this.m_is_market_trade_event)
     {
      //--- Se um novo negócio aparecer
      if(this.m_history.NewDeals()>0)
        {
         //--- Adiciona a flag de um evento de saldo da conta
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ACCOUNT_BALANCE;
        }
     }
   
//--- Se os eventos estiverem relacionados a ordens à mercado e posições do histórico
   else if(this.m_is_market_trade_event && this.m_is_history_trade_event)
     {
      //--- Se o número de ordens pendentes diminuiu e nenhum novo negócio apareceu
      if(this.m_market.NewPendingOrders()<0 && this.m_history.NewDeals()==0)
        {
         //--- Adiciona a flag de remoção de uma ordem pendente
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_REMOVED;
        }
      
      //--- Se houver um novo negócio e uma nova ordem no histórico
      if(this.m_history.NewDeals()>0 && this.m_history.NewOrders()>0)
        {
         //--- Pega o último negócio
         COrder* deal=this.GetLastDeal();
         if(deal!=NULL)
           {
            //--- No caso de um negócio de entrada à mercado
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_IN)
              {
               //--- Adiciona o flag de abertura de posição
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_OPENED;
               //--- Se o número de ordens pendentes diminuiu
               if(this.m_market.NewPendingOrders()<0)
                 {
                  //--- Adiciona a flag de uma ativação de ordem pendente
                  this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_ACTIVATED;
                 }
               //--- Obtém o ticket da ordem
               ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(order_ticket);
               if(order!=NULL)
                 {
                  //--- Se o volume atual da ordem exceder zero
                  if(order.VolumeCurrent()>0)
                    {
                     //--- Adiciona a flag de execução parcial
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                    }
                 }
              }
            
            //--- No caso de um negócio de saída à mercado
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT)
              {
               //--- Adiciona a flag de encerramento de posição
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED;
               //--- Obtém o ticket da ordem do negócio
               ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(order_ticket);
               if(order!=NULL)
                 {
                  //--- Se a posição do negócio ainda estiver presente no mercado
                  COrder* pos=this.GetPosition(deal.PositionID());
                  if(pos!=NULL)
                    {
                     //--- Adiciona a flag de execução parcial
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                    }
                  //--- Caso contrário, se a posição for encerrada por completo
                  else
                    {
                     //--- Se a ordem tiver a flag de encerramento pelo StopLoss
                     if(order.IsCloseByStopLoss())
                       {
                        //--- Adiciona a flag do encerramento por StopLoss
                        this.m_trade_event_code+=TRADE_EVENT_FLAG_SL;
                       }
                     //--- Se a ordem tiver a flag do encerramento por TakeProfit
                     if(order.IsCloseByTakeProfit())
                       {
                        //--- Adicionar a flag do encerramento por TakeProfit
                        this.m_trade_event_code+=TRADE_EVENT_FLAG_TP;
                       }
                    }
                 }
              }
            
            //--- Se encerrado pela oposta
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT_BY)
              {
               //--- Adiciona a flag de encerramento de posição
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED;
               //--- Adiciona a flag de encerramento pela oposta
               this.m_trade_event_code+=TRADE_EVENT_FLAG_BY_POS;
               //--- Pega a ordem do negócio
               ulong ticket_from=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(ticket_from);
               if(order!=NULL)
                 {
                  //--- Se a posição da ordem ainda estiver presente no mercado
                  COrder* pos=this.GetPosition(order.PositionID());
                  if(pos!=NULL)
                    {
                     //--- Adiciona a flag de execução parcial
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                    }
                 }
              }
           //--- final do último bloco de manipulação de negócios
           }
        }
     }
#endif 
   this.SetTradeEvent();
  }
//+------------------------------------------------------------------+

Assim, depois de criar o código do evento no método WorkWithHedgeCollections(), nós chamamos o método de decodificação de eventos. O que nos impede de decodificá-lo imediatamente? O ponto é que nosso método atual de decodificação é temporário. É necessário verificar o processo de decodificação. Uma classe de evento de negociação completa deve ser criada nos próximos artigos.
O método SetTradeEvent() define o evento de negociação, grava seu valor para a variável membro de classe m_acc_trade_event, enquanto os métodos LastTradeEvent() e ResetLastTradeEvent() permitem ler o valor da variável como o último evento de negociação na conta e redefini-lo (semelhante a GetLastError()) no programa que faz a chamada.

No manipulador da OnTick() do EA de teste TestDoEasyPart04.mqh, adicione as linhas para ler o último evento de negociação e exibi-lo como no comentário do gráfico:

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

Agora, se você executar este EA no testador e clicar nos botões, os eventos de negociação em andamento do método CEngine::SetTradeEvent() serão exibidos no diário, enquanto o comentário do gráfico exibirá a descrição do último evento ocorrido na conta recebida pelo EA da biblioteca usando o método LastTradeEvent():


Qual é o próximo?

No próximo artigo, nós desenvolveremos as classes de objetos de eventos e a coleção de objetos de eventos semelhantes a coleções de ordens e posições e ensinaremos ao objeto da biblioteca base para enviar os eventos para o programa.

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

Voltar ao conteúdo

Artigos anteriores da série:

Parte 1
Parte 2
Parte 3

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

Arquivos anexados |
MQL5.zip (44.67 KB)
Utilitário para seleção e navegação em MQL5 e MQL4: aumentamos a informatividade de gráficos Utilitário para seleção e navegação em MQL5 e MQL4: aumentamos a informatividade de gráficos

Neste artigo, continuaremos a expandir a funcionalidade do nosso utilitário. Desta vez, adicionaremos a possibilidade de exibir informações em gráficos projetados para facilitar nossa negociação. Em particular, adicionaremos ao gráfico os preços máximo e mínimo do dia anterior, os níveis arredondados, os preços máximo e mínimo do ano, a hora de início da sessão, etc.

Visualização do histórico de negociação multimoeda em relatórios em HTML e CSV Visualização do histórico de negociação multimoeda em relatórios em HTML e CSV

Como é sabido, desde seu lançamento, o MetaTrader 5 vem oferecendo testes multimoedas. Essa função é procurada pela maioria dos traders, mas, infelizmente, não é tão universal quanto gostaríamos. O artigo apresenta vários programas para traçar gráficos usando objetos gráficos baseados no histórico de negociação a partir de relatórios nos formatos HTML e CSV. A negociação de vários instrumentos pode ser analisada em paralelo em várias sub-janelas, ou numa só janela usando a comutação dinâmica realizada pelo usuário.

Estudo de técnicas de análise de velas (parte IV): Atualizações e adições ao Pattern Analyzer Estudo de técnicas de análise de velas (parte IV): Atualizações e adições ao Pattern Analyzer

O artigo apresenta uma nova versão do aplicativo Pattern Analyzer. Esta versão fornece correções de bugs e novos recursos, bem como a interface de usuário revisada. Os comentários e sugestões do artigo anterior foram levados em conta no desenvolvimento da nova versão. A aplicação resultante é descrita neste artigo.

Implementado OLAP na negociação (Parte 1): Noções básicas da análise de dados multidimensionais Implementado OLAP na negociação (Parte 1): Noções básicas da análise de dados multidimensionais

O artigo descreve os princípios gerais de como construir uma estrutura para analisar dados multidimensionais (OLAP) rapidamente, além disso, apresenta como implementá-la em MQL e como usá-la no ambiente MetaTrader usando um exemplo que mostra o processamento do histórico de uma conta de negociação.