English Русский 中文 Deutsch 日本語 Português
Zigzag universal

Zigzag universal

MetaTrader 5Ejemplos | 15 diciembre 2016, 11:02
3 233 0
Dmitry Fedoseev
Dmitry Fedoseev

Índice

Introducción

El Zigzag (Fig. 1) es uno de los indicadores más populares entre los usuario de MetaTrader 5. Hasta este momento, ha sido desarrollada una gran cantidad de versiones de este indicador. Sin embargo, algunas de ellas son demasiado lentas, lo que las hace inadecuadas para la creación de los Asesores Expertos (EA). Otras versiones devuelven constantemente los errores, lo que complica su uso incluso para la observación visual. Con los indicadores que trabajan rápido y sin errores, igualmente surgen dificultades al usarlos para el desarrollo del EA u otro indicador. La cosa es que no es tan fácil extraer las indicaciones del Zigzag e interpretarlas.


Fig. 1. Indicador ZigZag

En este artículo vamos a reflejar sobre qué es lo que hace falta para la construcción del Zigzag, hablaremos de diferentes maneras de su construcción, sacaremos conclusiones y obtendremos un algoritmo común. A base de este algoritmo será creado un indicador universal que permitirá escoger diferentes tipos del Zigzag a través de la ventana de propiedades.

Durante la creación del indicador vamos a utilizar la programación orientada a objetos (POO). Crearemos algunas clases base para diferentes fases de la creación del Zigzag, para cada una de las cuales desarrollaremos varias clases hijas. La división en las clases base y las clases derivadas tendrá por objetivo simplificar al máximo la creación de nuevas versiones de los indicadores Zigzag.

Aparte de la construcción del propio indicador Zigzag, en este artículo dedicaremos atención al uso del indicador obtenido para el desarrollo de otros indicadores y EAs. Nuestra tarea consiste en conseguir que la obtención de los datos del Zigzag y su uso como parte de otros algoritmos no sea complicado y duradero.

Particularidades del indicador Zigzag

El indicador Zigzag (Fig. 1) es una línea quebrada que une los máximos y mínimos locales del precio. A los principiantes les puede surgir inmediatamente la idea: ¡Qué bien sería comprar en los valles y vender en los picos! Desde luego, esta idea parece muy tentadora, pero lamentablemente, tan atractivo el Zigzag se ve solamente en el historial. En realidad, la cosa es algo diferente. El hecho de que se ha formado un pico o un valles se hace evidente sólo después de una cierta cantidad de barras después. En la Fig. 2 se muestra la situación cuando el último segmento del indicador ha terminado su formación (cambio), el precio ha dado la vuelta y se mueve en dirección contraria (hacia arriba).

 
Fig. 2. El Zigzag está dirigido hacia abajo y el precio ha dado la vuelta hacia arriba

No obstante, después de unas barras, el precio cae (Fig. 3) y el último segmento del Zigzag continúa su movimiento hacia abajo.

  
Fig. 3. El precio ha continuado su movimiento hacia abajo y el último segmento ha seguido su formación

Esta vez el indicador ha alcanzado su mínimo, pero podemos tener certeza de ello sólo después de unas barras (Fig. 4).

 
Fig. 4. Sólo después de unas barras, el Zigzag ha trazado nuevo segmento hacia arriba y se ha conocido sobre la formación de un valle


La Fig. 5 da una explicación más completa de estas particularidades del Zigzag. Ahí los puntos de color se utilizan para marcar las barras en las que se ha sabido sobre la formación del pico o valle anterior. En las barras con el punto azul, el indicador ha empezado a dibujar nuevos segmentos hacia arriba, y en las barras con los puntos rojos, nuevos segmentos hacia abajo.


 Fig. 5. Los puntos rojos y azules marcan las barras donde se ha sabido sobre la reversa del Zigzag   

A pesar de esta particularidad, el Zigzag no pierde su popularidad y atractivo. Como mínimo, facilita considerablemente el análisis visual de los gráficos, ayuda a filtrar el ruido y resalta la trayectoria principal del precio. En sentido más práctico, este indicador puede usarse para detectar los niveles de soporte/resistencia y reconocer los patrones. Además, a su base se puede dibujar las líneas de tendencia, igual que cualquier otra herramienta gráfica del análisis técnico, como los niveles de Fibonacci, abanico de Fibonacci, etc. Es imposible enumerar todo lo que puede surgir en la mente rebuscada del trader respecto al uso del Zigzag.

Opciones de construcción del Zigzag

Es evidente que el Zigzag tiene dos estados: está dirigido o hacia arriba o hacia abajo. Cuando la línea del indicador está apuntada hacia arriba, monitoreamos el precio por si aparece un máximo nuevo, y cuando la línea «mira» hacia abajo, estamos a la espera de aparición de un mínimo nuevo.  Tenemos que monitorear también el cumplimiento de las condiciones que significan el cambio de la dirección. Entonces, a fin de construir un Zigzag, necesitamos tres cosas.

  1. Hace falta obtener los datos iniciales. 
  2. Hay que definir las condiciones del cambio de la dirección de la línea. 
  3. Es necesario monitorear la aparición de nuevos máximos y mínimos.

Los datos iniciales pueden suponer una serie, por ejemplo, el precio de cierre de la barra; o dos series, por ejemplo, el precio máximo y mínimo de la barra. Si se utiliza una serie de datos, eso puede ser no sólo el precio de cierre, sino también prácticamente cualquier indicador, sea un oscilador, sea la media móvil. Durante la construcción del Zigzag según los datos del indicador, también se puede usar dos series de datos: una serie con datos del indicador construido a base de los precios máximos de las barras, otro, a base de los precios mínimos. 

Las condiciones del cambio de la dirección es el punto más importante que determina diferentes tipos de Zigzags. Estas condiciones pueden ser muy diferentes. Por ejemplo, esta condición puede ser la formación del máximo/mínimo de la barra N en la barra emergente. En otras palabras, si el valor de la serie inicial en la barra emergente es máximo o mínimo durante las últimas N barras, eso determina la dirección del Zigzag. El indicador clásico Zigzag funciona de acuerdo con este principio. Otro método trata del tamaño del retroceso del valor máximo o mínimo fijado. El tamaño del retroceso puede medirse en puntos (si la serie inicial es el precio) o en unidades convencionales (se se trata de cuelquier indicador). Además de estos do métodos, se puede determinar la dirección usando cualquier indicador, sea un estocástico, sea un ADX. Si el estocástico está por encima de 50, entonces el Zigzag apunta hacia arriba, si esta por debajo de 50, hacia abajo. Ahora intentaremos determinar la dirección por ADX: la línea del Zigzag apunta hacia arriba si la línea PDI está por encima de la línea MDI, de lo contrario, hacia abajo. 

De esta manera, combinando varias opciones según el punto 1 y punto 2, obtenemos una cantidad suficiente de diferentes versiones del Zigzag, es que nada nos impide usar los datos según el punto 1, por ejemplo, desde RSI, y determinar la dirección según el estocástico, etc. El punto 3 es necesario para que el indicador tenga la forma del Zigzag, aunque las opciones del trazado pueden ser absolutamente diferentes. 

Puesto que nuestro objetivo consiste en obtener un indicador universal, tenemos que dividir el algoritmo en partes con el máximo cuidado: una parte es absolutamente idéntica para todos los Zigzags (vamos a llamarla común), otra parte depende del tipo del Zigzag (vamos a llamarla parte individual). En la parte individual los búferes de indicadores se llenan con datos iniciales: de precios o de indicadores, y otro búfer (el que determina la dirección de la línea del Zigzag) se llena con los valores 1 o -1. Estos tres búferes se transfieren a la parte común, que traza a su base el propio indicador.

Para que sea más claro, primero vamos a crear un indicador separado que usa los precios high/low y cambia su dirección según la condición del máximo/mínimo de la barra N.

Zigzag simple a base de high/low

Cree nuevo indicador en MetaEditor (Menú principal — Archivo — Crear o la combinación Ctrl+N. En el Asistente para la creación del nuevo indicador introduzca el nombre "iHighLowZigZag", crea un parámetro externo "period" (tipo int, valor 12), seleccione el manejador de eventos OnCalculate(...,open,high,low,close), cree un búfer con el nombre "ZigZag" (tipo Section, color Red) y tres búferes con los nombres "Direction", "LastHighBar" y "LastLowBar" (tipo line, color none).

El búfer "ZigZag" va a utilizarse para la visualización del Zigzag, los demás búferes son auxiliares. En la función OnInit() para los búferes auxiliares al llamar a la función SetIndexBuffer(), sustituya el tipo INDICATOR_DATA por INDICATOR_CALCULATIONS. En la parte superior del archivo, cambie el valor de la propiedad indicator_plots: ponga el valor 1. Después de eso, el indicador va a dibujar sólo un búfer "ZigZag" y en el gráfico no habrá ninguna línea sobrante, pero al mismo tiempo los búferes estarán disponibles para ser llamados a través de la función iCustom()

Primero, en la función OnCalculate(), calcularemos el índice de la barra a partir de la cual se debe iniciar el cálculo (la variable start), de tal manera que el cálculo de todas las barras se realice cuando se inicie el indicador, y luego se calcule solamente cada barra nueva. Aparte de eso, inicializamos los elementos iniciales de los búferes:

  int start; // variable para el índice de la barra a partir de la cual va a empezar el cálculo  if(prev_calculated==0)
    { // en el inicio
     // inicialización de los elementos iniciales de los búferes
     DirectionBuffer[0]=0;
     LastHighBarBuffer[0]=0;
     LastLowBarBuffer[0]=0;
     start=1; // cálculo a partir de los siguientes elementos después de los inicializados
    }
  else
    { // en el proceso de trabajo
     start=prev_calculated-1;
    }
}

Ahora el ciclo principal de indicador:

for(int i=start;i<rates_total;i++)
     {

Como ha sido escrito antes, para conseguir la universalidad, es necesario dividir el código en el cálculo de la dirección del Zigzag y en su trazado. Vamos a seguir este principio ahora también. Primero, escribiremos el código para determinar la dirección. Para determinar la dirección, usamos las funciones ArrayMaximum() y ArrayMinimum(). Si el máximo o el mínimo ha sido detectado en la barra calculada, al elemento del búfer Direction le asignamos el valor 1 o -1. Para disponer de la información sobre la dirección actual del Zigzag en cada barra, antes de determinar la dirección, tomaremos el valor desde el elemento anterior del búfer Direction y lo asignaremos al elemento actual:

// desde el elemento anterior del búfer obtenemos
// el valor de la dirección definida anteriormente
   DirectionBuffer[i]=DirectionBuffer[i-1];

// cálculo de la barra inicial para las funciones
// ArrayMaximum() y ArrayMinimum()
   int ps=i-period+1;
// determinación de las barras del máximo y mínimo en
// el intervalo period de barras
   int hb=ArrayMaximum(high,ps,period);
   int lb=ArrayMinimum(low,ps,period);

// si ha sido detectado el máximo o el mínimo
   if(hb==i && lb!=i)
     { // máximo detectado
      DirectionBuffer[i]=1;
     }
   else if(lb==i && hb!=i)
     { // mínimo detectado
      DirectionBuffer[i]=-1;
     }

Preste atención en la última parte del código: aquí se revela el mínimo o el máximo, se comprueba que en la barra actual exista el máximo sin la presencia del mínimo, o viceversa, que haya el mínimo sin la presencia del máximo. A veces, se encuentran las barras muy largas, y en ellas se determinan ambas direcciones. En este caso, en el búfer Direction va a estar la dirección determinada anteriormente.

Generalmente, en el terminal MetaTrader5 es posible crear un Zigzag que dibuja los segmentos verticales, pudiendo visualizar dos cambios de la dirección del indicador en una barra. No obstante, en el presente artículo no vamos a considerar los Zigzags de este tipo. 

Vamos a continuar escribiendo el código en el ciclo principal: el siguiente fragmento del código será responsable del trazado de las líneas del Zigzag. Igual como lo hemos hecho con el búfer Direction, hagamos con otros dos búfers:

LastHighBarBuffer[i]=LastHighBarBuffer[i-1];
LastLowBarBuffer[i]=LastLowBarBuffer[i-1];  

Estos búferes van a contener los datos sobre los índices de las La barras con el último máximo o el mínimo del Zigzag. Además de que los índices de estas barras son necesarias para el trazado del indicador, estos búferes también facilitan considerablemente el proceso de la llamada del Zigzag desde el Asesor Experto. No tendremos que repasar las barras en el ciclo en busca del último pico.

Procúrese de vaciar el búfer para el Zigzag:

ZigZagBuffer[i]=EMPTY_VALUE;  

Es necesario hacer porque el cálculo completo del indicador se realiza no sólo en el momento de su inicialización, sino también durante algunos otros eventos, por ejemplo, durante la carga del historial. En el búfer pueden quedarse los datos antiguos que van a desfigurar la apariencia de la línea del indicador. 

Ahora vamos directamente al trazado. Aquí el algoritmo se divide en cuatro ramas: inicio del nuevo movimiento hacia arriba, inicio del nuevo movimiento hacia abajo, continuación del movimiento hacia arriba, continuación del movimiento hacia abajo. Utilizamos los operadores switch para la verificación de los valores de la dirección en la barra calculada y en la barra anterior:

switch((int)DirectionBuffer[i])
  {
   case 1:
      switch((int)DirectionBuffer[i-1])
        {
         case 1:
            // continuación del movimiento ascendiente
            ...
            break;
         case -1:
            // inicio del movimiento ascendiente
            ...
            break;
        }
      break;
   case -1:
      switch((int)DirectionBuffer[i-1])
        {
         case -1:
            // continuación del movimiento descendiente
            ...
            break;
         case 1:
            // inicio del movimiento descendiente    
            ...
            break;
        }
      break;

Nos queda escribir cuatro bloques. Entraremos en detalles de dos de ellos: inicio del movimiento ascendiente y continuación del movimiento ascendiente. El inicio del movimiento ascendiente tiene lugar cuando en el búfer Direction el valor se cambia de -1 a 1. Al mismo tiempo, dibujamos el primer punto del Zigzag y guardamos la información sobre el índice de la barra en la que ha empezado la dirección nueva:

ZigZagBuffer[i]=high[i];
LastHighBarBuffer[i]=i;

La continuación del movimiento es un poco más complicada. Se verifica si el valor en la barra actual es más grande que el valor máximo conocido del Zigzag. Si es más grande, hay que desplazar el fin del último segmento, es decir, eliminar el punto dibujado anteriormente y colocar el nuevo. Aquí también guardamos la información sobre la barra en la que está dibujado el punto nuevo:

// continuación del movimiento hacia arriba
   if(high[i]>high[(int)LastHighBarBuffer[i]])
     { // nuevo máximo
      // eliminamos el punto antiguo del Zigzag
      ZigZagBuffer[(int)LastHighBarBuffer[i]]=EMPTY_VALUE;
      // colocamos el punto nuevo
      ZigZagBuffer[i]=high[i];
      // índice de la barra con el pico nuevo
      LastHighBarBuffer[i]=i;
     }

Eso es todo. No olvide cerrar el ciclo con la llave de cierre. Nos queda testear el indicador en el Probador de Estrategias en modo visual. El indicador totalmente hecho "iHighLowZigZag" se encuentra en el anexo.

Zigzag simple a base de high/low

Ahora vamos a rehacer el indicador recién creado para operar según el precio close. No es necesario hacer todo de nuevo: guardamos el indicador  "iHighLowZigZag" con el nombre "iCloseZigZag" y sustituimos las llamadas a los arrays high y low por la llamada al array close. Parece que eso es todo, pero el testeo mostrará anomalías en el trabajo del indicador (Fig. 6).

 
Fig. 6. Trabajo incorrecto del Zigzag a base de close recompuesto del indicador a base de high/low

Vamos a analizar por qué ocurre esto. Si el precio high de la barra emergente ha formado el máximo en algún intervalo de barras, independientemente de cómo va a cambiar el precio de cierre de la barra, este máximo va seguir siendo el máximo. Si el máximo ha sido formado por el precio de cierre, a lo largo de la siguiente formación de la barra este precio puede cambiar y el máximo dejará de existir. Durante la determinación del nuevo máximo/mínimo de la misma dirección, se hace la eliminación del punto antiguo: pues, aquí tenemos un problema. El nuevo máximo ha sido cancelado, el nuevo punto ha sido eliminado, pero el punto antiguo también ha sido eliminado. Entonces, tenemos que restaurar la posición del punto antiguo. La información sobre la posición de los últimos extremos se encuentra en los búferes: LastHighBarBuffer y LastLowBarBuffer. Vamos a usarlos para restaurar dos últimos puntos. Añadimos dos líneas del código al ciclo principal del indicador, antes de operador switch:

ZigZagBuffer[(int)LastHighBarBuffer[i]]=close[(int)LastHighBarBuffer[i]];
ZigZagBuffer[(int)LastLowBarBuffer[i]]=close[(int)LastLowBarBuffer[i]];  

Después de esta modificación, el indicador va a funcionar correctamente. Puede encontrar el indicador "iCloseZigZag" en los archivos adjuntos al artículo. 

Comienzo de creación del Zigzag universal

La universalidad del Zigzag será conseguida a través de la solución separada de tres tareas:
  1. Relleno de los búferes con datos iniciales. Van a usarse dos búferes. Son necesarios para poder llenarlos con los precios high y low. Para obtener el Zigzag a base de close o a base de algún otro indicador, se puede llenar los dos búferes con los mismos valores. 
  2. Relleno del búfer Direction a base del análisis de datos iniciales.
  3. Trazado del Zigzag.
Cada tarea será resuelta usando su clase base individual y las clases derivadas adicionales, lo que asegura la posibilidad de la elección de diferentes opciones y sus combinaciones a través de la ventana de propiedades del indicador.

Clase de datos iniciales

Cree el archivo de inclusión "CSorceData.mqh", incluya la clase CSorceData en él. Será la clase base. Va a contener un método virtual Calculate, semejante a la función OnCalculate() del indicador, pero con algunas modificaciones. En este método se pasan dos arrays adicionales: BufferHigh[] y BufferLow[]. Estos búferes se llenan con los datos a base de los cuales en va a calcularse el futuro el Zigzag. Puesto que a los datos iniciales puede pertenecer no sólo el precio, sino los valores de cualquier otro indicador, entonces es necesario realizar el control de la carga del indicador. Para eso adicionamos el método virtual CheckHandle() (tipo bool):

class CSorceData
  {
private:
public:
   virtual int Calculate(const int rates_total,
                         const int prev_calculated,
                         const datetime &time[],
                         const double &open[],
                         const double &high[],
                         const double &low[],
                         const double &close[],
                         const long &tick_volume[],
                         const long &volume[],
                         const int &spread[],
                         double &BufferHigh[],
                         double &BufferLow[])
     {
      return(0);
     }
   virtual bool CheckHandle()
     {
      return(true);
     }

  };

Ahora vamos a crear algunas clases derivadas. Una de ellas será para el precio high/low:

class CHighLow:public CSorceData
  {
private:
public:
   int Calculate(const int rates_total,
                 const int prev_calculated,
                 const datetime &time[],
                 const double &open[],
                 const double &high[],
                 const double &low[],
                 const double &close[],
                 const long &tick_volume[],
                 const long &volume[],
                 const int &spread[],
                 double &BufferHigh[],
                 double &BufferLow[])
     {
      int start=0;
      if(prev_calculated!=0)
        {
         start=prev_calculated-1;
        }
      for(int i=start;i<rates_total;i++)
        {
         BufferHigh[i]=high[i];
         BufferLow[i]=low[i];
        }
      return(rates_total);
     }
  };

 La segunda, para el precio close. Ella va a diferenciarse por el código en el ciclo:

for(int i=start;i<rates_total;i++)
  {
   BufferHigh[i]=close[i];
   BufferLow[i]=close[i];
  }

El nombre de esta clase es "CClose:public CSorceData". El método CheckHandle() no se utiliza por ahora.

Además, vamos a crear un par de clases para recibir los datos desde los indicadores. Vamos a escoger los indicadores con diferente cantidad de parámetros y diferente ubicación (en el gráfico del precio o en la subventana separada), RSI y media móvil. Escribiremos nuestras clases ajustándose a ellos.

Crearemos la clase para RSI, vamos a llamarla "CRSI:public CSorceData". Añadiremos una variable para el manejador del indicador a la sección private:

   private:
      int m_handle;

Añadiremos el constructor: le vamos a pasar los parámetros de RSI, y la carga del indicador va a realizarse en él:

void CRSI(int period,ENUM_APPLIED_PRICE price)
  {
   m_handle=iRSI(Symbol(),Period(),period,price);
  }

Ahora el método CheckHandle():

bool CheckHandle()
  {
   return(m_handle!=INVALID_HANDLE);
  }

No vamos a usar el ciclo en el método Calculate, simplemente hagamos el copiado de los búferes:

int to_copy;
   if(prev_calculated==0)
     {
      to_copy=rates_total;
     }
   else
     {
      to_copy=rates_total-prev_calculated;
      to_copy++;
     }

   if(CopyBuffer(m_handle,0,0,to_copy,BufferHigh)<=0)
     {
      return(0);
     }

   if(CopyBuffer(m_handle,0,0,to_copy,BufferLow)<=0)
     {
      return(0);
     }
   return(rates_total);

Atención: en caso del copiado fallido (llamada a la función CopyBuffer()), el método devuelve 0, y en caso del éxito, rates_total. Eso está hecho para que haya la posibilidad de recalcular el indicador en caso del copiado fallido. 

De la misma manera, crearemos la clase pare la media móvil con el nombre "CMA:public CSorceData". La diferencia estará sólo en el constructor:

void CMA(int period,int shift,ENUM_MA_METHOD method,ENUM_APPLIED_PRICE price)
   {
    m_handle=iMA(Symbol(),Period(),period,shift,method,price);
   }

En este caso, los métodos Calculate() han salido absolutamente idénticos, pero para otros indicadores pueden haber algunas diferencias, en particular, con los números de los búferes. Puede encontrar el archivo totalmente hecho "CSorceData.mqh" en el anexo. Aunque hagamos reservas, se puede considerarlo totalmente hecho puramente de forma convencional, puesto que él supone la posterior extensión mediante la adición de los métodos derivados para otros indicadores.

Clase de dirección

La clase va a ubicarse en el archivo "CZZDirection.mqh", el nombre de la clase base es "CZZDirection". La clase va a tener el método virtual Calculate() en el que se pasan los parámetros que permiten determinar las barras para el cálculo (variables rates_total, prev_calculated), los búferes con datos iniciales y el búfer para la dirección. Antes ya hemos mencionado que la dirección para el Zigzag puede ser identificada según el indicador, por eso vamos a asegurar la posibilidad de usar los indicadores. Añadimos el método virtual CheckHandle():

class CZZDirection
  {
private:
public:
   virtual int Calculate(const int rates_total,
                         const int prev_calculated,
                         double &BufferHigh[],
                         double &BufferLow[],
                         double &BufferDirection[])
     {
      return(0);
     }
   virtual bool CheckHandle()
     {
      return(true);
     }
  };

Ahora escribiremos la clase derivada para determinar la dirección, como en el indicador "iHighLowZigZag". Para determinar la dirección según este método, vamos a necesitar el parámetro "period", por eso añadiremos la variable m_period y el constructor con el parámetro del período en la sección private:

class CNBars:public CZZDirection
  {
private:
   int               m_period;
public:
   void CNBars(int period)
     {
      m_period=period;
     }
   int Calculate(const int rates_total,
                 const int prev_calculated,
                 double &BufferHigh[],
                 double &BufferLow[],
                 double &BufferDirection[]
                 )
     {
      int start;

      if(prev_calculated==0)
        {
         BufferDirection[0]=0;
         start=1;
        }
      else
        {
         start=prev_calculated-1;
        }

      for(int i=start;i<rates_total;i++)
        {

         BufferDirection[i]=BufferDirection[i-1];

         int ps=i-m_period+1;
         int hb=ArrayMaximum(BufferHigh,ps,m_period);
         int lb=ArrayMinimum(BufferLow,ps,m_period);

         if(hb==i && lb!=i)
           { // máximo detectado
            BufferDirection[i]=1;
           }
         else if(lb==i && hb!=i)
           { // mínimo detectado
            BufferDirection[i]=-1;
           }

        }
      return(rates_total);
     }

Crearemos otra clase derivada para determinar la dirección según el indicador CCI. La posición del CCI por encima de cero va a corresponder a la dirección del Zigzag hacia arriba, la posición por debajo de cero, a la dirección hacia abajo.

class CCCIDir:public CZZDirection
   {
private:
    int               m_handle;
public:
    void CCCIDir(int period,ENUM_APPLIED_PRICE price)
      {
       m_handle=iCCI(Symbol(),Period(),period,price);
      }
    bool CheckHandle()
      {
       return(m_handle!=INVALID_HANDLE);
      }
    int Calculate(const int rates_total,
                  const int prev_calculated,
                  double &BufferHigh[],
                  double &BufferLow[],
                  double &BufferDirection[]
                  )
      {
       int start;
       if(prev_calculated==0)
         {
          BufferDirection[0]=0;
          start=1;
         }
       else
         {
          start=prev_calculated-1;
         }

       for(int i=start;i<rates_total;i++)
         {

          BufferDirection[i]=BufferDirection[i-1];

          double buf[1];
          if(CopyBuffer(m_handle,0,rates_total-i-1,1,buf)<=0)return(0);

          if(buf[0]>0)
            {
             BufferDirection[i]=1;
            }
          else if(buf[0]<0)
            {
             BufferDirection[i]=-1;
            }
         }
       return(rates_total);
      }
   };

El constructor de la clase recibe los parámetros de CCI y se realiza su carga. Se utiliza el método CheckHandle(), hay que llamarlo después de la creación del objeto. En el ciclo principal se realiza la comprobación del CCI y el llenado del búfer BufferDirection.

Puede encontrar el archivo "CZZDirection.mqh" entre los archivos adjuntos al artículo. 

Clase de dibujo

Existen varias opciones para dibujar el Zigzag. Se puede trazarlo usando una sola línea, se puede colorear, colocar puntos en los picos, etc. En este artículo nos limitaremos a una forma del diseño, pero también crearemos la clase base y las clases derivadas para el caso de su modificación en el futuro. La clase va a ubicarse en el archivo "CZZDraw.mqh", el nombre de la clase es "CZZDraw". La clase tendrá un método virtual Calculate() con los mismos parámetros que la clase de dirección. Además de eso, va a recibir tres arrays para el Zigzag: BufferLastHighBar (para el índice del último máximo), BufferLastLowBar (para el índice del último mínimo), BufferZigZag (el propio Zigzag). 

class CZZDraw
  {
private:
public:
   virtual int Calculate(const int rates_total,
                         const int prev_calculated,
                         double &BufferHigh[],
                         double &BufferLow[],
                         double &BufferDirection[],
                         double &BufferLastHighBar[],
                         double &BufferLastLowBar[],
                         double &BufferZigZag[]
                         )
     {
      return(0);
     }
  };
La clase derivada: 
class CSimpleDraw:public CZZDraw
   {
private:
public:
    virtual int Calculate(const int rates_total,
                          const int prev_calculated,
                          double &BufferHigh[],
                          double &BufferLow[],
                          double &BufferDirection[],
                          double &BufferLastHighBar[],
                          double &BufferLastLowBar[],
                          double &BufferZigZag[]
                          )
      {
       int start;
       if(prev_calculated==0)
         {
          BufferLastHighBar[0]=0;
          BufferLastLowBar[0]=0;
          start=1;
         }
       else
         {
          start=prev_calculated-1;
         }

       for(int i=start;i<rates_total;i++)
         {
          BufferLastHighBar[i]=BufferLastHighBar[i-1];
          BufferLastLowBar[i]=BufferLastLowBar[i-1];

          BufferZigZag[i]=EMPTY_VALUE;

          BufferZigZag[(int)BufferLastHighBar[i]]=BufferHigh[(int)BufferLastHighBar[i]];
          BufferZigZag[(int)BufferLastLowBar[i]]=BufferLow[(int)BufferLastLowBar[i]];

          switch((int)BufferDirection[i])
            {
             case 1:
                switch((int)BufferDirection[i-1])
                  {
                   case 1:
                      if(BufferHigh[i]>BufferHigh[(int)BufferLastHighBar[i]])
                        {
                         BufferZigZag[(int)BufferLastHighBar[i]]=EMPTY_VALUE;
                         BufferZigZag[i]=BufferHigh[i];
                         BufferLastHighBar[i]=i;
                        }
                      break;
                   case -1:
                      BufferZigZag[i]=BufferHigh[i];
                      BufferLastHighBar[i]=i;
                      break;
                  }
                break;
             case -1:
                switch((int)BufferDirection[i-1])
                  {
                   case -1:
                      if(BufferLow[i]<BufferLow[(int)BufferLastLowBar[i]])
                        {
                         BufferZigZag[(int)BufferLastLowBar[i]]=EMPTY_VALUE;
                         BufferZigZag[i]=BufferLow[i];
                         BufferLastLowBar[i]=i;
                        }
                      break;
                   case 1:
                      BufferZigZag[i]=BufferLow[i];
                      BufferLastLowBar[i]=i;
                      break;
                  }
                break;
            }
         }
       return(rates_total);
      }
   };
No vamos a analizar esta clase al detalle, porque todo eso ya ha sido descrito en las secciones «Zigzag simple a base de high/low» y «Zigzag simple a base de close». Puede encontrar el archivo "CZZDraw.mqh" entre los archivos adjuntos al artículo.

Reunimos tres clases juntos

Finalmente, nos queda escribir el indicador con el uso de tres clases creadas anteriormente. La clase de los datos iniciales asegura la posibilidad de usar los datos del precio y los datos del indicador RSI, que suele trabajar en una subventana. Los datos del precio se puede mostrar en una subventana, pero no se puede mostrar el indicador RSI en el gráfico del precio. Entonces, vamos a crear el indicador para la subventana. 

Cree nuevo indicador en MetaEditor (Menú principal — Archivo — Crear o la combinación Ctrl+N). En el Asistente para la creación del indicador nuevo introduzca el nombre "iUniZigZagSW", crea un parámetro externo "period" (tipo int, valor 12), seleccione el manejador de eventos OnCalculate(...,open,high,low,close), cree los siguientes búferes: 

NombreEstiloColor
HighLineGreen
LowLineGreen
ZigZagSect ionRed
DirectionLinenone
LastHighBarLine none 
LastLowBarLinenone
Despué s de la creación del indicador nuevo, incluimos tres archivos con las clases en él: 

#include <CSorceData.mqh>
#include <CZZDirection.mqh>
#include <CZZDraw.mqh>

El indicador debe tener los parámetros para para seleccionar el tipo de datos iniciales y el tipo de determinación de la dirección. Crearemos dos enumeraciones para eso:

enum ESorce
  {
   Src_HighLow=0,
   Src_Close=1,
   Src_RSI=2,
   Src_MA=3
  };
enum EDirection
  {
   Dir_NBars=0,
   Dir_CCI=1
  };

Crearemos dos parámetros externos de estos tipos: 

input ESorce      SrcSelect=Src_HighLow;
input EDirection  DirSelect=Dir_NBars;

Hacen falta los parámetros correspondientes para los datos iniciales de RSI y МА igual que para el indicador CCI. Vamos a añadirlos:

input int                  RSIPeriod   =  14;
input ENUM_APPLIED_PRICE   RSIPrice    =  PRICE_CLOSE;
input int                  MAPeriod    =  14;
input int                  MAShift     =  0;
input ENUM_MA_METHOD       MAMethod    =  MODE_SMA;
input ENUM_APPLIED_PRICE   MAPrice     =  PRICE_CLOSE;
input int                  CCIPeriod   =  14;
input ENUM_APPLIED_PRICE   CCIPrice    =  PRICE_TYPICAL;

Además, necesitamos un parámetro para determinar la dirección para n-bars:

input int                  ZZPperiod   =  14;

Ahora algo más interesante, son tres punteros de acuerdo con los tipos de las clases base (debajo de los parámetros externos):

CSorceData * src;
CZZDirection * dir;
CZZDraw * zz;

En la función OnInit, de acuerdo con la selección de las variables SrcSelect y DirSelect, cargamos las clases derivadas correspondientes. Primero, SrcSelect:

switch(SrcSelect)
  {
   case Src_HighLow:
      src=new CHighLow();
      break;
   case Src_Close:
      src=new CClose();
      break;
   case Src_RSI:
      src=new CRSI(RSIPeriod,RSIPrice);
      break;
   case Src_MA:
      src=new CMA(MAPeriod,MAShift,MAMethod,MAPrice);
      break;
  }

Después de la carga, comprobamos el manejador:

if(!src.CheckHandle())
  {
   Alert("Error de la carga del indicador");
   return(INIT_FAILED);
  }

Luego, DirSelect:

switch(DirSelect)
  {
   case Dir_NBars:
      dir=new CNBars(ZZPeriod);
      break;
   case Dir_CCI:
      dir=new CCCIDir(CCIPeriod,CCIPrice);
      break;
  }

Comprobación del manejador:

if(!dir.CheckHandle())
  {
   Alert("Error de la carga del indicador 2");
   return(INIT_FAILED);
  }

La tercera clase:

zz = new CSimpleDraw();

Eliminamos los objetos en la función OnDeinit():

void OnDeinit(const int reason)
  {
   if(CheckPointer(src)==POINTER_DYNAMIC)
     {
      delete(src);
     }
   if(CheckPointer(dir)==POINTER_DYNAMIC)
     {
      delete(dir);
     }
   if(CheckPointer(zz)==POINTER_DYNAMIC)
     {
      delete(zz);
     }
  }

Y finalmente, hacemos los retoques finales, pasamos a la función OnCalculate(). Los métodos Calculate() de las clases CSorceData y CZZDirection pueden devolver 0, por eso comprobamos el resultado. En caso del error (valor obtenido es 0), también devolvemos 0 para que en el siguiente tick se haga un recálculo completo:

int rv;

rv=src.Calculate(rates_total,
                 prev_calculated,
                 time,
                 open,
                 high,
                 low,
                 close,
                 tick_volume,
                 volume,
                 spread,
                 HighBuffer,
                 LowBuffer);

if(rv==0)return(0);

rv=dir.Calculate(rates_total,
                 prev_calculated,
                 HighBuffer,
                 LowBuffer,
                 DirectionBuffer);

if(rv==0)return(0);

zz.Calculate(rates_total,
             prev_calculated,
             HighBuffer,
             LowBuffer,
             DirectionBuffer,
             LastHighBarBuffer,
             LastLowBarBuffer,
             ZigZagBuffer);

return(rates_total);

Puede encontrar el indicador "iUniZigZagSW" entre los archivos adjuntos al artículo.

Versión para el gráfico de precios

Todas las versiones creadas anteriormente están disponibles en el indicador resultante, tanto con la fuente de datos que corresponden al gráfico de precios, como para la subventana, por eso ha sido creado para la subventana. Estaría bien ver el Zigzag en el gráfico de precios. En este caso, habría que sacrificar la fuente de datos de RSI. Hacemos una copia del indicador con el nombre "iUniZigZag", reemplzamos la propiedda indicator_separate_window por indicator_chart_window, eliminamos la opción Src_RSI de la enumeración ESorce, eliminamos la opción RSI de la función OnInit() y obtenemos la versión para el gráfico de precios. Puede encontrar el indicador "iUniZigZag" entre los archivos adjuntos al artículo.  

Versión para price

Par el terminal MetaTrader, es posible crear los indicadores que trabajan no con los datos iniciales estrictamente definidos, sino a base de cualquier otro indicador ubicado en el gráfico. Al insertar este indicador en el gráfico o en la subventana, como parámetro «aplicar a» debe ser la opción «datos del indicador anterior» o «datos del primer indicador». Vamos a modificar el indicador "iUniZigZagSW» de tal manera que se pueda adjuntarlo a otro indicador. Guardamos el indicador con el nombre "iUniZigZagPriceSW" y eliminamos todo lo que está relacionado con la clase CSorceData, cambiamos el tipo de la función OnCalculate, y al principio de la función escribimos el ciclo para el llenado de los búferes HighBuffer y LowBuffer con los valores del array price:

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[]
                )
  {
   int start;
   if(prev_calculated==0)
     {
      start=0;
     }
   else
     {
      start=prev_calculated-1;
     }

   for(int i=start;i<rates_total;i++)
     {
      HighBuffer[i]=price[i];
      LowBuffer[i]=price[i];
     }
   int rv;
   rv=dir.Calculate(rates_total,
                    prev_calculated,
                    HighBuffer,
                    LowBuffer,
                    DirectionBuffer);

   if(rv==0)return(0);
   zz.Calculate(rates_total,
                prev_calculated,
                HighBuffer,
                LowBuffer,
                DirectionBuffer,
                LastHighBarBuffer,
                LastLowBarBuffer,
                ZigZagBuffer);
   return(rates_total);
  }

De la misma manera, se puede crear la versión que trabaja a base de price en el gráfico de precios. Para eso, en el indicador "iUniZigZagPriceSW" basta con reemplazar la propiedad indicator_separate_window por indicator_chart_window. Puede encontrar el indicador "iUniZigZagPriceSW" en el anexo, ahí también hay el indicador iUniZigZagPrice, la versión a base de price para el gráfico de precios. 

Llamada desde el Asesor Experto

Normalmente, al llamar al Zigzag desde el EA, se realiza la búsqueda del último pico o valle a través del ciclo, se realiza el repaso de las barras y la comprobación de los valores en el búfer que dibuja el Zigzag. Todo eso en conjunto trabaja muy lento. El Zigzag desarrollado en este artículo tiene los búferes adicionales que permiten obtener rápidamente todos los datos necesarios. En el búfer DirectionBuffer se encuentran los datos sobre la dirección del último segmento del Zigzag. Los búferes LastHighBarBuffer y LastLowBarBuffer contienen los índices de las barras en las que está marcado el último pico y el último valle. Sabiendo el índice de la barra al contar de un lado y el número de las barras, se puede calcular el índice de la barra al contar de otro lado (en el indicador el recuento se hace de izquierda a derecha, y la función CopyBuffer() trabaja con el recuento de derecha a izquierda) Teniendo el índice de la barra, se puede obtener el valor del Zigzag en esta barra.

Para obtener los datos desde el indicador, se puede usar el código siguiente. Vamos a experimentar con el indicador "iUniZigZagSW". Cargamos el indicador en la función OnInit():

handle=iCustom(Symbol(),Period(),"iUniZigZagSW",SrcSelect,
               DirSelect,
               RSIPeriod,
               RSIPrice,
               MAPeriod,
               MAShift,
               MAMethod,
               MAPrice,
               CCIPeriod,
               CCIPrice,
               ZZPeriod);
En la función OnTick(), obtenemos la dirección y la mostramos en el comentario del gráfico:
   string cs="";
// dirección
   double dir[1];
   if(CopyBuffer(handle,3,0,1,dir)<=0)
     {
      Print("Error al obtener los datos desde el Zigzag");
      return;
     }
   if(dir[0]==1)
     {
      cs=cs+"Dirección ascendiente";
     }
   if(dir[0]==-1)
     {
      cs=cs+"Dirección descendiente";
     }
   Comment(cs,"\n",GetTickCount());

Ahora obtenemos los valores de algunos últimos picos/valles. Si la línea del indicador apunta hacia arriba, obtendremos el índice de la barra con el último pico desde el búfer LastHighBarBuffer. Luego, lo usamos para calcular el índice de la barra al contar de derecha a izquierda. Usando este índice, obtenemos el valor del búfer ZigZagBuffer. Podemos ir más allá: para la misma barra en la que hemos obtenido el valor del Zigzag, podemos obtener el valor desde el búfer LastLowBarBuffer. Este será el índice de la barra con el valle anterior. Y así, alterando las llamada a los búferes LastHighBarBuffer y LastLowBarBuffer, podemos recopilar los datos sobre todos los picos/valles de la línea del indicador. Abajo se muestra el ejemplo del código para la obtención de dos últimos puntos del Zigzag cuando está apuntado hacia arriba: 

if(dir[0]==1)
  {
   // índice de la barra del último pico al contar de cero de izquierda
   if(CopyBuffer(handle,4,0,1,lhb)<=0)
     {
      Print("Error al obtener los datos desde el Zigzag 2");
      return;
     }
   // índice de la barra al contar de derecha de cero
   ind=bars-(int)lhb[0]-1;

   // valor del Zigzag en la barra ind
   if(CopyBuffer(handle,2,ind,1,zz)<=0)
     {
      Print("Error al obtener los datos desde el Zigzag 3");
      return;
     }
   //===
   // índice del valle anterior a este pico
   if(CopyBuffer(handle,5,ind,1,llb)<=0)
     {
      Print("Error al obtener los datos desde el Zigzag 4");
      return;
     }
   // índice de la barra al contar de derecha de cero
   ind=bars-(int)llb[0]-1;

   // valor del Zigzag en la barra ind
   if(CopyBuffer(handle,2,ind,1,zz1)<=0)
     {
      Print("Error al obtener los datos desde el Zigzag 5");
      return;
     }

   cs=cs+"\n"+(string)zz1[0]+" "+(string)zz[0];
  }
else if(dir[0]==-1)
  {

  }

El ejemplo completo se encuentra en el anexo en el EA con el nombre "eUniZigZagSW". En el comentario del gráfico, el EA muestra el mensaje sobre la dirección del Zigzag, en la segunda línea se muestran dos números con los valores de dos últimos puntos del Zigzag (Fig. 7). La tercera línea simplemente contiene un número devuelto por la función GetTickCount(), para que esté claro que el EA está trabajando.

 
Fig. 7. La esquina izquierda contiene el mensaje que muestra el EA

Naturalmente, los datos sobre dos últimos puntos del indicador pueden ser obtenidos desde los búferes LastHighBarBuffer y LastLowBarBuffer, tomando sus valores en la barra cero o en la primera barra, pero el sentido de este ejemplo consiste en la extracción consecutiva de los datos desde cualquier número de puntos del Zigzag.

Llamada desde otro indicador

Si necesitamos hacer un indicador a base de otro, en caso con el Zigzag es más fácil hacerlo sin llamar al indicador a través de iCustom(), sino hacer su copia y modificarla un poco. En algunas ocasiones, este enfoque puede ser justificado (desde el punto de vista de la velocidad y la sencillez de la modificación), en otras, no es así (desde el punto de vista del uso repetido y universalidad del código). Los indicadores creados en este artículo permiten acceder a ellos a través de la función iCustom durante el desarrollo de otros indicadores.

Por sí mismo, el Zigzag en el historial no es lo mismo lo que era durante la formación de este historial. Sin embargo, tenemos los búferes LastHighBarBuffer y LastLowBarBuffer en los cuales se almacenan los datos sobre los estados intermedios del Zigzag. Para que esté más claro, escribiremos un indicador que dibuja las flechas durante el cambio de la dirección de la línea del indicador (cambio del valor del búfer DirectionBuffer), y que marca los puntos en las barras en las cuales han sido registrados nuevos máximos/mínimos del Zigzag (cambio de los valores de los búferes LastHighBarBuffer y LastLowBarBuffer). No vamos a considerar detalladamente el código de este indicador, ya que se encuentra en el anexo bajo el nombre "iUniZigZagSWEvents". Este tipo del indicador se muestra en la Fig. 8.

 
Fig. 8. Indicador iUniZigZagSWEvents

Conclusión

Puesto que el artículo es un material de estudio y no la entrega de soluciones hechas y totalmente acabadas, todos los indicadores creados en este artículo poseen el conjunto mínimo de datos iniciales y tipos de determinación de la dirección. No obstante, el proceso de la creación de los indicadores ha sido considerado muy detalladamente, así que después de estudiar el presente artículo, Usted podrá crear pos sí mismo las clases derivadas que necesita. Además, durante el intento de obtener un indicador totalmente universal, surgen dificultades relacionadas no tanto con el proceso de su creación, como con su uso posterior. Al añadir diferentes indicadores como fuentes de datos o para determinación de la dirección, es necesario añadir los parámetros de estos indicadores a la ventana de propiedades. Al fin y al cabo, el número de parámetros se hace muy grande, y será muy incómodo utilizar este indicador. Será más ergonómico crear indicadores separados, usando las clases universales obtenidas en este artículo.      

Archivos adjuntos

  • iHighLowZigZag.mq5 — Zigzag simple a base de high/low.
  • iCloseZigZag.mq5 — Zigzag simple a base de close.
  • CSorceData.mqh — clase para la elección de datos iniciales.
  • CZZDirection.mqh — clase para determinar la dirección del Zigzag.
  • CZZDraw.mqh — clase de dibujo del Zigzag.
  • iUniZigZagSW.mq5 — Zigzag universal para la subventana.
  • iUniZigZag.mq5 — Zigzag universal para el gráfico de precios.
  • iUniZigZagPriceSW.mq5 — Zigzag universal a base de price para la subventana.
  • iUniZigZagPrice.mq5 — Zigzag universal a base de price para el gráfico de precios. 
  • eUniZigZagSW — ejemplo de la llamada al indicador "iUniZigZagSW" desde el EA a través de la función iCustom().
  • iUniZigZagSWEvents — ejemplo de la creación de otro indicador con la llamada al indicador "iUniZigZagSW" a través de la función iCustom(). 

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

Archivos adjuntos |
mql5.zip (18.42 KB)
LifeHack para tráders: Informe comparativo de varias simulaciones LifeHack para tráders: Informe comparativo de varias simulaciones
En el artículo se analiza el inicio simultáneo de la simulación del asesor en cuatro símbolos diferentes. La comparación final de los cuatro informes de la simulación se realizará en un recuadro, como sucede al elegir los productos en una tienda online. Como bonus adicional, se muestran los gráficos de distribución creados de forma automática para cada símbolo.
Estrategia de trading '80-20' Estrategia de trading '80-20'
En este artículo se describe la creación de las herramientas (indicador y Asesor Experto) para el análisis de la estrategia comercial '80-20'. Las reglas de esta Estrategia Comercial han sido tomadas del libro titulado «Street Smarts: High Probability Short-Term Trading Strategies» escrito por Linda Raschke y Laurence Connors. Las reglas han sido formalizadas en el lenguaje MQL5, y el indicador y el Asesor Experto diseñados a base de esta estrategia han sido probados en el historial actual del mercado.
Ejemplo de desarrollo de una estrategia con spread para futuros en la bolsa de Moscú Ejemplo de desarrollo de una estrategia con spread para futuros en la bolsa de Moscú
MetaTrader 5 permite desarrollar y simular robots que comercien simultáneamente en varios instrumentos. El simulador de estrategias incorporado en la plataforma descarga de forma automática del servidor comercial del bróker la historia de ticks y tiene en cuenta las especificaciones de los contratos: el desarrollador no tiene que hacer nada con sus propias manos. Esto permite reproducir todas las condiciones del entorno comercial de forma fácil y extraordinariamente fiable. MetaTrader 5 permite desarrollar y poner a prueba robots, incluso simulando intervalos de milisegundos entre la llegada de ticks de diferentes símbolos. En este artículo mostraremos cómo realizar el desarrollo y la simulación de una estretegia de spread con dos futuros de la bolsa de Moscú.
Sistema comercial 'Turtle Soup' y su modificación 'Turtle Soup Plus One' Sistema comercial 'Turtle Soup' y su modificación 'Turtle Soup Plus One'
En este artículo han sido formalizadas y programadas las reglas de las estrategias comerciales llamadas «Turtle Soup» y «Turtle Soup Plus One» del libro titulado «Street Smarts: High Probability Short-Term Trading Strategies», escrito por Linda Raschke y Laurence Connors. Las estrategias descritas en este libro recibieron bastante amplia acogida, pero es importante comprender que sus autores las ideaban basándose en el comportamiento del mercado de hace 15-20 años.