English Русский 中文 Deutsch 日本語 Português
Asesor experto multiplataforma: Introducción

Asesor experto multiplataforma: Introducción

MetaTrader 5Integración | 24 agosto 2016, 13:38
1 911 0
Enrico Lambino
Enrico Lambino

Índice


Introducción

Entre las causas para crear un asesor experto multiplataforma para los terminales MetaTrader, podemos enumerar las siguientes:

  • Usted tiene intención de compartir su asesor con otros tráders, independientemente de la versión de la plataforma que usen.
  • Usted quiere definir claramente la diferencia entre MQL4 y MQL5.
  • Usted quiere ahorrar tiempo a la hora de crear un código.
  • Usted quiere evitar problemas de migración en sus robots comerciales en MetaTrader5, por si MetaTrader 4 dejara de repente de tener soporte.
  • Usted ya usa MetaTrader 5, pero por algún motivo quiere poner a prueba su asesor en MetaTrader 4.
  • Uted sigue trabajando con MetaTrader 4, pero quiere utilizar MQL5 Cloud Service para simular y optimizar sus robots comerciales.

Cuando un desarrollador crea un asesor experto, un indicador o incluso un script, normalmente respeta la siguiente secuencia de acciones:

  1. Desarrolla un programa usando un lenguaje (MQL4 o MQL5)

  2. Lo pone minuciosamente a prueba

  3. Implementa el mismo programa en otro lenguaje

Los defectos de este esquema son los siguientes:

  1. Es necesario implementar de nuevo todas las partes del robot comercial, incluyendo las funciones idénticas para las dos versiones

  2. Pueden surgir complicaciones al depurar el programa y al acompañarlo posteriormente

  3. Reduce la productividad

Si escribimos programas por separado, la implementación paralela aumenta casi el doble el volumen de código necesario: un programa para MQL4, y otro para MQL5. En lo que respecta a las complejidades en la depuración y el posterior mantenimiento de tales asesores, los problemas residen en lo siguiente: si una de las versiones necesita que se introduzcan cambios, las mismas actualizaciones deberán introducirse en otra versión. Además, en virtud de las diferencias entre MQL4 y MQL5, las dos versiones de un mismo programa en determinado momento pueden tener divergencias. Potencialmente esto traerá multitud de problemas, puesto que las divergencias en el código no suelen ser obvias al usar implementaciones por separado.


Ejemplo del asesor Hello World

Vamos a comenzar por un sencillo experto, escrito para MQL5: el asesor Hello World. Más abajo mostramos un ejemplo típico de un asesor de este tipo en la versión de MQL recordada más arriba.

HelloWorld.mq5

#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CObject
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(void)
  {
   Print("Hello World!");
  }

CHelloWorld hello;
//+------------------------------------------------------------------+
//| función de inicialización del asesor                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//--- 
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| función de desinicialización del asesor                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 

  }
//+------------------------------------------------------------------+
//| Función de tick del experto                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 
   hello.Greeting();
   ExpertRemove();
  }

En MQL4 escribiremos un código análogo conforme al mismo método.

(HelloWorld.mq4)

#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CObject
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }

CHelloWorld hello;
//+------------------------------------------------------------------+
//| función de inicialización del asesor                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//--- 
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| función de desinicialización del asesor                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 

  }
//+------------------------------------------------------------------+
//| Función de tick del experto                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 
   hello.Greeting();
   ExpertRemove();
  }


Archivos fuente y archivos de encabezamiento

Preste atención: los dos archivos fuente mostrados más abajo son iguales. Es imposible tener un archivo fuente que funcione en las dos plataformas. El motivo de ello se encuentra en la compilación de los archivos fuente:

  • el resultado de la compilación en MQ4 es la creación de un archivo EX4.
  • el resultado de la compilación en MQ5 es la creación de un archivo EX5.

En otras palabras, como ya hemos dicho más arriba, no podemos obtener un archivo fuente que funcione en ambas plataformas. No obstante, podemos hacer que las dos fuentes remitan a un único archivo de encabezamiento, como ilustramos en la imagen de abajo:

Archivos fuente y archivos de encabezamiento


Lo ideal sería que lo consiguiésemos todo en un único archivo de encabezamiento con dos archivos fuente que contuvieran solo una línea: la inclusión del archivo de encabezamiento. Para el experto Hello World, mostrado más arriba, podemos reescribir el archivo de encabezamiento de la siguiente forma:

HelloWorld_SingleHeader.mqh

#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CObject
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(void);
   virtual void      Greeting(const string str1,const string str2);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(void)
  {
   Print("Hello World!");
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(const string str1,const string str2)
  {
   string str=NULL;
   Print(StringConcatenate(str,str1,str2));
  }
CHelloWorld hello;
//+------------------------------------------------------------------+
//| función de inicialización del asesor                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//--- 
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| función de desinicialización del asesor                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 

  }
//+------------------------------------------------------------------+
//| Función de tick del experto                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 
   hello.Greeting("Hello ","World!");
   ExpertRemove();
  }
//+------------------------------------------------------------------+


Los archivos fuente en MQL4 y MQL5 contienen un línea de código, la directiva #include, que remite al archivo de encabezamiento:

(HelloWorld_SingleHeader.mq4 y HelloWorld_SingleHeader.mq5)

#include <HelloWorld_SingleHeader.mqh>

El uso de este enfoque tiene una serie de ventajas. En primer lugar, podemos acortar potencialmente la cantidad de código fuente escrito para las dos plataformas hasta un 50% (por lo menos para el ejemplo dado). La segunda ventaja de este enfoque, es que nos permite trabajar solo en una implementación, y no en dos por separado. Puesto que solo tenemos una fuente para trabajar, los cambios que se hagan en la versión MQL4 se aplicarán también en MQL5, y al revés.

Recordemos que en el enfoque habitual, los cambios introducidos en el archivo fuente se debían introducir por separado también en la fuente para la otra versión de la plataforma.

Pero los asesores rara vez se escriben con tanta facilidad como en el ejemplo que he puesto. Es mucho más complicado. Y cuanto más complicado sea un experto, más complejo será dar soprte a sus dos versiones por separado.


Compilación condicional

MQL4 y MQL5 tienen mucho en común, pero también tienen muchas diferencias. Entre estas diferencias está la implementación de la función StringConcatenate. En MQL4 la función se define de la forma siguiente:

string  StringConcatenate( 
   void argument1,        // primer parámetro de cualquier tipo simple
   void argument2,        // segundo parámetro de cualquier tipo simple 
   ...                    // siguiente parámetro de cualquier tipo simple 
   );

En MQL5 su implementación es un poco distinta:

int  StringConcatenate( 
   string&  string_var,   // línea que se formará 
   void argument1,        // primer parámetro de cualquier tipo simple
   void argument2,        // segundo parámetro de cualquier tipo simple 
   ...                    // siguiente parámetro de cualquier tipo simple 
   );

Podemos usar esta función en Hello World al sobrecargar el método Greeting() en nuestra clase. El nuevo método adoptará dos argumentos de línea, cuyo resultado concatenado se imprimirá en el terminal. Actualizamos nuestro archivo de encabezamiento:

(HelloWorld_SingleHeader.mqh)

#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CObject
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(void);
   virtual void      Greeting(const string str1,const string str2);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(void)
  {
   Print("Hello World!");
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(const string str1,const string str2)
  {
   string str=NULL;
   Print(StringConcatenate(str,str1,str2));
  }
CHelloWorld hello;
//+------------------------------------------------------------------+
//| función de inicialización del asesor                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//--- 
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| función de desinicialización del asesor                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 

  }
//+------------------------------------------------------------------+
//| Función de tick del experto                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 
   hello.Greeting("Hello ","World!");
   ExpertRemove();
  }
//+------------------------------------------------------------------+


Usando esta versión actualizada veremos el siguiente resultado imprimido en el terminal MetaTrader 4:

Hello World!

En MetaTrader 5 el resultado se diferenciará de lo que se esperaba inicialmente:

12

En MQL4 la función retorna el texto concatenado en forma de línea. En lugar de esto, en MQL5 se retorna un valor entero que representa el tamaño de la línea concatenada. Para que el asesor demuestre el mismo comportamiento en las dos plataformas sin que sea necesario reescribir la mayor parte del código, podemos usar la compilación condicional, como se muestra en el fragmento de código que vemos más abajo.

CHelloWorld::Greeting(const string str1,const string str2)
  {
   #ifdef __MQL5__
      string str=NULL;
      StringConcatenate(str,str1,str2);
      Print(str);
   #else
      Print(StringConcatenate(str1,str2));
   #endif
  }

Preste atención a que se trata de una directiva del preprocesador. Aquí puede darse un aumento adicional del tiempo de compilación, pero no del tiempo de ejecución. En MQL4, el compilador interpretará el código indicado más arriba de la siguiente forma:

CHelloWorld::Greeting(const string str1,const string str2)
  {
      Print(StringConcatenate(str1,str2));
}

El compilador MQL5 lee este código así:

CHelloWorld::Greeting(const string str1,const string str2)
  {
      string str=NULL;
      StringConcatenate(str,str1,str2);
      Print(str);
}


Implementando la separación de clases

En este momento ya podemos comprender qué tipos de código nos ayudarán a crear un asesor multiplataforma.

  1. Funciones compatibles
    • Funciones generales
    • Cálculos
  2. Funciones incompatibles
    • Funciones que operan de forma diferente
    • Funciones disponibles en una versión, pero no en la otra
    • Diferentes métodos de ejecución

En MQL4 y MQL5 hay multitud de funciones que se comportan de forma idéntica en ambos lenguajes. Uno de los ejemplos de tales funciones es Print(). Funciona de la misma forma, independientemente de la versión de la plataforma en la que haya sido iniciado el experto. Se puede ver también un ejemplo de código fuente compatible en forma de cálculos habituales. El resultado 1+1 será idéntico en MQL4 y en MQL5, igual que en cualquier otro lenguaje de programación del mundo real. En ambas situaciones, la implementación de la separación de clases es extremadamente rara.

Pero en los casos en los que una parte determinada del código fuente o bien no se compila, o bien se ejecuta de otra forma en versiones diferentes de la plataforma, la implementación de la división nos será necesaria. Ejemplo del primer caso de código incompatible: la función StringConcatenate. A pesar de tener el mismo nombre, esta función se comporta de forma diferente en MQL4 y en MQL5. Asimismo, hay ciertas funciones que no tienen análogo directo en otro lenguaje. Por ejemplo, la función OrderCalcMargin function, que (por lo menos en el momento en que escribo este artículo) no tiene equivalentes en MQL4. El tercer caso es probablemente el más complejo para el desarrollo multiplataforma, puesto que precisamente aquí la implementación puede variar para diferentes desarrolladores. En este caso, para reducir el volumen del código puede ser necesario encontrar un demoninador común entre las dos versiones de la plataforma, y después implementar la separación de las clases a medida que se haga necesario.

En este caso, no sería mala idea ponerse por completo en manos de la compilación condicional. Puesto que con el tiempo el código se hace más largo, la presencia de una gran cantidad de instrucciones semejantes complica sobremanera la depuración y el posterior mantenimiento de los programas. En la programación orientada a objetos, podemos necesitar dividir la implementación en tres partes: (1) implementación básica, (2) implementación específica de MQL4 (3) e implementación específica de MQL5.

La implementación de la clase básica contendrá código al que se da soporte en ambas versiones. En los casos en los que aparecen incompatibilidades, hay que apartarse de la implementación básica o dejarla vacía, y utilizar después implementaciones separadas para ambos lenguajes.

Para nuestro asesor Hello World, nosotros anunciaremos la clase básica y le daremos el nombre CHelloWorldBase. En este se contiene código común para MQL4 y MQL5. Incluye el método fuente Greeting(), que ya hemos definido al principio de este artículo:

HelloWorld_SingleHeader.mqh

#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorldBase : public CObject
  {
public:
                     CHelloWorldBase(void);
                    ~CHelloWorldBase(void);
   virtual void      Greeting(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::CHelloWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::~CHelloWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::Greeting(void)
  {
   Print("Hello World!");
  }
//+------------------------------------------------------------------+


A continuación, creamos los objetos de la clase específica para la plataforma o lenguaje, que han sido heredados de la clase básica, e introducimos diferentes variantes de implementación para alcanzar este u otro resultado deseado.

(HelloWorld_SingleHeader_MQL4.mqh)

#include "HelloWorld_SingleHeader.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CHelloWorldBase
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(const string str1,const string str2);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(const string str1,const string str2)
  {
   Print(StringConcatenate(str1,str2));
  }
//+------------------------------------------------------------------+


HelloWorld_SingleHeader_MQL5.mqh

#include "HelloWorld_SingleHeader.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CHelloWorldBase
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(const string str1,const string str2);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(const string str1,const string str2)
  {
   string str=NULL;
   StringConcatenate(str,str1,str2);
   Print(str);
  }
//+------------------------------------------------------------------+


Después desplazamos las funciones de manejo de eventos hacia atrás, donde normalmente se encuentran (en los límites del archivo fuente principal).

HelloWorld_SingleHeader.mq5

#include <HelloWorld_SingleHeader_MQL5.mqh>
CHelloWorld hello;
//+------------------------------------------------------------------+
//| función de inicialización del asesor                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//--- 
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| función de desinicialización del asesor                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 

  }
//+------------------------------------------------------------------+
//| Función de tick del experto                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 
   hello.Greeting("Hello ","World!");
   ExpertRemove();
  }
//+------------------------------------------------------------------+

HelloWorld_SingleHeader.mq4

#include <HelloWorld_SingleHeader_MQL4.mqh>
CHelloWorld hello;
//+------------------------------------------------------------------+
//| función de inicialización del asesor                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//--- 
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| función de desinicialización del asesor                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 

  }
//+------------------------------------------------------------------+
//| Función de tick del experto                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 
   hello.Greeting("Hello ","World!");
   ExpertRemove();
  }
//+------------------------------------------------------------------+

En este ejemplo en concreto, es más práctico utilizar un archivo de encabezamiento que contenga la clase básica y dos clases derivadas en los límites de la directiva de la compilación condicional. Sin embargo, en la mayoría de los casos es imprescindible reubicar las clases en archivos por separado, en especial si los códigos fuente implicados son de grandes dimensiones.


Cómo incluir archivos

El comportamiento natural para los desarrolladores es sencillamente remitir al archivo de encabezamiento que contiene la definición de las clases que se usarán en el programa. Por ejemplo, en la implementación MQL5 del asesor HelloWorld, podemos ver que las dos versiones (HelloWorld_SingleHeader.mq4 y HelloWorld_SingleHeader.mq5) son prácticamente iguales, excepto por el archivo de encabezamiento específico que contienen.

#include <HelloWorld_SingleHeader_MQL4.mqh>
#include <HelloWorld_SingleHeader_MQL5.mqh>
Otro enfoque sería remitir a un archivo de encabezamiento que contenga la implementación básica. Entonces, al final de este archivo de encabezamiento podemos usar la directiva de la compilación condicional para remitir al archivo de encabezamiento que contiene la clase derivada correspondiente, dependiendo del compilador utilizado.
#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorldBase : public CObject
  {
public:
                     CHelloWorldBase(void);
                    ~CHelloWorldBase(void);
   virtual void      Greeting(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::CHelloWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::~CHelloWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::Greeting(void)
  {
   Print("Hello World!");
  }
//+------------------------------------------------------------------+
#ifdef __MQL5__
   #include "HelloWorld_SingleHeader_MQL5.mqh"
#else
   #include "HelloWorld_SingleHeader_MQL4.mqh"
#endif
//+------------------------------------------------------------------+

A continuación, en el archivo fuente principal remitimos precisamente a este archivo de encabezamiento, y no al específico para un lenguaje determinado.

#include <HelloWorld_SingleHeader.mqh>
CHelloWorld hello;
//+------------------------------------------------------------------+
//| función de inicialización del asesor                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//--- 
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| función de desinicialización del asesor                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 

  }
//+------------------------------------------------------------------+
//| Función de tick del experto                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 
   hello.Greeting("Hello ","World!");
   ExpertRemove();
  }
//+------------------------------------------------------------------+

Después de ello, eliminamos la directiva #include para los archivos de encabezamiento específicos para los lenguajes (las líneas tachadas muestran los fragmentos de código eliminados).

#include "HelloWorld_SingleHeader.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CHelloWorldBase
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(const string str1,const string str2);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(const string str1,const string str2)
  {
   Print(StringConcatenate(str1,str2));
  }
//+------------------------------------------------------------------+

#include "HelloWorld_SingleHeader.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CHelloWorldBase
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(const string str1,const string str2);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(const string str1,const string str2)
  {
   string str=NULL;
   StringConcatenate(str,str1,str2);
   Print(str);
  }
//+------------------------------------------------------------------+


Este enfoque es recomendable y tiene varias ventajas. En primer lugar, guarda intactas las directivas #include para ambos archivos fuente (MQL4 y MQL5). Asimismo, esto le evitará tener que elegir qué archivo de encabezamiento y qué ruta (por ejemplo, MQL4/ o MQL5/) incluir en esta directiva del procesador. El tercer punto positivo es que este método guarda las inclusiones básicas en los archivos de encabezamiento básicos. Si usamos las directivas de inclusión en los archivos de encabezamiento específicos del lenguaje, se usarán exclusivamente para las versiones correspondientes del lenguaje (MQL4 o MQL5).

Separando archivos y carpetas

Al desarrollar un asesor comercial en la programación orientada a objetos es prácticamente imposible escribir todo el código definiendo solo una clase. Una de las pruebas de ello son las clases de las estrategias comerciales en la Biblioteca estándar de MQL5. A medida que crece el número de líneas de código, sería más práctico dividir el mismo en varios archivos de encabezamiento. En este artículo se recomienda el siguiente formato de carpetas:

/Include

/Base

/MQL4

/MQL5

Las tres carpetas pueden ser ubicadas directamente dentro del directorio Include, o en una subcarpeta dentro de él.

Para nuestro ejemplo de código, tomaremos la siguiente estructura de carpetas:

/Include

/MQLx-Intro

/Base

HelloWorldBase.mqh

/MQL4

HelloWorld.mqh

/MQL5

HelloWorld.mqh

El uso de una estructura de este tipo nos permite organizar mejor nuestro código. Asimismo, esto puede evitarnos el problema derivado de los conflictos al asignar los nombres de los archivos, que ya intentábamos evitar antes.

Debido al cambio de ubicación de las carpetas de nuestros archivos de encabezamiento, necesitamos actualizar el archivo de encabezamiento principal para la clase con la nueva ubicación de sus dos descendientes:

#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorldBase : public CObject
  {
public:
                     CHelloWorldBase(void);
                    ~CHelloWorldBase(void);
   virtual void      Greeting(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::CHelloWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::~CHelloWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::Greeting(void)
  {
   Print("Hello World!");
  }
//+------------------------------------------------------------------+
#ifdef __MQL5__
   #include "..\MQL5\HelloWorld.mqh"
#else
   #include "..\MQL4\HelloWorld.mqh"
#endif
//+------------------------------------------------------------------+

Asimismo, actualizamos el archivo fuente con el cambio de ubicación para nuestra clase básica. Para este momento los archivos fuente de ambas versiones ya son idénticos:

HelloWorld_Sample.mq4 and HelloWorld_Sample.mq5

#include <MQLx-Intro\Base\HelloWorldBase.mqh>
CHelloWorld hello;
//+------------------------------------------------------------------+
//| función de inicialización del asesor                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//--- 
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| función de desinicialización del asesor                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 

  }
//+------------------------------------------------------------------+
//| Función de tick del experto                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 
   hello.Greeting("Hello ","World!");
   ExpertRemove();
  }
//+------------------------------------------------------------------+



Herencia

Supongamos que quisiéramos ampliar la clase CHelloWorld, que hemos definido antes, a una clase con el nombre CGoodByeWorld. En esta clase, para crear un mensaje con el texto "Goodbye World!" se usará el método Greeting() de CHelloWorld. Uno de los métodos (que yo recomiendo) para hacer esto, es remitir a la clase padre básica, de la que actúa CHelloWorldBase. Después procedemos de forma análoga con CHelloWorldBase, al final de este archivo se incluye la directiva del preprocesador de la compilación condicional, que remite a la clase derivada correcta. La jerarquía de la herencia tendrá el aspecto siguiente:

Jerarquía de la herencia

No obstante, el método de inclusión de los archivos de encabezamiento se distinguirá un poco.

Estructura de la inclusión

Incluimos los archivos de encabezamiento solo para la clase básica. En este caso, cuando se usa solo la jerarquía de una clase, incluimos solo el archivo de encabezamiento de la clase básica con la mayor abstracción (GoodByeWorldBase), puesto que el enlace a este archivo incluye automáticamente otros archivos de encabezamiento. Preste atención a que no utilizamos #include para remitir a archivos de encabezamiento específicos de una plataforma determinada, puesto que incluirlos será responsabilidad de los archivos de encabezamientos básicos.

Igualmente se actualizará nuestra estructura de carpetas, que como resultado ya contendrá nuevos archivos de encabezamiento:

/Include

/MQLx-Intro

/Base

HelloWorldBase.mqh

GoodByeWorldBase.mqh

/MQL4

HelloWorld.mqh

GoodByeWorld.mqh

/MQL5

HelloWorld.mqh

GoodByeWorld.mqh


Más abajo mostramos la implementación de la clase CGoodByeWorldBase:

#include "HelloWorldBase.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CGoodByeWorldBase : public CHelloWorld
  {
public:
                     CGoodByeWorldBase(void);
                    ~CGoodByeWorldBase(void);
   virtual void      GoodBye(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CGoodByeWorldBase::CGoodByeWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CGoodByeWorldBase::~CGoodByeWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CGoodByeWorldBase::GoodBye(void)
  {
   Greeting("Goodbye ","World!");
  }
//+------------------------------------------------------------------+
#ifdef __MQL5__
   #include "..\MQL5\GoodByeWorld.mqh"
#else
   #include "..\MQL4\GoodByeWorld.mqh"
#endif
//+------------------------------------------------------------------+

Nótese que incluso si el archivo incluye "HelloWorldBase.mqh", la clase CGoodByeWorldBase se hereda de CHelloWorld, y no de CHelloWorldBase. Entonces, la versión de CHelloWorld que se iniciará depende, a fin de cuentas, de la versión del compilador MQL que se utilice. En el caso contrario, funcionará la clase CHelloWorldBase expandida. No obstante, en este ejemplo, dado que en el método Goodbye() se usa el método Greeting(), CGoodByeWorldBase deberá heredarse directamente de la implementación de CHelloWorld especifica para la plataforma.

Puesto que el método GoodBye() es común para las dos versiones, es ideal contenerlo en la implementación básica. Como para el objeto de nuestra clase ya no existen otros métodos adicionales, sus descendientes tampoco tendrán nuevos métodos de clase. Ahora podemos implementar el descendiente de la siguiente forma:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CGoodByeWorld : public CGoodByeWorldBase
  {
  };
//+------------------------------------------------------------------+

El archivo fuente principal también deberemos actualizarlo, creando esta vez un objeto heredado de CGoodByeWorld y que al mismo tiempo llame a GoodBye() dentro del manejador OnTick.

(HelloWorld_Sample.mq4 and HelloWorld_Sample.mq5)

#include <MQLx-Intro\Base\GoodByeWorldBase.mqh>
CGoodByeWorld hello;
//+------------------------------------------------------------------+
//| función de inicialización del asesor                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//--- 
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| función de desinicialización del asesor                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 

  }
//+------------------------------------------------------------------+
//| Función de tick del experto                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 
   hello.Greeting("Hello ","World!");
   hello.GoodBye();
   ExpertRemove();
  }
//+------------------------------------------------------------------+


Después del inicio de las versiones de nuestro asesor, en el terminal aparecerán los siguientes resultados:

Hello World!
Goodbye World!
ExpertRemove() function called

Limitaciones

En la mayoría de los casos, este enfoque permite al programador acelerar y facilitar de forma productiva el desarrollo de asesores multiplataforma. No obstante, me gustaría advertir al lector acerca de ciertas limitaciones que pueden convertir en algo difícil o incluso imposible el uso del método descrito en este artículo.

1. Limitaciones para MetaTrader 4.

En MetaTrader 4, como en la plataforma más antigua, no existen determinadas funciones que sí hay en MetaTrader 5. En los casos cuando se necesita para el funcionamiento del asesor una función que no existe en una de las plataformas, es imprescindible desarrollar una solución personalizada exclusivamente para la otra versión. En general, es un problema de los asesores nativos de MetaTrader 5, para los que se necesita un análogo en MetaTrader 4. Los usuarios de Metatrader 4 experimentan menos problemas en este sentido. Y es que la mayoría de las funciones de MetaTrader 4, si no todas, tienen análogos o por lo menos soluciones alternativas sencillas en Metatrader 5.

2. Direferencias significativas en la ejecución o convenciones entre las dos plataformas. 

Las dos plataformas se diferencian considerablemente en ciertas operaciones. En mayor medida, esto se refiere a las operaciones comerciales. En estas situaciones, el desarrollador elegirá qué convenciones adoptar. Por ejemplo, puede utilizar las convenciones de MetaTrader 4 y pasarlas a MetaTrader 5, para alcanzar el mismo comportamiento final. O - en caso contrario - habrá que adoptar el enfoque comercial acostumbrado para MetaTrader 5 con respecto a los asesores de MetaTrader 4.

Conclusión

En este artículo hemos mostrado un método con cuya ayuda es posible desarrollar un asesor experto multiplataforma. El método descrito propone usar las clases básicas que contienen implementaciones comunes para ambas plataformas comerciales. En los lugares donde los dos lenguajes se diferencian el uno del otro, se introduce una implementación aparte en forma de clases heredadas de esta clase básica. El mismo método se repite para las clases que deben ser definidas a continuación en la jerarquía. Este método puede demostrar su utilidad al desarrollar aplicaciones multiplataforma con un menor gasto de tiempo. El mantenimiento del código, además, se simplifica: ya no hay que realizar implementaciones por separado, de forma paralela.

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/2569

Archivos adjuntos |
MQLx-Intro.zip (69.5 KB)
Interfaces gráficas IX: Elementos "Indicador de progreso" y "Gráfico lineal" (Capítulo 2) Interfaces gráficas IX: Elementos "Indicador de progreso" y "Gráfico lineal" (Capítulo 2)
El segundo capítulo de la novena parte de la serie estará dedicada a los elementos «Indicador de progreso» y «Gráfico lineal». Como siempre mostraremos los ejemplos detallados de cómo puede usar estos elementos en sus aplicaciones MQL.
Estudiamos la clase CCanvas. Suavizado y sombras Estudiamos la clase CCanvas. Suavizado y sombras
El algoritmo de suavizado de la clase CCanvas es la base de todas las construcciones en las que se usa el suavizado. En el artículo se cuenta cómo funciona este algoritmo y se muestran ejemplos visuales de su funcionamiento. Además, se analizará el dibujado de las sombras de los objetos gráficos y se desarrollará un algoritmo detallado del dibujado de la sombra en el elemento canvas. Para los cálculos se ha utilizado la biblioteca de análisis numérico ALGLIB.
Interfaces gráficas X: Actualizaciones para la librería Easy And Fast (build 2) Interfaces gráficas X: Actualizaciones para la librería Easy And Fast (build 2)
Desde la anterior publicación del artículo de esta serie, la librería Easy And Fast ha adquirido nuevas posibilidades. Ha sido hecha la optimización parcial del esquema y del código de la librería, eso ha reducido un poco el consumo de recursos de la CPU. Algunos métodos que se repiten con frecuencia en muchas clases de los controles han sido traspasados a la clase base CElement.
Interfaces gráficas IX: Control "Paleta para seleccionar el color" (Capítulo 1) Interfaces gráficas IX: Control "Paleta para seleccionar el color" (Capítulo 1)
Con este artículo se abre la novena parte de la serie sobre el desarrollo de la librería para la creación de las interfaces gráficas en el entorno de los terminales de trading MetaTrader. Se compone de dos partes en las que se muestran nuevos controles y elementos de la interfaz: «Paleta para seleccionar el color», «Botón para abrir la paleta de colores», «Indicador de progreso» y «Gráfico lineal».