Biblioteca para desenvolvimento fácil e rápido de programas para a MetaTrader (parte XI). Compatibilidade com a MQL4 - Eventos de encerramento de posição

Artyom Trishkin | 2 setembro, 2019

Conteúdo


Remoção das propriedades não utilizadas

Enquanto eu trabalhava na definição dos eventos, eu notei que na MQL5 todos os parâmetros de horário são definidos em milissegundos. A MQL4 não possui essas propriedades de ordem e posição, mas nada nos impede de usar o horário em segundos expressado em milissegundos para a MQL4. Em outras palavras, qualquer horário em segundos é simplesmente duplicado pelo horário em milissegundos, não sendo usado em nenhum lugar. O horário de recebimento e exibição a ser definido em segundos é bastante idêntico ao recebimento em milissegundos, exceto pelo três dígitos adicionais, que indicam o número de milissegundos no formato de horário exibido.

Portanto, eu decidi remover todas as propriedades de horário definidas em segundos das propriedades da ordem, caso um ordem tenha a mesma propriedade em milissegundos.
Como nós decidimos remover algo, seria bom adicionar algo também. Então, vamos adicionar a nova propriedade "comentário personalizado" a cada ordem. Ela pode ser definida a qualquer momento para qualquer ordem ou posição (ambas de abertura e encerramento/remoção). Por que nós precisamos disso? Por exemplo, isso pode ser necessário para os rótulos de texto para as ordens que atendam a determinadas condições ou para a exibição visual (posteriormente, a biblioteca deve apresentar seu próprio shell gráfico) para que uma ordem marcada com um rótulo de texto possa ser facilmente exibido usando várias construções gráficas.

Abrimos o arquivo Defines.mqh, pressionamos Ctrl+F para encontrar todas as propriedades da ordem que possuem o horário em segundos e uma propriedade semelhante com o final "_MSC" (essa propriedade é definida em milissegundos). Removemos as propriedades em milissegundos da ordem deixando as propriedades em segundos e substituindo o número de propriedades do tipo inteiro de 24 para 21:

//+------------------------------------------------------------------+
//| Order, deal, position integer properties                         |
//+------------------------------------------------------------------+
enum ENUM_ORDER_PROP_INTEGER
  {
   ORDER_PROP_TICKET = 0,                                   // Order ticket
   ORDER_PROP_MAGIC,                                        // Order magic number
   ORDER_PROP_TIME_OPEN,                                    // Open time (MQL5 Deal time)
   ORDER_PROP_TIME_CLOSE,                                   // Close time (MQL5 Execution or removal time - ORDER_TIME_DONE)
   ORDER_PROP_TIME_OPEN_MSC,                                // Open time in milliseconds (MQL5 Deal time in msc)
   ORDER_PROP_TIME_CLOSE_MSC,                               // Close time in milliseconds (MQL5 Execution or removal time - ORDER_TIME_DONE_MSC)
   ORDER_PROP_TIME_EXP,                                     // Order expiration date (for pending orders)
   ORDER_PROP_STATUS,                                       // Order status (from the ENUM_ORDER_STATUS enumeration)
   ORDER_PROP_TYPE,                                         // Order/deal type
   ORDER_PROP_REASON,                                       // Deal/order/position reason or source
   ORDER_PROP_STATE,                                        // Order status (from the ENUM_ORDER_STATE enumeration)
   ORDER_PROP_POSITION_ID,                                  // Position ID
   ORDER_PROP_POSITION_BY_ID,                               // Opposite position ID
   ORDER_PROP_DEAL_ORDER_TICKET,                            // Ticket of the order that triggered a deal
   ORDER_PROP_DEAL_ENTRY,                                   // Deal direction – IN, OUT or IN/OUT
   ORDER_PROP_TIME_UPDATE,                                  // Position change time in seconds
   ORDER_PROP_TIME_UPDATE_MSC,                              // Position change time in milliseconds
   ORDER_PROP_TICKET_FROM,                                  // Parent order ticket
   ORDER_PROP_TICKET_TO,                                    // Derived order ticket
   ORDER_PROP_PROFIT_PT,                                    // Profit in points
   ORDER_PROP_CLOSE_BY_SL,                                  // Flag of closing by StopLoss
   ORDER_PROP_CLOSE_BY_TP,                                  // Flag of closing by TakeProfit
   ORDER_PROP_GROUP_ID,                                     // Order/position group ID
   ORDER_PROP_DIRECTION,                                    // Direction type (Buy, Sell)
  }; 
#define ORDER_PROP_INTEGER_TOTAL    (24)                    // Total number of integer properties
#define ORDER_PROP_INTEGER_SKIP     (0)                     // Number of order properties not used in sorting
//+------------------------------------------------------------------+

Após as alterações, a listagem de propriedades inteiras da ordem ficará da seguinte maneira:

//+------------------------------------------------------------------+
//| Order, deal, position integer properties                         |
//+------------------------------------------------------------------+
enum ENUM_ORDER_PROP_INTEGER
  {
   ORDER_PROP_TICKET = 0,                                   // Order ticket
   ORDER_PROP_MAGIC,                                        // Order magic number
   ORDER_PROP_TIME_OPEN,                                    // Open time in milliseconds (MQL5 Deal time)
   ORDER_PROP_TIME_CLOSE,                                   // Close time in milliseconds (MQL5 Execution or removal time - ORDER_TIME_DONE)
   ORDER_PROP_TIME_EXP,                                     // Order expiration date (for pending orders)
   ORDER_PROP_STATUS,                                       // Order status (from the ENUM_ORDER_STATUS enumeration)
   ORDER_PROP_TYPE,                                         // Order/deal type
   ORDER_PROP_REASON,                                       // Deal/order/position reason or source
   ORDER_PROP_STATE,                                        // Order status (from the ENUM_ORDER_STATE enumeration)
   ORDER_PROP_POSITION_ID,                                  // Position ID
   ORDER_PROP_POSITION_BY_ID,                               // Opposite position ID
   ORDER_PROP_DEAL_ORDER_TICKET,                            // Ticket of the order that triggered a deal
   ORDER_PROP_DEAL_ENTRY,                                   // Deal direction – IN, OUT or IN/OUT
   ORDER_PROP_TIME_UPDATE,                                  // Position change time in milliseconds
   ORDER_PROP_TICKET_FROM,                                  // Parent order ticket
   ORDER_PROP_TICKET_TO,                                    // Derived order ticket
   ORDER_PROP_PROFIT_PT,                                    // Profit in points
   ORDER_PROP_CLOSE_BY_SL,                                  // Flag of closing by StopLoss
   ORDER_PROP_CLOSE_BY_TP,                                  // Flag of closing by TakeProfit
   ORDER_PROP_GROUP_ID,                                     // Order/position group ID
   ORDER_PROP_DIRECTION,                                    // Direction type (Buy, Sell)
  }; 
#define ORDER_PROP_INTEGER_TOTAL    (21)                    // Total number of integer properties
#define ORDER_PROP_INTEGER_SKIP     (0)                     // Number of order properties not used in sorting
//+------------------------------------------------------------------+

Vamos encontrar a enumeração das possíveis opções de seleção por horário e remover as constantes de seleção em milissegundos:

//+------------------------------------------------------------------+
//| Possible selection options by time                               |
//+------------------------------------------------------------------+
enum ENUM_SELECT_BY_TIME
  {
   SELECT_BY_TIME_OPEN,                                     // By open time
   SELECT_BY_TIME_CLOSE,                                    // By close time
   SELECT_BY_TIME_OPEN_MSC,                                 // By open time in milliseconds
   SELECT_BY_TIME_CLOSE_MSC,                                // By close time in milliseconds
  };
//+------------------------------------------------------------------+

A enumeração consistirá de apenas duas constantes:

//+------------------------------------------------------------------+
//| Possible selection options by time                               |
//+------------------------------------------------------------------+
enum ENUM_SELECT_BY_TIME
  {
   SELECT_BY_TIME_OPEN,                                     // By open time (in milliseconds)
   SELECT_BY_TIME_CLOSE,                                    // By close time (in milliseconds)
  };
//+------------------------------------------------------------------+

Agora, ao definir a seleção por horário, a seleção é realizada em milissegundos para a MQL5 e em segundos para a MQL4.

Adicionamos o novo "comentário personalizado" para as propriedades do tipo string das ordens e aumentamos o número total de propriedades do tipo string para 4:

//+------------------------------------------------------------------+
//| Order, deal, position string properties                          |
//+------------------------------------------------------------------+
enum ENUM_ORDER_PROP_STRING
  {
   ORDER_PROP_SYMBOL = (ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL), // Order symbol
   ORDER_PROP_COMMENT,                                      // Order comment
   ORDER_PROP_COMMENT_EXT,                                  // Order custom comment
   ORDER_PROP_EXT_ID                                        // Order ID in the external trading system
  };
#define ORDER_PROP_STRING_TOTAL     (4)                     // Total number of string properties
//+------------------------------------------------------------------+

Vamos remover todas as referências à milissegundos na enumeração de possíveis critérios de ordenação (agora elas são usadas por padrão no ordenamento por horário) e adicionar o critério para ordenar pelo comentário personalizado:

//+------------------------------------------------------------------+
//| Possible criteria of orders and deals sorting                    |
//+------------------------------------------------------------------+
#define FIRST_ORD_DBL_PROP          (ORDER_PROP_INTEGER_TOTAL-ORDER_PROP_INTEGER_SKIP)
#define FIRST_ORD_STR_PROP          (ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL-ORDER_PROP_INTEGER_SKIP)
enum ENUM_SORT_ORDERS_MODE
  {
   //--- Sort by integer properties
   SORT_BY_ORDER_TICKET          =  0,                      // Sort by order ticket
   SORT_BY_ORDER_MAGIC           =  1,                      // Sort by order magic number
   SORT_BY_ORDER_TIME_OPEN       =  2,                      // Sort by order open time in milliseconds
   SORT_BY_ORDER_TIME_CLOSE      =  3,                      // Sort by order close time in milliseconds
   SORT_BY_ORDER_TIME_EXP        =  4,                      // Sort by order expiration date
   SORT_BY_ORDER_STATUS          =  5,                      // Sort by order status (market order/pending order/deal/balance, credit operation)
   SORT_BY_ORDER_TYPE            =  6,                      // Sort by order type
   SORT_BY_ORDER_REASON          =  7,                      // Sort by order/position reason/source
   SORT_BY_ORDER_STATE           =  8,                     // Sort by order status
   SORT_BY_ORDER_POSITION_ID     =  9,                     // Sort by position ID
   SORT_BY_ORDER_POSITION_BY_ID  =  10,                     // Sort by opposite position ID
   SORT_BY_ORDER_DEAL_ORDER      =  11,                     // Sort by order a deal is based on
   SORT_BY_ORDER_DEAL_ENTRY      =  12,                     // Sort by deal direction – IN, OUT or IN/OUT
   SORT_BY_ORDER_TIME_UPDATE     =  13,                     // Sort by position change time in seconds
   SORT_BY_ORDER_TICKET_FROM     =  14,                     // Sort by parent order ticket
   SORT_BY_ORDER_TICKET_TO       =  15,                     // Sort by derived order ticket
   SORT_BY_ORDER_PROFIT_PT       =  16,                     // Sort by order profit in points
   SORT_BY_ORDER_CLOSE_BY_SL     =  17,                     // Sort by order closing by StopLoss flag
   SORT_BY_ORDER_CLOSE_BY_TP     =  18,                     // Sort by order closing by TakeProfit flag
   SORT_BY_ORDER_GROUP_ID        =  19,                     // Sort by order/position group ID
   SORT_BY_ORDER_DIRECTION       =  20,                     // Sort by direction (Buy, Sell)
   //--- Sort by real properties
   SORT_BY_ORDER_PRICE_OPEN      =  FIRST_ORD_DBL_PROP,     // Sort by open price
   SORT_BY_ORDER_PRICE_CLOSE     =  FIRST_ORD_DBL_PROP+1,   // Sort by close price
   SORT_BY_ORDER_SL              =  FIRST_ORD_DBL_PROP+2,   // Sort by StopLoss price
   SORT_BY_ORDER_TP              =  FIRST_ORD_DBL_PROP+3,   // Sort by TakeProfit price
   SORT_BY_ORDER_PROFIT          =  FIRST_ORD_DBL_PROP+4,   // Sort by profit
   SORT_BY_ORDER_COMMISSION      =  FIRST_ORD_DBL_PROP+5,   // Sort by commission
   SORT_BY_ORDER_SWAP            =  FIRST_ORD_DBL_PROP+6,   // Sort by swap
   SORT_BY_ORDER_VOLUME          =  FIRST_ORD_DBL_PROP+7,   // Sort by volume
   SORT_BY_ORDER_VOLUME_CURRENT  =  FIRST_ORD_DBL_PROP+8,   // Sort by unexecuted volume
   SORT_BY_ORDER_PROFIT_FULL     =  FIRST_ORD_DBL_PROP+9,   // Sort by profit+commission+swap criterion
   SORT_BY_ORDER_PRICE_STOP_LIMIT=  FIRST_ORD_DBL_PROP+10,  // Sort by Limit order when StopLimit order is activated
   //--- Sort by string properties
   SORT_BY_ORDER_SYMBOL          =  FIRST_ORD_STR_PROP,     // Sort by symbol
   SORT_BY_ORDER_COMMENT         =  FIRST_ORD_STR_PROP+1,   // Sort by comment
   SORT_BY_ORDER_COMMENT_EXT     =  FIRST_ORD_STR_PROP+2,   // Sort by custom comment
   SORT_BY_ORDER_EXT_ID          =  FIRST_ORD_STR_PROP+3    // Sort by order ID in an external trading system
  };
//+------------------------------------------------------------------+

Isso conclui as alterações no arquivo Defines.mqh. Agora nós precisamos remover todas as referências as propriedades de ordens removidas nos arquivos da biblioteca:

nós substituímos todas as instâncias dos modos de ordenação em todos os arquivos da biblioteca

SORT_BY_ORDER_TIME_OPEN_MSC

e

SORT_BY_ORDER_TIME_CLOSE_MSC

para

SORT_BY_ORDER_TIME_OPEN

e

SORT_BY_ORDER_TIME_CLOSE

Nós removemos todas as referências para ordenar as propriedades em milissegundos (agora são milissegundos por padrão) nos arquivos das classes herdadas da ordem abstrata HistoryDeal.mqh, HistoryOrder.mqh, HistoryPending.mqh, MarketOrder.mqh, MarketPending.mqh e MarketPosition.mqh:

ORDER_PROP_TIME_CLOSE_MSC

e
ORDER_PROP_TIME_UPDATE_MSC

Na arquivo da classe de ordem abstrata COrder Order.mqh, nós removemos da seção privada os métodos que retornam o horário em segundos:

   datetime          OrderOpenTime(void)           const;
   datetime          OrderCloseTime(void)          const;
   datetime          OrderExpiration(void)         const;
   datetime          PositionTimeUpdate(void)      const;
   datetime          PositionTimeUpdateMSC(void)   const;

Removemos da seção pública da classe os métodos de acesso simplificado que retornam o horário em milissegundos. Ela será retornada pelos métodos que retornam o horário em segundos:

//+------------------------------------------------------------------+
//| Methods of a simplified access to the order object properties    |
//+------------------------------------------------------------------+
   //--- Return (1) ticket, (2) parent order ticket, (3) derived order ticket, (4) magic number, (5) order reason,
   //--- (6) position ID, (7) opposite position ID, (8) group ID, (9) type, (10) flag of closing by StopLoss,
   //--- (11) flag of closing by TakeProfit (12) open time, (13) close time, (14) open time in milliseconds,
   //--- (15) close time in milliseconds, (16) expiration date, (17) state, (18) status, (19) order type by direction
   long              Ticket(void)                                       const { return this.GetProperty(ORDER_PROP_TICKET);                     }
   long              TicketFrom(void)                                   const { return this.GetProperty(ORDER_PROP_TICKET_FROM);                }
   long              TicketTo(void)                                     const { return this.GetProperty(ORDER_PROP_TICKET_TO);                  }
   long              Magic(void)                                        const { return this.GetProperty(ORDER_PROP_MAGIC);                      }
   long              Reason(void)                                       const { return this.GetProperty(ORDER_PROP_REASON);                     }
   long              PositionID(void)                                   const { return this.GetProperty(ORDER_PROP_POSITION_ID);                }
   long              PositionByID(void)                                 const { return this.GetProperty(ORDER_PROP_POSITION_BY_ID);             }
   long              GroupID(void)                                      const { return this.GetProperty(ORDER_PROP_GROUP_ID);                   }
   long              TypeOrder(void)                                    const { return this.GetProperty(ORDER_PROP_TYPE);                       }
   bool              IsCloseByStopLoss(void)                            const { return (bool)this.GetProperty(ORDER_PROP_CLOSE_BY_SL);          }
   bool              IsCloseByTakeProfit(void)                          const { return (bool)this.GetProperty(ORDER_PROP_CLOSE_BY_TP);          }
   datetime          TimeOpen(void)                                     const { return (datetime)this.GetProperty(ORDER_PROP_TIME_OPEN);        }
   datetime          TimeClose(void)                                    const { return (datetime)this.GetProperty(ORDER_PROP_TIME_CLOSE);       }
   datetime          TimeOpenMSC(void                                 const { return (datetime)this.GetProperty(ORDER_PROP_TIME_OPEN_MSC);    }
   datetime          TimeCloseMSC(void)                                 const { return (datetime)this.GetProperty(ORDER_PROP_TIME_CLOSE_MSC);   }
   datetime          TimeExpiration(void)                               const { return (datetime)this.GetProperty(ORDER_PROP_TIME_EXP);         }
   ENUM_ORDER_STATE  State(void)                                        const { return (ENUM_ORDER_STATE)this.GetProperty(ORDER_PROP_STATE);    }
   ENUM_ORDER_STATUS Status(void)                                       const { return (ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS);  }
   ENUM_ORDER_TYPE   TypeByDirection(void)                              const { return (ENUM_ORDER_TYPE)this.GetProperty(ORDER_PROP_DIRECTION); }
   
   //--- Return (1) open price, (2) close price, (3) profit, (4) commission, (5) swap, (6) volume, 

Also, add the methods returning and setting an order custom comment:

   //--- Return (1) symbol, (2) comment, (3) ID at an exchange
   string            Symbol(void)                                       const { return this.GetProperty(ORDER_PROP_SYMBOL);                     }
   string            Comment(void)                                      const { return this.GetProperty(ORDER_PROP_COMMENT);                    }
   string            CommentExt(void)                                   const { return this.GetProperty(ORDER_PROP_COMMENT_EXT);                }
   string            ExternalID(void)                                   const { return this.GetProperty(ORDER_PROP_EXT_ID);                     }

   //--- Get the full order profit
   double            ProfitFull(void)                                   const { return this.Profit()+this.Comission()+this.Swap();              }
   //--- Get order profit in points
   int               ProfitInPoints(void) const;
//--- Set (1) group ID and (2) custom comment
   void              SetGroupID(const long group_id)                          { this.SetProperty(ORDER_PROP_GROUP_ID,group_id);                 }
   void              SetCommentExt(const string comment_ext)                  { this.SetProperty(ORDER_PROP_COMMENT_EXT,comment_ext);           }
   

No construtor fechado da classe COrder, removemos o armazenamento das propriedades do horário em segundos e substituímos pelas propriedades em milissegundos pelas propriedades em segundos, onde salvamos o horário em milissegundos. Adicionamos o armazenamento de um comentário personalizado como uma string vazia:

//+------------------------------------------------------------------+
//| Closed parametric constructor                                    |
//+------------------------------------------------------------------+
COrder::COrder(ENUM_ORDER_STATUS order_status,const ulong ticket)
  {
//--- Save integer properties
   this.m_ticket=ticket;
   this.m_long_prop[ORDER_PROP_STATUS]                               = order_status;
   this.m_long_prop[ORDER_PROP_MAGIC]                                = this.OrderMagicNumber();
   this.m_long_prop[ORDER_PROP_TICKET]                               = this.OrderTicket();
   this.m_long_prop[ORDER_PROP_TIME_EXP]                             = this.OrderExpiration();
   this.m_long_prop[ORDER_PROP_TYPE]                                 = this.OrderType();
   this.m_long_prop[ORDER_PROP_STATE]                                = this.OrderState();
   this.m_long_prop[ORDER_PROP_DIRECTION]                            = this.OrderTypeByDirection();
   this.m_long_prop[ORDER_PROP_POSITION_ID]                          = this.OrderPositionID();
   this.m_long_prop[ORDER_PROP_REASON]                               = this.OrderReason();
   this.m_long_prop[ORDER_PROP_DEAL_ORDER_TICKET]                    = this.DealOrderTicket();
   this.m_long_prop[ORDER_PROP_DEAL_ENTRY]                           = this.DealEntry();
   this.m_long_prop[ORDER_PROP_POSITION_BY_ID]                       = this.OrderPositionByID();
   this.m_long_prop[ORDER_PROP_TIME_OPEN]                            = this.OrderOpenTimeMSC();     
   this.m_long_prop[ORDER_PROP_TIME_CLOSE]                           = this.OrderCloseTimeMSC();    
   this.m_long_prop[ORDER_PROP_TIME_UPDATE]                          = this.PositionTimeUpdateMSC();

   
//--- Save real properties
   this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_OPEN)]         = this.OrderOpenPrice();
   this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_CLOSE)]        = this.OrderClosePrice();
   this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT)]             = this.OrderProfit();
   this.m_double_prop[this.IndexProp(ORDER_PROP_COMMISSION)]         = this.OrderCommission();
   this.m_double_prop[this.IndexProp(ORDER_PROP_SWAP)]               = this.OrderSwap();
   this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME)]             = this.OrderVolume();
   this.m_double_prop[this.IndexProp(ORDER_PROP_SL)]                 = this.OrderStopLoss();
   this.m_double_prop[this.IndexProp(ORDER_PROP_TP)]                 = this.OrderTakeProfit();
   this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME_CURRENT)]     = this.OrderVolumeCurrent();
   this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_STOP_LIMIT)]   = this.OrderPriceStopLimit();
   
//--- Save string properties
   this.m_string_prop[this.IndexProp(ORDER_PROP_SYMBOL)]             = this.OrderSymbol();
   this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT)]            = this.OrderComment();
   this.m_string_prop[this.IndexProp(ORDER_PROP_EXT_ID)]             = this.OrderExternalID();
   
//--- Save additional integer properties
   this.m_long_prop[ORDER_PROP_PROFIT_PT]                            = this.ProfitInPoints();
   this.m_long_prop[ORDER_PROP_TICKET_FROM]                          = this.OrderTicketFrom();
   this.m_long_prop[ORDER_PROP_TICKET_TO]                            = this.OrderTicketTo();
   this.m_long_prop[ORDER_PROP_CLOSE_BY_SL]                          = this.OrderCloseByStopLoss();
   this.m_long_prop[ORDER_PROP_CLOSE_BY_TP]                          = this.OrderCloseByTakeProfit();
   this.m_long_prop[ORDER_PROP_GROUP_ID]                             = 0;
   
//--- Save additional real properties
   this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT_FULL)]        = this.ProfitFull();
   
//--- Save additional string properties
   this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT_EXT)]        = "";
  }
//+------------------------------------------------------------------+

No método que retorna o ID da posição para a MQL4, fizemos o seguinte: se esta for uma posição de mercado, retornamos o seu ticket, caso contrário retornamos zero. O ticket de abertura de posição atua como um ID de posição em MQL5. Ele permanece inalterado durante toda a vida útil da posição.

Portanto, na MQL4, apenas um ticket da posição pode servir como um ID de posição. Uma ordem pendente em MQL4 não tem esse ID. Se uma ordem for removida, nenhuma posição será aberta com ela. Se uma ordem for ativada, ela não será encontrada no histórico de ordens em MQL4, no entanto, a posição receberá o seu ticket, portanto, o ticket atua como o ID da posição.
//+------------------------------------------------------------------+
//| Return the position ID                                           |
//+------------------------------------------------------------------+
long COrder::OrderPositionID(void) const
  {
#ifdef __MQL4__
   return(this.Status()==ORDER_STATUS_MARKET_POSITION ? this.Ticket() : 0);
#else
   long id=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_POSITION   : id=::PositionGetInteger(POSITION_IDENTIFIER);             break;
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : id=::OrderGetInteger(ORDER_POSITION_ID);                  break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : id=::HistoryOrderGetInteger(m_ticket,ORDER_POSITION_ID);  break;
      case ORDER_STATUS_DEAL              : id=::HistoryDealGetInteger(m_ticket,DEAL_POSITION_ID);    break;
      default                             : id=0;                                                     break;
     }
   return id;
#endif
  }
//+------------------------------------------------------------------+

Vamos complementar o método que retorna o ID da posição oposta para MQL4:

//+------------------------------------------------------------------+
//| Retorna o ID da posição oposta                                   |
//+------------------------------------------------------------------+
long COrder::OrderPositionByID(void) const
  {
   long ticket=0;
#ifdef __MQL4__
   string order_comment=::OrderComment();
   if(::StringFind(order_comment,"close hedge by #")>WRONG_VALUE) ticket=::StringToInteger(::StringSubstr(order_comment,16));
#else
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : ticket=::OrderGetInteger(ORDER_POSITION_BY_ID);                 break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : ticket=::HistoryOrderGetInteger(m_ticket,ORDER_POSITION_BY_ID); break;
      default                             : ticket=0;                                                       break;
     }
#endif
   return ticket;
  }
//+------------------------------------------------------------------+

Aqui, se for em MQL4 e se o comentário da ordem apresentar a linha "close hedge by #", calculamos na linha do comentário o índice do início do número do ticket da ordem oposta e atribuímos ao valor retornado por este método.

Removemos da classe listada a implementação dos dois métodos que não são mais necessários, pois não queremos mais o horário em segundos:

//+------------------------------------------------------------------+
//| Return open time                                                 |
//+------------------------------------------------------------------+
datetime COrder::OrderOpenTime(void) const
  {
#ifdef __MQL4__
   return ::OrderOpenTime();
#else 
   datetime res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_POSITION   : res=(datetime)::PositionGetInteger(POSITION_TIME);                 break;
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : res=(datetime)::OrderGetInteger(ORDER_TIME_SETUP);                 break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=(datetime)::HistoryOrderGetInteger(m_ticket,ORDER_TIME_SETUP); break;
      case ORDER_STATUS_DEAL              : res=(datetime)::HistoryDealGetInteger(m_ticket,DEAL_TIME);         break;
      default                             : res=0;                                                             break;
     }
   return res;
#endif 
  }
//+------------------------------------------------------------------+
//| Return close time                                                |
//+------------------------------------------------------------------+
datetime COrder::OrderCloseTime(void) const
  {
#ifdef __MQL4__
   return ::OrderCloseTime();
#else 
   datetime res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=(datetime)::HistoryOrderGetInteger(m_ticket,ORDER_TIME_DONE);  break;
      case ORDER_STATUS_DEAL              : res=(datetime)::HistoryDealGetInteger(m_ticket,DEAL_TIME);         break;
      default                             : res=0;                                                             break;
     }
   return res;
#endif 
  }
//+------------------------------------------------------------------+

Para uma exibição mais significativa do estado da ordem em MQL4, vamos fazer pequenas alterações no método que retorna a descrição do estado da ordem:

//+------------------------------------------------------------------+
//| Return the order status name                                     |
//+------------------------------------------------------------------+
string COrder::StatusDescription(void) const
  {
   ENUM_ORDER_STATUS status=this.Status();
   ENUM_ORDER_TYPE   type=(ENUM_ORDER_TYPE)this.TypeOrder();
   return
     (
      status==ORDER_STATUS_BALANCE           ?  TextByLanguage("Балансовая операция","Balance operation") :
      #ifdef __MQL5__
      status==ORDER_STATUS_MARKET_ORDER || status==ORDER_STATUS_HISTORY_ORDER ?  
         (
          type==ORDER_TYPE_CLOSE_BY ? TextByLanguage("Закрывающий ордер","Order for closing by")         :
          TextByLanguage("Ордер на ","The order to ")+(type==ORDER_TYPE_BUY ? TextByLanguage("покупку","buy") : TextByLanguage("продажу","sell"))
         ) :
      #else 
      status==ORDER_STATUS_HISTORY_ORDER     ?  TextByLanguage("Исторический ордер","History order")     :
      #endif 
      status==ORDER_STATUS_DEAL              ?  TextByLanguage("Сделка","Deal")                          :
      status==ORDER_STATUS_MARKET_POSITION   ?  TextByLanguage("Позиция","Active position")              :
      status==ORDER_STATUS_MARKET_PENDING    ?  TextByLanguage("Установленный отложенный ордер","Active pending order") :
      status==ORDER_STATUS_HISTORY_PENDING   ?  TextByLanguage("Отложенный ордер","Pending order") :
      EnumToString(status)
     );
  }
//+------------------------------------------------------------------+

Aqui, vamos retornar para as ordens pendentes remotas e posições encerradasem MQL4, a descrição do estado como "Ordem histórica".

No método que retorna a descrição da propriedade do tipo inteiro da ordem, alteramos as linhas que contêm as descrições das propriedades ORDER_PROP_TIME_OPEN, ORDER_PROP_TIME_CLOSE e ORDER_PROP_TIME_UPDATE para que as propriedades em milissegundos sejam retornadas para elas:

//+------------------------------------------------------------------+
//| Return description of an order's integer property                |
//+------------------------------------------------------------------+
string COrder::GetPropertyDescription(ENUM_ORDER_PROP_INTEGER property)
  {
   return
     (
   //--- General properties
      property==ORDER_PROP_MAGIC             ?  TextByLanguage("Магик","Magic number")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TICKET            ?  TextByLanguage("Тикет","Ticket")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          " #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TICKET_FROM       ?  TextByLanguage("Тикет родительского ордера","Ticket of parent order")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          " #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TICKET_TO         ?  TextByLanguage("Тикет наследуемого ордера","Inherited order ticket")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          " #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TIME_EXP          ?  TextByLanguage("Дата экспирации","Date of expiration")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          (this.GetProperty(property)==0     ?  TextByLanguage(": Не задана",": Not set") :
          ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS))
         )  :
      property==ORDER_PROP_TYPE              ?  TextByLanguage("Тип","Type")+": "+this.TypeDescription()                   :
      property==ORDER_PROP_DIRECTION         ?  TextByLanguage("Тип по направлению","Type by direction")+": "+this.DirectionDescription() :
      
      property==ORDER_PROP_REASON            ?  TextByLanguage("Причина","Reason")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+this.GetReasonDescription(this.GetProperty(property))
         )  :
      property==ORDER_PROP_POSITION_ID       ?  TextByLanguage("Идентификатор позиции","Position identifier")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_DEAL_ORDER_TICKET ?  TextByLanguage("Сделка на основании ордера с тикетом","Deal by order ticket")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_DEAL_ENTRY        ?  TextByLanguage("Направление сделки","Deal entry")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+this.GetEntryDescription(this.GetProperty(property))
         )  :
      property==ORDER_PROP_POSITION_BY_ID    ?  TextByLanguage("Идентификатор встречной позиции","Opposite position identifier")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TIME_OPEN         ?  TextByLanguage("Время открытия в милисекундах","Opening time in milliseconds")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")"
         )  :
      property==ORDER_PROP_TIME_CLOSE        ?  TextByLanguage("Время закрытия в милисекундах","Closing time in milliseconds")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")"
         )  :
      property==ORDER_PROP_TIME_UPDATE       ?  TextByLanguage("Время изменения позиции в милисекундах","Time to change the position in milliseconds")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(this.GetProperty(property)!=0 ? TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")" : "0")
         )  :
      property==ORDER_PROP_STATE             ?  TextByLanguage("Состояние","Statе")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": \""+this.StateDescription()+"\""
         )  :
   //--- Additional property
      property==ORDER_PROP_STATUS            ?  TextByLanguage("Статус","Status")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": \""+this.StatusDescription()+"\""
         )  :
      property==ORDER_PROP_PROFIT_PT         ?  (
                                                 this.Status()==ORDER_STATUS_MARKET_PENDING ? 
                                                 TextByLanguage("Дистанция от цены в пунктах","Distance from price in points") : 
                                                 TextByLanguage("Прибыль в пунктах","Profit in points")
                                                )+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_CLOSE_BY_SL       ?  TextByLanguage("Закрытие по StopLoss","Close by StopLoss")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(this.GetProperty(property)   ?  TextByLanguage("Да","Yes") : TextByLanguage("Нет","No"))
         )  :
      property==ORDER_PROP_CLOSE_BY_TP       ?  TextByLanguage("Закрытие по TakeProfit","Close by TakeProfit")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(this.GetProperty(property)   ?  TextByLanguage("Да","Yes") : TextByLanguage("Нет","No"))
         )  :
      property==ORDER_PROP_GROUP_ID          ?  TextByLanguage("Идентификатор группы","Group identifier")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(string)this.GetProperty(property)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

e adicionamos o retorno da descrição do comentário personalizado para o método que retorna a descrição da propriedade do tipo string:

//+------------------------------------------------------------------+
//| Return description of the order's string property                |
//+------------------------------------------------------------------+
string COrder::GetPropertyDescription(ENUM_ORDER_PROP_STRING property)
  {
   return
     (
      property==ORDER_PROP_SYMBOL         ?  TextByLanguage("Символ","Symbol")+": \""+this.GetProperty(property)+"\""            :
      property==ORDER_PROP_COMMENT        ?  TextByLanguage("Комментарий","Comment")+
         (this.GetProperty(property)==""  ?  TextByLanguage(": Отсутствует",": Not set"):": \""+this.GetProperty(property)+"\"") :
      property==ORDER_PROP_COMMENT_EXT    ?  TextByLanguage("Пользовательский комментарий","Custom comment")+
         (this.GetProperty(property)==""  ?  TextByLanguage(": Не задан",": Not set"):": \""+this.GetProperty(property)+"\"") :
      property==ORDER_PROP_EXT_ID         ?  TextByLanguage("Идентификатор на бирже","Exchange identifier")+
         (!this.SupportProperty(property) ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
         (this.GetProperty(property)==""  ?  TextByLanguage(": Отсутствует",": Not set"):": \""+this.GetProperty(property)+"\"")):
      ""
     );
  }
//+------------------------------------------------------------------+

Isso conclui as alterações na classe de ordem abstrata COrder.

No arquivo de funções de serviço DELib.mqh, fizemos uma pequena melhoria na função que retorna o nome da ordem/posição pelo tipo da ordem:

//+------------------------------------------------------------------+
//| Return order name                                                |
//+------------------------------------------------------------------+
string OrderTypeDescription(const ENUM_ORDER_TYPE type,bool as_order=true)
  {
   string pref=(#ifdef __MQL5__ "Market order" #else (as_order ? "Market order" : "Position") #endif );
   return
     (
      type==ORDER_TYPE_BUY_LIMIT       ?  "Buy Limit"                                                 :
      type==ORDER_TYPE_BUY_STOP        ?  "Buy Stop"                                                  :
      type==ORDER_TYPE_SELL_LIMIT      ?  "Sell Limit"                                                :
      type==ORDER_TYPE_SELL_STOP       ?  "Sell Stop"                                                 :
   #ifdef __MQL5__
      type==ORDER_TYPE_BUY_STOP_LIMIT  ?  "Buy Stop Limit"                                            :
      type==ORDER_TYPE_SELL_STOP_LIMIT ?  "Sell Stop Limit"                                           :
      type==ORDER_TYPE_CLOSE_BY        ?  TextByLanguage("Закрывающий ордер","Order for closing by")  :  
   #else 
      type==ORDER_TYPE_BALANCE         ?  TextByLanguage("Балансовая операция","Balance operation")   :
      type==ORDER_TYPE_CREDIT          ?  TextByLanguage("Кредитная операция","Credit operation")     :
   #endif 
      type==ORDER_TYPE_BUY             ?  pref+" Buy"                                                 :
      type==ORDER_TYPE_SELL            ?  pref+" Sell"                                                :  
      TextByLanguage("Неизвестный тип ордера","Unknown order type")
     );
  }
//+------------------------------------------------------------------+

Aqui nós adicionamos a flag que gerencia a exibição do nome da ordem em MQL4 ou como uma ordem ou como uma posição. A exibição em MQL4 "como uma ordem" é definida por padrão. Por que isso foi feito? Suponha que uma ordem que causou a abertura de uma posição seja exibida entre colchetes ao enviar o evento de abertura da posição para o diário. Nesse caso, a mensagem [Position Sell #123] para a posição de venda aberta por uma ordem à mercado (não pendente) com o ticket 123 como uma ordem que causou a abertura da posição é substituída pela entrada mais significativa [Market order Sell #123].

Vamos melhorar o método AddToListMarket() da classe de coleções de ordens e posições de mercado. Em vez do horário de atualização da posição em milissegundos ORDER_PROP_TIME_UPDATE_MSC, agora nós usamos a hora de atualização da posição ORDER_PROP_TIME_UPDATE (é definida em milissegundos por padrão):

//+--------------------------------------------------------------------------------+
//| 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)+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 the list"));
      delete order;
     }
   return false;
  }
//+------------------------------------------------------------------+

Substituímos o horário da ordem em milissegundos pelo horário da ordem no método para a criação das ordens de controle e incluí-las na lista (pelo mesmo motivo):

//+------------------------------------------------------------------+
//| 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.TimeOpen());
   order_control.SetTimePrev(order.TimeOpen());
   order_control.SetVolume(order.Volume());
   order_control.SetTime(order.TimeOpen());
   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;
  }
//+------------------------------------------------------------------+

No arquivo HistoryCollection.mqh do método para selecionar as ordens pelo horário da classe de coleção do histórico de ordens e negócios CHistoryCollection, nós introduzimos a melhoria da seleção da propriedade comparada.
Como nós já tivemos uma seleção de quatro propriedades (horário de abertura em milissegundos, horário de fechamento em milissegundos, horário de abertura em segundos e horário de fechamento em segundos) e agora removemos duas delas, a seleção agora é simplificada:

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

   CArrayObj *list=new CArrayObj();
   if(list==NULL)
     {
      ::Print(DFUN+TextByLanguage("Ошибка создания временного списка","Error creating temporary list"));
      return NULL;
     }
   datetime begin=begin_time,end=(end_time==0 ? END_TIME : end_time);
   if(begin_time>end_time) begin=0;
   list.FreeMode(false);
   ListStorage.Add(list);
   //---
   this.m_order_instance.SetProperty(property,begin);
   int index_begin=this.m_list_all_orders.SearchGreatOrEqual(&m_order_instance);
   if(index_begin==WRONG_VALUE)
      return list;
   this.m_order_instance.SetProperty(property,end);
   int index_end=this.m_list_all_orders.SearchLessOrEqual(&m_order_instance);
   if(index_end==WRONG_VALUE)
      return list;
   for(int i=index_begin; i<=index_end; i++)
      list.Add(this.m_list_all_orders.At(i));
   return list;
  }
//+------------------------------------------------------------------+

No arquivo CEngine.mqh, substituímos todas as instâncias da constante de horário SORT_BY_ORDER_TIME_OPEN_MSC em milissegundos pela constante de horário SORT_BY_ORDER_TIME_OPEN. Agora, o horário em milissegundos é usado por essa constante por padrão.
Isso conclui as melhorias dos arquivos da biblioteca relacionados à remoção do horário em segundos.

Implementação de eventos de encerramento de posição para a MQL4

Diversas experiências e testes realizados durante a busca de possíveis opções de identificação da ocorrência de eventos de encerramento de posição e remoção de ordens em MQL4 acabou se tornando desencorajador. Diferentemente da MQL5, a MQL4 apresenta muito menos dados que podem ser usados para a identificação decisiva de eventos.
Em MQL5, nós podemos facilmente usar os dados de ordens pertencentes a uma determinada posição e definir um evento, enquanto na MQL4, uma posição e uma ordem pendente são consideradas ordens. Se nós tivéssemos uma ordem pendente na MetaTrader 4, após a remoção, nós enfrentaríamos:

Para garantir que um evento pertence à remoção de uma ordem pendente (a posição em MQL4 também é uma ordem), nós verificamos o número de posições em aberto. Se ela não for alterada, a ação é executada com uma ordem pendente. Tudo parece estar normal e lógico aqui até encerrarmos qualquer uma das posições em aberto (uma ordem) na MetaTrader 4 parcialmente. Ao encerrar parcialmente uma posição, nós temos algo semelhante ao remover uma ordem pendente:

Este é o mesmo estado ao remover uma ordem pendente. Nós podemos observar isso ao iniciar um EA de teste do artigo anterior no testador. Abrimos uma posição, encerramos parcialmente ela, definimos uma ordem pendente e removemos ela. Durante a última ação, duas entradas aparecem no diário simultaneamente: encerramento parcial da posição e remoção da ordem pendente. Eu já mencionei a conformidade dos critérios para definir esses dois eventos acima. O programa simplesmente define os dois eventos ao mesmo tempo e um deles está incorreto.
Aqui, nós podemos usar a verificação do número alterado de ordens pendentes — ao definir o encerramento parcial da posição, nós também verificaremos a alteração do número de ordens pendentes de mercado. Se ele não foi alterado, o evento é o encerramento parcial da posição.

Tudo parece lógico, mas essa abordagem impõe restrições à ordem que permite as operações de negociação na MetaTrader 4. Em outras palavras, nós não podemos remover uma ordem pendente e encerrar parcialmente uma posição em um único loop, pois isso viola a lógica da definição de evento mencionada acima. Nós podemos implementar uma solução para contornar essa limitação, mas é necessário refazer as classes de coleções do histórico de ordens e posições. Para definir uma mudança no ambiente de mercado, nós precisamos usar as listas de ordens e posições temporárias que foram alteradas na conta, em vez de gerenciar o número de mudanças ocorridas. Os eventos devem ser tratados de acordo com os dados dessas listas. Nesse caso, cada tipo de evento possui sua própria lista e a criação de eventos para enviá-los ao programa deve ser realizada de acordo com as listas de ordens e posições alteradas.

Talvez, após a conclusão desta série de artigos, eu vou organizar essa busca e manipulação de eventos. Mas, por enquanto, vamos usar o controle do número de ordens pendentes de mercado. Lembre-se da limitação da MetaTrader 4 e desenvolva as funções de encerramento/remoção das ordens, levando isso em consideração. Para usuários finais, essa limitação permanece oculta. Eles não devem gerenciá-lo, já que as funções para trabalhar com a biblioteca, que consideram tais limitações para a MQL4 devem ser introduzidas.

Para definir um encerramento parcial, nós precisamos gerenciar o volume total da conta. Isso significa que nós precisamos passar o seu valor de alteração para o método Refresh() da classe CEventsCollection. Como sempre, tudo começa no objeto base da biblioteca. Vamos fazer a adição necessária à chamada do método para atualizar a classe de coleção de eventos.

Adicionamos um parâmetro transferível adicional para o método Refresh da classe CEventsCollection no método de classe CEngine::TradeEventsControl():

//+------------------------------------------------------------------+
//| Check trading events                                             |
//+------------------------------------------------------------------+
void CEngine::TradeEventsControl(void)
  {
//--- Initialize the trading events code and flags
   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_market.GetListControl(),
                            this.m_is_history_trade_event,this.m_is_market_trade_event,
                            this.m_history.NewOrders(),this.m_market.NewPendingOrders(),
                            this.m_market.NewPositions(),this.m_history.NewDeals(),
                            this.m_market.ChangedVolumeValue());
      //--- Get the account's last trading event
      this.m_acc_trade_event=this.m_events.GetLastTradeEvent();
     }
  }
//+------------------------------------------------------------------+

Agora nós precisamos alterar o método Refresh() da própria classe CEventsCollection, implementando ainda outro parâmetro (valor da alteração do volume).
Adicionamos o novo parâmetro para a definição do método Refresh() no arquivo EventsCollection.mqh:

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,
                             CArrayObj* list_control,
                             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,
                             const double changed_volume);
//--- 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);
  };

Nós precisamos fazer as adições na implementação do método Refresh():

//+------------------------------------------------------------------+
//| Update the event list                                            |
//+------------------------------------------------------------------+
void CEventsCollection::Refresh(CArrayObj* list_history,
                                CArrayObj* list_market,
                                CArrayObj* list_changes,
                                CArrayObj* list_control,
                                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,
                                const double changed_volume)
  {

Agora, nós precisamos criar o manipulador de eventos de encerramento de posição e encerramento parcial e resolver o problema da definição incorreta do evento de remoção da ordem pendente durante o encerramento de posição parcial.

Na seção da privada da classe, alteramos a primeira forma de chamar o novo método de criação de eventos pela adição da passagem da lista de ordens de controle para o método. Nós precisamos identificar as ordens que participam do encerramento de uma posição por uma oposta. Além disso, adicionamos o método que retorna a lista de posições do histórico (encerradas) e o método que retorna o ponteiro para a ordem de controle pelo ticket da posição:

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
   MqlTick           m_tick;                          // Last tick structure
   ulong             m_position_id;                   // Position ID (MQL4)
   ENUM_ORDER_TYPE   m_type_first;                    // Opening order type (MQL4)
   
//--- Create a trading event depending on the order (1) status and (2) change type
   void              CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market,CArrayObj* list_control);
   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 from the list and return the list of (1) market pending orders, (2) open positions
   CArrayObj*        GetListMarketPendings(CArrayObj* list);
   CArrayObj*        GetListPositions(CArrayObj* list);
//--- Select from the list and return the list of historical (1) closed orders,
//--- (2) removed pending orders, (3) deals, (4) all closing orders 
   CArrayObj*        GetListHistoryPositions(CArrayObj* list);
   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 position deals 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 (1) control order by ticket, (2) opening order type by position ticket (MQL4)
   COrderControl*    GetOrderControlByTicket(CArrayObj* list,const ulong ticket);
   ENUM_ORDER_TYPE   GetTypeFirst(CArrayObj* list,const ulong ticket);
//--- Return the flag of the event object presence in the event list
   bool              IsPresentEventInList(CEvent* compared_event);
//--- Existing order/position change event handler
   void              OnChangeEvent(CArrayObj* list_changes,const int index);

public:

Implementamos o método que retorna a lista de posições encerradas além do corpo da classe:

//+------------------------------------------------------------------+
//| Select only closed positions from the list                       |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListHistoryPositions(CArrayObj *list)
  {
   if(list.Type()!=COLLECTION_HISTORY_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of the history collection"));
      return NULL;
     }
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_ORDER,EQUAL);
   return list_orders;
  }
//+------------------------------------------------------------------+

O método não contém nada de novo para nós. Eu já descrevi os métodos semelhantes nas partes anteriores da descrição da biblioteca.

Vamos implementar o método que retorna uma ordem de controle por um ticket e mudar o método que retorna o tipo da ordem de controle pelo ticket:

//+------------------------------------------------------------------+
//| Return a control order by a position ticket (MQL4)               |
//+------------------------------------------------------------------+
COrderControl* CEventsCollection::GetOrderControlByTicket(CArrayObj *list,const ulong ticket)
  {
   if(list==NULL)
      return NULL;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      COrderControl* ctrl=list.At(i);
      if(ctrl==NULL)
         continue;
      if(ctrl.Ticket()==ticket)
         return ctrl;
     }
   return NULL;
  }
//+------------------------------------------------------------------+
//| Return an opening order type by a position ticket (MQL4)         |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE CEventsCollection::GetTypeFirst(CArrayObj* list,const ulong ticket)
  {
   if(list==NULL)
      return WRONG_VALUE;
   COrderControl* ctrl=this.GetOrderControlByTicket(list,ticket);
   if(ctrl==NULL)        
      return WRONG_VALUE;
   return (ENUM_ORDER_TYPE)ctrl.TypeOrder();
  }
//+------------------------------------------------------------------+

Como os métodos semelhantes considerados anteriormente, o método que retorna a ordem de controle pelo ticket é simples. Ela recebe a lista de ordens de controle e o ticket da posição cuja ordem de controle é o que queremos obter.
Pegamos a ordem da lista e comparamos ela com o ticket passado para o método em um loop do tamanho da lista. Se os tickets forem iguais, retornamos o ponteiro para a ordem de controle, caso contrário, retornamos NULL.

O método GetTypeFirst() que retorna o tipo de ordem de controle anteriormente consistia em um loop pela lista de ordens de controle com a busca de uma ordem com o ticket igual a aquele passado ao método. Quando essa ordem foi detectada, foi retornado o seu tipo.
Agora que nós temos o método que retorna a ordem de controle pelo ticket da posição, nós podemos excluir o loop de busca do método GetTypeFirst(). Isto é exatamente o que eu fiz. Agora no método, nós obtemos uma ordem de controle pelo ticket da posição usando o método GetOrderControlByTicket(). Se for obtido com sucesso (não NULL), retornamos o tipo da ordem obtida, caso contrário é retornado -1.

Agora nós podemos adicionar o tratamento do encerramento da posição para MQL4 ao método de atualização da coleção de eventos:

//+------------------------------------------------------------------+
//| Update the event list                                            |
//+------------------------------------------------------------------+
void CEventsCollection::Refresh(CArrayObj* list_history,
                                CArrayObj* list_market,
                                CArrayObj* list_changes,
                                CArrayObj* list_control,
                                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,
                                const double changed_volume)
  {
//--- 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 (MQL5, MQL4)
      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);
            //--- 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,list_control);
              }
           }
        }
      #ifdef __MQL4__
         //--- If the number of positions increased (MQL4)
         if(new_market_positions>0)
           {
            //--- Get the list of open positions
            CArrayObj* list=this.GetListPositions(list_market);
            if(list!=NULL)
              {
               //--- Sort the new list by a position open time
               list.Sort(SORT_BY_ORDER_TIME_OPEN);
               //--- Take the number of positions equal to the number of newly placed open positions from the end of the list in a loop (the last N events)
               int total=list.Total(), n=new_market_positions;
               for(int i=total-1; i>=0 && n>0; i--,n--)
                 {
                  //--- Receive a position from the list. If this is a position, search for opening order data and set a trading event
                  COrder* position=list.At(i);
                  if(position!=NULL && position.Status()==ORDER_STATUS_MARKET_POSITION)
                    {
                     //--- Find an order and set (1) a type of an order that led to opening a position and a (2) position ID 
                     this.m_type_first=this.GetTypeFirst(list_control,position.Ticket());
                     this.m_position_id=position.Ticket();
                     this.CreateNewEvent(position,list_history,list_market,list_control);
                    }
                 }
              }
           }
         //--- If the number of positions decreased or a position is closed partially (MQL4)
         else if(new_market_positions<0 || (new_market_positions==0 && changed_volume<0 && new_history_orders>0 && new_market_pendings>WRONG_VALUE))
           {
            //--- Get the list of closed positions
            CArrayObj* list=this.GetListHistoryPositions(list_history);
            if(list!=NULL)
              {
               //--- Sort the new list by position close time
               list.Sort(SORT_BY_ORDER_TIME_CLOSE);
               //--- Take the number of positions equal to the number of newly closed positions 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 position, look for data of an opening order and set a trading event
                  COrder* position=list.At(i);
                  if(position!=NULL && position.Status()==ORDER_STATUS_HISTORY_ORDER)
                    {
                     //--- If there is a control order of a closed position
                     COrderControl* ctrl=this.GetOrderControlByTicket(list_control,position.Ticket());
                     if(ctrl!=NULL)
                       {
                        //--- Set an (1) order type that led to a position opening, (2) position ID and create a position closure event
                        this.m_type_first=(ENUM_ORDER_TYPE)ctrl.TypeOrder();
                        this.m_position_id=position.Ticket();
                        this.CreateNewEvent(position,list_history,list_market,list_control);
                       }
                    }
                 }
              }
           }
      #endif 
     }
//--- If an event in an account history
   if(is_history_event)
     {
      //--- If the number of historical orders increased (MQL5, MQL4)
      if(new_history_orders>0)
        {
         //--- Get the list of newly removed pending orders
         CArrayObj* list=this.GetListHistoryPendings(list_history);
         if(list!=NULL)
           {
            //--- Sort the new list by order removal time
            list.Sort(SORT_BY_ORDER_TIME_CLOSE);
            //--- 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,list_control);
              }
           }
        }
      //--- If the number of deals increased (MQL5)
      #ifdef __MQL5__
         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);
               //--- 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);
                 }
              }
           }
      #endif 
     }
  }
//+------------------------------------------------------------------+

O tratamento do encerramento de posição é bastante claro e transparente. Sua descrição é comentada na lista e não há sentido em focar na sua lógica, pois os comentários do código são detalhados o suficiente.

Como agora mais uma lista é passada para a primeira forma de chamada do método para a criação de um novo evento, adicionamos a passagem da lista de ordens de controle à chamada do método para a MQL5 no método Refresh() da classe de coleção de eventos CEventsCollection:

      //--- If the number of deals increased (MQL5)
      #ifdef __MQL5__
         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);
               //--- 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,list_control);
                 }
              }
           }
      #endif 

Agora vamos adicionar o código da criação de um evento de encerramento de posição à primeira forma de uma chamada de evento no método de criação de eventos:

CEventsCollection::CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market,CArrayObj* list_control).

Como o método é bem extenso, nós veremos apenas o código para a criação de um evento de encerramento de posição para a MQL4:

//--- Position closed (__MQL4__)
   if(status==ORDER_STATUS_HISTORY_ORDER)
     {
      //--- Set the "position closed" trading event code
      this.m_trade_event_code=TRADE_EVENT_FLAG_POSITION_CLOSED;
      //--- Set the "request executed in full" reason
      ENUM_EVENT_REASON reason=EVENT_REASON_DONE;
      //--- If the closure by StopLoss flag is set for an order, a position is closed by StopLoss
      if(order.IsCloseByStopLoss())
        {
         //--- set the "closure by StopLoss" reason
         reason=EVENT_REASON_DONE_SL;
         //--- add the StopLoss closure flag to the event code
         this.m_trade_event_code+=TRADE_EVENT_FLAG_SL;
        }
      //--- If the closure by TakeProfit flag is set for an order, a position is closed by TakeProfit
      if(order.IsCloseByTakeProfit())
        {
         //--- set the "closure by TakeProfit" reason
         reason=EVENT_REASON_DONE_TP;
         //--- add the TakeProfit closure flag to the event code
         this.m_trade_event_code+=TRADE_EVENT_FLAG_TP;
        }
      //--- If an order has the property with an inherited order filled, a position is closed partially
      if(order.TicketTo()>0)
        {
         //--- set the "partial closure" reason
         reason=EVENT_REASON_DONE_PARTIALLY;
         //--- add the partial closure flag to the event code
         this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
        }
      //--- Check closure by an opposite position
      COrder* order_close_by=this.GetCloseByOrderFromList(list_history,order.Ticket());
      //--- Declare the variables of the opposite order properties and initialize them with the values of the current closed position 
      ENUM_ORDER_TYPE close_by_type=this.m_type_first;
      double close_by_volume=order.Volume();
      ulong  close_by_ticket=order.Ticket();
      long   close_by_magic=order.Magic();
      string close_by_symbol=order.Symbol();
      //--- If the list of historical orders features an order with a closed position ID, the position is closed by an opposite one
      if(order_close_by!=NULL)
        {
         //--- Fill in the properties of an opposite closing order using data on the opposite position properties
         close_by_type=(ENUM_ORDER_TYPE)order_close_by.TypeOrder();
         close_by_ticket=order_close_by.Ticket();
         close_by_magic=order_close_by.Magic();
         close_by_symbol=order_close_by.Symbol();
         close_by_volume=order_close_by.Volume();
         //--- set the "close by" reason
         reason=EVENT_REASON_DONE_BY_POS;
         //--- add the close by flag to the event code
         this.m_trade_event_code+=TRADE_EVENT_FLAG_BY_POS;
         //--- Take data on (1) closed and (2) opposite positions from the list of control orders
         //--- (in this list, the properties of two opposite positions remain the same as before the closure)
         COrderControl* ctrl_closed=this.GetOrderControlByTicket(list_control,order.Ticket());
         COrderControl* ctrl_close_by=this.GetOrderControlByTicket(list_control,close_by_ticket);
         double vol_closed=0;
         double vol_close_by=0;
         //--- If no errors detected when receiving these two opposite orders
         if(ctrl_closed!=NULL && ctrl_close_by!=NULL)
           {
            //--- Calculate closed volumes of a (1) closed and (2) an opposite positions
            vol_closed=ctrl_closed.Volume()-order.Volume();
            vol_close_by=vol_closed-close_by_volume;
            //--- If a position is closed partially (the previous volume exceeds the currently closed one)
            if(ctrl_closed.Volume()>order.Volume())
              {
               //--- add the partial closure flag to an event code
               this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
               //--- set the "partial closure" reason
               reason=EVENT_REASON_DONE_PARTIALLY_BY_POS;
              }
           }
        }
      //--- Create the position closure event
      CEvent* event=new CEventPositionClose(this.m_trade_event_code,order.Ticket());
      if(event!=NULL && order.PositionByID()==0)
        {
         event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeClose());                               // Event time
         event.SetProperty(EVENT_PROP_REASON_EVENT,reason);                                        // Event reason (from the ENUM_EVENT_REASON enumeration)
         event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,close_by_type);                              // Event deal type
         event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());                           // Event deal ticket
         event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,close_by_type);                             // Type of the order that triggered an event deal (the last position order)
         event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,this.m_type_first);                      // Type of an order that triggered a position deal (the first position order)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,close_by_ticket);                         // Ticket of an order, based on which an event deal is opened (the last position order)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());                       // Ticket of an order, based on which a position deal is opened (the first position order)
         event.SetProperty(EVENT_PROP_POSITION_ID,this.m_position_id);                             // Position ID
         event.SetProperty(EVENT_PROP_POSITION_BY_ID,close_by_ticket);                             // Opposite position ID
         event.SetProperty(EVENT_PROP_MAGIC_BY_ID,close_by_magic);                                 // Opposite position magic number
            
         event.SetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE,order.TypeOrder());                      // Position order type before direction changed
         event.SetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE,order.Ticket());                       // Position order ticket before direction changed
         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_PRICE_OPEN_BEFORE,order.PriceOpen());                        // Order price before modification<
         event.SetProperty(EVENT_PROP_PRICE_SL_BEFORE,order.StopLoss());                           // StopLoss before modification
         event.SetProperty(EVENT_PROP_PRICE_TP_BEFORE,order.TakeProfit());                         // TakeProfit before modification
         event.SetProperty(EVENT_PROP_PRICE_EVENT_ASK,this.m_tick.ask);                            // Ask price during an event
         event.SetProperty(EVENT_PROP_PRICE_EVENT_BID,this.m_tick.bid);                            // Bid price during an event
         
         event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                                  // Order/deal/position magic number
         event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpen());                       // Time of an order, based on which a position deal is opened (the first position order)
         event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());                              // Event price
         event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());                               // Order/deal/position open price
         event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());                             // Order/deal/position close price
         event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());                                  // StopLoss position price
         event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());                                // TakeProfit position price
         event.SetProperty(EVENT_PROP_VOLUME_ORDER_INITIAL,order.Volume());                        // Requested order volume
         event.SetProperty(EVENT_PROP_VOLUME_ORDER_EXECUTED,order.Volume()-order.VolumeCurrent()); // Executed order volume
         event.SetProperty(EVENT_PROP_VOLUME_ORDER_CURRENT,order.VolumeCurrent());                 // Remaining (unexecuted) order volume
         event.SetProperty(EVENT_PROP_VOLUME_POSITION_EXECUTED,order.Volume());                    // Executed position volume
         event.SetProperty(EVENT_PROP_PROFIT,order.Profit());                                      // Profit
         event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                                      // Order symbol
         event.SetProperty(EVENT_PROP_SYMBOL_BY_ID,close_by_symbol);                               // Opposite position symbol
         //--- Set 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 is already in the list."));
            delete event;
           }
        }
     }
#endif 

Como nós já descrevemos a lógica da criação de eventos nos artigos anteriores, vamos mencionar brevemente: primeiro, definimos o código de evento como "encerramento de posição" e o motivo do evento como "solicitação executada por completo". Em seguida, visualizaremos as várias propriedades de uma ordem encerrada, adicionamos as flags necessárias ao código do evento com base nessas propriedades e alteramos o motivo do evento, se necessário. Ao definir o encerramento de posição oposto, nós usamos os dados de ordens de controle nas posições encerradas. Esses dados nos fornecem todas as informações sobre as ordens opostas antes do encerramento conjunto, permitindo definir os tipos de ordens e seu volume, além de descobrir qual deles iniciou o encerramento (close by).
Todos os dados coletados são registrados nas propriedades do evento e um novo evento de encerramento de posição é criado.

Eu também venho melhorando o método que retorna a lista de todas as ordens de encerramento da posição para a MQL4:

//+------------------------------------------------------------------+
//|  Return the list of all closing CloseBy orders from the list     |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListCloseByOrders(CArrayObj *list)
  {
   if(list.Type()!=COLLECTION_HISTORY_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of history collection"));
      return NULL;
     }
#ifdef __MQL5__
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,ORDER_TYPE_CLOSE_BY,EQUAL);
#else 
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_BY_ID,0,NO_EQUAL);
#endif 
   return list_orders;
  }
//+------------------------------------------------------------------+

Para a MQL5, nós selecionamos apenas as ordens do tipo ORDER_TYPE_CLOSE_BY na lista de ordens do histórico. Como não existem tais ordens em MQL4, nós selecionamos apenas as ordens com o ID da posição oposta da propriedade preenchida, mais especificamente, as ordens que possuem essa propriedade não são iguais a zero.

Foi aprimorado também o método que retorna a última ordem de encerramento de posição para a MQL4:

//+------------------------------------------------------------------+
//| Return the last closing order                                    |
//| from the list of all position orders                             |
//+------------------------------------------------------------------+
COrder* CEventsCollection::GetCloseByOrderFromList(CArrayObj *list,const ulong position_id)
  {
#ifdef __MQL5__
   CArrayObj* list_orders=this.GetListAllOrdersByPosID(list,position_id);
   list_orders=CSelect::ByOrderProperty(list_orders,ORDER_PROP_TYPE,ORDER_TYPE_CLOSE_BY,EQUAL);
   if(list_orders==NULL || list_orders.Total()==0) return NULL;
   list_orders.Sort(SORT_BY_ORDER_TIME_OPEN);
#else 
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_BY_ID,position_id,EQUAL);
   if(list_orders==NULL || list_orders.Total()==0) return NULL;
   list_orders.Sort(SORT_BY_ORDER_TIME_CLOSE);
#endif 
   COrder* order=list_orders.At(list_orders.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

Em MQL5, nós podemos obter a última ordem pertencente a uma posição a qualquer momento. No entanto, este não é o caso da MQL4. Portanto, no caso da MQL4, eu decidi retornar uma ordem de posição oposta se presente ou NULL se uma posição não foi encerrada por uma posição oposta. Isso nos permite implementar parcialmente a obtenção de uma ordem de encerramento quando uma posição é encerrada por uma oposta.

Essas são todas as alterações e melhorias necessárias para definir o encerramento da posição para a MQL4.

Encontre a lista completa de todas as classes nos arquivos anexados abaixo.

Teste

Para realizar o teste, nós vamos usar o EA de teste do artigo anterior TestDoEasyPart10.mq4 localizado na \MQL4\Experts\TestDoEasy\Part10 e salvamos ele na nova pasta \MQL4\Experts\TestDoEasy\Part11 sob o nome TestDoEasyPart11.mq4.

Como nós removemos as constantes de horário em milissegundos da enumeração ENUM_SORT_ORDERS_MODE de possíveis critérios de ordenação de ordens e negócios, nós precisamos substituir a ordenação colocando o horário em que uma constante removida é definida

list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);

com a ordenação pelo horário no manipulador do pressionamento do botão de remoção da ordem pendente do EA :

      //--- If the BUTT_DELETE_PENDING button is pressed: Remove the first pending order
      else if(button==EnumToString(BUTT_DELETE_PENDING))
        {
         //--- Get the list of all orders
         CArrayObj* list=engine.GetListMarketPendings();
         if(list!=NULL)
           {
            //--- Sort the list by placement time
            list.Sort(SORT_BY_ORDER_TIME_OPEN);
            int total=list.Total();
            //--- In the loop from the position with the most amount of time
            for(int i=total-1;i>=0;i--)
              {
               COrder* order=list.At(i);
               if(order==NULL)
                  continue;
               //--- delete the order by its ticket
               #ifdef __MQL5__
                  trade.OrderDelete(order.Ticket());
               #else 
                  PendingOrderDelete(order.Ticket());
               #endif 
              }
           }
        }

Compilamos o EA e definimos o StopLoss em pontos e TakeProfit em pontos no parâmetro de entrada do testador para zero para que as posições sejam fechadas sem as ordens de stop. Em seguida, iniciamos o EA no testador, abrimos uma posição e encerramos parcialmente.
Em seguida coloque e remova a ordem pendente:


Agora, os eventos de encerramento parcial e remoção de ordem pendente são definidos como eventos separados.

Inicie o EA novamente e clique nos botões observando as definição de eventos:


Como nós podemos ver, os eventos estão definidos corretamente. Um evento de encerramento é definido, modificações nos níveis de stop e preços de ordens pendentes também são rastreados.

Qual é o próximo?

Neste artigo, nós concluímos a conversão das funcionalidades da biblioteca existente para compatibilidade com a MQL4. Nos próximos artigos, nós criaremos os novos objetos na "conta" e "símbolo" da sua coleção e eventos.

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.
Parte 7. Eventos de ativação da ordem StopLimit, preparação da funcionalidade para os eventos de modificação de ordens e posições.
Parte 8. Eventos de modificação de ordens e posições.
Parte 9. Compatibilidade com a MQL4 - Preparação dos dados.
Parte 10. Compatibilidade com a MQL4 - Eventos de abertura de posição e ativação de stops pendentes.