Biblioteca para desenvolvimento fácil e rápido de programas MetaTrader (parte VII): Eventos de ativação da ordem StopLimit, preparação da funcionalidade para os eventos de modificação de ordens e posições

Artyom Trishkin | 14 agosto, 2019

Conteúdo

Conceito

Nas partes anteriores dedicadas à biblioteca multi-plataforma para a MetaTrader 5 e MetaTrader 4, nós desenvolvemos ferramentas para a criação de funções de caso de usuário, permitindo o acesso rápido de programas até quaisquer dados de ordem e posições em contas hedging e netting. Estas são as funções para o monitoramento de eventos que ocorrem nas ordens e posições — colocação, remoção e ativação das ordens pendentes, bem como a abertura e encerramento de posições.
No entanto, a funcionalidade para o monitoramento da ativação das ordens StopLimit, que já foram colocadas e a modificação das ordens e posições de mercado ainda não estão implementadas.

Neste artigo, nós implementaremos o monitoramento da ordem StopLimit que leva ao evento de colocação de uma ordem Limit.
A biblioteca monitorará esses eventos e enviará as mensagens necessárias para o programa, para que os eventos possam ser usados posteriormente.

Implementação

Ao testar a ativação das ordens StopLimit, eu notei que esse evento não é refletido no histórico da conta, significando que ele não pode ser simplesmente obtido do histórico da conta "do jeito que está". Portanto, nós precisamos monitorar o estado das ordens existentes até o momento em que ele é alterado (no nosso caso, isso significa alterar o tipo da ordem colocada com o mesmo ticket).

Eu vou abordar a implementação do monitoramento da ativação das ordem StopLimit a partir de uma perspectiva prática. Além de desenvolver a funcionalidade necessária, eu deixarei ela monitorar outros eventos por meio das alterações das ordens e posições existentes (alterando o preço das ordens pendentes existentes, seus níveis de StopLoss e TakeProfit, bem como os mesmos níveis pertencentes as posições em aberto).

A lógica da funcionalidade preparada é a seguinte:

Nós temos acesso à lista completa de todas as ordens e posições ativas na conta. A lista também nos permite obter o estado atual de cada uma das propriedades do objeto. Para monitorar as alterações das propriedades monitoradas, nós precisamos ter uma lista adicional contendo o estado "passado" das propriedades, que inicialmente será igual ao atual.
Ao comparar as propriedades dos objetos dessas duas listas, uma propriedade é considerada alterada assim que for detectado uma diferença em qualquer uma das propriedades monitoradas. Nesse caso, um objeto "alterado" é criado imediatamente. Tanto as propriedades do passado quanto a alterada são gravadas nela e o objeto é colocado na nova lista — "a lista de objetos alterados".
Essa lista deve ser tratada na classe que monitora os eventos da conta.
Naturalmente, nós podemos enviar um evento imediatamente após a detecção das alterações nas propriedades do objeto, mas podemos ter uma situação em que vários objetos são alterados em um tick. Se nós lidarmos com as alterações imediatamente, nós podemos lidar com a alteração apenas do último objeto do pacote, o que é inaceitável. Isso significa que nós devemos criar a lista de todos os objetos alterados e verificar o tamanho da lista na classe do manipulador de eventos. Cada objeto alterado da lista de objetos alterados é tratado em um loop. Isso nos impede de perder algumas das alterações ocorridas simultaneamente nas propriedades de ordem e posição.

Ao criar uma coleção de ordens e posições de mercado na terceira parte da descrição da biblioteca, nós decidimos atualizar a lista e armazenar a soma hash atual e anterior calculada como sendo a alteração do horário do ticket+posição em milissegundos e o volume. Isso nos permite acompanhar constantemente o estado atual de ordens e posições. No entanto, para monitorar as alterações nas propriedades de ordem e posição, esses dados são insuficientes para o cálculo da soma de hash.

Isso significa que nós adicionamos esses três preços à soma hash, mas cada um dos preços é convertido em um número do tipo ulong de sete dígitos pela remoção de um ponto decimal e aumentando a capacidade numérica por uma única ordem (para considerar as cotações de seis dígitos). Por exemplo, se o preço for 1.12345, o valor da soma hash é 1123450.

Vamos começar a implementação.

Adicione as enumerações com as flags possível de posição e as opções de alteração da ordem junto com as próprias opções que devem ser monitoradas para o arquivo Define.mqh:

//+------------------------------------------------------------------+
//| List of flags of possible order and position change options      |
//+------------------------------------------------------------------+
enum ENUM_CHANGE_TYPE_FLAGS
  {
   CHANGE_TYPE_FLAG_NO_CHANGE    =  0,                      // No changes
   CHANGE_TYPE_FLAG_TYPE         =  1,                      // Order type change
   CHANGE_TYPE_FLAG_PRICE        =  2,                      // Price change
   CHANGE_TYPE_FLAG_STOP         =  4,                      // StopLoss change
   CHANGE_TYPE_FLAG_TAKE         =  8,                      // TakeProfit change
   CHANGE_TYPE_FLAG_ORDER        =  16                      // Order properties change flag
  };
//+------------------------------------------------------------------+
//| Possible order and position change options                       |
//+------------------------------------------------------------------+
enum ENUM_CHANGE_TYPE
  {
   CHANGE_TYPE_NO_CHANGE,                                   // No changes
   CHANGE_TYPE_ORDER_TYPE,                                  // Order type change
   CHANGE_TYPE_ORDER_PRICE,                                 // Order price change
   CHANGE_TYPE_ORDER_PRICE_STOP_LOSS,                       // Order and StopLoss price change 
   CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT,                     // Order and TakeProfit price change
   CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT,           // Order, StopLoss and TakeProfit price change
   CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT,                 // StopLoss and TakeProfit change
   CHANGE_TYPE_ORDER_STOP_LOSS,                             // Order's StopLoss change
   CHANGE_TYPE_ORDER_TAKE_PROFIT,                           // Order's TakeProfit change
   CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT,              // Change position's StopLoss and TakeProfit
   CHANGE_TYPE_POSITION_STOP_LOSS,                          // Change position's StopLoss
   CHANGE_TYPE_POSITION_TAKE_PROFIT,                        // Change position's TakeProfit
  };
//+------------------------------------------------------------------+

Quanto as flags de possíveis opções de alteração da propriedade de ordem e posição:

Eu acredito que a flag da ordem requer esclarecimento: o tipo da ordem e o preço podem mudar de forma inequívoca apenas para as ordens pendentes (alteração do tipo da posição (reversão) em uma conta netting não é considerada, pois nós implementamos o seu monitoramento na sexta parte da descrição da biblioteca), enquanto os preços de StopLoss e TakeProfit podem ser alterados para ambas as ordens e posições. É por isso que nós precisamos da flag ordem. Isso nos permite definir com precisão um evento e enviar o tipo de evento para a classe de monitoramento de eventos.

A enumeração de todas as opções possíveis de alteração da ordem e posição apresenta todas as opções que nós devemos rastrear no futuro. Neste artigo, nós vamos implementar o monitoramento de apenas um evento de ativação da ordem StopLimit (CHANGE_TYPE_ORDER_TYPE).

Adicionar oito novos eventos (a ser enviado ao programa durante a identificação) para a enumeração ENUM_TRADE_EVENT da lista de possíveis eventos de negociação da conta:

//+------------------------------------------------------------------+
//| List of possible trading events on the account                   |
//+------------------------------------------------------------------+
enum ENUM_TRADE_EVENT
  {
   TRADE_EVENT_NO_EVENT = 0,                                // No trading event
   TRADE_EVENT_PENDING_ORDER_PLASED,                        // Pending order placed
   TRADE_EVENT_PENDING_ORDER_REMOVED,                       // Pending order removed
//--- enumeration members matching the ENUM_DEAL_TYPE enumeration members
//--- (constant order below should not be changed, no constants should be added/deleted)
   TRADE_EVENT_ACCOUNT_CREDIT = DEAL_TYPE_CREDIT,           // Accruing credit (3)
   TRADE_EVENT_ACCOUNT_CHARGE,                              // Additional charges
   TRADE_EVENT_ACCOUNT_CORRECTION,                          // Correcting entry
   TRADE_EVENT_ACCOUNT_BONUS,                               // Accruing bonuses
   TRADE_EVENT_ACCOUNT_COMISSION,                           // Additional commissions
   TRADE_EVENT_ACCOUNT_COMISSION_DAILY,                     // Commission charged at the end of a trading day
   TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY,                   // Commission charged at the end of a trading month
   TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY,               // Agent commission charged at the end of a trading day
   TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY,             // Agent commission charged at the end of a month
   TRADE_EVENT_ACCOUNT_INTEREST,                            // Accrued interest on free funds
   TRADE_EVENT_BUY_CANCELLED,                               // Canceled buy deal
   TRADE_EVENT_SELL_CANCELLED,                              // Canceled sell deal
   TRADE_EVENT_DIVIDENT,                                    // Accruing dividends
   TRADE_EVENT_DIVIDENT_FRANKED,                            // Accruing franked dividends
   TRADE_EVENT_TAX                        = DEAL_TAX,       // Tax
//--- constants related to the DEAL_TYPE_BALANCE deal type from the DEAL_TYPE_BALANCE enumeration
   TRADE_EVENT_ACCOUNT_BALANCE_REFILL     = DEAL_TAX+1,     // Replenishing account balance
   TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL = DEAL_TAX+2,     // Withdrawing funds from an account
//--- Remaining possible trading events
//--- (constant order below can be changed, constants can be added/deleted)
   TRADE_EVENT_PENDING_ORDER_ACTIVATED    = DEAL_TAX+3,     // Pending order activated by price
   TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL,             // Pending order partially activated by price
   TRADE_EVENT_POSITION_OPENED,                             // Position opened
   TRADE_EVENT_POSITION_OPENED_PARTIAL,                     // Position opened partially
   TRADE_EVENT_POSITION_CLOSED,                             // Position closed
   TRADE_EVENT_POSITION_CLOSED_BY_POS,                      // Position closed partially
   TRADE_EVENT_POSITION_CLOSED_BY_SL,                       // Position closed by StopLoss
   TRADE_EVENT_POSITION_CLOSED_BY_TP,                       // Position closed by TakeProfit
   TRADE_EVENT_POSITION_REVERSED_BY_MARKET,                 // Position reversal by a new deal (netting)
   TRADE_EVENT_POSITION_REVERSED_BY_PENDING,                // Position reversal by activating a pending order (netting)
   TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL,         // Position reversal by partial market order execution (netting)
   TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL,        // Position reversal by partial pending order activation (netting)
   TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET,               // Added volume to a position by a new deal (netting)
   TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL,       // Added volume to a position by partial activation of an order (netting)
   TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING,              // Added volume to a position by activating a pending order (netting)
   TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL,      // Added volume to a position by partial activation of a pending order (netting)
   TRADE_EVENT_POSITION_CLOSED_PARTIAL,                     // Position closed partially
   TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS,              // Position closed partially by an opposite one
   TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL,               // Position closed partially by StopLoss
   TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP,               // Position closed partially by TakeProfit
   TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER,                  // StopLimit order activation
   TRADE_EVENT_MODIFY_ORDER_PRICE,                          // Changing order price
   TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS,                // Changing order and StopLoss price 
   TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT,              // Changing order and TakeProfit price
   TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT,    // Changing order, StopLoss and TakeProfit price
   TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT,          // Changing order's StopLoss and TakeProfit price
   TRADE_EVENT_MODIFY_POSITION_STOP_LOSS,                   // Changing position StopLoss
   TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT,                 // Changing position TakeProfit
  };

Finalmente, adicionamos a nova constante descrevendo a ativação da ordem StopLimit à lista ENUM_EVENT_REASON de enumerações do motivo do evento:

//+------------------------------------------------------------------+
//| Event reason                                                     |
//+------------------------------------------------------------------+
enum ENUM_EVENT_REASON
  {
   EVENT_REASON_REVERSE,                                    // Position reversal (netting)
   EVENT_REASON_REVERSE_PARTIALLY,                          // Position reversal by partial request execution (netting)
   EVENT_REASON_REVERSE_BY_PENDING,                         // Position reversal by pending order activation (netting)
   EVENT_REASON_REVERSE_BY_PENDING_PARTIALLY,               // Position reversal in case of a pending order partial execution (netting)
   //--- All constants related to a position reversal should be located in the above list
   EVENT_REASON_ACTIVATED_PENDING,                          // Pending order activation
   EVENT_REASON_ACTIVATED_PENDING_PARTIALLY,                // Pending order partial activation
   EVENT_REASON_STOPLIMIT_TRIGGERED,                        // StopLimit order activation
   EVENT_REASON_CANCEL,                                     // Cancelation
   EVENT_REASON_EXPIRED,                                    // Order expiration
   EVENT_REASON_DONE,                                       // Request executed in full
   EVENT_REASON_DONE_PARTIALLY,                             // Request executed partially
   EVENT_REASON_VOLUME_ADD,                                 // Add volume to a position (netting)
   EVENT_REASON_VOLUME_ADD_PARTIALLY,                       // Add volume to a position by a partial request execution (netting)
   EVENT_REASON_VOLUME_ADD_BY_PENDING,                      // Add volume to a position when a pending order is activated (netting)
   EVENT_REASON_VOLUME_ADD_BY_PENDING_PARTIALLY,            // Add volume to a position when a pending order is partially executed (netting)
   EVENT_REASON_DONE_SL,                                    // Closing by StopLoss
   EVENT_REASON_DONE_SL_PARTIALLY,                          // Partial closing by StopLoss
   EVENT_REASON_DONE_TP,                                    // Closing by TakeProfit
   EVENT_REASON_DONE_TP_PARTIALLY,                          // Partial closing by TakeProfit
   EVENT_REASON_DONE_BY_POS,                                // Closing by an opposite position
   EVENT_REASON_DONE_PARTIALLY_BY_POS,                      // Partial closing by an opposite position
   EVENT_REASON_DONE_BY_POS_PARTIALLY,                      // Closing an opposite position by a partial volume
   EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY,            // Partial closing of an opposite position by a partial volume
   //--- Constants related to DEAL_TYPE_BALANCE deal type from the ENUM_DEAL_TYPE enumeration
   EVENT_REASON_BALANCE_REFILL,                             // Refilling the balance
   EVENT_REASON_BALANCE_WITHDRAWAL,                         // Withdrawing funds from the account
   //--- List of constants is relevant to TRADE_EVENT_ACCOUNT_CREDIT from the ENUM_TRADE_EVENT enumeration and shifted to +13 relative to ENUM_DEAL_TYPE (EVENT_REASON_ACCOUNT_CREDIT-3)
   EVENT_REASON_ACCOUNT_CREDIT,                             // Accruing credit
   EVENT_REASON_ACCOUNT_CHARGE,                             // Additional charges
   EVENT_REASON_ACCOUNT_CORRECTION,                         // Correcting entry
   EVENT_REASON_ACCOUNT_BONUS,                              // Accruing bonuses
   EVENT_REASON_ACCOUNT_COMISSION,                          // Additional commissions
   EVENT_REASON_ACCOUNT_COMISSION_DAILY,                    // Commission charged at the end of a trading day
   EVENT_REASON_ACCOUNT_COMISSION_MONTHLY,                  // Commission charged at the end of a trading month
   EVENT_REASON_ACCOUNT_COMISSION_AGENT_DAILY,              // Agent commission charged at the end of a trading day
   EVENT_REASON_ACCOUNT_COMISSION_AGENT_MONTHLY,            // Agent commission charged at the end of a month
   EVENT_REASON_ACCOUNT_INTEREST,                           // Accruing interest on free funds
   EVENT_REASON_BUY_CANCELLED,                              // Canceled buy deal
   EVENT_REASON_SELL_CANCELLED,                             // Canceled sell deal
   EVENT_REASON_DIVIDENT,                                   // Accruing dividends
   EVENT_REASON_DIVIDENT_FRANKED,                           // Accruing franked dividends
   EVENT_REASON_TAX                                         // Tax
  };
#define REASON_EVENT_SHIFT    (EVENT_REASON_ACCOUNT_CREDIT-3)

Nós fizemos todas as alterações no arquivo Defines.mqh.

Como nós decidimos criar e armazenar a lista de controle das ordens, essa lista deve armazenar os objetos com um conjunto minimamente suficiente de propriedades para definir o momento de alteração de um dos objetos de ordem e posição de mercado.

Vamos criar a classe objeto de controle da ordem.

Criamos a nova classe OrderControl.mqh na pasta da biblioteca Collections. Definimos a classe da biblioteca padrão CObject como base e incluímos os arquivos necessários para o funcionamento da classe:

//+------------------------------------------------------------------+
//|                                                 OrderControl.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\Defines.mqh"             
#include "..\Objects\Orders\Order.mqh"
#include <Object.mqh>                 
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class COrderControl : public CObject
  {
private:

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

Declaramos diretamente todas as variáveis e métodos necessários na seção privada da classe:

private:
   ENUM_CHANGE_TYPE  m_changed_type;                                    // Order change type
   MqlTick           m_tick;                                            // Tick structure
   string            m_symbol;                                          // Symbol
   ulong             m_position_id;                                     // Position ID
   ulong             m_ticket;                                          // Order ticket
   long              m_magic;                                           // Magic number
   ulong             m_type_order;                                      // Order type
   ulong             m_type_order_prev;                                 // Previous order type
   double            m_price;                                           // Order price
   double            m_price_prev;                                      // Previous order price
   double            m_stop;                                            // StopLoss price
   double            m_stop_prev;                                       // Previous StopLoss price
   double            m_take;                                            // TakeProfit price
   double            m_take_prev;                                       // Previous TakeProfit price
   double            m_volume;                                          // Order volume
   datetime          m_time;                                            // Order placement time
   datetime          m_time_prev;                                       // Order previous placement time
   int               m_change_code;                                     // Order change code
//--- return the presence of the property change flag
   bool              IsPresentChangeFlag(const int change_flag)   const { return (this.m_change_code & change_flag)==change_flag;   }
//--- Return the order parameters change type
   void              CalculateChangedType(void);

Todas as variáveis membro da turma possuem descrições claras. Eu deveria fazer um esclarecimento sobre a variável que armazena a estrutura do tick: quando uma ordem StopLimit é ativada, nós precisamos salvar o horário da ativação. A hora deve ser definida em milissegundos, porém, a função TimeCurrent() retorna o tempo sem os milissegundos. Para obter o horário do último tick que uma ordem foi ativada com os milissegundos, nós usaremos a função padrão SymbolInfoTick() preenchendo a estrutura de ticks com os dados, incluindo o tempo do tick em milissegundos.

O código de alteração da ordem é composta pelas flags que nós descrevemos na enumeração ENUM_CHANGE_TYPE_FLAGS e depende das alterações ocorridas na propriedade da ordem. O método privado CalculateChangedType() descrito abaixo verifica as flags e cria o código de modificação da ordem.

Na seção pública da classe, organizamos os métodos para receber e gravar os dados sobre o estado anterior e atual das propriedades da ordem de controle. o método definindo o tipo de alteração ocorrido na propriedade da ordem , o método definindo o novo estado de uma ordem alterada, o método retornando o tipo de alteração ocorrida e o método verificando a alteração das propriedades da ordem, bem como o que define e retorna o tipo de alteração ocorrido. O método é chamado da classe de coleções de ordens e posições de mercado para detectar a alteração das ordens e posições ativas.

//+------------------------------------------------------------------+
//|                                                 OrderControl.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\Defines.mqh"
#include "..\Objects\Orders\Order.mqh"
#include <Object.mqh>
//+------------------------------------------------------------------+
//| Order and position control class                                 |
//+------------------------------------------------------------------+
class COrderControl : public CObject
  {
private:
   ENUM_CHANGE_TYPE  m_changed_type;                                    // Order change type
   MqlTick           m_tick;                                            // Tick structure
   string            m_symbol;                                          // Symbol
   ulong             m_position_id;                                     // Position ID
   ulong             m_ticket;                                          // Order ticket
   long              m_magic;                                           // Magic number
   ulong             m_type_order;                                      // Order type
   ulong             m_type_order_prev;                                 // Previous order type
   double            m_price;                                           // Order price
   double            m_price_prev;                                      // Previous order price
   double            m_stop;                                            // StopLoss price
   double            m_stop_prev;                                       // Previous StopLoss price
   double            m_take;                                            // TakeProfit price
   double            m_take_prev;                                       // Previous TakeProfit price
   double            m_volume;                                          // Order volume
   datetime          m_time;                                            // Order placement time
   datetime          m_time_prev;                                       // Order previous placement time
   int               m_change_code;                                     // Order change code
//--- return the presence of the property change flag
   bool              IsPresentChangeFlag(const int change_flag)   const { return (this.m_change_code & change_flag)==change_flag;   }
//--- Calculate the order parameters change type
   void              CalculateChangedType(void);
public:
//--- Set the (1,2) current and previous type (2,3) current and previous price, (4,5) current and previous StopLoss,
//--- (6,7) current and previous TakeProfit, (8,9) current and previous placement time, (10) volume
   void              SetTypeOrder(const ulong type)                     { this.m_type_order=type;        }
   void              SetTypeOrderPrev(const ulong type)                 { this.m_type_order_prev=type;   }
   void              SetPrice(const double price)                       { this.m_price=price;            }
   void              SetPricePrev(const double price)                   { this.m_price_prev=price;       }
   void              SetStopLoss(const double stop_loss)                { this.m_stop=stop_loss;         }
   void              SetStopLossPrev(const double stop_loss)            { this.m_stop_prev=stop_loss;    }
   void              SetTakeProfit(const double take_profit)            { this.m_take=take_profit;       }
   void              SetTakeProfitPrev(const double take_profit)        { this.m_take_prev=take_profit;  }
   void              SetTime(const datetime time)                       { this.m_time=time;              }
   void              SetTimePrev(const datetime time)                   { this.m_time_prev=time;         }
   void              SetVolume(const double volume)                     { this.m_volume=volume;          }
//--- Set (1) change type, (2) new current status
   void              SetChangedType(const ENUM_CHANGE_TYPE type)        { this.m_changed_type=type;      }
   void              SetNewState(COrder* order);
//--- Check and set order parameters change flags and return the change type
   ENUM_CHANGE_TYPE  ChangeControl(COrder* compared_order);

//--- Return (1,2,3,4) position ID, ticket, magic and symbol, (5,6) current and previous type (7,8) current and previous price,
//--- (9,10) current and previous StopLoss, (11,12) current and previous TakeProfit, (13,14) current and previous placement time, (15) volume
   ulong             PositionID(void)                             const { return this.m_position_id;     }
   ulong             Ticket(void)                                 const { return this.m_ticket;          }
   long              Magic(void)                                  const { return this.m_magic;           }
   string            Symbol(void)                                 const { return this.m_symbol;          }
   ulong             TypeOrder(void)                              const { return this.m_type_order;      }
   ulong             TypeOrderPrev(void)                          const { return this.m_type_order_prev; }
   double            Price(void)                                  const { return this.m_price;           }
   double            PricePrev(void)                              const { return this.m_price_prev;      }
   double            StopLoss(void)                               const { return this.m_stop;            }
   double            StopLossPrev(void)                           const { return this.m_stop_prev;       }
   double            TakeProfit(void)                             const { return this.m_take;            }
   double            TakeProfitPrev(void)                         const { return this.m_take_prev;       }
   ulong             Time(void)                                   const { return this.m_time;            }
   ulong             TimePrev(void)                               const { return this.m_time_prev;       }
   double            Volume(void)                                 const { return this.m_volume;          }
//--- Return the change type
   ENUM_CHANGE_TYPE  GetChangeType(void)                          const { return this.m_changed_type;    }

//--- Constructor
                     COrderControl(const ulong position_id,const ulong ticket,const long magic,const string symbol) :
                                                                        m_change_code(CHANGE_TYPE_FLAG_NO_CHANGE),
                                                                        m_changed_type(CHANGE_TYPE_NO_CHANGE),
                                                                        m_position_id(position_id),m_symbol(symbol),m_ticket(ticket),m_magic(magic) {;}
  };
//+------------------------------------------------------------------+

O construtor da classe recebe o ID da posição, o ticket, o número mágico e a ordem/posição do símbolo. Na sua lista de inicialização, redefinimos de forma imediata as flags de alteração da ordem e o tipo da alteração, bem como a gravação de dados da ordem/posição obtidos nos parâmetros passados para as variáveis membro da classe correspondentes.

Implementamos os métodos declarados fora do corpo da classe.
O método privado que calcula o tipo de alteração do parâmetro da ordem/posição:

//+------------------------------------------------------------------+
//| Calculate order parameters change type                           |
//+------------------------------------------------------------------+
void COrderControl::CalculateChangedType(void)
  {
   this.m_changed_type=
     (
      //--- If the order flag is set
      this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_ORDER) ? 
        (
         //--- If StopLimit order is activated
         this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TYPE)    ?  CHANGE_TYPE_ORDER_TYPE :
         //--- If an order price is modified
         this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_PRICE)   ? 
           (
            //--- If StopLoss and TakeProfit are modified together with the price
            this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT :
            //--- If TakeProfit modified together with the price
            this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT : 
            //--- If StopLoss modified together with the price
            this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS   :
            //--- Only order price is modified
            CHANGE_TYPE_ORDER_PRICE
           ) : 
         //--- Price is not modified
         //--- If StopLoss and TakeProfit are modified
         this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT :
         //--- If TakeProfit is modified
         this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_TAKE_PROFIT : 
         //--- If StopLoss is modified
         this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS   :
         //--- No changes
         CHANGE_TYPE_NO_CHANGE
        ) : 
      //--- Position
      //--- If position's StopLoss and TakeProfit are modified
      this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT :
      //--- If position's TakeProfit is modified
      this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_POSITION_TAKE_PROFIT : 
      //--- If position's StopLoss is modified
      this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS   :
      //--- No changes
      CHANGE_TYPE_NO_CHANGE
     );
  }
//+------------------------------------------------------------------+

O método grava o tipo da alteração ocorrida da enumeração declarada anteriormente ENUM_CHANGE_TYPE para a variável membro de classe m_changed_type, dependendo da presença das flags dentro da variável m_change_code.
Todas as ações relacionadas à verificação das flags são descritas nos comentários das linhas dos métodos listados e devem ser fáceis de entender.
O método privado verifica a presença da flag dentro da variável m_change_code

bool IsPresentChangeFlag(const int change_flag const { return (this.m_change_code & change_flag)==change_flag }

O método recebe a flag marcada. Sua presença dentro de m_change_code é verificada através da operação AND bit a bit, retornando o resultado booleano da comparação (operação bit a bit entre o código e os valores da flag) com o valor da flag marcada.

O método retornando um novo estado relevante das propriedades de ordem/posição:

//+------------------------------------------------------------------+
//| Set the new relevant status                                      |
//+------------------------------------------------------------------+
void COrderControl::SetNewState(COrder* order)
  {
   if(order==NULL || !::SymbolInfoTick(this.Symbol(),this.m_tick))
      return;
   //--- New type
   this.SetTypeOrderPrev(this.TypeOrder());
   this.SetTypeOrder(order.TypeOrder());
   //--- New price
   this.SetPricePrev(this.Price());
   this.SetPrice(order.PriceOpen());
   //--- New StopLoss
   this.SetStopLossPrev(this.StopLoss());
   this.SetStopLoss(order.StopLoss());
   //--- New TakeProfit
   this.SetTakeProfitPrev(this.TakeProfit());
   this.SetTakeProfit(order.TakeProfit());
   //--- New time
   this.SetTimePrev(this.Time());
   this.SetTime(this.m_tick.time_msc);
  }
//+------------------------------------------------------------------+

O ponteiro para a ordem/posição, na qual ocorreu uma alteração de uma das propriedades, é passado para o método.
Assim que uma alteração de uma das propriedades de ordem/posição é detectada, nós precisamos salvar o novo estado para as verificações adicionais, o método salva primeiro o estado de sua propriedade atual como sendo a anterior e grava o valor da propriedade da ordem passada para o método como sendo o seu estado atual.
Ao salvar o horário de um evento ocorrido, use a função padrão SymbolInfoTick() para receber o horário do tick em milissegundos.

O método principal chamado da classe CMarketCollection, responsável pela definição das alterações ocorridas:

//+------------------------------------------------------------------+
//| Check and set order parameters change flags                      |
//+------------------------------------------------------------------+
ENUM_CHANGE_TYPE COrderControl::ChangeControl(COrder *compared_order)
  {
   this.m_change_code=CHANGE_TYPE_FLAG_NO_CHANGE;
   if(compared_order==NULL || compared_order.Ticket()!=this.m_ticket)
      return CHANGE_TYPE_NO_CHANGE;
   if(compared_order.Status()==ORDER_STATUS_MARKET_ORDER || compared_order.Status()==ORDER_STATUS_MARKET_PENDING)
      this.m_change_code+=CHANGE_TYPE_FLAG_ORDER;
   if(compared_order.TypeOrder()!=this.m_type_order)
      this.m_change_code+=CHANGE_TYPE_FLAG_TYPE;
   if(compared_order.PriceOpen()!=this.m_price)
      this.m_change_code+=CHANGE_TYPE_FLAG_PRICE;
   if(compared_order.StopLoss()!=this.m_stop)
      this.m_change_code+=CHANGE_TYPE_FLAG_STOP;
   if(compared_order.TakeProfit()!=this.m_take)
      this.m_change_code+=CHANGE_TYPE_FLAG_TAKE;
   this.CalculateChangedType();
   return this.GetChangeType();
  }
//+------------------------------------------------------------------+

O método recebe o ponteiro para a ordem/posição verificada e inicializa o código de alteração. Se um objeto vazio de uma ordem comparada for passada ou o seu ticket não for igual ao ticket da ordem de controle atual, é retornado o código de ausência de alteração.

Em seguida, verificamos todas as propriedades monitoradas de ordens de controle e verificadas. Se uma incompatibilidade for encontrada, será adicionado ao código de alteração a flag necessária descrevendo essa alteração.
Em seguida, o tipo de alteração é calculado pelo código de alteração totalmente formado no método CalculateChangedType() e é retornado para o programa de chamada usando o método GetChangeType().

A listagem completa da classe de ordem de controle:

//+------------------------------------------------------------------+
//|                                                 OrderControl.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\Defines.mqh"
#include "..\Objects\Orders\Order.mqh"
#include <Object.mqh>
//+------------------------------------------------------------------+
//| Order and position control class                                 |
//+------------------------------------------------------------------+
class COrderControl : public CObject
  {
private:
   ENUM_CHANGE_TYPE  m_changed_type;                                    // Order change type
   MqlTick           m_tick;                                            // Tick structure
   string            m_symbol;                                          // Symbol
   ulong             m_position_id;                                     // Position ID
   ulong             m_ticket;                                          // Order ticket
   long              m_magic;                                           // Magic number
   ulong             m_type_order;                                      // Order type
   ulong             m_type_order_prev;                                 // Previous order type
   double            m_price;                                           // Order price
   double            m_price_prev;                                      // Previous order price
   double            m_stop;                                            // StopLoss price
   double            m_stop_prev;                                       // Previous StopLoss price
   double            m_take;                                            // TakeProfit price
   double            m_take_prev;                                       // Previous TakeProfit price
   double            m_volume;                                          // Order volume
   datetime          m_time;                                            // Order placement time
   datetime          m_time_prev;                                       // Order previous placement time
   int               m_change_code;                                     // Order change code
//--- return the presence of the property change flag
   bool              IsPresentChangeFlag(const int change_flag)   const { return (this.m_change_code & change_flag)==change_flag;   }
//--- Calculate the order parameters change type
   void              CalculateChangedType(void);
public:
//--- Set the (1,2) current and previous type (2,3) current and previous price, (4,5) current and previous StopLoss,
//--- (6,7) current and previous TakeProfit, (8,9) current and previous placement time, (10) volume
   void              SetTypeOrder(const ulong type)                     { this.m_type_order=type;        }
   void              SetTypeOrderPrev(const ulong type)                 { this.m_type_order_prev=type;   }
   void              SetPrice(const double price)                       { this.m_price=price;            }
   void              SetPricePrev(const double price)                   { this.m_price_prev=price;       }
   void              SetStopLoss(const double stop_loss)                { this.m_stop=stop_loss;         }
   void              SetStopLossPrev(const double stop_loss)            { this.m_stop_prev=stop_loss;    }
   void              SetTakeProfit(const double take_profit)            { this.m_take=take_profit;       }
   void              SetTakeProfitPrev(const double take_profit)        { this.m_take_prev=take_profit;  }
   void              SetTime(const datetime time)                       { this.m_time=time;              }
   void              SetTimePrev(const datetime time)                   { this.m_time_prev=time;         }
   void              SetVolume(const double volume)                     { this.m_volume=volume;          }
//--- Set (1) change type, (2) new current status
   void              SetChangedType(const ENUM_CHANGE_TYPE type)        { this.m_changed_type=type;      }
   void              SetNewState(COrder* order);
//--- Check and set order parameters change flags and return the change type
   ENUM_CHANGE_TYPE  ChangeControl(COrder* compared_order);

//--- Return (1,2,3,4) position ID, ticket, magic and symbol, (5,6) current and previous type (7,8) current and previous price,
//--- (9,10) current and previous StopLoss, (11,12) current and previous TakeProfit, (13,14) current and previous placement time, (15) volume
   ulong             PositionID(void)                             const { return this.m_position_id;     }
   ulong             Ticket(void)                                 const { return this.m_ticket;          }
   long              Magic(void)                                  const { return this.m_magic;           }
   string            Symbol(void)                                 const { return this.m_symbol;          }
   ulong             TypeOrder(void)                              const { return this.m_type_order;      }
   ulong             TypeOrderPrev(void)                          const { return this.m_type_order_prev; }
   double            Price(void)                                  const { return this.m_price;           }
   double            PricePrev(void)                              const { return this.m_price_prev;      }
   double            StopLoss(void)                               const { return this.m_stop;            }
   double            StopLossPrev(void)                           const { return this.m_stop_prev;       }
   double            TakeProfit(void)                             const { return this.m_take;            }
   double            TakeProfitPrev(void)                         const { return this.m_take_prev;       }
   ulong             Time(void)                                   const { return this.m_time;            }
   ulong             TimePrev(void)                               const { return this.m_time_prev;       }
   double            Volume(void)                                 const { return this.m_volume;          }
//--- Return the change type
   ENUM_CHANGE_TYPE  GetChangeType(void)                          const { return this.m_changed_type;    }

//--- Constructor
                     COrderControl(const ulong position_id,const ulong ticket,const long magic,const string symbol) :
                                                                        m_change_code(CHANGE_TYPE_FLAG_NO_CHANGE),
                                                                        m_changed_type(CHANGE_TYPE_NO_CHANGE),
                                                                        m_position_id(position_id),m_symbol(symbol),m_ticket(ticket),m_magic(magic) {;}
  };
//+------------------------------------------------------------------+
//| Check and set the order parameters change flags                  |
//+------------------------------------------------------------------+
ENUM_CHANGE_TYPE COrderControl::ChangeControl(COrder *compared_order)
  {
   this.m_change_code=CHANGE_TYPE_FLAG_NO_CHANGE;
   if(compared_order==NULL || compared_order.Ticket()!=this.m_ticket)
      return CHANGE_TYPE_NO_CHANGE;
   if(compared_order.Status()==ORDER_STATUS_MARKET_ORDER || compared_order.Status()==ORDER_STATUS_MARKET_PENDING)
      this.m_change_code+=CHANGE_TYPE_FLAG_ORDER;
   if(compared_order.TypeOrder()!=this.m_type_order)
      this.m_change_code+=CHANGE_TYPE_FLAG_TYPE;
   if(compared_order.PriceOpen()!=this.m_price)
      this.m_change_code+=CHANGE_TYPE_FLAG_PRICE;
   if(compared_order.StopLoss()!=this.m_stop)
      this.m_change_code+=CHANGE_TYPE_FLAG_STOP;
   if(compared_order.TakeProfit()!=this.m_take)
      this.m_change_code+=CHANGE_TYPE_FLAG_TAKE;
   this.CalculateChangedType();
   return this.GetChangeType();
  }
//+------------------------------------------------------------------+
//| Calculate the order parameters change type                       |
//+------------------------------------------------------------------+
void COrderControl::CalculateChangedType(void)
  {
   this.m_changed_type=
     (
      //--- If the order flag is set
      this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_ORDER) ? 
        (
         //--- If StopLimit order is activated
         this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TYPE)    ?  CHANGE_TYPE_ORDER_TYPE :
         //--- If an order price is modified
         this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_PRICE)   ? 
           (
            //--- If StopLoss and TakeProfit are modified together with the price
            this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT :
            //--- If TakeProfit modified together with the price
            this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT : 
            //--- If StopLoss modified together with the price
            this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS   :
            //--- Only order price is modified
            CHANGE_TYPE_ORDER_PRICE
           ) : 
         //--- Price is not modified
         //--- If StopLoss and TakeProfit are modified
         this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT :
         //--- If TakeProfit is modified
         this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_TAKE_PROFIT : 
         //--- If StopLoss is modified
         this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS   :
         //--- No changes
         CHANGE_TYPE_NO_CHANGE
        ) : 
      //--- Position
      //--- If position's StopLoss and TakeProfit are modified
      this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT :
      //--- If position's TakeProfit is modified
      this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_POSITION_TAKE_PROFIT : 
      //--- If position's StopLoss is modified
      this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS   :
      //--- No changes
      CHANGE_TYPE_NO_CHANGE
     );
  }
//+------------------------------------------------------------------+
//| Set the new relevant status                                      |
//+------------------------------------------------------------------+
void COrderControl::SetNewState(COrder* order)
  {
   if(order==NULL || !::SymbolInfoTick(this.Symbol(),this.m_tick))
      return;
   //--- New type
   this.SetTypeOrderPrev(this.TypeOrder());
   this.SetTypeOrder(order.TypeOrder());
   //--- New price
   this.SetPricePrev(this.Price());
   this.SetPrice(order.PriceOpen());
   //--- New StopLoss
   this.SetStopLossPrev(this.StopLoss());
   this.SetStopLoss(order.StopLoss());
   //--- New TakeProfit
   this.SetTakeProfitPrev(this.TakeProfit());
   this.SetTakeProfit(order.TakeProfit());
   //--- New time
   this.SetTimePrev(this.Time());
   this.SetTime(this.m_tick.time_msc);
  }
//+------------------------------------------------------------------+


Vamos melhorar a classe de coleções de ordens e posições de mercado CMarketCollection.

Nós precisamos acompanhar as alterações das propriedade ocorridas nas ordens e posições ativas. Como nós recebemos todas as ordens e posições de mercado nesta classe, seria razoável verificar também a sua modificação.

Incluímos o arquivo da classe da ordem de controle. Na seção privada da classe, declaramos a lista para armazenar as ordens e posições de controle, a lista para armazenar as ordens e posições alteradas, a variável membro de classe para armazenar o tipo de alteração da ordem e a variável para armazenar a taxa para converter o preço na soma hash.
Além disso, declaramos os métodos privados:
o método para converter as propriedades da ordem em uma soma hash, o método para adicionar um ordem ou uma posição à lista de ordens pendentes e posições na conta, o método para criação e adição de uma ordem de controle à lista de ordens de controle e o método para criar e adicionar uma ordem alterada à lista de ordens alteradas, o método para remover uma ordem da lista de ordens de controle por um ticket e um ID da posição, o método retornando o índice da ordem de controle na lista de ordens de controle por um ticket e ID da posição e o manipulador de um evento de alteração de ordem/posição existente.

Na seção pública da classe, declaramos o método retornando a lista de ordens alteradas criada.

//+------------------------------------------------------------------+
//|                                             MarketCollection.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Orders\MarketOrder.mqh"
#include "..\Objects\Orders\MarketPending.mqh"
#include "..\Objects\Orders\MarketPosition.mqh"
#include "OrderControl.mqh"
//+------------------------------------------------------------------+
//| Collection of market orders and positions                        |
//+------------------------------------------------------------------+
class CMarketCollection : public CListObj
  {
private:
   struct MqlDataCollection
     {
      ulong          hash_sum_acc;           // Hash sum of all orders and positions on the account
      int            total_market;           // Number of market orders on the account
      int            total_pending;          // Number of pending orders on the account
      int            total_positions;        // Number of positions on the account
      double         total_volumes;          // Total volume of orders and positions on the account
     };
   MqlDataCollection m_struct_curr_market;   // Current data on market orders and positions on the account
   MqlDataCollection m_struct_prev_market;   // Previous data on market orders and positions on the account
   CListObj          m_list_all_orders;      // List of pending orders and positions on the account
   CArrayObj         m_list_control;         // List of control orders
   CArrayObj         m_list_changed;         // List of changed orders
   COrder            m_order_instance;       // Order object for searching by property
   ENUM_CHANGE_TYPE  m_change_type;          // Order change type
   bool              m_is_trade_event;       // Trading event flag
   bool              m_is_change_volume;     // Total volume change flag
   double            m_change_volume_value;  // Total volume change value
   ulong             m_k_pow;                // Ratio for converting the price into a hash sum
   int               m_new_market_orders;    // Number of new market orders
   int               m_new_positions;        // Number of new positions
   int               m_new_pendings;         // Number of new pending orders
//--- Save the current values of the account data status as previous ones
   void              SavePrevValues(void)                                                                { this.m_struct_prev_market=this.m_struct_curr_market;                  }
//--- Convert order data into a hash sum value
   ulong             ConvertToHS(COrder* order) const;
//--- Add an order or a position to the list of pending orders and positions on an account and sets the data on market orders and positions on the account
   bool              AddToListMarket(COrder* order);
//--- (1) Create and add a control order to the list of control orders, (2) a control order to the list of changed control orders
   bool              AddToListControl(COrder* order);
   bool              AddToListChanges(COrderControl* order_control);
//--- Remove an order by a ticket or a position ID from the list of control orders
   bool              DeleteOrderFromListControl(const ulong ticket,const ulong id);
//--- Return the control order index in the list by a position ticket and ID
   int               IndexControlOrder(const ulong ticket,const ulong id);
//--- Handler of an existing order/position change event
   void              OnChangeEvent(COrder* order,const int index);
public:
//--- Return the list of (1) all pending orders and open positions, (2) modified orders and positions
   CArrayObj*        GetList(void)                                                                       { return &this.m_list_all_orders;                                       }
   CArrayObj*        GetListChanges(void)                                                                { return &this.m_list_changed;                                          }
//--- Return the list of orders and positions with an open time from begin_time to end_time
   CArrayObj*        GetListByTime(const datetime begin_time=0,const datetime end_time=0);
//--- Return the list of orders and positions by selected (1) double, (2) integer and (3) string property fitting a compared condition
   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);  }
//--- Return the number of (1) new market order, (2) new pending orders, (3) new positions, (4) occurred trading event flag, (5) changed volume
   int               NewMarketOrders(void)                                                         const { return this.m_new_market_orders;                                      }
   int               NewPendingOrders(void)                                                        const { return this.m_new_pendings;                                           }
   int               NewPositions(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;                                    }
//--- Constructor
                     CMarketCollection(void);
//--- Update the list of pending orders and positions
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

Adicionamos ao construtor da classe a limpeza e ordenação da lista de ordens de controle e a lista de ordens alteradas, assim como o cálculo da razão que define a soma hash para o construtor de classe:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CMarketCollection::CMarketCollection(void) : m_is_trade_event(false),m_is_change_volume(false),m_change_volume_value(0)
  {
   this.m_list_all_orders.Sort(SORT_BY_ORDER_TIME_OPEN);
   this.m_list_all_orders.Clear();
   ::ZeroMemory(this.m_struct_prev_market);
   this.m_struct_prev_market.hash_sum_acc=WRONG_VALUE;
   this.m_list_all_orders.Type(COLLECTION_MARKET_ID);
   this.m_list_control.Clear();  
   this.m_list_control.Sort();   
   this.m_list_changed.Clear();  
   this.m_list_changed.Sort();   
   this.m_k_pow=(ulong)pow(10,6);
  }
//+------------------------------------------------------------------+

The method of converting order properties into the number for calculating the hash sum:

//+---------------------------------------------------------------------+
//| Convert the order price and its type into a number for the hash sum |
//+---------------------------------------------------------------------+
ulong CMarketCollection::ConvertToHS(COrder *order) const
  {
   if(order==NULL)
      return 0;
   ulong price=ulong(order.PriceOpen()*this.m_k_pow);
   ulong stop=ulong(order.StopLoss()*this.m_k_pow);
   ulong take=ulong(order.TakeProfit()*this.m_k_pow);
   ulong type=order.TypeOrder();
   ulong ticket=order.Ticket();
   return price+stop+take+type+ticket;
  }
//+------------------------------------------------------------------+

O método recebe um ponteiro para a ordem cujos dados devem ser convertidos em um número. Em seguida, as propriedades do tipo double da ordem são convertidas em um número para a soma hash por uma simples multiplicação pela razão previamente calculada no construtor da classe, todos os valores das propriedades são somadas e retornadas como um número ulong.

Foi melhorado na classe o método Refresh() para atualizar os dados atuais no ambiente de mercado. Nós descrevemos isso na terceira parte da descrição da biblioteca.

As alterações afetaram a adição de objetos à lista de ordens e posições. Agora, essas sequências do mesmo tipo estão localizadas no método único AddToListMarket(). Após declarar um objeto de ordem na lista de ordens e posições, a presença da mesma ordem é verificada na lista de ordens de controle. Se tal ordem estiver ausente, é criado um objeto de ordem de controle e adicionado à lista de ordens de controle usando o método AddToListControl(). Se a ordem de controle estiver presente, é chamado o método OnChangeEvent() para comparar as propriedades da ordem atual com a ordem de controle.

Todas as ações executadas são descritas nas linhas de comentários destacadas no texto da listagem dos métodos.

//+------------------------------------------------------------------+
//| Update the list of orders                                        |
//+------------------------------------------------------------------+
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;
   this.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();
      //--- Get the control order index by a position ticket and ID
      int index=this.IndexControlOrder(ticket);
      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;
         //--- Add a position object to the list of market orders and positions
         if(!this.AddToListMarket(position))
            continue;
         //--- If there is no order in the list of control orders and positions, add it
         if(index==WRONG_VALUE)
           {
            if(!this.AddToListControl(order))
              {
               ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольный ордер ","Failed to add control order "),order.TypeDescription()," #",order.Ticket());
              }
           }
         //--- If the order is already present in the list of control orders, check it for changed properties
         if(index>WRONG_VALUE)
           {
            this.OnChangeEvent(position,index);
           }
        }
      else
        {
         CMarketPending *order=new CMarketPending(ticket);
         if(order==NULL) continue;
         //--- Add a pending order object to the list of market orders and positions
         if(!this.AddToListMarket(order))
            continue;
         //--- If there is no order in the list of control orders and positions, add it
         if(index==WRONG_VALUE)
           {
            if(!this.AddToListControl(order))
              {
               ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольный ордер ","Failed to add control order "),order.TypeDescription()," #",order.Ticket());
              }
           }
         //--- If the order is already present in the list of control orders, check it for changed properties
         if(index>WRONG_VALUE)
           {
            this.OnChangeEvent(order,index);
           }
        }
     }
//--- MQ5
#else 
//--- Positions
   int total_positions=::PositionsTotal();
   for(int i=0; i<total_positions; i++)
     {
      ulong ticket=::PositionGetTicket(i);
      if(ticket==0) continue;
      CMarketPosition *position=new CMarketPosition(ticket);
      if(position==NULL) continue;
      //--- Add a position object to the list of market orders and positions
      if(!this.AddToListMarket(position))
         continue;
      //--- Get the control order index by a position ticket and ID
      int index=this.IndexControlOrder(ticket,position.PositionID());
      //--- If the order is not present in the list of control orders, add it
      if(index==WRONG_VALUE)
        {
         if(!this.AddToListControl(position))
           {
            ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольую позицию ","Failed to add control position "),position.TypeDescription()," #",position.Ticket());
           }
        }
      //--- If the order is already present in the list of control orders, check it for changed properties
      else if(index>WRONG_VALUE)
        {
         this.OnChangeEvent(position,index);
        }
     }
//--- Orders
   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);
      //--- Market order
      if(type<ORDER_TYPE_BUY_LIMIT)
        {
         CMarketOrder *order=new CMarketOrder(ticket);
         if(order==NULL) continue;
         //--- Add a market order object to the list of market orders and positions
         if(!this.AddToListMarket(order))
            continue;
        }
      //--- Pending order
      else
        {
         CMarketPending *order=new CMarketPending(ticket);
         if(order==NULL) continue;
         //--- Add a pending order object to the list of market orders and positions
         if(!this.AddToListMarket(order))
            continue;
         //--- Get the control order index by a position ticket and ID
         int index=this.IndexControlOrder(ticket,order.PositionID());
         //--- If the order is not present in the control order list, add it
         if(index==WRONG_VALUE)
           {
            if(!this.AddToListControl(order))
              {
               ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольный ордер ","Failed to add control order "),order.TypeDescription()," #",order.Ticket());
              }
           }
         //--- If the order is already in the control order list, check it for changed properties
         else if(index>WRONG_VALUE)
           {
            this.OnChangeEvent(order,index);
           }
        }
     }
#endif 
//--- First launch
   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_orders=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();
     }
  }
//+------------------------------------------------------------------+

O método que adiciona as ordens e posições à lista de coleção de ordens e posições de mercado:

//+--------------------------------------------------------------------------------+
//| Add an order or a position to the list of orders and positions on the account  |
//+--------------------------------------------------------------------------------+
bool CMarketCollection::AddToListMarket(COrder *order)
  {
   if(order==NULL)
      return false;
   ENUM_ORDER_STATUS status=order.Status();
   if(this.m_list_all_orders.InsertSort(order))
     {
      if(status==ORDER_STATUS_MARKET_POSITION)
        {
         this.m_struct_curr_market.hash_sum_acc+=order.GetProperty(ORDER_PROP_TIME_UPDATE_MSC)+this.ConvertToHS(order);
         this.m_struct_curr_market.total_volumes+=order.Volume();
         this.m_struct_curr_market.total_positions++;
         return true;
        }
      if(status==ORDER_STATUS_MARKET_PENDING)
        {
         this.m_struct_curr_market.hash_sum_acc+=this.ConvertToHS(order);
         this.m_struct_curr_market.total_volumes+=order.Volume();
         this.m_struct_curr_market.total_pending++;
         return true;
        }
     }
   else
     {
      ::Print(DFUN,order.TypeDescription()," #",order.Ticket()," ",TextByLanguage("не удалось добавить в список","failed to add to list"));
      delete order;
     }
   return false;
  }
//+------------------------------------------------------------------+

O ponteiro para a ordem adicionada à lista de coleção é passado para o método. Depois de adicionar uma ordem à lista de coleção, os dados da estrutura, que armazena o estado atual de ordens e posições de mercado para uma verificação subsequente e que define as alterações no número de ordens e posições, são alteradas dependendo do estado da ordem.

O método para criar uma ordem de controle e adicioná-la à lista de ordens de controle:

//+------------------------------------------------------------------+
//| Create and add an order to the list of control orders            |
//+------------------------------------------------------------------+
bool CMarketCollection::AddToListControl(COrder *order)
  {
   if(order==NULL) 
      return false;
   COrderControl* order_control=new COrderControl(order.PositionID(),order.Ticket(),order.Magic(),order.Symbol());
   if(order_control==NULL)
      return false;
   order_control.SetTime(order.TimeOpenMSC());         
   order_control.SetTimePrev(order.TimeOpenMSC());     
   order_control.SetVolume(order.Volume());            
   order_control.SetTime(order.TimeOpenMSC());         
   order_control.SetTypeOrder(order.TypeOrder());      
   order_control.SetTypeOrderPrev(order.TypeOrder());  
   order_control.SetPrice(order.PriceOpen());          
   order_control.SetPricePrev(order.PriceOpen());      
   order_control.SetStopLoss(order.StopLoss());        
   order_control.SetStopLossPrev(order.StopLoss());    
   order_control.SetTakeProfit(order.TakeProfit());    
   order_control.SetTakeProfitPrev(order.TakeProfit());
   if(!this.m_list_control.Add(order_control))
     {
      delete order_control;
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

O ponteiro para uma ordem e posição de mercado é passado para o método. Se um objeto inválido for passado, ele retorna false.
Uma nova ordem de controle é então criada, para que o seu construtor receba imediatamente o ID da posição, o ticket, o número mágico e o símbolo de um objeto de ordem, que foi passada para o método. Todos os dados necessários para identificar as modificações da ordem/posição são então preenchidos.
Se a adição de uma nova ordem de controle à lista de ordens de controle falhar, a ordem será removida e será retornado 'false'
.

Como nós sempre adicionamos novas ordens e posições à lista de ordens e posições de controle, isso pode se tornar muito volumoso após um longo tempo de funcionamento. Ordens e posições não vivem para sempre, e suas cópias de controle não devem ser permanentemente armazenadas na lista ocupando memória sem nenhum motivo. Para remover ordens de controle desnecessárias da lista, use o método DeleteOrderFromListControl(), que remove uma ordem de controle da lista de ordens de controle pelo ticket e ID da posição.

Por enquanto, o método apenas foi declarado, mas não implementado. A implementação será feita após a preparação de toda a funcionalidade para monitorar as modificações da ordem e posição.

O método que retorna o índice da ordem de controle na lista de ordens de controle pelo ticket e ID da posição:

//+------------------------------------------------------------------+
//| Return an order index by a ticket in the list of control orders  |
//+------------------------------------------------------------------+
int CMarketCollection::IndexControlOrder(const ulong ticket,const ulong id)
  {
   int total=this.m_list_control.Total();
   for(int i=0;i<total;i++)
     {
      COrderControl* order=this.m_list_control.At(i);
      if(order==NULL)
         continue;
      if(order.PositionID()==id && order.Ticket()==ticket)
         return i;
     }
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+

O método recebe um ticket da ordem/posição e um ID da posição. Uma ordem de controle com o ticket e ID correspondentes é buscada ao longo de todas as ordens de controle através de um loop, e o seu índice é retornado na lista de ordens de controle. Se a ordem não for encontrada, -1 é retornado.

Método do manipulador de eventos para alterar uma ordem/posição existente:

//+------------------------------------------------------------------+
//| Handler of changing an existing order/position                   |
//+------------------------------------------------------------------+
void CMarketCollection::OnChangeEvent(COrder* order,const int index)
  {
   COrderControl* order_control=this.m_list_control.At(index);
   if(order_control!=NULL)
     {
      this.m_change_type=order_control.ChangeControl(order);
      ENUM_CHANGE_TYPE change_type=(order.Status()==ORDER_STATUS_MARKET_POSITION ? CHANGE_TYPE_ORDER_TAKE_PROFIT : CHANGE_TYPE_NO_CHANGE);
      if(this.m_change_type>change_type)
        {
         order_control.SetNewState(order);
         if(!this.AddToListChanges(order_control))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить модифицированный ордер в список изменённых ордеров","Could not add modified order to list of modified orders"));
           }
        }
     }
  }
//+------------------------------------------------------------------+

O método recebe o ponteiro para a ordem verificada e o índice da ordem de controle apropriada na lista de ordens de controle.
Obtemos a ordem de controle da lista pelo seu índice e verificamos as alterações nas propriedades da ordem de controle correspondentes às propriedades da ordem de controle verificada usando o método ChangeControl(). O método recebe o ponteiro para a ordem de controle. Se a diferença for encontrada, o método retornará o tipo de alteração que foi gravada na variável membro de classe m_change_type.
Em seguida, verificamos o estado da ordem verificada e definimos o valor acima na qual a alteração foi considerada como ocorrida.. Para a posição, este valor deve exceder a constante CHANGE_TYPE_ORDER_TAKE_PROFIT da enumeração ENUM_CHANGE_TYPE, pois todos os valores iguais ou inferiores a essa constante estão relacionados apenas a uma ordem pendente. Para uma ordem pendente, o valor deve exceder a constante CHANGE_TYPE_NO_CHANGE.
Se a variável obtida m_change_type exceder aquela especificada, a modificação é detectada. Neste caso, o estado atual da ordem de controle é salvo para uma verificação subsequente e uma cópia da ordem de controle é colocada na lista de ordens alteradas para a manipulação subsequente da lista na classe CEventsCollection.

O método para criar uma ordem de controle alterada e adicioná-la à lista de ordens alteradas:

//+------------------------------------------------------------------+
//|Create and add a control order to the list of changed orders      |
//+------------------------------------------------------------------+
bool CMarketCollection::AddToListChanges(COrderControl* order_control)
  {
   if(order_control==NULL)
      return false;
   COrderControl* order_changed=new COrderControl(order_control.PositionID(),order_control.Ticket(),order_control.Magic(),order_control.Symbol());
   if(order_changed==NULL)
      return false;
   order_changed.SetTime(order_control.Time());                    
   order_changed.SetTimePrev(order_control.TimePrev());            
   order_changed.SetVolume(order_control.Volume());                
   order_changed.SetTypeOrder(order_control.TypeOrder());          
   order_changed.SetTypeOrderPrev(order_control.TypeOrderPrev());  
   order_changed.SetPrice(order_control.Price());                  
   order_changed.SetPricePrev(order_control.PricePrev());          
   order_changed.SetStopLoss(order_control.StopLoss());            
   order_changed.SetStopLossPrev(order_control.StopLossPrev());    
   order_changed.SetTakeProfit(order_control.TakeProfit());        
   order_changed.SetTakeProfitPrev(order_control.TakeProfitPrev());
   order_changed.SetChangedType(order_control.GetChangeType());    
   if(!this.m_list_changed.Add(order_changed))
     {
      delete order_changed;
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

O método recebe o ponteiro para a ordem de controle modificada. A cópia da ordem deve ser colocada na lista de ordens e posições de controle alteradas.
Em seguida, uma nova ordem de controle é criada. Ela recebe imediatamente o ID da posição, o ticket, o número mágico e o símbolo correspondente aos da ordem de controle alterada.
Depois disso, as propriedades da ordem de controle alterada são simplesmente copiadas para as propriedades do objeto recém-criado elemento por elemento.
Finalmente, colocamos a cópia recém-criada de uma ordem de controle alterada na lista de ordens alteradas.
Se a ordem recém-criada não puder ser colocada na lista, o objeto da ordem recém-criado é removido e é retornado false.

Nós concluímos a implementação das alterações na classe CMarketCollection. Agora vamos passar para a classe CEventsCollection.

A classe de coleção de eventos CEventsCollection deve apresentar os eventos de manipulação, nos quais a lista de ordens alteradas, que foi criada na classe de coleção de ordem e posição de mercado, não está vazia. Isso significa que ele contém as ordens e posições alteradas que devem ser manipuladas (criamos um novo evento e enviamos uma mensagem apropriada para o programa que realizou a chamada).

Vamos adicionar a definição dos dois métodos à seção privada da classe, além do método já existente: o novo método sobrecarregado de criação de um novo evento e o método para lidar com a alteração de uma ordem/posição existente, enquanto o método Refresh() recebe a capacidade de passar a lista de ordens alteradas ao método:

//+------------------------------------------------------------------+
//|                                             EventsCollection.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Orders\Order.mqh"
#include "..\Objects\Events\EventBalanceOperation.mqh"
#include "..\Objects\Events\EventOrderPlaced.mqh"
#include "..\Objects\Events\EventOrderRemoved.mqh"
#include "..\Objects\Events\EventPositionOpen.mqh"
#include "..\Objects\Events\EventPositionClose.mqh"
//+------------------------------------------------------------------+
//| Collection of account events                                     |
//+------------------------------------------------------------------+
class CEventsCollection : public CListObj
  {
private:
   CListObj          m_list_events;                   // List of events
   bool              m_is_hedge;                      // Hedge account flag
   long              m_chart_id;                      // Control program chart ID
   int               m_trade_event_code;              // Trading event code
   ENUM_TRADE_EVENT  m_trade_event;                   // Account trading event
   CEvent            m_event_instance;                // Event object for searching by property
   
//--- Create a trading event depending on the (1) order status and (2) change type
   void              CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market);
   void              CreateNewEvent(COrderControl* order);
//--- Create an event for a (1) hedging account, (2) netting account
   void              NewDealEventHedge(COrder* deal,CArrayObj* list_history,CArrayObj* list_market);
   void              NewDealEventNetto(COrder* deal,CArrayObj* list_history,CArrayObj* list_market);
//--- Select and return the list of market pending orders
   CArrayObj*        GetListMarketPendings(CArrayObj* list);
//--- Select from the list and return the list of historical (1) removed pending orders, (2) deals, (3) all closing orders 
   CArrayObj*        GetListHistoryPendings(CArrayObj* list);
   CArrayObj*        GetListDeals(CArrayObj* list);
   CArrayObj*        GetListCloseByOrders(CArrayObj* list);
//--- Return the list of (1) all position orders by its ID, (2) all deal positions by its ID,
//--- (3) all market entry deals by position ID, (4) all market exit deals by position ID,
//--- (5) all position reversal deals by position ID
   CArrayObj*        GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsInByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsOutByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsInOutByPosID(CArrayObj* list,const ulong position_id);
//--- Return the total volume of all deals (1) IN, (2) OUT of the position by its ID
   double            SummaryVolumeDealsInByPosID(CArrayObj* list,const ulong position_id);
   double            SummaryVolumeDealsOutByPosID(CArrayObj* list,const ulong position_id);
//--- Return the (1) first, (2) last and (3) closing order from the list of all position orders,
//--- (4) an order by ticket, (5) market position by ID,
//--- (6) the last and (7) penultimate InOut deal by position ID
   COrder*           GetFirstOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetLastOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetCloseByOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetHistoryOrderByTicket(CArrayObj* list,const ulong order_ticket);
   COrder*           GetPositionByID(CArrayObj* list,const ulong position_id);
//--- Return the flag of the event object presence in the event list
   bool              IsPresentEventInList(CEvent* compared_event);
//--- Handler of an existing order/position change
   void              OnChangeEvent(CArrayObj* list_changes,CArrayObj* list_history,CArrayObj* list_market,const int index);
public:
//--- Select events from the collection with time within the range from begin_time to end_time
   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0);
//--- Return the full event collection list "as is"
   CArrayObj        *GetList(void)                                                                       { return &this.m_list_events;                                           }
//--- Return the list by selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   CArrayObj        *GetList(ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
//--- Update the list of events
   void              Refresh(CArrayObj* list_history,
                             CArrayObj* list_market,
                             CArrayObj* list_changes,
                             const bool is_history_event,
                             const bool is_market_event,
                             const int  new_history_orders,
                             const int  new_market_pendings,
                             const int  new_market_positions,
                             const int  new_deals);
//--- Set the control program chart ID
   void              SetChartID(const long id)        { this.m_chart_id=id;         }
//--- Return the last trading event on the account
   ENUM_TRADE_EVENT  GetLastTradeEvent(void)    const { return this.m_trade_event;  }
//--- Reset the last trading event
   void              ResetLastTradeEvent(void)        { this.m_trade_event=TRADE_EVENT_NO_EVENT;   }
//--- Constructor
                     CEventsCollection(void);
  };
//+------------------------------------------------------------------+

Vamos implementar os novos métodos fora do corpo da classe.

O método sobrecarregado para criar um evento de modificação de ordem/posição:

//+------------------------------------------------------------------+
//| Create a trading event depending on the order change type        |
//+------------------------------------------------------------------+
void CEventsCollection::CreateNewEvent(COrderControl* order)
  {
   CEvent* event=NULL;
//--- Pending StopLimit order placed
   if(order.GetChangeType()==CHANGE_TYPE_ORDER_TYPE)
     {
      this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_PLASED;
      event=new CEventOrderPlased(this.m_trade_event_code,order.Ticket());
     }
//--- 
   if(event!=NULL)
     {
      event.SetProperty(EVENT_PROP_TIME_EVENT,order.Time());                        // Event time
      event.SetProperty(EVENT_PROP_REASON_EVENT,EVENT_REASON_STOPLIMIT_TRIGGERED);  // Event reason (from the ENUM_EVENT_REASON enumeration)
      event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrderPrev());          // Type of the order that triggered an event
      event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());               // Ticket of the order that triggered an event
      event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder());             // Event order type
      event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket());              // Event order ticket
      event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder());          // Position first order type
      event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());           // Position first order ticket
      event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());                 // Position ID
      event.SetProperty(EVENT_PROP_POSITION_BY_ID,0);                               // Opposite position ID
      event.SetProperty(EVENT_PROP_MAGIC_BY_ID,0);                                  // Opposite position magic number
         
      event.SetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE,order.TypeOrderPrev());      // Position order type before changing the direction
      event.SetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE,order.Ticket());           // Position order ticket before changing direction
      event.SetProperty(EVENT_PROP_TYPE_ORD_POS_CURRENT,order.TypeOrder());         // Current position order type
      event.SetProperty(EVENT_PROP_TICKET_ORD_POS_CURRENT,order.Ticket());          // Current position order ticket
         
      event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                      // Order magic number
      event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimePrev());           // First position order time
      event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PricePrev());                  // Event price
      event.SetProperty(EVENT_PROP_PRICE_OPEN,order.Price());                       // Order open price
      event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.Price());                      // Order close price
      event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());                      // StopLoss order price
      event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());                    // TakeProfit order price
      event.SetProperty(EVENT_PROP_VOLUME_ORDER_INITIAL,order.Volume());            // Requested order volume
      event.SetProperty(EVENT_PROP_VOLUME_ORDER_EXECUTED,0);                        // Executed order volume
      event.SetProperty(EVENT_PROP_VOLUME_ORDER_CURRENT,order.Volume());            // Remaining (unexecuted) order volume
      event.SetProperty(EVENT_PROP_VOLUME_POSITION_EXECUTED,0);                     // Executed position volume
      event.SetProperty(EVENT_PROP_PROFIT,0);                                       // Profit
      event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                          // Order symbol
      event.SetProperty(EVENT_PROP_SYMBOL_BY_ID,order.Symbol());                    // Opposite position symbol
      //--- Set the control program chart ID, decode the event code and set the event type
      event.SetChartID(this.m_chart_id);
      event.SetTypeEvent();
      //--- Add the event object if it is not in the list
      if(!this.IsPresentEventInList(event))
        {
         this.m_list_events.InsertSort(event);
         //--- Send a message about the event and set the value of the last trading event
         event.SendEvent();
         this.m_trade_event=event.TradeEvent();
        }
      //--- If the event is already present in the list, remove a new event object and display a debugging message
      else
        {
         ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
         delete event;
        }
     }
  }
//+------------------------------------------------------------------+

Eu descrevi o novo método de criação de eventos na quinta parte da descrição da biblioteca ao criar uma coleção de eventos.
Este método é quase idêntico. A única diferença é o tipo da ordem, cujo ponteiro é passado ao método.
Verifica-se o tipo de alteração que ocorreu na ordem no início do método e o código de alteração é definido na variável membro de classe m_trade_event_code de acordo com o tipo da alteração.
Em seguida, é criado o evento correspondente ao tipo de alteração, suas propriedades são preenchidas de acordo com o tipo da alteração, o evento é colocado na lista de eventos e é enviado para o programa de controle.

O método melhorado para atualizar a lista de eventos:

//+------------------------------------------------------------------+
//| Update the event list                                            |
//+------------------------------------------------------------------+
void CEventsCollection::Refresh(CArrayObj* list_history,
                                CArrayObj* list_market,
                                CArrayObj* list_changes,
                                const bool is_history_event,
                                const bool is_market_event,
                                const int  new_history_orders,
                                const int  new_market_pendings,
                                const int  new_market_positions,
                                const int  new_deals)
  {
//--- Exit if the lists are empty
   if(list_history==NULL || list_market==NULL)
      return;
//--- If the event is in the market environment
   if(is_market_event)                         
     {                                         
      //--- if the order properties were changed 
      int total_changes=list_changes.Total();  
      if(total_changes>0)                      
        {                                      
         for(int i=total_changes-1;i>=0;i--)   
           {                                   
            this.OnChangeEvent(list_changes,i);
           }                                   
        }                                      
      //--- if the number of placed pending orders increased
      if(new_market_pendings>0)
        {
         //--- Receive the list of the newly placed pending orders
         CArrayObj* list=this.GetListMarketPendings(list_market);
         if(list!=NULL)
           {
            //--- Sort the new list by order placement time
            list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
            //--- Take the number of orders equal to the number of newly placed ones from the end of the list in a loop (the last N events)
            int total=list.Total(), n=new_market_pendings;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Receive an order from the list, if this is a pending order, set a trading event
               COrder* order=list.At(i);
               if(order!=NULL && order.Status()==ORDER_STATUS_MARKET_PENDING)
                  this.CreateNewEvent(order,list_history,list_market);
              }
           }
        }
     }
//--- If the event is in the account history
   if(is_history_event)
     {
      //--- If the number of historical orders increased
      if(new_history_orders>0)
        {
         //--- Receive the list of removed pending orders only
         CArrayObj* list=this.GetListHistoryPendings(list_history);
         if(list!=NULL)
           {
            //--- Sort the new list by order removal time
            list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC);
            //--- Take the number of orders equal to the number of newly removed ones from the end of the list in a loop (the last N events)
            int total=list.Total(), n=new_history_orders;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Receive an order from the list. If this is a removed pending order without a position ID, 
               //--- this is an order removal - set a trading event
               COrder* order=list.At(i);
               if(order!=NULL && order.Status()==ORDER_STATUS_HISTORY_PENDING && order.PositionID()==0)
                  this.CreateNewEvent(order,list_history,list_market);
              }
           }
        }
      //--- If the number of deals increased
      if(new_deals>0)
        {
         //--- Receive the list of deals only
         CArrayObj* list=this.GetListDeals(list_history);
         if(list!=NULL)
           {
            //--- Sort the new list by deal time
            list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
            //--- Take the number of deals equal to the number of new ones from the end of the list in a loop (the last N events)
            int total=list.Total(), n=new_deals;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Receive a deal from the list and set a trading event
               COrder* order=list.At(i);
               if(order!=NULL)
                  this.CreateNewEvent(order,list_history,list_market);
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

 Este método também foi considerado na quinta parte da descrição da biblioteca ao criar uma coleção de eventos. A diferença desse método está no bloco de código para manipular os eventos de modificação no caso em que o tamanho da lista de ordens alteradas não for zero. Cada ordem alterada da lista é tratada no método do manipulador de eventos de mudança de ordens em um loop:

//+------------------------------------------------------------------+
//| The handler of an existing order/position change event           |
//+------------------------------------------------------------------+
void CEventsCollection::OnChangeEvent(CArrayObj* list_changes,const int index)
  {
   COrderControl* order_changed=list_changes.Detach(index);
   if(order_changed!=NULL)
     {
      if(order_changed.GetChangeType()==CHANGE_TYPE_ORDER_TYPE)
        {
         this.CreateNewEvent(order_changed);
        }
      delete order_changed;
     }
  }
//+------------------------------------------------------------------+

Ao manipular a lista de ordens alteradas, nós precisamos obter uma ordem modificada da lista e remover o objeto da ordem e o seu ponteiro correspondente da lista após o término da manipulação, para evitar manipular o mesmo evento várias vezes.
Felizmente, quando se trabalha com uma array dinâmico de ponteiros de objeto CArrayObj, a biblioteca padrão fornece o método Detach() que recebe o elemento da posição especificada e o remove do array. Em outras palavras, nós recebemos o ponteiro para o objeto armazenado no array pelo índice e removemos esse ponteiro do array. Se o tipo de alteração for CHANGE_TYPE_ORDER_TYPE (alteração do tipo de ordem — acionando uma ordem pendente StopLimit e transformando-a em uma ordem Limit), criamos um novo evento — de ativação da ordem StopLimit. Depois que o objeto é manipulado pelo ponteiro obtido usando o método Detach(), o ponteiro (que não é mais necessário) é simplesmente removido.

Isso conclui a melhoria da classe CEventsCollection.

Para que todas as alterações entrem em vigor, a lista de ordens alteradas da classe de coleção de ordens e posições de mercado deve ser recebida e o seu tamanho deve ser escrito no objeto principal da biblioteca — na classe CEngine (o método TradeEventsControl()). Quando o método de atualização do evento Refresh() da classe de coleção de eventos é chamado, o tamanho da lista de ordens alteradas deve ser verificado adicionalmente, enquanto a lista de ordens modificadas deve ser passada para o método Refresh() da coleção de eventos para manipulação:

//+------------------------------------------------------------------+
//| Check trading events                                             |
//+------------------------------------------------------------------+
void CEngine::TradeEventsControl(void)
  {
//--- Initialize the trading events code and flag
   this.m_is_market_trade_event=false;
   this.m_is_history_trade_event=false;
//--- Update the lists 
   this.m_market.Refresh();
   this.m_history.Refresh();
//--- First launch actions
   if(this.IsFirstStart())
     {
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      return;
     }
//--- Check the changes in the market status and account history 
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();

//--- If there is any event, send the lists, the flags and the number of new orders and deals to the event collection, and update it
   int change_total=0;
   CArrayObj* list_changes=this.m_market.GetListChanges();
   if(list_changes!=NULL)
      change_total=list_changes.Total();
   if(this.m_is_history_trade_event || this.m_is_market_trade_event || change_total>0)
     {
      this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),list_changes,
                            this.m_is_history_trade_event,this.m_is_market_trade_event,
                            this.m_history.NewOrders(),this.m_market.NewPendingOrders(),
                            this.m_market.NewMarketOrders(),this.m_history.NewDeals());
      //--- Receive the last account trading event
      this.m_acc_trade_event=this.m_events.GetLastTradeEvent();
     }
  }
//+------------------------------------------------------------------+

Como a ativação de uma ordem StopLimit leva a uma ordem Limit, nós "qualificaremos" esse evento como uma ordem pendente, enquanto o motivo do evento é a ativação de uma ordem StopLimit EVENT_REASON_STOPLIMIT_TRIGGERED. Nós já definimos sua constante na enumeração ENUM_EVENT_REASON do arquivo Defines.mqh.

Vamos melhorar a classe EventOrderPlased para exibir o programa de eventos no diário e enviá-lo ao programa de controle:

Basta adicionar o manipulador do motivo do evento EVENT_REASON_STOPLIMIT_TRIGGERED.

//+------------------------------------------------------------------+
//|                                             EventOrderPlased.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Event.mqh"
//+------------------------------------------------------------------+
//| Placing a pending order event                                    |
//+------------------------------------------------------------------+
class CEventOrderPlased : public CEvent
  {
public:
//--- Constructor
                     CEventOrderPlased(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_MARKET_PENDING,event_code,ticket) {}
//--- Supported order properties (1) real, (2) integer
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
//--- (1) Display a brief message about the event in the journal, (2) Send the event to the chart
   virtual void      PrintShort(void);
   virtual void      SendEvent(void);
  };
//+------------------------------------------------------------------+
//| Return 'true' if the event supports the passed                   |
//| integer property, otherwise return 'false'                       |
//+------------------------------------------------------------------+
bool CEventOrderPlased::SupportProperty(ENUM_EVENT_PROP_INTEGER property)
  {
   if(property==EVENT_PROP_TYPE_DEAL_EVENT         ||
      property==EVENT_PROP_TICKET_DEAL_EVENT       ||
      property==EVENT_PROP_TYPE_ORDER_POSITION     ||
      property==EVENT_PROP_TICKET_ORDER_POSITION   ||
      property==EVENT_PROP_POSITION_ID             ||
      property==EVENT_PROP_POSITION_BY_ID          ||
      property==EVENT_PROP_TIME_ORDER_POSITION
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Return 'true' if the event supports the passed                   |
//| real property, otherwise return 'false'                          |
//+------------------------------------------------------------------+
bool CEventOrderPlased::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
  {
   if(property==EVENT_PROP_PRICE_CLOSE             ||
      property==EVENT_PROP_PROFIT
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Display a brief message about the event in the journal           |
//+------------------------------------------------------------------+
void CEventOrderPlased::PrintShort(void)
  {
   int    digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS);
   string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   string sl=(this.PriceStopLoss()>0 ? ", sl "+::DoubleToString(this.PriceStopLoss(),digits) : "");
   string tp=(this.PriceTakeProfit()>0 ? ", tp "+::DoubleToString(this.PriceTakeProfit(),digits) : "");
   string vol=::DoubleToString(this.VolumeOrderInitial(),DigitsLots(this.Symbol()));
   string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : "");
   string type=this.TypeOrderFirstDescription()+" #"+(string)this.TicketOrderEvent();
   string event=TextByLanguage(" Установлен "," Placed ");
   string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceOpen(),digits);
   string txt=head+this.Symbol()+event+vol+" "+type+price+sl+tp+magic;
   //--- If StopLimit order is activated
   if(this.Reason()==EVENT_REASON_STOPLIMIT_TRIGGERED)
     {
      head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimeEvent())+" -\n";
      event=TextByLanguage(" Сработал "," Triggered ");
      type=
        (
         OrderTypeDescription(this.TypeOrderPosPrevious())+" #"+(string)this.TicketOrderEvent()+
         TextByLanguage(" по цене "," at price ")+DoubleToString(this.PriceEvent(),digits)+" -->\n"+
         vol+" "+OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderEvent()+
         TextByLanguage(" на цену "," on price ")+DoubleToString(this.PriceOpen(),digits)
        );
      txt=head+this.Symbol()+event+"("+TimeMSCtoString(this.TimePosition())+") "+vol+" "+type+sl+tp+magic;
     }
   ::Print(txt);
  }
//+------------------------------------------------------------------+
//| Send the event to the chart                                      |
//+------------------------------------------------------------------+
void CEventOrderPlased::SendEvent(void)
  {
   this.PrintShort();
   ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.TicketOrderEvent(),this.PriceOpen(),this.Symbol());
  }
//+------------------------------------------------------------------+

Aqui tudo é muito fácil de entender. Não faz sentido insistir em ações simples.

Isso conclui a melhoria da biblioteca para a monitoração da ativação de uma ordem StopLimit.

Teste

Para testar as melhorias implementadas, nós usaremos o EA do artigo anterior. Basta renomear o EA TestDoEasyPart06.mq5 da pasta \MQL5\Experts\TestDoEasy\Part06 para TestDoEasyPart07.mq5 e salvá-lo na nova subpasta \MQL5\Experts\TestDoEasy\Part07.

Compile o EA, inicie-o no testador, coloque uma ordem StopLimit e aguarde a sua ativação:



Qual é o próximo?

A funcionalidade implementada no artigo inclui a capacidade de adicionar rapidamente o monitoramento de outros eventos: modificação de propriedades de ordem pendentes — seus níveis de preço, StopLoss e TakeProfit, bem como a modificação dos níveis de StopLoss e TakeProfit das posições. Nós vamos considerar essas tarefas no próximo artigo.

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

Voltar ao conteúdo

Artigos anteriores da série:

Parte 1. Conceito, gerenciamento de dados.
Parte 2. Coleção do histórico de ordens e negócios.
Parte 3 Coleção de ordens e posições de mercado, busca e ordenação.
Parte 4 Eventos de negociação. Conceito.
Parte 5. Classes e coleção de eventos de negociação. Envio de eventos para o programa.
Parte 6. Eventos da conta netting.