Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte X): Compatibilidad con MQL4 - Eventos de apertura de posición y activación de órdenes pendientes

9 agosto 2019, 18:22
Artyom Trishkin
0
373

Contenido


Asesor de prueba

En el artículo anterior, eliminamos en los archivos de la biblioteca los errores relacionados con las diferencias entre MQL4 y MQL5, y también creamos una colección de órdenes y posiciones históricas para MQL4. En este artículo, vamos a continuar desarrollando la unión en una sola biblioteca de MQL4 y MQL5, implementando, asimismo, la determinación de los eventos de apertura de posiciones y la activación de órdenes pendientes.
El orden de los pasos a la hora de completar la implementación será inverso. Anteriormente, siempre creábamos la funcionalidad, y después el asesor de prueba. Ahora, para comprender qué debemos completar, deberemos iniciar el asesor de prueba y mirar dónde funciona y dónde falla. Y arreglar posteriormente aquello en lo que falla.

Para trabajar, vamos a tomar el asesor de prueba TestDoEasyPart08.mq5 del octavo artículo de la descripción de la biblioteca, de la carpeta \MQL5\Experts\TestDoEasy\Part08 y guardarlo con el nuevo nombre TestDoEasyPart10.mq4 en la carpeta MetaTrader 4 \MQL4\Experts\TestDoEasy\Part10.

Al intentar compilarlo, aparecen 34 errores de compilación. Prácticamente todos ellos están relacionados con la ausencia de clases comerciales en la biblioteca estándar para MQL4:


Ahora, pasamos al primer error, que nos indica que no hay archivo de inclusión


y corregimos. Vamos a incluir un archivo solo para MQL5:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart08.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
#ifdef __MQL5__
#include <Trade\Trade.mqh>
#endif 
//--- enums

La nueva compilación finaliza con 33 errores. Pasamos de nuevo al primer error de todos, que nos indica el tipo ausente al declarar el objeto de clase comercial CTrade: no está en MQL4.

Hacemos exactamente lo mismo, usamos la directiva de compilación condicional:

//--- global variables
CEngine        engine;
#ifdef __MQL5__
CTrade         trade;
#endif 
SDataButt      butt_data[TOTAL_BUTT];

Compilamos. Ahora, MQL4 no puede reconocer el objeto trade de la clase CTrade. Arreglamos el problema de la misma forma:

//--- setting trade parameters
#ifdef __MQL5__
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_NO);
#endif 
//---
   return(INIT_SUCCEEDED);
  }

A continuación, enmarcamos por todo el código del asesor todas las entradas de los objetos trade en la directiva de compilación condicional, pero usando la directiva #else, allí se ubicará el código para MQL4. Vamos a analizar el primer error con el tipo desconocido trade, después de introducir la anterior corrección y compilar:

      //--- Если нажата кнопка BUTT_BUY: Открыть позицию Buy
      if(button==EnumToString(BUTT_BUY))
        {
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit);
         //--- Открываем позицию Buy
         #ifdef __MQL5__
            trade.Buy(lot,Symbol(),0,sl,tp);
         #else 
                                            
         #endif 
        }

Después de enmarcar todas las entradas del objeto trade en la directiva de compilación condicional, topamos con otro error más, que nos indica que el compilador no puede determinar con exactitud qué llamada de la función sobrecargada utilizar, debido a la insuficiencia de parámetros:


Si miramos atentamente el código, comprenderemos el motivo de la confusión del compilador:

//+------------------------------------------------------------------+
//| Возвращает флаг наличия объекта с префиксом                      |
//+------------------------------------------------------------------+
bool IsPresentObects(const string object_prefix)
  {
   for(int i=ObjectsTotal(0)-1;i>=0;i--)
      if(StringFind(ObjectName(0,i,0),object_prefix)>WRONG_VALUE)
         return true;
   return false;
  }
//+------------------------------------------------------------------+

En MQL5, esta función tiene solo una forma de llamada:

int  ObjectsTotal(
   long  chart_id,           // идентификатор графика
   int   sub_window=-1,      // индекс окна
   int   type=-1             // тип объекта     
   );

donde el primer parámetro es el identificador del gráfico (el actual, si es 0)

mientras que en MQL4, desde hace tiempo, la función tiene dos formas de llamada. La primera es igual que en MQL5:

int  ObjectsTotal(
   long  chart_id,           // идентификатор графика
   int   sub_window=-1,      // индекс окна
   int   type=-1             // тип объекта     
   );

y la segunda, anticuada, con solo un parámetro:

int  ObjectsTotal(
   int   type=EMPTY         // тип объекта     
   );

En MQL5, la transmisión a una función de un 0 como identificador del gráfico (gráfico actual) no provoca ninguna duda ni contradicción, pero en MQL4, el compilador debe determinar qué tipo de llamada usar en función los parámetros transmitidos. Y aquí no puede determinar exactamente si le estamos transmitiendo el identificador del gráfico actual (0) y debe usar la primera forma de llamada (ya que los otros dos parámetros tienen valores por defecto, y por lo tanto pueden no ser transmitidos a la función), o si estamos transmitiendo el índice de la ventana (o el tipo de objeto) y debe usar la segunda forma de llamada.

La solución es simple: transmitimos como segundo parámetro el número de la subventana (0 = ventana principal del gráfico):

//+------------------------------------------------------------------+
//| Возвращает флаг наличия объекта с префиксом                      |
//+------------------------------------------------------------------+
bool IsPresentObects(const string object_prefix)
  {
   for(int i=ObjectsTotal(0,0)-1;i>=0;i--)
      if(StringFind(ObjectName(0,i,0),object_prefix)>WRONG_VALUE)
         return true;
   return false;
  }
//+------------------------------------------------------------------+

y

//+------------------------------------------------------------------+
//| Контроль состояния кнопок                                        |
//+------------------------------------------------------------------+
void PressButtonsControl(void)
  {
   int total=ObjectsTotal(0,0);
   for(int i=0;i<total;i++)
     {
      string obj_name=ObjectName(0,i);
      if(StringFind(obj_name,prefix+"BUTT_")<0)
         continue;
      PressButtonEvents(obj_name);
     }
  }
//+------------------------------------------------------------------+

Ahora, todo se compila sin errores. Antes de iniciar la simulación, tenemos que recordar que hemos excluido del código todas las funciones comerciales, "eliminándolas" con directivas de compilación condicional, por lo que en el asesor no hay funciones comerciales para MQL4. Esto significa que debemos añadirlas.
Recordemos que estamos escribiendo el código para el simulador, y por lo tanto, no vamos a realizar ninguna comprobación requerida a la hora de comerciar en una cuenta real/demo, vamos a limitarnos a las comprobaciones mínimas.
Puesto que en las funciones se transmiten los tickets de las órdenes y posiciones y los niveles de precio ya calculados, lo único que debemos hacer es seleccionar una orden/posición según el ticket y comprobar el tipo y la hora de cierre. Si el tipo no coincide con el tipo de la orden o posición, mostraremos un mensaje sobre ello y saldremos de la función con error. Si una orden ha sido eliminada o una posición ha sido cerrada, mostramos el mensaje correspondiente y salimos con error. A continuación, llamamos la función de apertura/cierre/modificación y retornamos el resultado de su ejecución.

Al final del listado en el archivo DELib.mqh, escribimos todas las funciones de prueba que necesitamos para MQL4:

#ifdef __MQL4__
//+------------------------------------------------------------------+
//| Временные функции MQL4 для тестера                               |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Открытие позиции Buy                                             |
//+------------------------------------------------------------------+
bool Buy(const double volume,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2)
  {
   string sym=(symbol==NULL ? Symbol() : symbol);
   double price=0;
   ResetLastError();
   if(!SymbolInfoDouble(sym,SYMBOL_ASK,price))
     {
      Print(DFUN,TextByLanguage("Не удалось получить цену Ask. Ошибка ","Could not get Ask price. Error "),(string)GetLastError());
      return false;
     }
   if(!OrderSend(sym,ORDER_TYPE_BUY,volume,price,deviation,sl,tp,comment,(int)magic,0,clrBlue))
     {
      Print(DFUN,TextByLanguage("Не удалось открыть позицию Buy. Ошибка ","Failed to open a Buy position. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Установка отложенного ордера BuyLimit                            |
//+------------------------------------------------------------------+
bool BuyLimit(const double volume,const double price_set,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2)
  {
   string sym=(symbol==NULL ? Symbol() : symbol);
   ResetLastError();
   if(!OrderSend(sym,ORDER_TYPE_BUY_LIMIT,volume,price_set,deviation,sl,tp,comment,(int)magic,0,clrBlue))
     {
      Print(DFUN,TextByLanguage("Не удалось установить ордер BuyLimit. Ошибка ","Could not place order BuyLimit. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Установка отложенного ордера BuyStop                             |
//+------------------------------------------------------------------+
bool BuyStop(const double volume,const double price_set,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2)
  {
   string sym=(symbol==NULL ? Symbol() : symbol);
   ResetLastError();
   if(!OrderSend(sym,ORDER_TYPE_BUY_STOP,volume,price_set,deviation,sl,tp,comment,(int)magic,0,clrBlue))
     {
      Print(DFUN,TextByLanguage("Не удалось установить ордер BuyStop. Ошибка ","Could not place order BuyStop. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Открытие позиции Sell                                            |
//+------------------------------------------------------------------+
bool Sell(const double volume,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2)
  {
   string sym=(symbol==NULL ? Symbol() : symbol);
   double price=0;
   ResetLastError();
   if(!SymbolInfoDouble(sym,SYMBOL_BID,price))
     {
      Print(DFUN,TextByLanguage("Не удалось получить цену Bid. Ошибка ","Could not get Bid price. Error "),(string)GetLastError());
      return false;
     }
   if(!OrderSend(sym,ORDER_TYPE_SELL,volume,price,deviation,sl,tp,comment,(int)magic,0,clrRed))
     {
      Print(DFUN,TextByLanguage("Не удалось открыть позицию Sell. Ошибка ","Failed to open a Sell position. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Установка отложенного ордера SellLimit                           |
//+------------------------------------------------------------------+
bool SellLimit(const double volume,const double price_set,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2)
  {
   string sym=(symbol==NULL ? Symbol() : symbol);
   ResetLastError();
   if(!OrderSend(sym,ORDER_TYPE_SELL_LIMIT,volume,price_set,deviation,sl,tp,comment,(int)magic,0,clrRed))
     {
      Print(DFUN,TextByLanguage("Не удалось установить ордер SellLimit. Ошибка ","Could not place order SellLimit. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Установка отложенного ордера SellStop                            |
//+------------------------------------------------------------------+
bool SellStop(const double volume,const double price_set,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2)
  {
   string sym=(symbol==NULL ? Symbol() : symbol);
   ResetLastError();
   if(!OrderSend(sym,ORDER_TYPE_SELL_STOP,volume,price_set,deviation,sl,tp,comment,(int)magic,0,clrRed))
     {
      Print(DFUN,TextByLanguage("Не удалось установить ордер SellStop. Ошибка ","Could not place order SellStop. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Закрытие позиции по тикету                                       |
//+------------------------------------------------------------------+
bool PositionClose(const ulong ticket,const double volume=0,const int deviation=2)
  {
   ResetLastError();
   if(!OrderSelect((int)ticket,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать позицию. Ошибка ","Could not select position. Error "),(string)GetLastError());
      return false;
     }
   if(OrderCloseTime()>0)
     {
      Print(DFUN,TextByLanguage("Позиция уже закрыта","Position already closed"));
      return false;
     }
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
   if(type>ORDER_TYPE_SELL)
     {
      Print(DFUN,TextByLanguage("Ошибка. Не позиция: ","Error. Not position: "),OrderTypeDescription(type)," #",ticket);
      return false;
     }
   double price=0;
   color  clr=clrNONE;
   if(type==ORDER_TYPE_BUY)
     {
      price=SymbolInfoDouble(OrderSymbol(),SYMBOL_BID);
      clr=clrBlue;
     }
   else
     {
      price=SymbolInfoDouble(OrderSymbol(),SYMBOL_ASK);
      clr=clrRed;
     }
   double vol=(volume==0 || volume>OrderLots() ? OrderLots() : volume);
   ResetLastError();
   if(!OrderClose((int)ticket,vol,price,deviation,clr))
     {
      Print(DFUN,TextByLanguage("Не удалось закрыть позицию. Ошибка ","Could not close position. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Закрытие позиции встречной                                       |
//+------------------------------------------------------------------+
bool PositionCloseBy(const ulong ticket,const ulong ticket_by)
  {
   ResetLastError();
   if(!OrderSelect((int)ticket,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать позицию. Ошибка ","Could not select position. Error "),(string)GetLastError());
      return false;
     }
   if(OrderCloseTime()>0)
     {
      Print(DFUN,TextByLanguage("Позиция уже закрыта","Position already closed"));
      return false;
     }
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
   if(type>ORDER_TYPE_SELL)
     {
      Print(DFUN,TextByLanguage("Ошибка. Не позиция: ","Error. Not position: "),OrderTypeDescription(type)," #",ticket);
      return false;
     }
   ResetLastError();
   if(!OrderSelect((int)ticket_by,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать встречную позицию. Ошибка ","Could not select the opposite position. Error "),(string)GetLastError());
      return false;
     }
   if(OrderCloseTime()>0)
     {
      Print(DFUN,TextByLanguage("Встречная позиция уже закрыта","Opposite position already closed"));
      return false;
     }
   ENUM_ORDER_TYPE type_by=(ENUM_ORDER_TYPE)OrderType();
   if(type_by>ORDER_TYPE_SELL)
     {
      Print(DFUN,TextByLanguage("Ошибка. Встречная позиция не является позицией: ","Error. Opposite position is not a position: "),OrderTypeDescription(type_by)," #",ticket_by);
      return false;
     }
   color clr=(type==ORDER_TYPE_BUY ? clrBlue : clrRed);
   ResetLastError();
   if(!OrderCloseBy((int)ticket,(int)ticket_by,clr))
     {
      Print(DFUN,TextByLanguage("Не удалось закрыть позицию встречной. Ошибка ","Could not close position by opposite position. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Удаление отложенного ордера по тикету                            |
//+------------------------------------------------------------------+
bool PendingOrderDelete(const ulong ticket)
  {
   ResetLastError();
   if(!OrderSelect((int)ticket,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать ордер. Ошибка ","Could not select order. Error "),(string)GetLastError());
      return false;
     }
   if(OrderCloseTime()>0)
     {
      Print(DFUN,TextByLanguage("Ордер уже удалён","Order already deleted"));
      return false;
     }
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
   if(type<ORDER_TYPE_SELL || type>ORDER_TYPE_SELL_STOP)
     {
      Print(DFUN,TextByLanguage("Ошибка. Не ордер: ","Error. Not order: "),PositionTypeDescription((ENUM_POSITION_TYPE)type)," #",ticket);
      return false;
     }
   color clr=(type<ORDER_TYPE_SELL_LIMIT ? clrBlue : clrRed);
   ResetLastError();
   if(!OrderDelete((int)ticket,clr))
     {
      Print(DFUN,TextByLanguage("Не удалось удалить ордер. Ошибка ","Could not delete order. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Модификация позиции по тикету                                    |
//+------------------------------------------------------------------+
bool PositionModify(const ulong ticket,const double sl,const double tp)
  {
   ResetLastError();
   if(!OrderSelect((int)ticket,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать позицию. Ошибка ","Could not select position. Error "),(string)GetLastError());
      return false;
     }
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
   if(type>ORDER_TYPE_SELL)
     {
      Print(DFUN,TextByLanguage("Ошибка. Не позиция: ","Error. Not position: "),OrderTypeDescription(type)," #",ticket);
      return false;
     }
   if(OrderCloseTime()>0)
     {
      Print(DFUN,TextByLanguage("Ошибка. Для модификации выбрана закрытая позиция: ","Error. Closed position selected for modification: "),PositionTypeDescription((ENUM_POSITION_TYPE)type)," #",ticket);
      return false;
     }
   color clr=(type==ORDER_TYPE_BUY ? clrBlue : clrRed);
   ResetLastError();
   if(!OrderModify((int)ticket,OrderOpenPrice(),sl,tp,0,clr))
     {
      Print(DFUN,TextByLanguage("Не удалось модифицировать позицию. Ошибка ","Failed to modify position. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Модификация отложенного ордера по тикету                         |
//+------------------------------------------------------------------+
bool PendingOrderModify(const ulong ticket,const double price_set,const double sl,const double tp)
  {
   ResetLastError();
   if(!OrderSelect((int)ticket,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать ордер. Ошибка ","Could not select order. Error "),(string)GetLastError());
      return false;
     }
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
   if(type<ORDER_TYPE_SELL || type>ORDER_TYPE_SELL_STOP)
     {
      Print(DFUN,TextByLanguage("Ошибка. Не ордер: ","Error. Not order: "),PositionTypeDescription((ENUM_POSITION_TYPE)type)," #",ticket);
      return false;
     }
   if(OrderCloseTime()>0)
     {
      Print(DFUN,TextByLanguage("Ошибка. Для модификации выбран удалённый ордер: ","Error. Deleted order selected for modification: "),OrderTypeDescription(type)," #",ticket);
      return false;
     }
   color clr=(type<ORDER_TYPE_SELL_LIMIT ? clrBlue : clrRed);
   ResetLastError();
   if(!OrderModify((int)ticket,price_set,sl,tp,0,clr))
     {
      Print(DFUN,TextByLanguage("Не удалось модифицировать ордер. Ошибка ","Failed to order modify. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
#endif 

Estas funciones serán temporales, pronto escribiremos las clases comerciales completas para MQL5 y MQL4, eliminando estas funciones posteriormente del listado.

Ahora, tenemos que completar la llamada de las funciones recién escritas en los lugares donde hemos dejado espacio para la llamada de funciones comerciales para MQL4, en el código del asesor. Pulsamos Ctrl+F e introducimos en el campo de búsqueda trade, de esta forma, encontraremos rápidamente los lugares en el código donde debemos establecer las llamadas de las funciones comerciales de MQL4.

Comenzando por la función para el procesamiento de la pulsación de botones PressButtonEvents() y hasta el final del listado, introducimos la llamada de las funciones comerciales de MQL4 en los lugares necesarios. Dado que hay mucho código, y la selección de la función necesaria es unívoca y no suscita dudas, no vamos el código a mostrar aquí, el lector podrá encontrarlo en los archivos adjuntos al artículo. Vamos a analizar solo el procesamiento de la pulsación de dos botones: el botón de apertura de una posición Buy y el botón de colocación de una orden pendiente BuyLimit:

//+------------------------------------------------------------------+
//| Обработка нажатий кнопок                                         |
//+------------------------------------------------------------------+
void PressButtonEvents(const string button_name)
  {
   //--- Преобразуем имя кнопки в её строковый идентификатор
   string button=StringSubstr(button_name,StringLen(prefix));
   //--- Если кнопка в нажатом состоянии
   if(ButtonState(button_name))
     {
      //--- Если нажата кнопка BUTT_BUY: Открыть позицию Buy
      if(button==EnumToString(BUTT_BUY))
        {
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit);
         //--- Открываем позицию Buy
         #ifdef __MQL5__
            trade.Buy(lot,Symbol(),0,sl,tp);
         #else 
            Buy(lot,Symbol(),magic_number,sl,tp);
         #endif 
        }
      //--- Если нажата кнопка BUTT_BUY_LIMIT: Выставить BuyLimit
      else if(button==EnumToString(BUTT_BUY_LIMIT))
        {
         //--- Получаем корректную цену установки ордера относительно уровня StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending);
         //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit);
         //--- Устанавливаем ордер BuyLimit
         #ifdef __MQL5__
            trade.BuyLimit(lot,price_set,Symbol(),sl,tp);
         #else 
            BuyLimit(lot,price_set,Symbol(),magic_number,sl,tp);
         #endif 
        }
      //--- Если нажата кнопка BUTT_BUY_STOP: Выставить BuyStop

Al realizar simulaciones con el código de la biblioteca, hemos observado una peculiaridad extraña: los eventos que MQL4 ve sin mejorar el código no se han representado inmediatamente en el diario, sino pasado cierto tiempo. Hemos investigado los motivos, y llegado a la conclusión de que la causa reside en el contador del temporizador de la colección, que funciona en el temporizador de CEngine. Tenemos un retraso mínimo de 16 milisegundos para el contador del temporizador de la colección que creamos en la tercera parte de la descripción de la biblioteca, al crear el objeto básico de la biblioteca. Sin embargo, como al usar el simulador no trabajamos con el temporizador y llamamos directamente desde OnTick() del asesor al manejador OnTimer() de la biblioteca y trabajamos con los ticks, el retraso de 16 se convierte en un retraso de 16 ticks. Para corregir esta desgradable peculiaridad, hemos tenido que mejorar un poco la clase CEngine, introduciendo en ella un método que retorna la bandera de trabajo en el simulador y el procesamiento del trabajo en el simulador en el manejador OnTimer(), que a su vez se llama desde OnTick() del asesor al trabajar en el simulador.

Para introducir los cambios, hemos creado una variable de miembro de clase privada y un método que retorna el valor de esta variable:

//+------------------------------------------------------------------+
//| Класс-основа библиотеки                                          |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // Коллекция исторических ордеров и сделок
   CMarketCollection    m_market;                        // Коллекция рыночных ордеров и сделок
   CEventsCollection    m_events;                        // Коллекция событий
   CArrayObj            m_list_counters;                 // Список счётчиков таймера
   bool                 m_first_start;                   // Флаг первого запуска
   bool                 m_is_hedge;                      // Флаг хедж-счёта
   bool                 m_is_tester;                     // Флаг работы в тестере
   bool                 m_is_market_trade_event;         // Флаг торгового события на счёте
   bool                 m_is_history_trade_event;        // Флаг торгового события в истории счёта
   ENUM_TRADE_EVENT     m_acc_trade_event;               // Торговое событие на счёте
//--- Возвращает индекс счётчика по id

public:
   //--- Возвращает список рыночных (1) позиций, (2) отложенных ордеров и (3) маркет-ордеров
   CArrayObj*           GetListMarketPosition(void);
   CArrayObj*           GetListMarketPendings(void);
   CArrayObj*           GetListMarketOrders(void);
   //--- Возвращает список исторических (1) ордеров, (2) удалённых отложенных ордеров, (3) сделок, (4) всех маркет-ордеров позиции по её идентификатору
   CArrayObj*           GetListHistoryOrders(void);
   CArrayObj*           GetListHistoryPendings(void);
   CArrayObj*           GetListDeals(void);
   CArrayObj*           GetListAllOrdersByPosID(const ulong position_id);
//--- Сбрасывает последнее торговое событие
   void                 ResetLastTradeEvent(void)                       { this.m_events.ResetLastTradeEvent(); }
//--- Возвращает (1) последнее торговое событие, (2) флаг счёта-хедж, (3) флаг работы в тестере
   ENUM_TRADE_EVENT     LastTradeEvent(void)                      const { return this.m_acc_trade_event;       }
   bool                 IsHedge(void)                             const { return this.m_is_hedge;              }
   bool                 IsTester(void)                            const { return this.m_is_tester;             }
//--- Создаёт счётчик таймера
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- Таймер
   void                 OnTimer(void);
//--- Конструктор/Деструктор
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+

El valor de esta variable de bandera de trabajo en el simulador se establece en el constructor de la clase:

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

Comprobamos el funcionamiento en el simulador en el manejador OnTimer() de la clase CEngine, y dependiendo de si el funcionamiento tiene lugar en el simulador o no, trabajamos de la forma correspondiente según el contador del temporizador o según el ticket:

//+------------------------------------------------------------------+
//| CEngine таймер                                                   |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
//--- Таймер коллекций исторических ордеров и сделок и рыночных ордеров и позиций
   int index=this.CounterIndex(COLLECTION_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter!=NULL)
        {
         //--- Если это не тестер
         if(!this.IsTester())
           {
            //--- Если пауза завершилась - работаем с событиями коллекций
            if(counter.IsTimeDone())
               this.TradeEventsControl();
           }
         //--- Если тестер - работаем с событиями коллекций по тику
         else 
           {
            this.TradeEventsControl();
           }
        }
     }
  }
//+------------------------------------------------------------------+

Compilamos el asesor, lo iniciamos en el simulador y probamos los botones:


Los mensajes en el diario indican que nuestra biblioteca ve algunos eventos: la colocación de una orden pendiente y la modificación de los parámetros de las órdenes y posiciones. El resto de los eventos por ahora no los ve.

Así que vamos a tratar de corregir los errores.

Mejorando la biblioteca

Lo primero que tenemos que comprobar es por qué la biblioteca no ve la eliminación de órdenes pendientes.
Todos nuestros eventos son monitoreados en el método de la clase de colección de eventos CEventsCollection::Refresh(). Nos interesan los eventos de la historia de la cuenta. Pasamos al método y miramos el código encargado del seguimiento de los cambios en la colección de órdenes y transacciones históricas de MQL5:
     }
//--- Если событие в истории счёта
   if(is_history_event)
     {
      //--- Если увеличилось количество исторических ордеров
      if(new_history_orders>0)
        {
         //--- Получаем список только удалённых отложенных ордеров
         CArrayObj* list=this.GetListHistoryPendings(list_history);
         if(list!=NULL)
           {
            Print(DFUN);
            //--- Сортируем новый список по времени удаления ордера
            list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC);
            //--- Берём в цикле с конца списка количество ордеров, равное количеству новых удалённых отложенных ордеров (последние N событий)
            int total=list.Total(), n=new_history_orders;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Получаем ордер из списка, и если это удалённый отложенный ордер и у него нет идентификатора позиции, 
               //--- то это удаление ордера - устанавливаем торговое событие
               COrder* order=list.At(i);
               if(order!=NULL && order.Status()==ORDER_STATUS_HISTORY_PENDING && order.PositionID()==0)
                  this.CreateNewEvent(order,list_history,list_market);
              }
           }
        }
      //--- Если увеличилось количество сделок

Encontramos el lugar necesario y vemos que, para identificar con exactitud precisamente la eliminación de una orden pendiente (no la activación), en MQL5 hemos utilizado el hecho de que la propiedad de la orden que indica la ID de la posición no ha sido rellenada, es decir, tiene un valor cero (en MQL5, si la orden se activara y generara una transacción, y después una posición, el identificador de la posición tendría el valor del identificador de la posición abierta como resultado de la activación de la orden). En MQL4, este campo se rellena directamente con el ticket de dicha orden, lo cual es incorrecto.
Pasamos al constructor cerrado de la clase de orden abstracta y localizamos la línea con la entrada de la propiedad de la orden sobre el identificador de la posición:

//+------------------------------------------------------------------+
//| Constructor paramétrico Closed                             |
//+------------------------------------------------------------------+
COrder::COrder(ENUM_ORDER_STATUS order_status,const ulong ticket)
  {
//--- Guardando las propiedades de tipo entero
   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_OPEN]                            = (long)(ulong)this.OrderOpenTime();
   this.m_long_prop[ORDER_PROP_TIME_CLOSE]                           = (long)(ulong)this.OrderCloseTime();
   this.m_long_prop[ORDER_PROP_TIME_EXP]                             = (long)(ulong)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_MSC]                        = this.OrderOpenTimeMSC();
   this.m_long_prop[ORDER_PROP_TIME_CLOSE_MSC]                       = this.OrderCloseTimeMSC();
   this.m_long_prop[ORDER_PROP_TIME_UPDATE]                          = (long)(ulong)this.PositionTimeUpdate();
   this.m_long_prop[ORDER_PROP_TIME_UPDATE_MSC]                      = (long)(ulong)this.PositionTimeUpdateMSC();
   
//--- Guardando las propiedades de tipo real
   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();
   
//--- Guardando las propiedades de tipo string
   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();
   
//--- Guardando las propiedades adicionales de tipo entero
   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;
   
//--- Guardando las propiedades adicionales de tipo real
   this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT_FULL)]        = this.ProfitFull();
  }
//+------------------------------------------------------------------+

De ello se encarga el método OrderPositionID(). Pasamos al mismo y vemos que, para MQL4, se establece directamente el ticket en el identificador:

//+------------------------------------------------------------------+
//| Retorna el identificador de la posición                                 |
//+------------------------------------------------------------------+
long COrder::OrderPositionID(void) const
  {
#ifdef __MQL4__
   return ::OrderTicket();
#else
   long res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_POSITION   : res=::PositionGetInteger(POSITION_IDENTIFIER);            break;
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_POSITION_ID);                 break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=::HistoryOrderGetInteger(m_ticket,ORDER_POSITION_ID); break;
      case ORDER_STATUS_DEAL              : res=::HistoryDealGetInteger(m_ticket,DEAL_POSITION_ID);   break;
      default                             : res=0;                                                    break;
     }
   return res;
#endif
  }
//+------------------------------------------------------------------+

Incialmente, ahí se debía indicar 0 (no hay posiciones abiertas al eliminar esta orden). Eso es lo que vamos a hacer:

//+------------------------------------------------------------------+
//| Retorna el identificador de la posición                                 |
//+------------------------------------------------------------------+
long COrder::OrderPositionID(void) const
  {
#ifdef __MQL4__
   return 0;
#else
   long res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_POSITION   : res=::PositionGetInteger(POSITION_IDENTIFIER);            break;
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_POSITION_ID);                 break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=::HistoryOrderGetInteger(m_ticket,ORDER_POSITION_ID); break;
      case ORDER_STATUS_DEAL              : res=::HistoryDealGetInteger(m_ticket,DEAL_POSITION_ID);   break;
      default                             : res=0;                                                    break;
     }
   return res;
#endif
  }
//+------------------------------------------------------------------+

Compilamos el asesor, lo iniciamos en el simulador y luego colocamos y después eliminamos una orden pendiente:


Ahora, el evento de eliminación de una orden pendiente es monitoreado.

Sin embargo, si esperamos a que la orden pendiente se active, podremos ver de nuevo que este evento, al igual que la simple apertura de una posición, no es visble para la biblioteca. Buscamos la causa.

Como podemos recordar, todo comienza con el manejador OnTimer() de la clase CEngine:

//+------------------------------------------------------------------+
//| CEngine таймер                                                   |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
//--- Таймер коллекций исторических ордеров и сделок и рыночных ордеров и позиций
   int index=this.CounterIndex(COLLECTION_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter!=NULL)
        {
         //--- Если это не тестер
         if(!this.IsTester())
           {
            //--- Если пауза завершилась - работаем с событиями коллекций
            if(counter.IsTimeDone())
               this.TradeEventsControl();
           }
         //--- Если тестер - работаем с событиями коллекций по тику
         else 
           {
            this.TradeEventsControl();
           }
        }
     }
  }
//+------------------------------------------------------------------+

Por el código podemos ver que el control del evento se realiza en el método TradeEventsControl(). Pasamos al mismo y vemos que, al darse cualquier evento, llamamos el método de actualización de eventos de la clase de colección de eventos CEventsCollection::Refresh():

//+------------------------------------------------------------------+
//| Проверка торговых событий                                        |
//+------------------------------------------------------------------+
void CEngine::TradeEventsControl(void)
  {
//--- Инициализация кода и флагов торговых событий
   this.m_is_market_trade_event=false;
   this.m_is_history_trade_event=false;
//--- Обновление списков 
   this.m_market.Refresh();
   this.m_history.Refresh();
//--- Действия при первом запуске
   if(this.IsFirstStart())
     {
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      return;
     }
//--- Проверка изменений в рыночном состоянии и в истории счёта 
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();

//--- Если есть любое событие, отправляем списки, флаги и количество новых ордеров и сделок в коллекцию событий и обновляем коллекцию событий
   int change_total=0;
   CArrayObj* list_changes=this.m_market.GetListChanges();
   if(list_changes!=NULL)
      change_total=list_changes.Total();
   if(this.m_is_history_trade_event || this.m_is_market_trade_event || change_total>0)
     {
      this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),list_changes,
                            this.m_is_history_trade_event,this.m_is_market_trade_event,
                            this.m_history.NewOrders(),this.m_market.NewPendingOrders(),
                            this.m_market.NewMarketOrders(),this.m_history.NewDeals());
      //--- Получаем последнее торговое событие на счёте
      this.m_acc_trade_event=this.m_events.GetLastTradeEvent();
     }
  }
//+------------------------------------------------------------------+

Aquí enviamos al método las listas de la colección histórica y de mercado, las banderas de cambio en estas colecciones, el número de nuevas órdenes históricas y nuevas órdenes de mercado y posiciones activas, así como el número de nuevas transacciones. Sin embargo, si miramos atentamente, veremos que, en lugar del número de nuevas posiciones de mercado, al método se transmite un número de nuevas órdenes de mercado que nosotros no utilizamos en ninguna parte de la biblioteca. Se trata de un error del autor, pues todo se ha creado inicialmente para MQL5, mientras que el número de nuevas posiciones se debe enviar al método para MQL4; y es que en MQL5, determinamos las nuevas posiciones según el número de transacciones. Así, incurrimos en un error al rellenar "a ojo" los datos transmitidos al método para MQL4. Ahora se entiende por qué el método no ve las nuevas posiciones de mercado.
Vamos a corregirlo. Y, ya de paso, solucionaremos el problema siguiente:
En MQL4, por desgracia, no existe por defecto la posibilidad de conocer la orden que ha generado una posición, al contrario de MQL5, que permite hacerlo fácilmente. Sin embargo, ya tenemos preparada y lista para usar una lista con las órdenes de control para el seguimiento de los cambios de las propiedades de las órdenes y posiciones: sus modificaciones. Y todavía no hemos limpiado esta lista de datos innecesarios. Esta lista nos va a ayudar a monitorear la orden que sirve de base para abrir la posición, ayudando de paso a identificar el evento: o bien se trata de la apertura de una orden de mercado, o bien se trata de la activación de un orden pendiente.

Añadimos a la colección de órdenes y posiciones de mercado (la clase CMarketCollection en el archivo MarketCollection.mqh)
el método público que retorna la lista de órdenes de control:

public:
//--- Возвращает список (1) всех отложенных ордеров и открытых позиций, (2) контрольных ордеров и позиций
   CArrayObj*        GetList(void)                                                                       { return &this.m_list_all_orders;                                       }
   CArrayObj*        GetListChanges(void)                                                                { return &this.m_list_changed;                                          }
   CArrayObj*        GetListControl(void)                                                                { return &this.m_list_control;                                          }
//--- Возвращает список ордеров и позиций со временем открытия в диапазоне от begin_time до end_time
   CArrayObj*        GetListByTime(const datetime begin_time=0,const datetime end_time=0);
//--- Возвращает список ордеров и позиций по выбранному (1) double, (2) integer и (3) string свойству, удовлетворяющему сравниваемому условию
   CArrayObj*        GetList(ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
   CArrayObj*        GetList(ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
   CArrayObj*        GetList(ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
//--- Возвращает количество (1) новых маркет-ордеров, (2) новых отложенных ордеров, (3) новых позиций, (4) флаг произошедшего торгового события (5) величину изменённого объёма
   int               NewMarketOrders(void)                                                         const { return this.m_new_market_orders;                                      }
   int               NewPendingOrders(void)                                                        const { return this.m_new_pendings;                                           }
   int               NewPositions(void)                                                            const { return this.m_new_positions;                                          }
   bool              IsTradeEvent(void)                                                            const { return this.m_is_trade_event;                                         }
   double            ChangedVolumeValue(void)                                                      const { return this.m_change_volume_value;                                    }
//--- Конструктор
                     CMarketCollection(void);
//--- Обновляет список отложенных ордеров и позиций
   void              Refresh(void);
  };
//+------------------------------------------------------------------+
Para usar los datos de esta lista, necesitamos transmitirla al méotod Refresh() de la clase CEventsCollection.

Por eso, vamos a escribir todos los cambios necesarios, descritos más arriba:

//+------------------------------------------------------------------+
//| Проверка торговых событий                                        |
//+------------------------------------------------------------------+
void CEngine::TradeEventsControl(void)
  {
//--- Инициализация кода и флагов торговых событий
   this.m_is_market_trade_event=false;
   this.m_is_history_trade_event=false;
//--- Обновление списков 
   this.m_market.Refresh();
   this.m_history.Refresh();
//--- Действия при первом запуске
   if(this.IsFirstStart())
     {
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      return;
     }
//--- Проверка изменений в рыночном состоянии и в истории счёта 
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();

//--- Если есть любое событие, отправляем списки, флаги и количество новых ордеров и сделок в коллекцию событий и обновляем коллекцию событий
   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_acc_trade_event=this.m_events.GetLastTradeEvent();
     }
  }
//+------------------------------------------------------------------+

Aquí, en el método TradeEventsControl() de la clase CEngine, hemos añadido la transmisión de una lista más (la lista de órdenes de control) al método Refresh() de la clase CEventsCollection, y hemos corregido la transmisión errónea a este método del número de órdenes de mercado por la transmisión del número de posiciones nuevas.

Introducimos los cambios en la definición del método Refresh() en el cuerpo de la clase CEventsCollection:

public:
//--- Выбирает события из коллекции со временем в диапазоне от begin_time до end_time
   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0);
//--- Возвращает полный список-коллекцию событий "как есть"
   CArrayObj        *GetList(void)                                                                       { return &this.m_list_events;                                           }
//--- Возвращает список по выбранному (1) целочисленному, (2) вещественному и (3) строковому свойству, удовлетворяющему сравниваемому критерию
   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);  }
//--- Обновляет список событий
   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);
//--- Устанавливает идентификатор графика управляющей программы
   void              SetChartID(const long id)        { this.m_chart_id=id;         }
//--- Возвращает последнее торговое событие на счёте
   ENUM_TRADE_EVENT  GetLastTradeEvent(void)    const { return this.m_trade_event;  }
//--- Сбрасывает последнее торговое событие
   void              ResetLastTradeEvent(void)        { this.m_trade_event=TRADE_EVENT_NO_EVENT;   }
//--- Конструктор
                     CEventsCollection(void);
  };
//+------------------------------------------------------------------+

y en su implementación fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Обновляет список событий                                         |
//+------------------------------------------------------------------+
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)
  {

En el método para actualizar la lista de eventos de la clase de colección de eventos todavía no hemos escrito el procesamiento de la apertura de posiciones para MQL4. Y vamos a necesitar varios métodos más para ella.
Para obtener la lista de posiciones abiertas nos falta el método para obtener la misma. Y tampoco tenemos el método para determinar según la lista de órdenes de control el tipo de orden cuya activación ha provocado la apertura de una posición.
Asimsimo, vamos a necesitar dos miembros privados de clase para guardar en la lista de órdenes de control el tipo encontrado de orden abierta y el identificador de la posición que serán definidos en el bloque de código para el procesamiento de eventos de apertura de posiciones de mercado para MQL4.
Los añadimos a la sección privada de la clase:

//+------------------------------------------------------------------+
//| Коллекция событий счёта                                          |
//+------------------------------------------------------------------+
class CEventsCollection : public CListObj
  {
private:
   CListObj          m_list_events;                   // Список событий
   bool              m_is_hedge;                      // Флаг хедж-счёта
   long              m_chart_id;                      // Идентификатор графика управляющей программы
   int               m_trade_event_code;              // Код торгового события
   ENUM_TRADE_EVENT  m_trade_event;                   // Торговое событие на счёте
   CEvent            m_event_instance;                // Объект-событие для поиска по свойству
   MqlTick           m_tick;                          // Структура последнего тика
   ulong             m_position_id;                   // Идентификатор позиции (MQL4)  
   ENUM_ORDER_TYPE   m_type_first;                    // Тип открывающего ордера (MQL4)
   
//--- Создаёт торговое событие в зависимости от (1) статуса и (2) типа изменения ордера
   void              CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market);
   void              CreateNewEvent(COrderControl* order);
//--- Создаёт событие для (1) хеджевого счёта, (2) неттингового счёта
   void              NewDealEventHedge(COrder* deal,CArrayObj* list_history,CArrayObj* list_market);
   void              NewDealEventNetto(COrder* deal,CArrayObj* list_history,CArrayObj* list_market);
//--- Выбирает из списка и возвращает список (1) рыночных отложенных ордеров, (2) открытых позиций
   CArrayObj*        GetListMarketPendings(CArrayObj* list);
   CArrayObj*        GetListPositions(CArrayObj* list);
//--- Выбирает из списка и возвращает список исторических (1) удалённых отложенных ордеров, (2) сделок, (3) всех закрывающих ордеров 
   CArrayObj*        GetListHistoryPendings(CArrayObj* list);
   CArrayObj*        GetListDeals(CArrayObj* list);
   CArrayObj*        GetListCloseByOrders(CArrayObj* list);
//--- Возвращает список (1) всех ордеров позиции по её идентификатору, (2) всех сделок позиции по её идентификатору
//--- (3) всех сделок на вход в рынок по идентификатору позиции, (4) всех сделок на выход из рынка по идентификатору позиции,
//--- (5) всех сделок на разворот позиции по идентификатору позиции
   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);
//--- Возвращает суммарный объём всех сделок (1) IN, (2) OUT позиции по её идентификатору
   double            SummaryVolumeDealsInByPosID(CArrayObj* list,const ulong position_id);
   double            SummaryVolumeDealsOutByPosID(CArrayObj* list,const ulong position_id);
//--- Возвращает (1) первый, (2) последний и (3) закрывающий ордер из списка всех ордеров позиции,
//--- (4) ордер по тикету, (5) рыночную позицию по идентификатору,
//--- (6) последнюю и (7) предпоследнюю сделку InOut по идентификатору позиции
   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);
//--- Возвращает тип открывающего ордера по тикету позиции (MQL4)
   ENUM_ORDER_TYPE   GetTypeFirst(CArrayObj* list,const ulong ticket);
//--- Возвращает флаг наличия объекта-события в списке событий
   bool              IsPresentEventInList(CEvent* compared_event);
//--- Обработчик события изменения существующего ордера/позиции
   void              OnChangeEvent(CArrayObj* list_changes,const int index);

public:

Implementamos fuera del cuerpo de la clase el método de obtención de la lista de posiciones abiertas:

//+------------------------------------------------------------------+
//| Выбирает из списка только рыночные позиции                       |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListPositions(CArrayObj *list)
  {
   if(list.Type()!=COLLECTION_MARKET_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком рыночной коллекции","Error. The list is not a list of the market collection"));
      return NULL;
     }
   CArrayObj* list_positions=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_POSITION,EQUAL);
   return list_positions;
  }
//+------------------------------------------------------------------+

Transmitimos al método la lista completa de órdenes y posiciones de mercado, después de lo cual, dicha lista se filtra según el estado "posición de mercado". Acto seguido, la lista obtenida como resultado del filtrado se retorna al programa que ha realizado llamada.

Vamos a escribir el método que retorna el tipo de la orden cuya activación da como resultado la apertura de una posición:

//+------------------------------------------------------------------+
//| Возвращает тип открывающего ордера по тикету позиции (MQL4)      |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE CEventsCollection::GetTypeFirst(CArrayObj* list,const ulong ticket)
  {
   if(list==NULL)
      return WRONG_VALUE;
   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 (ENUM_ORDER_TYPE)ctrl.TypeOrder();
     }
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+

Transmitimos al método la lista de órdenes de control y el ticket de la posición que acaba de abrirse. A continuación, en un ciclo desde el inicio de la lista (asumiendo que la orden pendiente haya sido colocada antes que otras posiciones abiertas, y su ticket llegue más rápido) obtenemos de la lista la orden de control y comparamos su ticket con el transmitido a la función. Si hemos encontrado el ticket, esta orden será la de apertura para la posición (cuyo ticket se ha transmitido al método), así que retornamos el tipo de esta orden. Si no hemos encontrado órdenes con ese ticket, ertornamos -1.

Ahora podemos mejorar el procesamiento de eventos con posiciones para MQL4.

Añadimos al método de actualización de la lista de eventos el procesamiento de la apertura de posición para MQL4:

//+------------------------------------------------------------------+
//| Обновляет список событий                                         |
//+------------------------------------------------------------------+
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)
  {
//--- Если списки пустые - выход
   if(list_history==NULL || list_market==NULL)
      return;
//--- Если событие в рыночном окружении
   if(is_market_event)
     {
      //--- если было изменение свойств ордера
      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(new_market_pendings>0)
        {
         //--- Получаем список только установленных отложенных ордеров
         CArrayObj* list=this.GetListMarketPendings(list_market);
         if(list!=NULL)
           {
            //--- Сортируем новый список по времени установки ордера
            list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
            //--- Берём в цикле с конца списка количество ордеров, равное количеству новых установленных ордеров (последние N событий)
            int total=list.Total(), n=new_market_pendings;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Получаем ордер из списка, и если это отложенный ордер - устанавливаем торговое событие
               COrder* order=list.At(i);
               if(order!=NULL && order.Status()==ORDER_STATUS_MARKET_PENDING)
                  this.CreateNewEvent(order,list_history,list_market);
              }
           }
        }
      #ifdef __MQL4__
      //--- Если увеличилось количество позиций
      if(new_market_positions>0)
        {
         //--- Получаем список открытых позиций
         CArrayObj* list=this.GetListPositions(list_market);
         if(list!=NULL)
           {
            //--- Сортируем новый список по времени открытия позиции
            list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
            //--- Берём в цикле с конца списка количество позиций, равное количеству новых открытых позиций (последние N событий)
            int total=list.Total(), n=new_market_positions;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Получаем позицию из списка, и если это позиция - ищем данные открывающего ордера и устанавливаем торговое событие
               COrder* position=list.At(i);
               if(position!=NULL && position.Status()==ORDER_STATUS_MARKET_POSITION)
                 {
                  //--- Находим ордер и устанавливаем (1) тип ордера, на основании которого была открыта позиция и (2) идентификатор позиции 
                  this.m_type_first=this.GetTypeFirst(list_control,position.Ticket());
                  this.m_position_id=position.Ticket();
                  this.CreateNewEvent(position,list_history,list_market);
                 }
              }
           }
        }
      #endif 
     }
//--- Если событие в истории счёта
   if(is_history_event)
     {
      //--- Если увеличилось количество исторических ордеров
      if(new_history_orders>0)
        {
         //--- Получаем список только удалённых отложенных ордеров
         CArrayObj* list=this.GetListHistoryPendings(list_history);
         if(list!=NULL)
           {
            //--- Сортируем новый список по времени удаления ордера
            list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC);
            //--- Берём в цикле с конца списка количество ордеров, равное количеству новых удалённых отложенных ордеров (последние N событий)
            int total=list.Total(), n=new_history_orders;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Получаем ордер из списка, и если это удалённый отложенный ордер и у него нет идентификатора позиции, 
               //--- то это удаление ордера - устанавливаем торговое событие
               COrder* order=list.At(i);
               if(order!=NULL && order.Status()==ORDER_STATUS_HISTORY_PENDING && order.PositionID()==0)
                  this.CreateNewEvent(order,list_history,list_market);
              }
           }
        }
      //--- Если увеличилось количество сделок
      if(new_deals>0)
        {
         //--- Получаем список только сделок
         CArrayObj* list=this.GetListDeals(list_history);
         if(list!=NULL)
           {
            //--- Сортируем новый список по времени совершения сделки
            list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
            //--- Берём в цикле с конца списка количество сделок, равное количеству новых сделок (последние N событий)
            int total=list.Total(), n=new_deals;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Получаем сделку из списка и устанавливаем торговое событие
               COrder* order=list.At(i);
               if(order!=NULL)
                  this.CreateNewEvent(order,list_history,list_market);
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

Todas las acciones para el procesamiento de una nueva posición o la activación de una orden pendiente para MQL4 se describen en los comentarios al código, y esperamos que no necesiten explicación alguna.

Ahora, vamos a pasar al método de creación de un nuevo evento CEventsCollection::CreateNewEvent() y a encontrar el bloque de código encargado de crear el evento de apertura de posición para MQL4 (el comienzo de este bloque se describe en los comentarios al código). Asimismo, vamos a completar la definición del evento de apertura de posición y los motivos de la misma, además de añadir a los datos de la posición abierta los datos de la orden que la abre y el identificador de la posición:

//--- Открыта позиция (__MQL4__)
   if(status==ORDER_STATUS_MARKET_POSITION)
     {
      //--- Установим код торгового события "позиция открыта"
      this.m_trade_event_code=TRADE_EVENT_FLAG_POSITION_OPENED;
      //--- Установим причину "запрос исполнен полностью""
      ENUM_EVENT_REASON reason=EVENT_REASON_DONE;
      //--- Если открывающий ордер - отложенный
      if(this.m_type_first>ORDER_TYPE_SELL && this.m_type_first<ORDER_TYPE_BALANCE)
        {
         //--- установим причину "срабатывание отложенного ордера"
         reason=EVENT_REASON_ACTIVATED_PENDING;
         //--- добавим к коду события флаг активации отложенного ордера
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_ACTIVATED;
        }
      CEvent* event=new CEventPositionOpen(this.m_trade_event_code,order.Ticket());
      if(event!=NULL)
        {
         event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());                             // Время события
         event.SetProperty(EVENT_PROP_REASON_EVENT,reason);                                        // Причина события (из перечисления ENUM_EVENT_REASON)
         event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,this.m_type_first);                          // Тип сделки события
         event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());                           // Тикет сделки события
         event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,this.m_type_first);                         // Тип ордера, на основании которого открыта сделка события (последний ордер позиции)
         event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,this.m_type_first);                      // Тип ордера, на основании которого открыта сделка позиции (первый ордер позиции)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket());                          // Тикет ордера, на основании которого открыта сделка события (последний ордер позиции)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());                       // Тикет ордера, на основании которого открыта сделка позиции (первый ордер позиции)
         event.SetProperty(EVENT_PROP_POSITION_ID,this.m_position_id);                             // Идентификатор позиции
         event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID());                        // Идентификатор встречной позиции
         event.SetProperty(EVENT_PROP_MAGIC_BY_ID,0);                                              // Магический номер встречной позиции
            
         event.SetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE,order.TypeOrder());                      // Тип ордера позиции до смены направления
         event.SetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE,order.Ticket());                       // Тикет ордера позиции до смены направления
         event.SetProperty(EVENT_PROP_TYPE_ORD_POS_CURRENT,order.TypeOrder());                     // Тип ордера текущей позиции
         event.SetProperty(EVENT_PROP_TICKET_ORD_POS_CURRENT,order.Ticket());                      // Тикет ордера текущей позиции
      
         event.SetProperty(EVENT_PROP_PRICE_OPEN_BEFORE,order.PriceOpen());                        // Цена установки ордера до модификации
         event.SetProperty(EVENT_PROP_PRICE_SL_BEFORE,order.StopLoss());                           // Цена StopLoss до модификации
         event.SetProperty(EVENT_PROP_PRICE_TP_BEFORE,order.TakeProfit());                         // Цена TakeProfit до модификации
         event.SetProperty(EVENT_PROP_PRICE_EVENT_ASK,this.m_tick.ask);                            // Цена Ask в момент события
         event.SetProperty(EVENT_PROP_PRICE_EVENT_BID,this.m_tick.bid);                            // Цена Bid в момент события
         
         event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                                  // Магический номер ордера/сделки/позиции
         event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpenMSC());                    // Время ордера, на основании которого открыта сделка позиции (первый ордер позиции)
         event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());                              // Цена, на которой произошло событие
         event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());                               // Цена открытия ордера/сделки/позиции
         event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());                             // Цена закрытия ордера/сделки/позиции
         event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());                                  // Цена StopLoss позиции
         event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());                                // Цена TakeProfit позиции
         event.SetProperty(EVENT_PROP_VOLUME_ORDER_INITIAL,order.Volume());                        // Запрашиваемый объём ордера
         event.SetProperty(EVENT_PROP_VOLUME_ORDER_EXECUTED,order.Volume()-order.VolumeCurrent()); // Исполненный объём ордера
         event.SetProperty(EVENT_PROP_VOLUME_ORDER_CURRENT,order.VolumeCurrent());                 // Оставшийся (неисполненный) объём ордера
         event.SetProperty(EVENT_PROP_VOLUME_POSITION_EXECUTED,order.Volume());                    // Исполненный объём позиции
         event.SetProperty(EVENT_PROP_PROFIT,order.Profit());                                      // Профит
         event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                                      // Символ ордера
         event.SetProperty(EVENT_PROP_SYMBOL_BY_ID,order.Symbol());                                // Символ встречной позиции
         //--- Установка идентификатора графика управляющей программы и расшифровка кода события и установка типа события
         event.SetChartID(this.m_chart_id);
         event.SetTypeEvent();
         //--- Если объекта-события нет в списке - добавляем
         if(!this.IsPresentEventInList(event))
           {
            this.m_list_events.InsertSort(event);
            //--- Отправляем сообщение о событии и устанавливаем значение последнего торгового события
            event.SendEvent();
            this.m_trade_event=event.TradeEvent();
           }
         //--- Если это событие уже есть в списке - удаляем новый объект-событие и выводим отладочное сообщение
         else
           {
            ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event is already in the list."));
            delete event;
           }
        }
     }
//--- Новая сделка (__MQL5__)

Después de introducir todos los cambios, la biblioteca deberá ser capaz de "ver" la apertura de posiciones y la activación de órdenes pendientes en MQL4.


Simulación

Vamos a comprobar los cambios realizados. Compilamos el asesor TestDoEasyPart10.mq4, lo iniciamos en el simulador, abrimos y cerramos varias posiciones, colocamos varias órdenes pendientes y esperamos la activación de alguna de ellas, y también comprobamos la colocación de niveles stop y el funcionamiento del trailing (modificación de posiciones y órdenes pendientes). Todos los eventos que la biblioteca ya "ve" para MQL4, se representarán el diario del simulador:


Si observamos atentamente el diario del simulador, veremos que la biblioteca aún no ve el cierre de posiciones. Sin embargo, al activarse la orden pendiente BuyLimit #3, vemos en el diario una entrada que indica que se ha activado una orden pendiente [BuyLimit #3], generando la posición Buy #3: la biblioteca ahora ve la activación de órdenes pendientes, y además sabe de qué orden procede la posición. Asimismo, podemos ver una ligera omisión en la función de modificación: el color de la etiqueta de la orden pendiente modificada por trailing BuyStop #1 se vuelto roja. Pero la librería ve todos los eventos de modificación de las órdenes y posiciones.

Vamos a añadir las correcciones realizadas a las funciones comerciales de MQL4 para el simulador de estrategias en el archivo DELib.mqh. Solo vamos a crear una función más, que retornará el tipo de la posición Buy o Sell dependiendo del tipo de orden pendiente transmitida a ella, además de sustituir en las líneas de seleccióon de color las flechas para comprobar el tipo de orden sobre la comprobación del tipo de orden según la dirección:

//+------------------------------------------------------------------+
//| Модификация отложенного ордера по тикету                         |
//+------------------------------------------------------------------+
bool PendingOrderModify(const ulong ticket,const double price_set,const double sl,const double tp)
  {
   ResetLastError();
   if(!OrderSelect((int)ticket,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать ордер. Ошибка ","Could not select order. Error "),(string)GetLastError());
      return false;
     }
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
   if(type<ORDER_TYPE_BUY_LIMIT || type>ORDER_TYPE_SELL_STOP)
     {
      Print(DFUN,TextByLanguage("Ошибка. Не ордер: ","Error. Not order: "),PositionTypeDescription((ENUM_POSITION_TYPE)type)," #",ticket);
      return false;
     }
   if(OrderCloseTime()>0)
     {
      Print(DFUN,TextByLanguage("Ошибка. Для модификации выбран удалённый ордер: ","Error. Deleted order selected for modification: "),OrderTypeDescription(type)," #",ticket);
      return false;
     }
   color clr=(TypeByPendingDirection(type)==ORDER_TYPE_BUY ? clrBlue : clrRed);
   ResetLastError();
   if(!OrderModify((int)ticket,price_set,sl,tp,0,clr))
     {
      Print(DFUN,TextByLanguage("Не удалось модифицировать ордер. Ошибка ","Failed to order modify. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Возвращает тип по направлению отложенного ордера                 |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE TypeByPendingDirection(const ENUM_ORDER_TYPE type)
  {
   if(type==ORDER_TYPE_BUY_LIMIT  || type==ORDER_TYPE_BUY_STOP)  return ORDER_TYPE_BUY;
   if(type==ORDER_TYPE_SELL_LIMIT || type==ORDER_TYPE_SELL_STOP) return ORDER_TYPE_SELL;
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+


¿Qué es lo próximo?

En el siguiente artículo, implementaremos el seguimiento del cierre de posiciones y corregiremos los errores que pueden surgir en la implementación actual del seguimiento de eventos para MQL4, ya que la colocación y eliminación de órdenes se monitorea con código para MQL5, y ahí existen ciertos matices que debemos tener en cuenta al trabajar con MQL4.

Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y los archivos del asesor de prueba. Puede descargarlo todo y ponerlo a prueba por sí mismo.
Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.

Volver al contenido

Artículos de esta serie:

Parte 1: Concepto y organización de datos.
Parte 2: Colecciones de las ó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 colección de eventos comerciales. Envío de eventos al programa.
Parte 6. Eventos en la cuenta con compensación.
Parte 7. Eventos de activación de órdenes StopLimit, preparación de la funcionalidad para el registro de 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.



Traducción del ruso hecha por MetaQuotes Software Corp.
Artículo original: https://www.mql5.com/ru/articles/6767

Archivos adjuntos |
MQL5.zip (99.2 KB)
MQL4.zip (99.2 KB)
Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte IX): Compatibilidad con MQL4 - Preparando los datos Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte IX): Compatibilidad con MQL4 - Preparando los datos

En artículos anteriores, comenzamos a crear una gran biblioteca multiplataforma, cuyo cometido es simplificar la escritura de programas para las plataformas MetaTrader 5 y MetaTrader 4. En la novena parte, hemos creado una clase que monitoreará los eventos de modificación de las órdenes y posiciones de mercado. En el presente artículo, comenzaremos a desarrollar la biblioteca para hacerla totalmente compatible con MQL4.

Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte VIII): Eventos de modificación de órdenes y posiciones Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte VIII): Eventos de modificación de órdenes y posiciones

En artículos anteriores, comenzamos a crear una gran biblioteca multiplataforma, cuyo cometido es simplificar la escritura de programas para las plataformas MetaTrader 5 y MetaTrader 4. En el séptimo artículo, añadimos el seguimiento de los eventos de activación de órdenes StopLimit y preparamos la funcionalidad para monitorear el resto de eventos que tienen lugar con las órdenes y posiciones. En el presente artículo, vamos a crear una clase que monitoreará los eventos de modificación de las órdenes y posiciones de mercado.

Desarrollamos un Asesor Experto multiplataforma para colocar StopLoss y TakeProfit de acuerdo con nuestros riesgos Desarrollamos un Asesor Experto multiplataforma para colocar StopLoss y TakeProfit de acuerdo con nuestros riesgos

En este artículo, vamos a diseñar un EA que nos permite automatizar el proceso de la definición del lote que se usa para entrar en la transacción de acuerdo con nuestros riesgos. Además, este EA permitirá colocar automáticamente Take Profit con una proporción seleccionada respecto a Stop Loss, es decir, para cumplir la razón de 3 a 1, 4 a 1 o cualquier otra seleccionada por nosotros.

Creando una lista de correo electrónico por medio de los servicios Google Creando una lista de correo electrónico por medio de los servicios Google

El trader que mantiene relaciones comerciales con otros traders, suscriptores, clientes o incluso con los amigos puede necesitar crear una lista de correo. Enviar las capturas de pantalla, revistas, registros o informes son tareas bastante relevantes que nos necesarias cada día, pero tampoco son tan raras. En cualquier caso, a algunos traders les gustaría disponer de esta posibilidad. En este artículo, se trata de las cuestiones relacionadas con el uso simultáneo de varios servicios Google, desarrollo del ensamblado correspondiente en C# e integración con las herramientas en MQL.