El poder del ZigZag (Parte II): Ejemplos de obtención, procesamiento y representación de datos.

10 mayo 2019, 16:44
Anatoli Kazharski
0
1 124

Contenido


Introducción

En la primera parte, describimos un indicador ZigZag modificado y una clase para la obtención de datos de los indicadores de este tipo. Ahora vamos a mostrar cómo crear indicadores basados ​​en dichas herramientas, además de escribir un experto para las pruebas, que realizará transacciones según las señales generadas por el indicador ZigZag.

Como añadido, en este artículo se ofrecerá una nueva versión de la biblioteca de creación de interfaces gráficas EasyAndFast

Los principales temas tratados en el artículo son:

  • los indicadores que se pueden usar para determinar la naturaleza del comportamiento del precio;
  • un experto con una interfaz gráfica para recopilar ciertas estadísticas sobre el comportamiento del precio;
  • un experto para calcular el número de segmentos del indicador ZigZag en los intervalos especificados.


Indicadores para determinar el carácter del comportamiento del precio

Vamos a analizar tres indicadores para definir la naturaleza del comportamiento del precio. 

  • FrequencyChangeZZ — calcula la frecuencia de formación de segmentos en direcciones opuestas del indicador ZigZag.
  • SumSegmentsZZ — calcula las sumas de los segmentos del conjunto obtenido y su media.
  • PercentageSegmentsZZ — determina el porcentaje de las sumas de los segmentos y la diferencia entre ellos.
  • MultiPercentageSegmentsZZ — determina el carácter de la formación de varios segmentos de un marco temporal mayor usando como base los valores del anterior indicador PercentageSegmentsZZ .

Cada uno de estos indicadores posee la misma estructura de código que el indicador ZigZag de la primera parte del artículo. Por lo tanto, nos vamos a centrar solo en la función principal (FillIndicatorBuffers), donde se reciben los datos y se rellenan los búferes de indicador.


Indicador FrequencyChangeZZ

Para el indicador FrequencyChangeZZ, el código de la función principal será como en la lista de abajo. A la función se transmiten el índice de la barra y la matriz tiempo. A continuación, a partir de la hora de la barra actual, se copia el número necesario de elementos (datos fuente) del indicador ZigZag y la matriz de tiempo. Si hemos obtenido los datos fuente, se solicitan los datos finales. Después de ello, solo queda llamar al método que retorna el número de barras en el conjunto de segmentos. El resultado se guarda en el elemento actual del búfer de indicador.

//+------------------------------------------------------------------+
//| Rellena los búferes de indicador                                 |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const datetime &time[])
  {
   int copy_total=1000;
   for(int t=0; t<10; t++)
     {
      if(::CopyBuffer(zz_handle,2,time[i],copy_total,h_zz_buffer_temp)==copy_total &&
         ::CopyBuffer(zz_handle,3,time[i],copy_total,l_zz_buffer_temp)==copy_total &&
         ::CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total)
        {
         //--- Obteniendo los datos de ZZ
         zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
         //--- Guardando el número de barras en el conjunto de segmentos en el búfer de indicador
         segments_bars_total_buffer[i]=zz.SegmentsTotalBars();
         break;
        }
     }
  }

En los parámetros externos del indicador, señalaremos que:

(1) debemos calcular los valores en todos los datos disponibles del gráfico,
(2) la desviación mínima para formar un nuevo segmento del indicador ZigZag y
(3) el número de extremos para obtener los datos finales.

Todos los indicadores mostrados en este artículo tendrán los mismos parámetros

 Fig. 1 – Parámetros externos del indicador.

Fig. 1 – Parámetros externos del indicador.

En el gráfico, el indicador FrequencyChangeZZ dibuja un gráfico en una subventana, como se muestra a continuación. Para mayor claridad, hemos cargado el indicador ZigZag en el gráfico principal. Podemos ver claramente por los valores de este indicador cuándo se ralentiza el precio al elegir su dirección.  

 Fig. 2 - Demostración del funcionamiento del indicador FrequencyChangeZZ.

Fig. 2 - Demostración del funcionamiento del indicador FrequencyChangeZZ.


Indicador SumSegmentsZZ

En el indicador SumSegmentsZZ, la función principal de obtención de datos tiene el aspecto que se muestra en la lista siguiente. Lo mismo que en el ejemplo anterior. La única diferencia es que aquí se rellenan tres búferes de indicador. De forma separada para los segmentos dirigidos hacia arriba y hacia abajo. Y otro más para calcular la media de esos parámetros en los valores actuales.

//+------------------------------------------------------------------+
//| Rellena los búferes de indicador                                 |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const datetime &time[])
  {
   int copy_total=1000;
   for(int t=0; t<10; t++)
     {
      if(CopyBuffer(zz_handle,2,time[i],copy_total,h_zz_buffer_temp)==copy_total &&
         CopyBuffer(zz_handle,3,time[i],copy_total,l_zz_buffer_temp)==copy_total &&
         CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total)
        {
         //--- Obteniendo los datos de ZZ
         zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
         //--- Obteniendo los datos por segmentos
         segments_up_total_buffer[i] =zz.SumSegmentsUp();
         segments_dw_total_buffer[i] =zz.SumSegmentsDown();
         segments_average_buffer[i]  =(segments_up_total_buffer[i]+segments_dw_total_buffer[i])/2;
         break;
        }
     }
  }

Tras cargar el indicador SumSegmentsZZ en el gráfico, usted podrá ver un resultado como el de la captura de pantalla. Aquí se puede ver que, cuando la línea azul está por encima de la roja, la suma de los segmentos ascendentes es mayor que la suma de los segmentos descendentes. Y la lógica inversa, cuando la línea roja está por encima. Experimentando con el simulador de estrategias, podremos ver si este hecho indica o no de forma unívoca la dirección que seguirá el precio. A primera vista, podemos decir que, cuanto más supere la suma de los segmentos unidireccionales a la suma de los segmentos opuestos, mayor será la probabilidad de viraje. 

 Fig. 3 - Demostración del funcionamiento del indicador SumSegmentsZZ.

Fig. 3 - Demostración del funcionamiento del indicador SumSegmentsZZ.


Indicador PercentageSegmentsZZ

A continuación, vamos a analizar el indicador PercentageSegmentsZZ. En la función principal de este indicador, también se deben rellenar tres búferes de indicador: uno para cada uno de los porcentajes de las sumas de los segmentos dirigidos (1) hacia arriba, (2) hacia abajo y (3) por separado para la diferencia entre estos valores

//+------------------------------------------------------------------+
//| Rellena los búferes de indicador                                 |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const datetime &time[])
  {
   int copy_total=1000;
   for(int t=0; t<10; t++)
     {
      if(CopyBuffer(zz_handle,2,time[i],copy_total,h_zz_buffer_temp)==copy_total &&
         CopyBuffer(zz_handle,3,time[i],copy_total,l_zz_buffer_temp)==copy_total &&
         CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total)
        {
         //--- Obteniendo los datos de ZZ
         zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
         //--- Obteniendo los datos por segmentos
         double sum_up =zz.SumSegmentsUp();
         double sum_dw =zz.SumSegmentsDown();
         double sum    =sum_up+sum_dw;
         //--- Proporción porcentual y diferencia
         if(sum>0)
           {
            segments_up_total_buffer[i]   =zz.PercentSumSegmentsUp();
            segments_dw_total_buffer[i]   =zz.PercentSumSegmentsDown();
            segments_difference_buffer[i] =fabs(segments_up_total_buffer[i]-segments_dw_total_buffer[i]);
            break;
           }
        }
     }
  }

Más abajo se muestra lo que ha resultado finalmente. Vamos a intentar interpretar el resultado obtenido. Cuando la diferencia de porcentajes entre las sumas de los segmentos en direcciones opuestas es menor que un cierto umbral, podemos considerarlo flat. En este caso, también es necesario considerar que las proporciones a menudo cambian entre sí, ya que el precio puede desplazarse durante mucho tiempo en una dirección, y la diferencia en este caso estará por debajo del nivel elegido por el optimizador. En estos casos, se usan modelos en los que se analiza la formación de patrones en una secuencia determinada.

 Fig. 4 - Demostración del funcionamiento del indicador PercentageSegmentsZZ.

Fig. 4 - Demostración del funcionamiento del indicador PercentageSegmentsZZ.


Indicador MultiPercentageSegmentsZZ

En el artículo anterior, mostramos un experto en el que los datos del indicador ZigZag se analizaban de forma simultánea desde marcos temporales mayores y menores. Por consiguiente, fue posible analizar con más detalle cómo se comportaba el precio dentro de los segmentos del indicador desde un marco temporal mayor. Dicho de otra forma, determinamos cómo se formaron los segmentos del marco temporal mayor en el marco temporal menor. Vamos a intentar ver el aspecto de este grupo de indicadores como un indicador separado que muestra estos valores en la historia de precios.

Asimismo, como sucedía con el experto de la parte anterior, obtendremos los cuatro valores de diferencia entre los porcentajes de las sumas de los segmentos en dirección opuesta. Uno para el marco temporal mayor y tres para el menor, calculados según los tres últimos segmentos del indicador ZigZag en el marco temporal mayor. Los colores de los búferes de indicador serán los mismos que en el experto de la parte anterior. Después, crearemos un experto para probar de este indicador y será mucho más sencillo para nosotros entender qué datos y durante qué periodo de tiempo observamos en el gráfico.

//--- Número de búferes
#property indicator_buffers 4
#property indicator_plots   4
//--- Colores de los búferes de color
#property indicator_color1 clrSilver
#property indicator_color2 clrRed
#property indicator_color3 clrLimeGreen
#property indicator_color4 clrMediumPurple

Declaramos cuatro ejemplares de la clase CZigZagModule:

#include <Addons\Indicators\ZigZag\ZigZagModule.mqh>
CZigZagModule zz_higher_tf;
CZigZagModule zz_current0;
CZigZagModule zz_current1;
CZigZagModule zz_current2;

En los parámetros externos, añadimos la posibilidad de establecer el marco temporal para un indicador mayor:

input             int NumberOfBars    =0;         // Number of bars to calculate ZZ
input             int MinImpulseSize  =0;         // Minimum points in a ray
input             int CopyExtremum    =5;         // Copy extremums
input ENUM_TIMEFRAMES HigherTimeframe =PERIOD_H1; // Higher timeframe

La función principal para el rellenado de los búferes de indicador se ha implementado de la forma siguiente. Primero, obtenemos los datos fuente del marco temporal mayor, que indicamos en los parámetros externos. Después, obtenemos los datos finales y guardamos el valor del parámetro. A continuación, obtenemos consecutivamente los datos de los tres segmentos del indicador del marco temporal mayor. Después de ello, rellenamos todos los búferes de indicador. Para que el indicador se calcule correctamente con la historia y en la última barra en tiempo real/en el simulador, hemos tenido que escribir dos bloques de código aparte. 

//+------------------------------------------------------------------+
//| Rellena los búferes de indicador                                 |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const int total,const datetime &time[])
  {
   int index=total-i-1;
   int copy_total=1000;
   int h_buff=2,l_buff=3;
   datetime start_time_in =NULL;
   datetime stop_time_in  =NULL;
//--- Obteniendo los datos fuente del marco temporal mayor
   datetime stop_time=time[i]-(PeriodSeconds(HigherTimeframe)*copy_total);
   CopyBuffer(zz_handle_htf,2,time[i],stop_time,h_zz_buffer_temp);
   CopyBuffer(zz_handle_htf,3,time[i],stop_time,l_zz_buffer_temp);
   CopyTime(_Symbol,HigherTimeframe,time[i],stop_time,t_zz_buffer_temp);
//--- Obteniendo los datos finales del marco temporal mayor
   zz_higher_tf.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
   double htf_value=zz_higher_tf.PercentSumSegmentsDifference();
//--- Datos del primer segmento
   zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,0,start_time_in,stop_time_in);
   zz_current0.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
//--- Datos del segundo segmento
   zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,1,start_time_in,stop_time_in);
   zz_current1.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
//--- Datos del tercer segmento
   zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,2,start_time_in,stop_time_in);
   zz_current2.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
//--- En la última barra
   if(i<total-1)
     {
      buffer_zz_higher_tf[i] =htf_value;
      buffer_segment_0[i]    =zz_current0.PercentSumSegmentsDifference();
      buffer_segment_1[i]    =zz_current1.PercentSumSegmentsDifference();
      buffer_segment_2[i]    =zz_current2.PercentSumSegmentsDifference();
     }
//--- En la historia
   else
     {
      //--- Si hay una nueva barra del marco temporal mayor
      if(new_bar_time!=t_zz_buffer_temp[0])
        {
         new_bar_time=t_zz_buffer_temp[0];
         //---
         if(i>2)
           {
            int f=1,s=2;
            buffer_zz_higher_tf[i-f] =buffer_zz_higher_tf[i-s];
            buffer_segment_0[i-f]    =buffer_segment_0[i-s];
            buffer_segment_1[i-f]    =buffer_segment_1[i-s];
            buffer_segment_2[i-f]    =buffer_segment_2[i-s];
           }
        }
      else
        {
         buffer_zz_higher_tf[i] =htf_value;
         buffer_segment_0[i]    =zz_current0.PercentSumSegmentsDifference();
         buffer_segment_1[i]    =zz_current1.PercentSumSegmentsDifference();
         buffer_segment_2[i]    =zz_current2.PercentSumSegmentsDifference();
        }
     }
  }

Hacemos una copia del experto del anterior artículo y lo completamos con solo unas líneas, para poner a prueba el indicador MultiPercentageSegmentsZZ. Añadimos un parámetro externo para establecer el marco temporal mayor. Para que el indicador se muestre durante la simulación del experto en el simulador en el modo de visualización, basta con obtener su manejador

//--- Parámetros externos
input            uint CopyExtremum    =3;         // Copy extremums
input             int MinImpulseSize  =0;         // Min. impulse size
input ENUM_TIMEFRAMES HigherTimeframe =PERIOD_H1; // Higher timeframe

...

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {

...

//--- Ruta al indicador ZZ
   string zz_path1="Custom\\ZigZag\\ExactZZ_Plus.ex5";
   string zz_path2="Custom\\ZigZag\\MultiPercentageSegmentsZZ.ex5";
//--- Obtenemos los manejadores de los indicadores
   zz_handle_current   =::iCustom(_Symbol,_Period,zz_path1,0,MinImpulseSize,false,false);
   zz_handle_higher_tf =::iCustom(_Symbol,HigherTimeframe,zz_path1,0,MinImpulseSize,false,false);
   zz_handle           =::iCustom(_Symbol,_Period,zz_path2,0,MinImpulseSize,CopyExtremum,HigherTimeframe);

...

   return(INIT_SUCCEEDED);
  }

Este es el aspecto que tiene en el simulador:

 Fig. 5 - Demostración del funcionamiento del indicador MultiPercentageSegmentsZZ.

Fig. 5 - Demostración del funcionamiento del indicador MultiPercentageSegmentsZZ.

Todos los indicadores descritos anteriormente se pueden usar en diversas combinaciones y simultáneamente en diferentes marcos temporales. En el siguiente apartado, aplicando las herramientas analizadas, intentaremos recopilar algunas estadísticas sobre el conjunto de símbolos, para así comprender cuáles de ellos son más adecuados para comerciar en el canal de precios.


Experto para recopilar y representar estadísticas

Como añadido, en este artículo se publica una nueva versión de la biblioteca de creación de interfaces gráficas EasyAndFast. Solo vamos a enumerar aquí las posibilidades que tienen ahora los usuarios de esta biblioteca:

  • Cambiar el color de fondo de cada celda en el recuadro (clase CTable).
  • Dirección de la clasificación.
  • Si se ha establecido ese modo, al pulsar la casilla de verificación, no se destacará la línea en la celda del recuadro.
  • Añadido a la clase CKeys el soporte de teclado numérico.
  • Añadida la clase CFrame para combinar los elementos en grupos:

 Fig. 6 – Ejemplo de combinación de elementos en grupos.

Fig. 6 – Ejemplo de combinación de elementos en grupos.

  • Desplazamiento vertical con la ruleta del ratón en recuadros y listas.
  • Añadida la clase CWndCreate, en la que se incluyen los métodos de plantilla para la creación rápida de la mayor parte de los tipos de elementos. Debe usarse como básica para la clase personalizada. El uso de esta clase permite no repetir la declaración e implementación de los mismos métodos de creación de elementos en diferentes proyectos, lo que aumenta enormemente la velocidad del desarrollo. 
  • Añadida a la clase CElement la comprobación de secuencia correcta de creación de elementos.
  • En la clase CWndEvents, después de eliminar un elemento, es obligatorio resetear el identificador.
  • Añadido a la clase CWndEvents el método GetActiveWindowIndex() para obtener el índice de la ventana activada.
  • Correcciones en la clase CListView. En el método Clear() es necesario resetear algunos campos auxiliares para prevenir la salida de los límites de la matriz en otros métodos de la clase CListView.

Podrá descargar la nueva versión de la biblioteca en Codebase

A continuación, vamos a crear un experto de prueba para recopilar ciertas estadísticas, usando la nueva versión de la biblioteca EasyAndFast. Primero, crearemos la interfaz gráfica de la aplicación, y solo después crearemos los métodos para recopilar y mostrar las estadísticas.

Vamos a determinar los controles de la interfaz gráfica:

  • Formulario para los elementos de control.
  • Barra de estado.
  • Campo de entrada para filtrar las divisas que deben recopilarse en la lista de la ventana de observación del mercado.
  • Calendarios desplegables para indicar las fechas de inicio y finalización de la recopilación de estadísticas.
  • Campo de edición para el nivel del indicador.
  • Botón para solicitar los datos.
  • Recuadro para representar los datos recopilados.
  • Indicador de ejecución.

Como mencionamos anteriormente, para crear la interfaz gráfica de una forma más rápida y conveniente, ahora necesitamos conectar a la clase personalizada la clase CWndCreatecomo clase base. La conexión completa sería la siguiente: CWndContainer -> CWndEvents -> CWndCreate -> CProgram. La presencia de la clase CWndCreate en este esquema añade la capacidad de crear los elementos de la interfaz gráfica en una sola línea, sin crear métodos aparte en la clase personalizada, como se hacía antes. La clase contiene diferentes plantillas prácticamente para todos los elementos de la biblioteca. En caso necesario, podemos complementarla con nuevas plantillas. 

Para crear la interfaz gráfica, declararemos los elementos enumerados anteriormente, como se muestra en la siguiente lista de códigos. En la versión actual de la clase CWndCreate aún no existe una plantilla para la creación rápida de recuadros, por eso, crearemos este método nosostros mismos

//+------------------------------------------------------------------+
//|                                                      Program.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include <EasyAndFastGUI\WndCreate.mqh>
//+------------------------------------------------------------------+
//| Clase para crear la aplicación                                   |
//+------------------------------------------------------------------+
class CProgram : public CWndCreate
  {
private:
   //--- Ventana
   CWindow           m_window;
   //--- Barra de estado
   CStatusBar        m_status_bar;
   //--- Calendarios desplegables
   CDropCalendar     m_from_date;
   CDropCalendar     m_to_date;
   //--- Botones
   CButton           m_request;
   //--- Campo de edición
   CTextEdit         m_filter;
   CTextEdit         m_level;
   //--- Cuadros combinados
   CComboBox         m_data_type;
   //--- Recuadros
   CTable            m_table;
   //--- Barra de progreso
   CProgressBar      m_progress_bar;
   //---
public:
   //---Crea una interfaz gráfica
   bool              CreateGUI(void);
   //---
private:
   //--- Recuadros
   bool              CreateTable(const int x_gap,const int y_gap);
  };

Para crear una interfaz gráfica con dicho contenido, solo tenemos que llamar a los métodos necesarios de la clase CWndCreate, especificando como argumentos los valores de las propiedades, como se muestra en el listado de códigos a continuación. Para aclarar a qué propiedad se refiere este u otro parámetro del método, debemos colocar sobre él el cursor textual y pulsar la combinación de teclas Ctrl + Shift + Space:

 Fig. 7 – Visualizando los parámetros del método.

Fig. 7 – Visualizando los parámetros del método.

Si tenemos establecer directamente algunas propiedades adicionales, podremos hacerlo de igual forma que en el ejemplo del campo de edición del filtro de divisas. Aquí se indica que la casilla de verificación por defecto quedará desactivada justo después de crear el elemento.

//+------------------------------------------------------------------+
//| Crea la interfaz gráfica                                         |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
  {
//--- Creando el formulario para los controles
   if(!CWndCreate::CreateWindow(m_window,"ZZ Market Scanner",1,1,640,480,true,true,true,true))
      return(false);
//--- Barra de estado
   string text_items[1];
   text_items[0]="For Help, press F1";
   int width_items[]={0};
   if(!CWndCreate::CreateStatusBar(m_status_bar,m_window,1,23,22,text_items,width_items))
      return(false);
//--- Campo de edición para el filtrado de divisas
   if(!CWndCreate::CreateTextEdit(m_filter,"Symbols filter:",m_window,0,true,7,25,627,535,"USD","Example: EURUSD,GBP,NOK"))
      return(false);
   else
      m_filter.IsPressed(true);
//--- Calendarios desplegables
   if(!CWndCreate::CreateDropCalendar(m_from_date,"From:",m_window,0,7,50,130,D'2018.01.01'))
      return(false);
   if(!CWndCreate::CreateDropCalendar(m_to_date,"To:",m_window,0,150,50,117,::TimeCurrent()))
      return(false);
//--- Campo de edición para indicar el nivel
   if(!CWndCreate::CreateTextEdit(m_level,"Level:",m_window,0,false,280,50,85,50,100,0,1,0,30))
      return(false);
//--- Botón
   if(!CWndCreate::CreateButton(m_request,"Request",m_window,0,375,50,70))
      return(false);
//--- Recuadro
   if(!CreateTable(2,75))
      return(false);
//--- Barra de progreso
   if(!CWndCreate::CreateProgressBar(m_progress_bar,"Processing:",m_status_bar,0,2,3))
      return(false);
//--- Finalizando la creación de GUI
   CWndEvents::CompletedGUI();
   return(true);
  }

En el caso del recuadro, crearemos nuestro propio método, ya que se trata de un elemento complejo con un gran número de propiedades que se deben especificar antes de crear el elemento. En el recuadro habrá cuatro columnas. En la primera, se representarán las parejas de divisas. En las demás, se mostrarán los datos estadísticos de los tres marcos temporales: M5, H1, H8.

//+------------------------------------------------------------------+
//| Creando el recuadro                                              |
//+------------------------------------------------------------------+
bool CProgram::CreateTable(const int x_gap,const int y_gap)
  {
#define COLUMNS1_TOTAL 4
#define ROWS1_TOTAL    1
//--- Guardamos el puntero al elemento principal
   m_table.MainPointer(m_window);
//--- Matriz de anchura de las columnas
   int width[COLUMNS1_TOTAL];
   ::ArrayInitialize(width,50);
   width[0]=80;
//--- Matriz de sangría del texto en las columnas según el eje X
   int text_x_offset[COLUMNS1_TOTAL];
   ::ArrayInitialize(text_x_offset,7);
//--- Matriz de alineamiento del texto en columnas
   ENUM_ALIGN_MODE align[COLUMNS1_TOTAL];
   ::ArrayInitialize(align,ALIGN_CENTER);
   align[0]=ALIGN_LEFT;
//--- Propiedades
   m_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
   m_table.TextAlign(align);
   m_table.ColumnsWidth(width);
   m_table.TextXOffset(text_x_offset);
   m_table.ShowHeaders(true);
   m_table.IsSortMode(true);
   m_table.IsZebraFormatRows(clrWhiteSmoke);
   m_table.AutoXResizeMode(true);
   m_table.AutoYResizeMode(true);
   m_table.AutoXResizeRightOffset(2);
   m_table.AutoYResizeBottomOffset(24);
//--- Creamos el elemento de control
   if(!m_table.CreateTable(x_gap,y_gap))
      return(false);
//--- Encabezados
   string headers[]={"Symbols","M5","H1","H8"};
   for(uint i=0; i<m_table.ColumnsTotal(); i++)
      m_table.SetHeaderText(i,headers[i]);
//--- Añadimos el objeto a la matriz general de grupos de objetos
   CWndContainer::AddToElementsArray(0,m_table);
   return(true);
  }

Ahora, vamos analizar los métodos de obtención de datos. Primero, debemos obtener los símbolos con los que vamos a trabajar. En esta versión del experto, obtendremos los datos de los símbolos fórex. En este caso, además, excluiremos aquellos símbolos cuyo comercio esté prohibido. Aquí también se observa el método auxiliar CheckFilterText(), con cuya ayuda comprobaremos el símbolo según el filtro. En el campo de edición, el usuario puede introducir separados por comas los valores de texto que deben estar en los nombres de los símbolos. Si la casilla de verificación de este campo edición está desactivada o el texto no ha sido introducido, la comprobación no se realizará. Si estas comprobaciones han tenido lugar y necesitamos encontrar una coincidencia, entonces el texto introducido se divide en subcadenas y se realiza la búsqueda de la línea necesaria. 

class CProgram : public CWndCreate
  {
private:
   //--- Comprobando el símbolo según el filtro
   bool              CheckFilterText(const string symbol_name);
  };
//+------------------------------------------------------------------+
//| Comprobando el símbolo según el filtro                           |                           
//+------------------------------------------------------------------+
bool CProgram::CheckFilterText(const string symbol_name)
  {
   bool check=false;
//--- Si está activado el filtro de nombres de los símbolos
   if(!m_filter.IsPressed())
      return(true);
//--- Si se ha introducido el texto
   string text=m_filter.GetValue();
   if(text=="")
      return(true);
//--- Dividir en subcadenas
   string elements[];
   ushort sep=::StringGetCharacter(",",0);
   ::StringSplit(text,sep,elements);
//--- Comprobando la coincidencia
   int elements_total=::ArraySize(elements);
   for(int e=0; e<elements_total; e++)
     {
      //--- Eliminar los espacios de los extremos
      ::StringTrimLeft(elements[e]);
      ::StringTrimRight(elements[e]);
      //--- Si se han encontrado coincidencias
      if(::StringFind(symbol_name,elements[e])>-1)
        {
         check=true;
         break;
        }
     }
//--- Resultado
   return(check);
  }

En el método CProgram::GetSymbols(), iteramos por el ciclo por todos los símbolos disponibles en el servidor y reunimos en una matriz todos aquellos que se correspondan con los criterios indicados. En el ciclo general, se eliminan todos los símbolos de la ventana de observación del mercado, y después se añaden a esta ventana solo aquellos símbolos que están en la matriz

class CProgram : public CWndCreate
  {
private:
   //--- Matriz de símbolos
   string            m_symbols[];
   //---
private:
   //--- Obtiene los símbolos
   void              GetSymbols(void);
  };
//+------------------------------------------------------------------+
//| Obtiene los símbolos                                             |
//+------------------------------------------------------------------+
void CProgram::GetSymbols(void)
  {
//--- Progreso
   m_progress_bar.LabelText("Get symbols...");
   m_progress_bar.Update(0,1);
//--- Limpiando la matriz de símbolos
   ::ArrayFree(m_symbols);
//--- Reunimos la matriz de símbolos fórex
   int symbols_total=::SymbolsTotal(false);
   for(int i=0; i<symbols_total; i++)
     {
      //--- Obtenemos el nombre del símbolo
      string symbol_name=::SymbolName(i,false);
      //--- Ocultándolo en la ventana de Observación del mercado
      ::SymbolSelect(symbol_name,false);
      //--- Si no se trata de un símbolo fórex, pasamos al siguiente
      if(::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_CALC_MODE)!=SYMBOL_CALC_MODE_FOREX)
         continue;
      //--- Si el comercio está prohibido, pasamos al siguiente
      if(::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_DISABLED)
         continue;
      //--- Comprobando el símbolo según el filtro
      if(!CheckFilterText(symbol_name))
         continue;
      //--- Guardamos el símbolo en la matriz
      int array_size=::ArraySize(m_symbols);
      ::ArrayResize(m_symbols,array_size+1,1000);
      m_symbols[array_size]=symbol_name;
     }
//--- Si la matriz está vacía, establecemos el símbolo actual por defecto
   int array_size=::ArraySize(m_symbols);
   if(array_size<1)
     {
      ::ArrayResize(m_symbols,array_size+1);
      m_symbols[array_size]=_Symbol;
     }
//--- Mostramos en la ventana de Observación del mercado
   int selected_symbols_total=::ArraySize(m_symbols);
   for(int i=0; i<selected_symbols_total; i++)
      ::SymbolSelect(m_symbols[i],true);
  }

Para obtener los datos de los símbolos reunidos, debemos primero obtener los manejadores del indicador en ellos. Cada vez que obtenemos el manejador del indicador, debemos esperar hasta el final de su cálculo, antes de copiar sus datos para el posterior análisis. Después de obtener todos los datos, se realizan los cálculos necesarios. 

Con este objetivo se ha pensado el método CProgram::GetSymbolsData(). El método adopta dos parámetros: el símbolo y el marco temporal. Una vez obtenid el manejador del indicador, descubrimos cuántas barras hay en el intervalo temporal establecido. El intervalo de fechas se puede indicar usando los controles en la interfaz gráfica de la aplicación. A continuación, vamos a intentar obtener los datos de cálculo del indicador. La cuestión es que justo después de obtener el manejador, el cálculo del indicador podría no haber finalizado. Por eso, si la función BarsCalculated() ha retornado el valor -1, intentamos obtener de nuevo el valor correcto, hasta que sea igual o mayor que el número total de barras en el intervalo temporal indicado.

Después de que se hayan calculado los datos del indicador, podemos tratar de obtenerlos en la matriz. Aquí también podría ser que necesitemos varios intentos hasta que el número sea superior o igual o mayor que el valor total del número de barras

Si los datos del indicador se han copiado con éxito en la matriz, solo queda realizar los cálculos necesarios. En este caso, calculamos el porcentaje de la proporción entre el número total de datos y la cantidad de aquellos datos presentes cuando el valor del indicador está por encima del nivel establecido. Este nivel también se puede indicar en la interfaz gráfica de la aplicación. 

Al final del método, debemos eliminar el manejador del indicador, liberando con ello su parte de cálculos. El método CProgram::GetSymbolsData() se llamará varias veces para la lista de símbolos indicada y en varios marcos temporales. El cálculo para cada uno debe realizarse una sola vez; el valor resultante se mostrará en el recuadro de la interfaz gráfica, por lo que ya no se necesitarán los controles y podrán ser eliminados. 

class CProgram : public CWndCreate
  {
private:
   //--- Obteniendo los datos de los símbolos
   double            GetSymbolsData(const string symbol,const ENUM_TIMEFRAMES period);
  };
//+------------------------------------------------------------------+
//| Obteniendo los datos de los símbolos                             |
//+------------------------------------------------------------------+
double CProgram::GetSymbolsData(const string symbol,const ENUM_TIMEFRAMES period)
  {
   double result       =0.0;
   int    buffer_index =2;
//--- Obtenemos el manejador del indicador
   string path   ="::Indicators\\Custom\\ZigZag\\PercentageSegmentsZZ.ex5";
   int    handle =::iCustom(symbol,period,path,0,0,5);
   if(handle!=INVALID_HANDLE)
     {
      //--- Copiando los datos en el intervalo indicado
      double   data[];
      datetime start_time =m_from_date.SelectedDate();
      datetime end_time   =m_to_date.SelectedDate();
      //--- Número de barras en el intervalo indicado
      int bars_total=::Bars(symbol,period,start_time,end_time);
      //--- Número de barras en el intervalo indicado
      int bars_calculated=::BarsCalculated(handle);
      if(bars_calculated<bars_total)
        {
         while(true)
           {
            ::Sleep(100);
            bars_calculated=::BarsCalculated(handle);
            if(bars_calculated>=bars_total)
               break;
           }
        }
      //--- Obteniendo los datos
      int copied=::CopyBuffer(handle,buffer_index,start_time,end_time,data);
      if(copied<1)
        {
         while(true)
           {
            ::Sleep(100);
            copied=::CopyBuffer(handle,buffer_index,start_time,end_time,data);
            if(copied>=bars_total)
               break;
           }

        }
      //--- Salimos, si los datos no han sido obtenidos
      int total=::ArraySize(data);
      if(total<1)
         return(result);
      //--- Calculando el número de repeticiones
      int counter=0;
      for(int k=0; k<total; k++)
        {
         if(data[k]>(double)m_level.GetValue())
            counter++;
        }
      //--- Proporción porcentual
      result=((double)counter/(double)total)*100;
     }
//--- Liberando el indicador
   ::IndicatorRelease(handle);
//--- Retornando el valor
   return(result);
  }

Cada vez que reunimos una nueva lista de símbolos, debemos reconstruir el recuadro de nuevo Para ello, solo tenemos que eliminar todas las líneas y añadir el número necesario.

class CProgram : public CWndCreate
  {
private:
   //--- Reconstruyendo el recuadro
   void              RebuildingTables(void);
  };
//+------------------------------------------------------------------+
//| Reconstruyendo el recuadro                                       |
//+------------------------------------------------------------------+
void CProgram::RebuildingTables(void)
  {
//--- Eliminar todas las líneas
   m_table.DeleteAllRows();
//--- Añadiendo los datos
   int symbols_total=::ArraySize(m_symbols);
   for(int i=1; i<symbols_total; i++)
      m_table.AddRow(i);
  }

Para rellenar las columnas del recuadro con datos, se usa el método CProgram::SetData(). A este se le transmiten dos parámetros: el índice de la columna y el marco temporal. Aquí, iteramos en el ciclo por las celdas de la columna indicada y las rellenamos con los valores calculados. Para que el usuario tenga claro lo que está sucediendo ahora, vamos a mostrar una barra de progreso en la que se representarán el símbolo y el marco temporal para los que se ha obtenido la información.

class CProgram : public CWndCreate
  {
private:
   //--- Estableciendo los valores en la columna indicada
   void              SetData(const int column_index,const ENUM_TIMEFRAMES period);
   //--- Marco temporal en la línea
   string            GetPeriodName(const ENUM_TIMEFRAMES period);
  };
//+------------------------------------------------------------------+
//| Estableciendo los valores en la columna indicada                 |
//+------------------------------------------------------------------+
void CProgram::SetData(const int column_index,const ENUM_TIMEFRAMES period)
  {
   for(uint r=0; r<(uint)m_table.RowsTotal(); r++)
     {
      double value=GetSymbolsData(m_symbols[r],period);
      m_table.SetValue(column_index,r,string(value),2,true);
      m_table.Update();
      //--- Progreso
      m_progress_bar.LabelText("Data preparation ["+m_symbols[r]+","+GetPeriodName(period)+"]...");
      m_progress_bar.Update(r,m_table.RowsTotal());
     }
  }
//+------------------------------------------------------------------+ 
//| Retornando el valor de línea del periodo                         |
//+------------------------------------------------------------------+ 
string CProgram::GetPeriodName(const ENUM_TIMEFRAMES period)
  {
   return(::StringSubstr(::EnumToString(period),7));
  }

Aquí, el método principal para rellenar el recuadro con datos es CProgram::SetDataToTable(). Primero se reconstruye el recuadro. A continuación, debemos establecer en él los encabezados y el tipo de datos (TYPE_DOUBLE). En la primera columna establecemos los símbolos reunidos. Después de esto, debemos redibujar el recuadro, para ver los cambios de inmediato.

Ahora, podemo proceder a obtener los datos de los indicadores en todos los símbolos y marcos temporales establecidos. Para ello, solo tenemos que llamar aquí al método CProgram::SetData(), transmitiendo como parámetros el índice de la columna y el marco temporal. 

class CProgram : public CWndCreate
  {
private:
   //--- Rellenando el recuadro con datos
   void              SetDataToTable(void);
  };
//+------------------------------------------------------------------+
//| Rellenando el recuadro con datos                                 |
//+------------------------------------------------------------------+
void CProgram::SetDataToTable(void)
  {
//--- Progreso
   m_progress_bar.LabelText("Data preparation...");
   m_progress_bar.Update(0,1);
//--- Redibujando el recuadro
   RebuildingTable();
//--- Encabezados
   string headers[]={"Symbols","M5","H1","H8"};
   for(uint i=0; i<m_table.ColumnsTotal(); i++)
      m_table.SetHeaderText(i,headers[i]);
   for(uint i=1; i<m_table.ColumnsTotal(); i++)
      m_table.DataType(i,TYPE_DOUBLE);
//--- Establecemos los valores en la primera columna
   for(uint r=0; r<(uint)m_table.RowsTotal(); r++)
      m_table.SetValue(0,r,m_symbols[r],0,true);
//--- Mostrar recuadro
   m_table.Update(true);
//--- Rellenando con datos las demás columnas
   SetData(1,PERIOD_M5);
   SetData(2,PERIOD_H1);
   SetData(3,PERIOD_H8);
  }

Antes de comenzar el proceso de obtención de datos con la ayuda del método CProgram::GetData(), debemos hacer visible el indicador de ejecución. Para ello, se usa el método CProgram::StartProgress(). Después de finalizar el proceso de obtención de datos, debemos ocultar el indicador de ejecución y quitar el enfoque con el botón pulsado. Para lograrlo, debemos llamar al método CProgram::EndProgress()

class CProgram : public CWndCreate
  {
private:
   //--- Obteniendo datos
   void              GetData(void);

   //--- (1) Comienzo y (2) final del proceso
   void              StartProgress(void);
   void              EndProgress(void);
  };
//+------------------------------------------------------------------+
//| Obteniendo datos                                                 |
//+------------------------------------------------------------------+
void CProgram::GetData(void)
  {
//--- Iniciando del progreso
   StartProgress();
//--- Obteniendo la lista de símbolos
   GetSymbols();
//--- Rellenando el recuadro con datos
   SetDataToTable();
//--- Finalizando el progreso
   EndProgress();
  }
//+------------------------------------------------------------------+
//| Iniciando del progreso                                           |
//+------------------------------------------------------------------+
void CProgram::StartProgress(void)
  {
   m_progress_bar.LabelText("Please wait...");
   m_progress_bar.Update(0,1);
   m_progress_bar.Show();
   m_chart.Redraw();
  }
//+------------------------------------------------------------------+
//| Finalizando el progreso                                          |
//+------------------------------------------------------------------+
void CProgram::EndProgress(void)
  {
//--- Ocultando el indicador de ejecución
   m_progress_bar.Hide();
//--- Actualizar el botón
   m_request.MouseFocus(false);
   m_request.Update(true);
   m_chart.Redraw();
  }

Cuando el usuario pulsa el botón Request, se genera el evento personalizado ON_CLICK_BUTTON; según el identificador del elemento, podemos determinar qué botón ha sido pulsado. Si se trata del botón Request, iniciamos el proceso de obtención de datos

En el método de creación del recuadro, hemos incluido la posibilidad de clasificar el recuadro pulsando sus encabezados. Cada vez que hacemos esto, se genera el evento personalizado ON_SORT_DATA. Al obtener este evento, es necesario actualizar el recuadro para representar los cambios

//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Eventos de pulsación de botones
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      if(lparam==m_request.Id())
        {
         //--- Obteniendo datos
         GetData();
         return;
        }
      //---
      return;
     }
//--- Eventos del recuadro clasificado
   if(id==CHARTEVENT_CUSTOM+ON_SORT_DATA)
     {
      if(lparam==m_table.Id())
        {
         m_table.Update(true);
         return;
        }
      //---
      return;
     }
  }

Vamos a ver ahora qué hemos logrado con ello. Si compilamos un programa y lo cargamos en el gráfico, veremos un resultado como el que se muestra en la captura de pantalla. Por defecto, se han establecido los parámetros siguientes:

  • El campo de edición Symbols filter está activado, y en él se indica que debemos obtener los datos solo de aquellos símbolos en cuyo nombre se encuentre USD.
  • Los datos se deben obtener en el intervalo de fechas que va del 2018.01.01 al 2018.12.21.
  • En el campo Level, se ha establecido un nivel con un valor 30, con respecto al cual se realizan los cálculos.
  • En esta versión, los marcos temporales en los que debemos realizar los cálculos están rigurosamente definidos en el código: M5, H1, H8.

 Fig. 8 – Interfaz gráfica de la aplicación MQL.

Fig. 8 – Interfaz gráfica de la aplicación MQL.

La pulsación del botón Request inicia el proceso de obtención de datos:

 Fig. 9 – Proceso de obtención de datos.

Fig. 9 – Proceso de obtención de datos.

Después de obtener todos los datos, podrá clasificarlos:

 Fig. 10 – Clasificación de los datos del recuadro.

Fig. 10 – Clasificación de los datos del recuadro.

Podrá modificar y usar esta aplicación para resolver algunas de sus tareas. La tabla se puede rellenar con cualquier otro valor.

Como ejemplo adicional del uso de los recuadros, vamos a mostrar a continuación cómo hacer más sencilla la visibilidad de sus datos. Ya mencionamos al inicio de este apartado del artículo, que en la última versión de la biblioteca EasyAndFast existe la posibilidad de establecer el color del fondo de las celdas del recuadro. Esto permite formatear el recuadro según nuestras propias condiciones, al igual que se hace en diferentes editores de recuadros. En la captura de pantalla de abajo se muestra cómo formatear los datos en los recuadros de Excel. Para cada celda se ha establecido su propio color de fondo, e incluso a la hora de clasificar las matrices, los colores de los fondos se quedarán siempre con los mismos valores.

 Fig. 11 – Escalas de color en Excel.

Fig. 11 – Escalas de color en Excel.

Este tipo de formateo de recuadros da la posibilidad de realizar más rápidamente el análisis visual de los datos en las investigaciones. 

Vamos a introducir cambios y adiciones en la aplicación MQL que hemos estudiado anteriormente. Para establecer un único color en cada celda del recuadro, debemos desactivar el formateo en el estilo «cebra». Vamos a comentar esta línea de código.

// m_table.IsZebraFormatRows(clrWhiteSmoke);

Ahora vamos a crear el método para formatear el recuadro: CProgram::SetColorsToTable(). Para trabajar con el color, usaremos la clase CColors. Ya existe en la biblioteca de creación de interfaces gráficas, por eso, no será necesario incluir el archivo en el proyecto. Declaramos dos matrices para el trabajo: (1) una matriz para obtener los colores del gradiente, y otra (2) matriz para los colores que se usarán para formar el gradiente. En este caso, crearemos un gradiente de tres colores. Cuanto menor sea el valor, más se aproximará el color al matiz rojo (clrTomato). Cuanto mayor sea el valor, más se aproximará el color al azul (clrCornflowerBlue). Para definir estas dos zonas de color, añadiremos el color blanco como color de transición (intermedio). 

Vamos determinar el intervalo de valores desde el mínimo al máximo. Precisamente ese tamaño será la matriz del gradiente. Estableceremos el tamaño de la matriz y rellenaremos esta en el método CColors::Gradient(). En el ciclo final se establecen los colores de las celdas del recuadro. Para no salirnos de los límites de la matriz, el índice se calcula como el valor de la celda menos el valor mínimo del intervalo. Al final del método, es obligatorio actualizar el recuadro para representar todos los cambios.

class CProgram : public CWndCreate
  {
private:
   //--- Rellenando el recuadro con el color de fondo para las celdas
   void              SetColorsToTable(void);
  };
//+------------------------------------------------------------------+
//| Formateando el recuadro                                          |
//+------------------------------------------------------------------+
void CProgram::SetColorsToTable(void)
  {
//--- Para trabajar con el color
   CColors clr;
//--- Matriz para obtener el gradiente
   color out_colors[];
//--- Gradiente de tres colores
   color colors[3]={clrTomato,clrWhite,clrCornflowerBlue};
//--- Encontrando los valores mínimo y máximo en el recuadro
   double max =0;
   double min =100;
   for(uint c=1; c<(uint)m_table.ColumnsTotal(); c++)
     {
      for(uint r=0; r<(uint)m_table.RowsTotal(); r++)
        {
         max =::fmax(max,(double)m_table.GetValue(c,r));
         min =::fmin(min,(double)m_table.GetValue(c,r));
        }
     }
//--- Corrigiendo hasta el entero inferior más próximo
   max =::floor(max);
   min =::floor(min);
//--- Obteniendo el intervalo
   int range =int(max-min)+1;
//--- Obteniendo la matriz de gradiente de los colores
   clr.Gradient(colors,out_colors,range);
//--- Estableciendo el color de fondo de las celdas
   for(uint c=1; c<(uint)m_table.ColumnsTotal(); c++)
     {
      for(uint r=0; r<(uint)m_table.RowsTotal(); r++)
        {
         int index=(int)m_table.GetValue(c,r)-(int)min;
         m_table.BackColor(c,r,out_colors[index],true);
        }
     }
//--- Actualizar el recuadro
   m_table.Update();
  }

Más abajo se muestra el aspecto que tiene en la interfaz gráfica. En este caso, los resultados indican que, cuanto menor sea el valor, menos tendencias hay en el segmento investigado. Quizá tenga sentido definir cómo dar un intervalo más amplio, para obtener la información con el mayor número de datos posible. 

 Fig. 12 – Escala de color para visualizar los datos en el recuadro.

Fig. 12 – Escala de color para visualizar los datos en el recuadro.

Tenga en cuenta que, cuantos más intervalos de fechas establezca, más datos utilizará y, por consiguiente, más tiempo necesitará para formar los datos y calcular los parámetros. Si nos faltan algunos datos, se intentará descargarlos desde el servidor. 


Calculando el número de segmentos según el tamaño

Ahora, vamos a escribir un programa para calcular el número de segmentos según su tamaño. Haremos una copia del experto del apartado anterior e introduciremos los cambios y adiciones necesarios. Aquí habrá dos recuadros: en el primero de ellos, se usa solo una columna con la lista de símbolos investigados. En el segundo recuadro, habrá dos columnas de datos: (1) intervalos crecientes en puntos y (2) número de segmentos por intervalos en la primera columna. En la captura de pantalla se muestra qué aspecto tiene la interfaz gráfica justo después de cargar el programa en el gráfico.

 Fig. 13 – Programa para calcular el número de segmentos según su tamaño.

Fig. 13 – Programa para calcular el número de segmentos según su tamaño.

El botón Request solicita la lista de símbolos según el filtro indicado. Al pulsar el botón Calculate, se recopilan los datos del intervalo temporal indicado. Dichos datos se distriubyen posteriormente en el segundo recuadro. 

Básicamente, todos los métodos se han quedado igual que en el experto anterior, por eso, vamos a analizar solo lo relacionado con el segundo recuadro. En primer lugar, debemos obtener los datos del indicador. Esto se hace en el método CProgram::GetIndicatorData(). Primero conectamos con el indicador ZigZag, y después obtenemos sus datos en el intervalo temporal establecido. En la barra de estado, mostraremos la información con el símbolo, el periodo y el número de segmentos del indicador obtenidos. 

class CProgram : public CWndCreate
  {
private:
   //--- Obteniendo los datos del indicador
   void              GetIndicatorData(const string symbol,const ENUM_TIMEFRAMES period);
  };
//+------------------------------------------------------------------+
//| Obteniendo los datos del indicador                               |
//+------------------------------------------------------------------+
void CProgram::GetIndicatorData(const string symbol,const ENUM_TIMEFRAMES period)
  {
//--- Obtenemos el manejador del indicador
   string path   ="::Indicators\\Custom\\ZigZag\\ExactZZ_Plus.ex5";
   int    handle =::iCustom(symbol,period,path,0,0);
   if(handle!=INVALID_HANDLE)
     {
      //--- Copiando los datos en el intervalo indicado
      datetime start_time =m_from_date.SelectedDate();
      datetime end_time   =m_to_date.SelectedDate();
      m_zz.GetZigZagData(handle,2,3,symbol,period,start_time,end_time);
      //--- Mostrando la información en la barra de estado
      string text="["+symbol+","+(string)GetPeriodName(period)+"] - Segments total: "+(string)m_zz.SegmentsTotal();
      m_status_bar.SetValue(0,text);
      m_status_bar.GetItemPointer(0).Update(true);
     }
//--- Liberando el indicador
   ::IndicatorRelease(handle);
  }

Para la primera columna del recuadro, debemos calcular los intervalos de precio con el salto indicado. Para ello, se usa el método CProgram::GetLevels(). Para determinar el número de intervalos, primero debemos obtener el tamaño máximo del segmento en el conjunto de datos obtenido. Acto seguido, rellenamos en el ciclo la matriz con niveles con el salto indicado hasta que lleguemos hasta el valor máximo. 

class CProgram : public CWndCreate
  {
private:
   //--- Matriz de intervalos
   int               m_levels_array[];
   //---
private:
   //--- Obteniendo los niveles
   void              GetLevels(void);
  };
//+------------------------------------------------------------------+
//| Obteniendo los niveles                                           |
//+------------------------------------------------------------------+
void CProgram::GetLevels(void)
  {
//--- Liberando la matriz
   ::ArrayFree(m_levels_array);
//--- Obteniendo el tamaño máximo del segmento
   int max_value=int(m_zz.LargestSegment()/m_symbol.Point());
//--- Rellenando la matriz con niveles
   int counter_levels=0;
   while(true)
     {
      int size=::ArraySize(m_levels_array);
      ::ArrayResize(m_levels_array,size+1);
      m_levels_array[size]=counter_levels;
      //---
      if(counter_levels>max_value)
         break;
      //---
      counter_levels+=(int)m_step.GetValue();
     }
  }

Para rellenar el segundo recuadro con datos, se ha pensado el método CProgram::SetDataToTable2(). Al inicio, se comprueba si se ha destacado o no un símbolo en la lista del primer recuadro. Si resulta que no, el programa saldrá del método, enviando a continuación un mensaje al diario de registro de expertos. Si se ha destacado alguna línea en el primer recuadro, determinamos el símbolo y obtenemos los datos del mismo. Después de ello, se llaman los métodos analizados anteriormente, para obtener los datos del indicador y calcular los niveles. Obtenemos los datos del indicador con el mismo marco temporal en el que se ha cargado el experto.

Cuando ya conocemos el número de niveles, ya podemos construir un recuadro del tamaño necesario y rellenarlo con valores. Primero, rellenamos la primera columna con los valores de los intervalos. A continuación, rellenamos la segunda columna. De manera secuencial y por todos los intervalos, aumentamos el contador en las celdas para los segmentos que se ubican en este intervalo.

class CProgram : public CWndCreate
  {
private:
   //--- Rellenando el recuadro 2 con datos
   void              SetDataToTable2(void);
  };
//+------------------------------------------------------------------+
//| Rellenando el recuadro 2 con datos                               |
//+------------------------------------------------------------------+
void CProgram::SetDataToTable2(void)
  {
//--- Salir, si la línea no está seleccionada
   if(m_table1.SelectedItem()==WRONG_VALUE)
     {
      ::Print(__FUNCTION__," > Select a symbol in the table on the left!");
      return;
     }
//--- Iniciando del progreso
   StartProgress();
//--- Ocultar recuadro
   m_table2.Hide();
//--- Obteniendo el símbolo del primer recuadro
   string symbol=m_table1.GetValue(0,m_table1.SelectedItem());
   m_symbol.Name(symbol);
//--- Obteniendo los datos del indicador
   GetIndicatorData(symbol,_Period);
//--- Obteniendo los niveles
   GetLevels();
//--- Redibujando el recuadro
   RebuildingTable2();
//--- Estableciendo los intervalos en la primera columna
   for(uint r=0; r<(uint)m_table2.RowsTotal(); r++)
      m_table2.SetValue(0,r,(string)m_levels_array[r],0);
//--- Obteniendo los valores en la segunda columna
   int items_total=::ArraySize(m_levels_array);
   int segments_total=m_zz.SegmentsTotal();
   for(int i=0; i<items_total-1; i++)
     {
      //--- Progreso
      m_progress_bar.LabelText("Get data ["+(string)m_levels_array[i]+"]...");
      m_progress_bar.Update(i,m_table2.RowsTotal());
      //---
      for(int s=0; s<segments_total; s++)
        {
         int size=int(m_zz.SegmentSize(s)/m_symbol.Point());
         if(size>m_levels_array[i] && size<m_levels_array[i+1])
           {
            int value=(int)m_table2.GetValue(1,i)+1;
            m_table2.SetValue(1,i,(string)value,0);
           }
        }
     }
//--- Mostrar recuadro
   m_table2.Update(true);
//--- Finalizando el progreso
   EndProgress();
  }

Para el ejemplo, obtendremos ejemplos de los segmentos del símbolo EURUSD desde el 2010 hasta la actualidad en el gráfico de cinco minutos. Pondremos los intervalos con un salto 100 de cinco dígitos. El resultado se muestra en la captura de pantalla de abajo.

El número total de segmentos es 302145. Podemos ver que el número máximo de segmentos se encuentra en el intervalo de 0 a 100. A continuación, el número de segmentos va disminuyendo de un nivel a otro. En el periodo temporal indicado, el tamaño máximo del segmento ha alcanzado 2400 puntos de cinco dígitos. 

 Fig. 14 – Resultado del cálculo del número de segmentos según su tamaño.

Fig. 14 – Resultado del cálculo del número de segmentos según su tamaño.


Calculando el número de segmentos según la duración

Para disponer de información más completa, estaría bien conocer la longitud de los segmentos en los grupos reunidos. Para encontrar algún patrón, deberemos disponer de todas las estadísticas de los datos investigados. Vamos a crear una versión más del experto. Bastará con hacer una copia del programa del anterior apartado y añadir otro recuadro a la interfaz gráfica. En el recuadro habrá dos columnas: (1) el número de barras y (2) el número de segmentos con este número de barras. En la captura de pantalla se muestra qué aspecto tiene la interfaz gráfica justo después de cargar el programa en el gráfico.

 Fig. 15 – Programa para calcular el número de segmentos según su duración.

Fig. 15 – Programa para calcular el número de segmentos según su duración.

La secuencia de acciones para obtener los datos en todos los recuadros será la siguiente:

  • Pulsamos el botón Request para obtener la lista de símbolos.
  • Elegimos el símbolo destacando la línea en el primer recuadro.
  • Pulsamos el botón Calculate para obtener los datos en el segundo recuadro.
  • Para obtener los datos en el tercer recuadro, elegimos el intervalo necesario destacando la línea en el segundo recuadro.

En la lista de abajo se muestra el código del método CProgram::SetDataToTable3() para obtener los datos y rellenar el tercer recuadro. De acuerdo con la línea destacada aquí, obtenemos el intervalo en el que debemos calcular el número de segmentos según su duración. El número de líneas en este recuadro es determinado por el segmento de mayor duración en barras del conjunto de datos obtenido. Al rellenar la segunda columna del recuadro, iteramos secuencialmente todas las líneas y calculamos los segmentos que se ajustan según el tamaño del intervalo seleccionado y según el número de barras.

class CProgram : public CWndCreate
  {
private:
   //--- Rellenando el recuadro 3 con datos
   void              SetDataToTable3(void);
  };
//+------------------------------------------------------------------+
//| Rellenando el recuadro 3 con datos                               |
//+------------------------------------------------------------------+
void CProgram::SetDataToTable3(void)
  {
//--- Salir, si la línea no está seleccionada
   if(m_table2.SelectedItem()==WRONG_VALUE)
     {
      ::Print(__FUNCTION__," > Select a range in the table on the left!");
      return;
     }
//--- Iniciando del progreso
   StartProgress();
//--- Ocultar recuadro
   m_table3.Hide();
//--- Obtenemos la línea destacada
   int selected_row_index=m_table2.SelectedItem();
//--- Intervalo
   int selected_range=(int)m_table2.GetValue(0,selected_row_index);
//--- Redibujando el recuadro
   RebuildingTable3();
//--- Establecemos los valores en la primera columna
   for(uint r=0; r<(uint)m_table3.RowsTotal(); r++)
      m_table3.SetValue(0,r,(string)(r+1),0);
//--- Obteniendo los valores en la segunda columna
   int segments_total=m_zz.SegmentsTotal();
   for(uint r=0; r<(uint)m_table3.RowsTotal(); r++)
     {
      //--- Progreso
      m_progress_bar.LabelText("Get data ["+(string)r+"]...");
      m_progress_bar.Update(r,m_table3.RowsTotal());
      //---
      for(int s=0; s<segments_total; s++)
        {
         int size =int(m_zz.SegmentSize(s)/m_symbol.Point());
         int bars =m_zz.SegmentBars(s);
         //---
         if(size>selected_range && 
            size<selected_range+(int)m_step.GetValue() && 
            bars==r+1)
           {
            int value=(int)m_table3.GetValue(1,r)+1;
            m_table3.SetValue(1,r,(string)value,0);
           }
        }
     }
//--- Mostrar recuadro
   m_table3.Update(true);
//--- Finalizando el progreso
   EndProgress();
  }

Al destacar una línea en los recuadros y listas, se genera el evento personalizado ON_CLICK_LIST_ITEM. En este caso, monitoreamos la llegada de este evento con el identificador del segundo recuadro.

//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Eventos de pulsación en las líneas
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM)
     {
      //--- Pulsando una línea del recuadro
      if(lparam==m_table2.Id())
        {
         //--- Obteniendo los datos en el tercer recuadro
         SetDataToTable3();
         return;
        }
      //---
      return;
     }
...
  }

Al obtener una nueva lista de símbolos o al calcular los datos en un nuevo símbolo destacado en el primer recuadro, deberemos limpiar los datos obsoletos de los cálculos anteriores, para que no haya confusiones sobre qué datos se representan ahora.

//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Eventos de pulsación de botones
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- Pulsando el botón 'Request'
      if(lparam==m_request.Id())
        {
         //--- Obteniendo los datos en el primer recuadro
         SetDataToTable1();
         //--- Limpiando los recuadros de datos obsoletos
         m_table2.DeleteAllRows(true);
         m_table3.DeleteAllRows(true);
         return;
        }
      //--- Pulsando el botón 'Calculate'
      if(lparam==m_calculate.Id())
        {
         //--- Obteniendo los datos en el segundo recuadro
         SetDataToTable2();
         //--- Limpiando los recuadros de datos obsoletos
         m_table3.DeleteAllRows(true);
        }
      //---
      return;
     }
...
  }

Iniciamos en el gráfico el experto descrito anteriormente y obtenemos un resultado como el que se muestra abajo. En este caso, se ha formado una lista de parejas divisas en las que se encuentra el dólar (USD). A continuación, hemos obtenido los datos del símbolo GBPUSD desde inicios de 2018 y hemos formado una lista de intervalos (segundo recuadro) con un salto de 100 y los segmentos calculados para cada uno de ellos. Como ejemplo, en el segundo recuadro se ha destacado una línea con un intervalo de 200 y un número de segmentos de 1922 (de 200 a 300). En el tercer recuadro se muestran las duraciones de todos los segmentos del intervalo destacado en el segundo recuadro. Por ejemplo, podemos ver que en este periodo ha habido en GBPUSD solo 11 segmentos con una duración de 10 barras del intervalo indicado.

 Fig. 16 – Resultado del cálculo del número de segmentos según su duración.

Fig. 16 – Resultado del cálculo del número de segmentos según su duración.


Algunas sutilezas sobre el trabajo con la interfaz gráfica

Como adición, nos gustaría mostrar un ejemplo sobre cómo gestionar adecuadamente el evento de cambio de símbolo y marco temporal de un gráfico al utilizar una interfaz gráfica en un programa MQL. Puesto que en las interfaces gráficas puede haber muchos controles distintos, podríamos necesitar un tiempo considerable para cargar e inicializar todo el conjunto. En ciertos casos, este tiempo invertido se puede reducir. Precisamente, el cambio de símbolo y marco temporal de un gráfico pertenece a dichos casos. En ellos, no es necesario eliminar y crear la interfaz gráfica una y otra vez.

La forma correcta de actuar es la siguiente:

En la clase principal del programa, creamos un campo para guardar el último motivo de desinicialización del programa:

class CProgram : public CWndCreate
  {
private:
   //--- Último motivo de desinicialización
   int               m_last_deinit_reason;
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CProgram::CProgram(void) : m_last_deinit_reason(WRONG_VALUE)
  {
  }

Durante la desinicialización, eliminaremos la interfaz gráfica en todos los casos, excepto en aquellos en los que el motivo sea REASON_CHARTCHANGE.

//+------------------------------------------------------------------+
//| Desinicialización                                                |
//+------------------------------------------------------------------+
void CProgram::OnDeinitEvent(const int reason)
  {
//--- Guardando el último motivo de desinicialización
   m_last_deinit_reason=reason;
//--- Eliminar GUI si el motivo no está relacionado con el cambio de símbolo y periodo
   if(reason!=REASON_CHARTCHANGE)
     {
      CWndEvents::Destroy();
     }
  }

Ya que la interfaz gráfica se crea en el momento de la desinicialización del programa llamando al método CProgram::CreateGUI(), ahora basta con comprobar el último motivo de la desinicialización. Si el motivo es que se ha cambiado el símbolo o el marco temporal, no será necesario crear la interfaz gráfica, bastará con salir del método anunciando que todo está en orden.

//+------------------------------------------------------------------+
//| Crea la interfaz gráfica                                         |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
  {
//--- Salir, si hemos cambiado el gráfico o marco temporal
   if(m_last_deinit_reason==REASON_CHARTCHANGE)
      return(true);
...
   return(true);
  }


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 la próxima parte, mostraremos varios ejemplos más sobre qué información podemos obtener con la ayuda de las herramientas creadas en estos artículos.

Nombre del archivo Comentarios
MQL5\Indicators\Custom\ZigZag\FrequencyChangeZZ.mq5 Indicador para medir la frecuencia de formación de segmentos en direcciones opuestas del indicador ZigZag
MQL5\Indicators\Custom\ZigZag\SumSegmentsZZ.mq5 Indicador para la suma de los segmentos del conjunto obtenido y su media.
MQL5\Indicators\Custom\ZigZag\PercentageSegmentsZZ.mq5 Indicador de proporción porcentual de las sumas de los segmentos y sus diferencias
MQL5\Indicators\Custom\ZigZag\MultiPercentageSegmentsZZ.mq5 Indicador para determinar el carácter de la formación de varios segmentos de un marco temporal mayor con ayuda del indicador de diferencia de proporción porcentual de las sumas en direcciones opuestas
MQL5\Experts\ZigZag\TestZZ_05.mq5 Experto para simular el indicador MultiPercentageSegmentsZZ
MQL5\Experts\ZigZag\ZZ_Scanner_01.mq5 Experto para recopilar estadísticas del indicador PercentageSegmentsZZ
MQL5\Experts\ZigZag\ZZ_Scanner_02.mq5 Experto para calcular segmentos en diferentes intervalos de precio
MQL5\Experts\ZigZag\ZZ_Scanner_03.mq5  Experto para calcular segmentos en diferentes intervalos de precio y con diferente duración


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

Archivos adjuntos |
Files.zip (45.43 KB)
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.

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.

Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte I): Concepto, organización de datos y primeros resultados Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte I): Concepto, organización de datos y primeros resultados

Tras analizar una ingente cantidad de estrategias comerciales, ejecutar multitud de encargos de preparación de programas para los terminales MT5 y MT4, y visitar distintos sitios web de MetaTrader, hemos llegado a la conclusión de que una mayoría aplastante de esta diversidad se construye en la práctica sobre un número fijo de funciones elementales, acciones y valores que se repiten de un programa a otro. El resultado de semejante trabajo es la biblioteca multiplataforma "DoEasy", que permite crear fácil y rápidamente programas para МetaТrader 5 y МetaТrader 4

Análisis sintáctico de MQL usando las herramientas de MQL Análisis sintáctico de MQL usando las herramientas de MQL

El presente artículo describe el preprocesador, escáner y el parser (analizador sintáctico) para el análisis sintáctico de los códigos fuente en el lenguaje MQL. La implementación en MQL se adjunta.