El poder del ZigZag (Parte I). Desarrollando la clase base del indicador
Contenido
- Introducción
- Versión extendida del indicador ZigZag
- Clase para obtener los datos del indicador ZigZag
- Visualización del conjunto de datos obtenido
- Experto para la simulación de los resultados obtenidos
- Continuamos el desarrollo de la clase CZigZagModule
- Conclusión
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.
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.
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).
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).
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.
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.
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 archivo | Comentarios |
---|---|
MQL5\Indicators\Custom\ZigZag\ExactZZ_Plus.mq5 | Versión modificada del indicador ZigZag |
MQL5\Experts\ZigZag\TestZZ_01.mq5 | Experto para la simulación de un conjunto de datos |
MQL5\Experts\ZigZag\TestZZ_02.mq5 | Experto para la simulación de tres conjuntos de datos de marcos temporales distintos |
MQL5\Experts\ZigZag\TestZZ_03.mq5 | Experto para simular la obtención de los datos dentro del segmento indicado del marco temporal mayor. |
MQL5\Experts\ZigZag\TestZZ_04.mq5 | Experto 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 Ltd.
Artículo original: https://www.mql5.com/ru/articles/5543
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso