![El poder del ZigZag (Parte II): Ejemplos de obtención, procesamiento y representación de datos.](https://c.mql5.com/i/articles/overlay.png)
El poder del ZigZag (Parte II): Ejemplos de obtención, procesamiento y representación de datos.
Contenido
- Introducción
- Indicadores para determinar el carácter del comportamiento del precio
- Indicador FrequencyChangeZZ
- Indicador SumSegmentsZZ
- Indicador PercentageSegmentsZZ
- Indicador MultiPercentageSegmentsZZ
- Experto para recopilar y representar estadísticas
- Calculando el número de segmentos según el tamaño
- Calculando el número de segmentos según la duración
- Algunas sutilezas sobre el funcionamiento de la interfaz gráfica
- Conclusión
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.
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.
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.
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.
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.
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.
- 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.
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.
La pulsación del botón Request inicia el 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.
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.
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.
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.
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.
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.
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.
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 Ltd.
Artículo original: https://www.mql5.com/ru/articles/5544
![Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte I): Concepto, organización de datos y primeros resultados](https://c.mql5.com/2/35/MQL5-avatar-doeasy.png)
![Estudio de técnicas de análisis de velas (Parte I): Comprobando los patrones existentes](https://c.mql5.com/2/35/Pattern_I__2.png)
![Análisis sintáctico de MQL usando las herramientas de MQL](https://c.mql5.com/2/35/MQL5-avatar-analysis.png)
![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](https://c.mql5.com/2/35/Select_Symbols_Utility_MQL5__1.png)
![MQL5 - Lenguaje de estrategias comerciales para el terminal de cliente MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- 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