Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte IX): Compatibilidad con MQL4 - Preparando los datos

Artyom Trishkin | 8 agosto, 2019

Contenido

En las anteriores partes de la descripción de la biblioteca multiplataforma para MetaTrader 5 y MetaTrader 4, preparamos el instrumental:

Ahora, vamos a proceder a implementar la compatibilidad de la biblioteca con MQL4, puesto que, posteriormente, deberemos crear las clases comerciales, y la biblioteca tendrá que funcionar correctamente para ello tanto en MQL5 como en MQL4.

En el presente artículo, comenzaremos a mejorar la biblioteca para implementar su capacidad multiplataforma.

Diferencias entre MQL4 y MQL5

Vamos a copiar la carpeta completa de nuestra biblioteca en la carpeta correspondiente de MetaTrader 4: \MQL4\Include\DoEasy; asimismo, vamos tomar los asesores de prueba de las carpetas correspondientes a los artículos con los asesores MQL5 y a guardarlas con la extensión *.mq4 en el directorio de expertos \MQL4\Experts\TestDoEasy, en la carpeta correspondiente al número del artículo (en este caso, Part09) , donde se guardará el asesor de prueba para este artículo.

Encontramos en el navegador del editor la carpeta con la biblioteca \MQL4\Include\DoEasy, clicamos sobre ella con el botón derecho y seleccionamos "Compilar".


Esto hará que se compilen todos los archivos de la biblioteca, obteniendo como resultado más de dos mil errores de compilación:


Si analizamos los errores obtenidos, veremos que la mayoría están relacionados con las constantes y enumeraciones de MQL5, sobre las que MQL4 no sabe nada. Significa que debemos "presentar" a MQL4 con las constantes usadas por la biblioteca. También hay errores de otro tipo, como la ausencia en MQL4 de ciertas funciones de MQL5 que nosotros usamos: esto significa que crearemos su lógica de funcionamiento con la ayuda de las funciones MQL4 existentes.

Y, naturalmente, los sistemas de órdenes de MQL4 y MQL5 se diferencian sustancialmente. En este punto, deberemos crear para MQL4 un manejador de eventos aparte, diferente al ya implementado en MQL5, puesto que, por desgracia, en la lista de órdenes históricas en MQL4 se ofrece bastante menos información sobre las órdenes (sobre las transacciones no hay información en absoluto), y aquí no conseguiremos tomar los datos sobre las órdenes y transacciones directamente de la lista del terminal: nos veremos obligados a comparar lógicamente lo que sucede en las listas de órdenes de mercado activas e históricas y, basándonos en dicha comparación, determinar los eventos sucedidos.

Mejorando la biblioteca

En la carpeta raíz de la biblioteca DoEasy, creamos el nuevo archivo de inclusión ToMQL4.mqh, en el que describiremos las constantes y enumeraciones necesarias para MQL4. A continuación, lo incluimos en el archivo Defines.mqh para la compilación en MQL4, al principio del todo del listado Defines.mqh:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        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"
//+------------------------------------------------------------------+
//| Archivos de inclusión                                                 |
//+------------------------------------------------------------------+
#ifdef __MQL4__      
#include "ToMQL4.mqh"
#endif               
//+------------------------------------------------------------------+

Después de dicha inclusión, la biblioteca al completo verá todo lo que esté escrito en el archivo ToMQL4.mqh al realizar la compilación para MQL4.

Vamos a pasar al principio de la lista de errores en el diario del editor "Errores", pulsando la tecla NumPad "Home", o simplemente desplazándonos con el ratón al comienzo, haciendo doble clic sobre el error a la derecha del todo:


El editor nos llevará al archivo Defines.mqh, a la línea donde se ha obtenido este error:

//+------------------------------------------------------------------+
//| Список возможных торговых событий на счёте                       |
//+------------------------------------------------------------------+
enum ENUM_TRADE_EVENT
  {
   TRADE_EVENT_NO_EVENT = 0,                                // Нет торгового события
   TRADE_EVENT_PENDING_ORDER_PLASED,                        // Отложенный ордер установлен
   TRADE_EVENT_PENDING_ORDER_REMOVED,                       // Отложенный ордер удалён
//--- члены перечисления, совпадающие с членами перечисления ENUM_DEAL_TYPE
//--- (порядок следования констант ниже менять нельзя, удалять и добавлять новые - нельзя)
   TRADE_EVENT_ACCOUNT_CREDIT = DEAL_TYPE_CREDIT,           // Начисление кредита (3)
   TRADE_EVENT_ACCOUNT_CHARGE,                              // Дополнительные сборы

Como es natural, MQL4 no sabe nada sobre las transacciones y sus tipos. Debemos informarle sobre ellos. Podemos simplemente abrir la guía del editor MQL5 y encontrar la información que necesitamos sobre las propiedades de las transacciones usando la búsqueda según DEAL_TYPE_CREDIT:

Identificador

Descripción

Tipo

DEAL_TICKET

Ticket de la transacción. Número único asignado a cada transacción

long

DEAL_ORDER

Orden que ha originado la transacción

long

DEAL_TIME

Hora de ejecución de la transacción

datetime

DEAL_TIME_MSC

Hora de ejecución de la transacción en milisegundos desde 01.01.1970

long

DEAL_TYPE

Tipo de transacción

ENUM_DEAL_TYPE

DEAL_ENTRY

Dirección de la transacción: entrada en el mercado, salida del mercado o viraje

ENUM_DEAL_ENTRY

DEAL_MAGIC

Magic number для сделки (ver ORDER_MAGIC)

long

DEAL_REASON

Causa u origen del comportamiento de la transacción

ENUM_DEAL_REASON

DEAL_POSITION_ID

Identificador de la posición en cuya apertura, modificación o cierre ha participado esta transacción. Cada posición tiene un identificador único, asignado a todas las transacciones realizadas en el símbolo durante la existencia completa de la posición.

long


En este recuadro, nos interesa ENUM_DEAL_TYPE. Seguimos el enlace y obtenemos la lista con todos los tipos de transacciones:

Identificador

Descripción

DEAL_TYPE_BUY

Compra

DEAL_TYPE_SELL

Venta

DEAL_TYPE_BALANCE

Balance

DEAL_TYPE_CREDIT

Crédito

DEAL_TYPE_CHARGE

Cargas adicionales

DEAL_TYPE_CORRECTION

Corrección

DEAL_TYPE_BONUS

Bonus

DEAL_TYPE_COMMISSION

Comisiones adicionales

DEAL_TYPE_COMMISSION_DAILY

Comisión calculada al final de la jornada de trading

DEAL_TYPE_COMMISSION_MONTHLY

Comisión calculada al final del mes

DEAL_TYPE_COMMISSION_AGENT_DAILY

Comisión de agente calculada al final de la jornada de trading

DEAL_TYPE_COMMISSION_AGENT_MONTHLY

Comisión de agente calculada al final del mes

DEAL_TYPE_INTEREST

Calculo de intereses sobre fondos libres

DEAL_TYPE_BUY_CANCELED

Transacción de compra cancelada. Puede surgir la situación cuando una transacción de compra realizada anteriormente se cancele. En este caso el tipo de la operación realizada (DEAL_TYPE_BUY) se cambia a DEAL_TYPE_BUY_CANCELED, y su beneficio/pérdida se anula. El beneficio/pérdida producido/a anteriormente se carga/se quita de la cuenta por medio de una operación contable separada

DEAL_TYPE_SELL_CANCELED

Transacción de venta cancelada. Puede surgir la situación cuando una transacción de venta realizada anteriormente se cancele. En este caso el tipo de la operación realizada (DEAL_TYPE_SELL) se cambia a DEAL_TYPE_SELL_CANCELED, y su beneficio/pérdida se anula. El beneficio/pérdida producido/a anteriormente se carga/se quita de la cuenta por medio de una operación contable separada

DEAL_DIVIDEND

Operaciones con dividendos

DEAL_DIVIDEND_FRANKED

Operaciones con dividendos franqueados (no tributables)

DEAL_TAX

Carga impositiva


Vamos a añadir los tipos de transacciones de la enumeración ENUM_DEAL_TYPE al archivo ToMQL4.mqh:

//+------------------------------------------------------------------+
//|                                                       ToMQL4.mqh |
//|              Copyright 2017, Artem A. Trishkin, Skype artmedia70 |
//|                         https://www.mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Artem A. Trishkin, Skype artmedia70"
#property link      "https://www.mql5.com/ru/users/artmedia70"
#property strict
#ifdef __MQL4__
//+------------------------------------------------------------------+
//| Типы сделок MQL5                                                 |
//+------------------------------------------------------------------+
enum ENUM_DEAL_TYPE
  {
   DEAL_TYPE_BUY,
   DEAL_TYPE_SELL,
   DEAL_TYPE_BALANCE,
   DEAL_TYPE_CREDIT,
   DEAL_TYPE_CHARGE,
   DEAL_TYPE_CORRECTION,
   DEAL_TYPE_BONUS,
   DEAL_TYPE_COMMISSION,
   DEAL_TYPE_COMMISSION_DAILY,
   DEAL_TYPE_COMMISSION_MONTHLY,
   DEAL_TYPE_COMMISSION_AGENT_DAILY,
   DEAL_TYPE_COMMISSION_AGENT_MONTHLY,
   DEAL_TYPE_INTEREST,
   DEAL_TYPE_BUY_CANCELED,
   DEAL_TYPE_SELL_CANCELED,
   DEAL_DIVIDEND,
   DEAL_DIVIDEND_FRANKED,
   DEAL_TAX
  };
//+------------------------------------------------------------------+
#endif 

Guardamos el archivo y compilamos de nuevo todos los archivos de la biblioteca. Ahora hay menos errores:


Pasamos de nuevo al inicio de la lista de errores y clicamos sobre el primero. Ahora es ENUM_POSITION_TYPE, vamos a añadir:

//+------------------------------------------------------------------+
//|                                                       ToMQL4.mqh |
//|              Copyright 2017, Artem A. Trishkin, Skype artmedia70 |
//|                         https://www.mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Artem A. Trishkin, Skype artmedia70"
#property link      "https://www.mql5.com/ru/users/artmedia70"
#property strict
#ifdef __MQL4__
//+------------------------------------------------------------------+
//| Типы сделок MQL5                                                 |
//+------------------------------------------------------------------+
enum ENUM_DEAL_TYPE
  {
   DEAL_TYPE_BUY,
   DEAL_TYPE_SELL,
   DEAL_TYPE_BALANCE,
   DEAL_TYPE_CREDIT,
   DEAL_TYPE_CHARGE,
   DEAL_TYPE_CORRECTION,
   DEAL_TYPE_BONUS,
   DEAL_TYPE_COMMISSION,
   DEAL_TYPE_COMMISSION_DAILY,
   DEAL_TYPE_COMMISSION_MONTHLY,
   DEAL_TYPE_COMMISSION_AGENT_DAILY,
   DEAL_TYPE_COMMISSION_AGENT_MONTHLY,
   DEAL_TYPE_INTEREST,
   DEAL_TYPE_BUY_CANCELED,
   DEAL_TYPE_SELL_CANCELED,
   DEAL_DIVIDEND,
   DEAL_DIVIDEND_FRANKED,
   DEAL_TAX
  };
//+------------------------------------------------------------------+
//| Направление открытой позиции                                     |
//+------------------------------------------------------------------+
enum ENUM_POSITION_TYPE
  {
   POSITION_TYPE_BUY,
   POSITION_TYPE_SELL
  };
//+------------------------------------------------------------------+
#endif 

Después de compilar, obtenemos aún menos errores. Pasamos al primer error, determinamos el motivo y añadimos la siguiente enumeración:

//+------------------------------------------------------------------+
//|                                                       ToMQL4.mqh |
//|              Copyright 2017, Artem A. Trishkin, Skype artmedia70 |
//|                         https://www.mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Artem A. Trishkin, Skype artmedia70"
#property link      "https://www.mql5.com/ru/users/artmedia70"
#property strict
#ifdef __MQL4__
//+------------------------------------------------------------------+
//| Типы сделок MQL5                                                 |
//+------------------------------------------------------------------+
enum ENUM_DEAL_TYPE
  {
   DEAL_TYPE_BUY,
   DEAL_TYPE_SELL,
   DEAL_TYPE_BALANCE,
   DEAL_TYPE_CREDIT,
   DEAL_TYPE_CHARGE,
   DEAL_TYPE_CORRECTION,
   DEAL_TYPE_BONUS,
   DEAL_TYPE_COMMISSION,
   DEAL_TYPE_COMMISSION_DAILY,
   DEAL_TYPE_COMMISSION_MONTHLY,
   DEAL_TYPE_COMMISSION_AGENT_DAILY,
   DEAL_TYPE_COMMISSION_AGENT_MONTHLY,
   DEAL_TYPE_INTEREST,
   DEAL_TYPE_BUY_CANCELED,
   DEAL_TYPE_SELL_CANCELED,
   DEAL_DIVIDEND,
   DEAL_DIVIDEND_FRANKED,
   DEAL_TAX
  };
//+------------------------------------------------------------------+
//| Направление открытой позиции                                     |
//+------------------------------------------------------------------+
enum ENUM_POSITION_TYPE
  {
   POSITION_TYPE_BUY,
   POSITION_TYPE_SELL
  };
//+------------------------------------------------------------------+
//| Состояние ордера                                                 |
//+------------------------------------------------------------------+
enum ENUM_ORDER_STATE
  {
   ORDER_STATE_STARTED,
   ORDER_STATE_PLACED,
   ORDER_STATE_CANCELED,
   ORDER_STATE_PARTIAL,
   ORDER_STATE_FILLED,
   ORDER_STATE_REJECTED,
   ORDER_STATE_EXPIRED,
   ORDER_STATE_REQUEST_ADD,
   ORDER_STATE_REQUEST_MODIFY,
   ORDER_STATE_REQUEST_CANCEL
  };
//+------------------------------------------------------------------+
#endif 

Durante la siguiente compilación, nos hemos encontrado el tipo erróneo de orden ORDER_TYPE_BUY_STOP_LIMIT.
En MQL4 ya existe la enumeración ENUM_ORDER_TYPE, a la que no podemos añadir nuevas constantes. Esto significa que deberemos añadirlas como macrosustituciones.

En MQL5, la constante ORDER_TYPE_BUY_STOP_LIMIT de la enumeración ENUM_ORDER_TYPE tiene el valor 6. Pero en MQL4, este tipo de orden existe. Esta operación contable, igual que ORDER_TYPE_SELL_STOP_LIMIT en MQL5, tiene el valor 7, y en MQL4, este tipo de orden es una operación de crédito.

Por eso, vamos a asignarles valores superiores a la constante de la orden de cierre ORDER_TYPE_CLOSE_BY en MQL5: ORDER_TYPE_CLOSE_BY+1 y ORDER_TYPE_CLOSE_BY+2, respectivamente:

//+------------------------------------------------------------------+
//|                                                       ToMQL4.mqh |
//|              Copyright 2017, Artem A. Trishkin, Skype artmedia70 |
//|                         https://www.mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Artem A. Trishkin, Skype artmedia70"
#property link      "https://www.mql5.com/ru/users/artmedia70"
#property strict
#ifdef __MQL4__
//+------------------------------------------------------------------+
//| Типы сделок MQL5                                                 |
//+------------------------------------------------------------------+
enum ENUM_DEAL_TYPE
  {
   DEAL_TYPE_BUY,
   DEAL_TYPE_SELL,
   DEAL_TYPE_BALANCE,
   DEAL_TYPE_CREDIT,
   DEAL_TYPE_CHARGE,
   DEAL_TYPE_CORRECTION,
   DEAL_TYPE_BONUS,
   DEAL_TYPE_COMMISSION,
   DEAL_TYPE_COMMISSION_DAILY,
   DEAL_TYPE_COMMISSION_MONTHLY,
   DEAL_TYPE_COMMISSION_AGENT_DAILY,
   DEAL_TYPE_COMMISSION_AGENT_MONTHLY,
   DEAL_TYPE_INTEREST,
   DEAL_TYPE_BUY_CANCELED,
   DEAL_TYPE_SELL_CANCELED,
   DEAL_DIVIDEND,
   DEAL_DIVIDEND_FRANKED,
   DEAL_TAX
  };
//+------------------------------------------------------------------+
//| Направление открытой позиции                                     |
//+------------------------------------------------------------------+
enum ENUM_POSITION_TYPE
  {
   POSITION_TYPE_BUY,
   POSITION_TYPE_SELL
  };
//+------------------------------------------------------------------+
//| Состояние ордера                                                 |
//+------------------------------------------------------------------+
enum ENUM_ORDER_STATE
  {
   ORDER_STATE_STARTED,
   ORDER_STATE_PLACED,
   ORDER_STATE_CANCELED,
   ORDER_STATE_PARTIAL,
   ORDER_STATE_FILLED,
   ORDER_STATE_REJECTED,
   ORDER_STATE_EXPIRED,
   ORDER_STATE_REQUEST_ADD,
   ORDER_STATE_REQUEST_MODIFY,
   ORDER_STATE_REQUEST_CANCEL
  };
//+------------------------------------------------------------------+
//| Типы ордеров                                                     |
//+------------------------------------------------------------------+
#define ORDER_TYPE_CLOSE_BY         (8) 
#define ORDER_TYPE_BUY_STOP_LIMIT   (9) 
#define ORDER_TYPE_SELL_STOP_LIMIT  (10)
//+------------------------------------------------------------------+
#endif 

Compilamos la biblioteca al completo. Ahora, después de introducir las macrosustituciones de los tipos de órdenes StopLimit, aparece un error que señala al archivo DELib.mqh, a las funciones que retornan el precio correcto de colocación de una orden, concretamente, indica el hecho de que la enumeración ENUM_ORDER_TYPE no tiene los valores 9 y 10, dado que usamos en el operador switch el valor del tipo de orden, y este tiene el tipo de enumeración ENUM_ORDER_TYPE:

//+------------------------------------------------------------------+
//| Возвращает корректную цену постановки ордера                     |
//| относительно StopLevel                                           |
//+------------------------------------------------------------------+
double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double price=0,const int spread_multiplier=2)
  {
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   switch(order_type)
     {
      case ORDER_TYPE_BUY_LIMIT        :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg);
      case ORDER_TYPE_BUY_STOP         :  
      case ORDER_TYPE_BUY_STOP_LIMIT   :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg);
      case ORDER_TYPE_SELL_LIMIT       :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg);
      case ORDER_TYPE_SELL_STOP        :  
      case ORDER_TYPE_SELL_STOP_LIMIT  :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg);
      default                          :  Print(DFUN,TextByLanguage("Не правильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0;
     }
  }
//+------------------------------------------------------------------+
//| Возвращает корректную цену постановки ордера                     |
//| относительно StopLevel                                           |
//+------------------------------------------------------------------+
double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const int distance_set,const double price=0,const int spread_multiplier=2)
  {
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   switch(order_type)
     {
      case ORDER_TYPE_BUY_LIMIT        :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg);
      case ORDER_TYPE_BUY_STOP         :  
      case ORDER_TYPE_BUY_STOP_LIMIT   :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg);
      case ORDER_TYPE_SELL_LIMIT       :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg);
      case ORDER_TYPE_SELL_STOP        :  
      case ORDER_TYPE_SELL_STOP_LIMIT  :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg);
      default                          :  Print(DFUN,TextByLanguage("Не правильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0;
     }
  }
//+------------------------------------------------------------------+

La solución es sencilla: convertimos order_type en switch en un tipo entero:

//+------------------------------------------------------------------+
//| Возвращает корректную цену постановки ордера                     |
//| относительно StopLevel                                           |
//+------------------------------------------------------------------+
double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double price=0,const int spread_multiplier=2)
  {
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   switch((int)order_type)
     {
      case ORDER_TYPE_BUY_LIMIT        :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg);
      case ORDER_TYPE_BUY_STOP         :  
      case ORDER_TYPE_BUY_STOP_LIMIT   :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg);
      case ORDER_TYPE_SELL_LIMIT       :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg);
      case ORDER_TYPE_SELL_STOP        :  
      case ORDER_TYPE_SELL_STOP_LIMIT  :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg);
      default                          :  Print(DFUN,TextByLanguage("Не правильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0;
     }
  }
//+------------------------------------------------------------------+
//| Возвращает корректную цену постановки ордера                     |
//| относительно StopLevel                                           |
//+------------------------------------------------------------------+
double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const int distance_set,const double price=0,const int spread_multiplier=2)
  {
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   switch((int)order_type)
     {
      case ORDER_TYPE_BUY_LIMIT        :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg);
      case ORDER_TYPE_BUY_STOP         :  
      case ORDER_TYPE_BUY_STOP_LIMIT   :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg);
      case ORDER_TYPE_SELL_LIMIT       :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg);
      case ORDER_TYPE_SELL_STOP        :  
      case ORDER_TYPE_SELL_STOP_LIMIT  :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg);
      default                          :  Print(DFUN,TextByLanguage("Не правильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0;
     }
  }
//+------------------------------------------------------------------+

Compilamos una vez más. Ahora vemos un error en el archivo Order.mqh, MQL4 no conoce los valores de las constantes ORDER_FILLING_RETURN, ORDER_TIME_GTC, ORDER_REASON_SL, ORDER_REASON_TP y ORDER_REASON_EXPERT.

//+------------------------------------------------------------------+
//| Retorna el tipo de ejecución según el resto                             |
//+------------------------------------------------------------------+
long COrder::OrderTypeFilling(void) const
  {
#ifdef __MQL4__
   return (long)ORDER_FILLING_RETURN;
#else 
   long res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_TYPE_FILLING);                break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=::HistoryOrderGetInteger(m_ticket,ORDER_TYPE_FILLING);break;
      default                             : res=0;                                                    break;
     }
   return res;
#endif 
  }
//+------------------------------------------------------------------+
//| Retorna el tiempo de expiración de la orden                                    |
//+------------------------------------------------------------------+
long COrder::OrderTypeTime(void) const
  {
#ifdef __MQL4__
   return (long)ORDER_TIME_GTC;
#else 
   long res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_TYPE_TIME);                break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=::HistoryOrderGetInteger(m_ticket,ORDER_TYPE_TIME);break;
      default                             : res=0;                                                 break;
     }
   return res;
#endif 
  }
//+------------------------------------------------------------------+
//| Motivo o razón de la colocación de la orden                          |
//+------------------------------------------------------------------+
long COrder::OrderReason(void) const
  {
#ifdef __MQL4__
   return
     (
      this.OrderCloseByStopLoss()   ?  ORDER_REASON_SL      :
      this.OrderCloseByTakeProfit() ?  ORDER_REASON_TP      :  
      this.OrderMagicNumber()!=0    ?  ORDER_REASON_EXPERT  : WRONG_VALUE
     );
#else 
   long res=WRONG_VALUE;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_POSITION   : res=::PositionGetInteger(POSITION_REASON);          break;
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_REASON);                break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=::HistoryOrderGetInteger(m_ticket,ORDER_REASON);break;
      case ORDER_STATUS_DEAL              : res=::HistoryDealGetInteger(m_ticket,DEAL_REASON);  break;
      default                             : res=WRONG_VALUE;                                    break;
     }
   return res;
#endif 
  }
//+------------------------------------------------------------------+

Vamos a añadir macrosustituciones al archivo ToMQL4.mqh (las añadimos al final, no vamos a mostrar el listado completo, para ahorrar espacio):

//+------------------------------------------------------------------+
//| Типы ордеров, политика исполнения, срок действия, причины        |
//+------------------------------------------------------------------+
#define ORDER_TYPE_CLOSE_BY         (8)
#define ORDER_TYPE_BUY_STOP_LIMIT   (9)
#define ORDER_TYPE_SELL_STOP_LIMIT  (10)
#define ORDER_FILLING_RETURN        (2)
#define ORDER_TIME_GTC              (0)
#define ORDER_REASON_EXPERT         (3)
#define ORDER_REASON_SL             (4)
#define ORDER_REASON_TP             (5)
//+------------------------------------------------------------------+
#endif 

La nueva compilación nos lleva a la ausencia de la función MQL5 HistoryOrderGetTicket() en el archivo HistoryCollection.mqh del método CHistoryCollection::OrderSearch(). El análisis del código nos indica que será más sencillo usar aquí directivas de compilación condicional. Completamos el método:

//+------------------------------------------------------------------+
//| Возвращает тип и тикет "потерянного" ордера                      |
//+------------------------------------------------------------------+
ulong CHistoryCollection::OrderSearch(const int start,ENUM_ORDER_TYPE &order_type)
  {
   ulong order_ticket=0;
#ifdef __MQL5__
   for(int i=start-1;i>=0;i--)
     {
      ulong ticket=::HistoryOrderGetTicket(i);
      if(ticket==0)
         continue;
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::HistoryOrderGetInteger(ticket,ORDER_TYPE);
      if(this.IsPresentOrderInList(ticket,type))
         continue;
      order_ticket=ticket;
      order_type=type;
     }
#else 
   for(int i=start-1;i>=0;i--)
     {
      if(!::OrderSelect(i,SELECT_BY_POS,MODE_HISTORY))
         continue;
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderType();
      ulong ticket=::OrderTicket();
      if(ticket==0 || type<ORDER_TYPE_BUY_LIMIT || type>ORDER_TYPE_SELL_STOP)
         continue;
      if(this.IsPresentOrderInList(ticket,type))
         continue;
      order_ticket=ticket;
      order_type=type;
     }
#endif    
   return order_ticket;
  }
//+------------------------------------------------------------------+

Enmarcamos todo lo que hay en él para MQL5 con la directiva #ifdef __MQL5__ y completamos el código para MQL4 después de la directiva #else, hasta la directiva #endif.

El siguiente error aparece en el constructor de la clase CEvent. Vamos a completar el código usando las mismas directivas de compilación condicional:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CEvent::CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket) : m_event_code(event_code),m_digits(0)
  {
   this.m_long_prop[EVENT_PROP_STATUS_EVENT]       =  event_status;
   this.m_long_prop[EVENT_PROP_TICKET_ORDER_EVENT] =  (long)ticket;
   this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_digits_acc=#ifdef __MQL4__ 2 #else (int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS) #endif;
   this.m_chart_id=::ChartID();
  }
//+------------------------------------------------------------------+

Aquí, al comprobar si la cuenta es del tipo "cobertura", aparece el error de ausencia constante, por eso, simplemente retornamos true de inmediato: en MetaTrader 4, todas las cuentas son de tipo cobertura.
Al obtener el número de dígitos decimales en la divisa de la cuenta, también retornamos 2, ya que en MQL4 no hay posibilidad de obtener este valor.

La siguiente compilación nos lleva al método CEventsCollection::NewDealEventHedge(), la creación de un evento para una cuenta de cobertura en MetaTrader 5. Trabaja con transacciones que no existen en MQL4. Por el momento, desactivaremos totalmente este método, enmarcando el código completo del método dentro de la compilación condicional:

Pegamos al inicio del método la directiva

//+------------------------------------------------------------------+
//| Создаёт событие для хеджевого счёта                              |
//+------------------------------------------------------------------+
void CEventsCollection::NewDealEventHedge(COrder* deal,CArrayObj* list_history,CArrayObj* list_market)
  {
#ifdef __MQL5__
   double ask=::SymbolInfoDouble(deal.Symbol(),SYMBOL_ASK);
   double bid=::SymbolInfoDouble(deal.Symbol(),SYMBOL_BID);
   //--- Вход в рынок

y al final del método

#endif 
  }
//+------------------------------------------------------------------+

A continuación, vemos un error en el método CEventsCollection::NewDealEventNetto(), la creación de un evento para una cuenta de compensación. La solución es la misma que en el caso anterior: enmarcamos el código completo del método NewDealEventNetto() con un directiva de compilación condicional.

Compilamos y topamos con el error de constante desconocida DEAL_ENTRY_IN en el método CEventsCollection::GetListAllDealsInByPosID(). Añadimos la enumeración necesaria al archivo ToMQL4.mqh (podríamos desactivar de nuevo el código de la compilación condicional, pero hemos pensado que esta enumeración podría sernos necesaria más adelante):

//+------------------------------------------------------------------+
//| Типы сделок MQL5                                                 |
//+------------------------------------------------------------------+
enum ENUM_DEAL_TYPE
  {
   DEAL_TYPE_BUY,
   DEAL_TYPE_SELL,
   DEAL_TYPE_BALANCE,
   DEAL_TYPE_CREDIT,
   DEAL_TYPE_CHARGE,
   DEAL_TYPE_CORRECTION,
   DEAL_TYPE_BONUS,
   DEAL_TYPE_COMMISSION,
   DEAL_TYPE_COMMISSION_DAILY,
   DEAL_TYPE_COMMISSION_MONTHLY,
   DEAL_TYPE_COMMISSION_AGENT_DAILY,
   DEAL_TYPE_COMMISSION_AGENT_MONTHLY,
   DEAL_TYPE_INTEREST,
   DEAL_TYPE_BUY_CANCELED,
   DEAL_TYPE_SELL_CANCELED,
   DEAL_DIVIDEND,
   DEAL_DIVIDEND_FRANKED,
   DEAL_TAX
  };
//+------------------------------------------------------------------+
//| Способ изменения позиции                                         |
//+------------------------------------------------------------------+
enum ENUM_DEAL_ENTRY
  {
   DEAL_ENTRY_IN,
   DEAL_ENTRY_OUT,
   DEAL_ENTRY_INOUT,
   DEAL_ENTRY_OUT_BY
  };
//+------------------------------------------------------------------+
//| Направление открытой позиции                                     |
//+------------------------------------------------------------------+
enum ENUM_POSITION_TYPE
  {
   POSITION_TYPE_BUY,
   POSITION_TYPE_SELL
  };
//+------------------------------------------------------------------+

Después, nos encontramos con el error de comprobación sobre el tipo "cobertura", que ya conocemos, pero ahora en el constructor de la clase de colección de eventos. Vamos a corregirlo:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CEventsCollection::CEventsCollection(void) : m_trade_event(TRADE_EVENT_NO_EVENT),m_trade_event_code(TRADE_EVENT_FLAG_NO_EVENT)
  {
   this.m_list_events.Clear();
   this.m_list_events.Sort(SORT_BY_EVENT_TIME_EVENT);
   this.m_list_events.Type(COLLECTION_EVENTS_ID);
   this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_chart_id=::ChartID();
   ::ZeroMemory(this.m_tick);
  }
//+------------------------------------------------------------------+

A continuación, implementamos la misma corrección en el constructor de la clase CEngine:

//+------------------------------------------------------------------+
//| CEngine конструктор                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),m_acc_trade_event(TRADE_EVENT_NO_EVENT)
  {
   ::ResetLastError();
   if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
      Print(DFUN,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError());
   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;
  }
//+------------------------------------------------------------------+

Ya está todo. Ahora, la biblioteca se compila sin errores. Pero esta es solo la primera etapa. A continuación, debemos iniciarla. Como hemos desactivado varios métodos con la compilación condicional, ahora tendremos que desarrollarlos para que funcionen en MetaTrader 4.

En MQL5, las operaciones contables son transacciones y se ubican en la lista de órdenes y transacciones históricas. En MQL4, en cambio, las operaciones contables son órdenes con el tipo ORDER_TYPE_BALANCE (6) y ORDER_TYPE_CREDIT (7). Por eso, hemos decidido hacer para MQL4 una clase de objeto de operación contable, que guardaremos igualmente en la lista de órdenes y posiciones históricas.

Creamos la nueva clase CHistoryBalance en la carpeta \MQL4\Include\DoEasy\Objects\Orders del archivo HistoryBalance.mqh. COrder deberá ser la clase básica:

//+------------------------------------------------------------------+
//|                                               HistoryBalance.mqh |
//|                        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"
//+------------------------------------------------------------------+
//| Archivos de inclusión                                                 |
//+------------------------------------------------------------------+
#include "Order.mqh"
//+------------------------------------------------------------------+
//| Историческая балансовая операция                                 |
//+------------------------------------------------------------------+
class CHistoryBalance : public COrder
  {
public:
   //--- Конструктор
                     CHistoryBalance(const ulong ticket) : COrder(ORDER_STATUS_BALANCE,ticket) {}
   //--- Поддерживаемые свойства сделки (1) вещественные, (2) целочисленные
   virtual bool      SupportProperty(ENUM_ORDER_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_ORDER_PROP_DOUBLE property);
   virtual bool      SupportProperty(ENUM_ORDER_PROP_STRING property);
  };
//+------------------------------------------------------------------+
//| Возвращает истину, если ордер поддерживает переданное            |
//| целочисленное свойство, возвращает ложь в противном случае       |
//+------------------------------------------------------------------+
bool CHistoryBalance::SupportProperty(ENUM_ORDER_PROP_INTEGER property)
  {
   if(property==ORDER_PROP_TICKET      ||
      property==ORDER_PROP_TIME_OPEN   || 
      property==ORDER_PROP_STATUS      ||
      property==ORDER_PROP_TYPE        ||
      property==ORDER_PROP_REASON
     ) return true;
   return false;
  }
//+------------------------------------------------------------------+
//| Возвращает истину, если ордер поддерживает переданное            |
//| вещественное свойство, возвращает ложь в противном случае        |
//+------------------------------------------------------------------+
bool CHistoryBalance::SupportProperty(ENUM_ORDER_PROP_DOUBLE property)
  {
   return(property==ORDER_PROP_PROFIT ? true : false);
  }
//+------------------------------------------------------------------+
//| Возвращает истину, если ордер поддерживает переданное            |
//| строковое свойство, возвращает ложь в противном случае           |
//+------------------------------------------------------------------+
bool CHistoryBalance::SupportProperty(ENUM_ORDER_PROP_STRING property)
  {
   if(property==ORDER_PROP_SYMBOL || property==ORDER_PROP_EXT_ID)
      return false;
   return true;
  }
//+------------------------------------------------------------------+

Esta clase no tiene nada nuevo para nosotros, ya hemos analizado todas las clases de la órdenes históricas en la quinta parte de la descripció de la biblioteca.

Queremos destacar que hay dos tipos de operaciones contables: las operaciones de saldo y las de crédito. Sus tipos tienen los valores numéricos 6 y 7, respectivamente. Usaremos para ambos tipos una misma clase de operación contable, concretando el tipo en la propiedad de la orden "motivo".

Añadimos los dos "motivos" de aparición de la orden que faltan. Los incluimos en el archivo ToMQL4.mqh:

//+------------------------------------------------------------------+
//| Типы ордеров, политика исполнения, срок действия, причины        |
//+------------------------------------------------------------------+
#define ORDER_TYPE_CLOSE_BY         (8)
#define ORDER_TYPE_BUY_STOP_LIMIT   (9)
#define ORDER_TYPE_SELL_STOP_LIMIT  (10)
#define ORDER_FILLING_RETURN        (2)
#define ORDER_TIME_GTC              (0)
#define ORDER_REASON_EXPERT         (3)
#define ORDER_REASON_SL             (4)
#define ORDER_REASON_TP             (5)
#define ORDER_REASON_BALANCE        (6)
#define ORDER_REASON_CREDIT         (7)
//+------------------------------------------------------------------+

Y ya que tenemos una nueva clase heredera de la clase de orden abstracta, será necesario completar la funcionalidad restante en COrder.

En el método COrder::OrderPositionID(), sustituimos el retorno del número mágico para MQL4

//+------------------------------------------------------------------+
//| Retorna el identificador de la posición                                 |
//+------------------------------------------------------------------+
long COrder::OrderPositionID(void) const
  {
#ifdef __MQL4__
   return ::OrderMagicNumber();
#else

por el retorno del ticket (más adelante, implementaremos algo semejante a PositionID para las posiciones de MQL4):

//+------------------------------------------------------------------+
//| Retorna el identificador de la posición                                 |
//+------------------------------------------------------------------+
long COrder::OrderPositionID(void) const
  {
#ifdef __MQL4__
   return ::OrderTicket();
#else

En el método que retorna el estado de la orden en MQL4 siempre se ha retornado ORDER_STATE_FILLED de la enumeración ENUM_ORDER_STATE, lo cual no es cierto para las órdenes pendientes eliminadas. Vamos a implementar la comprobación del estado de la orden, y si se trata de una orden pendiente eliminada, retornaremos ORDER_STATE_CANCELED.

//+------------------------------------------------------------------+
//| Возвращает состояние ордера                                      |
//+------------------------------------------------------------------+
long COrder::OrderState(void) const
  {
#ifdef __MQL4__
   return(this.Status()==ORDER_STATUS_HISTORY_ORDER ? ORDER_STATE_FILLED : ORDER_STATE_CANCELED);
#else
   long res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=::HistoryOrderGetInteger(m_ticket,ORDER_STATE); break;
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_STATE);                 break;
      case ORDER_STATUS_MARKET_POSITION   : 
      case ORDER_STATUS_DEAL              : 
      default                             : res=0;                                              break;
     }
   return res;
#endif
  }
//+------------------------------------------------------------------+

Añadimos los dos "motivos" nuevamente añadidos al método que retorna el motivo de la orden para MQL4:

//+------------------------------------------------------------------+
//| Motivo o razón de la colocación de la orden                          |
//+------------------------------------------------------------------+
long COrder::OrderReason(void) const
  {
#ifdef __MQL4__
   return
     (
      this.TypeOrder()==ORDER_TYPE_BALANCE   ?  ORDER_REASON_BALANCE :
      this.TypeOrder()==ORDER_TYPE_CREDIT    ?  ORDER_REASON_CREDIT  :
      this.OrderCloseByStopLoss()            ?  ORDER_REASON_SL      :
      this.OrderCloseByTakeProfit()          ?  ORDER_REASON_TP      :  
      this.OrderMagicNumber()!=0             ?  ORDER_REASON_EXPERT  : WRONG_VALUE
     );
#else 

En nuestro caso, el método que retorna el volumen no ejecutado para MQL4, siempre ha retornado el lote de la orden, lo cual no es correcto para las posiciones. Para las órdenes pendientes eliminadas, retornaremos el lote de la orden, y para las posiciones, retornaremos cero:

//+------------------------------------------------------------------+
//| Retorna el volumen no ejecutado                                   |
//+------------------------------------------------------------------+
double COrder::OrderVolumeCurrent(void) const
  {
#ifdef __MQL4__
   return(this.Status()==ORDER_STATUS_HISTORY_PENDING ? ::OrderLots() : 0);
#else 

Añadimos la descripción de los dos nuevos "motivos" en el método que retorna la descripción del motivo de la orden. Comprobaremos el beneficio para las operaciones de balance y crédito, y si es superior a cero, significará que los fondos han sido depositados, de lo contrario, que han sido retirados:

//+------------------------------------------------------------------+
//| Descripción del motivo                                                 |
//+------------------------------------------------------------------+
string COrder::GetReasonDescription(const long reason) const
  {
#ifdef __MQL4__
   return
     (
      this.IsCloseByStopLoss()            ?  TextByLanguage("Срабатывание StopLoss","Due to StopLoss")                  :
      this.IsCloseByTakeProfit()          ?  TextByLanguage("Срабатывание TakeProfit","Due to TakeProfit")              :
      this.Reason()==ORDER_REASON_EXPERT  ?  TextByLanguage("Выставлен из mql4-программы","Placed from mql4 program")   :
      this.Comment()=="cancelled"         ?  TextByLanguage("Отменён","Cancelled")                                      :
      this.Reason()==ORDER_REASON_BALANCE ?  (
                                              this.Profit()>0 ? TextByLanguage("Пополнение баланса","Deposit of funds on the account balance") :
                                              TextByLanguage("Снятие средств с баланса","Withdrawals from the balance")
                                             )                                                                          :
      this.Reason()==ORDER_REASON_CREDIT  ?  (
                                              this.Profit()>0 ? TextByLanguage("Начисление кредитных средств","Received credit funds") :
                                              TextByLanguage("Изъятие кредитных средств","Withdrawal of credit")
                                             )                                                                          :
                                             
      TextByLanguage("Свойство не поддерживается в MQL4","Property is not supported in MQL4")
     );
#else 

Asimismo, hemos realizado algunas pequeñas correcciones, cuya descripción carece de sentido práctico. En general, están relacionadas con el texto mostrado en el diario de MQL5 y MQL4. Todas las correcciones están disponibles en los archivos de la biblioteca adjuntos al artículo.

A continuación, vamos a mejorar la clase de la colección histórica en el archivo HistoryCollection.mqh.
En primer lugar, incluimos en ella el archivo de la nueva clase:

//+------------------------------------------------------------------+
//| Archivos de inclusión                                                 |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Orders\HistoryOrder.mqh"
#include "..\Objects\Orders\HistoryPending.mqh"
#include "..\Objects\Orders\HistoryDeal.mqh"
#ifdef __MQL4__
#include "..\Objects\Orders\HistoryBalance.mqh"
#endif 
//+------------------------------------------------------------------+

Ya que la clase CHistoryBalance es solo necesaria para la versión MQL4 de la biblioteca, la inclusión del archivo de esta clase también se encontrará enmarcada en una directiva de compilación condicional para MQL4.

Ahora tenemos una nueva clase de operaciones contables. Para crearla y ubicarla en la colección, vamos a necesitar añadir en el método Refresh() de la clase CHistoryCollection para MQL4 la comprobación de los tipos de órdenes sobre el tipo de operación de crédito y balance, así como su adición a la colección:

//+------------------------------------------------------------------+
//| Обновляет список ордеров и сделок                                |
//+------------------------------------------------------------------+
void CHistoryCollection::Refresh(void)
  {
#ifdef __MQL4__
   int total=::OrdersHistoryTotal(),i=m_index_order;
   for(; i<total; i++)
     {
      if(!::OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)) continue;
      ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)::OrderType();
      //--- Закрытые позиции
      if(order_type<ORDER_TYPE_BUY_LIMIT)
        {
         CHistoryOrder *order=new CHistoryOrder(::OrderTicket());
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to the list"));
            delete order;
           }
        }
      //--- Балансовые/кредитные операции
      else if(order_type>ORDER_TYPE_SELL_STOP)
        {
         CHistoryBalance *order=new CHistoryBalance(::OrderTicket());
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to the list"));
            delete order;
           }
        }
      else
        {
         //--- Удалённые отложенные ордера
         CHistoryPending *order=new CHistoryPending(::OrderTicket());
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to the list"));
            delete order;
           }
        }
     }
//---
   int delta_order=i-m_index_order;
   this.m_index_order=i;
   this.m_delta_order=delta_order;
   this.m_is_trade_event=(this.m_delta_order!=0 ? true : false);
//--- __MQL5__
#else 

Vamos a hacer algunas correcciones en la clase de orden histórica:
//+------------------------------------------------------------------+
//| Возвращает истину, если ордер поддерживает переданное            |
//| вещественное свойство, возвращает ложь в противном случае        |
//+------------------------------------------------------------------+
bool CHistoryOrder::SupportProperty(ENUM_ORDER_PROP_DOUBLE property)
  {
   if(
   #ifdef __MQL5__
      property==ORDER_PROP_PROFIT                  || 
      property==ORDER_PROP_PROFIT_FULL             || 
      property==ORDER_PROP_SWAP                    || 
      property==ORDER_PROP_COMMISSION              ||
      property==ORDER_PROP_PRICE_CLOSE             ||
      (
       property==ORDER_PROP_PRICE_STOP_LIMIT       && 
       (
        this.TypeOrder()<ORDER_TYPE_BUY_STOP_LIMIT || 
        this.TypeOrder()>ORDER_TYPE_SELL_STOP_LIMIT  
       )
      )
   #else
      property==ORDER_PROP_PRICE_STOP_LIMIT        && 
      this.Status()==ORDER_STATUS_HISTORY_ORDER
   #endif 
     ) return false;

   return true;
  }
//+------------------------------------------------------------------+

Anteriormente, el precio de colocación de una orden StopLimit en MQL5 no se mostraba en el diario, por eso hemos implementado la siguiente comprobación: si la propiedad comprobadaes el precio de una orden StopLimit, y además el tipo de orden no es StopLimit, entonces, solo en este caso, la propiedad no será utilizada. En caso contrario, se tratará de una orden StopLimit, y esta propiedad será necesaria.
Para MQL4
, el precio de colocación de una orden StopLimit para las posiciones no se usa.

Bien, con esto, podemos dar por finalizadas las mejoras de la primera etapa de compatibilidad con MQL4.

Simulación

Para realizar la simulación, tomaremos el asesor TestDoEasyPart03_1.mq5 de la carpeta de asesores MQL5 \MQL5\Experts\TestDoEasy\Part03 y lo guardaremos con el nombre TestDoEasyPart09.mq4 en la carpeta de asesores MQL4 \MQL4\Experts\TestDoEasy\Part09.

El asesor se compila sin realizar cambio alguno, pero si echamos un vistazo al código, veremos que usa una lista de transacciones que no se encuentra en MQL4:

//--- enums
enum ENUM_TYPE_ORDERS
  {
   TYPE_ORDER_MARKET,   // Market-orders
   TYPE_ORDER_PENDING,  // Pending orders
   TYPE_ORDER_DEAL      // Deals
  };
//--- input parameters

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- обновляем историю
   history.Refresh();
//--- получаем список-коллекцию в диапазоне дат
   CArrayObj* list=history.GetListByTime(InpTimeBegin,InpTimeEnd,SELECT_BY_TIME_CLOSE);
   if(list==NULL)
     {
      Print("Could not get collection list");
      return INIT_FAILED;
     }
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      //--- получаем ордер из списка
      COrder* order=list.At(i);
      if(order==NULL) continue;
      //--- если это сделка
      if(order.Status()==ORDER_STATUS_DEAL && InpOrderType==TYPE_ORDER_DEAL)
         order.Print();
      //--- если это исторический маркет-ордер
      if(order.Status()==ORDER_STATUS_HISTORY_ORDER && InpOrderType==TYPE_ORDER_MARKET)
         order.Print();
      //--- если это удалённый отложенный ордер
      if(order.Status()==ORDER_STATUS_HISTORY_PENDING && InpOrderType==TYPE_ORDER_PENDING)
         order.Print();
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Solo tenemos que sustituir las transacciones por operaciones contables. En este caso, usaremos la compilación condicional directamente en el asesor, lo cual no es muy adecuado para el producto final, donde todas las acciones destinadas a la limitación de las versiones del lenguaje deben estar ocultas para el usuario. En este caso, simplemente vamos a poner prueba las mejoras de la biblioteca, así que no es para tanto.

Vamos a añadir algunos pequeños cambios al código del asesor, sustituyendo las transacciones de MQL5 por operaciones contables de MQL4:

//+------------------------------------------------------------------+
//|                                           TestDoEasyPart03_1.mq4 |
//|                        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\Collections\HistoryCollection.mqh>
//--- enums
enum ENUM_TYPE_ORDERS
  {
   TYPE_ORDER_MARKET,   // Market-orders
   TYPE_ORDER_PENDING,  // Pending orders
#ifdef __MQL5__
   TYPE_ORDER_DEAL      // Deals
#else 
   TYPE_ORDER_BALANCE   // Balance/Credit
#endif 
  };
//--- input parameters
input ENUM_TYPE_ORDERS  InpOrderType   =  TYPE_ORDER_MARKET;   // Show type:
input datetime          InpTimeBegin   =  0;                   // Start date of required range
input datetime          InpTimeEnd     =  END_TIME;            // End date of required range
//--- global variables
CHistoryCollection history;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- обновляем историю
   history.Refresh();
//--- получаем список-коллекцию в диапазоне дат
   CArrayObj* list=history.GetListByTime(InpTimeBegin,InpTimeEnd,SELECT_BY_TIME_CLOSE);
   if(list==NULL)
     {
      Print("Could not get collection list");
      return INIT_FAILED;
     }
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      //--- получаем ордер из списка
      COrder* order=list.At(i);
      if(order==NULL) continue;
   //--- если это сделка
   #ifdef __MQL5__
      if(order.Status()==ORDER_STATUS_DEAL && InpOrderType==TYPE_ORDER_DEAL)
         order.Print();
   #else 
   //--- если это балансовая/кредитная операция
      if(order.Status()==ORDER_STATUS_BALANCE && InpOrderType==TYPE_ORDER_BALANCE)
         order.Print();
   #endif 
   //--- если это исторический маркет-ордер
      if(order.Status()==ORDER_STATUS_HISTORY_ORDER && InpOrderType==TYPE_ORDER_MARKET)
         order.Print();
   //--- если это удалённый отложенный ордер
      if(order.Status()==ORDER_STATUS_HISTORY_PENDING && InpOrderType==TYPE_ORDER_PENDING)
         order.Print();
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

Compilamos e iniciamos el asesor en el terminal (este asesor, perteneciente al tercer artículo, funciona solo en el manejador OnInit(), por eso, mostrará una sola vez la lista necesaria de la colección histórica después del inicio, o bien tras modificar la listas en los ajustes).

Antes de iniciar el asesor, es recomendable seleccionar con el botón derecho el punto "Historia completa" en la pestaña "Historia de la cuenta" del menú contextual en el terminal, ya que en MetaTrader 4 la cantidad de historia disponible para los programas depende del tamaño de la historia seleccionado en esta pestaña.

En los ajustes se encuentra seleccionado "Balance/Credit", y el primer depósito de saldo se mostrará en el diario:


Ahora, debemos comprobar si la búsqueda y la representación de las posiciones cerradas son correctas. Como hemos abierto la cuenta en MetaTrader 4 hace poco, no se han realizado transacciones en la misma. Hemos abierto una posición Sell, le hemos colocado un StopLoss y un TakeProfit, y hemos esperado un rato tomando café. Al regresar, la posición había sido cerrada por un stop, después de lo cual, el mercado comenzó a moverse en dirección a la posición abierta de venta, como siempre :)

Sin embargo, ahora tenemos una posición cerrada para realizar pruebas.
En los ajustes se encuentra seleccionado "Market-orders":


Ahora, comprobamos la lista de órdenes pendientes eliminadas. Hemos colocado un par de órdenes, eliminándolas después.
En los ajustes se encuentra seleccionado "Pending orders":


La lista de órdenes pendientes eliminadas también se representa.

¿Qué es lo próximo?

En el siguiente artículo, implementaremos el trabajo con posiciones de mercado y órdenes pendientes activas en 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.