Conectar indicadores personalizados como recursos

Para su funcionamiento, los programas MQL pueden requerir uno o varios indicadores personalizados. Todos ellos pueden incluirse como recursos en el ejecutable ex5, lo que facilita su distribución e instalación.

La directiva #resource con la descripción del indicador anidado tiene el siguiente formato:

#resource "path_indicator_name.ex5"

Las reglas de configuración y búsqueda del archivo especificado son las mismas que para todos los recursos en general.

Ya hemos utilizado esta función en el ejemplo de Asesor Experto, en la versión final de UnityMartingale.mq5.

#resource "\\Indicators\\MQL5Book\\p6\\UnityPercentEvent.ex5"

En ese Asesor Experto, en lugar del nombre del indicador, este recurso se pasó a la función iCustom: «::Indicators\\MQL5Book\\p6\\UnityPercentEvent.ex5».

El caso en que un indicador personalizado en la función OnInit crea una o más instancias de sí mismo requiere una consideración aparte (si esta solución técnica en sí parece extraña, daremos un ejemplo práctico después de los ejemplos introductorios).

Como sabemos, para utilizar un recurso desde un programa MQL, debe especificarse de la siguiente forma: ruta_nombre_archivo.ex5::nombre_del_recurso. Por ejemplo, si el indicador EmbeddedIndicator.ex5 se incluye como recurso en otro indicador MainIndicator.mq5 (más concretamente, en su imagen binaria MainIndicator.ex5), entonces el nombre especificado al llamarse a sí mismo a través de iCustom ya no puede ser corto, sin una ruta, y la ruta debe incluir la ubicación del indicador «padre» dentro de la carpeta MQL5. De lo contrario, el sistema no podrá encontrar el indicador anidado.

De hecho, en circunstancias normales, un indicador puede llamarse a sí mismo utilizando, por ejemplo, el operador iCustom(_Symbol, _Period, myself,...), donde myself es una cadena igual a MQLInfoString(MQL_PROGRAM_NAME) o al nombre que se asignó previamente a la propiedad INDICATOR_SHORTNAME en el código. Pero cuando el indicador se encuentra dentro de otro programa MQL como recurso, el nombre ya no hace referencia al archivo correspondiente porque el archivo que servía de prototipo para el recurso se quedó en el ordenador donde se realizó la compilación, y en el ordenador del usuario sólo existe el archivo MainIndicator.ex5. Esto requerirá un cierto análisis del entorno del programa al iniciar el programa.

Veámoslo en la práctica.

Para empezar, vamos a crear un indicador NonEmbeddedIndicator.mq5. Es importante señalar que se encuentra en la carpeta MQL5/Indicators/MQL5Book/p7/SubFolder/, es decir, en una SubFolder relativa a la carpeta p7 asignada para todos los indicadores de esta parte del libro. Esto se hace intencionadamente para emular una situación en la que el archivo compilado no está presente en el ordenador del usuario. Ahora veremos cómo funciona (o mejor dicho, demuestra el problema).

El indicador tiene un único parámetro de entrada Reference. Su propósito es contar el número de copias de sí mismo: cuando se crea por primera vez, el parámetro es igual a 0, y el indicador creará su propia copia con el valor de parámetro de 1. La segunda copia, después de «ver» el valor 1, ya no creará otra copia (de lo contrario nos quedaríamos rápidamente sin recursos sin la condición límite para detener la reproducción).

input int Reference = 0;

La variable handle está reservada para el manejador del indicador de copia.

int handle = 0;

En el manejador OnInit, para mayor claridad, primero mostramos el nombre y la ruta del programa MQL.

int OnInit()
{
   const string name = MQLInfoString(MQL_PROGRAM_NAME);
   const string path = MQLInfoString(MQL_PROGRAM_PATH);
   Print(Reference);
   Print("Name: " + name);
   Print("Full path: " + path);
   ...

A continuación viene el código adecuado para el auto-lanzamiento de un indicador independiente (existente en forma del conocido archivo NonEmbeddedIndicator.ex5).

   if(Reference == 0)
   {
      handle = iCustom(_Symbol_Periodname1);
      if(handle == INVALID_HANDLE)
      {
         return INIT_FAILED;
      }
   }
   Print("Success");
   return INIT_SUCCEEDED;
}

Podríamos colocar con éxito un indicador de este tipo en el gráfico y recibir entradas del tipo siguiente en el registro (usted tendrá sus propias rutas del sistema de archivos):

0

Name: NonEmbeddedIndicator

Full path: C:\Program Files\MT5East\MQL5\Indicators\MQL5Book\p7\SubFolder\NonEmbeddedIndicator.ex5

Success

1

Name: NonEmbeddedIndicator

Full path: C:\Program Files\MT5East\MQL5\Indicators\MQL5Book\p7\SubFolder\NonEmbeddedIndicator.ex5

Success

La copia se ha iniciado correctamente con sólo utilizar el nombre «NonEmbeddedIndicator».

Dejemos este indicador por ahora y creemos un segundo, FaultyIndicator.mq5, en el que incluiremos el primer indicador como recurso (preste atención a la especificación de subfolder en la ruta relativa del recurso; esto es necesario porque el indicador FaultyIndicator.mq5 se encuentra en la carpeta de un nivel superior: MQL5/Indicators/MQL5Book/p7/).

// FaultyIndicator.mq5
#resource "SubFolder\\NonEmbeddedIndicator.ex5"
   
int handle;
   
int OnInit()
{
   handle = iCustom(_Symbol_Period"::SubFolder\\NonEmbeddedIndicator.ex5");
   if(handle == INVALID_HANDLE)
   {
      return INIT_FAILED;
   }
   return INIT_SUCCEEDED;
}

Si intenta ejecutar el archivo compilado FaultyIndicator.ex5 se producirá un error:

0

Name: NonEmbeddedIndicator

Full path: C:\Program Files\MT5East\MQL5\Indicators\MQL5Book\p7\FaultyIndicator.ex5 »

» ::SubFolder\NonEmbeddedIndicator.ex5

cannot load custom indicator 'NonEmbeddedIndicator' [4802]

Cuando se lanza una copia de un indicador anidado, se busca en la carpeta del indicador principal, en la que se describe el recurso. Pero no hay ningún archivo NonEmbeddedIndicator.ex5 porque el recurso requerido está dentro de FaultyIndicator.ex5.

Para resolver el problema, modificamos NonEmbeddedIndicator.mq5. En primer lugar, démosle otro nombre más apropiado, EmbeddedIndicator.mq5. En el código fuente tenemos que añadir una función de ayuda GetMQL5Path, que puede aislar la parte relativa dentro de la carpeta MQL5 de la ruta general del programa MQL lanzado (esta parte también contendrá el nombre del recurso si el indicador se lanza desde un recurso).

// EmbeddedIndicator.mq5
string GetMQL5Path()
{
   static const string MQL5 = "\\MQL5\\";
   static const int length = StringLen(MQL5) - 1;
   static const string path = MQLInfoString(MQL_PROGRAM_PATH);
   const int start = StringFind(pathMQL5);
   if(start != -1)
   {
      return StringSubstr(pathstart + length);
   }
   return path;
}

Teniendo en cuenta la nueva función, cambiaremos la llamada a iCustom en el manejador OnInit.

int OnInit()
{
   ...
   const string location = GetMQL5Path();
   Print("Location in MQL5:" + location);
   if(Reference == 0)
   {
      handle = iCustom(_Symbol_Periodlocation1);
      if(handle == INVALID_HANDLE)
      {
         return INIT_FAILED;
      }
   }
   return INIT_SUCCEEDED;
}

Asegurémonos de que esta edición no rompe el lanzamiento del indicador. La superposición en un gráfico hace que aparezcan las líneas esperadas en el registro:

0

Name: EmbeddedIndicator

Full path: C:\Program Files\MT5East\MQL5\Indicators\MQL5Book\p7\SubFolder\EmbeddedIndicator.ex5

Location in MQL5:\Indicators\MQL5Book\p7\SubFolder\EmbeddedIndicator.ex5

Success

1

Name: EmbeddedIndicator

Full path: C:\Program Files\MT5East\MQL5\Indicators\MQL5Book\p7\SubFolder\EmbeddedIndicator.ex5

Location in MQL5:\Indicators\MQL5Book\p7\SubFolder\EmbeddedIndicator.ex5

Success

Aquí añadimos la salida de depuración de la ruta relativa que recibió la función GetMQL5Path. Esta línea se utiliza ahora en iCustom, y funciona de este modo: se ha creado una copia.

Ahora vamos a incrustar este indicador como un recurso en otro indicador en la carpeta MQL5Book/p7 con el nombre MainIndicator.mq5. MainIndicator.mq5 es completamente idéntico a FaultyIndicator.mq5 excepto por el recurso conectado.

// MainIndicator.mq5
#resource "SubFolder\\EmbeddedIndicator.ex5"
...
int OnInit()
{
   handle = iCustom(_Symbol_Period"::SubFolder\\EmbeddedIndicator.ex5");
   ...
}

Vamos a compilarlo y ejecutarlo. Las entradas aparecen en el registro con una nueva ruta relativa que incluye el recurso anidado.

0

Name: EmbeddedIndicator

Full path: C:\Program Files\MT5East\MQL5\Indicators\MQL5Book\p7\MainIndicator.ex5 »

» ::SubFolder\EmbeddedIndicator.ex5

Location in MQL5:\Indicators\MQL5Book\p7\MainIndicator.ex5::SubFolder\EmbeddedIndicator.ex5

Success

1

Name: EmbeddedIndicator

Full path: C:\Program Files\MT5East\MQL5\Indicators\MQL5Book\p7\MainIndicator.ex5 »

» ::SubFolder\EmbeddedIndicator.ex5

Location in MQL5:\Indicators\MQL5Book\p7\MainIndicator.ex5::SubFolder\EmbeddedIndicator.ex5

Success

Como podemos ver, esta vez el indicador anidado ha creado correctamente una copia de sí mismo, ya que ha utilizado un nombre cualificado con una ruta relativa y un nombre de recurso «\Indicators\MQL5Book\p7\MainIndicator.ex5::SubFolder\EmbeddedIndicator.ex5».

Durante múltiples experimentos con el lanzamiento de este indicador, tenga en cuenta que las copias anidadas no se descargan inmediatamente del gráfico después de que se elimine el indicador principal. Por lo tanto, los reinicios deben realizarse sólo después de haber esperado a que se produzca la descarga: de lo contrario, se reutilizarán las copias que aún se estén ejecutando, y las líneas de inicialización anteriores no aparecerán en el registro. Para controlar la descarga, se ha añadido una impresión del valor Reference al manejador OnDeinit.

Prometimos demostrar que crear una copia del indicador no es algo extraordinario. Como demostración aplicada de esta técnica, utilizamos el indicador DeltaPrice.mq5, que calcula la diferencia en incrementos de precio de una orden determinada. Orden 0 significa sin diferenciación (solo para comprobar la serie temporal original), 1 significa diferenciación simple, 2 significa diferenciación doble, y así sucesivamente.

El orden se especifica en el parámetro de entrada Differentiating.

input int Differencing = 1;

Las series de diferencias se mostrarán en un único búfer en la subventana.

#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots   1
   
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrDodgerBlue
#property indicator_width1 2
#property indicator_style1 STYLE_SOLID
   
double Buffer[];

En el manejador OnInit, configuramos el búfer y creamos el mismo indicador, pasando el valor reducido en 1 en el parámetro de entrada.

#include <MQL5Book/AppliedTo.mqh// APPLIED_TO_STR macro
 
int handle = 0;
   
int OnInit()
{
   const string label = "DeltaPrice (" + (string)Differencing + "/"
      + APPLIED_TO_STR() + ")";
   IndicatorSetString(INDICATOR_SHORTNAMElabel);
   PlotIndexSetString(0PLOT_LABELlabel);
   
   SetIndexBuffer(0Buffer);
   if(Differencing > 1)
   {
      handle = iCustom(_Symbol_PeriodGetMQL5Path(), Differencing - 1);
      if(handle == INVALID_HANDLE)
      {
         return INIT_FAILED;
      }
   }
   return INIT_SUCCEEDED;
}

Para evitar posibles problemas con la incrustación del indicador como recurso, utilizamos la función ya probada GetMQL5Path.

En la función OnCalculate realizamos la operación de restar valores vecinos de la serie temporal. Cuando Differentiating es igual a 1, los operandos son elementos del array price. Con un valor mayor de Differentiating, leemos el búfer de la copia del indicador creada para la orden anterior.

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
{
   for(int i = fmax(prev_calculated - 11); i < rates_total; ++i)
   {
      if(Differencing > 1)
      {
         static double value[2];
         CopyBuffer(handle0rates_total - i - 12value);
         Buffer[i] = value[1] - value[0];
      }
      else if(Differencing == 1)
      {
         Buffer[i] = price[i] - price[i - 1];
      }
      else
      {
         Buffer[i] = price[i];
      }
   }
   return rates_total;
}

El tipo inicial de precio diferenciado se establece en el cuadro de diálogo de configuración del indicador, en la lista desplegable Apply to. De manera predeterminada, se trata del precio Close.

Así es como se ven varias copias del indicador en el gráfico con diferentes órdenes de diferenciación.

Diferencia de precio Close de diferentes órdenes

Diferencia en los precios de cierre de distintas órdenes