English Русский 中文 Deutsch 日本語 Português
preview
Símbolo personalizados: fundamentos de uso en la práctica

Símbolo personalizados: fundamentos de uso en la práctica

MetaTrader 5Probador | 22 diciembre 2020, 08:22
1 180 0
Stanislav Korotky
Stanislav Korotky

En MetaTrader 5, existe ahora la posibilidad de crear los llamados símbolos personalizados o de usuario con cotizaciones y ticks propios. Podemos acceder a ellos tanto desde la interfaz del terminal, como a nivel programático a través de la MQL API. Los símbolos personalizados se representan en los gráficos estándar, permiten utilizar indicadores en estos, realizar el marcado con objetos e incluso construir estrategias comerciales basadas en ellos.

Los datos fuente pueden ser tanto las cotizaciones de los símbolos de trabajo suministradas por el bróker, como fuentes externas. En este artículo, analizaremos varios métodos populares para transformar los símbolos de trabajo que ofrecen herramientas analíticas adicionales para los tráders:

  • gráficos de igual volumen y rango,
  • gráficos de ticks,
  • desplazamiento temporal de las cotizaciones con conversión de las figuras de velas,
  • renko.

Asimismo, desarrollaremos un mecanismo para adaptar los asesores expertos al comercio con el símbolo real que se encuentra asociado con el símbolo derivado personalizado en cuyo gráfico se está ejecutando el asesor.

En este artículo, los gráficos de los símbolos de origen (estándar) usan un fondo negro y los gráficos de los símbolos personalizados usan un fondo blanco.

Gráficos de igual volumen/rango (equal volume / range charts)

Un gráfico de equivolumen (igual volumen) es un gráfico de barras construido según el principio de igualdad del volumen incluido en ellas. En un gráfico normal, cada nueva barra se forma con la periodicidad establecida, que coincide con el tamaño del marco temporal. En un gráfico de equivolumen, cada barra se considera formada cuando la suma de los ticks o los volúmenes reales alcanzan el valor preestablecido. En ese momento, el programa comienza a calcular la suma para la barra siguiente. Obviamente, en el proceso de cálculo de los volúmenes, se controlan los movimientos de los precios, por lo que obtenemos los cuatro precios habituales en el gráfico: open, high, low, close.

Aunque el eje horizontal en el gráfico de equivolumen todavía indica cronología, las marcas de tiempo para cada barra son arbitrarias y dependen de la volatilidad (del número o el tamaño de las transacciones) en cada periodo temporal. Muchos tráders consideran que este método de formación de barras resulta mejor para describir un mercado cambiante que un valor de marco temporal constante.

Por desgracia, ni MetaTrader 4 ni MetaTrader 5 ofrecen gráficos de equivolumen, por así decirlo, listos para usar. Debemos generarlos de una forma especial.

Para MetaTrader 4, podemos conseguirlo con ayuda de los llamados gráficos offline. Esto método fue descrito en el artículo "Una nueva mirada al gráfico Equivolume".

Para MetaTrader 5, implementaremos el mismo algoritmo aplicando los símbolos personalizados. Para facilitar la tarea, tomaremos el experto no comercial del artículo indicado y lo adaptaremos a MQL API MetaTrader 5.

El archivo original EqualVolumeBars.mq4 ha sido renombrado como EqualVolumeBars.mq5 y ligeramente modificado. Concretamente, las palabras clave extern, que describen los parámetros de entrada, han sido sustituidas por input. En lugar de los dos parámetros StartYear y StartMonth, hemos comenzado a utilizar uno: StartDate. El parámetro CustomPeriod usado en MetaTrader 4 para establecer un marco temporal no estándar no lo necesitamos ahora y, por consiguiente, lo hemos eliminado.

Debemos destacar que los volúmenes de MetaTrader 4 son todos volúmenes de ticks, es decir, muestran el número de ticks (cambios de precio) en una barra. La idea original consistía en procesar las barras M1 (con sus volúmenes de ticks) o un archivo csv externo con los ticks ofrecidos por otro bróker, calcular los ticks entrantes por unidad de tiempo y formar una nueva barra de equivolumen en cuanto se alcanzara el número de ticks indicado. Las barras se escribían en un archivo hst que se podía abrir en MetaTrader 4 como un gráfico offline.

Para MetaTrader 5, no necesitaremos la parte del código relacionada con la lectura de archivos csv y la escritura de archivos hst. En su lugar, podemos leer la historia real de ticks y formar las barras usando la API de los símbolos personalizados. Asimismo, MetaTrader 5 permite a los brókeres ofrecer volúmenes y ticks reales (para los instrumentos de las bolsas de valores y otras, pero normalmente no están disponibles para instrumentos de fórex). Si tenemos este modo activado, las barras de equivolumen se podrán construir según los volúmenes reales, y no según el número de ticks.

El parámetro de entrada FromM1 determina si el asesor procesará las barras M1 (true por defecto) o la historia de ticks (false). Al procesar los ticks, será mejor no seleccionar como inicio un pasado demasiado lejano, ya que esto puede necesitar mucho tiempo y espacio en el disco. Si usted ya ha experimentado con la historia de ticks, comprenderá las capacidades de su PC y, a buen seguro, habrá valorado el potencial de los recursos disponibles.

Las barras de igual intervalo se dibujan de forma similar. No obstante, aquí se abre una nueva barra cuando el precio supera un número determinado de puntos. Debemos tener en cuenta que estas barras solo están disponibles en el modo de ticks (FromM1 == false).

El tipo de gráfico (EqualTickVolumes, EqualRealVolumes, RangeBars) se indica usando el parámetro de entrada WorkMode.

La forma más simple de trabajar con los símbolos personalizados es usando la biblioteca Symbol (autor fxsaber). Podemos incluirla en un asesor utilizando la directiva #include:

  #include <Symbol.mqh>

Ahora, si fuera necesario, podemos crear de la siguiente forma un símbolo personalizado basado en el símbolo del gráfico de trabajo actual:

  if(!SymbolSelect(symbolName, true))
  {
    const SYMBOL Symb(symbolName);
    Symb.CloneProperties(_Symbol);
    
    if(!SymbolSelect(symbolName, true))
    {
      Alert("Can't select symbol:", symbolName, " err:", GetLastError());
      return INIT_FAILED;
    }
  }

donde symbolName es la línea con el nombre del símbolo personalizado.

Este fragmento de inicialización y muchas otras tareas auxiliares de gestión de símbolos personalizados (en concreto, el restablecimiento una historia existente y la apertura de un gráfico con un nuevo símbolo personalizado) se implementarán de manera similar en todos los programas analizados, y estarán disponibles para su revisión en los códigos fuente, si bien los omitiremos en el artículo, debido a su carácter secundario.

Cuando surge una nueva barra de equivolumen o cambia la actual, llamamos a la función WriteToFile, que, en el caso de MetaTrader 5, se implementa usando la llamada CustomRatesUpdate:

  void WriteToFile(datetime t, double o, double l, double h, double c, long v, long m = 0)
  {
    MqlRates r[1];
    
    r[0].time = t;
    r[0].open = o;
    r[0].low = l;
    r[0].high = h;
    r[0].close = c;
    r[0].tick_volume = v;
    r[0].spread = 0;
    r[0].real_volume = m;
    
    int code = CustomRatesUpdate(symbolName, r);
    if(code < 1)
    {
      Print("CustomRatesUpdate failed: ", GetLastError());
    }
  }

Para nuestra sorpresa, el ciclo de barras M1 (modo FromM1 = true) prácticamente no cambia respecto a la versión MQL4, lo cual significa que, con solo adaptar la función WriteToFile, podemos obtener un código MQL5 funcional para barras M1. La única parte que debemos modificar es la generación de ticks en RefreshWindow. En MetaTrader 4, esto se implementaba enviando mensajes de Windows para emular los ticks en un gráfico offline. En MetaTrader 5, se usa la función CustomTicksAdd:

  void RefreshWindow(const datetime t)
  {
    MqlTick ta[1];
    SymbolInfoTick(_Symbol, ta[0]);
    ta[0].time = t;
    ta[0].time_msc = ta[0].time * 1000;
    if(CustomTicksAdd(symbolName, ta) == -1)
    {
      Print("CustomTicksAdd failed:", GetLastError(), " ", (long) ta[0].time);
      ArrayPrint(ta);
    }
  }

La generación de ticks llama al evento OnTick en los gráficos de los símbolos personalizados, lo cual permite (potencialmente) operar a los asesores ejecutados en dichos gráficos. No obstante, esta tecnología requiere de ciertas acciones adicionales que analizaremos más adelante.

El modo de generación de barras de equivolumen a partir de la historia de ticks (FromM1 = false) es un poco más complejo. Aquí, necesitaremos leer los ticks reales utilizando las funciones estándar CopyTicks/CopyTicksRange. Toda esta funcionalidad se encuentra en la clase TicksBuffer.

  #define TICKS_ARRAY 10000
  
  class TicksBuffer
  {
    private:
      MqlTick array[];
      int tick;
    
    public:
      bool fill(ulong &cursor, const bool history = false)
      {
        int size = history ? CopyTicks(_Symbol, array, COPY_TICKS_ALL, cursor, TICKS_ARRAY) : CopyTicksRange(_Symbol, array, COPY_TICKS_ALL, cursor);
        if(size == -1)
        {
          Print("CopyTicks failed: ", GetLastError());
          return false;
        }
        else if(size == 0)
        {
          if(history) Print("End of CopyTicks at ", (datetime)(cursor / 1000));
          return false;
        }
        
        cursor = array[size - 1].time_msc + 1;
        tick = 0;
      
        return true;
      }
      
      bool read(MqlTick &t)
      {
        if(tick < ArraySize(array))
        {
          t = array[tick++];
          return true;
        }
        return false;
      }
  };

En el método fill, se solicitan por fragmentos los ticks según TICKS_ARRAY. Luegos estos entran en la matriz, desde donde el método read los lee por separado. Con la ayuda de estos métodos, el algoritmo encargado de iterar la historia de ticks se implementa de forma similar a la iteración de la historia de barras M1 (los códigos fuente completos se adjuntan al final del artículo).

    TicksBuffer tb;
    
    while(tb.fill(cursor, true) && !IsStopped())
    {
      MqlTick t;
      while(tb.read(t))
      {
        ...
        // New or first bar
        if(IsNewBar() || now_volume < 1)
        {
          WriteToFile(...);
        }
      }
    }

Con cada inicio, el asesor borra la historia existente del símbolo personalizado indicado (si ya existe), usando la función Reset. Si fuera necesario, podemos mejorar este comportamiento guardando la historia y continuando la generación de barras desde la anterior finalización.

Si el lector lo desea, ​​podrá comparar los códigos fuente de EqualVolumeBars.mq4 y el código de EqualVolumeBars.mq5 resultante.

Vamos a ver cómo funciona el nuevo asesor experto. Aquí tenemos el gráfico de trabajo EURUSD H1 donde se ubica el EA:

Asesor experto EqualVolumeBars en el gráfico EURUSD H1 en MetaTrader 5

Asesor experto EqualVolumeBars en el gráfico EURUSD H1 en MetaTrader 5

Y este es el aspecto del gráfico de equivolumen creado, en el que se asignan 1000 ticks para cada barra.

Gráfico de equivolumen EURUSD con 1000 ticks por barra generado por el asesor experto EqualVolumeBars en MetaTrader 5

Gráfico de equivolumen EURUSD con 1000 ticks por barra generado por el asesor experto EqualVolumeBars en MetaTrader 5

Debemos considerar que los volúmenes de ticks en todas las barras son iguales, salvo en la última, que todavía se está formando (el conteo de ticks sigue en marcha).

Vamos a comprobar el otro modo de trabajo, de igual rango. A continuación, mostramos un gráfico con 100 puntos dentro del intervalo de cada barra.

Gráfico EURUSD de igual rango con barras en un intervalo de 100 pips generado por EqualVolumeBars Expert en MetaTrader 5

Gráfico EURUSD de igual rango con barras en un intervalo de 100 pips generado por EqualVolumeBars Expert en MetaTrader 5

Para los instrumentos bursátiles, el asesor experto nos permite usar el modo de volumen real, por ejemplo, de la forma siguiente:

Gráfico original de LKOH con un volumen real de 10,000 por barra generado por el experto EqualVolumeBars en MetaTrader 5

Gráfico de equivolumen de LKOH con un volumen real de 10,000 por barra generado por el experto EqualVolumeBars en MetaTrader 5

Gráficos inicial (a) y de equivolumen (b) de LKOH con un volumen real de 10000 por barra generado por EqualVolumeBars EA en MetaTrader 5

No importa el marco temporal del símbolo en el que se ejecuta el asesor, ya que las barras M1 o la historia de ticks siempre se usan para los cálculos.

El marco temporal de los gráficos de los símbolos personalizados debe ser igual a M1 (el menor marco temporal disponible en el terminal). Por consiguiente, el tiempo de las barras, en general, se corresponde con sus momentos de formación de manera bastante próxima. No obstante, durante los movimientos intensos del mercado, cuando el número de ticks o el tamaño de los volúmenes forman varias barras por minuto, el tiempo de las barras adelantará al real. Cuando el mercado se tranquiliza, las marcas temporales de las barras de equivolumen vuelven a la normalidad. Probablemente, esta limitación de la plataforma no resulte especialmente crítica para las barras de equivolumen o de igual rango, ya que la propia idea de dichos gráficos consiste en desvincularlos del tiempo absoluto.

Gráficos de ticks

El gráfico de ticks en MetaTrader 5 está disponible en la ventana de observación de mercado. Por algún motivo, su implementación se distingue de los gráficos habituales. El gráfico muestra un número limitado de ticks (que sepamos, hasta 2000), es pequeño y no se puede expandir a pantalla completa, y tampoco dispone de todas las posibilidades que suelen ofrecer los gráficos estándar, como la capacidad de usar indicadores, objetos y asesores expertos.

Gráfico de ticks en la ventana de observación de mercado en MetaTrader 5

Gráfico de ticks en la ventana de observación de mercado en MetaTrader 5

Incluso sin ser un scalper, podríamos preguntarnos por qué no hay herramientas de análisis integradas para los ticks, especialmente teniendo en cuenta que MetaTrader 5 integra soporte para la historia de ticks reales y la plataforma en sí se posiciona, entre otras cosas, como una herramienta para el comercio de alta frecuencia (High Frequency Trading, HFT). Algunos tráders consideran que los ticks son demasiado pequeños, entidades no dignas de atención e incluso constituyentes de ruido, pero otros tratan de ganar dinero con ellos. Por consiguiente, si no disponemos de herramientas integradas, tendrá sentido mostrar independientemente el flujo de ticks en un gráfico estándar, con la capacidad de escalar, utilizar plantillas e incluso expertos. Esta posibilidad nos la ofrecen nuevamente los símbolos personalizados.

Podemos encargar el trabajo a funciones de la API MQL ya conocidas, como CopyTicks y CustomRatesUpdate. Usando estas, resulta sencillo implementar un asesor experto no comercial que genere un símbolo personalizado basado en el símbolo de trabajo del gráfico actual, y en la historia de un símbolo personalizado, cada barra M1 es un tick aparte. Adjuntamos un ejemplo de este código fuente al artículo en el archivo Ticks2Bars.mq5. Por ejemplo, si colocamos un asesor experto en el gráfico EURUSD (el marco temporal no importa), crearemos el símbolo EURUSD_ticks.

Los parámetros de entrada del asesor experto son los siguientes:

  • Limit — número de barras (ticks) que se crean después del inicio. El valor predeterminado es 1000; si establecemos 0, comenzará la generación de ticks en línea, sin una historia previa.
  • Reset — esta opción restablece la historia de ticks/barras anterior después del inicio. Por defecto es true.
  • LoopBack — esta opción activa el modo de búfer circular, en el que los ticks (con índices superiores al límite) son eliminados de la matriz interna de "cotizaciones" a medida que se añaden nuevos ticks al principio, de forma que el gráfico siempre tiene barras Limit. Por defecto es true; cuando LoopBack está activado, el parámetro Limit debe ser superior a 0; cuando LoopBack está desactivado, la matriz se amplía constantemente y aumenta el número de barras en el gráfico.
  • EmulateTicks — esta opción emula nuevos eventos de llegada de ticks para el símbolo personalizado. Se trata de una característica importante para las llamadas de los asesores expertos. Por defecto es true.
  • RenderBars — método para visualizar barras/ticks: OHLC o HighLow; por defecto, es OHLC; en este modo, el tick supone una barra con todas las funciones con un cuerpo (high = ask, low = bid, last (si hay) = close; si last = 0, open y close será iguales a uno de los precios high o low correspondientes, dependiendo de la dirección del movimiento del precio desde el momento del tick anterior; en el modo HighLow, los ticks se muestran como barras pin en las que high = ask, low = bid, open = close = (ask + bid) / 2.

El trabajo principal de la función es realizado por la función apply:

  bool apply(const datetime cursor, const MqlTick &t, MqlRates &r)
  {
    static MqlTick p;
    
    // eliminate strange things
    if(t.ask == 0 || t.bid == 0 || t.ask < t.bid) return false;
    
    r.high = t.ask;
    r.low = t.bid;
    
    if(t.last != 0)
    {
      if(RenderBars == OHLC)
      {
        if(t.last > p.last)
        {
          r.open = r.low;
          r.close = r.high;
        }
        else
        {
          r.open = r.high;
          r.close = r.low;
        }
      }
      else
      {
        r.open = r.close = (r.high + r.low) / 2;
      }
      
      if(t.last < t.bid) r.low = t.last;
      if(t.last > t.ask) r.high = t.last;
      r.close = t.last;
    }
    else
    {
      if(RenderBars == OHLC)
      {
        if((t.ask + t.bid) / 2 > (p.ask + p.bid) / 2)
        {
          r.open = r.low;
          r.close = r.high;
        }
        else
        {
          r.open = r.high;
          r.close = r.low;
        }
      }
      else
      {
        r.open = r.close = (r.high + r.low) / 2;
      }
    }
    
    r.time = cursor;
    r.spread = (int)((t.ask - t.bid)/_Point);
    r.tick_volume = 1;
    r.real_volume = (long)t.volume;
  
    p = t;
    return true;
  }

En ella, para el momento "cursor" actual, los campos de la estructura MqlTick son transferidos a los campos de la estructura MqlRates que luego se escribe en la historia.

Este es el aspecto del gráfico de un símbolo personalizado con barras de tick (como comparación mostramos junto a él un gráfico de ticks estándar):

Gráfico de ticks EURUSD plenamente funcional en MetaTrader 5

Gráfico de ticks EURUSD plenamente funcional en MetaTrader 5

Obviamente, en el gráfico de la derecha ya podemos colocar indicadores, objetos o expertos para automatizar el análisis y el comercio según los ticks.

Debemos considerar que las horas de las barras en el gráfico de barras de tick son ficticias. Si el modo LoopBack está activado, la última barra siempre tendrá la hora actual exacta al minuto, y las barras anteriores se encontrarán en el pasado con incrementos de 1 minuto (el marco temporal mínimo en MetaTrader 5). Si el modo LoopBack está desactivado, las horas de las barras aumentarán constantemente en 1 minuto partiendo del momento en que se ha iniciado el asesor, es decir, todas las barras por encima del límite inicial estarán en un futuro virtual.

No obstante, la barra M1 más a la derecha se corresponde con el tick más reciente y el precio actual "close" (o "last"). Esto nos permite comerciar en dichos gráficos usando asesores expertos, tanto en línea como en el simulador. Para trabajar en línea, el asesor necesitará una ligera modificación, porque, colocado en el gráfico del símbolo "XY_ticks", deberá comerciar en realidad con el instrumento original "XY" (los símbolos personalizados solo existen en la terminal y no se conocen en el servidor). En el ejemplo anterior, "EURUSD_ticks" debe ser reemplazado por "EURUSD" en todas las órdenes comerciales.

Si un asesor experto recibe señales comerciales de indicadores, entonces puede bastar con crear instancias del mismo en el gráfico de símbolos personalizados, en lugar del símbolo de trabajo actual, y ejecutar este asesor en este gráfico de símbolos de trabajo. Pero este método no siempre se puede aplicar. Más adelante, describiremos otro método para adaptar asesores expertos al comercio con símbolos personalizados.

A la hora de trabajar con gráficos de ticks, algunas de las dificultades se relacionan con el hecho de que se actualizan muy rápidamente. Por ello, resulta casi imposible analizar y marcar manualmente los gráficos; todo debe automatizarse con indicadores o scripts.

El enfoque presentado con las "cotizaciones de ticks" permite simular estrategias de scalping sin acumular datos especiales en los búferes internos ni calcular señales usando búferes como base, mientras que podemos simplemente utilizar los indicadores u objetos habituales.

El desplazamiento temporal y la metamorfosis de las figuras de velas

Muchos tráders usan en su práctica patrones de velas como señal principal o adicional. Este método resulta visualmente informativo, pero tiene una desventaja importante.

Los patrones de velas describen una geometría predefinida de una secuencia de barras. Todas las barras se forman a medida que el precio cambia a lo largo del tiempo. No obstante, en su esencia, el tiempo es constante, aunque los gráficos presentan el tiempo dividido artificialmente en segmentos que se corresponden con barras y alineados en una determinada zona horaria (seleccionada por el bróker). Por ejemplo, si desplazamos un gráfico con barras de horas H1 unos minutos (digamos, 15 minutos), lo más probable es que cambie la geometría de las barras. Como resultado, los anteriores patrones existentes podrían desaparecer por completo, formándose nuevos en otros lugares. Y esto teniendo en cuenta que "price action" no cambiaría de ninguna forma.

Si miramos algunos de los patrones de velas populares, podremos ver fácilmente que están formados por movimientos de precios semjantes, y que la diferencia de aspecto se debe a la hora de inicio del cálculo de la barra. Por ejemplo, si desplazamos el eje temporal media barra, entonces un "martillo" ("hammer") puede transformarse en un "claro en las nubes" ("piercing"), mientras que un "ahorcado" ("hangman") puede convertirse en "nuebes oscuras" ("dark cloud cover"). Dependiendo del valor de cambio y los cambios locales del precio, el patrón también puede convertirse en "envolvente" bajista o alcista. Si cambiamos a un marco temporal inferior, todos los patrones anteriores podrían convertirse en una "estrella de la mañana" o una "estrella de la tarde".

En otras palabras, cada patrón supone una función desde el inicio del cálculo de tiempo y escala. En concreto, si modificamos el punto de partida, podremos detectar una figura e interceptar todas las demás que sean equivalentes a ella.

Los patrones de viraje (muy populares entre los tráders, ya que permiten determinar el inicio de un movimiento) se pueden presentar de la siguiente forma simplificada:

Movimientos de viraje del precio y estructuras de velas equivalentes

Movimientos de viraje del precio y estructuras de velas equivalentes

La figura muestra una representación esquemática de un viraje hacia arriba y un viraje hacia abajo; cada uno de ellos con dos variantes de configuración de barras. Como hemos visto anteriormente, pueden existir más patrones de significado similar. No sería razonable monitorear todos. Sería más adecuado saber determinar los patrones de velas para cualquier momento inicial.

Además, la conversión de la hora puede suponer un desplazamiento aceptable en relación con GMT para alguna otra zona horaria, lo cual significa que, teóricamente, estos nuevos patrones de velas también deberían funcionar en esta nueva área, al igual que los formados en nuestra zona horaria. Como los servidores comerciales se encuentran por todo el mundo y en todas las zonas horarias, los tráders, sin lugar a dudas, pueden ver señales absolutamente diferentes. Y para los tráders, cada señal es valiosa.

Podemos llegar a la conclusión de que los aficionados a los patrones de velas deben considerar su variabilidad dependiendo del punto de partida. Y aquí es donde los símbolos personalizados resultan útiles.

Una señal personalizada basada en el símbolo de trabajo nos permite construir barras y ticks con marcas temporales desplazadas según un valor especificado hacia el futuro o hacia el pasado. Este valor de cambio se puede interpretar como parte de una barra de cualquier marco temporal seleccionado. En este caso, los precios y el movimiento no cambian, pero esto aún puede ofrecernos un resultado interesante. En primer lugar, se posibilita la detección de patrones de velas que pasarían desapercibidos sin dicha modificación. En segundo lugar, podemos ver una barra incompleta por delante.

Por ejemplo, si avanzamos las cotizaciones 5 minutos, las barras del gráfico M15 se abrirán y cerrarán un tercio "antes" del gráfico original (cada 10, 25, 40, 55 minutos de la hora). Si el cambio resulta insignificante, las cifras en los gráficos original y personalizado serán casi idénticas; no obstante, las señales (calculadas según las barras, en particular, de indicador) del gráfico personalizado llegarán por adelantado.

La creación de este símbolo personalizado temporalmente desplazado se implementa en el asesor experto TimeShift.mq5.

La magnitud del desplazamiento se indica en el parámetro de entrada Shift (en segundos). El asesor experto funciona según los ticks, ofreciéndonos así la oportunidad de calcular la historia de las cotizaciones convertidas al inicio, partiendo de la fecha especificada en el parámetro Start. A continuación, los ticks se procesan online, si el modo de generación de eventos OnTick está activado, para lo cual tenemos el parámetro EmulateTicks (por defecto, true).

La conversión de la hora se implementa de una forma bastante simple, de la misma manera tanto para los ticks en la historia como para los ticks en línea; por ejemplo, en este último caso, utilizando la función add:

  ulong lastTick;
  
  void add()
  {
    MqlTick array[];
    int size = CopyTicksRange(_Symbol, array, COPY_TICKS_ALL, lastTick + 1, LONG_MAX);
    if(size > 0)
    {
      lastTick = array[size - 1].time_msc;
      for(int i = 0; i < size; i++)
      {
        array[i].time += Shift;
        array[i].time_msc += Shift * 1000;
      }
      if(CustomTicksAdd(symbolName, array) == -1)
      {
        Print("Tick error: ", GetLastError());
      }
    }
  }
  
  void OnTick(void)
  {
    ...
    if(EmulateTicks)
    {
      add();
    }
  }

Más abajo, mostramos los gráficos EURUSD H1 originales y modificados.

Gráfico EURUSD H1 con TimeShift Expert

Gráfico EURUSD H1 con TimeShift Expert

Después de un desplazamiento de media hora (media barra), la imagen cambia.

Gráfico personalizado EURUSD H1 con un desplazamiento de media hora

Gráfico personalizado EURUSD H1 con un desplazamiento de media hora

Los lectores familiarizados con los patrones de velas, seguro que notarán muchas diferencias, incluidas las nuevas señales que no se encontraban en el gráfico original. Por consiguiente, utilizando un indicador de velas común en un gráfico de símbolos personalizado, podremos obtener el doble de alertas y oportunidades comerciales. Podríamos incluso automatizar el proceso usando asesores expertos, pero, en ese caso, deberíamos "enseñarles" a estos a comerciar con el símbolo real del gráfico del símbolo "virtual" derivado. Consideraremos esta tarea al final del artículo. Ahora, vamos a analizar otro tipo de gráfico personalizado, probablemente el más popular: Renko.

Renko

Para implementar los gráficos Renko, usaremos el asesor experto no comercial RenkoTicks.mq5. El asesor genera renko como cotizaciones de símbolos personalizados mientras procesa ticks reales (disponibles en la plataforma MetaTrader 5 de su bróker). Las cotizaciones (barras) y el marco temporal de trabajo del gráfico en el que se ejecuta RenkoTicks no son importantes.

A la hora de generar renko, una alternativa a los símbolos personalizados podría ser un indicador o dibujo (usando objetos o en un lienzo (canvas)), pero, en ambos casos, resultaría imposible utilizar indicadores o scripts en los pseudográficos resultantes.

Todas los bloques de renko se forman en el marco temporal M1. Esto se hace a propósito, ya que, en ocasiones, las barras de renko se pueden formar una tras otra muy rápidamente, por ejemplo, en momentos de alta volatilidad, y el tiempo entre barras debe ser lo más corto posible. Un minuto es la distancia mínima admitida en MetaTrader 5. Por eso, el gráfico renko siempre debe tener el marco temporal M1. No tiene sentido cambiar el gráfico de renko a otro marco temporal. La hora de comienzo de cada barra de minuto en él no coincide con la hora en que comenzó a formarse el bloque de renko existente. La hora de finalización de la barra de un minuto es ficitcia y, en su lugar, debemos comprobar el inicio de la barra de 1 minuto siguiente.

Por desgracia, a veces se deben formar varias barras de renko en un minuto. Como MetaTrader 5 no permite esto, el asesor genera las barras como secuencias de barras M1 colindantes, incrementando artificialmente los recuentos cada minuto. Como resultado, la hora formal de las barras de renko podría no coincidir con la real (podría estar adelantada). Por ejemplo, con un tamaño de renko de 100 pips, un movimiento de 300 pips que tuviera lugar a las 12:00:00 y requiriera 10 segundos, habría creado barras de renko a las 12:00:00, 12:00:05, 12:00: 10. En cambio, el asesor generará barras a las 12:00, 12:01, 12:02.

Cuando ocurre algo así en la historia de cotizaciones, puede darse el siguiente problema: dichas barras renko transferidas desde el pasado se superpondrán con otras barras formadas a partir de las barras posteriores del gráfico original. Supongamos que sucede otro movimiento de 100 puntos a las 12:02 y que, por lo tanto, necesitamos generar una barra renko con una hora de apertura de 12:02, ¡pero esta vez ya está ocupada! Para resolver este tipo de conflicto, el asesor experto dispone de un modo especial que incrementa forzosamente en 1 minuto la hora de la siguiente barra formada, si el recuento requerido ya está ocupado. Este modo se establece con el parámetro SkipOverflows, establecido por defecto en false (las barras no se superponen, sino que se mueven hacia el futuro, de ser necesario). Si SkipOverflows es igual a true, las barras con horas superpuestas se sobrescribirán entre sí y el renko resultante no será completamente correcto.

Cabe señalar especialmente que, en tiempo real, resulta posible una situación similar, es decir, con un movimiento fuerte y generación de varios bloques de "tiempo anticipado", ¡entonces las barras se formarán en el futuro! Entonces, en nuestro ejemplo, a las 12:00:10 ya habrá barras renko con la hora de apertura 12:00, 12:01, 12:02. Debemos considerar esto a la hora de analizar y comerciar.

Solo existen un par de formas de resolver este problema, concretamente, aumentar el tamaño del bloque renko. No obstante, cuenta con un inconveniente obvio: disminuye la precisión de la construcción de renko, es decir, dentro de él se registrarán movimientos de precios más vastos y habrá menos bloques. También podemos realizar el "empaquetado" (desplazamiento hacia la izquierda) de las barras antiguas, pero esto puede requerir la reconstrucción de indicadores u objetos.

Debido a las peculiaridades de la plataforma, el asesor genera ticks ficticios con una hora igual a la hora de apertura de la última barra de renko. Su único cometido es iniciar el manejador OnTick en un asesor experto comercial. Si los ticks se han trasladado sin cambios del símbolo original al personalizado, se estropearía la propia estructura del renko. Entonces, prosiguiendo con el ejemplo del movimiento fuerte que hemos discutido anteriormente, podemos intentar enviar a las 12:00:10 un tick al gráfico de renko con la hora real. Pero la hora de este tick no se corresponderá con la última barra (actual) 0, sino con la barra 2, que tiene la hora de apertura 12:00. Como resultado, este tick estropeará la barra de renko 12:00 (que está en la historia) o provocará un error. Renko puede estropearse también por la situación opuesta: cuando el movimiento es demasiado lento. Si las cotizaciones se encuentran dentro del rango de una barra durante mucho tiempo, la barra de renko permanecerá con la misma hora de apertura, mientras que los nuevos ticks podrían tener una hora que supere en más de un minuto a la barra de renko 0. Si estos ticks se envían a un gráfico renko, esto formaría barras (bloques) fantasma en el "futuro".

Debemos tener en cuenta que los ticks de renko en la historia se forman en un estilo minimalista, 1 tick por bloque. Al trabajar online, todos los ticks se envían a renko.

De manera semejante a otros símbolos personalizados, este enfoque nos permite usar cualquier indicador, script y objeto en los gráficos renko, y también comerciar con asesores expertos.

Parámetros principales:

  • RenkoBoxSize - tamaño del bloque renko en puntos, por defecto es 100.
  • ShowWicks — bandera de representación de las sombras, por defecto es true.
  • EmulateOnLineChart — bandera de retransmisión de los ticks, por defecto es true.
  • OutputSymbolName — nombre de símbolo personalizado para el renko generado; por defecto, es una línea vacía. En esta caso, además, el nombre se forma como "Symbol_T_Type_Size", donde Symbol es el símbolo de trabajo actual, T es el signo del modo de ticks, Type es "r" (renko) para representar las sombras o "b" (bloque) sin sombras, y Size es RenkoBoxSize; por ejemplo: "EURUSD_T_r100".
  • Reset — indicador para recalcular todo el gráfico de renko, por defecto, es false. Si lo establecemos como true, recomendamos esperar el resultado y volver a establecerlo como false para evitar que se recalcule con cada reinicio del terminal. Este modo resulta útil cuando la generación de barras renko ha fallado en alguna posición. En general, la opción siempre está desactivada, ya que el asesor puede continuar con el cálculo desde la última barra de renko disponible.
  • StartFrom, StopAt — fechas de inicio y finalización de la historia procesada; por defecto, se usa cero, lo que indica que se utilizará toda la historia disponible. Durante el primer uso del asesor, recomendamos establecer StartFrom en el pasado reciente, para valorar la velocidad del sistema al generar barras de renko según ticks reales.
  • SkipOverflows — bandera para el procesamiento de los conflictos de superposición de bloques; por defecto está establecido como false, lo que indica que la nueva hora de la barra se incrementará en 1 minuto forzosamente si el cálculo requerido ya está ocupado por el bloque anterior.
  • CloseTimeMode — si es true, los bloques se formarán completamente en el momento del cierre (un evento de "tick" por bloque); por defecto es false.

La clase Renko se encarga de procesar el flujo de ticks y crear nuevas barras renko usándolo como base. Sus principales componentes se muestran en el siguiente pseudocódigo:

  class Renko
  {
    protected:
      bool incrementTime(const datetime time);
      void doWriteStruct(const datetime dtTime, const double dOpen, const double dHigh, const double dLow, const double dClose, const double dVol, const double dRealVol, const int spread);
      
    public:
      datetime checkEnding();
      void continueFrom(const datetime time);
      void doReset();
  
      void onTick(const MqlTick &t);
      void updateChartWindow(const double bid = 0, const double ask = 0);
  };

Los métodos protegidos incrementTime y doWriteStruct ejecutan, respectivamente, el cambio a la siguiente lectura M1 libre más próxima a la hora indicada para el próximo bloque renko, así como el registro de la barra usando la llamada de CustomRatesUpdate. Los primeros tres métodos de la parte pública se encargan de inicializar el algoritmo al darse el inicio. El asesor experto puede comprobar la existencia de cotizaciones Renko anteriores (esto se hace con el método checkEnding, que retorna la fecha y la hora de finalización de la historia) y, dependiendo de si existen o no, el asesor continuará desde el momento indicado usando el método continueFrom (restaurando los valores de las variables internas), o bien utilizando doReset para manejar los ticks desde un estado "vacío".

El método onTick, como entender por su nombre, se llama en cada tick (tanto en la historia como en línea) y, si fuera necesario, forma una barra renko usando doWriteStruct (hemos usado un algoritmo del famoso asesor RenkoLiveChart.mq4 con algunas correcciones). Si establecemos la emulación de ticks en la configuración del asesor, también se llamará updateChartWindow. A continuación, adjuntamos los códigos fuente completos.

La clase TickProvider se encarga de "entregar" los ticks al objeto Renko:

  class TickProvider
  {
    public:
      virtual bool hasNext() = 0;
      virtual void getTick(MqlTick &t) = 0;
  
      bool read(Renko &r)
      {
        while(hasNext() && !IsStopped())
        {
          MqlTick t;
          getTick(t);
          r.onTick(t);
        }
        
        return IsStopped();
      }
  };

Es abstracta, ya que declara una interfaz común para leer/recibir ticks de dos fuentes distintas: la historia de ticks del símbolo base al inicio del asesor y la cola de eventos de OnTick cuando trabajamos en línea. El método de lectura es un ciclo de ticks universal que usa los métodos virtuales hasNext() y getTick().

La lectura de la historial de ticks se efectúa en la clase HistoryTickProvider de una manera conocida: se usan CopyTicksRange y el búfer intermedio MqlTick array[], en el que los ticks son solicitados por día:

  class HistoryTickProvider : public TickProvider
  {
    private:
      datetime start;
      datetime stop;
      ulong length;     // in seconds
      MqlTick array[];
      int size;
      int cursor;
      
      int numberOfDays;
      int daysCount;
      
    protected:
      void fillArray()
      {
        cursor = 0;
        do
        {
          size = CopyTicksRange(_Symbol, array, COPY_TICKS_ALL, start * 1000, MathMin(start + length, stop) * 1000);
          Comment("Processing: ", DoubleToString(daysCount * 100.0 / (numberOfDays + 1), 0), "% ", TTSM(start));
          if(size == -1)
          {
            Print("CopyTicksRange failed: ", GetLastError());
          }
          else
          {
            if(size > 0 && array[0].time_msc < start * 1000) // prevent older than requested data returned
            {
              start = stop;
              size = 0;
            }
            else
            {
              start = (datetime)MathMin(start + length, stop);
              if(size > 0) daysCount++;
            }
          }
        }
        while(size == 0 && start < stop);
      }
    
    public:
      HistoryTickProvider(const datetime from, const long secs, const datetime to = 0): start(from), stop(to), length(secs), cursor(0), size(0)
      {
        if(stop == 0) stop = TimeCurrent();
        numberOfDays = (int)((stop - start) / DAY_LONG);
        daysCount = 0;
        fillArray();
      }
  
      bool hasNext() override
      {
        return cursor < size;
      }
  
      void getTick(MqlTick &t) override
      {
        if(cursor < size)
        {
          t = array[cursor++];
          if(cursor == size)
          {
            fillArray();
          }
        }
      }
  };

La clase de proveedor de ticks online CurrentTickProvider resulta mucho más sencilla:

  class CurrentTickProvider : public TickProvider
  {
    private:
      bool ready;
      
    public:
      bool hasNext() override
      {
        ready = !ready;
        return ready;
      }
      
      void getTick(MqlTick &t) override
      {
        SymbolInfoTick(_Symbol, t);
      }
  };

La parte principal del procesamiento de ticks tiene el siguiente aspecto en forma abreviada:

  const long DAY_LONG = 60 * 60 * 24;
  bool _FirstRun = true;
  
  Renko renko;
  CurrentTickProvider online;
  
  void OnTick(void)
  {
    if(_FirstRun)
    {
      // find existing renko tail to supersede StartFrom
      const datetime trap = renko.checkEnding();
      if(trap > TimeCurrent())
      {
        Print("Symbol/Timeframe data not ready...");
        return;
      }
      if((trap == 0) || Reset) renko.doReset();
      else renko.continueFrom(trap);
  
      HistoryTickProvider htp((trap == 0 || Reset) ? StartFrom : trap, DAY_LONG, StopAt);
      
      const bool interrupted = htp.read(renko);
      _FirstRun = false;
      
      if(!interrupted)
      {
        Comment("RenkoChart (" + (string)RenkoBoxSize + "pt): open ", _SymbolName, " / ", renko.getBoxCount(), " bars");
      }
      else
      {
        Print("Interrupted. Custom symbol data is inconsistent - please, reset or delete");
      }
    }
    else if(StopAt == 0) // process online if not stopped explicitly
    {
      online.read(renko);
    }
  }

En el primer inicio, se busca el final de la historia de renko. Luego se crea el objeto HistoryTickProvider con la hora de inicio StartFrom, o bien desde la historia (si se encuentra), y después se leen todos los ticks. Todos los ticks adicionales son procesados online a través del objeto CurrentTickProvider (creado en el contexto global, al igual que el objeto Renko).

Vamos a generar un gráfico renko basado en EURUSD con un tamaño de barra de 100 pips a partir de 2019. Para hacerlo, ejecutaremos el asesor en el gráfico EURUSD H1 con la configuración predeterminada, excepto StartFrom. El marco temporal solo importa cuando el asesor se reinicia con la historia de renko disponible; en este caso, el recálculo de renko comenzará con un retardo de la hora de la barra en la que cae el penúltimo bloque de renko.

Por ejemplo, para el EURUSD H1 original:

Gráfico EURUSD H1 con el experto RenkoTicks

Gráfico EURUSD H1 con el experto RenkoTicks

obtenemos el siguiente gráfico:

Gráfico renko EURUSD con un tamaño de bloque de 100 puntos

Gráfico renko EURUSD con un tamaño de bloque de 100 puntos

Para que se vea más claro, hemos añadido un par de indicadores MA.

Ahora que hemos obtenido las cotizaciones para el símbolo renko, es el momento de desarrollar un asesor de prueba para comerciar.

Experto de trabajo en el cruzamiento de dos medias móviles

Vamos a seleccionar una de las estrategias comerciales más simples: el cruzamiento de dos medias móviles. La anterior captura de pantalla ilustra muy bien la idea. Cuando la MA rápida (roja) cruza la MA lenta (azul) hacia arriba o hacia abajo, abrimos una posición de compra o venta, respectivamente. El sistema se basa en el viraje.

Sería difícil crear un asesor desde cero, pero MetaTrader 5 ofrece un wizard MQL que puede generar asesores expertos usando como base la biblioteca de clases estándar (incluida en el terminal). Resulta muy adecuado para los tráders no familiarizados con la programación. La estructura del código resultante es común para una gran cantidad de robots y, por consiguiente, será una buena idea utilizarla para la tarea principal: adaptar los robots al comercio con símbolos personalizados. Los asesores expertos creados sin ayuda de la biblioteca estándar también se pueden adaptar según el mismo método, pero, como su creación puede ser muy diferente, el programador tendrá que realizar las modificaciones adecuadas, si fuera necesario (generalmente, los programadores experimentados pueden adaptar cualquier otro asesor usando nuestro ejemplo).

Curiosamente, aunque se trata de una de las estrategias más populares (al menos es la más popular al prender sobre comercio algorítmico), la biblioteca estándar no dispone de señal de cruzamiento de dos MA. Por consiguiente, necesitamos escribir el módulo de señal apropiado. Vamos a llamarlo Signal2MACross.mqh. A continuación, mostramos el código del mismo, que cumple con las reglas necesarias para que la señal se utilice con el wizard de MQL.

Comenzaremos con un encabezado, un comentario especial con la descripción de la señal en el formato adecuado, que lo hace accesible desde el MetaEditor:

  // wizard description start
  //+------------------------------------------------------------------+
  //| Description of the class                                         |
  //| Title=Signals of 2 MAs crosses                                   |
  //| Type=SignalAdvanced                                              |
  //| Name=2MA Cross                                                   |
  //| ShortName=2MACross                                               |
  //| Class=Signal2MACross                                             |
  //| Page=signal_2mac                                                 |
  //| Parameter=SlowPeriod,int,11,Slow MA period                       |
  //| Parameter=FastPeriod,int,7,Fast Ma period                        |
  //| Parameter=MAMethod,ENUM_MA_METHOD,MODE_LWMA,Method of averaging  |
  //| Parameter=MAPrice,ENUM_APPLIED_PRICE,PRICE_OPEN,Price type       |
  //| Parameter=Shift,int,0,Shift                                      |
  //+------------------------------------------------------------------+
  // wizard description end

El nombre de la clase (línea Class) debe coincidir con el nombre de la clase real en el siguiente código MQL. La señal dispone de los 5 parámetros típicos de una pareja de MA: 2 periodos (rápido y lento), método de promediado, tipo de precio y desplazamiento.

Como era de esperar, la clase se hereda de CExpertSignal, y contiene 2 instancias de objetos de indicador CiMA, las variables con los parámetros de trabajo, y los métodos "setter" de los parámetros (los nombres de los métodos deben coincidir con los nombres en el encabezado). Asimismo, la clase ha redefinido los métodos virtuales llamados al inicializar los indicadores, verificar la configuración y detectar las señales de compra y venta.

  class Signal2MACross : public CExpertSignal
  {
    protected:
      CiMA              m_maSlow;         // object-indicator
      CiMA              m_maFast;         // object-indicator
      
      // adjustable parameters
      int               m_slow;
      int               m_fast;
      ENUM_MA_METHOD    m_method;
      ENUM_APPLIED_PRICE m_type;
      int               m_shift;
      
      // "weights" of market models (0-100)
      int               m_pattern_0;      // model 0 "fast MA crosses slow MA"
  
    public:
                        Signal2MACross(void);
                       ~Signal2MACross(void);
                       
      // parameters setters
      void              SlowPeriod(int value) { m_slow = value; }
      void              FastPeriod(int value) { m_fast = value; }
      void              MAMethod(ENUM_MA_METHOD value) { m_method = value; }
      void              MAPrice(ENUM_APPLIED_PRICE value) { m_type = value; }
      void              Shift(int value) { m_shift = value; }
      
      // adjusting "weights" of market models
      void              Pattern_0(int value) { m_pattern_0 = value; }
      
      // verification of settings
      virtual bool      ValidationSettings(void);
      
      // creating the indicator and timeseries
      virtual bool      InitIndicators(CIndicators *indicators);
      
      // checking if the market models are formed
      virtual int       LongCondition(void);
      virtual int       ShortCondition(void);
  
    protected:
      // initialization of the indicators
      bool              InitMAs(CIndicators *indicators);
      
      // getting data
      double            FastMA(int ind) { return(m_maFast.Main(ind)); }
      double            SlowMA(int ind) { return(m_maSlow.Main(ind)); }
  };

La clase describe una sola estrategia (patrón o modelo): cuando la MA rápida cruza la lenta, se inicia una compra (cuando cruza hacia arriba) o una venta (cuando cruza hacia abajo). El peso del modelo es por defecto de 100.

  Signal2MACross::Signal2MACross(void) : m_slow(11), m_fast(7), m_method(MODE_LWMA), m_type(PRICE_OPEN), m_shift(0), m_pattern_0(100)
  {
  }

Las condiciones para abrir posiciones se definen en los dos siguientes métodos (siendo rigurosos, el código no comprueba el cruzamiento, sino la ubicación de una MA en relación con la otra, pero para un sistema que siempre está en el mercado, el efecto será el mismo y el código será más simple):

  int Signal2MACross::LongCondition(void)
  {
    const int idx = StartIndex();
    
    if(FastMA(idx) > SlowMA(idx))
    {
      return m_pattern_0;
    }
    return 0;
  }
  
  int Signal2MACross::ShortCondition(void)
  {
    const int idx = StartIndex();
  
    if(FastMA(idx) < SlowMA(idx))
    {
      return m_pattern_0;
    }
    return 0;
  }

La función StartIndex se define en la clase principal. Como podemos ver por el código, el índice es el número de la barra para la que se analiza la señal. Si en los ajustes del experto se indica el trabajo según los ticks (Expert_EveryTick = true, ver más adelante), entonces el índice inicial será igual a 0; de lo contrario (es decir, si operamos según las barras cerradas), el índice será 1.

Guardamos el archivo Signal2MACross.mqh en la carpeta MQL5/Include/Expert/Signal/MySignals, luego reiniciamos el MetaEditor (si se está ejecutando) para "captar" el nuevo módulo en el wizard de MQL.

Ahora podemos generar un asesor experto basado en nuestra señal. Seleccionamos File | New en el menú y abrimos la ventana de diálogo del wizard. A continuación, seguimos los pasos enumerados:

  1. seleccionamos el punto Expert Adviser (generate),
  2. indicamos el nombre del asesor, por ejemplo Experts\Examples\MA2Cross,
  3. añadimos la señal "Signals of 2 MAs crosses"
  4. dejamos la opción sin trailing "Trailing stop not used"
  5. dejamos la opción de gestión de capital "Trading with fixed volume"

Como resultado, obtendremos un código fuente del asesor semejante al mostrado a continuación:

  #include <Expert\Expert.mqh>
  #include <Expert\Signal\MySignals\Signal2MACross.mqh>
  #include <Expert\Trailing\TrailingNone.mqh>
  #include <Expert\Money\MoneyFixedLot.mqh>
  
  //+------------------------------------------------------------------+
  //| Inputs                                                           |
  //+------------------------------------------------------------------+
  // inputs for expert
  input string             Expert_Title              = "MA2Cross";  // Document name
  ulong                    Expert_MagicNumber        = 7623;
  bool                     Expert_EveryTick          = false;
  // inputs for main signal
  input int                Signal_ThresholdOpen      = 10;          // Signal threshold value to open [0...100]
  input int                Signal_ThresholdClose     = 10;          // Signal threshold value to close [0...100]
  input double             Signal_PriceLevel         = 0.0;         // Price level to execute a deal
  input double             Signal_StopLevel          = 0.0;         // Stop Loss level (in points)
  input double             Signal_TakeLevel          = 0.0;         // Take Profit level (in points)
  input int                Signal_Expiration         = 0;           // Expiration of pending orders (in bars)
  input int                Signal_2MACross_SlowPeriod = 11;         // 2MA Cross(11,7,MODE_LWMA,...) Slow MA period
  input int                Signal_2MACross_FastPeriod = 7;          // 2MA Cross(11,7,MODE_LWMA,...) Fast Ma period
  input ENUM_MA_METHOD     Signal_2MACross_MAMethod  = MODE_LWMA;   // 2MA Cross(11,7,MODE_LWMA,...) Method of averaging
  input ENUM_APPLIED_PRICE Signal_2MACross_MAPrice   = PRICE_OPEN;  // 2MA Cross(11,7,MODE_LWMA,...) Price type
  input int                Signal_2MACross_Shift     = 0;           // 2MA Cross(11,7,MODE_LWMA,...) Shift
  input double             Signal_2MACross_Weight    = 1.0;         // 2MA Cross(11,7,MODE_LWMA,...) Weight [0...1.0]
  // inputs for money
  input double             Money_FixLot_Percent      = 10.0;        // Percent
  input double             Money_FixLot_Lots         = 0.1;         // Fixed volume
  
  //+------------------------------------------------------------------+
  //| Global expert object                                             |
  //+------------------------------------------------------------------+
  CExpert ExtExpert;
  
  //+------------------------------------------------------------------+
  //| Initialization function of the expert                            |
  //+------------------------------------------------------------------+
  int OnInit()
  {
    // Initializing expert
    if(!ExtExpert.Init(Symbol(), Period(), Expert_EveryTick, Expert_MagicNumber))
    {
      printf(__FUNCTION__ + ": error initializing expert");
      ExtExpert.Deinit();
      return(INIT_FAILED);
    }
    // Creating signal
    CExpertSignal *signal = new CExpertSignal;
    if(signal == NULL)
    {
      printf(__FUNCTION__ + ": error creating signal");
      ExtExpert.Deinit();
      return(INIT_FAILED);
    }
    
    ExtExpert.InitSignal(signal);
    signal.ThresholdOpen(Signal_ThresholdOpen);
    signal.ThresholdClose(Signal_ThresholdClose);
    signal.PriceLevel(Signal_PriceLevel);
    signal.StopLevel(Signal_StopLevel);
    signal.TakeLevel(Signal_TakeLevel);
    signal.Expiration(Signal_Expiration);
    
    // Creating filter Signal2MACross
    Signal2MACross *filter0 = new Signal2MACross;
    if(filter0 == NULL)
    {
      printf(__FUNCTION__ + ": error creating filter0");
      ExtExpert.Deinit();
      return(INIT_FAILED);
    }
    signal.AddFilter(filter0);
    
    // Set filter parameters
    filter0.SlowPeriod(Signal_2MACross_SlowPeriod);
    filter0.FastPeriod(Signal_2MACross_FastPeriod);
    filter0.MAMethod(Signal_2MACross_MAMethod);
    filter0.MAPrice(Signal_2MACross_MAPrice);
    filter0.Shift(Signal_2MACross_Shift);
    filter0.Weight(Signal_2MACross_Weight);
  
    ...
    
    // Check all trading objects parameters
    if(!ExtExpert.ValidationSettings())
    {
      ExtExpert.Deinit();
      return(INIT_FAILED);
    }
    
    // Tuning of all necessary indicators
    if(!ExtExpert.InitIndicators())
    {
      printf(__FUNCTION__ + ": error initializing indicators");
      ExtExpert.Deinit();
      return(INIT_FAILED);
    }
  
    return(INIT_SUCCEEDED);
  }

Adjuntamos al artículo el código completo de MA2Cross.mq5. Ya está todo está listo para compilar, hacer las pruebas en el simulador e incluso optimizar cualquier símbolo, incluido nuestro renko personalizado. Y como el propio renko se convertirá en un campo de pruebas aún mayor, dedicaremos un momento a explicar algunos de los detalles relacionados con renko.

Cada bloque renko, en su forma "rectangular", no existe hasta que esté completamente formado por el movimiento del precio. Cuando aparece el siguiente bloque, aún no sabemos del siguiente bloque, no solo el precio de cierre, sino también el precio de apertura, porque hay dos posibles direcciones opuestas: hacia arriba y hacia abajo. Cuando se cierra finalmente el bloque, es el precio de cierre el que resulta decisivo y más característico. Por eso, el valor predeterminado del parámetro Signal_2MACross_MAPrice en el asesor se cambia a PRICE_CLOSE, y no se recomienda modificarlo. El lector puede experimentar con otros tipos de precios, pero la idea de renko no consiste solo en librarse del tiempo, sino también en descartar las pequeñas fluctuaciones de los precios, lo cual se logra cuantificando según el tamaño del bloque.

Debemos tener en cuenta que la barra 0 de renko siempre se encuentra incompleta (en la mayoría de los casos es una vela sin cuerpo, no un rectángulo), por eso, usaremos la señal de la primera barra. Para ello, estableceremos el valor del parámetro Expert_EveryTick igual como false.

Generamos el renko personalizado con un tamaño de bloque de 100 puntos basado en EURUSD. Como resultado, obtenemos el símbolo EURUSD_T_r100. Lo seleccionamos en el simulador, asegurándonos de establecer el marco temporal M1.

Veamos cómo se comporta el asesor experto en este símbolo para el periodo 2019-2020 (primer semestre), por ejemplo, con periodos predeterminados de 7 y 11 (podemos verificar otras combinaciones de forma independiente mediante la optimización).

Resultado de la estrategia de cruzamiento de dos MA (MA2CrossCustom) en el gráfico renko de 100 puntos derivado de EURUSD

Resultado de la estrategia de cruzamiento de dos MA (MA2CrossCustom) en el gráfico renko de 100 puntos derivado de EURUSD

Para comparar el símbolo personalizado con un símbolo real, ofrecemos aquí un informe del EA MA2CrossCustom similar a MA2Cross con un parámetro WorkSymbol vacío. En el siguiente apartado, veremos cómo obtener MA2CrossCustom a partir de MA2Cross.

Como podemos ver en el recuadro de transacciones, las ofertas se ejecutan a precios que son múltiplos del tamaño del bloque: los precios de venta coinciden completamente y los precios de compra se distinguen según el tamaño del spread (nuestro generador de renko guarda en cada barra el valor máximo de spread registrado en su formación: si el usuario lo desea, puede cambiar este comportamiento en el código fuente). Renko se construye según el tipo de precio usado en el gráfico fuente, que en nuestro caso es bid. Para los instrumentos bursátiles, utilizaremos last.

Recuadro de transacciones al comerciar con un símbolo renko personalizado basado en EURUSD

Recuadro de transacciones al comerciar con un símbolo renko personalizado basado en EURUSD

Por el momento, merece la pena señalar que el resultado es demasiado bueno para ser cierto. En realidad, aquí hay muchos matices ocultos.

El comercio en el simulador con el símbolo renko influye en la precisión de los resultados de cualquier forma: según los precios abiertos, M1 OHLC y ticks.

En un renko estándar, el precio de apertura de la barra no siempre se alcanza en el momento en que se marca la barra, sino, en muchos casos, bastante más tarde (ya que el precio "deambula" hacia arriba y hacia abajo durante algún tiempo dentro del tamaño del renko, y eventualmente puede cambiar de dirección, formando una barra de viraje). La hora de marcado de la barra es la hora de finalización de la barra anterior.

El precio de cierre tampoco se corresponde con la hora de cierre, porque la barra renko es una barra M1, es decir, tiene una longitud fija de 1 minuto.

Podemos generar un renko no estándar, en el que las barras estén marcadas con la hora en que terminan, en lugar de comenzar. Entonces, el precio de cierre se corresponderá con la hora de cierre. No obstante, la hora de apertura se dará 1 minuto antes del cierre y, por consiguiente, no se corresponderá con el precio de apertura real (este es el precio de cierre más/menos el tamaño del renko).

Se supone que el análisis de renko se realiza sobre la base de las barras formadas, pero su precio característico es el precio de cierre, y el simulador, al comerciar barra por barra, ofrece para la barra actual (la última) solo el precio de apertura (no hay modo para precios de cierre). Aquí, los precios de apertura de la barra son por definición predictores. Si utilizamos las señales de las barras cerradas (en general, desde la 1ª), las transacciones se ejecutarán de todos modos al precio actual de la barra número 0. Incluso si utilizamos los modos de tick, el simulador generará los ticks para renko según las reglas comunes, utilizando puntos de referencia basados ​​en la configuración de cada barra. El simulador no considera la estructura y el comportamiento específicos de las cotizaciones renko (que estamos intentando emular visualmente con barras M1). Si imaginamos hipotéticamente una formación única de una barra completa, todavía tendrá cuerpo, y para tales barras, el simulador generará ticks a partir del precio de apertura. Si establecemos un volumen de tick de la barra igual a uno, la barra perderá su configuración (se convertirá en una etiqueta de precio con el mismo OHLC).

De esta forma, todos los métodos de construcción de renko dispondrán de artefactos de ejecución de órdenes al poner a prueba un símbolo de renko personalizado.

En otras palabras, la propia estructura de renko posibilita la obtención de griales de prueba en los símbolos de renko, porque este se asoma al futuro en un salto igual al tamaño de la barra de renko.

Por eso, debemos probar el sistema comercial no en una barra renko por separado, sino combinada con la ejecución de órdenes comerciales en un símbolo real.

Renko nos ofrece análisis y sincronización: cuándo tenemos precisamente que entrar en el mercado.

Por eso, solo hemos comprobado la posibilidad de comerciar con un asesor experto en un símbolo personalizado. Esto limita el campo de aplicación del asesor por parte del simulador. Para que el asesor experto sea universal, es decir, capaz de comerciar online con el símbolo original mientras se encuentra en el gráfico renko, hay algunas cosas que añadir. Ya de paso, estaremos equilibrando el problema derivado de los indicadores comerciales demasiado optimistas.

Adaptación de asesores para comerciar en los gráficos de los símbolos personalizados

El símbolo personalizado es solo conocido por el terminal del cliente, y no se encuentra en el servidor comercial. Obviamente, un asesor experto ubicado en el gráfico de un símbolo personalizado deberá generar todas las órdenes comerciales para el símbolo original (en el que se basa el símbolo personalizado). Como la solución más simple a este problema, podemos ejecutar el asesor en el gráfico del símbolo original, pero recibir las señales (por ejemplo, de indicadores) del símbolo personalizado. No obstante, muchos tráders prefieren ver el panorama completo. Además, las modificaciones selectivas del código pueden acarrear errores. Por ello, resulta deseable que editemos el código fuente lo mínimo posible.

Por desgracia, no podemos vincular el nombre del símbolo original y el nombre del símbolo renko creado a partir de él con la ayuda de la propia plataforma. Una solución adecuada sería disponer entre las propiedades del símbolo personalizado de un campo de cadena "origin" o "parent", en el que podríamos escribir el nombre del símbolo real. Por defecto, se encontraría vacío. Pero cuando se rellenara, la plataforma reemplazaría automáticamente el símbolo en todas las órdenes comerciales y las solicitudes de la historia. Como este mecanismo no existe en la plataforma, tendremos que implementarlo nosotros mismos. Los nombres del símbolo fuente y el símbolo personalizado se establecerán con la ayuda de parámetros. Las propiedades personalizadas del símbolo disponen de un campo con un significado adecuado: SYMBOL_BASIS. Pero, como no podemos garantizar que los generadores arbitrarios de símbolos personalizados (cualquier programa MQL) rellenen correctamente el parámetro o lo usen exactamente para este cometido, necesitaremos implementar otra solución.

Para ello, hemos desarrollado la clase CustomOrder (ver el archivo CustomOrder.mqh, adjunto a continuación). Contiene métodos de envoltorio para todas las funciones de la API MQL relacionadas con el envío de las órdenes comerciales y solicitudes de la historia que contengan un parámetro de cadena con el nombre del símbolo del instrumento. Estos métodos sustituyen un símbolo personalizado por el símbolo de trabajo actual o viceversa. Las demás funciones de la API no requieren de "captación". A continuación, mostramos el código.

  class CustomOrder
  {
    private:
      static string workSymbol;
      
      static void replaceRequest(MqlTradeRequest &request)
      {
        if(request.symbol == _Symbol && workSymbol != NULL)
        {
          request.symbol = workSymbol;
          if(request.type == ORDER_TYPE_BUY
          || request.type == ORDER_TYPE_SELL)
          {
            if(request.price == SymbolInfoDouble(_Symbol, SYMBOL_ASK)) request.price = SymbolInfoDouble(workSymbol, SYMBOL_ASK);
            if(request.price == SymbolInfoDouble(_Symbol, SYMBOL_BID)) request.price = SymbolInfoDouble(workSymbol, SYMBOL_BID);
          }
        }
      }
      
    public:
      static void setReplacementSymbol(const string replacementSymbol)
      {
        workSymbol = replacementSymbol;
      }
      
      static bool OrderSend(MqlTradeRequest &request, MqlTradeResult &result)
      {
        replaceRequest(request);
        return ::OrderSend(request, result);
      }
      
      static bool OrderCalcProfit(ENUM_ORDER_TYPE action, string symbol, double volume, double price_open, double price_close, double &profit)
      {
        if(symbol == _Symbol && workSymbol != NULL)
        {
          symbol = workSymbol;
        }
        return ::OrderCalcProfit(action, symbol, volume, price_open, price_close, profit);
      }
      
      static string PositionGetString(ENUM_POSITION_PROPERTY_STRING property_id)
      {
        const string result = ::PositionGetString(property_id);
        if(property_id == POSITION_SYMBOL && result == workSymbol) return _Symbol;
        return result;
      }
      
      static string OrderGetString(ENUM_ORDER_PROPERTY_STRING property_id)
      {
        const string result = ::OrderGetString(property_id);
        if(property_id == ORDER_SYMBOL && result == workSymbol) return _Symbol;
        return result;
      }
      
      static string HistoryOrderGetString(ulong ticket_number, ENUM_ORDER_PROPERTY_STRING property_id)
      {
        const string result = ::HistoryOrderGetString(ticket_number, property_id);
        if(property_id == ORDER_SYMBOL && result == workSymbol) return _Symbol;
        return result;
      }
      
      static string HistoryDealGetString(ulong ticket_number, ENUM_DEAL_PROPERTY_STRING property_id)
      {
        const string result = ::HistoryDealGetString(ticket_number, property_id);
        if(property_id == DEAL_SYMBOL && result == workSymbol) return _Symbol;
        return result;
      }
      
      static bool PositionSelect(string symbol)
      {
        if(symbol == _Symbol && workSymbol != NULL) return ::PositionSelect(workSymbol);
        return ::PositionSelect(symbol);
      }
      
      static string PositionGetSymbol(int index)
      {
        const string result = ::PositionGetSymbol(index);
        if(result == workSymbol) return _Symbol;
        return result;
      }
      ...
  };
  
  static string CustomOrder::workSymbol = NULL;

Para reducir al mínimo las ediciones en el código fuente del cliente, ofrecemos las siguientes macros (para todos los métodos):

  bool CustomOrderSend(const MqlTradeRequest &request, MqlTradeResult &result)
  {
    return CustomOrder::OrderSend((MqlTradeRequest)request, result);
  }
  
  #define OrderSend CustomOrderSend

Estas permiten redirigir automáticamente todas las llamadas de las funciones API estándar a los métodos de la clase CustomOrder; lo único que debemos hacer es incluir CustomOrder.mqh en el asesor e indicar el símbolo de trabajo:

  #include <CustomOrder.mqh>
  #include <Expert\Expert.mqh>
  ...
  input string WorkSymbol = "";
  
  int OnInit()
  {
    if(WorkSymbol != "")
    {
      CustomOrder::setReplacementSymbol(WorkSymbol);
      
      // force a chart for the work symbol to open (in visual mode only)
      MqlRates rates[1];
      CopyRates(WorkSymbol, PERIOD_H1, 0, 1, rates);
    }
    ...
  }

Es importante que la directiva #include <CustomOrder.mqh> vaya en primer lugar, antes de todas las demás. Por consiguiente, afectará a todos los códigos fuente, incluidas las bibliotecas estándar conectadas. Si no especificamos símbolo comodín, el archivo CustomOrder.mqh incluido no tendrá ningún efecto en el asesor y transferirá el control a las funciones estándar de la API.

Hemos renombrado el asesor MA2Cross modificado como MA2CrossCustom.mq5.

Ahora, podemos establecer WorkSymbol en EURUSD, dejando igual todas las demás configuraciones y comenzando la simulación. Ahora, el asesor comercia realmente con EURUSD, aunque se ejecuta en el gráfico de símbolo de renko.

Resultado de la estrategia de cruzamiento de dos MA (MA2CrossCustom) en el gráfico renko de 100 puntos al operar con el símbolo EURUSD real

Resultado de la estrategia de cruzamiento de dos MA (MA2CrossCustom) en el gráfico renko de 100 puntos al operar con el símbolo EURUSD real

En esta ocasión, el resultado se acerca más a la realidad.

En las operaciones de EURUSD, los precios se distinguen de manera más significativa de los precios de cierre de la barra renko. Esto se debe a que las barras de renko siempre están marcadas por el comienzo del minuto (se trata de una limitación del marco temporal M1 en la plataforma), pero el precio cruza el borde de la barra en momentos aleatorios dentro del minuto. Como el asesor experto comercia en el gráfico en el modo barra por barra (que no debe confundirse con el modo de simulación), la aparición de la señal se "traslada" a la apertura de una barra de minutos de EURUSD, cuando el precio suele ser diferente. De media, el error supone la esperanza matemática del rango de barras de minutos por transacción.

Transacciones de EURUSD realizadas por el asesor experto a partir de un gráfico Renko derivado de EURUSD

Transacciones de EURUSD realizadas por el asesor experto a partir de un gráfico Renko derivado de EURUSD

Para eliminar las divergencias, el asesor tendría que procesar todos los ticks, pero ya hemos aclarado que la lógica de la generación de ticks en el simulador es diferente a la lógica de la formación de renko: en concreto, el precio de apertura de las barras de viraje siempre tiene una brecha igual a un bloque renko respecto al cierre de la barra anterior.

Este problema no existe en el comercio online.

Vamos a comprobar la funcionalidad de CustomOrder usando otro asesor experto escrito sin utilizar la biblioteca estándar. Para ello, usaremos el asesor ExprBot del artículo sobre el Cálculo de expresiones matemáticas, que también explota la estrategia de cruzamiento de dos MA y realiza transacciones comerciales usando la biblioteca MT4Orders. A continuación, adjuntamos el asesor experto ExprBotCustom.mq5 modificado, junto con los archivos de encabezado necesarios (carpeta ExpresSParserS).

Mostramos en el mismo intervalo de fechas de 2019-2020 (primera mitad del año) los resultados con la misma configuración (periodos 7/11, tipo de promedio LWMA y precios CLOSE en la 1ª barra).

Resultado de la estrategia de cruzamiento de dos MA (ExprBotCustom) en el gráfico renko de 100 puntos derivado de EURUSD

Resultado de la estrategia de cruzamiento de dos MA (ExprBotCustom) en el gráfico renko de 100 puntos derivado de EURUSD

Resultado de la estrategia de cruzamiento de dos MA (ExprBotCustom) en el gráfico renko de 100 puntos al operar con el símbolo EURUSD real

Resultado de la estrategia de cruzamiento de dos MA (ExprBotCustom) en el gráfico renko de 100 puntos al operar con el símbolo EURUSD real

Estos resultados son muy similares a los obtenidos con el asesor MA2CrossCustom.

Podemos deducir entonces que el enfoque propuesto resuelve el problema. No obstante, la actual implementación de CustomOrder es solo un mínimo básico. Es posible que necesitemos mejoras según la estrategia comercial y las características específicas del símbolo de trabajo.

Conclusión

Hemos analizado varias formas de generar símbolos personalizados usando las cotizaciones de los símbolos de trabajo ofrecidos por el bróker. La generalización de datos especiales y los algoritmos de acumulación permiten ver las cotizaciones habituales desde un ángulo distinto y construir sistemas comerciales avanzados usando estos como base.

Obviamente, los símbolos personalizados ofrecen muchas más posibilidades. Las potenciales aplicaciones de esta tecnología son mucho más amplias. Por ejemplo, podemos usar símbolos sintéticos, delta de volumen y fuentes de datos de terceros. La transformación del programa que hemos descrito permite utilizar estas capacidades en los gráficos estándar de MetaTrader 5, en el Simulador de estrategias y en el modo online.

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/8226

Archivos adjuntos |
MQL5CUST.zip (61.87 KB)
Redes neuronales: así de sencillo (Parte 2): Entrenamiento y prueba de la red Redes neuronales: así de sencillo (Parte 2): Entrenamiento y prueba de la red
En el presente artículo, proseguiremos nuestro estudio de las redes neuronales, iniciado en el artículo anterior, y analizaremos un ejemplo de uso en los asesores de la clase CNet que hemos creado. Asimismo, analizaremos dos modelos de red neuronal que han mostrado resultados semejantes tanto en su tiempo de entrenamiento, como en la precisión de sus predicciones.
Trabajando con las series temporales en la biblioteca DoEasy (Parte 49): Indicadores estándar de período, símbolo y búfer múltiples Trabajando con las series temporales en la biblioteca DoEasy (Parte 49): Indicadores estándar de período, símbolo y búfer múltiples
En el presente artículo, vamos a mejorar las clases de la biblioteca para tener la posibilidad de crear los indicadores estándar de período y símbolo múltiples que requieren varios búferes de indicador para visualizar sus datos.
Optimización paralela con el método de enjambre de partículas (Particle Swarm Optimization) Optimización paralela con el método de enjambre de partículas (Particle Swarm Optimization)
El presente artículo describimos un modo de optimización rápida usando el método de enjambre de partículas, y presentamos una implementación en MQL lista para utilizar tanto en el modo de flujo único dentro de un EA, como en el modo paralelo de flujo múltiples como un complemento ejecutado en los agentes locales del simulador.
Trabajando con las series temporales en la biblioteca DoEasy (Parte 51): Indicadores estándar compuestos de período y símbolo múltiples Trabajando con las series temporales en la biblioteca DoEasy (Parte 51): Indicadores estándar compuestos de período y símbolo múltiples
En este artículo, vamos a finalizar el desarrollo de indicadores estándar de período y símbolo múltiples. A base del indicador Ichimoku Kinko Hyo, vamos a analizar la creación de los indicadores personalizados de composición compleja que disponen de los búferes dibujados auxiliares para la visualización de los datos en el gráfico.