El poder del ZigZag (Parte I). Desarrollando la clase base del indicador

3 mayo 2019, 09:51
Anatoli Kazharski
0
568

Contenido


Introducción

En uno de los artículos anteriores, mostramos las diferentes formas de presentación del indicador Relative Strength Index (RSI). En una de sus versiones, el resultado obtenido se puede usar para recibir de forma simultánea señales de tendencia y de flat. En este indicador, quizá falta una cosa: la posibilidad de determinar el carácter del comportamiento del precio, lo cual también puede ser muy importante a la hora de toma decisiones sobre cuándo se debe comerciar y cuándo se debe detener el comercio. 

Muchos investigadores ignoran la determinación del carácter del precio, o bien no le prestan la suficiente atención. En este caso, además, se usan métodos complejos que con frecuencia son simplemente «cajas negras», tales como: aprendizaje de máquinas o redes neuronales. En estos casos, lo más importante es: «¿Qué datos suministrar a la entrada para el entrenamiento de este u otro modelo?» En este artículo, ampliaremos el instrumental para dichas investigaciones. Mostraremos cómo elegir los símbolos más adecuados para comerciar, antes de comenzar la búsqueda de los parámetros óptimos. Para ello, usaremos una versión modificada de ZigZag y una clase de código con cuya ayuda se simplifica notablemente la obtención y el trabajo con datos de este tipo.

En esta serie de artículos implementaremos:

  • una versión modificada del indicador ZigZag
  • una clase para obtener los datos de ZigZag
  • un experto para simular la obtención de datos
  • indicadores con cuya ayuda se pueda determinar el carácter del comportamiento del precio
  • un experto con interfaz gráfica para la recopilación de ciertas estadísticas de comportamiento del precio
  • un experto comercial que opere según las señales del indicador ZigZag


Versión extendida del indicador ZigZag

Normalmente, el indicador ZigZag se construye según los máximos y mínimos de las barras, sin tener en cuenta el spread. En este artículo, mostraremos una versión modificada en la que el spread se tendrá en cuenta en la construcción de segmentos para los extremos inferiores del ZigZag. Se presupone que en el sistema comercial, las transacciones se realizarán dentro del canal de precio. Esto es importante, ya que sucede con frecuencia (por ejemplo, de noche) que el precio de compra (ask) supera sustancialmente el precio de venta (bid), por ello, construir un indicador solo según los precios bid en este caso sería incorrecto. Y es que no tiene sentido construir los extremos inferiores del indicador según los mínimos de las barras si no existe la posibilidad de realizar la compra a estos precios. Claro que podemos tener en cuenta el spread en las condiciones comerciales, pero lo mejor es que todo se vea directamente en el gráfico. Esto simplificará el desarrollo de la estrategia comercial, ya que todo será más verosímil desde el principio.

Además, estaría bien ver todos los puntos en los que los extremos del ZigZaga se han actualizado. Entonces, la imagen general estaría más completa. Vamos a ver a continuación el código de este indicador. Nos detendremos solo en las características y funciones principales.

Para construir los segmentos, necesitaremos dos búferes de indicador. Uno para los máximos, y el segundo para los mínimos. Pero, en este caso, esto se representará en el gráfico como una sola línea. Por ello, vamos a necesitar en total seis búferes de indicador, mientras que dibujaremos cinco.

Vamos a enumerar los búferes de indicador:

  • Precio Ask mínimo. Según estos valores se construirán los mínimos del ZigZag
  • Precio Bid máximo. Según estos valores se construirán los máximos del ZigZag
  • Máximos
  • Mínimos
  • Todos los máximos del segmento registrados están dirigidos hacia arriba
  • Todos los mínimos del segmento registrados están dirigidos hacia abajo

#property indicator_chart_window
#property indicator_buffers 6
#property indicator_plots   5
//---
#property indicator_color1  clrRed
#property indicator_color2  clrCornflowerBlue
#property indicator_color3  clrGold
#property indicator_color4  clrOrangeRed
#property indicator_color5  clrSkyBlue

//--- Búferes de indicador:
double low_ask_buffer[];    // Precio Ask mínimo
double high_bid_buffer[];   // Precio Bid máximo
double zz_H_buffer[];       // Máximos
double zz_L_buffer[];       // Mínimos
double total_zz_h_buffer[]; // Todos los máximos
double total_zz_l_buffer[]; // Todos los mínimos

Haremos de tal forma que en los parámetros externos se pueda indicar el número de barras (NumberOfBars) para la construcción de las líneas de indicador. El valor cero indica que se usarán todos los datos disponibles en el gráfico. El parámetro MinImpulseSize establece cuántos puntos deberá desviarse el precio respecto al último extremo para comenzar a construir un segmento dirigido en la dirección contraria. Como parámetros adicionales, añadiremos la posibilidad de configurar qué búferes de indicador se mostrarán en el gráfico y de qué color serán los segmentos del ZigZag.

//--- Parámetros externos
input int   NumberOfBars   =0;       // Number of bars
input int   MinImpulseSize =100;     // Minimum points in a ray
input bool  ShowAskBid     =false;   // Show ask/bid
input bool  ShowAllPoints  =false;   // Show all points
input color RayColor       =clrGold; // Ray color

Declaramos a nivel global las variables auxiliares que necesitaremos para calcular los extremos. Debemos recordar los índices de los extremos calculados anteriormente y monitorear la dirección actual del segmento, así como guardar el precio mínimo del precio ask y el precio máximo del precio bid.

//--- Variables para ZZ
int    last_zz_max  =0;
int    last_zz_min  =0;
int    direction_zz =0;
double min_low_ask  =0;
double max_high_bid =0;

Para rellenar los búferes de indicador para los precios ask mínimos y los precios bid máximos, se usa la función FillAskBidBuffers(). Para el búfer bid, guardamos los valores de la matriz high, y para el búfer ask, los valores de la matriz low teniendo en cuenta el spread.

//+------------------------------------------------------------------+
//| rellena los búferes de indicador de High Bid y Low Ask           |
//+------------------------------------------------------------------+
void FillAskBidBuffers(const int i,const datetime &time[],const double &high[],const double &low[],const int &spread[])
  {
//--- Salir si no hemos llegado hasta la fecha inicial
   if(time[i]<first_date)
      return;
//---
   high_bid_buffer[i] =high[i];
   low_ask_buffer[i]  =low[i]+(spread[i]*_Point);
  }

La función FillIndicatorBuffers() ha sido pensada para determinar los extremos del indicador ZigZag. Los cálculos se realizan solo a partir de la fecha indicada, dependiendo del número de barras indicadas en el parámetro externo MinImpulseSize. Dependiendo de qué dirección del segmento ha sido determinada en la anterior llamada de la función, el programa entra en el bloque de código correspondiente.

Para determinar la dirección, se comprueban las siguientes condiciones:

  • La dirección actual del segmento hacia arriba
    • El Bid máximo actual supera el último máximo:
      • Si esta condición se cumple, (1) reseteamos el máximo anterior, (2) guardamos el índice actual de la matriz de datos y (3) asignamos el valor actual del Bid máximo a los elementos actuales de los búferes de indicador.
      • Si esta misma condición no se cumple, significa que la dirección del segmento ha cambiado y deberemos comprobar las condiciones de formación del extremo inferior:
        • El Ask mínimo actual es inferior al último máximo
        • La distancia entre el Ask mínimo actual y el último máximo del ZigZag supera el umbral indicado (MinImpulseSize).
          • Si esta condición se cumple, (1) registramos el índice actual de la matriz de datos, (2) guardamos en la variable la nueva dirección del segmento (hacia abajo) y (3) asignamos el valor actual del Ask mínimo a los elementos actuales de los búferes de indicador.
  • Dirección actual del segmento hacia abajo
    • El Ask mínimo actual es inferior al último mínimo:
      • Si esta condición se cumple, (1) reseteamos el mínimo anterior, (2) guardamos el índice actual de la matriz de datos y (3) asignamos el valor actual del Ask mínimo a los elementos actuales de los búferes de indicador.
      • Si esta misma condición no se cumple, significa que la dirección del segmento ha cambiado y deberemos comprobar las condiciones de formación del extremo superior:
        • El Bid máximo actual superior al último mínimo:
        • La distancia entre el Bid máximo actual y el último mínimo del ZigZag supera el umbral indicado (MinImpulseSize).
          • Si esta condición se cumple, (1) registramos el índice actual de la matriz de datos, (2) guardamos en la variable la nueva dirección del segmento (hacia arriba) y (3) asignamos el valor actual del Bid máximo a los elementos actuales de los búferes de indicador.

Más abajo, podemos analizar con mayor detalle el código de la función FillIndicatorBuffers():

//+------------------------------------------------------------------+
//| Rellenando los búferes de indicador ZZ                           |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const datetime &time[])
  {
   if(time[i]<first_date)
      return;
//--- Si la dirección de ZZ es ascendente
   if(direction_zz>0)
     {
      //--- Si hay un nuevo máximo
      if(high_bid_buffer[i]>=max_high_bid)
        {
         zz_H_buffer[last_zz_max] =0;
         last_zz_max              =i;
         max_high_bid             =high_bid_buffer[i];
         zz_H_buffer[i]           =high_bid_buffer[i];
         total_zz_h_buffer[i]     =high_bid_buffer[i];
        }
      //--- Si la dirección ha cambiado (hacia abajo)
      else
        {
         if(low_ask_buffer[i]<max_high_bid && 
            fabs(low_ask_buffer[i]-zz_H_buffer[last_zz_max])>MinImpulseSize*_Point)
           {
            last_zz_min          =i;
            direction_zz         =-1;
            min_low_ask          =low_ask_buffer[i];
            zz_L_buffer[i]       =low_ask_buffer[i];
            total_zz_l_buffer[i] =low_ask_buffer[i];
           }
        }
     }
//--- Si la dirección de ZZ es descendente
   else
     {
      //--- Si hay un nuevo mínimo
      if(low_ask_buffer[i]<=min_low_ask)
        {
         zz_L_buffer[last_zz_min] =0;
         last_zz_min              =i;
         min_low_ask              =low_ask_buffer[i];
         zz_L_buffer[i]           =low_ask_buffer[i];
         total_zz_l_buffer[i]     =low_ask_buffer[i];
        }
      //--- Si la dirección ha cambiado (hacia arriba)
      else
        {
         if(high_bid_buffer[i]>min_low_ask && 
            fabs(high_bid_buffer[i]-zz_L_buffer[last_zz_min])>MinImpulseSize*_Point)
           {
            last_zz_max          =i;
            direction_zz         =1;
            max_high_bid         =high_bid_buffer[i];
            zz_H_buffer[i]       =high_bid_buffer[i];
            total_zz_h_buffer[i] =high_bid_buffer[i];
           }
        }
     }
  }

En la lista de abajo se muestra el código de la función principal del indicador. El indicador se calcula solo según las barras formadas. A continuación, (1) se resetean las matrices y variables, (2) se determina el número de barras para los cálculos y desde qué índice debemos comenzar. En la primera ocasión, se calculan los datos para todos los elementos de los búferes de indicador, y en lo sucesivo, solo en la última barra. Después de ejecutar los cálculos y comprobaciones preliminares, se calculan y rellenan los búferes de indicador.

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(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[])
  {
//--- Prevenir el cálculo en cada tick
   if(prev_calculated==rates_total)
      return(rates_total);
//--- Si se trata del primer cálculo
   if(prev_calculated==0)
     {
      //--- Reseteamos los búferes de indicador
      ZeroIndicatorBuffers();
      //--- Reseteando las variables
      ZeroIndicatorData();
      //--- Comprobando el número de datos disponibles
      if(!CheckDataAvailable())
         return(0);
      //--- Si se han indicado para el copiado más datos de los existentes, usaremos los que haya disponibles
      DetermineNumberData();
      //--- Determinamos desde quá barra comenzar el dibujado de cada símbolo
      DetermineBeginForCalculate(rates_total);
     }
   else
     {
      //--- Calculamos solo el último resultado
      start=prev_calculated-1;
     }
//--- Rellenamos los búferes de indicador de High Bid y Low Ask
   for(int i=start; i<rates_total; i++)
      FillAskBidBuffers(i,time,high,low,spread);
//--- Rellenamos con datos los búferes de indicador
   for(int i=start; i<rates_total-1; i++)
      FillIndicatorBuffers(i,time);
//--- Retornamos el tamaño de la matriz de datos
   return(rates_total);
  }

Más abajo, mostramos qué aspecto tiene el indicador en el gráfico de día de EURUSD:

 Fig. 1 – Indicador ZigZag modificado en el gráfico EURUSD con marco temporal D1.

Fig. 1 – Indicador ZigZag modificado en el gráfico EURUSD con marco temporal D1.

En la siguiente captura de pantalla, se muestra el indicador en el gráfico EURMXN con el marco temporal M5. Aquí se muestra un segmento del gráfico en el que el spread se amplía fuertemente durante la noche. Podemos ver que el indicador se calcula correctamente teniendo en cuenta el spread.

 Fig. 1 – Indicador ZigZag modificado en el gráfico EURMXN con marco temporal M5.

Fig. 1 – Indicador ZigZag modificado en el gráfico EURMXN con marco temporal M5.

En el siguiente apartado, vamos a analizar la clase del código cuyos métodos sirven para obtener todos los datos necesarios para determinar el comportamiento actual del precio.


Clase para obtener los datos del indicador ZigZag

El precio se mueve de forma caótica e impredecible. Los movimientos laterales, cuando el precio cambia con frecuencia su dirección, pueden ser bruscamente sustituidos por largas tendencias unidireccionales y seguras. Nos vemos obligados a monitorear constantemente el estado actual, pero necesitamos herramientas que puedan interpretar correctamente el carácter del comportamiento del precio. Para ello, se ha escrito la clase de código CZigZagModule, que contiene todos los métodos necesarios para trabajar con los datos del indicador ZigZag. A continuación, vamos a ver con mayor detalle cómo es la construcción de esta clase.

Puesto que podemos trabajar con varios ejemplares de clase al mismo tiempo, por ejemplo, con los datos de ZigZag con diversos marcos temporales, podríamos necesitar visualizar los segmentos obtenidos usando líneas de tendencia de colores diferentes. Por ello, en el archivo con la clase CZigZagModule incluimos el archivo ChartObjectsLines.mqh de la biblioteca estándar. De este archivo vamos a necesitar la clase CChartObjectTrend para trabajar con líneas de tendencia. El color de las líneas de tendencia se puede establecer con el método público CZigZagModule::LinesColor(). Por defecto, se ha establecido el color gris (clrGray).

//+------------------------------------------------------------------+
//|                                                 ZigZagModule.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include <ChartObjects\ChartObjectsLines.mqh>
//+------------------------------------------------------------------+
//| Clase para obtener los datos del indicador ZigZag                |
//+------------------------------------------------------------------+
class CZigZagModule
  {
protected:
   //--- Líneas de los segmentos
   CChartObjectTrend m_trend_lines[];

   //--- Color de las líneas de los segmentos
   color             m_lines_color;
   //---
public:
                     CZigZagModule(void);
                    ~CZigZagModule(void);
   //---
public:
   //--- Color de las líneas
   void              LinesColor(const color clr) { m_lines_color=clr; }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CZigZagModule::CZigZagModule(void) : m_lines_color(clrGray)
  {
// ...
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CZigZagModule::~CZigZagModule(void)
  {
  }

Antes de obtener los datos del indicador ZigZag, debemos establecer qué número de extremos necesitaremos para trabajar. Para ello, debemos llamar el método CZigZagModule::CopyExtremums(). Para guardar (1) los precios de los extremos, (2) los índices de las barras de los extremos y (3) la hora de las barras, así como (4) el número de segmentos para construir las líneas de tendencia, en el gráfico se declaran matrices dinámicas aparte. Su tamaño se establece en este mismo método.

El número de segmentos se calcula automáticamente a partir del número de extremos indicados. Por ejemplo, si transmitimos al método CZigZagModule::CopyExtremums() el valor 1, obtendremos los datos de un máximo y un mínimo. En este caso, es solo un segmento del indicador ZigZag. Si transmitimos un valor superior a 1, el número de segmentos siempre será como el número de extremos copiados multiplicado por 2 y menos 1. Es decir, el número de segmentos será siempre impar:

  • De un extremo – 1 segmento
  • De dos extremos – 3 segmentos
  • De tres extremos – 5 segmentos, y así sucesivamente
class CZigZagModule
  {
protected:
   int               m_copy_extremums;    // Número de mínimos/máximos registrados
   int               m_segments_total;    // Número de segmentos
   //--- Precios de los extremos
   double            m_zz_low[];
   double            m_zz_high[];
   //--- Números de las barras de los extremos
   int               m_zz_low_bar[];
   int               m_zz_high_bar[];
   //--- Hora de las barras de los extremos
   datetime          m_zz_low_time[];
   datetime          m_zz_high_time[];
   //---
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CZigZagModule::CZigZagModule(void) : m_copy_extremums(1),
                                     m_segments_total(1)
  {
   CopyExtremums(m_copy_extremums);
  }

//+------------------------------------------------------------------+
//| Número de extremos para el trabajo                               |
//+------------------------------------------------------------------+
void CZigZagModule::CopyExtremums(const int total)
  {
   if(total<1)
      return;
//---
   m_copy_extremums =total;
   m_segments_total =total*2-1;
//---
   ::ArrayResize(m_zz_low,total);
   ::ArrayResize(m_zz_high,total);
   ::ArrayResize(m_zz_low_bar,total);
   ::ArrayResize(m_zz_high_bar,total);
   ::ArrayResize(m_zz_low_time,total);
   ::ArrayResize(m_zz_high_time,total);
   ::ArrayResize(m_trend_lines,m_segments_total);
  }

Antes de comenzar a trabajar con los datos del indicador ZigZag, debemos colocarlos en las matrices de clase analizadas anteriormente, para que su uso resulte más cómodo. Vamos a necesitar campos auxiliares, que se utilizarán como contadores de extremos. 

Para obtener los datos, se ha pensado el método CZigZagModule::GetZigZagData(). En él hay que transmitir las matrices originales de datos del indicador ZigZag, así como la matriz de tiempo. Estos datos originales se pueden obtener con la ayuda de las funciones CopyBuffer() y CopyTime(). Antes de obtener los valores necesarios a partir de los datos fuente, se resetean todos los campos y matrices. A continuación, en el ciclo principal obtenemos el número indicado (1) de precios extremos, (2) los índices de las barras de los extremos y (3) la hora de los extremos. 

La dirección del segmento actual se determina al final del método. Aquí, si la hora del máximo del segmento actual es superior a la hora del mínimo, la dirección es ascendente, de lo contrario, descendente.

class CZigZagModule
  {
protected:
   int               m_direction;         // Dirección
   int               m_counter_lows;      // Contador de mínimos
   int               m_counter_highs;     // Contador de máximos
   //---
public:
   //--- Obteniendo los datos
   void              GetZigZagData(const double &zz_h[],const double &zz_l[],const datetime &time[]);
   //--- Reseteando la estructura
   void              ZeroZigZagData(void);
  };
//+------------------------------------------------------------------+
//| Obteniendo los datos del zig zag                                 |
//+------------------------------------------------------------------+
void CZigZagModule::GetZigZagData(const double &zz_h[],const double &zz_l[],const datetime &time[])
  {
   int h_total =::ArraySize(zz_h);
   int l_total =::ArraySize(zz_l);
   int total   =h_total+l_total;
//--- Reseteando las variables de ZZ
   ZeroZigZagData();
//--- Iteramos en el ciclo por los valores de ZZ copiados
   for(int i=0; i<total; i++)
     {
      //--- Si ya hemos obtenido el número necesario de máximos y mínimos de ZZ, salimos del ciclo
      if(m_counter_highs==m_copy_extremums && m_counter_lows==m_copy_extremums)
         break;
      //--- Controlando la salida fuera de los límites de la matriz
      if(i>=h_total || i>=l_total)
         break;
      //--- Rellenamos la matriz de máximos hasta que copiemos el número necesario
      if(zz_h[i]>0 && m_counter_highs<m_copy_extremums)
        {
         m_zz_high[m_counter_highs]      =zz_h[i];
         m_zz_high_bar[m_counter_highs]  =i;
         m_zz_high_time[m_counter_highs] =time[i];
         //---
         m_counter_highs++;
        }
      //--- Rellenamos la matriz de mínimos hasta que copiemos el número necesario
      if(zz_l[i]>0 && m_counter_lows<m_copy_extremums)
        {
         m_zz_low[m_counter_lows]      =zz_l[i];
         m_zz_low_bar[m_counter_lows]  =i;
         m_zz_low_time[m_counter_lows] =time[i];
         //---
         m_counter_lows++;
        }
     }
//--- Determinamos la dirección de movimiento del precio
   m_direction=(m_zz_high_time[0]>m_zz_low_time[0])? 1 : -1;
  }

Ahora que hemos obtenido los datos, podemos analizar otros los métodos de esta clase. Para obtener los precios de los extremos, los índices de las barras de los extremos y las horas de las barras a las que se han formado estos extremos, basta con llamar al método correspondiente (ver la lista de código más abajo), estableciendo el índice del extremo. Como ejemplo, aquí mostramos el código de solo un método, CZigZagModule::LowPrice(), ya que todos ellos son prácticamente idénticos.

class CZigZagModule
  {
public:
   //--- Precio de los extremos según el índice establecido
   double            LowPrice(const int index);
   double            HighPrice(const int index);
   //--- Número de la barra de los extremos según el índice establecido
   int               LowBar(const int index);
   int               HighBar(const int index);
   //--- Hora de la barra de los extremos según el índice establecido
   datetime          LowTime(const int index);
   datetime          HighTime(const int index);
  };
//+------------------------------------------------------------------+
//| Valor del mínimo según el índice establecido                     |
//+------------------------------------------------------------------+
double CZigZagModule::LowPrice(const int index)
  {
   if(index>=::ArraySize(m_zz_low))
      return(0.0);
//---
   return(m_zz_low[index]);
  }

Si es necesario obtener el número del segmento, tenemos que llamar el método CZigZagModule::SegmentSize(), estableciendo como único parámetro el índice del segmento. Aquí, dependiendo de si el índice establecido es par o impar, se determinan de la forma correspondiente los índices de los extremos en los que se calcula el tamaño del segmento. Si el índice es par, los índices de los extremos coinciden, y no resulta necesario calcularlos dependiendo de la dirección del segmento.

class CZigZagModule
  {
public:
   //--- Tamaño del segmento según el índice establecido
   double            SegmentSize(const int index);
  };
//+------------------------------------------------------------------+
//| Retorna el tamaño del segmento según el índice                   |
//+------------------------------------------------------------------+
double CZigZagModule::SegmentSize(const int index)
  {
   if(index>=m_segments_total)
      return(-1);
//---
   double size=0;
//--- Si es un número par
   if(index%2==0)
     {
      int i=index/2;
      size=::fabs(m_zz_high[i]-m_zz_low[i]);
     }
//--- Si es un número impar
   else
     {
      int l=0,h=0;
      //---
      if(Direction()>0)
        {
         h=(index-1)/2+1;
         l=(index-1)/2;
        }
      else
        {
         h=(index-1)/2;
         l=(index-1)/2+1;
        }
      //---
      size=::fabs(m_zz_high[h]-m_zz_low[l]);
     }
//---
   return(size);
  }

Para obtener la suma de todos los segmentos se ha pensado el método CZigZagModule::SegmentsSum(). Aquí todo es muy sencillo, ya que tras iterar en el ciclo por todos los segmentos, se llama el método CZigZagModule::SegmentSize(), analizado anteriormente.

class CZigZagModule
  {
public:
   //--- Suma de todos los segmentos
   double            SegmentsSum(void);
  };
//+------------------------------------------------------------------+
//| Suma de los tamaños de todos los segmentos                       |
//+------------------------------------------------------------------+
double CZigZagModule::SegmentsSum(void)
  {
   double sum=0.0;
//---
   for(int i=0; i<m_segments_total; i++)
      sum+=SegmentSize(i);
//---
   return(sum);
  }

Asimismo, podríamos necesitar obtener la suma de todos los segmentos dirigidos solo hacia arriba o solo hacia abajo. Más abajo mostramos como ejemplo el código para los segmentos dirigidos hacia arriba. Aquí todo depende de la dirección del segmento actual. Si está dirigido hacia arriba, para los cálculos se usarán los índices actuales en el ciclo. Si la dirección actual es descendente, los cálculos se deben comenzar desde el primer índice con un desplazamiento de un elemento hacia atrás para los máximos. Para el caso en el que debemos obtener la suma de todos los segmentos dirigidos hacia abajo, se usa prácticamente el mismo método, con única diferencia: si la dirección actual es ascendente, el desplazamiento se realizará para los mínimos.

class CZigZagModule
  {
public:
   //--- Suma de los segmentos dirigidos (1) hacia arriba y (2) hacia abajo
   double            SumSegmentsUp(void);
   double            SumSegmentsDown(void);
  };
//+------------------------------------------------------------------+
//| Retorna el tamaño de todos los segmentos dirigidos hacia arriba  |
//+------------------------------------------------------------------+
double CZigZagModule::SumSegmentsUp(void)
  {
   double sum=0.0;
//---
   for(int i=0; i<m_copy_extremums; i++)
     {
      if(Direction()>0)
         sum+=::fabs(m_zz_high[i]-m_zz_low[i]);
      else
        {
         if(i>0)
            sum+=::fabs(m_zz_high[i-1]-m_zz_low[i]);
        }
     }
//---
   return(sum);
  }

Podría resultar útil tener la posibilidad de obtener el porcentaje de las sumas de los segmentos en la misma dirección con respecto a la suma de todos los segmentos en el conjunto. Para ello, use los métodos CZigZagModule::PercentSumSegmentsUp() y CZigZagModule::PercentSumSegmentsDown(). Con la ayuda de estos métodos, se puede obtener la diferencia porcentual de estas proporciones: el método CZigZagModule::PercentSumSegmentsDifference(), que a su vez puede indicar en qué dirección se desplaza ahora el precio (tendencia). Si la diferencia es pequeña, esto indicará que el precio oscila de forma homogénea en ambas direcciones(flat). 

class CZigZagModule
  {
public:
   //--- Porcentaje de las sumas de los segmentos con respecto a la suma total de todos los segmentos en el conjunto
   double            PercentSumSegmentsUp(void);
   double            PercentSumSegmentsDown(void);
   //--- Diferencia entre las sumas de los segmentos
   double            PercentSumSegmentsDifference(void);
  };
//+------------------------------------------------------------------+
//| Retorna el tanto por ciento de la suma de todos los segmentos    |
//| dirigidos hacia arriba                                           |
//+------------------------------------------------------------------+
double CZigZagModule::PercentSumSegmentsUp(void)
  {
   double sum=SegmentsSum();
   if(sum<=0)
      return(0);
//---
   return(SumSegmentsDown()/sum*100);
  }
//+------------------------------------------------------------------+
//| Retorna el tanto por ciento de la suma de todos los segmentos    |
//| dirigidos hacia abajo                                            |
//+------------------------------------------------------------------+
double CZigZagModule::PercentSumSegmentsDown(void)
  {
   double sum=SegmentsSum();
   if(sum<=0)
      return(0);
//---
   return(SumSegmentsUp()/sum*100);
  }
//+------------------------------------------------------------------+
//| Retorna la diferencia en tanto por ciento de la suma de todos    |
//| los segmentos                                                    |
//+------------------------------------------------------------------+
double CZigZagModule::PercentSumSegmentsDifference(void)
  {
   return(::fabs(PercentSumSegmentsUp()-PercentSumSegmentsDown()));
  }

Para determinar el carácter del comportamiento del precio, necesitaremos métodos para obtener la duración tanto de segmentos aparte, como del conjunto completo. El método CZigZagModule::SegmentBars() ha sido pensado para obtener el número de barras en el segmento indicado. La lógica de este método es la misma que la que analizamos anteriormente para obtener el tamaño del segmento con el método CZigZagModule::SegmentSize(), por eso, no vamos a mostrar aquí su código. 

Para obtener el número total de barras en el conjunto de datos resultante, use el método CZigZagModule::SegmentsTotalBars(). Aquí se determinan el índice inicial y final de las barras en el conjunto, y también se retorna la diferencia. Según el mismo principio funciona el método CZigZagModule::SegmentsTotalSeconds(), solo que aquí se retorna el número de segundos en el conjunto. 

class CZigZagModule
  {
public:
   //--- Número de barras en el segmento establecido
   int               SegmentBars(const int index);
   //--- (1) Número de barras y (2) segundos en el conjunto de segmentos
   int               SegmentsTotalBars(void);
   long              SegmentsTotalSeconds(void);
  };
//+------------------------------------------------------------------+
//| Número de barras de todos los segmentos                          |
//+------------------------------------------------------------------+
int CZigZagModule::SegmentsTotalBars(void)
  {
   int begin =0;
   int end   =0;
   int l     =m_copy_extremums-1;
//---
   begin =(m_zz_high_bar[l]>m_zz_low_bar[l])? m_zz_high_bar[l] : m_zz_low_bar[l];
   end   =(m_zz_high_bar[0]>m_zz_low_bar[0])? m_zz_low_bar[0] : m_zz_high_bar[0];
//---
   return(begin-end);
  }
//+------------------------------------------------------------------+
//| Número de segundos de todos los segmentos                        |
//+------------------------------------------------------------------+
long CZigZagModule::SegmentsTotalSeconds(void)
  {
   datetime begin =NULL;
   datetime end   =NULL;
   int l=m_copy_extremums-1;
//---
   begin =(m_zz_high_time[l]<m_zz_low_time[l])? m_zz_high_time[l] : m_zz_low_time[l];
   end   =(m_zz_high_time[0]<m_zz_low_time[0])? m_zz_low_time[0] : m_zz_high_time[0];
//---
   return(long(end-begin));
  }

Con frecuencia, puede ser necesario descubrir el tamaño del intervalo en el que se mueve el precio en el conjunto de datos observado. Para ello, se han incluido en la clase métodos para la obtención de los extremos máximos y mínimos, así como la diferencia entre ellos (intervalo de precio).

class CZigZagModule
  {
public:
   //--- (1) Valores mínimos y (2) máximos en el conjunto
   double            LowMinimum(void);
   double            HighMaximum(void);
   //--- Tamaño del intervalo de precio
   double            PriceRange(void);
  };
//+------------------------------------------------------------------+
//| Valor mínimo en el conjunto                                      |
//+------------------------------------------------------------------+
double CZigZagModule::LowMinimum(void)
  {
   return(m_zz_low[::ArrayMinimum(m_zz_low)]);
  }
//+------------------------------------------------------------------+
//| Valor máximo en el conjunto                                      |
//+------------------------------------------------------------------+
double CZigZagModule::HighMaximum(void)
  {
   return(m_zz_high[::ArrayMaximum(m_zz_high)]);
  }
//+------------------------------------------------------------------+
//| Intervalo de precio                                              |
//+------------------------------------------------------------------+
double CZigZagModule::PriceRange(void)
  {
   return(HighMaximum()-LowMinimum());
  }

Otro conjunto de métodos de clase CZigZagModule permite obtener valores tales como:

  • SmallestSegment() – retorna el menor tamaño del segmento en los datos obtenidos.
  • LargestSegment() – retorna el mayor tamaño del segmento en los datos obtenidos. 
  • LeastNumberOfSegmentBars() – retorna el menor número de barras en el segmento en los datos obtenidos.
  • MostNumberOfSegmentBars() – retorna el mayor número de barras en el segmento en los datos obtenidos.

En la clase hay métodos para obtener los tamaños de los segmentos y el número de barras de los segmentos según el índice establecido. Por eso, será sencillo comprender el código de los métodos de la lista anterior. La única diferencia en ellos reside en los métodos llamados, por eso solo vamos a mostrar el código de dos: CZigZagModule::SmallestSegmen() y CZigZagModule::MostNumberOfSegmentBars().

class CZigZagModule
  {
public:
   //--- Menor segmento en el conjunto
   double            SmallestSegment(void);
   //--- Mayor segmento en el conjunto
   double            LargestSegment(void);
   //--- Menor número de barras del segmento en el conjunto
   int               LeastNumberOfSegmentBars(void);
   //--- Mayor número de barras del segmento en el conjunto 
   int               MostNumberOfSegmentBars(void);
  };
//+------------------------------------------------------------------+
//| Menor segmento en el conjunto                                    |
//+------------------------------------------------------------------+
double CZigZagModule::SmallestSegment(void)
  {
   double min_size=0;
   for(int i=0; i<m_segments_total; i++)
     {
      if(i==0)
        {
         min_size=SegmentSize(0);
         continue;
        }
      //---
      double size=SegmentSize(i);
      min_size=(size<min_size)? size : min_size;
     }
//---
   return(min_size);
  }
//+------------------------------------------------------------------+
//| Mayor número de barras del segmento en el conjunto               |
//+------------------------------------------------------------------+
int CZigZagModule::MostNumberOfSegmentBars(void)
  {
   int max_bars=0;
   for(int i=0; i<m_segments_total; i++)
     {
      if(i==0)
        {
         max_bars=SegmentBars(0);
         continue;
        }
      //---
      int bars=SegmentBars(i);
      max_bars=(bars>max_bars)? bars : max_bars;
     }
//---
   return(max_bars);
  }

Al buscar los patrones, podríamos necesitar determinar cuánto se diferencia el segmento por su tamaño (en tanto por ciento) con respecto al anterior. Para resolver estas tareas, usaremos el método CZigZagModule::PercentDeviation().

class CZigZagModule
  {
public:
   //--- Desviación en tanto por ciento
   double            PercentDeviation(const int index);
  };
//+------------------------------------------------------------------+
//| Desviación en tanto por ciento                                   |
//+------------------------------------------------------------------+
double CZigZagModule::PercentDeviation(const int index)
  {
   return(SegmentSize(index)/SegmentSize(index+1)*100);
  }

Vamos a analizar cómo visualizar los datos obtenidos y usar la clase CZigZagModule en nuestros proyectos.


Visualización del conjunto de datos obtenido

Tras obtener los manejadores del indicador ZigZag de distintos marcos temporales, podemos visualizar los segmentos en el gráfico actual en el que está cargado el experto. Para visualizarlos, estableceremos objetos gráficos del tipo línea de tendencia. Para crear los objetos, se usa el método privado CZigZagModule::CreateSegment(). En él se transmite el índice del segmento y el sufijo (parámetro no obligatorio), con cuya ayuda se formará el nombre único del objeto gráfico, para excluir la cualquier repetición si necesitamos representar los datos del indicador ZigZag con diferentes parámetros o marcos temporales distintos. 

Los métodos públicos CZigZagModule::ShowSegments() y CZigZagModule::DeleteSegments() permiten representar y eliminar objetos gráficos.

class CZigZagModule
  {
public:
   //--- (1) Muestra y (2) eliminación de objetos
   void              ShowSegments(const string suffix="");
   void              DeleteSegments(void);
   //---
private:
   //--- Creando objetos
   void              CreateSegment(const int segment_index,const string suffix="");
  };
//+------------------------------------------------------------------+
//| Muestra los segmentos de ZZ en el gráfico                        |
//+------------------------------------------------------------------+
void CZigZagModule::ShowSegments(const string suffix="")
  {
   for(int i=0; i<m_segments_total; i++)
      CreateSegment(i,suffix);
  }
//+------------------------------------------------------------------+
//| Elimina los segmentos                                            |
//+------------------------------------------------------------------+
void CZigZagModule::DeleteSegments(void)
  {
   for(int i=0; i<m_segments_total; i++)
     {
      string name="zz_"+string(::ChartID())+"_"+string(i);
      ::ObjectDelete(::ChartID(),name);
     }
  }

Para obtener rápidamente la información básica sobre los datos del indicador obtenidos, se han añadido a la clase métodos para mostrar los comentarios al gráfico. Como ejemplo, adjuntamos más abajo el código del método de muestra abreviada de información con los datos del indicador calculados.

 class CZigZagModule
  {
public:
   //--- Comentarios al gráfico
   void              CommentZigZagData();
   void              CommentShortZigZagData();
  };
//+------------------------------------------------------------------+
//| Muestra de los datos de ZigZag en el comentario al gráfico       |
//+------------------------------------------------------------------+
void CZigZagModule::CommentShortZigZagData(void)
  {
   string comment="Current direction : "+string(m_direction)+"\n"+
                  "Copy extremums: "+string(m_copy_extremums)+
                  "\n---\n"+
                  "SegmentsTotalBars(): "+string(SegmentsTotalBars())+"\n"+
                  "SegmentsTotalSeconds(): "+string(SegmentsTotalSeconds())+"\n"+
                  "SegmentsTotalMinutes(): "+string(SegmentsTotalSeconds()/60)+"\n"+
                  "SegmentsTotalHours(): "+string(SegmentsTotalSeconds()/60/60)+"\n"+
                  "SegmentsTotalDays(): "+string(SegmentsTotalSeconds()/60/60/24)+
                  "\n---\n"+
                  "PercentSumUp(): "+::DoubleToString(SumSegmentsUp()/SegmentsSum()*100,2)+"\n"+
                  "PercentSumDown(): "+::DoubleToString(SumSegmentsDown()/SegmentsSum()*100,2)+"\n"+
                  "PercentDifference(): "+::DoubleToString(PercentSumSegmentsDifference(),2)+
                  "\n---\n"+
                  "SmallestSegment(): "+::DoubleToString(SmallestSegment()/_Point,0)+"\n"+
                  "LargestSegment(): "+::DoubleToString(LargestSegment()/_Point,0)+"\n"+
                  "LeastNumberOfSegmentBars(): "+string(LeastNumberOfSegmentBars())+"\n"+
                  "MostNumberOfSegmentBars(): "+string(MostNumberOfSegmentBars());
//---
   ::Comment(comment);
  }

A continuación, vamos a crear una aplicación en la que obtendremos y visualizaremos los datos obtenidos.


Experto para la simulación de los resultados obtenidos

Vamos a escribir un sencillo experto de prueba para obtener y visualizar los datos del indicador ZigZag. No realizaremos comprobaciones adicionales, para simplificar el código al máximo. Como ejemplo, bastará con mostrar el propio principio de obtención de datos, así como el funcionamiento del mismo. 

Incluimos en el archivo del experto el archivo con la clase CZigZagModule y declaramos su ejemplar. Aquí tendremos dos parámetros externos, en los que se podrá indicar cuántos extremos debemos copiar, así como la distancia mínima para formar un nuevo segmento del indicador ZigZag. Asimismo, declaramos a nivel global las matrices dinámicas para obtener los datos fuente y la variable para el manejador del indicador. 

//+------------------------------------------------------------------+
//|                                                    TestZZ_01.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include <ZigZagModule.mqh>
CZigZagModule zz_current;

//--- Parámetros externos
input int CopyExtremum   =3;
input int MinImpulseSize =0;

//--- Matrices para los datos fuente
double   l_zz[];
double   h_zz[];
datetime t_zz[];

//--- Manejador para el indicador ZZ
int zz_handle_current=WRONG_VALUE;

En la función OnInit() (1) obtenemos el manejador del indicador, (2) establecemos los extremos para formar los datos finales y el color de las líneas de los segmentos del conjunto obtenido, asimismo, (3) esteblecemos el orden de indexación inverso para las matrices para los datos originales.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Ruta hasta el indicador ZZ
   string zz_path="Custom\\ZigZag\\ExactZZ_Plus.ex5";
//--- Obtenemos el manejador del indicador
   zz_handle_current=::iCustom(_Symbol,_Period,zz_path,10000,MinImpulseSize,true,true);
//--- Esteblecemos el color para los segmentos y el número de extremos que debemos obtener
   zz_current.LinesColor(clrRed);
   zz_current.CopyExtremums(CopyExtremum);
//--- Establecemos el orden de indexación inverso (... 3 2 1 0)
   ::ArraySetAsSeries(l_zz,true);
   ::ArraySetAsSeries(h_zz,true);
   ::ArraySetAsSeries(t_zz,true);
   return(INIT_SUCCEEDED);
  }

Ahora, en la función OnTick(), primero obtenemos los datos originalesdel indicador según su manejador y hora de apertura de las barras. A continuación, preparamos los datos finales con la la llamada de CZigZagModule::GetZigZagData(). Y finalmente, visualizamos los segmentos de los datos del indicador ZigZag obtenidos y mostramos esta información como comentarios al gráfico.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(void)
  {
//--- Obtenemos los datos fuente
   int copy_total=1000;
   ::CopyTime(_Symbol,_Period,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_current,2,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_current,3,0,copy_total,l_zz);
//--- Obtenemos los datos finales
   zz_current.GetZigZagData(h_zz,l_zz,t_zz);
//--- Visualizamos los segmentos en el gráfico
   zz_current.ShowSegments();
//--- Mostramos los valores de los datos en los comentarios al gráfico
   zz_current.CommentZigZagData();
  }

Si iniciamos este experto en el simulador de estrategias en el modo de visualización, veremos el resultado siguiente. En este caso, se han obtenido 5 extremos de máximos de y de mínimos. En total, en el gráfico se destacan en color rojo 9 segmentos.

 Fig. 3 – Demostración en el modo de visualización (un ZigZag).

Fig. 3 – Demostración en el modo de visualización (un ZigZag).

Si necesitamos obtener los datos del indicador ZigZag simultáneamente de distintos marcos temporales, deberemos añadir ciertas cosas al código del experto de prueba. Veamos un ejemplo en el que necesitamos obtener los datos de tres marcos temporales. En este caso, debemos declarar tres ejemplares de la clase CZigZagModule. El primer marco temporal será el del gráfico actual en el que está iniciado el experto; los otros dos ejemplos serán M15 y H1.

#include <Addons\Indicators\ZigZag\ZigZagModule.mqh>
CZigZagModule zz_current;
CZigZagModule zz_m15;
CZigZagModule zz_h1;

Cada indicador tiene su propia variable para obtener el manejador:

//--- Manejadores del indicador ZZ
int zz_handle_current =WRONG_VALUE;
int zz_handle_m15     =WRONG_VALUE;
int zz_handle_h1      =WRONG_VALUE;

A continuación, en la función OnInit() obtenemos los manejadores para cada indicador por separado y establecemos los colores y el número de extremos:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Ruta hasta el indicador ZZ
   string zz_path="Custom\\ZigZag\\ExactZZ_Plus.ex5";
//--- Obtenemos los manejadores del indicador
   zz_handle_current =::iCustom(_Symbol,_Period,zz_path,10000,MinImpulseSize,false,false);
   zz_handle_m15     =::iCustom(_Symbol,PERIOD_M15,zz_path,10000,MinImpulseSize,false,false);
   zz_handle_h1      =::iCustom(_Symbol,PERIOD_H1,zz_path,10000,MinImpulseSize,false,false);
//--- Establecemos el color para los segmentos
   zz_current.LinesColor(clrRed);
   zz_m15.LinesColor(clrCornflowerBlue);
   zz_h1.LinesColor(clrGreen);
//--- Esteblecemos el número de extremos que debemos obtener
   zz_current.CopyExtremums(CopyExtremum);
   zz_m15.CopyExtremums(CopyExtremum);
   zz_h1.CopyExtremums(CopyExtremum);
//--- Establecemos el orden de indexación inverso (... 3 2 1 0)
   ::ArraySetAsSeries(l_zz,true);
   ::ArraySetAsSeries(h_zz,true);
   ::ArraySetAsSeries(t_zz,true);
   return(INIT_SUCCEEDED);
  }

La obtención de datos tiene lugar en la función OnTick(), com mostramos anteriormente, pero por separado para cada copia del indicador ZigZag. En el gráfico solo podemos mostrar los comentarios  de un indicador concreto. En este caso, estamos viendo los datos breves para el marco temporal actual del indicador.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   int copy_total=1000;
   ::CopyTime(_Symbol,_Period,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_current,2,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_current,3,0,copy_total,l_zz);
   zz_current.GetZigZagData(h_zz,l_zz,t_zz);
   zz_current.ShowSegments("_current");
   zz_current.CommentShortZigZagData();
//---
   ::CopyTime(_Symbol,PERIOD_M15,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_m15,2,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_m15,3,0,copy_total,l_zz);
   zz_m15.GetZigZagData(h_zz,l_zz,t_zz);
   zz_m15.ShowSegments("_m15");
//---
   ::CopyTime(_Symbol,PERIOD_H1,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_h1,2,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_h1,3,0,copy_total,l_zz);
   zz_h1.GetZigZagData(h_zz,l_zz,t_zz);
   zz_h1.ShowSegments("_h1");
  }

Más abajo, se muestra qué aspecto tiene:

 Fig. 4 – Demostración en el modo de visualización (tres ZigZags).

Fig. 4 – Demostración en el modo de visualización (tres ZigZags).

Podemos notar que los extremos de los indicadores de los marcos temporales mayores están un poco desplazados a la izquierda. Aquí no hay errores, simplemente se establecen los picos según la hora de apertura de las barras del marco temporal en el que se ha obtenido el manejador. 


Continuamos el desarrollo de la clase CZigZagModule

Mirando a los resultados obtenidos, podemos pensar que todo esto es suficiente para trabajar plenamente con el indicador ZigZag. Pero, en realidad, esto no es todo ni mucho menos, así que vamos a continuar desarrollando la clase del código CZigZagModule, rellenándola con nuevos métodos útiles. 

Para ello, hemos obtenido los datos del indicador ZigZag comenzando por la última barra formada y penetrando más y más en los datos históricos. Sin embargo, podría ser necesario obtener también los datos en un intervalo temporal determinado. Con este objetivo, vamos a escribir otro método más, CZigZagModule::GetZigZagData(), pero ya con otro conjunto de parámetros. En esta versión, obtendremos los datos fuente dentro del método, por eso, necesitaremos como parámetros de entrada el manejador del indicador, el símbolo, el marco temporal y el intervalo temporal (fecha inicial y final).

A continuación, debemos calcular por separado el número de mínimos y máximos en los datos obtenidos. El número de extremos para el trabajo posterior se determinará entonces con el número mínimo entre estos contadores

Al final, aquí se llama el método homónimo CZigZagModule::GetZigZagData() con otro conjunto de parámetros que ya hemos analizado anteriormente, cuando debíamos transmitir como parámetros las matrices con los datos fuente para obtener los datos finales.

class CZigZagModule
  {
private:
   //--- Matrices para obtener los datos fuente
   double            m_zz_lows_temp[];
   double            m_zz_highs_temp[];
   datetime          m_zz_time_temp[];
   //---
public:
   //--- Obteniendo los datos
   void              GetZigZagData(const int handle,const string symbol,const ENUM_TIMEFRAMES period,const datetime start_time,const datetime stop_time);
  };
//+------------------------------------------------------------------+
//| Obtiene los datos de ZZ del manejador transmitido                |
//+------------------------------------------------------------------+
void CZigZagModule::GetZigZagData(const int handle,const string symbol,const ENUM_TIMEFRAMES period,const datetime start_time,const datetime stop_time)
  {
//--- Obtenemos los datos fuente
   ::CopyTime(symbol,period,start_time,stop_time,m_zz_time_temp);
   ::CopyBuffer(handle,2,start_time,stop_time,m_zz_highs_temp);
   ::CopyBuffer(handle,3,start_time,stop_time,m_zz_lows_temp);
//--- Contadores
   int lows_counter  =0;
   int highs_counter =0;
//--- Calculamos los máximos
   int h_total=::ArraySize(m_zz_highs_temp);
   for(int i=0; i<h_total; i++)
     {
      if(m_zz_highs_temp[i]>0)
         highs_counter++;
     }
//--- Calculamos los mínimos
   int l_total=::ArraySize(m_zz_lows_temp);
   for(int i=0; i<l_total; i++)
     {
      if(m_zz_lows_temp[i]>0)
         lows_counter++;
     }
//--- Obtenemos el número de extremos
   int copy_extremums=(int)::fmin((double)highs_counter,(double)lows_counter);
   CopyExtremums(copy_extremums);
//--- Iteramos en el ciclo por los valores de ZZ copiados
   GetZigZagData(m_zz_highs_temp,m_zz_lows_temp,m_zz_time_temp);
  }

Para obtener las horas de los extremos menor y mayor en el conjunto de datos obtenido, use los métodos CZigZagModule::SmallestMinimumTime() y CZigZagModule::LargestMaximumTime(). 

class CZigZagModule
  {
public:
   //--- Hora del mínimo menor
   datetime          SmallestMinimumTime(void);
   //--- Hora del máximo mayor
   datetime          LargestMaximumTime(void);
  };
//+------------------------------------------------------------------+
//| Hora del mínimo menor                                            |
//+------------------------------------------------------------------+
datetime CZigZagModule::SmallestMinimumTime(void)
  {
   return(m_zz_low_time[::ArrayMinimum(m_zz_low)]);
  }
//+------------------------------------------------------------------+
//| Hora del máximo mayor
//+------------------------------------------------------------------+
datetime CZigZagModule::LargestMaximumTime(void)
  {
   return(m_zz_high_time[::ArrayMaximum(m_zz_high)]);
  }

Asimismo, ampliamos la lista de métodos para trabajar con los segmentos de ZigZag. Puede resultar cómodo obtener varios valores a la vez en las variables transmitidas por enlace. En la clase hay tres métodos semejantes:

  • SegmentBars() — retorna los índices inicial y final de la barra del segmento indicado.
  • SegmentPrices() — retorna los precios inicial y final del segmento indicado.
  • SegmentTimes() — retorna la hora inicial y final del segmento indicado.

Ya existe un esquema semejante en otros métodos mostrados anteriormente, por eso, aquí mostraremos como ejemplo solo el código de uno de los enumerados más arriba. 

class CZigZagModule
  {
public:
   //--- Retorna la barra inicial y final del segmento indicado
   bool              SegmentBars(const int index,int &start_bar,int &stop_bar);
   //--- Retorna los precios inicial y final del segmento indicado
   bool              SegmentPrices(const int index,double &start_price,double &stop_price);
   //--- Retorna la hora inicial y final del segmento indicado
   bool              SegmentTimes(const int index,datetime &start_time,datetime &stop_time);
  };
//+------------------------------------------------------------------+
//| Retorna la barra inicial y final del segmento indicado           |
//+------------------------------------------------------------------+
bool CZigZagModule::SegmentBars(const int index,int &start_bar,int &stop_bar)
  {
   if(index>=m_segments_total)
      return(false);
//--- Si es un número par
   if(index%2==0)
     {
      int i=index/2;
      //---
      start_bar =(Direction()>0)? m_zz_low_bar[i] : m_zz_high_bar[i];
      stop_bar  =(Direction()>0)? m_zz_high_bar[i] : m_zz_low_bar[i];
     }
//--- Si es un número impar
   else
     {
      int l=0,h=0;
      //---
      if(Direction()>0)
        {
         h=(index-1)/2+1;
         l=(index-1)/2;
         //---
         start_bar =m_zz_high_bar[h];
         stop_bar  =m_zz_low_bar[l];
        }
      else
        {
         h=(index-1)/2;
         l=(index-1)/2+1;
         //---
         start_bar =m_zz_low_bar[l];
         stop_bar  =m_zz_high_bar[h];
        }
     }
//---
   return(true);
  }

Supongamos que tenemos abierto un gráfico de cinco minutos (M5) y obtenemos los datos del marco temporal de una hora (H1). Estamos buscando patrones del marco temporal de una hora y necesitamos determinar el carácter del comportamiento del precio de este u otro segmento de ZigZag del marco temporal de una hora en el marco temporal actual. En otras palabras, queremos saber cómo se ha formado el segmento indicado en el marco temporal menor.

Como ya hemos dicho en el apartado anterior, los extremos de los segmentos de los marcos temporales mayores se representan en el marco temporal actual según la hora de apertura de los marcos temporales mayores. Ya tenemos el método CZigZagModule::SegmentTimes(), que retorna la hora de comienzo y finalización del segmento indicado. Si usamos este intervalo para obtener los datos de ZigZag del marco temporal menor, en la mayoría de los casos obtendremos muchos segmentos sobrantes que realmente pertenecen a otros segmentos del marco temporal mayor. Para los casos en los que es necesaria una gran precisión, escribiremos el método CZigZagModule::SegmentTimes(), pero con otro conjunto de parámetros. Aparte de esto, necesitaremos varios métodos auxiliares privados, para obtener (1) los datos fuente, así como (2) los índices de los valores máximo y mínimo en las matrices transmitidas. 

class CZigZagModule
  {
private:
   //--- Copia los datos fuente a las matrices transmitidas
   void              CopyData(const int handle,const int buffer_index,const string symbol,
                              const ENUM_TIMEFRAMES period,datetime start_time,datetime stop_time,
                              double &zz_array[],datetime &time_array[]);
   //--- Retorna el índice (1) del valor mínimo (2) y máximo de la matriz transmitida
   int               GetMinValueIndex(double &zz_lows[]);
   int               GetMaxValueIndex(double &zz_highs[]);
  };
//+------------------------------------------------------------------+
//| Copia los datos fuente a las matrices transmitidas               |
//+------------------------------------------------------------------+
void CZigZagModule::CopyData(const int handle,const int buffer_index,const string symbol,
                             const ENUM_TIMEFRAMES period,datetime start_time,datetime stop_time,
                             double &zz_array[],datetime &time_array[])
  {
   ::CopyBuffer(handle,buffer_index,start_time,stop_time,zz_array);
   ::CopyTime(symbol,period,start_time,stop_time,time_array);
  }
//+------------------------------------------------------------------+
//| Retorna el índice del valor máximo de la matriz transmitida      |
//+------------------------------------------------------------------+
int CZigZagModule::GetMaxValueIndex(double &zz_highs[])
  {
   int    max_index =0;
   double max_value =0;
   int total=::ArraySize(zz_highs);
   for(int i=0; i<total; i++)
     {
      if(zz_highs[i]>0)
        {
         if(zz_highs[i]>max_value)
           {
            max_index =i;
            max_value =zz_highs[i];
           }
        }
     }
//---
   return(max_index);
  }
//+------------------------------------------------------------------+
//| Retorna el índice del valor mínimo de la matriz transmitida      |
//+------------------------------------------------------------------+
int CZigZagModule::GetMinValueIndex(double &zz_lows[])
  {
   int    min_index =0;
   double min_value =INT_MAX;
   int total=::ArraySize(zz_lows);
   for(int i=0; i<total; i++)
     {
      if(zz_lows[i]>0)
        {
         if(zz_lows[i]<min_value)
           {
            min_index =i;
            min_value =zz_lows[i];
           }
        }
     }
//---
   return(min_index);
  }

Otro método, CZigZagModule::SegmentTimes(), está pensado para obtener la hora inicial y final del segmento indicado teniendo en cuenta el marco temporal menor. Aquí serán necesarias algunas aclaraciones. Al método se transmiten los siguientes parámetros:

  • handle — manejador del indicador ZigZag del marco temporal menor.
  • highs_buffer_index — índice del búfer de indicador donde se encuentran los extremos máximos.
  • lows_buffer_index — índice del búfer de indicador donde se encuentran los extremos mínimos.
  • symbol — símbolo del marco temporal menor.
  • period — periodo del marco temporal mayor.
  • in_period — periodo del marco temporal menor.
  • index — índice del segmento del marco temporal mayor.

Valores retornados de los parámetros por enlace:

  • start_time — hora de comienzo del segmento teniendo en cuenta el marco temporal mayor.
  • stop_time — hora de finalización del segmento teniendo en cuenta el marco temporal mayor.

Primero, tenemos que obtener la hora de apertura de la primera y la última barra. Para ello, debemos llamar el primer método CZigZagModule::SegmentTimes(), que hemos analizado anteriormente. 

A continuación, con la ayuda del método CZigZagModule::CopyData(), obtenemos los datos de los extremos y las horas de las barras. Dependiendo de la dirección que tiene el segmento, obtenemos los datos en una secuencia determinada. Si la dirección es ascendente, primero obtenemos los datos de los mínimos del ZigZag del marco temporal menor que componen la primera barra del segmento en el marco temporal mayor. A continuación, obtenemos los datos de los máximos del ZigZag del marco temporal mayor que componen la última barra del segmento en el marco temporal mayor. Cuando la dirección del segmento es descendente, la secuencia de acciones será inversa. Primero, debemos obtener los datos de los máximos, y después, de los mínimos. 

Después, cuando los datos fuente ya han sido obtenidos, debemos encontrar los índices de los valores máximo y mínimo. Con la ayuda de estos índices, debemos conocer la hora inicial y final del segmento analizado en el marco temporal menor.

class CZigZagModule
  {
public:
   //--- Retorna la hora inicial y final del segmento indicado teniendo en cuenta el marco temporal menor
   bool              SegmentTimes(const int handle,const int highs_buffer_index,const int lows_buffer_index,
                                  const string symbol,const ENUM_TIMEFRAMES period,const ENUM_TIMEFRAMES in_period,
                                  const int index,datetime &start_time,datetime &stop_time);
  };
//+------------------------------------------------------------------+
//| Retorna la hora inicial y final del segmento indicado            |
//| teniendo en cuenta el marco temporal menor                       |
//+------------------------------------------------------------------+
bool CZigZagModule::SegmentTimes(const int handle,const int highs_buffer_index,const int lows_buffer_index,
                                 const string symbol,const ENUM_TIMEFRAMES period,const ENUM_TIMEFRAMES in_period,
                                 const int index,datetime &start_time,datetime &stop_time)
  {
//--- Obtenemos la hora sin tener en cuenta el marco temporal actual
   datetime l_start_time =NULL;
   datetime l_stop_time  =NULL;
   if(!SegmentTimes(index,l_start_time,l_stop_time))
      return(false);
//---
   double   zz_lows[];
   double   zz_highs[];
   datetime zz_lows_time[];
   datetime zz_highs_time[];
   datetime start =NULL;
   datetime stop  =NULL;
   int      period_seconds=::PeriodSeconds(period);
//--- Obtenemos los datos fuente, si la dirección es ascendente
   if(SegmentDirection(index)>0)
     {
      //--- Datos de la primera barra del marco temporal mayor
      start =l_start_time;
      stop  =l_start_time+period_seconds;
      CopyData(handle,lows_buffer_index,symbol,in_period,start,stop,zz_lows,zz_lows_time);
      //--- Datos de la última barra del marco temporal mayor
      start =l_stop_time;
      stop  =l_stop_time+period_seconds;
      CopyData(handle,highs_buffer_index,symbol,in_period,start,stop,zz_highs,zz_highs_time);
     }
//--- Obtenemos los datos fuente, si la dirección descendente
   else
     {
      //--- Datos de la primera barra del marco temporal mayor
      start =l_start_time;
      stop  =l_start_time+period_seconds;
      CopyData(handle,highs_buffer_index,symbol,in_period,start,stop,zz_highs,zz_highs_time);
      //--- Datos de la última barra del marco temporal mayor
      start =l_stop_time;
      stop  =l_stop_time+period_seconds;
      CopyData(handle,lows_buffer_index,symbol,in_period,start,stop,zz_lows,zz_lows_time);
     }
//--- Buscamos el índice del valor máximo
   int max_index =GetMaxValueIndex(zz_highs);
//--- Buscamos el índice del valor mínimo
   int min_index =GetMinValueIndex(zz_lows);
//--- Obtenemos la hora de comienzo y finalización del segmento
   start_time =(SegmentDirection(index)>0)? zz_lows_time[min_index] : zz_highs_time[max_index];
   stop_time  =(SegmentDirection(index)>0)? zz_highs_time[max_index] : zz_lows_time[min_index];
//--- Ha habido éxito
   return(true);
  }

Ahora, vamos a escribir el experto para las pruebas. El marco temporal actual será M5. En este marco temporal cargamos el experto en el simulador en el modo de visualización. Obtendremos los datos del marco temporal de una hora (H1) y el actual. El código del experto se parece al que hemos visto anteriormente, por eso, aquí solo vamos a mostrar el contenido de la función OnTick().

Primero, obtenemos los datos para el marco temporal de una hora con el primer método y mostramos los segmentos en el gráfico para mayor claridad. A continuación, obtenemos como ejemplo los datos del ZigZag del marco temporal actual (M5) en el intervalo temporal del tercero (índice 2) del segmento de ZigZag del marco temporal de una hora. Para ello, primero obtenemos el inicio y la finalización del segmento teniendo en cuenta el marco temporal actual.

A continuación, obtenemos los datos para el marco temporal actual usando el segundo método, y también mostramos los segmentos en el gráfico para comprobar que todo funciona correctamente.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   int copy_total=1000;
   int h_buff=2,l_buff=3;
//--- Primer método de obtención de datos
   ::CopyTime(_Symbol,PERIOD_H1,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_h1,h_buff,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_h1,l_buff,0,copy_total,l_zz);
   zz_h1.GetZigZagData(h_zz,l_zz,t_zz);
   zz_h1.ShowSegments("_h1");
//---
   int      segment_index =2;
   int      start_bar     =0;
   int      stop_bar      =0;
   double   start_price   =0.0;
   double   stop_price    =0.0;
   datetime start_time    =NULL;
   datetime stop_time     =NULL;
   datetime start_time_in =NULL;
   datetime stop_time_in  =NULL;
//---
   zz_h1.SegmentBars(segment_index,start_bar,stop_bar);
   zz_h1.SegmentPrices(segment_index,start_price,stop_price);
   zz_h1.SegmentTimes(segment_index,start_time,stop_time);
   zz_h1.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,PERIOD_H1,_Period,segment_index,start_time_in,stop_time_in);
   
//--- Segundo método de obtención de datos
   zz_current.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
   zz_current.ShowSegments("_current");
   
//--- Mostrando los datos en los comentarios en el gráfico
   string comment="Current direction : "+string(zz_h1.Direction())+"\n"+
                  "\n---\n"+
                  "Direction > segment["+string(segment_index)+"]: "+string(zz_h1.SegmentDirection(segment_index))+
                  "\n---\n"+
                  "Start bar > segment["+string(segment_index)+"]: "+string(start_bar)+"\n"+
                  "Stop bar > segment["+string(segment_index)+"]: "+string(stop_bar)+
                  "\n---\n"+
                  "Start price > segment["+string(segment_index)+"]: "+::DoubleToString(start_price,_Digits)+"\n"+
                  "Stop price > segment["+string(segment_index)+"]: "+::DoubleToString(stop_price,_Digits)+
                  "\n---\n"+
                  "Start time > segment["+string(segment_index)+"]: "+::TimeToString(start_time,TIME_DATE|TIME_MINUTES)+"\n"+
                  "Stop time > segment["+string(segment_index)+"]: "+::TimeToString(stop_time,TIME_DATE|TIME_MINUTES)+
                  "\n---\n"+
                  "Start time (in tf) > segment["+string(segment_index)+"]: "+::TimeToString(start_time_in,TIME_DATE|TIME_MINUTES)+"\n"+
                  "Stop time (in tf) > segment["+string(segment_index)+"]: "+::TimeToString(stop_time_in,TIME_DATE|TIME_MINUTES)+
                  "\n---\n"+
                  "Extremums copy: "+string(zz_current.CopyExtremums())+"\n"+
                  "SmallestMinimumTime(): "+string(zz_current.SmallestMinimumTime())+"\n"+
                  "LargestMaximumTime(): "+string(zz_current.LargestMaximumTime());
//---
   ::Comment(comment);
  }

Este es el aspecto que tiene:

 Fig. 5 – Demostrando cómo obtener los datos dentro del segmento indicado.

Fig. 5 – Demostrando cómo obtener los datos dentro del segmento indicado.

A conitnuación, escribimos otro experto en el que obtendremos simultáneamente los datos de tres segmentos del marco temporal mayor.

Al inicio del archivo, debemos declarar ahora cuatro ejemplares de la clase CZigZagModule. Uno para el marco temporal mayor (H1) y tres para el marco temporal actual. En este caso, realizaremos las pruebas en el marco temporal de cinco minutos. 

CZigZagModule zz_h1;
CZigZagModule zz_current0;
CZigZagModule zz_current1;
CZigZagModule zz_current2;

Para mayor claridad, representaremos los segmentos del marco temporal menor dentro de los segmentos del marco temporal mayor con colores diferentes:

//--- Establecemos el color para los segmentos
   zz_current0.LinesColor(clrRed);
   zz_current1.LinesColor(clrLimeGreen);
   zz_current2.LinesColor(clrMediumPurple);
   zz_h1.LinesColor(clrCornflowerBlue);

En la función OnTick(), obtenemos primero los datos del marco temporal de una hora, y después obtenemos secuencialmente los datos del marco temporal menor para los segmentos primero, segundo y tercero. En los comentarios en el gráfico, mostramos la información de cada grupo de segmentos obtenidos del marco temporal menor, y por separado para el mayor. En este caso, se trata de la diferencia de los porcentajes de las sumas de los segmentos. Este valor se puede obtener con la ayuda del método CZigZagModule::PercentSumSegmentsDifference(). 

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   int copy_total=1000;
   int h_buff=2,l_buff=3;
//--- Primer método de obtención de datos
   ::CopyTime(_Symbol,PERIOD_H1,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_h1,h_buff,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_h1,l_buff,0,copy_total,l_zz);
   zz_h1.GetZigZagData(h_zz,l_zz,t_zz);
   zz_h1.ShowSegments("_h1");
//---
   datetime start_time_in =NULL;
   datetime stop_time_in  =NULL;
//--- Datos del primer segmento
   zz_h1.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,PERIOD_H1,_Period,0,start_time_in,stop_time_in);
   zz_current0.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
   zz_current0.ShowSegments("_current0");
//--- Datos del segundo segmento
   zz_h1.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,PERIOD_H1,_Period,1,start_time_in,stop_time_in);
   zz_current1.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
   zz_current1.ShowSegments("_current1");
//--- Datos del tercer segmento
   zz_h1.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,PERIOD_H1,_Period,2,start_time_in,stop_time_in);
   zz_current2.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
   zz_current2.ShowSegments("_current2");
//--- Mostrando los datos en los comentarios en el gráfico
   string comment="H1: "+::DoubleToString(zz_h1.PercentSumSegmentsDifference(),2)+"\n"+
                  "segment[0]: "+::DoubleToString(zz_current0.PercentSumSegmentsDifference(),2)+"\n"+
                  "segment[1]: "+::DoubleToString(zz_current1.PercentSumSegmentsDifference(),2)+"\n"+
                  "segment[2]: "+::DoubleToString(zz_current2.PercentSumSegmentsDifference(),2);
//---
   ::Comment(comment);
  }

Este es el aspecto que tiene en el gráfico:

 Fig. 5 – Demostrando cómo obtener los datos dentro de los tres segmentos indicados.

Fig. 5 – Demostrando cómo obtener los datos dentro de los tres segmentos indicados.

Al final, este enfoque ofrece posibilidades adicionales al analizar el carácter del comportamiento del precio dentro de los patrones. Supongamos que determinamos el patrón en el gráfico de una hora e investigamos cómo se ha comportado el precio dentro de cada segmento. Con la ayuda de los métodos de la clase CZigZagModule, podemos obtener todas las propiedades de los extremos y segmentos, tales como:

  • El precio, la hora y el número de la barra de un extremo por separado.
  • El tamaño de cada segmento por separado.
  • La duración de cada segmento en barras.
  • El tamaño del intervalo de precio de todo el conjunto de segmentos obtenidos.
  • La duración de la formación de todo el conjunto de segmentos (en barras).
  • La suma de los segmentos en una misma dirección.
  • La proporción de las sumas de los segmentos en distintas direcciones, etcétera. 

Usando este conjunto básico, podemos inventar multitud de valores propios y construir indicadores con ellos. Las pruebas nos mostrarán qué beneficio podemos extraer de todo ello. En esta página existen varios artículos que nos ayudarán a comenzar una investigación en esta dirección. 


Conclusión

Con mucha frecuencia, en los foros podemos leer opiniones acerca de que el indicador ZigZag no es adecuado para generar señales para realizar transacciones. Pero se trata de una equivocación. En realidad, no hay otro indicador que proporcione tanta información para determinar el carácter del comportamiento del precio. Ahora, el lector dispone de una herramienta que le ayudará a obtener los datos necesarios del indicador ZigZag para realizar un análisis más cómodo.

En las próximas partes de esta serie de artículos, mostraremos qué indicadores podemos crear con la ayuda de la clase CZigZagModule y del experto para obtener estadísticas mediante el ZigZag de diferentes símbolos, así como expertos para comprobar ciertas estrategias comerciales con este indicador.

Nombre del archivoComentarios
MQL5\Indicators\Custom\ZigZag\ExactZZ_Plus.mq5Versión modificada del indicador ZigZag
MQL5\Experts\ZigZag\TestZZ_01.mq5Experto para la simulación de un conjunto de datos
MQL5\Experts\ZigZag\TestZZ_02.mq5Experto para la simulación de tres conjuntos de datos de marcos temporales distintos
MQL5\Experts\ZigZag\TestZZ_03.mq5Experto para simular la obtención de los datos dentro del segmento indicado del marco temporal mayor.
MQL5\Experts\ZigZag\TestZZ_04.mq5Experto para simular la obtención de los datos dentro de los tres segmentos indicados del marco temporal mayor.


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

Archivos adjuntos |
MQL5.zip (17.45 KB)
Uso práctico de las redes neuronales de Kohonen en el trading algorítmico (Parte II) Optimización y previsión Uso práctico de las redes neuronales de Kohonen en el trading algorítmico (Parte II) Optimización y previsión

A base de las herramientas universales para el trabajo con las redes de Kohonen, se construye un sistema del análisis y la selección de los parámetros óptimos del EA, así como se considera la previsión de las series temporales. En la primera parte, corregimos y mejoramos las clases de redes neuronales disponibles públicamente, completándolas con algoritmos necesarios. Ahora ha llegado el momento para aplicarlas en la práctica.

Martingale como base de una estrategia comercial a largo plazo Martingale como base de una estrategia comercial a largo plazo

En este artículo, vamos a analizar con detalle el sistema martingale. Estudiaremos la posibilidad de aplicarlo, y cómo hacerlo de tal forma que reduzcamos los riesgos al mínimo. La principal desventaja de este sencillo sistema es la probabilidad de perder todo el depósito. Y usted debe tener este hecho en cuenta a la hora de comerciar, si es que finalmente decide usar dicho sistema de trading.

Utilidad para la selección y navegación en MQL5 y MQL4: añadiendo la búsqueda automática de patrones y visualización de símbolos encontrados Utilidad para la selección y navegación en MQL5 y MQL4: añadiendo la búsqueda automática de patrones y visualización de símbolos encontrados

En este artículo, seguiremos ampliando las capacidades de la utilidad para la selección y navegación por los instrumentos. Esta vez vamos a crear nuevas pestañas, con la apertura de las cuales van a abrirse sólo aquellos símbolos que correspondan a unos u otros parámetros nuestros. Asimismo, aprenderemos a incluir fácilmente nuestras propias pestañas con las reglas de filtración necesarias.

Estudio de técnicas de análisis de velas (Parte I): Comprobando los patrones existentes Estudio de técnicas de análisis de velas (Parte I): Comprobando los patrones existentes

En este artículo vamos a analizar modelos de velas (patrones) conocidos e investigar cuánto tienen de actual y efectivo en la realidad de hoy. El análisis de velas surgió hace más de 20 años, y sigue siendo bastante popular a día de hoy. Alguna gente incluso considera que las velas japonesas son el formato más fácil y cómodo para representar los precios de los activos.