Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XI) Compatibilidad con MQL4 - Eventos de cierre de posición

Artyom Trishkin | 22 agosto, 2019

Contenido


Eliminando lo sobrante

Al trabajar con la definición de eventos, encontramos que, para MQL5, en todos los lugares donde se debe realizar la selección por tiempo, lo hacemos usando la hora en milisegundos. En MQL4, las órdenes y posiciones no tienen tales propiedades, pero nada nos impide usar una hora en segundos expresada en milisegundos para MQL4. De esta forma, llegamos a la conclusión de que cualquier hora en segundos se ve expresada dos veces con la hora en milisegundos, que además no se utiliza en ninguna parte. Y, al obtener y representar la hora (cuando la necesitamos en segundos), su obtención en milisegundos es idéntica, con la excepción del "pico" de tres cifras que indican el número de milisegundos en el formato de hora mostrado.

Por eso, hemos decidido quitar de las propiedades de la orden todas las propiedades de la hora expresada en segundos, si la orden tiene la misma propiedad, pero expresada en milisegundos.
Y ya que hemos decidido quitar algo, deberíamos también añadir algo, por ello, vamos a añadir a cada orden la nueva propiedad "comentario de usuario". Será posible establecer esta para cualquier orden o posición, ya sea una orden abierta, o una orden cerrada/eliminada. En cualquier momento. ¿Para qué necesitamos esto? Pues aunque sea para las etiquetas de texto de las órdenes que cumplan con ciertas condiciones. Por ejemplo, resultará cómodo a la hora de implementar la representación visual (posteriormente, dispondrá de su propio envoltorio gráfico en la biblioteca) y representar las órdenes marcadas con etiquetas de texto con la ayuda de diversas construcciones gráficas.

Abrimos el archivo Defines.mqh. Usando Ctrl+F, encontramos todas las órdenes donde existe la hora expresada en segundos, y además existe existe exactamente la misma propiedad expresada en milisegundos, pero con la terminación "_MSC" (esta propiedad está expresada en milisegundos) y eliminamos las propiedades en milisegundos de la orden , dejando las propiedades en segundos, y cambiando el número de propiedades de tipo entero de 24 a 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
//+------------------------------------------------------------------+

Después de realizar los cambios, la lista de propiedades de tipo entero tendrá el aspecto que sigue:

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

Encontramos la enumeración con las posibles variantes de selección por hora y eliminamos las constantes de selección en milisegundos:

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

Esta enumeración se compondrá solo de dos 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)
  };
//+------------------------------------------------------------------+

Ahora, al indicar la selección por hora, esta se realizará según la hora en milisegundos para MQL5 y en segundos para MQL4.

En las propiedades de tipo string de la orden, añadimos la propiedad "comentario de usuario" y aumentamos el número total de propiedades de tipo string a 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
//+------------------------------------------------------------------+

En la enumeración de los posibles criterios de clasificación, eliminamos cualquier mención referente a los milisegundos, en nuestro caso, estos se usan ahora por defecto al realizar la clasificación por hora. A continuación, añadimos el criterio de clasificación según el comentario de usuario:

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

Ya hemos realizado todos los cambios en Defines.mqh. Ahora, necesitamos quitar todos los enlaces a las propiedades eliminadas de la orden en los archivos de la biblioteca:

en todos los archivos de la biblioteca, todas las entradas de los modos de clasificación

SORT_BY_ORDER_TIME_OPEN_MSC

y

SORT_BY_ORDER_TIME_CLOSE_MSC

los cambiamos por

SORT_BY_ORDER_TIME_OPEN

y

SORT_BY_ORDER_TIME_CLOSE

En los archivos de las clases herederas de orden abstracta HistoryDeal.mqh, HistoryOrder.mqh, HistoryPending.mqh, MarketOrder.mqh, MarketPending.mqh y MarketPosition.mqh, tenemos que eliminar cualquier mención referente a las propiedades en milisegundos de las órdenes (ya que ahora son en milisengundos por defecto):

ORDER_PROP_TIME_CLOSE_MSC

y
ORDER_PROP_TIME_UPDATE_MSC

En el archivo Order.mqh de la clase de orden abstracta COrder, eliminamos de la sección privada los métodos que retornan la hora en segundos:

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

Eliminamos de la sección pública los métodos de acceso simplificado que retornan la hora en milisegundos, esta será devuelta por los métodos que retornan la hora en 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, 

Asimismo, añadimos los métodos para retornar y establecer el comentario de usuario de la orden:

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

En el constructor cerrado de la clase COrder, eliminamos el guardado de propiedades de la hora en segundos, y sustituimos las propiedades en milisegundos por las propiedades en segundos, donde guardaremos la hora en milisegundos. Añadimos el guardado del comentario de usuario en forma de línea vacía:

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

En el método que retorna el identificador de la posición para MQL4, hacemos los siguiente: si se trata de una posición de mercado, retornamos el ticket, de lo contrario, cero. Y es que el identificador de la posición en MQL5 es el ticket de la orden que abre la posición, y este no cambia durante toda la vida útil de la posición.

En MQL4, solo el ticket de la posición puede servir de esa forma como identificador de la posición. Y las órdenes pendientes en MQL4 no tienen semejante identificador. Si la orden ha sido eliminada, esto significa que no se ha abierto posición alguna según esta orden, y si esta orden ha sido activada, esta orden en MQL4 no se encontrará en la historia de órdenes, si bien la posición recibe su ticket, por consiguiente, este ticket será también el identificador de la posición.
//+------------------------------------------------------------------+
//| 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 a completar el método que retorna el identificador de la posición opuesta para MQL4:

//+------------------------------------------------------------------+
//| Return the opposite position ID                                  |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

Aquí: si se trata de MQL4 y si en el comentario de la orden existe el comentario "close hedge by #", calculamos en la línea del comentario el índice del inicio del número del ticket de la orden opuesta y lo asignamos al valor retornado por este método.

Eliminamos del listado de la clase la implementación de dos métodos que ya no necesitamos, puesto que hemos renunciado a obtener la hora en 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 mostrar la descripción del estado de la orden en MQL4 de una forma más racional, vamos a añadir algunas correcciones al método que retorna la descripción del estado de la orden:

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

Aquí, vamos a retornar para las órdenes pendientes eliminadas y las posiciones cerradas en MQL4 la descripción del estado como "Orden histórica".

En el método que retorna la descripción de la propiedad de tipo entero, realizamos correcciones en las líneas con la descripción de las propiedades ORDER_PROP_TIME_OPEN, ORDER_PROP_TIME_CLOSE y ORDER_PROP_TIME_UPDATE, de forma que para estas propiedades se retornen las propiedades en milisegundos:

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

y añadimos al método que retorna la descripción de la propiedad de tipo string el retorno de la descripción del comentario de usuario:

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

Ya hemos realizado todos los cambios necesarios en la clase de orden abstracta Defines.mqh.

Vamos a introducir en el archivo de funciones de servicio DELib.mqh algunas mejoras en las funciones que retornan la denominación de una orden/posición según el tipo de orden:

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

Aquí, hemos añadido la bandera que controla la muestra de las órdenes para MQL4, o bien como orden, o bien como posición. Por defecto, encontraremos la muestra para MQL4 "como orden". ¿Por qué se ha hecho esto? Por ejemplo, al mostrar en el diario un evento de apertura de posición, se mostrará entre corchetes la orden sobre cuya base se ha abierto la posición. De esta forma, no se muestra [Position Sell #123] para una posición Sell abierta por una orden de mercado (no una orden pendiente) con el ticket 123 en calidad de orden que ha provocado la apertura de la posición, sino que se escribe una entrada más comprensible [Market order Sell #123].

Introducimos la corrección en el método AddToListMarket() de la clase de colección de órdenes y posiciones de mercado. En lugar de la hora de actualización de la posición en milisegundos ORDER_PROP_TIME_UPDATE_MSC, ahora usamos la hora de actualización de la posición ORDER_PROP_TIME_UPDATE (que se encuentra por defecto en milisegundos):

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

En el método para crear las órdenes de control y añadir estas a la lista, sustituimos la hora en milisegundos por la hora de la orden (por el mismo 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;
  }
//+------------------------------------------------------------------+

En el archivo HistoryCollection.mqh del método de selección de órdenes según la hora de la clase de colección de órdenes y transacciones históricas CHistoryCollection, introducimos la corrección en la selección de la propiedad comparada.
Dado que antes teníamos la selección de cuatro propiedades (hora de apertura en milisegundos, hora de cierre en milisegundos, hora de apertura en segundos y hora de cierre en segundos), y ahora hemos quitado dos propiedades, también la selección resulta más sencilla:

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

Sustituimos en el archivo CEngine.mqh todas las entradas de la constante de hora en milisegundos SORT_BY_ORDER_TIME_OPEN_MSC por la constante de hora SORT_BY_ORDER_TIME_OPEN: ahora, de acuerdo con esta constante, se usa por defecto la hora en milisegundos.
Con esto, hemos finalizado la corrección de los archivos de la biblioteca en cuanto a la eliminación de la hora en segundos.

Implementando los eventos de cierre de posición para MQL4

Los distintos experimentos y simulaciones realizados durante la búsqueda de posibles opciones de identificación de la aparición de eventos de cierre de posiciones y eliminación de órdenes en MQL4 nos hacen llegar a conclusiones pocos prometedoras: a diferencia de MQL5, en MQL4 hay bastante menos información que se pueda usar para identificar un evento de forma unívoca.
Mientras que en MQL5 podemos operar fácilmente con la información sobre las órdenes pertenecientes a una posición concreta, y determinar un evento según las propiedades de esta, en MQL4 no disponemos de semejante opción, pues en él, tanto una posición como una orden pendiente se consideran una orden. Y si tenemos una orden pendiente colocada en MetaTrader 4, y la hemos eliminado, después de hacerlo, veremos que:

Para determinar que este evento pertenece a la eliminación de una orden pendiente (ya que una posición en MQL4 también es una orden), comprobamos el número de posiciones abiertas: si no ha cambiado, significará que la acción se ha realizado con una orden pendiente. Todo esto parece lógico y normal, hasta que cerramos parcialmente cualquiera de las posiciones (órdenes) abiertas en MetaTrader 4. ¿Qué sucede al darse un evento de cierre parcial de una posición? Pues lo mismo que al cerrar una orden pendiente:

Y este estado es igual al de la eliminación de una orden pendiente. Podremos observar esta falta si iniciamos en el simulador de estrategias el asesor de prueba del artículo anterior, abrimos una posición, la cerramos parcialmente, colocamos una orden pendiente y luego la eliminamos. Al realizar la última acción, es decir, eliminar la orden pendiente, en el diario se mostrarán dos eventos de golpe: el cierre parcial de la posición y la eliminación de una orden pendiente. Ya hemos analizado más arriba la "igualdad" de los criterios de definición de estos dos eventos; el programa simplemente define dos eventos al mismo tiempo, y uno de ellos es incorrecto.
En este caso, nos servirá de ayuda comprobar si ha cambiado el número de órdenes pendientes: al determinar el cierre parcial de una posición, también comprobaremos si ha cambiado el número de órdenes de mercado pendientes. Si su número no ha cambiado, este evento supondrá el cierre parcial de una posición.

Todo parece lógico, pero este enfoque impone ciertas limitaciones a la secuencia permitida a la hora de realizar operaciones comericiales en MetaTrader 4. Es decir, en un mismo ciclo, no podemos eliminar una orden pendiente y cerrar parcialmente una posición, ya que esto perjudicará la lógica de definición de eventos anteriormente mencionada. En principio, existe una solución para evitar esta limitación, pero esto nos obligarán a modificar las clases de las colecciones histórica y de mercado de las órdenes y posiciones: para determinar un cambio en el entorno comercial, no deberemos controlar el número de cambios sucedidos, sino usar las listas temporales de órdenes y posiciones que se han manipulado en la cuenta. Además, deberemos procesar los eventos según lo datos de estas listas: entonces, cada tipo de evento tendrá su propia lista, y la creación de eventos para su posterior envío al programa se realizará según las listas de órdenes y posiciones modificadas.

Es posible que organicemos un tipo de búsqueda y procesamiento de eventos semejante al finalizar el presente ciclo de artículos. Por el momento, vamos a usar el control del número de órdenes de mercado pendientes. Mencionaremos esta limitación para MetaTrader 4 más adelante, creando posteriormente funciones para el cierre/eliminación de órdenes teniendo en cuenta esta limitación, todo a su debido tiempo. Por cierto: para el usuario final, esta limitación sigue quedando oculta, no deberá poder controlarla. En cualquier caso, ofreceremos funciones para trabajar con la biblioteca en las que se tendrá en cuenta esta limitación para MQL4.

Para determinar el cierre parcial, vamos a tener que controlar el volumen total en la cuenta, lo cual significa que necesitaremos transmitir la magnitud del cambio de la misma al método Refresh() de la clase CEventsCollection. Como siempre, todo comienza con el objeto básico de la biblioteca. Y esto significa que vamos a realizar las adiciones necesarias a la llamada del método de actualización de la clase de colección de eventos.

Introducimos el parámetro adicional transmisible al método Refresh de la clase CEventsCollection, en el método de la clase 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();
     }
  }
//+------------------------------------------------------------------+

Ahora, debemos modificar el propio método Refresh() de la clase CEventsCollection, introduciendo otro parámetro en los suyos: la magnitud del cambio de volumen.
En el archivo EventsCollection.mqh, añadimos el nuevo parámetro a la definición del método Refresh():

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

Y en la implementación del método Refresh(), también será necesario realizar las adiciones pertinentes:

//+------------------------------------------------------------------+
//| 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)
  {

Ahora, debemos crear un manejador para los eventos de cierre y cierre parcial de posiciones, así como resolver el problema de la definición incorrecta del evento de eliminación de una orden pendiente al cerrar parcialemte una posición.

Modificamos en la sección privada de la clase el primer formulario de llamada del método de creación de un nuevo evento, añadiéndole la transmisión de la lista de órdenes de control al método: necesitaremos la lista para identificar las órdenes que han participado en el cierre de una posición con otra opuesta. Asimismo, añadimos el método que retorna la lista de posiciones históricas (cerradas) y el método que retorna el puntero a la orden de control según el ticket de la posición:

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:

Vamos a implementar fuera del cuerpo de la clase el método que retorna la lista de posiciones abiertas.

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

No hay absolutamente nada nuevo para nosotros en el método: ya hemos analizado métodos idénticos en varias ocasiones en partes anteriores de la descripción de la biblioteca.

Vamos a implementar el método que retorna la orden de control según el ticket y a modificar el método que retorna el tipo de orden de control según el 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();
  }
//+------------------------------------------------------------------+

El método que retorna la orden de control según el ticket es sencillo, al igual que otros métodos idénticos que hemos analizado: transmitimos al método la lista de órdenes de control y el ticket de la posición cuya orden de control queremos obtener.
Tomamos una orden en el ciclo por el tamaño de la lista y la comparamos con la transmitida al método por el ticket. Si los tickets son iguales, retornamos el puntero a la orden de control, de lo contrario, retornamos NULL.

El método GetTypeFirst(), que retorna el tipo de la orden de control, antes se compoñía de un ciclo por la lista de órdenes de control, con búsqueda de la orden con un ticket igual al transmitido al método, y al encontrar esta orden, se retornaba su tipo.
Ahora, cuando tenemos el método que retorna la orden de control según el ticket de la posición, podemos eliminar del método GetTypeFirst() el ciclo de búsqueda. Eso precisamente hemos hecho: ahora, obtenemos en el método la orden de control según el ticket de la posición con la ayuda del método GetOrderControlByTicket(), y, en caso de obtenerla con éxito (no NULL), retornamos el tipo de la orden obtenida. De lo contrario, retornamos -1.

Ahora, podemos añadir al método de actualización de la colección de eventos el procesamiento del cierre de posiciones para MQL4:

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

El procesamiento del cierre de posiciones es totalmente comprensible y, puesto que hemos comentado su descripción en el listado, no vamos a perder tiempo analizando la lógica: los comentarios deberían aclarar cualquier duda.

Dado que ahora transmitimos una lista más al primer formulario de llamada del método de creación de un nuevo evento,
vamos a añadir la transmisión de la lista de las órdenes de control a la llamada del método para MQL5, en el método Refresh() de la clase de colección 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 

Ahora, vamos a añadir el código de creación del evento de cierre de posiciones al método de creación de eventos, en el primer formulario de su llamada:

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

Puesto que el método es bastante voluminoso, vamos a analizar solo el código de creación del evento de cierre de posición para 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 

Dado que ya hemos analizado la lógica de creación de eventos en artículos anteriores, nos limitaremos a dar una breve descripción de la lógica: primero establecemos el código de evento como "cierre de posición" y el motivo del evento como "solicitud ejecutada por completo". A continuación, analizamos las diferentes propiedades de la orden cerrada, y, basándonos en estas propiedades, añadimos al código de evento las banderas necesarias y modificamos el motivo del evento, si así se requiere. Al determinar el cierre con una posición opuesta, usamos los datos de las órdenes de control sobre las posiciones cerradas: a partir de estos datos, podemos obtener toda la información sobre las órdenes opuestas antes de su cierre conjunto, lo que nos permitirá determinar los tipos de las órdenes y su volumen, así como saber cuál de ellas ha provocado el cierre por orden opuesta.
A continuación, todos los datos reunidos se anotan en las propiedades del evento y se crea un nuevo evento de cierre de posición.

Hay más. Hemos mejorado el método que retorna para MQL4 la lista de todas las órdenes de cierre de la posición:

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

Aquí: mientras que para MQL5 seleccionamos de la lista de órdenes históricas solo las órdenes con el tipo ORDER_TYPE_CLOSE_BY, en MQL4, debido a su ausencia, seleccionamos de la lista solo aquellas órdenes en las que se ha rellenado la propiedad que guarda el identificador de la posición opuesta, en concreto, las órdenes en las que esta propiedad no es igual a cero.

De la misma forma, se ha mejorado el método que retorna para MQL4 la última orden de cierre de una posición:

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

Mientras que en MQL5 podemos obtener en cualquier momento la última orden perteneciente a una posición, en MQL4 no disponemos de tal posibilidad. Por eso, hemos decidido retornar para MQL4 la orden de la posición opuesta (si existe), o bien NULL, si la posición no ha sido cerrada por una opuesta. De esta manera, podremos implementar parcialmente la obtención de la orden de cierre, si la posición ha sido cerrada por una opuesta.

Estos son todos los cambios y mejoras necesarios para determinar el cierre de posiciones para MQL4.

Él lector podrá familiarizarse con el listado completo de todas las clases en los archivos adjuntos al final del artículo.

Simulación

Para la simulación, vamos a tomar el asesor de prueba del artículo anterior  TestDoEasyPart10.mq4 de la carpeta \MQL4\Experts\TestDoEasy\Part10 y guardarlo en la nueva carpeta \MQL4\Experts\TestDoEasy\Part11 con el nuevo nombre TestDoEasyPart11.mq4.

Debido a que se han eliminado de la enumeración de posibles criterios de clasificación de las órdenes y transacciones ENUM_SORT_ORDERS_MODE las constantes de la hora en milsegundos, vamos a necesitar corregir la clasificación por hora de colocación en el manejador de la pulsación del botón de eliminación de órdenes pendientes del asesor, en el lugar donde se encuentra la constante eliminada,  

list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);

por la clasificación por hora:

      //--- 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 el asesor, establecemos en el simulador los valores de los parámetros de entrada StopLoss in points y TakeProfit in points iguales a cero, para que las posiciones se abran sin órdenes stop. Iniciamos el asesor en el simulador, abrimos una posición y luego la cerramos parcialmente.
A continuación, colocamos y eliminamos una orden pendiente:


Ahora, los eventos de cierre parcial y de eliminación de una orden pendiente no se determinan como el mismo evento de cierre de posición, sino que se diferencian.

Iniciamos de nuevo y pulsamos los botones, prestando atención a la determinación de eventos:


Podemos ver que se determinan correctamente. El evento de cierre con posición opuesta se determina correctamente; asimismo, la modificación de los niveles stop de las posiciones y los precios de las órdenes pendientes funcionan y se monitorean de la forma habitual.

¿Qué es lo próximo?

En el presente artículo, hemos terminado la implememtación de la funcionalidad disponible para la biblioteca en cuanto a la compatibilidad con MQL4. En los próximos artículos, planeamos crear los nuevos objetos "cuenta" y "símbolo", las colecciones de estos objetos y sus eventos.

Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y los archivos del asesor de prueba. El lector podrá descargar y poner a prueba todo por sí mismo.
Si tiene cualquier duda, observación o sugerencia, podrá formularla en los comentarios al artículo.

Volver al contenido

Artículos de esta serie:

Parte 1. Concepto, organización de datos.
Parte 2. Colección de órdenes y transacciones históricas.
Parte 3. Colección de órdenes y posiciones de mercado, organización de la búsqueda.
Parte 4. Eventos comerciales. Concepto.
Parte 5. Clases y concepto de los eventos comerciales. Envío de eventos al programa.
Parte 6. Eventos en las cuentas de compensación.
Parte 7. Eventos de activación de órdenes StopLimit, preparación de la funcionalidad para los eventos de modificación de órdenes y posiciones.
Parte 8. Eventos de modificación de órdenes y posiciones.
Parte 9. Compatibilidad con MQL4 - Preparando los datos.
Parte 10. Compatibilidad con MQL4 - Eventos de apertura de posición y activación de órdenes pendientes.