Estudio de técnicas de análisis de velas (Parte IV): Actualizaciones y adiciones a la aplicación
Contenido
- Introducción
- Un vistazo general a las actualizaciones
- Implementando las actualizaciones
- Estructura de las ventanas. Método de creación de la ventana principal de la aplicación
- Estructura de las ventanas. Método de creación de la ventana de diálogo de la aplicación
- Sección de cálculos. Métodos redefinidos de búsqueda de velas y patrones
- Conclusión
Introducción
En el presente ciclo de artículos, se ha desarrollado una aplicación para MetaTrader 5 capaz de poner a prueba la actualidad de los modelos de velas existentes. Posteriormente, se ha añadido la posibilidad de crear patrones propios a partir de tipos de vela simples, tales como velas cortas, largas, Doji, spining top, etc. En la última parte, se ha desarrollado una biblioteca que permite crear tanto indicadores basados en modelos de vela, como expertos comerciales.
En el presente artículo, vamos a presentar la siguiente versión de la aplicación Pattern Analyzer. En esta versión se han corregido algunas imperfecciones y se han añadido nuevas capacidades; además, se ha dado un nuevo enfoque a la comodidad y la actualidad de la interfaz actual. En este caso, además, se han tenido en cuenta las sugerencias reflejadas en los comentarios de los artículos anteriores. Podrá familiarizarse con el resultado leyendo el presente artículo.
Un vistazo general a las actualizaciones
La interfaz de usuario es una parte importante de cualquier aplicación, dado que una estructura adecuada permite trabajar con ella de forma efectiva, así como obtener cómodamente los datos analizados. Por eso, vamos a analizar la implementación del nuevo aspecto exterior en comparación con el antiguo. Vamos a echar un vistazo a la antigua versión de la pestaña Análisis, fijándonos en los puntos susceptibles de mejora.
Fig.1 Interfaz anterior de la pestaña Análisis
Punto 1. Ubicación de las pestañas y dimensiones.
En la fig.1, las pestañas marcadas con la posición 1 se encuentran en la parte superior de la aplicación; en este caso, además, la parte superior derecha se encuentra vacía y no se usa, y no dispone de espacio posible para añadir varias pestañas más. La fuente de texto es demasiado pequeña para ser leída con comodidad. Por eso, las tres pestañas han pasado a la parte superior izquierda de la aplicación, adquiriendo una orientación vertical para resultar más notorias. Asimismo, disponen de sitio suficiente para añadir varios apartados adicionales.
Punto 2. Recuadros con los resultados de las simulaciones de los patrones.
La representación visual de la información no resulta cómoda del todo. Por eso, la fuente, la altura de las líneas y el tamaño del recuadro han sido redefinidos con un mayor tamaño, para lograr así que los resultados se lean mejor.
Punto 3. Selección del marco temporal actual.
Como hemos aclarado, la construcción de la estructura de selección Marco temporal —> Resultados para todos los patrones limita la visibilidad de los resultados de las pruebas, por eso, hemos desarrollado una variante de selección de marco temporal múltiple, así como la selección individual de los patrones analizados. Esto permitirá configurar de manera más flexible el trabajo con los patrones.
Punto 4. Intervalo de la muestra.
La idea implementada en la anterior versión consistía en simular los datos actuales hasta una cantidad determinada de velas dentro de la historia. Esto no permitía hacer la muestra más específica, concretamente, abarcando desde una fecha inicial hasta una final. Por eso, este método de muestra será sustituido por uno más avanzado. En la fig.2, más abajo, se muestra la solución de todos los puntos anteriormente descritos y las posibles mejoras.
Fig. 2 Interfaz mejorada de la pestaña Análisis.
Bien, vamos a enumerar las soluciones para corregir las variantes anteriores.
- Traslado de las pestañas a la parte izquierda y colocación vertical de las mismas.
- Selección manual de los patrones analizados.
- Selección manual de los marcos temporales actuales.
- Sustitución de la muestra de simulación en forma de número de velas por una nueva herramienta con Intervalo de Fechas.
Asimismo, debemos aumentar el tamaño de la ventana de la aplicación, para representar multitud de elementos nuevos. También resulta cómodo y novedoso el traslado del ajuste del Valor umbral de la tendencia (fig.3) en puntos desde la pestaña de Ajustes a las pestañas de Análisis y Búsqueda automática. En este caso, además, el ajuste es individual para cada una de las pestañas.
Fig.3 Traslado del ajuste del Valor umbral de la tendencia
El último elemento modificado ha sido la estructura de los recuadros de resultados. Se ha quitado la columna de Frecuencia, incluyendo en su lugar el parámetro Marco temporal, más actual y necesario.
Fig. 4 Nueva estructura del recuadro de resultados
Ahora vamos a analizar qué hemos necesitado mejorar en la segunda pestaña Búsqueda automática, que trabaja con los patrones generados.
Punto 1. Configuración en pestañas distintas.
Los ajustes relacionados directamente con el apartado de Búsqueda automática se encontraban en la pestaña Ajustes, y para modificarlos era necesario alternar constantemente entre Búsqueda automática y Ajustes. Por eso, casi todos los ajustes han sido trasladados a la pestaña Búsqueda automática. Asimismo, hemos añadido mejoras relacionadas con el Valor umbral de la tendencia, la selección de los marcos temporales actuales y el intervalo de fechas. El resultado de las actualizaciones de la Búsqueda automática se muestran en la fig.5.
Fig.5 Actualización de la funcionalidad en la pestaña Búsqueda automática
Ahora, el trabajo con los patrones generados es aún más cómodo. Además, merece la pena destacar que el Intervalo de fechas aquí es igualmente individual.
Implementando las actualizaciones
Vamos a ver con mayor detalle cómo han sido implementadas las actualizaciones mostradas más arriba, así como los cambios realizados en los cálculos.
Estructura de las ventanas. Método de creación de la ventana principal de la aplicación.
El método CProgram::CreateGUI(), encargado de crear la interfaz gráfica, ha sido complementado:
//+------------------------------------------------------------------+ //| Создаёт графический интерфейс программы | //+------------------------------------------------------------------+ bool CProgram::CreateGUI(void) { //--- Создание панели if(!CreateWindow("Pattern Analyzer")) return(false); //--- Создание диалогового окна if(!CreateWindowSetting1("Настройки")) return(false); //--- Создание диалогового окна if(!CreateWindowSetting2("Настройки диапазона дат")) return(false); //--- Создание диалогового окна if(!CreateWindowSetting3("Настройки диапазона дат")) return(false); //--- Finalizando la creación de GUI CWndEvents::CompletedGUI(); return(true); } //+-----------------------------------------------------------------
Estamos hablando de CreateWindowSetting2() y CreateWindowSetting3(), encargados de representar la nueva herramienta de ajuste del intervalo temporal de la muestra, mostrada en la fig.1. Asimismo, se ha desarrollado significativamente el método de creación de la ventana principal de la aplicación CreateWindow(). Se ha dividido en tres bloques que se corresponden con los elementos de la interfaz de cada una de las pestañas: Análisis, Búsqueda automática, Ajustes.
//+------------------------------------------------------------------+ //| The Analyze tab | //+------------------------------------------------------------------+ //--- Create buttons of the pattern set if(!CreatePatternSet(m_patterns,10,10)) return(false); //--- Timeframe headers if(!CreateTFLabel(m_text_labels[1],10,100,0)) return(false); //--- Create buttons of the timeframe set if(!CreateTimeframeSet(m_timeframes,10,125,0)) return(false); //--- Symbol filter search window if(!CreateSymbolsFilter(m_symb_filter1,m_request1,10,180,0)) return(false); //--- Create a button for date range selection if(!CreateDateRange(m_request3,280,180,0)) return(false); //--- Create an entry field for the threshold profit value if(!CreateThresholdValue(m_threshold1,400,180,100,0)) return(false); //--- Create a table of symbols if(!CreateSymbTable(m_symb_table1,10,225,0)) return(false); //--- Create a table of results if(!CreateTable1(m_table1,120,225,0)) return(false);
En la primera pestaña, se han añadido los métodos que representan los nuevos elementos de la interfaz.
- CreatePatternSet(). Nuevo método encargado de la representación de un conjunto de botones conmutables de selección de patrones.
- CreateTFLabel(). Etiqueta de texto de encabezado para un conjunto de marcos temporales.
- CreateTimeframeSet(). Conjunto de botones conmutables de marcos temporales.
- CreateDateRange(). Nuevo botón, que, al ser pulsado, abre la ventana de diálogo de selección del intervalo de fechas para el análisis.
- CreateThresholdValue(). Método revisado para representar el Valor umbral de la tendencia en puntos (fig.3).
//+------------------------------------------------------------------+ //| The AutoSearch tab | //+------------------------------------------------------------------+ if(!CreateTFLabel(m_text_labels[4],10,10,1)) return(false); //--- Buttons if(!CreateDualButton(m_buttons[6],m_buttons[7],200,50)) return(false); if(!CreateTripleButton(m_buttons[8],m_buttons[9],m_buttons[10],10,50)) return(false); //--- Timeframe headers if(!CreateTFLabel(m_text_labels[5],10,100,1)) return(false); //--- Create buttons of the timeframe set if(!CreateTimeframeSet(m_timeframes1,10,125,1)) return(false); //--- Edit fields if(!CreateSymbolsFilter(m_symb_filter2,m_request2,10,180,1)) return(false); //--- Create a button for date range selection if(!CreateDateRange(m_request4,280,180,1)) return(false); //--- Create an entry field for the threshold profit value if(!CreateThresholdValue(m_threshold2,400,180,100,1)) return(false); //--- Create a table of symbols if(!CreateSymbTable(m_symb_table2,10,225,1)) return(false); //--- Create a table of results if(!CreateTable2(m_table2,120,225,1)) return(false);
A la segunda pestaña de Búsqueda automática de la pestaña Ajustes (fig.5) se han trasladado los métodos responsables de la representación de los elementos de selección de las dimensiones del patrón generado CreateTripleButton() y la opción conmutable Repetir/No repetir, con el método CreateDualButton(). Asimismo, se han añadido los métodos encargados del encabezado del marco temporal y sus conjuntos.
//+------------------------------------------------------------------+ //| The Settings tab | //+------------------------------------------------------------------+ //--- Creating candlestick settings if(!CreateCandle(m_pictures[0],m_buttons[0],m_candle_names[0],"Long",10,10,"Images\\EasyAndFastGUI\\Candles\\long.bmp")) return(false); if(!CreateCandle(m_pictures[1],m_buttons[1],m_candle_names[1],"Short",104,10,"Images\\EasyAndFastGUI\\Candles\\short.bmp")) return(false); if(!CreateCandle(m_pictures[2],m_buttons[2],m_candle_names[2],"Spinning top",198,10,"Images\\EasyAndFastGUI\\Candles\\spin.bmp")) return(false); if(!CreateCandle(m_pictures[3],m_buttons[3],m_candle_names[3],"Doji",292,10,"Images\\EasyAndFastGUI\\Candles\\doji.bmp")) return(false); if(!CreateCandle(m_pictures[4],m_buttons[4],m_candle_names[4],"Marubozu",386,10,"Images\\EasyAndFastGUI\\Candles\\maribozu.bmp")) return(false); if(!CreateCandle(m_pictures[5],m_buttons[5],m_candle_names[5],"Hammer",480,10,"Images\\EasyAndFastGUI\\Candles\\hammer.bmp")) return(false); //--- Text labels if(!CreateTextLabel(m_text_labels[0],10,140)) return(false); if(!CreateTextLabel(m_text_labels[3],300,140)) return(false); //--- Edit fields if(!CreateCoef(m_coef1,10,180,"K1",1)) return(false); if(!CreateCoef(m_coef2,100,180,"K2",0.5)) return(false); if(!CreateCoef(m_coef3,200,180,"K3",0.25)) return(false); if(!CreateLanguageSetting(m_lang_setting,10,240,2)) return(false); //--- List views if(!CreateListView(300,180)) return(false); //--- if(!CreateCheckBox(m_checkbox1,300+8,160,"All candlesticks")) return(false); //--- Status Bar if(!CreateStatusBar(1,26)) return(false);
El apartado Ajustes ahora tiene menos elementos. En él han permanecido los ajustes individuales de las velas, los ajustes de los coeficientes para calcular la probabilidad y la efectividad, la selección del idioma de la interfaz y la selección de las velas para generar los patrones en la pestaña de Búsqueda automática.A continuación, vamos a ver los métodos con mayor detalle.
El método de creación de la selección de patrones CreatePatternSet(). supone un conjunto de botones conmutables para la selección de los patrones existentes para el análisis.
Fig.6 Principio de funcionamiento de la selección de patrones para el análisis
La implementación se muestra más abajo:
//+------------------------------------------------------------------+ //| Creates a set of pattern buttons | //+------------------------------------------------------------------+ bool CProgram::CreatePatternSet(CButton &button[],int x_gap,int y_gap) { ArrayResize(button,15); string pattern_names[15]= { "Hummer", "Invert Hummer", "Handing Man", "Shooting Star", "Engulfing Bull", "Engulfing Bear", "Harami Cross Bull", "Harami Cross Bear", "Harami Bull", "Harami Bear", "Doji Star Bull", "Doji Star Bear", "Piercing Line", "Dark Cloud Cover", "All Patterns" }; int k1=x_gap,k2=x_gap,k3=x_gap; for(int i=0;i<=14;i++) { if(i<5) { CreatePatternButton(button[i],pattern_names[i],k1,y_gap); k1+=150; } else if(i>=5 && i<10) { CreatePatternButton(button[i],pattern_names[i],k2,y_gap+30); k2+=150; } else if(i>=10 && i<14) { CreatePatternButton(button[i],pattern_names[i],k3,y_gap+60); k3+=150; } else if(i==14) { CreatePatternButton(button[i],pattern_names[i],k3,y_gap+60); } } return(true); } //+------------------------------------------------------------------+ //| Creates a button for selecting a pattern for analysis | //+------------------------------------------------------------------+ #resource "\\Images\\EasyAndFastGUI\\Candles\\passive.bmp" #resource "\\Images\\EasyAndFastGUI\\Candles\\pressed.bmp" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::CreatePatternButton(CButton &button,const string candlename,const int x_gap,const int y_gap) { //--- Save the pointer to the main control button.MainPointer(m_tabs1); //--- Attach to tab m_tabs1.AddToElementsArray(0,button); //--- Properties button.XSize(120); button.YSize(20); button.Font("Trebuchet"); button.FontSize(9); button.LabelColor(clrWhite); button.LabelColorHover(clrWhite); button.LabelColorPressed(clrWhite); button.IsCenterText(true); button.TwoState(true); button.IconFile("Images\\EasyAndFastGUI\\Candles\\passive.bmp"); button.IconFilePressed("Images\\EasyAndFastGUI\\Candles\\pressed.bmp"); //--- Create a control if(!button.CreateButton(candlename,x_gap,y_gap)) return(false); //--- Add the element pointer to the data base CWndContainer::AddToElementsArray(0,button); return(true); }
Debemos prestar atención a que el botón que selecciona/desmarca todos los patrones "All Patterns" es el último botón. Para procesar su pulsación, se usa un código adicional en la sección de procesamiento de los eventos de pulsación de botones:
if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { //--- Select and unselect all pattern buttons if(lparam==m_patterns[14].Id()) { if(m_patterns[14].IsPressed()) { for(int i=0;i<14;i++) m_patterns[i].IsPressed(true); } else if(!m_patterns[14].IsPressed()) { for(int i=0;i<14;i++) m_patterns[i].IsPressed(false); } for(int i=0;i<14;i++) m_patterns[i].Update(true); } ... }
El método de selección de marcos temporales CreateTimeframeSet() es muy semejante al anterior. También dispone de un conjunto de botones conmutables que sirven para seleccionar los marcos temporales analizados.
Fig.7 Principio de funcionamiento de la selección de marcos temporales para el análisis
La implementación se muestra en el siguiente listado:
if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { //--- Select and unselect all pattern buttons if(lparam==m_patterns[14].Id()) { if(m_patterns[14].IsPressed()) { for(int i=0;i<14;i++) m_patterns[i].IsPressed(true); } else if(!m_patterns[14].IsPressed()) { for(int i=0;i<14;i++) m_patterns[i].IsPressed(false); } for(int i=0;i<14;i++) m_patterns[i].Update(true); } ... }
También dispone de un botón para seleccionar/desmarcar todos los marcos temporales, y se procesa en la sección de pulsación de botones:
//--- Select and unselect all pattern buttons if(lparam==m_timeframes[21].Id()) { if(m_timeframes[21].IsPressed()) { for(int i=0;i<21;i++) m_timeframes[i].IsPressed(true); } else if(!m_timeframes[21].IsPressed()) { for(int i=0;i<21;i++) m_timeframes[i].IsPressed(false); } for(int i=0;i<21;i++) m_timeframes[i].Update(true); }
El próximo elemento nuevo es el botón de Intervalo de fechas. Forma parte de una nueva herramienta compleja para el ajuste del intervalo de la muestra. Se implementa con el método CreateDateRange().
Fig.8 Principio de funcionamiento de la selección del intervalo de fechas para el análisis
Su implementación se muestra más abajo:
//+------------------------------------------------------------------+ //| Creates a button to show the date range selection window | //+------------------------------------------------------------------+ bool CProgram::CreateDateRange(CButton &button,const int x_gap,const int y_gap,const int tab) { //--- Save the pointer to the main control button.MainPointer(m_tabs1); //--- Attach to tab m_tabs1.AddToElementsArray(tab,button); //--- Properties button.XSize(100); button.YSize(25); button.Font("Trebuchet"); button.FontSize(10); button.IsHighlighted(false); button.IsCenterText(true); button.BorderColor(C'0,100,255'); button.BackColor(clrAliceBlue); //--- Create a control if(!button.CreateButton("",x_gap,y_gap)) return(false); //--- Add the element pointer to the data base CWndContainer::AddToElementsArray(0,button); return(true); }
En el procesador del evento de pulsación del botón también existe un código encargado de representar la ventana diálogo con el intervalo de fechas:
//--- if(lparam==m_request3.Id()) { int x=m_request3.X(); int y=m_request3.Y()+m_request3.YSize(); m_window[2].X(x); m_window[2].Y(y); m_window[2].OpenWindow(); val=(m_lang_index==0)?"Настройки диапазона дат":"Date Range Settings"; m_window[2].LabelText(val); }
No hay necesidad de describir los nuevos elementos añadidos en la pestaña, puesto que son idénticos a la implementación de la pestaña Análisis, con la excepción de parámetros tales como las coordenadas. Por eso, vamos a pasar al análisis de los métodos encargados de la representación de otras nuevas ventanas, aparte de la principal.
Estructura de las ventanas. Método de creación de la ventana de diálogo de la aplicación.
Los métodos de representación de las ventanas de diálogo para las pestañas Análisis y Búsqueda automática son semejantes entre sí, por eso, vamos a analizar uno de ellos, y el segundo será similar.
//+------------------------------------------------------------------+ //| Creates a date range selection dialog in the Analyze tab | //+------------------------------------------------------------------+ bool CProgram::CreateWindowSetting2(const string caption_text) { //--- Add the pointer to the window array CWndContainer::AddWindow(m_window[2]); //--- Coordinates int x=m_request3.X(); int y=m_request3.Y()+m_request3.YSize(); //--- Properties m_window[2].XSize(372); m_window[2].YSize(300); m_window[2].WindowType(W_DIALOG); //--- Create the form if(!m_window[2].CreateWindow(m_chart_id,m_subwin,caption_text,x,y)) return(false); //--- if(!CreateCalendar(m_calendar1,m_window[2],10,25,D'01.01.2018',2)) return(false); if(!CreateCalendar(m_calendar2,m_window[2],201,25,m_calendar2.Today(),2)) return(false); //--- if(!CreateTimeEdit(m_time_edit1,m_window[2],10,200,"Time",2)) return(false); if(!CreateTimeEdit(m_time_edit2,m_window[2],200,200,"Time",2)) return(false); //--- return(true); }
Sección de cálculos. Métodos redefinidos de búsqueda de velas y patrones.
Debido a los cambios considerables sucedidos en la estructura de la interfaz de usuario, así como a la adición de nuevos elementos y a la exclusión de otros antiguos, hemos tenido que cambiar el método de cálculo. En la aplicación actual existen dos métodos de cálculo: el primero se encarga de los patrones existentes, y el segundo, de los generados.
Los cálculos se inician al pulsar sobre uno de los instrumentos comerciales disponibles en el recuadro de símbolos: esta regla se aplica de la misma forma para ambas pestañas, tanto para Análisis, como para Búsqueda automática. En este caso, además, tiene lugar la llamada de uno de los métodos, dependiendo de la pestaña.
//+------------------------------------------------------------------+ //| Symbol change in the Analyze tab | //+------------------------------------------------------------------+ bool CProgram::ChangeSymbol1(const long id) { //--- Check the element ID if(id!=m_symb_table1.Id()) return(false); //--- Exit if the line is not selected if(m_symb_table1.SelectedItem()==WRONG_VALUE) { //--- Show the full symbol description in the status bar m_status_bar.SetValue(0,"No symbol selected for analysis"); m_status_bar.GetItemPointer(0).Update(true); return(false); } //--- Get a symbol string symbol=m_symb_table1.GetValue(0,m_symb_table1.SelectedItem()); //--- Show the full symbol description in the status bar string val=(m_lang_index==0)?"Выбранный символ: ":"Selected symbol: "; m_status_bar.SetValue(0,val+::SymbolInfoString(symbol,SYMBOL_DESCRIPTION)); m_status_bar.GetItemPointer(0).Update(true); //--- GetPatternType(symbol); return(true); } //+------------------------------------------------------------------+ //| Symbol change in the AutoSearch tab | //+------------------------------------------------------------------+ bool CProgram::ChangeSymbol2(const long id) { //--- Check the element ID if(id!=m_symb_table2.Id()) return(false); //--- Exit if the line is not selected if(m_symb_table2.SelectedItem()==WRONG_VALUE) { //--- Show the full symbol description in the status bar m_status_bar.SetValue(0,"No symbol selected for analysis"); m_status_bar.GetItemPointer(0).Update(true); return(false); } //--- Get a symbol string symbol=m_symb_table2.GetValue(0,m_symb_table2.SelectedItem()); //--- Show the full symbol description in the status bar string val=(m_lang_index==0)?"Выбранный символ: ":"Selected symbol: "; m_status_bar.SetValue(0,val+::SymbolInfoString(symbol,SYMBOL_DESCRIPTION)); m_status_bar.GetItemPointer(0).Update(true); //--- if(!GetCandleCombitation()) { if(m_lang_index==0) MessageBox("Число выбранных свечей меньше размера исследуемого паттерна!","Ошибка",MB_OK); else if(m_lang_index==1) MessageBox("The number of selected candles is less than the size of the studied pattern!","Error",MB_OK); return(false); } //--- GetPatternType(symbol,m_total_combination); return(true); }
Podemos ver por el listado, que al final de cada uno de los métodos se llama el método GetPattertType(), que tiene dos tipos distintos de argumentos. Este método es clave a la hora de buscar patrones y procesar los resultados obtenidos. Por eso, vamos a analizar cada uno de los tipos con más detalle.
El primer tipo del método se usa al buscar los patrones existentes.
bool GetPatternType(const string symbol);
La implementación de este método es bastante larga, por eso, vamos mostrar un ejemplo para un solo patrón, el lector podrá estudiar los demás por su propia cuenta.
//+------------------------------------------------------------------+ //| Pattern recognition | //+------------------------------------------------------------------+ bool CProgram::GetPatternType(const string symbol) { CANDLE_STRUCTURE cand1,cand2; //--- RATING_SET hummer_coef[]; RATING_SET invert_hummer_coef[]; RATING_SET handing_man_coef[]; RATING_SET shooting_star_coef[]; RATING_SET engulfing_bull_coef[]; RATING_SET engulfing_bear_coef[]; RATING_SET harami_cross_bull_coef[]; RATING_SET harami_cross_bear_coef[]; RATING_SET harami_bull_coef[]; RATING_SET harami_bear_coef[]; RATING_SET doji_star_bull_coef[]; RATING_SET doji_star_bear_coef[]; RATING_SET piercing_line_coef[]; RATING_SET dark_cloud_cover_coef[]; //--- Receive data for selected timeframes GetTimeframes(m_timeframes,m_cur_timeframes1); int total=ArraySize(m_cur_timeframes1); //--- Check at least one selected timerame if(total<1) { if(m_lang_index==0) MessageBox("Вы не выбрали рабочий таймфрейм!","Ошибка",MB_OK); else if(m_lang_index==1) MessageBox("You have not selected a working timeframe!","Error",MB_OK); return(false); } int count=0; m_total_row=0; m_table_number=1; //--- Delete all rows m_table1.DeleteAllRows(); //--- Get the date range datetime start=StringToTime(TimeToString(m_calendar1.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit1.GetHours()+":"+(string)m_time_edit1.GetMinutes()+":00"); datetime end=StringToTime(TimeToString(m_calendar2.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit2.GetHours()+":"+(string)m_time_edit2.GetMinutes()+":00"); //--- Check specified dates if(start>end || end>TimeCurrent()) { if(m_lang_index==0) MessageBox("Неправильно выбран диапазон дат!","Ошибка",MB_OK); else if(m_lang_index==1) MessageBox("Incorrect date range selected!","Error",MB_OK); return(false); } //--- Hammer, bullish model if(m_patterns[0].IsPressed()) { ArrayResize(m_hummer_total,total); ArrayResize(hummer_coef,total); ZeroMemory(m_hummer_total); ZeroMemory(hummer_coef); ZeroMemory(cand1); count++; //--- Calculation by timeframes for(int j=0;j<total;j++) { MqlRates rt[]; ZeroMemory(rt); int copied=CopyRates(symbol,m_cur_timeframes1[j],start,end,rt); for(int i=0;i<copied;i++) { GetCandleType(symbol,cand1,m_cur_timeframes1[j],i); // Current candlestick if(cand1.trend==DOWN && // Checking the trend direction cand1.type==CAND_HAMMER) // Checking the "Hammer" { m_hummer_total[j]++; GetCategory(symbol,i+3,hummer_coef[j],m_cur_timeframes1[j],m_threshold_value1); } } AddRow(m_table1,"Hammer",hummer_coef[j],m_hummer_total[j],m_cur_timeframes1[j]); } } ... //--- if(count>0) { //--- m_table1.DeleteRow(m_total_row); //--- Update the table m_table1.Update(true); m_table1.GetScrollVPointer().Update(true); } else { if(m_lang_index==0) MessageBox("Вы не выбрали паттерн!","Ошибка",MB_OK); else if(m_lang_index==1) MessageBox("You have not chosen a pattern!","Error",MB_OK); } return(true); }
El algoritmo no es complicado, y funciona de la forma siguiente:
- Se declaran las estructuras necesarias para el cálculo.
- Con la ayuda del método GetTimeframes(), obtenemos los datos de los marcos temporales seleccionados desde la interfaz de usuario.
- Procesamos el posible error cuando no ha sido seleccionado ninguno de los marcos temporales.
- Si se ha seleccionado aunque sea un solo marco temporal, obtenemos los datos del intervalo de fechas y comprobamos si han sido introducidos correctamente. Asimismo, comprobamos la ausencia de errores de introducción.
- A continuación, comprobamos si cada uno de los patrones ha sido seleccionado para el cálculo, y buscamos ese patrón en los marcos temporales y el intervalo seleccionados anteriormente.
- Finalmente, comprobamos si se ha seleccionado aunque sea uno de los patrones para el cálculo. Si ha sido seleccionado, mostramos el cálculo en el recuadro de resultados. Si no ha sido seleccionado, se lo comunicamos al usuario.
A continuación, analizamos los métodos que han existido en anteriores versiones y que han sido modificados en la versión actualizada, además de un nuevo método para calcular los datos obtenidos y mostrar estos en el recuadro de resultados:
- El método de búsqueda de la vela del tipo especificado GetCandleType().
- El método de valoración de la efectividad del patrón localizado GetCategory().
- El método para calcular los datos obtenidos de los patrones y mostrar los resultados en el recuadro AddRow().
El método de búsqueda de patrones GetPatternType() tiene dos implementaciones distintas, mientras que esos tres métodos son universales. Vamos a analizar su implementación con mayor detalle:
//+------------------------------------------------------------------+ //| Candlestick type recognition | //+------------------------------------------------------------------+ bool CProgram::GetCandleType(const string symbol,CANDLE_STRUCTURE &res,ENUM_TIMEFRAMES timeframe,const int shift) { MqlRates rt[]; int aver_period=5; double aver=0.0; datetime start=TimeCurrent(); SymbolSelect(symbol,true); //--- Get the start date from the range depending on the type of patterns if(m_table_number==1) start=StringToTime(TimeToString(m_calendar1.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit1.GetHours()+":"+(string)m_time_edit1.GetMinutes()+":00"); else if(m_table_number==2) start=StringToTime(TimeToString(m_calendar3.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit3.GetHours()+":"+(string)m_time_edit3.GetMinutes()+":00"); //--- Shift date start+=PeriodSeconds(timeframe)*shift; int copied=CopyRates(symbol,timeframe,start,aver_period+1,rt); if(copied<6) { Print(start,": Not enough data for calculation — ",GetLastError()); } //--- Get details of the previous candlestick if(copied<aver_period) return(false); //--- res.open=rt[aver_period].open; res.high=rt[aver_period].high; res.low=rt[aver_period].low; res.close=rt[aver_period].close; //--- Determine the trend direction for(int i=0;i<aver_period;i++) aver+=rt[i].close; aver/=aver_period; if(aver<res.close) res.trend=UPPER; if(aver>res.close) res.trend=DOWN; if(aver==res.close) res.trend=FLAT; //--- Determine if it is a bullish or a bearish candlestick res.bull=res.open<res.close; //--- Get the absolute size of candlestick body res.bodysize=MathAbs(res.open-res.close); //--- Get sizes of shadows double shade_low=res.close-res.low; double shade_high=res.high-res.open; if(res.bull) { shade_low=res.open-res.low; shade_high=res.high-res.close; } double HL=res.high-res.low; //--- Calculate average body size of previous candlesticks double sum=0; for(int i=1; i<=aver_period; i++) sum+=MathAbs(rt[i].open-rt[i].close); sum/=aver_period; //--- Determine the candlestick type res.type=CAND_NONE; //--- long if(res.bodysize>sum*m_long_coef && res.bull) res.type=CAND_LONG_BULL; //--- sort if(res.bodysize<sum*m_short_coef && res.bull) res.type=CAND_SHORT_BULL; //--- long bear if(res.bodysize>sum*m_long_coef && !res.bull) res.type=CAND_LONG_BEAR; //--- sort bear if(res.bodysize<sum*m_short_coef && !res.bull) res.type=CAND_SHORT_BEAR; //--- doji if(res.bodysize<HL*m_doji_coef) res.type=CAND_DOJI; //--- marubozu if((shade_low<res.bodysize*m_maribozu_coef && shade_high<res.bodysize*m_maribozu_coef) && res.bodysize>0) res.type=CAND_MARIBOZU; //--- hammer if(shade_low>res.bodysize*m_hummer_coef2 && shade_high<res.bodysize*m_hummer_coef1) res.type=CAND_HAMMER; //--- invert hammer if(shade_low<res.bodysize*m_hummer_coef1 && shade_high>res.bodysize*m_hummer_coef2) res.type=CAND_INVERT_HAMMER; //--- spinning top if((res.type==CAND_SHORT_BULL || res.type==CAND_SHORT_BEAR) && shade_low>res.bodysize*m_spin_coef && shade_high>res.bodysize*m_spin_coef) res.type=CAND_SPIN_TOP; //--- ArrayFree(rt); return(true); }
El algoritmo de este método es el siguiente: obtenemos la fecha inicial del intervalo de la muestra, dependiendo de los patrones que investigamos, existentes o generados. Dado que este método se usa en el ciclo de cálculo en el intervalo de fechas, modificamos la fecha inicial, desplazándola del pasado hacia el futuro en una vela en el marco temporal establecido. A continuación, copiamos los datos necesarios para calcular los tipos simples de vela. Si los datos son insuficientes, se lo comunicamos al usuario.
¡Nota importante! Debemos monitorear la disponibilidad de datos históricos en el terminal MetaTrader 5, de lo contrario, la aplicación podría funcionar de forma incorrecta.
Si no hay datos suficientes, se comprueba si la vela actual pertenece a un cierto tipo de vela simple.
El método GetCategory(), como ya sabemos por los artículos anteriores, comprueba en la historia el comportamiento del precio después de la aparición del patrón.
//+------------------------------------------------------------------+ //| Determine profit categories | //+------------------------------------------------------------------+ bool CProgram::GetCategory(const string symbol,const int shift,RATING_SET &rate,ENUM_TIMEFRAMES timeframe,int threshold) { MqlRates rt[]; datetime start=TimeCurrent(); if(m_table_number==1) start=StringToTime(TimeToString(m_calendar1.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit1.GetHours()+":"+(string)m_time_edit1.GetMinutes()+":00"); else if(m_table_number==2) start=StringToTime(TimeToString(m_calendar3.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit3.GetHours()+":"+(string)m_time_edit3.GetMinutes()+":00"); start+=PeriodSeconds(timeframe)*shift; int copied=CopyRates(symbol,timeframe,start,4,rt); //--- Get details of the previous candlestick if(copied<4) { return(false); } double high1,high2,high3,low1,low2,low3,close0,point; close0=rt[0].close; high1=rt[1].high; high2=rt[2].high; high3=rt[3].high; low1=rt[1].low; low2=rt[2].low; low3=rt[3].low; if(!SymbolInfoDouble(symbol,SYMBOL_POINT,point)) return(false); //--- Check if it is the Uptrend if((int)((high1-close0)/point)>=threshold) { rate.a_uptrend++; } else if((int)((high2-close0)/point)>=threshold) { rate.b_uptrend++; } else if((int)((high3-close0)/point)>=threshold) { rate.c_uptrend++; } //--- Check if it is the Downtrend if((int)((close0-low1)/point)>=threshold) { rate.a_dntrend++; } else if((int)((close0-low2)/point)>=threshold) { rate.b_dntrend++; } else if((int)((close0-low3)/point)>=threshold) { rate.c_dntrend++; } return(true); }
En el algoritmo de este método, al igual que en el anterior, solo ha cambiado el método de obtención de la información sobre las velas analizadas. Esto se relaciona directamente con el cambio de la herramienta de selección del intervalo temporal.
El último método común para ambos GetPatternType() comprende la obtención, el cálculo y la muestra de los datos en el recuadro de resultados.
//+------------------------------------------------------------------+ //| Get, calculate and display data in the results table | //+------------------------------------------------------------------+ void CProgram::AddRow(CTable &table,string pattern_name,RATING_SET &rate,int found,ENUM_TIMEFRAMES timeframe) { int row=m_total_row; int total_patterns=ArraySize(m_total_combination); double p1,p2,k1,k2; int sum1=0,sum2=0; sum1=rate.a_uptrend+rate.b_uptrend+rate.c_uptrend; sum2=rate.a_dntrend+rate.b_dntrend+rate.c_dntrend; //--- p1=(found>0)?NormalizeDouble((double)sum1/found*100,2):0; p2=(found>0)?NormalizeDouble((double)sum2/found*100,2):0; k1=(found>0)?NormalizeDouble((m_k1*rate.a_uptrend+m_k2*rate.b_uptrend+m_k3*rate.c_uptrend)/found,3):0; k2=(found>0)?NormalizeDouble((m_k1*rate.a_dntrend+m_k2*rate.b_dntrend+m_k3*rate.c_dntrend)/found,3):0; //--- table.AddRow(row); if(m_table_number==1) table.SetValue(0,row,pattern_name); else if(m_table_number==2) { if(row<total_patterns) table.SetValue(0,row,m_total_combination[row]); else if(row>=total_patterns) { int i=row-int(total_patterns*MathFloor(double(row)/total_patterns)); table.SetValue(0,row,m_total_combination[i]); } } table.SetValue(1,row,(string)found); table.SetValue(2,row,TimeframeToString(timeframe)); table.SetValue(3,row,(string)p1,2); table.SetValue(4,row,(string)p2,2); table.SetValue(5,row,(string)k1,2); table.SetValue(6,row,(string)k2,2); ZeroMemory(rate); m_total_row++; } //+------------------------------------------------------------------+
El método recibe en sus argumentos toda la información necesaria para el cálculo. Su algoritmo es muy sencillo Solo hay dos aspectos que debemos mencionar aquí. En las anteriores versiones de la aplicación, el número exacto de líneas en el recuadro se conoce inicialmente. Por ejemplo, al procesar los datos de los patrones existentes, el recuadro de resultados siempre ha tenido el mismo número de líneas, que es igual al número de patrones determinados, es decir, 14. Ahora, resulta imposible saber qué número de patrones o marcos temporales de trabajo va a seleccionar el usuario. Por eso mismo, hemos introducido un sencillo contador de líneas, m_total_row. Así, la llamada del método AddRow() añade una línea al recuadro de resultados según dos rasgos: el patrón y el marco temporal.
El segundo aspecto tiene que ver con la pestaña Búsqueda automática. Antes, el número final de líneas era igual al número de combinaciones de los patrones generados. Ahora, el algoritno anterior ya no es adecuado, precisamente por el mismo motivo: no sabemos cuántos marcos temporales vamos a tener. Por eso, deberemos registrar de nuevo la matriz de combinaciones generadas para cada uno de los marcos temporales seleccionados.
Vamos a echar un vistazo ahora a la segunda variante del método GetPatternType().
bool GetPatternType(const string symbol,string &total_combination[]);
Aquí, aparte del símbolo actual, el segundo es el enlace a la matriz de línea con las combinaciones de los patrones generados.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::GetPatternType(const string symbol,string &total_combination[]) { CANDLE_STRUCTURE cand1[],cand2[],cand3[],cur_cand,prev_cand,prev_cand2; RATING_SET ratings; int total_patterns,m_pattern_total[]; string elements[]; //--- total_patterns=ArraySize(total_combination); ArrayResize(cand1,total_patterns); ArrayResize(cand2,total_patterns); ArrayResize(cand3,total_patterns); ArrayResize(m_pattern_total,total_patterns); ArrayResize(elements,m_pattern_size); //--- for(int i=0;i<total_patterns;i++) { StringReplace(total_combination[i],"[",""); StringReplace(total_combination[i],"]",""); if(m_pattern_size>1) { ushort sep=StringGetCharacter(",",0); StringSplit(total_combination[i],sep,elements); } m_pattern_total[i]=0; if(m_pattern_size==1) IndexToPatternType(cand1[i],(int)total_combination[i]); else if(m_pattern_size==2) { IndexToPatternType(cand1[i],(int)elements[0]); IndexToPatternType(cand2[i],(int)elements[1]); } else if(m_pattern_size==3) { IndexToPatternType(cand1[i],(int)elements[0]); IndexToPatternType(cand2[i],(int)elements[1]); IndexToPatternType(cand3[i],(int)elements[2]); } } //--- GetTimeframes(m_timeframes1,m_cur_timeframes2); int total=ArraySize(m_cur_timeframes2); if(total<1) { if(m_lang_index==0) MessageBox("Вы не выбрали рабочий таймфрейм!","Ошибка",MB_OK); else if(m_lang_index==1) MessageBox("You have not selected a working timeframe!","Error",MB_OK); return(false); } m_total_row=0; m_table_number=2; //--- Delete all rows m_table2.DeleteAllRows(); //--- datetime start=StringToTime(TimeToString(m_calendar3.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit3.GetHours()+":"+(string)m_time_edit3.GetMinutes()+":00"); datetime end=StringToTime(TimeToString(m_calendar4.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit4.GetHours()+":"+(string)m_time_edit4.GetMinutes()+":00"); //--- if(start>end || end>TimeCurrent()) { if(m_lang_index==0) MessageBox("Неправильно выбран диапазон дат!","Ошибка",MB_OK); else if(m_lang_index==1) MessageBox("Incorrect date range selected!","Error",MB_OK); return(false); } //--- if(m_pattern_size==1) { ZeroMemory(cur_cand); //--- Calculation by timeframes for(int i=0;i<total;i++) { MqlRates rt[]; ZeroMemory(rt); ZeroMemory(ratings); int copied=CopyRates(symbol,m_cur_timeframes2[i],start,end,rt); //--- Calculation by patterns for(int j=0;j<total_patterns;j++) { //--- Calculation by a date range for(int k=0;k<copied;k++) { //--- Get the current candlestick type GetCandleType(symbol,cur_cand,m_cur_timeframes2[i],k); // current candlestick //--- if(cur_cand.type==cand1[j].type && cur_cand.bull==cand1[j].bull) { m_pattern_total[j]++; GetCategory(symbol,k+3,ratings,m_cur_timeframes2[i],m_threshold_value2); } } AddRow(m_table2,"",ratings,m_pattern_total[j],m_cur_timeframes2[i]); m_pattern_total[j]=0; } } } else if(m_pattern_size==2) { ZeroMemory(cur_cand); ZeroMemory(prev_cand); //--- Calculation by timeframes for(int i=0;i<total;i++) { MqlRates rt[]; ZeroMemory(rt); ZeroMemory(ratings); int copied=CopyRates(symbol,m_cur_timeframes2[i],start,end,rt); //--- Calculation by patterns for(int j=0;j<total_patterns;j++) { //--- Calculation by a date range for(int k=0;k<copied;k++) { //--- Get the current candlestick type GetCandleType(symbol,prev_cand,m_cur_timeframes2[i],k+1); // previous candlestick GetCandleType(symbol,cur_cand,m_cur_timeframes2[i],k); // current candlestick //--- if(cur_cand.type==cand1[j].type && cur_cand.bull==cand1[j].bull && prev_cand.type==cand2[j].type && prev_cand.bull==cand2[j].bull) { m_pattern_total[j]++; GetCategory(symbol,k+4,ratings,m_cur_timeframes2[i],m_threshold_value2); } } AddRow(m_table2,"",ratings,m_pattern_total[j],m_cur_timeframes2[i]); m_pattern_total[j]=0; } } } else if(m_pattern_size==3) { ZeroMemory(cur_cand); ZeroMemory(prev_cand); ZeroMemory(prev_cand2); //--- Calculation by timeframes for(int i=0;i<total;i++) { MqlRates rt[]; ZeroMemory(ratings); int copied=CopyRates(symbol,m_cur_timeframes2[i],start,end,rt); //--- Calculation by patterns for(int j=0;j<total_patterns;j++) { //--- Calculation by a date range for(int k=0;k<copied;k++) { //--- Get the current candlestick type GetCandleType(symbol,prev_cand2,m_cur_timeframes2[i],k+2); // previous candlestick GetCandleType(symbol,prev_cand,m_cur_timeframes2[i],k+1); // previous candlestick GetCandleType(symbol,cur_cand,m_cur_timeframes2[i],k); // current candlestick //--- if(cur_cand.type==cand1[j].type && cur_cand.bull==cand1[j].bull && prev_cand.type==cand2[j].type && prev_cand.bull==cand2[j].bull && prev_cand2.type==cand3[j].type && prev_cand2.bull==cand3[j].bull) { m_pattern_total[j]++; GetCategory(symbol,k+5,ratings,m_cur_timeframes2[i],m_threshold_value2); } } AddRow(m_table2,"",ratings,m_pattern_total[j],m_cur_timeframes2[i]); m_pattern_total[j]=0; } } } //--- m_table2.DeleteRow(m_total_row); //--- Update the table m_table2.Update(true); m_table2.GetScrollVPointer().Update(true); return(true); }
El algoritmo de esta versión del método se basa en la necesidad de comprender en qué secuencia se da el cálculo a partir de los datos introducidos. Vamnos a omitir la obtención de los marcos temporales y el intervalo de fechas por parte del usuario, ya que hemos visto este proceso un poco más arriba. A continuación, el algoritmo verifica el tamaño de los patrones que están siendo puestos a prueba. Vamos a analizar un patrón de tres velas. Después de declarar las estructuras para guardar los datos de precio y de resetear la estructura ratings utilizada, entramos en el primer ciclo de la iteración de los marcos temporales, y obtenemos el número de datos copiados para cada uno de ellos. Esto permite determinar posteriormente en qué intervalo tendrá lugar la búsqueda de los patrones establecidos. Después del ciclo del marco temporal, entramos en el ciclo de cálculo para cada patrón en el marco temporal establecido. Y a continuación, para cada uno de los patrones, iteramos por las velas definidas en el intervalo de fechas especificado.
Para que resulte más comprensible, vamos a mostrar un ejemplo de cálculo y el orden de muestra de la información en el recuadro de resultados.
Fig.9 Ejemplo de cálculo y orden de muestra de los resultados en el recuadro
Como podemos ver por la fig.9, la simulación se ha realizado con la pareja de divisas EURUSD, con el patrón de 1 vela en los marcos temporales М15, М30, Н1, Н2. Se han seleccionado dos velas simples para la simulación, con los índices 1 y 2. En el recuadro de resultados se puede observar la implementación del algoritmo descrito más arriba. En concreto: primero se toma el marco temporal de 15 minutos, y luego se investigan en el mismo y por orden todos los patrones generados; acto seguido, se hace lo propio con el marco temporal de 30 minutos, y así sucesivamente.
Conclusión
Al final del artículo se adjunta un fichero con todos los archivos enumerados, clasificados por carpetas. Por eso, para que funcione correctamente, basta con colocar la carpeta MQL5 en la carpeta raíz del terminal. Para encontrar la carpeta raíz del terminal en la que se encuentra la carpeta MQL5 , debemos pulsar en MetaTarder 5 la combinación de teclas Ctrl+Shift+D o utilizar el menú contextual como se muestra en la fig.10, más abajo.
Fig.10 Abriendo la carpeta MQL5 en la carpeta raíz del terminal MetaTrader 5.
Programas usados en el artículo:
# |
Nombre |
Tipo |
Descripción |
---|---|---|---|
1 |
PatternAnalyzer.mq5 | Interfaz gráfica |
Panel de instrumentos para analizar el modelo de velas. |
2 | MainWindow.mqh | Biblioteca | Biblioteca para la construcción de la interfaz gráfica |
3 | Program.mqh | Biblioteca | Biblioteca de métodos para la creación de los elementos de la interfaz y los cálculos |
Artículos anteriores de esta serie:
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 II): Búsqueda automática de patrones nuevos.
Estudio de técnicas de análisis de velas (Parte III): Biblioteca para el trabajo con patrones.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/6301
- 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