Asesor experto multiplataforma: reutilizando los componentes de la Biblioteca Estándar MQL5

Enrico Lambino | 8 septiembre, 2016

Índice


Introducción

En la Biblioteca Estándar MQL5 hay ciertos componentes que pueden resultar útiles a la hora de desarrollar un asesor experto multiplataforma. Sin embargo, la incompatibilidad con el compilador MQL4 los hace no aptos para aquellas versiones de los asesores multiplataforma que han sido desarrolladas en MQL4. Existen, por lo menos, dos caminos a seguir en este caso.

  1. Escribir desde cero, guardando solo aquellos componentes que tienen soporte en ambas versiones del lenguaje.

  2. Compilar las clases y modificarlas de tal forma que los archivos de encabezamiento se compilen en MQL4.

En este artículo vamos a estudiar la segunda variante. El uso del primer método podría hacer bastante libre la implementación en el lado de MQL4. Sin embargo, su principal defecto es que no queremos reescribir mucho código. Sería más sencillo realizar la actualización hasta la última versión de las clases MQL5, que reescribirlo todo desde cero.

Estructura de las carpetas

A diferencia de la mayoría de la clases que se usarán, los objetos de clase que se utilizan repetidamente en la Biblioteca Estándar MQL5 deberán ser copiados en la carpeta Include dentro del catálogo de datos MQL4. Por eso, para que el asesor se pueda usar independientemente de su versión, será necesario realizar ciertos trabajos de carácter organizativo. Los métodos que utilizaremos para lograr este objetivo variarán un poco. Pero uno de los métodos más sencillos es fijar un archivo de encabezamiento concreto y obligarlo a funcionar de acuerdo con el archivo de encabezamiento que vamos a utilizar. El método se parece un poco al usado en los objetos de usuario de las clases:

  1. Para la versión de MQL5, el archivo de encabezamiento principal debe hacer referencia a la ubicación original del archivo de encabezamiento utilizado.

  2. En la versión de MQL4, el archivo de encabezamiento principal debe hacer referencia a la copia (modificada) del archivo de encabezamiento utilizado.

Una de las formas de hacer esto es crear la carpeta "Lib" en el directorio principal. Este se ubicará en el mismo lugar que el archivo de encabezamiento principal. Una carpeta análoga se creará en la carpeta MQL4 del catálogo multiplataforma (supongamos que se llama "MQLx-Reuse"). En ella se ubicarán las copias modificadas de las clases MQL5 correspondientes.

/Include

/MQLx-Reuse

/Base

/Lib

<Main Header Files>

/MQL4

/Lib

<Copy of MQL5 Header Files>

/MQL5

En la carpeta MQL5 no habrá carpeta "Lib", puesto que los archivos de encabezamiento principales harán referencia directa al archivo de encabezamiento que se encuentra dentro del catálogo de datos de MQL5. Por ejemplo, necesitamos reutilizar CSymbolInfo en un asesor multiplataforma. Concretamente esta clase es parte de la biblioteca estándar MQL5, pertenenciente a las clases comerciales. Se puede obtener acceso a ella usando esta directiva #include:

#include <Trade\SymbolInfo.mqh>

Para usar esto en nuestros ajustes actuales, necesitaremos crear un archivo de encabezamiento principal en la subcarpeta Lib dentro del catálogo Base, con la ayuda del código siguiente:

(/Base/Lib/SymbolInfo.mqh)

#ifdef __MQL5__
   #include <Trade\SymbolInfo.mqh>
#else
   #include "..\..\MQL4\Lib\SymbolInfo.mqh"
#endif

La versión de MQL4 llama la directiva de inclusión del archivo de encabezamiento ubicado en la carpeta "Lib". Pero en el momento actual, será la carpeta "Lib" del catálogo "MQL4" en nuestro directorio de usuario (“MQLx-Reuse”).

Como ya hemos dicho antes, la versión de MQL5 no necesita la carpeta "Lib", puesto que el archivo de encabezamiento principal se encuentra en la carpeta Base. Este ya indica a <Trade\SymbolInfo.mqh>, que normalmente se encuentra dentro de la carpeta Include MQL5.

Se trata solo de un enfoque que recomendamos, en ningún caso es obligatorio aplicarlo. En cualquier caso, resulta útil cuando carpetas por separado se usan en exclusiva para los archivos de encabezamiento que se utilizan repetidamente o se toman prestados de MQL5.

CSymbolInfo

Al usar el compilador de MQL4, la compilación del archivo de encabezamiento de la clase original CSymbolInfo dará error. Los errores suelen estar provocados por la incompatibilidad con MQL4, más concretamente se relacionan con el uso de las funciones SymbolInfoDouble y SymbolInfoInteger. La mayoría de las llamadas de estas funciones se pueden ver en el método Refresh(). El código original (no modificado) de la clase CSymbolInfo se muestra más abajo:

bool CSymbolInfo::Refresh(void)
  {
   long tmp=0;
//--- 
   if(!SymbolInfoDouble(m_name,SYMBOL_POINT,m_point))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE,m_tick_value))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE_PROFIT,m_tick_value_profit))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE_LOSS,m_tick_value_loss))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_SIZE,m_tick_size))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_CONTRACT_SIZE,m_contract_size))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_MIN,m_lots_min))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_MAX,m_lots_max))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_STEP,m_lots_step))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_LIMIT,m_lots_limit))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_SWAP_LONG,m_swap_long))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_SWAP_SHORT,m_swap_short))
      return(false);
   if(!SymbolInfoInteger(m_name,SYMBOL_DIGITS,tmp))
      return(false);
   m_digits=(int)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_ORDER_MODE,tmp))
      return(false);
   m_order_mode=(int)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_EXEMODE,tmp))
      return(false);
   m_trade_execution=(ENUM_SYMBOL_TRADE_EXECUTION)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_CALC_MODE,tmp))
      return(false);
   m_trade_calcmode=(ENUM_SYMBOL_CALC_MODE)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_MODE,tmp))
      return(false);
   m_trade_mode=(ENUM_SYMBOL_TRADE_MODE)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_SWAP_MODE,tmp))
      return(false);
   m_swap_mode=(ENUM_SYMBOL_SWAP_MODE)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_SWAP_ROLLOVER3DAYS,tmp))
      return(false);
   m_swap3=(ENUM_DAY_OF_WEEK)tmp;
   if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_INITIAL,m_margin_initial))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_MAINTENANCE,m_margin_maintenance))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_LONG,m_margin_long))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_SHORT,m_margin_short))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_LIMIT,m_margin_limit))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_STOP,m_margin_stop))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_STOPLIMIT,m_margin_stoplimit))
      return(false);
   if(!SymbolInfoInteger(m_name,SYMBOL_EXPIRATION_MODE,tmp))
      return(false);
   m_trade_time_flags=(int)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_FILLING_MODE,tmp))
      return(false);
   m_trade_fill_flags=(int)tmp;
//--- éxito
   return(true);
  }

De una forma similar a la que se discutió en el primer artículo de este ciclo, usaremos un archivo de encabezamiento general, que de forma ideal debería consolidar las semejanzas en el código entre las versiones MQL4 y MQL5. En realidad, podemos reescribir la clase CSymbolinfo en tres archivos diferentes, de tal forma que las semejanzas estén unidas en un archivo, y las diferencias se distribuyan en los dos otros archivos. Sin embargo, en este artículo vamos a ir por el camino más sencillo (y rápido): copiaremos el archivo de la clase CSymbolinfo y después comentaremos las líneas que no son compatibles con MQL4. Para ambas versiones, la estructura final de los archivos tendrá el aspecto que sigue:

Algunos identificadores de propiedades tales como SYMBOL_TRADE_TICK_VALUE_PROFIT y SYMBOL_TRADE_TICK_VALUE_LOSS no tienen soporte en MQL4. Es mejor comentarlos, así hasta que no aparezcan nuevos desarrollos que permitan crear un rodeo para usarlos.

En el código que sigue abajo se muestra cómo se comentan las funciones que pueden provocar un error al llamar el método Refresh en MQL4.

bool CSymbolInfo::Refresh(void)
  {
   long tmp=0;
//--- 
   if(!SymbolInfoDouble(m_name,SYMBOL_POINT,m_point))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE,m_tick_value))
      return(false);
//if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE_PROFIT,m_tick_value_profit))
//return(false);
//if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE_LOSS,m_tick_value_loss))
//return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_SIZE,m_tick_size))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_CONTRACT_SIZE,m_contract_size))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_MIN,m_lots_min))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_MAX,m_lots_max))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_STEP,m_lots_step))
      return(false);
//if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_LIMIT,m_lots_limit))
//return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_SWAP_LONG,m_swap_long))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_SWAP_SHORT,m_swap_short))
      return(false);
   if(!SymbolInfoInteger(m_name,SYMBOL_DIGITS,tmp))
      return(false);
   m_digits=(int)tmp;
//if(!SymbolInfoInteger(m_name,SYMBOL_ORDER_MODE,tmp))
//return(false);
//m_order_mode=(int)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_EXEMODE,tmp))
      return(false);
   m_trade_execution=(ENUM_SYMBOL_TRADE_EXECUTION)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_CALC_MODE,tmp))
      return(false);
   m_trade_calcmode=(ENUM_SYMBOL_CALC_MODE)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_MODE,tmp))
      return(false);
   m_trade_mode=(ENUM_SYMBOL_TRADE_MODE)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_SWAP_MODE,tmp))
      return(false);
   m_swap_mode=(ENUM_SYMBOL_SWAP_MODE)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_SWAP_ROLLOVER3DAYS,tmp))
      return(false);
   m_swap3=(ENUM_DAY_OF_WEEK)tmp;
   if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_INITIAL,m_margin_initial))
      return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_MAINTENANCE,m_margin_maintenance))
      return(false);
//if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_LONG,m_margin_long))
//return(false);
//if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_SHORT,m_margin_short))
//return(false);
//if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_LIMIT,m_margin_limit))
//return(false);
//if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_STOP,m_margin_stop))
//return(false);
//if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_STOPLIMIT,m_margin_stoplimit))
//return(false);
//if(!SymbolInfoInteger(m_name,SYMBOL_EXPIRATION_MODE,tmp))
//return(false);
//m_trade_time_flags=(int)tmp;
//if(!SymbolInfoInteger(m_name,SYMBOL_FILLING_MODE,tmp))
//return(false);
//m_trade_fill_flags=(int)tmp;
//--- éxito
   return(true);
  }

Hay enumeraciones incorporadas disponibles en MQL5, pero no en MQL4. Para esta clase ENUM_SYMBOL_CALC_MODE y ENUM_SYMBOL_SWAP_MODE eran imprescindibles al compilar el archivo de encabezamiento en MQL4. Para incluir estas enumeraciones, solo necesitamos declararlas en el archivo de encabezamiento de la clase:

enum ENUM_SYMBOL_CALC_MODE
  {
   SYMBOL_CALC_MODE_FOREX,
   SYMBOL_CALC_MODE_FUTURES,
   SYMBOL_CALC_MODE_CFD,
   SYMBOL_CALC_MODE_CFDINDEX,
   SYMBOL_CALC_MODE_CFDLEVERAGE,
   SYMBOL_CALC_MODE_EXCH_STOCKS,
   SYMBOL_CALC_MODE_EXCH_FUTURES,
   SYMBOL_CALC_MODE_EXCH_FUTURES_FORTS
  };
enum ENUM_SYMBOL_SWAP_MODE
  {
   SYMBOL_SWAP_MODE_DISABLED,
   SYMBOL_SWAP_MODE_POINTS,
   SYMBOL_SWAP_MODE_CURRENCY_SYMBOL,
   SYMBOL_SWAP_MODE_CURRENCY_MARGIN,
   SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT,
   SYMBOL_SWAP_MODE_INTEREST_CURRENT,
   SYMBOL_SWAP_MODE_INTEREST_OPEN,
   SYMBOL_SWAP_MODE_REOPEN_CURRENT,
   SYMBOL_SWAP_MODE_REOPEN_BID
  };

Preste atención a que han sido comentadas no todas las funciones soportadas en MQL4. Hasta ahora hemos seguido los pasos mínimos necesarios para que el archivo de encabezamiento pueda ser procesado por el compilador MQL4. Si no está seguro de si un método o función en particular es compatible con ambas versiones, recomendamos recurrir a la documentación de los dos lenguajes.

Debido a que cada versión tiene su propia copia por separado de CSymbolInfo, el archivo de encabezamiento principal no contendrá la definición de clase. En lugar de ello, incluirá sólo una indicación con la ubicación del archivo de encabezamiento para su inclusión, dependiendo de la versión del compilador usado:

#ifdef __MQL5__
   #include <Trade\SymbolInfo.mqh>
#else
   #include "..\..\MQL4\Lib\SymbolInfo.mqh"
#endif

El archivo de encabezamiento MQL5 indicará la ubicación original de CSymbolInfo dentro de la Biblioteca Estándar MQL5. A su vez, el archivo de encabezamiento MQL4 indicará al archivo de encabezamiento CSymbolInfo, que es una copia perfeccionada del archivo de encabezamiento MQL5 CSymbolInfo, que se encuentra en Lib.


CSymbolManager

Para los asesores multidivisa será necesaria la colección de ejemplares CSymbolInfo. CSymbolManager se encargará de crearla. La clase amplía CArrayObj y le permite guardar diferentes ejemplares de objetos con información de símbolos, junto con algunos métodos adicionales. Su "interrelación" con los ejemplares CSymbolinfo se ilustra la figura que vemos más abajo:

Es decir, se comporta de forma semejante a CArrayObj, aunque la clase es mejor usarla para guardar objetos de un tipo concreto (ejemplares CSymbolinfo). Dado que CSymbolinfo se basa en CArrayObj, que a su vez está disponible tanto en MQL4 como en MQL5, podemos limitar la mayor parte del código al archivo de encabezamiento principal, después de lo cual, haremos referencia a archivos vacíos de las clases para las dos versiones diferentes del lenguaje (independientemente del compilador que se use). Al final obtenemos una estructura de archivo que coincide con CSymbolInfo descrito anteriormente para la clase:


Añadir símbolos

Los ejemplares de la clase Csymbolnfo se añaden de forma análoga al método Add de la clase CArrayObj. Sin embargo, a diferencia de CArrayObj, el ejemplar añadido debe ser único. En otras palabras, no deben existir dos elementos con el mismo nombre de símbolo.

bool CSymbolManagerBase::Add(CSymbolInfo *node)
  {
   if(Search(node.Name())==-1)
      return CArrayObj::Add(node);       
   return false;
  }

La clase derivada usará el método Search, que es una sobrecarga del método de clase análogo CArrayObj. A su vez, el último no se usará, puesto que esto significaría utilizar el método Compare de la clase CSymbolInfo, que de hecho es un método de la clase CObject (CSymbolInfo no tiene método explícito Compare). El uso del método Compare necesita de la expansión de la clase CSymbolInfo. Esto puede ser una decisión compliacada, porque ya tenemos copias separadas de esta clase. El método Search comparará los objetos con el nombre de los símbolos buscados por ellos:

int CSymbolManagerBase::Search(string symbol=NULL)
  {
   if(symbol==NULL)
      symbol= Symbol();
   for(int i=0;i<Total();i++)
     {
      CSymbolInfo *item=At(i);
      if(StringCompare(item.Name(),symbol)==0)
         return i;
     }
   return -1;
  }

Para los asesores multidivisa, en la clase CSymbolManager se ha creado un puntero al símbolo prioritario. Puede tratarse del símbolo actual o de otro cualquiera.  Nosotros recurriremos a él con más frecuencia que a los demás. En esta clase podemos permitir al experto establecer el símbolo prioritario durante la inicialización. Si no lo hacemos, entonces el símbolo prioritario será el primer ejemplar procesado de CSymbolInfo:
void CSymbolManagerBase::SetPrimary(string symbol=NULL)
  {
   if(symbol==NULL)
      symbol= Symbol();
   m_symbol_primary=Get(symbol);
  }

CSymbolInfo *CSymbolManagerBase::GetPrimary(void)
  {
   if (!CheckPointer(m_symbol_primary) && Total()>0)
      SetPrimary(0);
   return m_symbol_primary;
  }


Actualizar símbolos

Para actualizar las cotizaciones de todos los símbolos en el gestor de símbolos, solo tenemos que pasar por todos los objetos de la matriz, y llamar para cada uno de ellos el método RefreshRates. Preste atención a que para una colección enorme de símbolos, recurrir a este método podría no ser la opción más práctica. En este caso, es mejor llamar la función RefreshRates en un ejemplar concreto de CSymbolInfo. El código que veremos a continuación muestra el método RefreshRates de la clase:

bool CSymbolManagerBase::RefreshRates(void)
  {
   for(int i=0;i<Total();i++)
     {
      CSymbolInfo *symbol=At(i);
      if(!CheckPointer(symbol))
         continue;
      if(!symbol.RefreshRates())
         return false;
     }
   return true;
  }

Preste atención a que los métodos Refresh y RefreshRates se distinguen en CSymbolInfo. El primero se usa durante la inicialización, el segundo, para actualizar la información del símbolo en el último tick procesado.

Más abajo se muestra el código completo de la implementación básica de la clase CSymbolManager:

(SymbolManagerBase.mqh)

#include <Arrays\ArrayObj.mqh>
#include "..\Lib\SymbolInfo.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CSymbolManagerBase : public CArrayObj
  {
protected:
   CSymbolInfo      *m_symbol_primary;
   CObject          *m_container;
public:
                     CSymbolManagerBase(void);
                    ~CSymbolManagerBase(void);
   virtual bool      Add(CSymbolInfo*);
   virtual void      Deinit(void);
   CSymbolInfo      *Get(string);
   virtual bool      RefreshRates(void);
   virtual int       Search(string);
   virtual CObject *GetContainer(void);
   virtual void      SetContainer(CObject*);
   virtual void      SetPrimary(string);
   virtual void      SetPrimary(const int);
   virtual CSymbolInfo *GetPrimary(void);
   virtual string    GetPrimaryName(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSymbolManagerBase::CSymbolManagerBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSymbolManagerBase::~CSymbolManagerBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CObject *CSymbolManagerBase::GetContainer(void)
  {
   return m_container;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSymbolManagerBase::SetContainer(CObject *container)
  {
   m_container=container;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSymbolManagerBase::Deinit(void)
  {
   Shutdown();
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CSymbolManagerBase::Add(CSymbolInfo *node)
  {
   if(Search(node.Name())==-1)
      return CArrayObj::Add(node);       
   return false;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSymbolInfo *CSymbolManagerBase::Get(string symbol=NULL)
  {
   if(symbol==NULL)
      symbol= Symbol();
   for(int i=0;i<Total();i++)
     {
      CSymbolInfo *item=At(i);
      if(!CheckPointer(item))
         continue;
      if(StringCompare(item.Name(),symbol)==0)
         return item;
     }
   return NULL;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CSymbolManagerBase::RefreshRates(void)
  {
   for(int i=0;i<Total();i++)
     {
      CSymbolInfo *symbol=At(i);
      if(!CheckPointer(symbol))
         continue;
      if(!symbol.RefreshRates())
         return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CSymbolManagerBase::Search(string symbol=NULL)
  {
   if(symbol==NULL)
      symbol= Symbol();
   for(int i=0;i<Total();i++)
     {
      CSymbolInfo *item=At(i);
      if(StringCompare(item.Name(),symbol)==0)
         return i;
     }
   return -1;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CSymbolManagerBase::SetPrimary(string symbol=NULL)
  {
   if(symbol==NULL)
      symbol= Symbol();
   m_symbol_primary=Get(symbol);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CSymbolManagerBase::SetPrimary(const int idx)
  {
   m_symbol_primary=At(idx);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSymbolInfo *CSymbolManagerBase::GetPrimary(void)
  {
   if (!CheckPointer(m_symbol_primary) && Total()>0)
      SetPrimary(0);
   return m_symbol_primary;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
string CSymbolManagerBase::GetPrimaryName(void)
  {
   if(CheckPointer(m_symbol_primary))
      return m_symbol_primary.Name();
   return NULL;
  }
//+------------------------------------------------------------------+
#ifdef __MQL5__
#include "..\..\MQL5\Symbol\SymbolManager.mqh"
#else
#include "..\..\MQL4\Symbol\SymbolManager.mqh"
#endif
//+------------------------------------------------------------------+

Todos los métodos son compatibles con ambas plataformas. De esta forma, las clases específicas para la plataforma serán idénticas, y no contendrán ningún método adicional.

(SymbolManager.mqh, MQL4 and MQL5)

class CSymbolManager : public CSymbolManagerBase
  {
public:
                     CSymbolManager(void);
                    ~CSymbolManager(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSymbolManager::CSymbolManager(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSymbolManager::~CSymbolManager(void)
  {
  }
//+------------------------------------------------------------------+

CAccountInfo

De forma análoga a como se ha hecho con CSymbolInfo, la mayoría de las funciones sin soporte deberán ser comentadas. Las funciones sin soporte, como por ejemplo, las manipulaciones con el modo de cálculo del margen (solo en MetaTrader 5), las funciones OrderCalcMargin y OrderCalcProfit, que no tienen análogos directos en MQL4. De esta forma, igual que en el caso de CSymbolinfo, vamos a crear copias aparte de una y otra clase para las dos plataformas, y después cambiaremos la versión de la clase para MQL4 de tal manera que pueda ser procesada con el compilador correspondiente. La estructura del archivo será la misma que para las otras clases discutidas anteriormente:


Más abajo mostramos la clase modificada .

//+------------------------------------------------------------------+
//|                                                  AccountInfo.mqh |
//|                   Copyright 2009-2016, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include <Object.mqh>
//+------------------------------------------------------------------+
//| Class CAccountInfo.                                              |
//| Designación: clase para el acceso a la información sobre la cuenta de usuario A                  |
//|              Derivada de la clase CObject.                         |
//+------------------------------------------------------------------+
/class CAccountInfo : public CObject
  {
public:
                     CAccountInfo(void);
                    ~CAccountInfo(void);
   //--- método de acceso rápido a las propiedades enteras de la cuenta de usuario
   long              Login(void) const;
   ENUM_ACCOUNT_TRADE_MODE TradeMode(void) const;
   string            TradeModeDescription(void) const;
   long              Leverage(void) const;
   ENUM_ACCOUNT_STOPOUT_MODE StopoutMode(void) const;
   string            StopoutModeDescription(void) const;
   //ENUM_ACCOUNT_MARGIN_MODE MarginMode(void) const;
   //string            MarginModeDescription(void) const;
   bool              TradeAllowed(void) const;
   bool              TradeExpert(void) const;
   int               LimitOrders(void) const;
   //--- método de acceso rápido a las propiedades enteras de la cuenta
   double            Balance(void) const;
   double            Credit(void) const;
   double            Profit(void) const;
   double            Equity(void) const;
   double            Margin(void) const;
   double            FreeMargin(void) const;
   double            MarginLevel(void) const;
   double            MarginCall(void) const;
   double            MarginStopOut(void) const;
   //--- método de acceso rápido a las propiedades de línea de la cuenta
   string            Name(void) const;
   string            Server(void) const;
   string            Currency(void) const;
   string            Company(void) const;
   //--- método de acceso a las funciones API MQL5
   long              InfoInteger(const ENUM_ACCOUNT_INFO_INTEGER prop_id) const;
   double            InfoDouble(const ENUM_ACCOUNT_INFO_DOUBLE prop_id) const;
   string            InfoString(const ENUM_ACCOUNT_INFO_STRING prop_id) const;
   //--- comprobando
   //double            OrderProfitCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation,
                                      //const double volume,const double price_open,const double price_close) const;
   //double            MarginCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation,
                                 //const double volume,const double price) const;
   //double            FreeMarginCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation,
                                     //const double volume,const double price) const;
   //double            MaxLotCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation,
                                 //const double price,const double percent=100) const;
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CAccountInfo::CAccountInfo(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CAccountInfo::~CAccountInfo(void)
  {
  }
//+------------------------------------------------------------------+
//| obtenemos el valor de la propiedad "ACCOUNT_LOGIN"                           |
//+------------------------------------------------------------------+
long CAccountInfo::Login(void) const
  {
   return(AccountInfoInteger(ACCOUNT_LOGIN));
  }
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_TRADE_MODE"                      |
//+------------------------------------------------------------------+
ENUM_ACCOUNT_TRADE_MODE CAccountInfo::TradeMode(void) const
  {
   return((ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE));
  }
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_TRADE_MODE" as string            |
//+------------------------------------------------------------------+
string CAccountInfo::TradeModeDescription(void) const
  {
   string str;
//--- 
   switch(TradeMode())
     {
      case ACCOUNT_TRADE_MODE_DEMO   : str="Demo trading account";    break;
      case ACCOUNT_TRADE_MODE_CONTEST: str="Contest trading account"; break;
      case ACCOUNT_TRADE_MODE_REAL   : str="Real trading account";    break;
      default                        : str="Unknown trade account";
     }
//--- 
   return(str);
  }
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_LEVERAGE"                        |
//+------------------------------------------------------------------+
long CAccountInfo::Leverage(void) const
  {
   return(AccountInfoInteger(ACCOUNT_LEVERAGE));
  }
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_MARGIN_SO_MODE"                  |
//+------------------------------------------------------------------+
ENUM_ACCOUNT_STOPOUT_MODE CAccountInfo::StopoutMode(void) const
  {
   return((ENUM_ACCOUNT_STOPOUT_MODE)AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE));
  }
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_MARGIN_SO_MODE" como línea        |
//+------------------------------------------------------------------+
string CAccountInfo::StopoutModeDescription(void) const
  {
   string str;
//--- 
   switch(StopoutMode())
     {
      case ACCOUNT_STOPOUT_MODE_PERCENT: str="Level is specified in percentage"; break;
      case ACCOUNT_STOPOUT_MODE_MONEY  : str="Level is specified in money";      break;
      default                          : str="Unknown stopout mode";
     }
//--- 
   return(str);
  }
/*
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_MARGIN_MODE"                     |
//+------------------------------------------------------------------+
ENUM_ACCOUNT_MARGIN_MODE CAccountInfo::MarginMode(void) const
  {
   return((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE));
  }
*/
/*
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_MARGIN_MODE" como línea           |
//+------------------------------------------------------------------+
string CAccountInfo::MarginModeDescription(void) const
  {
   string str;
//--- 
   switch(MarginMode())
     {
      case ACCOUNT_MARGIN_MODE_RETAIL_NETTING: str="Netting";  break;
      case ACCOUNT_MARGIN_MODE_EXCHANGE      : str="Exchange"; break;
      case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: str="Hedging";  break;
      default                                : str="Unknown margin mode";
     }
//--- 
   return(str);
  }
*/
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_TRADE_ALLOWED"                   |
//+------------------------------------------------------------------+
bool CAccountInfo::TradeAllowed(void) const
  {
   return((bool)AccountInfoInteger(ACCOUNT_TRADE_ALLOWED));
  }
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_TRADE_EXPERT"                    |
//+------------------------------------------------------------------+
bool CAccountInfo::TradeExpert(void) const
  {
   return((bool)AccountInfoInteger(ACCOUNT_TRADE_EXPERT));
  }
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_LIMIT_ORDERS"                    |
//+------------------------------------------------------------------+
int CAccountInfo::LimitOrders(void) const
  {
   return((int)AccountInfoInteger(ACCOUNT_LIMIT_ORDERS));
  }
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_BALANCE"                         |
//+------------------------------------------------------------------+
double CAccountInfo::Balance(void) const
  {
   return(AccountInfoDouble(ACCOUNT_BALANCE));
  }
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_CREDIT"                          |
//+------------------------------------------------------------------+
double CAccountInfo::Credit(void) const
  {
   return(AccountInfoDouble(ACCOUNT_CREDIT));
  }
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_PROFIT"                          |
//+------------------------------------------------------------------+
double CAccountInfo::Profit(void) const
  {
   return(AccountInfoDouble(ACCOUNT_PROFIT));
  }
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_EQUITY"                          |
//+------------------------------------------------------------------+
double CAccountInfo::Equity(void) const
  {
   return(AccountInfoDouble(ACCOUNT_EQUITY));
  }
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_MARGIN"                          |
//+------------------------------------------------------------------+
double CAccountInfo::Margin(void) const
  {
   return(AccountInfoDouble(ACCOUNT_MARGIN));
  }
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_FREEMARGIN"                      |
//+------------------------------------------------------------------+
double CAccountInfo::FreeMargin(void) const
  {
   return(AccountInfoDouble(ACCOUNT_FREEMARGIN));
  }
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_MARGIN_LEVEL"                    |
//+------------------------------------------------------------------+
double CAccountInfo::MarginLevel(void) const
  {
   return(AccountInfoDouble(ACCOUNT_MARGIN_LEVEL));
  }
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_MARGIN_SO_CALL"                  |
//+------------------------------------------------------------------+
double CAccountInfo::MarginCall(void) const
  {
   return(AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL));
  }
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_MARGIN_SO_SO"                    |
//+------------------------------------------------------------------+
double CAccountInfo::MarginStopOut(void) const
  {
   return(AccountInfoDouble(ACCOUNT_MARGIN_SO_SO));
  }
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_NAME"                            |
//+------------------------------------------------------------------+
string CAccountInfo::Name(void) const
  {
   return(AccountInfoString(ACCOUNT_NAME));
  }
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_SERVER"                          |
//+------------------------------------------------------------------+
string CAccountInfo::Server(void) const
  {
   return(AccountInfoString(ACCOUNT_SERVER));
  }
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_CURRENCY"                        |
//+------------------------------------------------------------------+
string CAccountInfo::Currency(void) const
  {
   return(AccountInfoString(ACCOUNT_CURRENCY));
  }
//+------------------------------------------------------------------+
//| Obtenemos el valor de la propiedad "ACCOUNT_COMPANY"                         |
//+------------------------------------------------------------------+
string CAccountInfo::Company(void) const
  {
   return(AccountInfoString(ACCOUNT_COMPANY));
  }
//+------------------------------------------------------------------+
//| Funciones de acceso AccountInfoInteger(...)                         |
//+------------------------------------------------------------------+
long CAccountInfo::InfoInteger(const ENUM_ACCOUNT_INFO_INTEGER prop_id) const
  {
   return(AccountInfoInteger(prop_id));
  }
//+------------------------------------------------------------------+
//| Funciones de acceso AccountInfoDouble(...)                          |
//+------------------------------------------------------------------+
double CAccountInfo::InfoDouble(const ENUM_ACCOUNT_INFO_DOUBLE prop_id) const
  {
   return(AccountInfoDouble(prop_id));
  }
//+------------------------------------------------------------------+
//| Funciones de acceso AccountInfoString(...)                          |
//+------------------------------------------------------------------+
string CAccountInfo::InfoString(const ENUM_ACCOUNT_INFO_STRING prop_id) const
  {
   return(AccountInfoString(prop_id));
  }
/*
//+------------------------------------------------------------------+
//| Funciones de acceso OrderCalcProfit(...).                            |
//| INPUT:  name            - nombre del símbolo,                           |
//|         trade_operation - operación comercial,                       |
//|         volume          - volumen de la posición abierta,        |
//|         price_open      - precio de apertura de la posición,         |
//|         price_close     - precio de cierre de la posición.         |
//+------------------------------------------------------------------+
double CAccountInfo::OrderProfitCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation,
                                      const double volume,const double price_open,const double price_close) const
  {
   double profit=EMPTY_VALUE;
//--- 
   if(!OrderCalcProfit(trade_operation,symbol,volume,price_open,price_close,profit))
      return(EMPTY_VALUE);
//--- 
   return(profit);
  }
*/
/*
//+------------------------------------------------------------------+
//| Funciones de acceso OrderCalcMargin(...).                           |
//| INPUT:  name            - nombre del símbolo,                           |
//|         trade_operation - operación comercial,                       |
//|         volume          - volumen de la posición abierta,        |
//|         price           - precio de apertura de la posición.         |
//+------------------------------------------------------------------+
double CAccountInfo::MarginCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation,
                                 const double volume,const double price) const
  {
   double margin=EMPTY_VALUE;
//--- 
   if(!OrderCalcMargin(trade_operation,symbol,volume,price,margin))
      return(EMPTY_VALUE);
//--- 
   return(margin);
  }
*/
/*
//+------------------------------------------------------------------+
//| Funciones de acceso OrderCalcMargin(...).                           |
//| INPUT:  name            - nombre del símbolo,                           |
//|         trade_operation - operación comercial,                       |
//|         volume          - volumen de la posición abierta,        |
//|         price           - precio de apertura de la posición.         |
//+------------------------------------------------------------------+
double CAccountInfo::FreeMarginCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation,
                                     const double volume,const double price) const
  {
   return(FreeMargin()-MarginCheck(symbol,trade_operation,volume,price));
  }
*/
/*
//+------------------------------------------------------------------+
//| Funciones de acceso OrderCalcMargin(...).                           |
//| INPUT:  name            - nombre del símbolo,                           |
//|         trade_operation - operación comercial,                       |
//|         price           - precio de apertura de la posición,         |
//|         percent         - margen libre [1-100%].  |
//+------------------------------------------------------------------+
double CAccountInfo::MaxLotCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation,
                                 const double price,const double percent) const
  {
   double margin=0.0;
//--- comprobando
   if(symbol=="" || price<=0.0 || percent<1 || percent>100)
     {
      Print("CAccountInfo::MaxLotCheck invalid parameters");
      return(0.0);
     }
//--- calculate margin requirements for 1 lot
   if(!OrderCalcMargin(trade_operation,symbol,1.0,price,margin) || margin<0.0)
     {
      Print("CAccountInfo::MaxLotCheck margin calculation failed");
      return(0.0);
     }
//--- 
   if(margin==0.0) // para las órdenes pendientes
      return(SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX));
//--- calculando volumen máximo
   double volume=NormalizeDouble(FreeMargin()*percent/100.0/margin,2);
//--- normalizando y comprobando límites
   double stepvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP);
   if(stepvol>0.0)
      volume=stepvol*MathFloor(volume/stepvol);
//--- 
   double minvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN);
   if(volume<minvol)
      volume=0.0;
//--- 
   double maxvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX);
   if(volume>maxvol)
      volume=maxvol;
//--- se retorna el volumen 
   return(volume);
  }
*/
//+------------------------------------------------------------------+


La plataforma puede obtener acceso simultáneo solo a una cuenta de usuario. De esta forma, ya no será necesario para nosotros crear una colección de ejemplares CAccountInfo.

CExpertTrade

A pesar de tener los mismos objetivos finales, MQL4 y MQL5 se diferencian en cuanto a la ejecución de operaciones comerciales. Esto significa que tenemos la posibilidad de usar la clase CExpertTrade en un asesor multiplataforma en la versión para MQL5, pero no se usará por completo en la versión MQL4. En este caso, para la versión MQL4 hemos creado un nuevo archivo de encabezamiento que continene una clase homónima. Seguimos guardando el archivo de encabezamiento básico de la clase de acuerdo con la siguiente estructura:

Esta clase emulará los miembros y métodos de la clase CExpertTrade, que se encuentra en la versión MQL5. De esta forma, con esta ejecución del código:

CExpertTrade trade;
trade.Buy(/*params*/);

ambas versiones tienen la posibilidad de "comprender" que tenemos la intención de entrar en una posición larga.

Las condiciones mínimas consistirán en emular los métodos para entrar en una posición. Son los métodos Buy y Sell en la clase CExpertTrade. Para salir del mercado, nuestras implementaciones divergen: será OrderClose para la versión MQL4, mientras que en MQL5 se usarán los métodos "nativos" PositionClose (con cobertura) o Buy y Sell (con compensación). El código que vemos abajo muestra la versión de la clase CExpertTrade para MQL4. Este es uno de los momentos en los que el código puede diferenciarse sustancialmente en la ejecución de programadores distintos:

#include <Arrays\ArrayInt.mqh>
enum ENUM_ORDER_TYPE_TIME
  {
   ORDER_TIME_GTC,
   ORDER_TIME_DAY,
   ORDER_TIME_SPECIFIED,
   ORDER_TIME_SPECIFIED_DAY
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CExpertTrade : public CObject
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
  {
protected:
   int               m_magic;
   ulong             m_deviation;
   ENUM_ORDER_TYPE_TIME m_order_type_time;
   datetime          m_order_expiration;
   bool              m_async_mode;
   uint              m_retry;
   int               m_sleep;
   color             m_color_long;
   color             m_color_short;
   color             m_color_buystop;
   color             m_color_buylimit;
   color             m_color_sellstop;
   color             m_color_selllimit;
   color             m_color_modify;
   color             m_color_exit;
   CSymbolInfo      *m_symbol;
public:
                     CExpertTrade(void);
                    ~CExpertTrade(void);
   //--- métodos de establecimiento y obtención
   color             ArrowColor(const ENUM_ORDER_TYPE type);
   uint              Retry() {return m_retry;}
   void              Retry(uint retry){m_retry=retry;}
   int               Sleep() {return m_sleep;}
   void              Sleep(int sleep){m_sleep=sleep;}
   void              SetAsyncMode(const bool mode) {m_async_mode=mode;}
   void              SetExpertMagicNumber(const int magic) {m_magic=magic;}
   void              SetDeviationInPoints(const ulong deviation) {m_deviation=deviation;}
   void              SetOrderExpiration(const datetime expire) {m_order_expiration=expire;}
   bool              SetSymbol(CSymbolInfo *);
   //-- métodos comerciales   
   virtual ulong     Buy(const double,const double,const double,const double,const string);
   virtual ulong     Sell(const double,const double,const double,const double,const string);
   virtual bool      OrderDelete(const ulong);
   virtual bool      OrderClose(const ulong,const double,const double);
   virtual bool      OrderCloseAll(CArrayInt *,const bool);
   virtual bool      OrderModify(const ulong,const double,const double,const double,const ENUM_ORDER_TYPE_TIME,const datetime,const double);
   virtual ulong     OrderOpen(const string,const ENUM_ORDER_TYPE,const double,const double,const double,const double,const double,const ENUM_ORDER_TYPE_TIME,const datetime,const string);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CExpertTrade::CExpertTrade(void) : m_magic(0),
                                   m_deviation(10),
                                   m_order_type_time(0),
                                   m_symbol(NULL),
                                   m_async_mode(0),
                                   m_retry(3),
                                   m_sleep(100),
                                   m_color_long(clrGreen),
                                   m_color_buystop(clrGreen),
                                   m_color_buylimit(clrGreen),
                                   m_color_sellstop(clrRed),
                                   m_color_selllimit(clrRed),
                                   m_color_short(clrRed),
                                   m_color_modify(clrNONE),
                                   m_color_exit(clrNONE)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CExpertTrade::~CExpertTrade(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CExpertTrade::SetSymbol(CSymbolInfo *symbol)
  {
   if(symbol!=NULL)
     {
      m_symbol=symbol;
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CExpertTrade::OrderModify(const ulong ticket,const double price,const double sl,const double tp,
                               const ENUM_ORDER_TYPE_TIME type_time,const datetime expiration,const double stoplimit=0.0)
  {
   return ::OrderModify((int)ticket,price,sl,tp,expiration,m_color_modify);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CExpertTrade::OrderDelete(const ulong ticket)
  {
   return ::OrderDelete((int)ticket);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
ulong CExpertTrade::Buy(const double volume,const double price,const double sl,const double tp,const string comment="")
  {
   if(m_symbol==NULL)
      return false;
   m_symbol.RefreshRates();
   string symbol=m_symbol.Name();
   double stops_level=m_symbol.StopsLevel()*m_symbol.Point();
   double ask=m_symbol.Ask();
   if(symbol=="")
      return 0;
   if(price!=0)
     {
      if(price>ask+stops_level)
         return OrderOpen(symbol,ORDER_TYPE_BUY_STOP,volume,0.0,price,sl,tp,m_order_type_time,m_order_expiration,comment);
      if(price<ask-stops_level)
         return OrderOpen(symbol,ORDER_TYPE_BUY_LIMIT,volume,0.0,price,sl,tp,m_order_type_time,m_order_expiration,comment);
     }
   return OrderOpen(symbol,ORDER_TYPE_BUY,volume,0.0,ask,sl,tp,m_order_type_time,m_order_expiration,comment);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
ulong CExpertTrade::Sell(const double volume,const double price,const double sl,const double tp,const string comment="")
  {
   if(m_symbol==NULL)
      return false;
   m_symbol.RefreshRates();
   string symbol=m_symbol.Name();
   double stops_level=m_symbol.StopsLevel()*m_symbol.Point();
   double bid=m_symbol.Bid();
   if(symbol=="")
      return 0;
   if(price!=0)
     {
      if(price>bid+stops_level)
         return OrderOpen(symbol,ORDER_TYPE_SELL_LIMIT,volume,0.0,price,sl,tp,m_order_type_time,m_order_expiration,comment);
      if(price<bid-stops_level)
         return OrderOpen(symbol,ORDER_TYPE_SELL_STOP,volume,0.0,price,sl,tp,m_order_type_time,m_order_expiration,comment);
     }
   return OrderOpen(symbol,ORDER_TYPE_SELL,volume,0.0,bid,sl,tp,m_order_type_time,m_order_expiration,comment);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
ulong CExpertTrade::OrderOpen(const string symbol,const ENUM_ORDER_TYPE order_type,const double volume,
                              const double limit_price,const double price,const double sl,const double tp,
                              const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC,const datetime expiration=0,
                              const string comment="")
  {
   bool res;
   ulong ticket=0;
   color arrowcolor=ArrowColor(order_type);
   datetime expire=0;
   if(order_type>1 && expiration>0) expire=expiration*1000+TimeCurrent();
   double stops_level=m_symbol.StopsLevel();
   for(uint i=0;i<m_retry;i++)
     {
      if(ticket>0)
         break;
      if(IsStopped())
         return 0;
      if(IsTradeContextBusy() || !IsConnected())
        {
         ::Sleep(m_sleep);
         continue;
        }
      if(stops_level==0 && order_type<=1)
        {
         ticket=::OrderSend(symbol,order_type,volume,price,(int)(m_deviation*m_symbol.Point()),0,0,comment,m_magic,expire,arrowcolor);
         ::Sleep(m_sleep);
         for(uint j=0;j<m_retry;j++)
           {
            if(res) break;
            if(ticket>0 && (sl>0 || tp>0))
              {
               if(OrderSelect((int)ticket,SELECT_BY_TICKET))
                 {
                  res=OrderModify((int)ticket,OrderOpenPrice(),sl,tp,OrderExpiration());
                  ::Sleep(m_sleep);
                 }
              }
           }
        }
      else
        {
         ticket=::OrderSend(symbol,order_type,volume,price,(int)m_deviation,sl,tp,comment,m_magic,expire,arrowcolor);
         ::Sleep(m_sleep);
        }
     }
   return ticket>0?ticket:0;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CExpertTrade::OrderClose(const ulong ticket,const double lotsize=0.0,const double price=0.0)
  {
   if(!OrderSelect((int)ticket,SELECT_BY_TICKET))
      return false;
   if(OrderCloseTime()>0)
      return true;
   double close_price=0.0;
   int deviation=0;
   if(OrderSymbol()==m_symbol.Name() && price>0.0)
     {
      close_price=NormalizeDouble(price,m_symbol.Digits());
      deviation=(int)(m_deviation*m_symbol.Point());
     }
   else
     {
      close_price=NormalizeDouble(OrderClosePrice(),(int)MarketInfo(OrderSymbol(),MODE_DIGITS));
      deviation=(int)(m_deviation*MarketInfo(OrderSymbol(),MODE_POINT));
     }
   double lots=(lotsize>0.0 || lotsize>OrderLots())?lotsize:OrderLots();
   return ::OrderClose((int)ticket,lots,close_price,deviation,m_color_exit);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CExpertTrade::OrderCloseAll(CArrayInt *other_magic,const bool restrict_symbol=true)
  {
   bool res=true;
   int total= OrdersTotal();
   for(int i=total-1;i>=0;i--)
     {
      double bid=0.0,ask=0.0;
      if(!OrderSelect(i,SELECT_BY_POS)) continue;
      if(OrderSymbol()!=m_symbol.Name() && restrict_symbol) continue;
      if(OrderMagicNumber()!=m_magic && other_magic.Search(OrderMagicNumber())<0) continue;
      m_symbol.RefreshRates();
      if(OrderSymbol()==m_symbol.Name())
        {
         bid = m_symbol.Bid();
         ask = m_symbol.Ask();
        }
      else
        {
         bid = MarketInfo(OrderSymbol(),MODE_BID);
         ask = MarketInfo(OrderSymbol(),MODE_ASK);
        }
      if(res) res=OrderClose(OrderTicket(),OrderLots(),OrderType()==ORDER_TYPE_BUY?bid:ask);
     }
   return res;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
color CExpertTrade::ArrowColor(const ENUM_ORDER_TYPE type)
  {
   switch(type)
     {
      case ORDER_TYPE_BUY:        return m_color_long;
      case ORDER_TYPE_SELL:       return m_color_short;
      case ORDER_TYPE_BUY_STOP:   return m_color_buystop;
      case ORDER_TYPE_BUY_LIMIT:  return m_color_buylimit;
      case ORDER_TYPE_SELL_STOP:  return m_color_sellstop;
      case ORDER_TYPE_SELL_LIMIT: return m_color_selllimit;
     }
   return clrNONE;
  }
//+------------------------------------------------------------------+

Preste atención a que en la versión MQL5, la clase CExpertTrade se hereda de la clase CTrade (que a su vez expande CObject). Por otra parte, en la versión MQL4, la clase CExpertTrade se hereda directamente de CObject. Esto significa que en la versión MQL4, las clases CTrade y CExpertTrade están unidas en un solo objeto.

Algunas funciones, tales como ORDER_TYPE_TIME, no tienen análogos directos en MQL4. No obstante, estas clases comerciales pueden resultar útiles en el caso de que se las amplíe y se emule el tiempo de expiración de una orden MQL5 en MQL4.

CTradeManager

En la clase CExpertTrade, que pertenece a MQL5, existe el método SetSymbol. Este método permite pasar el puntero a una información o símbolo de tal forma que indique a un ejemplar la información sobre otro símbolo (CSymbolInfo). Con la ayuda de este ajuste, la mayoría de los métodos que se encuentran en la clase ya no deberán tener el parámetro de línea de símbolo que designa el nombre del instrumento para el procesamiento.

En la mayoría de los casos, bastará con el uso normal de CExpertTrade y la conmutación normal de los símbolos. No obstante, el uso de este enfoque tiene algunas consideraciones a tener en cuenta. En ciertos casos, cuando se cambia el puntero a un símbolo, habrá que actualizar la desviación o el deslizamiento máximo. Esta consideración es válida, por ejemplo, cuando el experto comercie con instrumentos que tienen diferente número de dígitos tras la coma. Otro factor más es el número mágico, que se debe actualizar obligatoriamente si el asesor prefiere usar números mágicos distintos para las posiciones abiertas de cada símbolo usado.

Uno de los modos de lograr esto es usar un "gestor comercial". Al igual que el gestor de símbolos, analizado más arriba, este objeto ampliará la clase CArrayObj, y prácticamente tiene el mismo conjunto de métodos que CSymbolManager. El archivo de encabezamiento principal hará referencia a la clase derivada correcta, de acuerdo con el compilador utilizado. Al igual que el gestor de símbolos, el gestor comercial se ocupará del guardado y la extracción de datos. De esta forma, la mayor parte de su código se encontrará en el archivo de encabezamiento básico. La estructura de archivo se muestra en la figura de abajo.

El código de la clase básica de nuestro gestor comercial se muestra más abajo.

(TradeManagerBase.mqh)

#include <Arrays\ArrayObj.mqh>
#include "ExpertTradeXBase.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CTradeManagerBase : public CArrayObj
  {
public:
                     CTradeManagerBase(void);
                    ~CTradeManagerBase(void);
   virtual int       Search(string);
   virtual bool      Add(CExpertTradeX*);
   virtual void      Deinit(void);
   CExpertTrade     *Get(const string);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CTradeManagerBase::CTradeManagerBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CTradeManagerBase::~CTradeManagerBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CTradeManagerBase::Deinit(void)
  {
   Shutdown();
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CTradeManagerBase::Add(CExpertTradeX *node)
  {
   if(Search(node.Name())==-1)
      return CArrayObj::Add(node);
   return false;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CTradeManagerBase::Search(string symbol=NULL)
  {
   if(symbol==NULL)
      symbol= Symbol();
   for(int i=0;i<Total();i++)
     {
      CExpertTradeX *item=At(i);
      if(StringCompare(item.Name(),symbol)==0)
         return i;
     }
   return -1;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CExpertTrade *CTradeManagerBase::Get(const string name=NULL)
  {
   if(name==NULL && Total()>0)
      return At(0);
   for(int i=0;i<Total();i++)
     {
      CExpertTradeX *item=At(i);
      if(StringCompare(item.Name(),name)==0)
         return item;
     }
   return NULL;
  }
//+------------------------------------------------------------------+
#ifdef __MQL5__
#include "..\..\MQL5\Trade\TradeManager.mqh"
#else
#include "..\..\MQL4\Trade\TradeManager.mqh"
#endif
//+------------------------------------------------------------------+

Asimismo, podemos sencillamente usar un ejemplar de la clase CExpertTrade y ampliarlo de tal forma que tenga su propia colección de símbolos para conmutar. Pero este método ocupa un espacio de memoria adicional, puesto que será necesario también guardar los números mágicos y los cálculos de las desviaciones ligadas a determinados símbolos.

De forma semejante al gestor de símbolos, esta clase también utiliza el método de usuario Search para comparar dos instrumentos financieros (deberán ser únicos). La comparación se realiza usando los nombres de los instrumentos financieros, que a su vez se basan en los nombres de los ejemplares de la clase CSymbolInfo a los que hacen referencia. No obstante, para la versión de MQL5, el objeto no retorna ni el puntero al símbolo, ni el nombre del símbolo. En este caso, necesitamos expandir CExpertTrade, para permitir a los ejemplares de este objeto la posibilidad de retornar el nombre del símbolo que contiene. Le daremos al objeto el nombre CExpertTradeX. Al igual que todas las demás clases descritas en este artículo, tiene un archivo de encabezamiento básico que determina a cual de los archivos derivados de encabezamiento hacer referencia, dependiendo del compilador utilizado:

El fragmento que vemos a continuación muestra la implementación básica de la clase descrita más arriba:

(CExpertTradeXBase.mqh)

#include "ExpertTradeBase.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CExpertTradeXBase : public CExpertTrade
  {
public:
                     CExpertTradeXBase(void);
                    ~CExpertTradeXBase(void);
   string            Name(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CExpertTradeXBase::CExpertTradeXBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CExpertTradeXBase::~CExpertTradeXBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
string CExpertTradeXBase::Name(void)
  {
   if (CheckPointer(m_symbol))
      return m_symbol.Name();
   return NULL;
  }
//+------------------------------------------------------------------+
#ifdef __MQL5__
#include "..\..\MQL5\Trade\ExpertTradeX.mqh"
#else 
#include "..\..\MQL4\Trade\ExpertTradeX.mqh"
#endif
//+------------------------------------------------------------------+

Preste atención a que esta clase CExpertTradeX expande a la clase CExpertTrade. Podría parecer que este objeto se hereda de un mismo objeto. Sin embargo, realmente las versiones para MQL4 y MQL5 tienen diferentes versiones de CExpertTrade. Se trata de un objeto bastante sencillo, y sus métodos son compatibles con ambas plataformas, por eso, como sucede con algunas clases, declararemos sus clases específicas de plataforma sin métodos adicionales:

(CExpertTradeX.mqh, versión para MQL4 y MQL5)

class CExpertTradeX : public CExpertTradeXBase
  {
public:
                     CExpertTradeX(void);
                    ~CExpertTradeX(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CExpertTradeX::CExpertTradeX(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CExpertTradeX::~CExpertTradeX(void)
  {
  }
//+------------------------------------------------------------------+


Conclusión

En este artículo hemos mostrado cómo algunos componentes de la Biblioteca Estándar de MQL5 pueden ser transformados para su posible uso en asesores escritos para MQL4, lo que nos evita la necesidad de codificar estas clases desde cero para la versión MQL4. Las clases CSymbolInfo, CAccount y CExpertTrade han sido modificados, y los gestores de clases, tales como CSymbolManager y CTradeManager han sido desarrollados para poder permitirles controlar los numerosos ejemplares de uno de los objetos mencionados más arriba.