La implementación del modo multidivisa en MetaTrader 5

Konstantin Gruzdev | 4 abril, 2014


Introducción

En realidad, existe una gran cantidad de sistemas de trading, indicadores y Expert Advisors. Sin embargo, los desarrolladores todavía se enfrentan a los problemas concretos del desarrollo de sistemas multidivisa.

Con el lanzamiento del terminal de cliente MetaTrader 5 y el lenguaje de programación MQL5, hemos conseguido una nueva posibilidad de implementar un modo multidivisa completo y, por consiguiente, robots e indicadores multidivisa más eficientes. Estas nuevas posibilidades serán el tema de este artículo.


Descripción general de los métodos tradicionales

En nuestro caso, el método tradicional consiste en el intento de implementar un sistema multidivisa basado en las funciones comunes OnTick() y OnCalculate(), que han sustituido la función start() de MQL4. En pocas palabras, con la aparición de un nuevo tick o barra en el gráfico actual, se solicitan todos los pares de divisas (participando en el sistema multidivisa) de forma secuencial para su posterior análisis y toma de decisiones.

Los problemas de este método son los siguientes:

  1. La dependencia de todo el sistema de la llegada de ticks en un símbolo de trading del gráfico actual.

    Cuando los ticks en un símbolo del gráfico actual son frecuentes en un mercado rápido, realmente no hay ningún problema. Pero con un mercado lento, por ejemplo, por la noche, pueden haber muy pocos ticks: uno cada medio minuto o incluso menos. Durante los intervalos entre la llegada de los pocos ticks, todo el sistema multidivisa está en "reposo", aunque puedan ocurrir cambios muy frecuentes en los otros símbolos.

    Si el sistema está configurado para trabajar en períodos de tiempo largos, este defecto no es muy relevante. Pero cuanto más pequeño es el período de tiempo, más impacto tiene. El mundo va cada vez más rápido, los ordenadores son capaces de procesar cada vez más informaciones por unidad de tiempo y, por consiguiente, más gente tiende a trabajar con períodos más pequeños e incluso ticks.

  2. La complejidad de la sincronización del historial de los datos en todos los símbolos utilizados en un sistema multidivisa.

    "En MetaTrader 4, solo se dibujan las barras en las cuales ha ocurrido por lo menos un cambio de precio. Si no sucede ningún cambio de precio en el intervalo de un minuto, ocurrirá una brecha en el gráfico con un período de un minuto."; extracto de al introducción del artículo Gráficos "sin brechas".

    Se mantiene este enfoque de implementación de gráficos en MetaTrader 5. Es decir, el mismo número de barras en el gráfico para cada símbolo no quiere decir que están sincronizados en el tiempo. Por ejemplo, la barra número cien puede tener un tiempo de apertura distinto para cada símbolo. Por tanto, durante la construcción y el cálculo del indicador multidivisa, es importante asegurarse de que todas las barras son coherentes entre sí.

    Esto tampoco es muy relevante si el sistema está configurado para trabajar con un período de tiempo largo, ya que a medida que aumenta el tiempo, se reduce la posibilidad de que falten barras. Aunque, como dicen, todo cuidado es poco. Y nunca se sabe, salvo si no se hace nada.

    Debemos tener en cuenta el tiempo de sincronización de la barra incompleta actual por separado. La aparición una nueva barra en el gráfico, no significa que las barras nuevas se han formado también en los otros símbolos. Con lo cual, con el intento de averiguar el precio de una nueva barra a través de otro símbolo, mediante las funciones CopyXXXX(), se puede obtener el precio de la barra anterior del símbolo, o simplemente un error de copia. De hecho, una barra nueva en otro símbolo se puede formar mucho antes que la barra actual. Además, esto puede afectar la precisión de la evaluación de la situación.

    El artículo Crear un Indicador Multidivisa Usando un Número de Buffers de Indicador Intermediarios describe algunas opciones que resuelven más o menos el problema de la sincronización de los datos del historial.

  3. Otro punto importante relacionado con la sincronización de los datos: ¿cómo podemos averiguar que se ha actualizado el historial para algún símbolo de trading?

    Por ejemplo, al implementar un indicador de una sola divisa no hay ningún problema. Si la variable de entrada prev_calculated de la función OnCalculate() es igual a cero, volvemos a calcular el indicador. Pero, ¿qué hacemos si ha habido una actualización del historial para el símbolo, pero no en el gráfico actual? Esto puede ocurrir en cualquier momento y puede ser necesario volver a calcular el indicador multidivisa. La respuesta a esta pregunta es crucial.

Sin ni siquiera entrar en otros aspectos, podemos ver que estos tres ejemplos bastan para causar tantos problemas, que el código del EA o indicador multidivisa se hace muy extenso. Pero no se ha resuelto el problema en todo su alcance...


Una nueva esperanza con la función OnTimer()

La nueva capacidad del programa MQL para generar el evento Timer y el controlador estándar de evento OnTimer() da una esperanza para que surjan nuevos tipos de sistemas multidivisa. Esto se debe, por un lado, al hecho de que ahora el Expert Advisor o indicador multidivisa puede ser independiente de la llegada de ticks al símbolo del gráfico actual y, por otro lado, al hecho de que podamos observar el funcionamiento del EA a lo largo del tiempo. Pero...

Esto resuelve parcialmente los problemas descritos en el apartado 1 del capítulo anterior y, por supuesto, proporciona algunas ventajas. Pero al igual que cuando recibimos el evento NewTick, con la recepción del evento Timer, es necesario indagar de forma secuencial todos los pares de divisas para tratar de llevar a cabo el seguimiento de los cambios. A veces hay que hacer esto con bastante frecuencia, lo que aumenta de forma significativa el uso de los recursos de los programas de MQL5.

Los problemas planteados en los apartados 2 y 3, siguen con la misma dificultad. Además de esto, es necesario resolver los problemas específicos de la función OnTimer(). Por ejemplo, los problemas de inicialización y desinicialización del temporizador, su funcionamiento durante los fines de semana, etc.

Sin menospreciar las ventajas obvias del controlador de eventos OnTimer(), hay resaltar que todavía no permite la implementación de un modo multidivisa completo.


Las nuevas posibilidades de la función OnChartEvent()

Las limitaciones de las funciones ordinarias anteriores se deben a su restrictiva especialización: están diseñadas para controlar eventos predeterminados y concretos, no para sistemas multidivisa. 

Un elemento clave que puede salvar a los desarrolladores de sistemas multidivisa puede ser el controlador de eventos personalizados estándar OnChartEvent(). Permite al programador generar sus propios eventos según sus necesidades.

Por ejemplo, nadie nos puede impedir el uso de esta función para obtener los ticks de cualquier símbolo del gráfico actual. Lo único que tenemos que hacer es enviar un "espía" al gráfico con el símbolo correspondiente.

Con el peculiar término "espía", me refiero al siguiente indicador.

//+------------------------------------------------------------------+
//|                                                         iSpy.mq5 |
//|                                            Copyright 2010, Lizar |
//|                                               lizar-2010@mail.ru |
//+------------------------------------------------------------------+
#define VERSION         "1.00 Build 2 (26 Dec 2010)"

#property copyright   "Copyright 2010, Lizar"
#property link        "https://www.mql5.com/ru/users/Lizar"
#property version     VERSION
#property description "iSpy agent-indicator. If you want to get ticks, attach it to the chart"
#property indicator_chart_window

input long            chart_id=0;        // chart id
input ushort          custom_event_id=0; // event id

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,        // size of price[] array
                 const int prev_calculated,  // bars, calculated at the previous call
                 const int begin,            // starting index of data
                 const double& price[]       // array for the calculation
   )
  {
   double price_current=price[rates_total-1];

   //--- Initialization:
   if(prev_calculated==0) 
     { // Generate and send "Initialization" event
      EventChartCustom(chart_id,0,(long)_Period,price_current,_Symbol);
      return(rates_total);
     }
   
   // When the new tick, let's generate the "New tick" custom event
   // that can be processed by Expert Advisor or indicator
   EventChartCustom(chart_id,custom_event_id+1,(long)_Period,price_current,_Symbol);
   
   //--- return value of prev_calculated for next call
   return(rates_total);
  }

Podemos lanzar este "espía" dentro del gráfico del símbolo en cuestión, y luego controlar sus mensajes en el EA o el indicador mediante la función OnChartEvent(). Par descifrar correctamente los mensajes del espía", tenemos que interpretar los parámetros de esta función del siguiente modo:

Para mostrar el funcionamiento de varios "espías" al mismo tiempo, vamos a escribir un sencillo EA que llamamos exSpy.mq5 (la versión completa está disponible en el archivo):

//+------------------------------------------------------------------+
//|                                                        exSpy.mq5 |
//|                                            Copyright 2010, Lizar |
//|                            https://www.mql5.com/ru/users/Lizar |
//+------------------------------------------------------------------+
#define VERSION       "1.00 Build 1 (26 Dec 2010)"

#property copyright   "Copyright 2010, Lizar"
#property link        "https://www.mql5.com/ru/users/Lizar"
#property version     VERSION
#property description "The Expert Advisor shows the work of iSPY indicator"

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {   
   if(iCustom("GBPUSD",PERIOD_M1,"iSpy",ChartID(),0)==INVALID_HANDLE) 
      { Print("Error in setting of spy on GBPUSD"); return(true);}
   if(iCustom("EURUSD",PERIOD_M1,"iSpy",ChartID(),1)==INVALID_HANDLE) 
      { Print("Error in setting of spy on EURUSD"); return(true);}
   if(iCustom("USDJPY",PERIOD_M1,"iSpy",ChartID(),2)==INVALID_HANDLE) 
      { Print("Error in setting of spy on USDJPY"); return(true);}
      
   Print("Spys ok, waiting for events...");
   //---
   return(0);
  }
  
//+------------------------------------------------------------------+
//| The Standard event handler.                                      |
//| See MQL5 Reference for the details.                              |
//|                                                                  |
//| In this case it is used for decoding of spy messages, sent by    |
//| iSPY spy indicator                                               |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // event id:
                                     // if id-CHARTEVENT_CUSTOM=0-"initialization" event
                const long&   lparam, // chart period
                const double& dparam, // price
                const string& sparam  // symbol
               )
  {
   if(id>=CHARTEVENT_CUSTOM)      
     {
      Print(TimeToString(TimeCurrent(),TIME_SECONDS)," -> id=",
            id-CHARTEVENT_CUSTOM,":  ",sparam," ",
            EnumToString((ENUM_TIMEFRAMES)lparam)," price=",dparam);
     }
  }
//+------------------------------------------------------------------+

Lanzamos el Expert Advisor en cualquier gráfico.

Los resultados son los siguientes:


 

A partir del registro, se puede observar que obtenemos todos los ticks del símbolo en cuestión, así que el evento de "inicialización", que se recibirá sobre todo si hay una actualización o una carga del historial.

Vamos a resumir los resultados intermedios:

Básicamente, podríamos concluir el artículo en este punto, ya que hemos implementado con éxito un modo multidivisa mediante el terminal MetaTrader 5 y el lenguaje de programación MQL5. Pero esta es una implementación en "bruto". Por tanto, vamos a profundizar más en el tema.


La implementación de un modo multidivisa

Si usa el concepto anterior de implementación de un modo multidivisa en su formato original, empezará a tener problemas a partir de algún punto. El problema de este método es que no permite obtener todos los ticks para cada símbolo de trading, en el cual se está ejecutando el "espía".

Con un mercado rápido, en un segundo puede haber una cantidad de ticks en cada símbolo. Esto puede dar lugar a un "bloqueo" de la orden del evento. He aquí un mensaje de advertencia de la sección de Ayuda:

El terminal de cliente añade los eventos que van apareciendo a la cola de los eventos. De modo que lo eventos se procesan uno tras otro según el orden de su recepción. Hay una excepción para el evento NewTick. Si ya hay un evento en la cola o se está procesando el evento, el nuevo evento NewTick no se pone a la espera.

El tamaño de la cola de eventos está limitado. Si hay un desbordamiento de la cola, se eliminan los eventos antiguos sin ser procesados para permitir la recepción de nuevos eventos. Por tanto, se recomienda escribir controladores de eventos eficientes, y no se recomienda usar bucles infinitos (hay una excepción con el script que controla el evento Start solo).

El desbordamiento de la cola puede dar lugar a una pérdida importante de eventos para el indicador o EA multidivisa. Esto por un lado. Por otro lado, no siempre necesitamos ticks para todos los símbolos. A veces necesitamos obtener solo el evento "new bar" (barra nueva) en cualquier período de tiempo. O una cantidad de eventos "new bar" para distintos períodos de tiempo. Básicamente, nuestro "espía" no es adecuado para estos requisitos y su uso no es muy conveniente.

Hagámoslo genérico, para que nunca tengamos que volver a la cuestión de cómo obtener un evento basándose en el símbolo del EA o indicador multidivisa. Para ello, vamos a tomar como ejemplo la enumeración de los eventos ENUM_CHART_EVENT_SYMBOL a partir de la descripción de "MCM Control Panel" para Asesores Expertos e Indicadores Multidivisa:

enum ENUM_CHART_EVENT_SYMBOL
  {
   CHARTEVENT_INIT      =0,         // "Initialization" event
   CHARTEVENT_NO        =0,         // No events

   CHARTEVENT_NEWBAR_M1 =0x00000001, // "New bar" event on M1 chart
   CHARTEVENT_NEWBAR_M2 =0x00000002, // "New bar" event on M2 chart
   CHARTEVENT_NEWBAR_M3 =0x00000004, // "New bar" event on M3 chart
   CHARTEVENT_NEWBAR_M4 =0x00000008, // "New bar" event on M4 chart
   
   CHARTEVENT_NEWBAR_M5 =0x00000010, // "New bar" event on M5 chart
   CHARTEVENT_NEWBAR_M6 =0x00000020, // "New bar" event on M6 chart
   CHARTEVENT_NEWBAR_M10=0x00000040, // "New bar" event on M10 chart
   CHARTEVENT_NEWBAR_M12=0x00000080, // "New bar" event on M12 chart
   
   CHARTEVENT_NEWBAR_M15=0x00000100, // "New bar" event on M15 chart
   CHARTEVENT_NEWBAR_M20=0x00000200, // "New bar" event on M20 chart
   CHARTEVENT_NEWBAR_M30=0x00000400, // "New bar" event on M30 chart
   CHARTEVENT_NEWBAR_H1 =0x00000800, // "New bar" event on H1 chart
   
   CHARTEVENT_NEWBAR_H2 =0x00001000, // "New bar" event on H2 chart
   CHARTEVENT_NEWBAR_H3 =0x00002000, // "New bar" event on H3 chart
   CHARTEVENT_NEWBAR_H4 =0x00004000, // "New bar" event on H4 chart
   CHARTEVENT_NEWBAR_H6 =0x00008000, // "New bar" event on H6 chart
   
   CHARTEVENT_NEWBAR_H8 =0x00010000, // "New bar" event on H8 chart
   CHARTEVENT_NEWBAR_H12=0x00020000, // "New bar" event on H12 chart
   CHARTEVENT_NEWBAR_D1 =0x00040000, // "New bar" event on D1 chart
   CHARTEVENT_NEWBAR_W1 =0x00080000, // "New bar" event on W1 chart
     
   CHARTEVENT_NEWBAR_MN1=0x00100000, // "New bar" event on MN1 chart
   CHARTEVENT_TICK      =0x00200000, // "New tick" event
   
   CHARTEVENT_ALL       =0xFFFFFFFF, // All events
  };

De hecho, esta enumeración consiste en marcadores (flags) de los eventos del gráfico personalizado. Es el conjunto mínimo que puede necesitar el modo multidivisa en cuestión. Por supuesto, se puede complementar. La combinación de los marcadores determinará cuales son los eventos que se van a enviar desde el "espía".

Se pueden combinar los marcadores mediante la operación "OR". Por ejemplo, la combinación CHARTEVENT_NEWBAR_M1 | CHARTEVENT_NEWBAR_H1 quiere decir que vamos a enviar eventos "new bar" a partir del período de tiempo por horas y minutos con la ayuda del "espía". Estos marcadores serán los parámetros de entrada de nuestro indicador espía. Más adelante, los llamaremos "indicador agente".

De acuerdo con los nuevos conceptos, este será el código del indicador en sí:

//+------------------------------------------------------------------+
//|                                        Spy Control panel MCM.mq5 |
//|                                            Copyright 2010, Lizar |
//|                            https://www.mql5.com/en/users/Lizar |
//+------------------------------------------------------------------+
#define VERSION       "1.00 Build 3 (26 Dec 2010)"

#property copyright   "Copyright 2010, Lizar"
#property link        "https://www.mql5.com/en/users/Lizar"
#property version     VERSION
#property description "This is the MCM Control Panel agent-indicator."
#property description "Is launched on the required symbol on any time-frame"
#property description "and generates the custom NewBar event and/or NewTick"
#property description "for the chart which receives the event"

#property indicator_chart_window
  
input long                    chart_id;                 // identifier of the chart which receives the event
input ushort                  custom_event_id;          // event identifier  
input ENUM_CHART_EVENT_SYMBOL flag_event=CHARTEVENT_NO;// indicator, which determines the event type.

MqlDateTime time, prev_time;

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,       // size of the price[] array
                 const int prev_calculated, // bars processed at the previous call
                 const int begin,           // where the data begins
                 const double& price[]      // calculations array
   )
  {  
   double price_current=price[rates_total-1];

   TimeCurrent(time);
   
   if(prev_calculated==0)
     {
      EventCustom(CHARTEVENT_INIT,price_current);
      prev_time=time; 
      return(rates_total);
     }
   
//--- new tick
   if((flag_event & CHARTEVENT_TICK)!=0) EventCustom(CHARTEVENT_TICK,price_current);       

//--- check change time
   if(time.min==prev_time.min && 
      time.hour==prev_time.hour && 
      time.day==prev_time.day &&

      time.mon==prev_time.mon) return(rates_total);

//--- new minute
   if((flag_event & CHARTEVENT_NEWBAR_M1)!=0) EventCustom(CHARTEVENT_NEWBAR_M1,price_current);     
   if(time.min%2 ==0 && (flag_event & CHARTEVENT_NEWBAR_M2)!=0)  EventCustom(CHARTEVENT_NEWBAR_M2,price_current);
   if(time.min%3 ==0 && (flag_event & CHARTEVENT_NEWBAR_M3)!=0)  EventCustom(CHARTEVENT_NEWBAR_M3,price_current); 
   if(time.min%4 ==0 && (flag_event & CHARTEVENT_NEWBAR_M4)!=0)  EventCustom(CHARTEVENT_NEWBAR_M4,price_current);      
   if(time.min%5 ==0 && (flag_event & CHARTEVENT_NEWBAR_M5)!=0)  EventCustom(CHARTEVENT_NEWBAR_M5,price_current);     
   if(time.min%6 ==0 && (flag_event & CHARTEVENT_NEWBAR_M6)!=0)  EventCustom(CHARTEVENT_NEWBAR_M6,price_current);     
   if(time.min%10==0 && (flag_event & CHARTEVENT_NEWBAR_M10)!=0) EventCustom(CHARTEVENT_NEWBAR_M10,price_current);      
   if(time.min%12==0 && (flag_event & CHARTEVENT_NEWBAR_M12)!=0) EventCustom(CHARTEVENT_NEWBAR_M12,price_current);      
   if(time.min%15==0 && (flag_event & CHARTEVENT_NEWBAR_M15)!=0) EventCustom(CHARTEVENT_NEWBAR_M15,price_current);      
   if(time.min%20==0 && (flag_event & CHARTEVENT_NEWBAR_M20)!=0) EventCustom(CHARTEVENT_NEWBAR_M20,price_current);      
   if(time.min%30==0 && (flag_event & CHARTEVENT_NEWBAR_M30)!=0) EventCustom(CHARTEVENT_NEWBAR_M30,price_current);      
   if(time.min!=0) {prev_time=time; return(rates_total);}
//--- new hour
   if((flag_event & CHARTEVENT_NEWBAR_H1)!=0) EventCustom(CHARTEVENT_NEWBAR_H1,price_current);
   if(time.hour%2 ==0 && (flag_event & CHARTEVENT_NEWBAR_H2)!=0)  EventCustom(CHARTEVENT_NEWBAR_H2,price_current);
   if(time.hour%3 ==0 && (flag_event & CHARTEVENT_NEWBAR_H3)!=0)  EventCustom(CHARTEVENT_NEWBAR_H3,price_current);      
   if(time.hour%4 ==0 && (flag_event & CHARTEVENT_NEWBAR_H4)!=0)  EventCustom(CHARTEVENT_NEWBAR_H4,price_current);      
   if(time.hour%6 ==0 && (flag_event & CHARTEVENT_NEWBAR_H6)!=0)  EventCustom(CHARTEVENT_NEWBAR_H6,price_current);      
   if(time.hour%8 ==0 && (flag_event & CHARTEVENT_NEWBAR_H8)!=0)  EventCustom(CHARTEVENT_NEWBAR_H8,price_current);      
   if(time.hour%12==0 && (flag_event & CHARTEVENT_NEWBAR_H12)!=0) EventCustom(CHARTEVENT_NEWBAR_H12,price_current);      
   if(time.hour!=0) {prev_time=time; return(rates_total);}
//--- new day
   if((flag_event & CHARTEVENT_NEWBAR_D1)!=0) EventCustom(CHARTEVENT_NEWBAR_D1,price_current);      
//--- new week
   if(time.day_of_week==1 && (flag_event & CHARTEVENT_NEWBAR_W1)!=0) EventCustom(CHARTEVENT_NEWBAR_W1,price_current);      
//--- new month
   if(time.day==1 && (flag_event & CHARTEVENT_NEWBAR_MN1)!=0) EventCustom(CHARTEVENT_NEWBAR_MN1,price_current);      
   prev_time=time;
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

void EventCustom(ENUM_CHART_EVENT_SYMBOL event,double price)
  {
   EventChartCustom(chart_id,custom_event_id,(long)event,price,_Symbol);
   return;
  } 

Este indicador forma parte del MCM Control Panel, que lo hemos renombrado, los archivos adjuntos solo incluyen una versión actualizada del mismo (véase "Spy Control panel MCM.mq5”). Pero esto no quiere decir que no se puede utilizar por separado desde el panel.

Este indicador agente genera eventos de usuario personalizados y los envía al gráfico receptor para el procesamiento posterior de estos eventos en el EA o el indicador, mediante la función OnChartEvent(). Ahora, los parámetros de entrada de esta función deben interpretarse del siguiente modo:

 El EA de prueba no parece más complicado que el anterior (la versión completa está disponible en el archivo)

//+------------------------------------------------------------------+
//|                                      exSpy Control panel MCM.mq5 |
//|                                            Copyright 2010, Lizar |
//|                            https://www.mql5.com/en/users/Lizar |
//+------------------------------------------------------------------+
#define VERSION       "1.00 Build 1 (28 Dec 2010)"

#property copyright   "Copyright 2010, Lizar"
#property link        "https://www.mql5.com/en/users/Lizar"
#property version     VERSION
#property description "The EA demonstrates the work of the MCM Spy Control panel"

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {   
   if(iCustom("GBPUSD",PERIOD_M1,"Spy Control panel MCM",ChartID(),0,
             CHARTEVENT_NEWBAR_M1|CHARTEVENT_NEWBAR_M5)==INVALID_HANDLE) 
      { Print("Error in setting of spy on GBPUSD"); return(true);}
   if(iCustom("EURUSD",PERIOD_M1,"Spy Control panel MCM",ChartID(),1,
             CHARTEVENT_NEWBAR_M2)==INVALID_HANDLE) 
      { Print("Error in setting of spy on EURUSD"); return(true);}
   if(iCustom("USDJPY",PERIOD_M1,"Spy Control panel MCM",ChartID(),2,
             CHARTEVENT_NEWBAR_M6)==INVALID_HANDLE) 
      { Print("Error in setting of spy on USDJPY"); return(true);}
      
   Print("Spys ok, waiting for events...");
   //---
   return(0);
  }
  
//+------------------------------------------------------------------+
//| The Standard event handler.                                      |
//| See MQL5 Reference for the details.                              |
//|                                                                  |
//| In this case it is used for decoding of spy messages, sent by    |
//| iSPY Control panel MCM indicator.                                |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,           // event identifier
                  const long&   lparam, // the event flag.
                                       // event flag is a bit mask of ENUM_CHART_EVENT_SYMBOL enumeration.
                  const double& dparam, // price
                  const string& sparam  // symbol 
                 )
  {
   if(id>=CHARTEVENT_CUSTOM)      
     {
      Print(TimeToString(TimeCurrent(),TIME_SECONDS)," -> id=",id-CHARTEVENT_CUSTOM,
           ":  ",sparam," ",EnumToString((ENUM_CHART_EVENT_SYMBOL)lparam)," price=",dparam);
     }
  }

Los resultados del funcionamiento de exSpy Control panel MCM:


 

Como puede observar, recibimos periódicamente todos los eventos solicitados.

Una vez más, vamos a resumir los resultados intermedios:

Al final, la implementación de un modo multidivisa completo en MetaTrader 5 no es tan difícil.

Asimismo, me gustaría hacer hincapié en algunos matices.

En primer lugar, todos los eventos generados mediante los "agentes" para nuestro EA o indicador multidivisa son externos. En relación con esto, surge la pregunta: "¿Es necesario ejecutar los agentes directamente desde el EA o el indicador?" La respuesta es: "no."

En segundo lugar, en la función OnChartEvent(), el identificador de eventos id parece ser redundante, ya que podemos averiguar qué símbolo ha recibido el evento mediante el parámetro sparam; el nombre del símbolo de trading. Por tanto, ¿podemos tal vez usarlo para otra finalidad? La respuesta es: "sí, podemos." 

Estos argumentos han dado lugar a la aparición de "MCM Control Panel" para Asesores Expertos e Indicadores Multidivisa. Es una especie de "capa intermedia" entre el terminal y el EA o indicador. Esto nos proporciona aún más ventajas y flexibilidad en la configuración del entorno multidivisa:


El indicador RSI multidivisa para el índice dólar USDx

Para experimentar todas las ventajas del método anterior, propongo implementar la variante multidivisa del indicador RSI para el índice dólar USDx mediante MCM Control Panel.

Para empezar, quiero señalar algunas características especiales. Normalmente, cuando tratamos de analizar el índice dólar, solo calculamos los indicadores de las lecturas del índice. Desde mi punto de vista, esto no es del todo cierto, ya que cada símbolo del índice de los pares de divisas aporta sus propias contribuciones. Por tanto, como ejemplo, vamos a calcular el RSI del índice dólar mediante la fórmula similar a la del cálculo del índice:

Es decir, primero vamos a calcular el RSI para un par de divisas concreto, y luego leer el RSI del índice, teniendo en cuenta el peso de los coeficientes.

Los lectores pueden observar que existe un problema con la sincronización de los datos del historial de todos los símbolos que utiliza el sistema multidivisa, (véase el apartado 2 de la sección "Descripción general de los métodos tradicionales")

Se ha resuelto este problema en el indicador, mediante las funciones de la clase para la construcción de los buffers sincronizados de RSI (SynchronizedBufferRSI.mqh file). No tiene sentido proporcionar todo el código de la clase, con lo cual vamos a describir las partes más relevantes a continuación. 

En primer lugar, se define el buffer del indicador dentro de la clase con el modificador de acceso público:

public:
   double   buffer[];   // indicator buffer

En segundo lugar, se lleva a cabo la inicialización mediante el método de la clase:

//--- Initialization methods:
bool Init(int n,string symbol,int rsi_count,int rsi_period);

Y en tercer lugar, para cada barra, se sincroniza el valor del buffer del indicador con el período de tiempo actual, mediante el método de actualización de la clase:

//+------------------------------------------------------------------+
//| The method of receiving/updating indicator data for one bar      |
//| of the indicator buffer.                                         |
//| INPUT:  bar   - bar number                                       |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CSynchronizedBufferRSI::Refresh(int bar=0)
  {
   buffer[bar]=EMPTY_VALUE; // Initialization of the bar of the indicator buffer.
     
   //--- Inquire the time of the bar for the current graph:
   datetime time[1];      
   if(CopyTime(_Symbol,_Period,bar,1,time)!=1) return; // In case of an error, we wait for the next tick/bar...

   //--- Request the value of the indicator for the symbol for the time,
   //--- consistent with that of the bar of the current graph:
   double value[1];
   if(CopyBuffer(m_handle,0,time[0],time[0],value)!=1) return; // In case of an error, wait for the next tick/bar...

   buffer[bar]=value[0];
   return;
  }

Para una sincronización completa de todos los buffers de indicadores, tenemos que utilizar todo un período de tiempo por minutos sin "brechas", como se describe en el siguiente artículo. Pero para este método de sincronización de los buffers de indicadores, hemos elegido específicamente el período de tiempo del gráfico actual, ya que el indicador se muestra en el mismo.

Por mi experiencia propia, puedo decir que el uso de este método de sincronización tiene sentido con los períodos pequeños, para cualquier serie temporal o buffer de indicador, si su símbolo es distinto del símbolo del gráfico actual.

El gráfico muestra claramente por qué vale la pena hacerlo:


 

Para períodos de tiempo mayores, por lo general esto no se contempla.

Y por último, pero no menos importante. Tenemos a continuación, el código de un controlador de evento de usuario ordinario, que se usa en el indicador:

//+------------------------------------------------------------------+
//| The Standard event handler.                                      |
//| See MQL5 Reference for the details.                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // event identifier or position symbol in the "Market Match"+CHARTEVENT_CUSTOM  
                const long& lparam,   // event indicator
                const double& dparam, // price
                const string& sparam  // symbol
                )
  {
   int custom_id=id-CHARTEVENT_CUSTOM-1;
   
   if(custom_id>=0)      
     {
      if(lparam!=CHARTEVENT_NEWBAR_NO)
        { 
         //--- Recalculation of the last uncompleted bar:
         if(EventToPeriod(lparam)==_Period && sparam==_Symbol)
           { // Recalculation of the indicator, if a new bar on the current chart
            iRSIUSDx_Ind[0]=EMPTY_VALUE;
            //--- Updating the value of the RSI for all of the currency pairs for the new bar
            for(int i=0;i<symbol_total;i++) buffer[i].Refresh();
            iRSIUSDx(symbol_total);   // calculation of the current incomplete bar RSI for the index
            return;
           }
         
         buffer[custom_id].Refresh(); // The value of RSI for the custom_id of the currency pair for the current bar
         iRSIUSDx(symbol_total);      // calculation of the RSI for the current(uncompleted) bar RSIx
         return;
        }
      else 
        { 
         //--- Recalculation of the indicator for the "Initialization" event 
         buffer[custom_id].RefreshBuffer();     // Update of the RSI buffer for the custom_id of the currency pair
         Init_iRSIUSDx(symbol_total,calculate); // Update of the RSI buffer for the index
         return;
        }
     }
  }

Características del código:

Después de analizar todo el código del indicador RSI para el índice dólar USDx, su funcionamiento será mucho más claro.

Características de la instalación:


Conclusión

La implementación que hemos visto del modo multidivisa completo en MetaTrader 5 pone de manifiesto las ventajas de la plataforma y el lenguaje de programación MQL5 en la resolución de este problema. La mayoría de las dificultades encontradas anteriormente están resueltas ahora.

Obviamente, esto es solo el principio de un desarrollo en esta dirección. Sin duda, surgirán más y mejores opciones para la sincronización de los datos, la gestión de los modos multidivisa, etc. Espero que haya quedado claro que ahora disponemos de todas las herramientas necesarias para ello.