![Monitoreo multidivisas de las señales comerciales (Parte 4): Mejorando la funcionalidad y el sistema de búsqueda de las señales](https://c.mql5.com/i/articles/overlay.png)
Monitoreo multidivisas de las señales comerciales (Parte 4): Mejorando la funcionalidad y el sistema de búsqueda de las señales
Contenido
- Introducción
- Indicador personalizado para configurar la señal comercial
- Ampliación del sistema de las reglas para la búsqueda de la señal comercial
- Transformación de la lista de símbolos en forma de tabla
- Edición rápida de las reglas para la búsqueda desde el monitoreo
- Localización de la aplicación
- Posibilidades adicionales
- Conclusión
Introducción
En la tercera parte, creamos el sistema básico de la búsqueda de señales comerciales, pero este sistema se basaba en una reducida gama de indicadores y en una elección lacónica de las reglas para la búsqueda. Además, los lectores manifestaron sus deseos y formularon las sugerencias respecto a la mejora de la usabilidad de la parte visual del monitoreo comercial. Por tanto, continuaremos desarrollando nuestra aplicación de acuerdo con los puntos listados en el Contenido.
Indicador personalizado para configurar la señal comercial
Un complemento evidente a la creación y edición de las señales comerciales es la ampliación de la posibilidad de elegir no sólo los indicadores del conjunto estándar de MetaTrader 5 que figuran en el monitoreo, sino también poder usar la parte computacional de sus propios indicadores. Por eso, será bastante lógico añadir esta posibilidad durante la creación de una señal comercial. Vamos a basarnos en el proyecto desde el artículo anterior (puede descargarlo al final del presente artículo). Merece la pena aclarar enseguida que al ampliar el proyecto de la tercera parte, algunos ajustes de los controles de la interfaz gráfica, así como, los algoritmos del funcionamiento de los métodos en la clase base, van a modificarse y completarse (se indicará en ello aparte).
En primer lugar, añadimos la posibilidad de elegir y usar un indicador personalizado en la ventana de añadir/editar la señal. Recordaré que la implementación de la interfaz de esta ventana se encuentra en el archivo SetWindow.mqh de nuestro proyecto. Lo abrimos y buscamos el método CreateIndicatorType(). Tenemos que modificar precisamente este método.
//+------------------------------------------------------------------+ //| Crea el menú desplegable con la selección del tipo del indicador | //+------------------------------------------------------------------+ bool CProgram::CreateIndicatorType(const int x_gap,const int y_gap) { //--- Transferir el objeto del panel m_indicator_type.MainPointer(m_set_window); //--- #define SIZE 10 //--- Array de valores de los elementos en la lista string pattern_names[SIZE]= { "ATR","CCI","DeMarker","Force Ind","WPR","RSI","Momentum","ADX","ADX Wilder","Custom" }; //--- Establecemos las propiedades antes de la creación m_indicator_type.XSize(200); m_indicator_type.YSize(26); m_indicator_type.LabelYGap(4); m_indicator_type.ItemsTotal(SIZE); m_indicator_type.Font(m_base_font); m_indicator_type.FontSize(m_base_font_size); m_indicator_type.BackColor(m_background); m_indicator_type.GetButtonPointer().Font(m_base_font); m_indicator_type.GetButtonPointer().FontSize(m_base_font_size); m_indicator_type.GetButtonPointer().BackColor(clrWhite); m_indicator_type.GetButtonPointer().XGap(100); m_indicator_type.GetButtonPointer().XSize(100); m_indicator_type.GetListViewPointer().Font(m_base_font); m_indicator_type.GetListViewPointer().FontSize(m_base_font_size); m_indicator_type.GetListViewPointer().ItemYSize(25); m_indicator_type.GetListViewPointer().YSize(200); //--- Guardamos los valores de los elementos en la lista del cuadro combinado (combobox) for(int i=0; i<SIZE; i++) m_indicator_type.SetValue(i,pattern_names[i]); //--- Obtenemos el puntero de la lista CListView *lv=m_indicator_type.GetListViewPointer(); //--- Establecemos las propiedades de la lista lv.LightsHover(true); m_indicator_type.SelectItem(1); //--- Creamos el control if(!m_indicator_type.CreateComboBox("Indicator Type",x_gap,y_gap)) return(false); //--- Añadimos el objeto al array general de los grupos de objetos CWndContainer::AddToElementsArray(1,m_indicator_type); return(true); }
Ahora vamos a analizar qué es lo que ha cambiado en comparación con la etapa anterior. Primero, ha sido añadida la macro sustitución SIZE que va a indicar en el número de los elementos en la lista desplegable de la selección. Es necesaria para tener la posibilidad de modificar este número en un solo lugar (sin tener que hacer estos cambios en todas las partes del código). Luego, de acuerdo con el nuevo tamaño, añadimos el nuevo elemento de la lista Custom al final. Podemos observar esta modificación en la Fig. 1.
Fig. 1 Adición del elemento de la selección del indicador personalizado.
Ahora, hay que añadir nuevos elementos de la interfaz que van a servir para configurar el uso del indicador. Tenemos que ajustarnos a los argumentos de la función iCustom() para poder usar la parte de cálculo de nuestro indicador personalizado. Se trata del nombre del símbolo, período, ruta hacia el indicador compilado con la extensión *.ex5 y la lista de sus parámetros de entrada separados con coma.
int iCustom( string symbol, // nombre del símbolo ENUM_TIMEFRAMES period, // período string name // carpeta/nombre_del_usuario del indicador ... // lista de parámetros de entrada del indicador );
El nombre del símbolo y el timeframe van a colocarse según los valores seleccionados durante los dos primeros pasos de la configuración primaria de la aplicación, mientras que la ruta hacia el indicador y la lista de los parámetros tendrán que definirse por el usuario. Para eso, hay que añadir dos campos de edición. Por tanto, añadimos dos nuevas variables y un método en la clase base CProgram para la implementación:
CTextEdit m_custom_path; CTextEdit m_custom_param; bool CreateCustomEdit(CTextEdit &text_edit,const int x_gap,const int y_gap,const string default_text);
Puesto que el método va a aplicarse en la ventana para crear/editar la señal comercial, lo vamos a implementar en el archivo SetWindow.mqh:
//+------------------------------------------------------------------+ //| Campo de edición para el indicador personalizado | //+------------------------------------------------------------------+ bool CProgram::CreateCustomEdit(CTextEdit &text_edit,const int x_gap,const int y_gap,const string default_text) { //--- Guardamos el puntero al control principal text_edit.MainPointer(m_set_window); //--- Propiedades text_edit.XSize(100); text_edit.YSize(24); text_edit.Font(m_base_font); text_edit.FontSize(m_base_font_size); text_edit.GetTextBoxPointer().AutoSelectionMode(true); text_edit.GetTextBoxPointer().XGap(1); text_edit.GetTextBoxPointer().XSize(325); text_edit.GetTextBoxPointer().DefaultTextColor(clrSilver); text_edit.GetTextBoxPointer().DefaultText(default_text); text_edit.GetTextBoxPointer().BorderColor(clrBlack); //--- Creamos el control if(!text_edit.CreateTextEdit("",x_gap,y_gap)) return(false); text_edit.IsLocked(true); //--- Añadimos el objeto al array general de los grupos de objetos CWndContainer::AddToElementsArray(1,text_edit); return(true); }
Usando el método CreateCustomEdit(), creamos dos campos de edición para nuestra tarea. Para eso, en el cuerpo del método CreateSetWindow() del mismo archivo, buscamos la sección Ajustes del indicador seleccionado y la completamos con el siguiente código:
if(!CreateCustomEdit(m_custom_path,240,22+10+2*(25+10),"Enter the indicator path")) return(false); if(!CreateCustomEdit(m_custom_param,240,22+10+3*(25+10),"Enter indicator parameters separated by commas")) return(false);
Como resultado, dos campos para introducir los datos aparecen en la ventana de configuraciones (véase Fig. 2).
Fig. 2 Adición de los campos de edición para los ajustes del indicador personalizado.
No obstante, estarán inactivos en esta fase del desarrollo. Eso está relacionado con el hecho de que su disponibilidad tiene que depender de la selección del Tipo del indicador (Indicator type), es decir, estarán disponibles sólo si tenemos seleccionada la línea Custom en la lista desplegable. Por eso, para implementar esta tarea, accedemos al método RebuildParameters() y lo actualizamos. Pero para empezar, vamos a la sección Evento de la selección del elemento de la lista desplegable en el método OnEvent() y añadimos una verificación del evento para la lista necesaria de la selección del tipo del indicador.
//--- Selección de un elemento en la lista desplegable del combobox if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM) { //--- Tipo del indicador if(lparam==m_indicator_type.Id()) RebuildParameters(m_indicator_type.GetListViewPointer().SelectedItemIndex()); }
Ahora, vamos a modificar el método RebuildParameters() de tal manera que se muestren los ajustes actuales para cada uno de los indicadores disponibles que se seleccione, y que, además de eso, se queden activos los campos de edición de la ruta y los parámetros para nuestro indicador personalizado.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CProgram::RebuildParameters(int index) { switch(index) { case 0: m_period_edit.LabelText("ATR Period"); m_applied_price.Hide(); m_custom_param.IsLocked(true); m_custom_path.IsLocked(true); break; case 1: m_period_edit.LabelText("CCI Period"); m_applied_price.Show(); m_custom_param.IsLocked(true); m_custom_path.IsLocked(true); break; case 2: m_period_edit.LabelText("DeMarker Period"); m_applied_price.Hide(); m_custom_param.IsLocked(true); m_custom_path.IsLocked(true); break; case 3: m_period_edit.LabelText("Force Index Period"); m_applied_price.Hide(); m_custom_param.IsLocked(true); m_custom_path.IsLocked(true); break; case 4: m_period_edit.LabelText("WPR Period"); m_applied_price.Hide(); m_custom_param.IsLocked(true); m_custom_path.IsLocked(true); break; case 5: m_period_edit.LabelText("RSI Period"); m_applied_price.Show(); m_custom_param.IsLocked(true); m_custom_path.IsLocked(true); break; case 6: m_period_edit.LabelText("Momentum Period"); m_custom_param.IsLocked(true); m_custom_path.IsLocked(true); break; case 7: m_period_edit.LabelText("ADX Period"); m_applied_price.Hide(); m_custom_param.IsLocked(true); m_custom_path.IsLocked(true); break; case 8: m_period_edit.LabelText("ADXW Period"); m_applied_price.Hide(); m_custom_param.IsLocked(true); m_custom_path.IsLocked(true); break; case 9: m_period_edit.LabelText("Buffer Number"); m_applied_price.Hide(); m_custom_param.IsLocked(false); m_custom_path.IsLocked(false); break; default: m_period_edit.LabelText("Ind Period"); m_applied_price.Hide(); m_custom_param.IsLocked(true); m_custom_path.IsLocked(true); break; } m_period_edit.Update(true); }
Si ahora compilamos el proyecto, obtenemos el siguiente resultado:
Fig. 3 Adición de los campos de edición para el indicador personalizado.
A continuación, tendremos que completar el Evento de la pulsación en el botón Añadir señal (Add Signal). Al pulsarlo, los valores de la selección y los ajustes del indicador se establecen por defecto.
//--- Clic en el botón Añadir señal if(lparam==m_add_signal.Id()) { if(m_total_signals>4) { MessageBox("Maximum number of signals is 5","Signal Monitor"); return; } m_set_window.OpenWindow(); RebuildParameters(1); m_number_signal=-1; RebuildTimeframes(); m_new_signal.LabelText("Add"); m_new_signal.Update(true); m_indicator_type.SelectItem(1); m_indicator_type.GetButtonPointer().Update(true); }
Antes de adaptar nuevos controles en forma de los campos de edición al algoritmo del guardado de los ajustes de la señal ya existente, vamos a ampliar el sistema de las reglas para la búsqueda de la señal comercial. Porque llevará a la adición de nuevos controles de la interfaz, y es contraproducente cambiar cada vez el algoritmo del guardado de los conjuntos al completar los ajustes en el sistema. Sería lógico añadir todos los controles y elementos de las configuraciones, y luego completar el método del guardado del conjunto de los ajustes para la búsqueda de la señal según el sistema.
Ampliación del sistema de las reglas para la búsqueda de la señal comercial
En este momento, el monitoreo comercial es capaz de crear señales a base de la desigualdad. Es decir, más, menos o igual al valor definido. No obstante, esta selección no siempre refleja lo que nos gustaría ver durante la búsqueda de la señal. Por ejemplo, al usar un indicador del tipo oscilador, a veces tiene sentido definir un determinado intervalo del valor, en vez de usar el límite superior o inferior. Pues, he decidido agregar esta posibilidad. Primero, hay que añadir un conmutador entre el sistema del ajuste de la regla de la búsqueda anterior y el sistema nuevo. Para eso, añadimos una nueva lista desplegable con dos tipos de la definición de la regla: Comparación (Compare) e Intervalo (Interval).
Vamos a la clase base CProgram y añadimos una nueva variable, instancia de la clase CСombobox, y creamos el método que implementa el control de la interfaz:
CComboBox m_rule_interval; bool CreateRuleInterval(const int x_gap,const int y_gap);
Añadimos la implementación del método al archivo SetWindow.mqh porque esta lista desplegable pertenece a la ventana de configuraciones.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::CreateRuleInterval(const int x_gap,const int y_gap) { //--- Transferir el objeto del panel m_rule_interval.MainPointer(m_set_window); //--- Array de valores de los elementos en la lista string pattern_names[2]= { "Compare","Interval", }; //--- Establecemos las propiedades antes de la creación m_rule_interval.XSize(160); m_rule_interval.YSize(26); m_rule_interval.LabelYGap(4); m_rule_interval.ItemsTotal(2); m_rule_interval.Font(m_base_font); m_rule_interval.FontSize(m_base_font_size); m_rule_interval.BackColor(m_background); m_rule_interval.GetButtonPointer().Font(m_base_font); m_rule_interval.GetButtonPointer().FontSize(m_base_font_size); m_rule_interval.GetButtonPointer().BackColor(clrWhite); m_rule_interval.GetButtonPointer().XGap(90); m_rule_interval.GetButtonPointer().XSize(80); m_rule_interval.GetListViewPointer().Font(m_base_font); m_rule_interval.GetListViewPointer().FontSize(m_base_font_size); m_rule_interval.GetListViewPointer().ItemYSize(26); //--- Guardamos los valores de los elementos en la lista del cuadro combinado (combobox) for(int i=0; i<2; i++) m_rule_interval.SetValue(i,pattern_names[i]); //--- Obtenemos el puntero de la lista CListView *lv=m_rule_interval.GetListViewPointer(); //--- Establecemos las propiedades de la lista lv.LightsHover(true); m_rule_interval.SelectItem(0); //--- Creamos el control if(!m_rule_interval.CreateComboBox("Rule",x_gap,y_gap)) return(false); //--- Añadimos el objeto al array general de los grupos de objetos CWndContainer::AddToElementsArray(1,m_rule_interval); return(true); }
Puesto que el nuevo modo de la definición de la regla Intervalo tiene que tener un límite inferior y superior, vamos a añadir un valor más aparte del campo de edición del valor numérico que ya existe. Lo que era antes, va a representar el límite superior del intervalo; y lo que añadimos, va a representar el límite inferior. Está claro que, a la hora de usar los indicadores, es necesario considerar qué valores se encuentran por debajo de cero, como en el caso del indicador WPR. Por tanto, en este caso, los conceptos del límite superior e inferior van a alternarse. Para no tener que crear un método separado de implementación del campo de edición para el límite inferior del intervalo, simplemente vamos a modificar la variable actual que se encarga del campo de edición existente, así como, el método CreateRule(). La variable se convertirá en un array:
CTextEdit m_rule_value[2];
Dentro del método, añadimos un nuevo argumento que recibe la referencia a la instancia de la clase CTextEdit.
bool CreateRuleValue(CTextEdit &text_edit,const int x_gap,const int y_gap);
Claro que también modificaremos la implementación de este método.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::CreateRuleValue(CTextEdit &text_edit,const int x_gap,const int y_gap) { //--- Guardamos el puntero al control principal text_edit.MainPointer(m_set_window); //--- Propiedades text_edit.XSize(80); text_edit.YSize(24); text_edit.GetTextBoxPointer().XGap(1); text_edit.LabelColor(C'0,100,255'); text_edit.Font(m_base_font); text_edit.FontSize(m_base_font_size); text_edit.MaxValue(999); text_edit.StepValue(0.1); text_edit.MinValue(-999); text_edit.SetDigits(3); text_edit.SpinEditMode(true); //--- Creamos el control if(!text_edit.CreateTextEdit("",x_gap,y_gap)) return(false); text_edit.SetValue(string(5)); text_edit.GetTextBoxPointer().AutoSelectionMode(true); //--- Añadimos el objeto al array general de los grupos de objetos CWndContainer::AddToElementsArray(1,text_edit); return(true); }
Además, hay que modificar algunos valores del método ya existente CreateRule():
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::CreateRule(const int x_gap,const int y_gap) { //--- Transferir el objeto del panel m_rule_type.MainPointer(m_set_window); //--- Array de valores de los elementos en la lista string pattern_names[5]= { ">",">=","==","<","<=" }; //--- Establecemos las propiedades antes de la creación m_rule_type.XSize(80); m_rule_type.YSize(26); m_rule_type.LabelYGap(4); m_rule_type.ItemsTotal(5); m_rule_type.Font(m_base_font); m_rule_type.FontSize(m_base_font_size); m_rule_type.BackColor(m_background); m_rule_type.GetButtonPointer().Font(m_base_font); m_rule_type.GetButtonPointer().FontSize(m_base_font_size); m_rule_type.GetButtonPointer().BackColor(clrWhite); m_rule_type.GetButtonPointer().XGap(1); m_rule_type.GetButtonPointer().XSize(80); m_rule_type.GetListViewPointer().Font(m_base_font); m_rule_type.GetListViewPointer().FontSize(m_base_font_size); m_rule_type.GetListViewPointer().ItemYSize(26); //--- Guardamos los valores de los elementos en la lista del cuadro combinado (combobox) for(int i=0; i<5; i++) m_rule_type.SetValue(i,pattern_names[i]); //--- Obtenemos el puntero de la lista CListView *lv=m_rule_type.GetListViewPointer(); //--- Establecemos las propiedades de la lista lv.LightsHover(true); m_rule_type.SelectItem(0); //--- Creamos el control if(!m_rule_type.CreateComboBox("",x_gap,y_gap)) return(false); //--- Añadimos el objeto al array general de los grupos de objetos CWndContainer::AddToElementsArray(1,m_rule_type); return(true); }
Ahora, se puede encontrar la sección Ajustes de condiciones en el método de la creación de la ventana de ajustes CreateSetWindow() y alterar su código con el siguiente:
//--- Ajustes de las condiciones if(!CreateRuleValue(m_rule_value[0],200,22+10+5*(25+10))) return(false); if(!CreateRuleValue(m_rule_value[1],300,22+10+5*(25+10))) return(false); if(!CreateRule(200,22+10+5*(25+10))) return(false); if(!CreateRuleInterval(10,22+10+5*(25+10))) return(false);
Esta modificación permitirá reajustar la posición de los controles existentes de la interfaz y añadir los nuevos. Si todo ha sido hecho correctamente, el resultado será como en la Fig. 4, pero todavía no funciona nada si tratamos de cambiar el modo de la regla de Compare a Interval. Habrá que corregirlo.
Fig. 4 Añadir la selección del modo de la búsqueda de señales.
Para eso, en el método OnEvent(), buscamos la sección responsable del Evento de la Selección de un elemento en la lista desplegable y añadimos el código que permite visualizar los controles correctos de la interfaz dependiendo de la selección del modo de las reglas.
//--- Selección de un elemento en la lista desplegable del combobox if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM) { ... //--- Tipo de la regla if(lparam==m_rule_interval.Id()) { switch(m_rule_interval.GetListViewPointer().SelectedItemIndex()) { case 0: m_rule_value[0].Hide(); m_rule_type.Show(); break; case 1: m_rule_value[0].Show(); m_rule_type.Hide(); break; default: break; } } }
La siguiente modificación se refiere al traslado de algunos eventos durante la carga de la interfaz en una sección separada del método OnEvent(). Para eso, vamos a crear la sección del eventoTerminar la creación de la interfaz y trasladar el código desde el método CreateGUI(). Como resultado, en este método se quedará el siguiente código:
//+------------------------------------------------------------------+ //| Crea la interfaz gráfica del programa | //+------------------------------------------------------------------+ bool CProgram::CreateGUI(void) { //--- Cargando el idioma ChangeLanguage(); //--- Paso 1-3. Ventana de la selección de símbolos. if(!CreateStepWindow(m_lang[0])) return(false); //--- if(!CreateSetWindow(m_lang[17])) return(false); //--- Creando el formulario 2 para la paleta de colores if(!CreateColorWindow("Color Picker")) return(false); //--- Terminando la creación de GUI CWndEvents::CompletedGUI(); return(true); }
Es la nueva sección:
//--- Terminando la creación de la interfaz if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI) { m_back_button.Hide(); m_add_signal.Hide(); m_signal_header.Hide(); m_label_button[1].IsPressed(true); m_label_button[1].Update(true); for(int i=0; i<5; i++) m_signal_editor[i].Hide(); m_rule_value[0].Hide(); }
Aquí, llamaré su atención a lo siguiente: al cargar la aplicación, ha sido añadida una acción nueva, es decir, ocultar el campo recientemente creado para introducir el límite inferior del interval.
Una vez formados los controles nuevos de la interfaz y los parámetros nuevos, se puede proceder a la modificación del algoritmo para cargar y guardar los conjuntos de los ajustes de las señales comerciales. Para eso, iremos al cuerpo del método SaveSignalSet() y lo adaptaremos según las modificaciones actuales.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::SaveSignalSet(int index) { //--- int h=FileOpen("Signal Monitor\\signal_"+string(index)+".bin",FILE_WRITE|FILE_BIN); if(h==INVALID_HANDLE) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Fallo al crear el archivo de la configuración","Monitoreo de señales"); else MessageBox("Failed to create configuration file","Signal Monitor"); return(false); } if(index>4) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("El número máximo de las señales no debe ser más de 5","Monitoreo de señales"); else MessageBox("Maximum number of signals is 5","Signal Monitor"); return(false); } //--- Guardar la selección //--- Tipo del indicador m_signal_set[index].ind_type=m_indicator_type.GetListViewPointer().SelectedItemIndex(); //--- Período del indicador if(m_signal_set[index].ind_type!=9) { m_signal_set[index].ind_period=(int)m_period_edit.GetValue(); //--- Tipo del precio aplicado m_signal_set[index].app_price=m_applied_price.GetListViewPointer().SelectedItemIndex(); } else { string path=m_custom_path.GetValue(); string param=m_custom_param.GetValue(); if(path=="") { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Por favor, introduzca la ruta hacia el indicador","Monitoreo de señales"); else MessageBox("Enter the indicator path","Signal Monitor"); FileClose(h); return(false); } if(param=="") { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox(" Por favor, introduzca los parámetros separándolos con coma","Monitoreo de señales"); else MessageBox("Enter indicator parameters separated by commas","Signal Monitor"); FileClose(h); return(false); } StringToCharArray(path,m_signal_set[index].custom_path); StringToCharArray(param,m_signal_set[index].custom_val); m_signal_set[index].ind_period=(int)m_period_edit.GetValue(); } //--- Tipo de la regla m_signal_set[index].rule_int=m_rule_interval.GetListViewPointer().SelectedItemIndex(); //--- Tipo de la comparación m_signal_set[index].rule_type=m_rule_type.GetListViewPointer().SelectedItemIndex(); //--- Valor de la regla m_signal_set[index].rule_value1=(double)m_rule_value[0].GetValue(); m_signal_set[index].rule_value2=(double)m_rule_value[1].GetValue(); //--- Tipo de la visualización de la etiqueta de texto m_signal_set[index].label_type=m_label_button[0].IsPressed()?0:1; //--- Guardamos el valor del campo de edición en caso del segundo tipo if(m_label_button[1].IsPressed()) StringToCharArray(StringSubstr(m_text_box.GetValue(),0,3),m_signal_set[index].label_value); //--- Color de la etiqueta de texto m_signal_set[index].label_color=m_color_button[0].CurrentColor(); //--- Color del fondo if(m_set_param[0].IsPressed()) m_signal_set[index].back_color=m_color_button[1].CurrentColor(); else m_signal_set[index].back_color=clrNONE; //--- Color del borde if(m_set_param[1].IsPressed()) m_signal_set[index].border_color=m_color_button[2].CurrentColor(); else m_signal_set[index].border_color=clrNONE; //--- Valor de la ayuda m_signal_set[index].tooltip=m_set_param[2].IsPressed(); if(m_signal_set[index].tooltip) StringToCharArray(m_tooltip_text.GetValue(),m_signal_set[index].tooltip_text); //--- Imagen seleccionada m_signal_set[index].image=m_set_param[3].IsPressed(); if(m_signal_set[index].image) m_signal_set[index].img_index=m_pictures_slider.GetRadioButtonsPointer().SelectedButtonIndex(); //--- Timeframes seleccionados int tf=0; for(int i=0; i<21; i++) { if(!m_tf_button[i].IsLocked() && m_tf_button[i].IsPressed()) { m_signal_set[index].timeframes[i]=true; StringToCharArray(m_tf_button[i].LabelText(),m_signal_set[index].tf_name[i].tf); tf++; } else m_signal_set[index].timeframes[i]=false; } //--- if(tf<1) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox(" No hay timeframes seleccionados","Monitoreo de señales"); else MessageBox("No timeframes selected","Signal Monitor"); FileClose(h); return(false); } //--- FileWriteStruct(h,m_signal_set[index]); FileClose(h); Print(" La configuración signal_"+string(index)+" ha sido guardada con éxito"); //--- return(true); }
Aquí hay muchas modificaciones, así que es necesario analizar las más importantes. El primer cambio es la verificación de que si está seleccionado un indicador estándar o personalizado antes del guardado. Al seleccionar un indicador personalizado, escribiremos un algoritmo del guardado de la ruta hacia el indicador y sus parámetros, así como, el valor en el campo de edición del período, pero en caso del indicador personalizado este campo nos sirve para obtener el número del búfer del indicador.
Dado que hemos cambiado el número de parámetros guardados, hay que reflejar eso en la estructura SIGNAL que sirve del elemento a través del cual todo se guarda en un archivo binario. Por eso, vamos a completarla con nuevas variables:
struct SIGNAL { int ind_type; int ind_period; int app_price; int rule_int; int rule_type; double rule_value1; double rule_value2; int label_type; uchar label_value[10]; color label_color; color back_color; color border_color; bool tooltip; uchar tooltip_text[100]; bool image; int img_index; bool timeframes[21]; TFNAME tf_name[21]; uchar custom_path[100]; uchar custom_val[100]; };
Ahora, el campo de edición del valor del límite durante la comparación rule_value se sustituye por rule_value 1 y rule_value2 para los campos del límite inferior y superior en el modo Intervalo. Además, han sido añadidos custom_path y custom_val para almacenar datos sobre la ruta del indicador personalizado y sus parámetros. Por tanto, también hay que modificar el método de la carga del conjunto de los parámetros de la señal comercial desde el archivo LoadSignalSet():
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::LoadSignalSet(int index) { int h=FileOpen("Signal Monitor\\signal_"+string(index)+".bin",FILE_READ|FILE_BIN); if(h==INVALID_HANDLE) { MessageBox("Configuración no encontrada","Signal Monitor"); return(false); } ZeroMemory(m_signal_set[index]); FileReadStruct(h,m_signal_set[index]); //--- Cargando el tipo del indicador m_indicator_type.SelectItem(m_signal_set[index].ind_type); RebuildParameters(m_signal_set[index].ind_type); m_indicator_type.GetButtonPointer().Update(true); if(m_signal_set[index].ind_type!=9) { //--- Cargando el período del indicador m_period_edit.SetValue((string)m_signal_set[index].ind_period); m_period_edit.GetTextBoxPointer().Update(true); //--- Cargando el precio aplicado, si hay if(!m_applied_price.IsLocked()) { m_applied_price.SelectItem(m_signal_set[index].app_price); m_applied_price.GetButtonPointer().Update(true); } } else { m_period_edit.SetValue((string)m_signal_set[index].ind_period); m_custom_path.SetValue(CharArrayToString(m_signal_set[index].custom_path)); m_custom_param.SetValue(CharArrayToString(m_signal_set[index].custom_val)); m_custom_path.GetTextBoxPointer().Update(true); m_custom_param.GetTextBoxPointer().Update(true); } //--- Cargando la regla de la señal m_rule_interval.SelectItem(m_signal_set[index].rule_int); m_rule_interval.GetButtonPointer().Update(true); m_rule_type.SelectItem(m_signal_set[index].rule_type); m_rule_type.GetButtonPointer().Update(true); m_rule_value[0].SetValue((string)m_signal_set[index].rule_value1); m_rule_value[0].GetTextBoxPointer().Update(true); m_rule_value[1].SetValue((string)m_signal_set[index].rule_value2); m_rule_value[1].GetTextBoxPointer().Update(true); //--- Cargando la etiqueta de texto if(m_signal_set[index].label_type==0) { m_label_button[0].IsPressed(true); m_label_button[0].Update(true); m_label_button[1].IsPressed(false); m_label_button[1].Update(true); m_text_box.IsLocked(true); } else { m_label_button[0].IsPressed(false); m_label_button[0].Update(true); m_label_button[1].IsPressed(true); m_label_button[1].Update(true); m_text_box.IsLocked(false); m_text_box.ClearTextBox(); m_text_box.AddText(0,CharArrayToString(m_signal_set[index].label_value)); m_text_box.Update(true); } //--- Cargando el color de la etiqueta de texto m_color_button[0].CurrentColor(m_signal_set[index].label_color); m_color_button[0].Update(true); //--- Cargando el color del fondo if(m_signal_set[index].back_color==clrNONE) { m_set_param[0].IsPressed(false); m_set_param[0].Update(true); m_color_button[1].IsLocked(true); m_color_button[1].GetButtonPointer().Update(true); } else { m_set_param[0].IsPressed(true); m_set_param[0].Update(true); m_color_button[1].IsLocked(false); m_color_button[1].CurrentColor(m_signal_set[index].back_color); m_color_button[1].GetButtonPointer().Update(true); } //--- Cargando el color del borde if(m_signal_set[index].border_color==clrNONE) { m_set_param[1].IsPressed(false); m_set_param[1].Update(true); m_color_button[2].IsLocked(true); m_color_button[2].GetButtonPointer().Update(true); } else { m_set_param[1].IsPressed(true); m_set_param[1].Update(true); m_color_button[2].IsLocked(false); m_color_button[2].CurrentColor(m_signal_set[index].border_color); m_color_button[2].GetButtonPointer().Update(true); } //--- Cargando el valor de la ayuda if(!m_signal_set[index].tooltip) { m_set_param[2].IsPressed(false); m_set_param[2].Update(true); m_tooltip_text.IsLocked(true); m_tooltip_text.Update(true); } else { m_set_param[2].IsPressed(true); m_set_param[2].Update(true); m_tooltip_text.IsLocked(false); m_tooltip_text.ClearTextBox(); m_tooltip_text.AddText(0,CharArrayToString(m_signal_set[index].tooltip_text)); m_tooltip_text.Update(true); } //--- Cargando la imagen if(!m_signal_set[index].image) { m_set_param[3].IsPressed(false); m_set_param[3].Update(true); m_pictures_slider.IsLocked(true); m_pictures_slider.GetRadioButtonsPointer().Update(true); } else { m_set_param[3].IsPressed(true); m_set_param[3].Update(true); m_pictures_slider.IsLocked(false); m_pictures_slider.GetRadioButtonsPointer().SelectButton(m_signal_set[index].img_index); m_pictures_slider.GetRadioButtonsPointer().Update(true); } //--- Cargando timeframes seleccionados for(int i=0; i<21; i++) { if(!m_tf_button[i].IsLocked()) { m_tf_button[i].IsPressed(m_signal_set[index].timeframes[i]); m_tf_button[i].Update(true); } } //--- FileClose(h); return(true); }
Aquí, igual como en el método del guardado, se verifica si ha sido seleccionado un indicador estándar de la lista o un indicador personalizado, y dependiendo de eso se cargan los datos necesarios en la interfaz de la ventana de ajustes para la edición.
Después de configurar el guardado y la carga del conjunto de configuraciones de la señal comercial, podemos proceder a completar el algoritmo de la búsqueda de la señal. Para eso, vamos al método GetSignal(), buscamos la sección del código Verificar condiciones y la reemplazamos por lo siguiente:
//--- Verificando condiciones int s=0; if(signal_set.rule_int==0) { double r_value=signal_set.rule_value2; double c_value=val[0]; m_ind_value=c_value; switch(signal_set.rule_type) { case 0: if(c_value>r_value) s=1; break; case 1: if(c_value>=r_value) s=1; break; case 2: if(c_value==r_value) s=1; break; case 3: if(c_value<r_value) s=1; break; case 4: if(c_value<=r_value) s=1; break; default: s=0; break; } } else if(signal_set.rule_int==1) { double r_value_min=signal_set.rule_value1; double r_value_max=signal_set.rule_value2; double c_value=val[0]; m_ind_value=c_value; if(c_value>=r_value_min && c_value<=r_value_max) s=1; }
Además, incluimos los indicadores agregados en la sección Obteniendo el handle del indicador seleccionado:
//--- Obteniendo el handle del indicador seleccionado string str[],name; double arr[]; switch(signal_set.ind_type) { case 0: h=iATR(sy,tf,signal_set.ind_period); break; case 1: h=iCCI(sy,tf,signal_set.ind_period,app_price); break; case 2: h=iDeMarker(sy,tf,signal_set.ind_period); break; case 3: h=iForce(sy,tf,signal_set.ind_period,MODE_SMA,VOLUME_TICK); break; case 4: h=iWPR(sy,tf,signal_set.ind_period); break; case 5: h=iRSI(sy,tf,signal_set.ind_period,app_price); break; case 6: h=iMomentum(sy,tf,signal_set.ind_period,app_price); break; case 7: h=iADX(sy,tf,signal_set.ind_period); break; case 8: h=iADXWilder(sy,tf,signal_set.ind_period); break; case 9: StringSplit(m_custom_param.GetValue(),StringGetCharacter(",",0),str); ArrayResize(arr,ArraySize(str)); for(int i=0; i<ArraySize(str); i++) arr[i]=StringToDouble(str[i]); name=m_custom_path.GetValue(); h=GetCustomValue(tf,name,arr); break; default: break; }
Ahora, este bloque contiene la verificación del tipo del modo de la búsqueda (Comparación o Intervalo), y dependiendo de eso, su propio método para verificar la correspondencia del valor del indicador a las condiciones establecidas.
Transformación de la lista de símbolos en forma de tabla
La visualización de la posibilidad de seleccionar los símbolos en forma del nombre y casilla de verificación (chekbox) al lado era bastante suficiente para una cantidad no demasiado grande de los mismos en la cuenta comercial. Sin embargo, si hay más de cien símbolos, se puede observar un aumento considerable de la altura de la ventana de la aplicación, y como podemos recordar, ella está vinculada a la cantidad de las líneas con los símbolos y se redimensiona en función de ello. Por tanto, se ha decidido sustituir esta forma de la representación de los símbolos por una visualización en forma de la tabla. La ventaja de esta sustitución consiste en lo siguiente: si hay muchos símbolos en la cuenta comercial, todos los símbolos que se encuentran en las líneas que exceden la altura de la tabla se ocultan, y una barra de deslizamiento aparece a la derecha. Pero no se puede eliminar los checkbox por completo, porque también se utilizan al seleccionar los timeframes de trabajo. Por esa razón, tenemos que resolver algunos problemas más:
- Crear una tabla cuyas celdas van a contener los mismos nombres de los símbolos con los checkbox.
- Simplificar los checkbox ya existentes sólo para la selección de los timeframes de trabajo.
- Adaptar todas las modificaciones en la interfaz al algoritmo ya existente de la selección de los símbolos y timeframes de trabajo para la siguiente búsqueda en ellos.
Primero, tenemos que quitar la visualización de los checkbox antiguos en el primer paso de la configuración de la aplicación. Para eso, los ocultamos en el evento Terminando la creación de la interfaz. Ahora, ya sabemos que el número de los checkbox siempre se queda inalterable y es igual a 21, es decir, a la cantidad total de los timeframes disponibles en el terminal. Por eso, convertimos el array dinámico m_checkbox[] en estático con el tamaño 21.
//--- Ocultando los checkbox de los timeframes for(int i=0; i<21; i++) m_checkbox[i].Hide();
No olvidemos que el modo de la creación de los checkbox también tiene que ser corregido debido a su destinación cambiada. Por eso, entramos en el cuerpo del método CreateStepWindow() y reemplazamos la sección Checkbox por la siguiente:
//--- Checkbox int k=0; string timeframe_names[21]= { "M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30", "H1","H2","H3","H4","H6","H8","H12","D1","W1","MN" }; for(int j=0; j<=3; j++) { for(int i=0; i<7; i++) { if(k<21) if(!CreateCheckBox(m_checkbox[k],10+80*i,m_step_window.CaptionHeight()+70+j*25,timeframe_names[k])) return(false); k++; } }
También quitamos la línea del cálculo de la altura de la ventana basándose en la cantidad de los símbolos en la Observación del mercado.
m_step_window.ChangeWindowHeight(m_checkbox[m_all_symbols-1].YGap()+30+30);
Hacemos que la altura de la ventana sea estática:
m_step_window.YSize(500);
Ahora, tenemos que crear un objeto base de la tabla para su llenado posterior con los datos desde la Observación del mercado. Para ello, creamos una instancia de la clase CTable y el método de la implementación de la tabla.
//--- Tabla dibujada CTable m_table; bool CreateTable(const int x_gap,const int y_gap);
Lo implementamos en el archivo de la ventana principal StepWindow.mqh:
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::CreateTable(const int x_gap,const int y_gap) { #define COLUMNS1_TOTAL 7 #define ROWS1_TOTAL int(MathCeil(m_all_symbols/7)) //--- Guardamos el puntero al control principal m_table.MainPointer(m_step_window); //--- Array del ancho de columnas int width[COLUMNS1_TOTAL]; ::ArrayInitialize(width,80); //--- Array del margen del texto en las columnas por el eje X int text_x_offset[COLUMNS1_TOTAL]; ::ArrayInitialize(text_x_offset,25); //--- Array de alineación del texto dentro las columnas ENUM_ALIGN_MODE align[COLUMNS1_TOTAL]; ::ArrayInitialize(align,ALIGN_LEFT); //--- Array del margen de las imágenes en las columnas por el eje X int image_x_offset[COLUMNS1_TOTAL]; ::ArrayInitialize(image_x_offset,5); //--- Array del margen de las imágenes en las columnas por el eje Y int image_y_offset[COLUMNS1_TOTAL]; ::ArrayInitialize(image_y_offset,4); //--- Propiedades m_table.XSize(560); m_table.YSize(190); m_table.Font(m_base_font); m_table.FontSize(m_base_font_size); m_table.CellYSize(20); m_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL); m_table.TextAlign(align); m_table.ColumnsWidth(width); m_table.TextXOffset(text_x_offset); m_table.ImageXOffset(image_x_offset); m_table.ImageYOffset(image_y_offset); m_table.LabelXGap(5); m_table.LabelYGap(4); m_table.IconXGap(7); m_table.IconYGap(4); m_table.MinColumnWidth(0); m_table.LightsHover(true); m_table.SelectableRow(false); m_table.IsWithoutDeselect(false); m_table.ColumnResizeMode(true); m_table.IsZebraFormatRows(clrWhiteSmoke); m_table.AutoXResizeMode(true); m_table.AutoXResizeRightOffset(10); m_table.AutoYResizeMode(true); m_table.AutoYResizeBottomOffset(50); //--- Llenamos la tabla con datos InitializingTable(); //--- Creamos el control if(!m_table.CreateTable(x_gap,y_gap)) return(false); //--- Añadimos el objeto al array general de los grupos de objetos CWndContainer::AddToElementsArray(0,m_table); return(true); }
Pero antes de usar este método para crear la tabla, hay que llenarla con datos, a saber, con la lista de todos los símbolos de la Observación del mercado para la cuenta actual. El método InitializingTable() será responsable de ello; lo añadimos a la sección privada de la clase base y lo implementamos:
//+------------------------------------------------------------------+ //| Inicialización de la tabla | //+------------------------------------------------------------------+ void CProgram::InitializingTable(void) { //--- Array de imágenes 1 string image_array1[2]= { "Images\\EasyAndFastGUI\\Controls\\checkbox_off.bmp", "Images\\EasyAndFastGUI\\Controls\\checkbox_on_g.bmp" }; //--- int k=0; for(int c=0; c<COLUMNS1_TOTAL; c++) { //--- for(int r=0; r<ROWS1_TOTAL; r++) { if(k<m_all_symbols) { //--- Establecemos el tipo de la celda como Checkbox m_table.CellType(c,r,CELL_CHECKBOX); m_table.SetImages(c,r,image_array1); //--- Definimos el texto m_table.SetValue(c,r,SymbolName(k,false)); } k++; } } }
Ahora, usamos las técnicas obtenidas en el cuerpo del método CreateStepWindow() para crear y llenar la tabla. Como resultado, obtenemos una lista completa de todos los símbolos disponibles en la Observación del mercado (Fig. 5), y al mismo tiempo, con el mismo tipo de la selección a través del chekbox, pero en forma de la tabla.
Fig. 5 Resultado de la transformación de la lista de símbolos en forma de tabla.
El siguiente paso consiste en vincular la tabla recién creada con las mismas interacciones que había con el conjunto anterior de los checkbox. Es decir, para que se pueda:
- Seleccionar los conjuntos predefinidos ALL, Major, Crosses.
- Cuardar y cargar las plantillas del conjunto personalizado de símbolos.
Para implementar el primer punto, es necesario sustituir las secciones con los nombres de las plantillas por el siguiente código en el cuerpo del método OnEvent():
//--- All if(lparam==m_currency_set[0].Id() && m_currency_set[0].IsPressed()) { m_currency_set[1].IsPressed(false); m_currency_set[2].IsPressed(false); m_currency_set[1].Update(true); m_currency_set[2].Update(true); //--- int k=0; for(int c=0; c<7; c++) { //--- for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(k<m_all_symbols) m_table.ChangeImage(c,r,1); k++; } } m_table.Update(true); } //--- Majors else if(lparam==m_currency_set[1].Id() && m_currency_set[1].IsPressed()) { m_currency_set[0].IsPressed(false); m_currency_set[2].IsPressed(false); m_currency_set[0].Update(true); m_currency_set[2].Update(true); //--- string pairs[4]= {"EURUSD","GBPUSD","USDCHF","USDJPY"}; //--- Quitar la selección int k=0; for(int c=0; c<7; c++) { //--- for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(k<m_all_symbols) m_table.ChangeImage(c,r,0); k++; } } //--- k=0; for(int c=0; c<7; c++) { //--- for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(k<m_all_symbols) { for(int j=0; j<4; j++) { if(m_table.GetValue(c,r)==pairs[j]) m_table.ChangeImage(c,r,1); } } k++; } } m_table.Update(true); } //--- Crosses else if(lparam==m_currency_set[2].Id() && m_currency_set[2].IsPressed()) { m_currency_set[0].IsPressed(false); m_currency_set[1].IsPressed(false); m_currency_set[0].Update(true); m_currency_set[1].Update(true); //--- string pairs[20]= { "EURUSD","GBPUSD","USDCHF","USDJPY","USDCAD","AUDUSD","AUDNZD","AUDCAD","AUDCHF","AUDJPY", "CHFJPY","EURGBP","EURAUD","EURCHF","EURJPY","EURCAD","EURNZD","GBPCHF","GBPJPY","CADCHF" }; //--- Quitar la selección int k=0; for(int c=0; c<7; c++) { //--- for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(k<m_all_symbols) m_table.ChangeImage(c,r,0); k++; } } //--- k=0; for(int c=0; c<7; c++) { //--- for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(k<m_all_symbols) { for(int j=0; j<20; j++) { if(m_table.GetValue(c,r)==pairs[j]) m_table.ChangeImage(c,r,1); } } k++; } } m_table.Update(true); } //--- if((lparam==m_currency_set[0].Id() && !m_currency_set[0].IsPressed()) || (lparam==m_currency_set[1].Id() && !m_currency_set[1].IsPressed()) || (lparam==m_currency_set[2].Id() && !m_currency_set[2].IsPressed()) ) { //--- Quitar la selección int k=0; for(int c=0; c<7; c++) { //--- for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(k<m_all_symbols) m_table.ChangeImage(c,r,0); k++; } } m_table.Update(true); }
Para guardar y cargar los conjuntos seleccionados de los símbolos, en nuestra aplicación se usaban los métodos SaveSymbolSet() y LoadSymbolSet(), respectivamente. Por eso, precisamente dentro de ellos es necesario corregir aquella parte del código donde los datos se cogen de la lista de los checkbox por los datos desde la tabla recién creada, y luego también se cargan en ella.
//+------------------------------------------------------------------+ //| Guardando la plantilla en el archivo | //+------------------------------------------------------------------+ bool CProgram::SaveSymbolSet(string file_name) { if(file_name=="") { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Por favor, elija el nombre de la plantilla para el guardado","Monitoreo de señales"); else MessageBox("Choose a name for the template to save","Signal Monitor"); return(false); } int h=FileOpen("Signal Monitor\\"+file_name+".bin",FILE_WRITE|FILE_BIN); if(h==INVALID_HANDLE) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Fallo al crear el archivo de la configuración","Monitoreo de señales"); else MessageBox("Failed to create configuration file","Signal Mo nitor"); return(false); } else MessageBox("La configuración "+file_name+" ha sido guardada con éxito","Signal Monitor"); //--- Guardar la selección de símbolos int k=0; for(int c=0; c<7; c++) { //--- for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(k<m_all_symbols) m_save.tf[k]=m_table.SelectedImageIndex(c,r)>0?true:false; k++; } } //--- FileWriteStruct(h,m_save); FileClose(h); //--- return(true); } //+------------------------------------------------------------------+ //| Cargando los datos en el panel | //+------------------------------------------------------------------+ bool CProgram::LoadSymbolSet(string file_name) { if(file_name=="") { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Por favor, elija el nombre de la plantilla para la carga","Monitoreo de señales"); else MessageBox("Choose a name for the template to load","Signal Monitor"); return(false); } int h=FileOpen("Signal Monitor\\"+file_name+".bin",FILE_READ|FILE_BIN); if(h==INVALID_HANDLE) { MessageBox("La configuración "+file_name+" no ha sido encontrada","Signal Monitor"); return(false); } ZeroMemory(m_save); FileReadStruct(h,m_save); //--- Cargar la selección de símbolos int k=0; for(int c=0; c<7; c++) { //--- for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(k<m_all_symbols) { if(m_save.tf[k]) m_table.ChangeImage(c,r,1); else m_table.ChangeImage(c,r,0); } k++; } } m_table.Update(true); //--- FileClose(h); //--- return(true); }
Ahora, marcaremos el bloque donde se realiza la recopilación de los datos y su memorización en la estructura que será guardada en el archivo. Así como, la carga de los datos desde el archivo en la estructura y su traspaso posterior a la tabla.
Después de todos los cambios y adiciones, el resultado es la transformación visual que se observa en la Fig. 6. Pero el objetivo principal era proporcionar la posibilidad de trabajar más cómodamente con un gran número de símbolos que no cabían en la ventana de la aplicación en cuanto a la altura.
Fig. 6 Resultado de la adición de la tabla y sus interacciones con los controles de la interfaz.
A continuación, tenemos que editar los métodos del traspaso entre el Paso 1 y el Paso 2. Es que hemos cambiado el modo de obtener los datos sobre los símbolos seleccionados. Tenemos que modificar dos métodos que se responsabilizan del cambio entre los pasos de la configuración. Hay que modificar el método To_Step1() de tal manera que se oculte la posibilidad de seleccionar los timeframes y se muestre nuestra tabla al cambiar del Paso 2 al Paso 1.
//+------------------------------------------------------------------+ //| Ir al paso 1 | //+------------------------------------------------------------------+ void CProgram::ToStep_1(void) { //--- Cambiamos el encabezado m_step_window.LabelText("Signal Monitor Step 1: Choose Symbols"); m_step_window.Update(true); //--- Ocultamos el botón Atrás m_back_button.Hide(); //--- Mostramos la tabla m_table.Show(); //--- Ocultamos timeframes for(int i=0; i<21; i++) m_checkbox[i].Hide(); string names[3]= {"All","Majors","Crosses"}; //--- Cambiamos los nombres de los botones de los conjuntos for(int i=0; i<3; i++) { m_currency_set[i].IsPressed(false); m_currency_set[i].LabelText(names[i]); m_currency_set[i].Update(true); } //--- Mostramos el bloque del trabajo con las plantillas m_text_edit.Show(); m_load_button.Show(); m_save_button.Show(); //--- Establecemos el paso actual de la configuración m_current_step=1; }
En el método To_Step2(), hacemos lo contrario, es decir, ocultamos la tabla, mostramos la selección de los timeframes y recordamos la selección de los símbolos en el primer paso para los cálculos posteriores.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CProgram::ToStep_2(void) { //--- Comprobando si ha sido seleccionado por lo menos un símbolo int cnt=0; //--- for(int c=0; c<7; c++) { for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(m_table.SelectedImageIndex(c,r)>0) cnt++; } } //--- if(cnt<1) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("¡No hay símbolos seleccionados!","Atención"); else MessageBox("No symbols selected!","Warning"); return; } //--- Ocultamos la tabla m_table.Hide(); //--- Mostramos timeframes for(int i=0; i<21; i++) m_checkbox[i].Show(); //--- Calculamos el número de los símbolos seleccionados ArrayResize(m_symbols,cnt); cnt=0; //--- Guardamos los símbolos seleccionados en el array for(int c=0; c<7; c++) { for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(m_table.SelectedImageIndex(c,r)>0) { m_symbols[cnt]=m_table.GetValue(c,r); cnt++; } } } //--- Establecer símbolos seleccionados en la Observación del mercado for(int c=0; c<7; c++) { for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(m_table.SelectedImageIndex(c,r)>0) SymbolSelect(m_table.GetValue(c,r),true); else SymbolSelect(m_table.GetValue(c,r),false); } } //--- if(m_current_step==3) { m_add_signal.Hide(); m_signal_header.Hide(); m_next_button.LabelText("Next"); m_next_button.Update(true); for(int i=0; i<5; i++) m_signal_editor[i].Hide(); ClearSaves(); } //--- Cambiamos el encabezado m_step_window.LabelText("Signal Monitor Step 2: Choose Timeframes"); m_step_window.Update(true); string names[3]= {"All","Junior","Senior"}; //--- Cambiamos los nombres de los botones de los conjuntos for(int i=0; i<3; i++) { m_currency_set[i].LabelText(names[i]); m_currency_set[i].IsPressed(false); if(m_current_step==3) m_currency_set[i].Show(); m_currency_set[i].Update(true); } //--- Ocultamos el bloque del trabajo con las plantillas m_text_edit.Hide(); m_load_button.Hide(); m_save_button.Hide(); //--- Mostramos el botón Atrás m_back_button.Show(); //--- m_current_step=2; }
El último momento en la configuración de nueva visualización en forma de tabla será la corrección de la interacción entre los botones de la selección de los conjuntos definidos y la lista de los checkbox. Dado que la lista se ha quedado intacta, hay que introducir las modificaciones aquí:
//--- All if(lparam==m_currency_set[0].Id() && m_currency_set[0].IsPressed()) { m_currency_set[1].IsPressed(false); m_currency_set[2].IsPressed(false); m_currency_set[1].Update(true); m_currency_set[2].Update(true); //--- for(int i=0; i<21; i++) { m_checkbox[i].IsPressed(true); m_checkbox[i].Update(true); } } //--- Junior Timeframes else if(lparam==m_currency_set[1].Id() && m_currency_set[1].IsPressed()) { m_currency_set[0].IsPressed(false); m_currency_set[2].IsPressed(false); m_currency_set[0].Update(true); m_currency_set[2].Update(true); //--- string pairs[11]= { "M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30" }; //--- Quitar la selección for(int i=0; i<21; i++) { m_checkbox[i].IsPressed(false); m_checkbox[i].Update(true); } //--- for(int i=0; i<21; i++) { for(int j=0; j<11; j++) if(m_checkbox[i].LabelText()==pairs[j]) { m_checkbox[i].IsPressed(true); m_checkbox[i].Update(true); } } } //--- Senior Timeframes else if(lparam==m_currency_set[2].Id() && m_currency_set[2].IsPressed()) { m_currency_set[0].IsPressed(false); m_currency_set[1].IsPressed(false); m_currency_set[0].Update(true); m_currency_set[1].Update(true); //--- string pairs[10]= { "H1","H2","H3","H4","H6","H8","H12","D1","W1","MN" }; //--- Quitar la selección for(int i=0; i<21; i++) { m_checkbox[i].IsPressed(false); m_checkbox[i].Update(true); } //--- for(int i=0; i<21; i++) { for(int j=0; j<10; j++) if(m_checkbox[i].LabelText()==pairs[j]) { m_checkbox[i].IsPressed(true); m_checkbox[i].Update(true); } } } //--- if((lparam==m_currency_set[0].Id() && !m_currency_set[0].IsPressed()) || (lparam==m_currency_set[1].Id() && !m_currency_set[1].IsPressed()) || (lparam==m_currency_set[2].Id() && !m_currency_set[2].IsPressed()) ) { //--- Quitar la selección for(int i=0; i<21; i++) { m_checkbox[i].IsPressed(false); m_checkbox[i].Update(true); } }
Edición rápida de las reglas para la búsqueda desde el monitoreo
En el proceso del monitoreo de señales comerciales, puede surgir la necesidad de cambiar las condiciones de entrada de una de las señales creadas anteriormente. Actualmente, habrá que reiniciar la aplicación y volver a configurar cada señal del monitoreo, aparte de corregir la señal necesaria. Se debe reconocer que nos muy conveniente, ¿verdad? Así que necesitamos tener la posibilidad de editar las señales comerciales hechas desde el sistema del monitoreo. Para eso, vamos a completar la interfaz del monitoreo con un botón que va a abrir una pequeña ventana de diálogo, tal como se muestra en la Fig. 7, con la lista de las señales creadas, y al pulsar en cualquiera de ellas, tendremos la posibilidad de editar una señal ya creada.
Fig. 7 Esquema de la edición de la señal desde el monitoreo.
Vamos a empezar su implementación. Para visualizar el botón que abre la ventana con la lista de señales comerciales, vamos a escribir una propiedad adicional en el cuerpo del método CreateStepWindow():
m_step_window.TooltipsButtonIsUsed(true);
Lo desactivamos inmediatamente en el evento Terminando la creación de la interfaz para que este botón no aparezca en las fases de la configuración primaria de la aplicación, sino que se muestre en el paso cuando todas las señales ya están creadas y el monitoreo ya está iniciado:
//--- Terminando la creación de la interfaz if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI) { ... m_step_window.GetTooltipButtonPointer().Hide(); }
Que se muestre sólo cuando va a cargarse el monitoreo.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CProgram::AutoResize(const int x_size,const int y_size) { ... m_step_window.GetTooltipButtonPointer().Show(); }
Ahora, creamos la nueva ventana de diálogo donde va a mostrarse la lista de las señales creadas. Para eso, vamos a crear una variable-instancia de la clase CWindow y el método que implementa la creación de la ventana CreateFastEdit(), así como el método CreateFastEditor() para crear los botones al pulsar los cuales se realizara la edición de la señal.
CWindow m_fast_edit; bool CreateFastEdit(const string caption_text); bool CreateFastEditor(CButton &button,string text,const int x_gap,const int y_gap);
Vamos a implementar estos métodos:
//+------------------------------------------------------------------+ //| Crea la ventana para generar y editar señales comerciales | //+------------------------------------------------------------------+ bool CProgram::CreateFastEdit(const string caption_text) { //--- Añadimos el puntero de la ventana al array de ventanas CWndContainer::AddWindow(m_fast_edit); //--- Propiedades m_fast_edit.XSize(180); m_fast_edit.YSize(280); //--- Coordenadas int x=m_step_window.XGap()+m_step_window.XSize()+10; int y=m_step_window.YGap(); //--- m_fast_edit.CaptionHeight(22); m_fast_edit.IsMovable(true); m_fast_edit.CaptionColor(m_caption); m_fast_edit.CaptionColorLocked(m_caption); m_fast_edit.CaptionColorHover(m_caption); m_fast_edit.BackColor(m_background); m_fast_edit.FontSize(m_base_font_size); m_fast_edit.Font(m_base_font); m_fast_edit.WindowType(W_DIALOG); //--- Creando el formulario if(!m_fast_edit.CreateWindow(m_chart_id,m_subwin,caption_text,x,y)) return(false); //--- for(int i=0; i<5; i++) { if(!CreateFastEditor(m_fast_editor[i],"Signal_"+string(i),10,40*i+40)) return(false); } return(true); } //+------------------------------------------------------------------+ //| Crea un botón con imagen | //+------------------------------------------------------------------+ #resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\settings_light.bmp" bool CProgram::CreateFastEditor(CButton &button,string text,const int x_gap,const int y_gap) { //--- color baseclr=C'70,180,70'; color pressed=C'70,170,70'; //--- Guardamos el puntero a la ventana button.MainPointer(m_fast_edit); //--- Establecemos las propiedades antes de la creación button.XSize(110); button.YSize(30); button.Font(m_base_font); button.FontSize(m_base_font_size); button.IconXGap(3); button.IconYGap(7); button.IconFile("Images\\EasyAndFastGUI\\Icons\\bmp16\\settings_light.bmp"); button.BackColor(baseclr); button.BackColorHover(baseclr); button.BackColorPressed(pressed); button.BorderColor(baseclr); button.BorderColorHover(baseclr); button.BorderColorPressed(pressed); button.LabelColor(clrWhite); button.LabelColorPressed(clrWhite); button.LabelColorHover(clrWhite); button.IsCenterText(true); //--- Creamos el control if(!button.CreateButton(text,x_gap,y_gap)) return(false); //--- Añadimos el puntero al control a la base CWndContainer::AddToElementsArray(3,button); return(true); }
Y llamamos al método CreateFastEdit() en el cuerpo del método CreateGUI().
//+------------------------------------------------------------------+ //| Crea la interfaz gráfica del programa | //+------------------------------------------------------------------+ bool CProgram::CreateGUI(void) { //--- Paso 1-3. Ventana para seleccionar símbolos. if(!CreateStepWindow("Signal Monitor Step 1: Choose Symbols")) return(false); //--- if(!CreateSetWindow("Signal Monitor Edit Signal")) return(false); //--- Creando el formulario 2 para la paleta de colores if(!CreateColorWindow("Color Picker")) return(false); //--- Creando formulario de edición rápida if(!CreateFastEdit("Fast Signal Editor")) return(false); //--- Terminando la creación de GUI CWndEvents::CompletedGUI(); return(true); }
Ahora, necesitamos que se abra nuestra ventana de diálogo con señales al pulsar en el botón Configuración en el monitoreo. Para eso, escribiremos el siguiente código en la sección Evento de la pulsación en el botón del método OnEvent():
//--- ABRIR LA VENTANA DE LA CONFIGURACIÓN if(lparam==m_step_window.GetTooltipButtonPointer().Id()) { //--- Coordenadas int x=m_step_window.X()+m_step_window.XSize()+10; int y=m_step_window.Y(); m_fast_edit.X(x); m_fast_edit.Y(y); m_fast_edit.OpenWindow(); }
Si compilamos el proyecto ahora, obtenemos el siguiente resultado:
Fig. 8 Añadir la ventana de edición rápida de señales comerciales
No obstante, ahora se muestran todos los botones para editar las señales, mientras que nuestra tarea consiste en hacer que se vean solamente las señales creadas. Por eso, es necesario comprobar la cantidad actual de señales disponibles. Podemos hacerlo a través del evento Apertura de la ventana de diálogo:
//--- Apertura de la ventana de diálogo if(id==CHARTEVENT_CUSTOM+ON_OPEN_DIALOG_BOX) { if(m_current_step<4) return; for(int i=0; i<5; i++) { if(!FileIsExist("Signal Monitor\\signal_"+string(i)+".bin")) m_fast_editor[i].Hide(); } }
Aquí, se comprueba si existen los archivos que contienen los ajustes de las señales comerciales, y muestra sólo las señales creadas anteriormente. Por último, hagamos que se abra la ventana de la edición de la señal creada si pulsemos en el botón de esta señal. Lo vamos a hacer en la sección Evento de la pulsación en el botón.
Edición de la señal comercial for(int i=0; i<5; i++) { if(lparam==m_fast_editor[i].Id()) { m_fast_edit.CloseDialogBox(); LoadSignalSet(i); m_new_signal.LabelText("Save"); m_new_signal.Update(true); RebuildParameters(m_indicator_type.GetListViewPointer().SelectedItemIndex()); m_set_window.OpenWindow(); m_number_signal=i; } }
Al hacer clic en una de las señales en la ventana de edición rápida, podremos abrir la Ventana de Ajustes, cargar ahí los datos guardados previamente para esta señal comercial y continuar el trabajo con ella, cambiando lo necesario y guardando de nuevo en el archivo. En este caso, ahora no hace falta realizar la configuración completa del monitoreo comercial.
Localización de la aplicación
Para resolver el problema de la localización, hay que determinar todos los elementos de la interfaz que requieren la traducción y dejar intactos cuyas designaciones o nombres son de uso general. Para implementar esta tarea, usaremos un mecanismo bastante simple: crearemos un array string con datos que van a usarse para la substitución en los elementos de la interfaz de acuerdo con el idioma definido en los ajustes. Tendremos dos idiomas: ruso (español) e inglés. Procedemos a la implementación. Para empezar, en el archivo SignalMonitor.mq5, creamos una enumeración que nos permitirá seleccionar el idioma de la interfaz necesario al iniciar la aplicación, así como, cambiaremos los nombres de otros parámetros según el estándar, es decir, en inglés.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ enum UPDATE { MINUTE, // 1 minute MINUTE_15, // 15 minutes MINUTE_30, // 30 minutes HOUR, // 1 hour HOUR_4 // 4 hour }; enum LANG { RUSSIAN, // Russian ENGLISH // English }; //+------------------------------------------------------------------+ //| Parámetros de entrada del EA | //+------------------------------------------------------------------+ input int Inp_BaseFont = 10; // Base Font input color Caption = C'0,130,225'; // Caption Color input color Background = clrWhiteSmoke; // Back color input LANG Language = ENGLISH; // Interface language input UPDATE Update = MINUTE; // Update interval
Para transmitir la información sobre el idioma seleccionado por el usuario a la interfaz, crearemos una variable en la sección pública de la clase base CProgram.
//--- int m_language;
Se le asignará un número de orden del idioma seleccionado durante la inicialización de la aplicación.
program.m_language=Language;
Luego, en la sección privada de la clase base, crearemos un array para recibir datos para la substitución en la interfaz (según el idioma seleccionado), y un método que va a cargar los datos en la interfaz.
string m_lang[]; void ChangeLanguage(void);
Ahora, vamos a implementar el método declarado en el archivo Program.mqh y colocaremos los valores en los campos correspondientes de cada uno de los elementos de la interfaz, según la lista de los valores del conjunto de idioma.
//+------------------------------------------------------------------+ //| Cambiar el idioma de la interfaz | //+------------------------------------------------------------------+ void CProgram::ChangeLanguage(void) { //--- #define ITEMS 40 ArrayResize(m_lang,ITEMS); string rus[ITEMS]= { "Monitoreo de señales Paso 1: Seleccionar símbolos","Todas","Major","Crosses", "Atrás","Siguiente","Cargar (L)","Guardar (S)",«Nombre de plantilla","Monitoreo de señales Paso 2: Seleccionar timeframes", "Todos","Menores","Mayores", "Monitoreo de Señales Paso 3: Creación de señales comerciales","Crear","Añadir señal","Lista de señales", "Editor de señales comerciales","Tipo del indicador","1. Ajustes del indicador","Precio aplicado", "Por favor, introduzca la ruta del indicador","Por favor, introduzca los parámetros separándolos con coma"", "2. Ajuste de señal","Regla","Etiqueta","Valor","Texto","Color de la etiqueta","Fondo","Borde","Ayuda", "Imagen","Timeframes","Añadir"," Cancelar","Monitoreo de señales comerciales","Número del búfer","Guardar" }; string eng[ITEMS]= { "Signal Monitor Step 1: Choose Symbols","ALL","Major","Crosses", "Back","Next","Load(L)","Save(S)","Template name","Signal Monitor Step 2: Choose Timeframes", "ALL","Junior","Senior", "Signal Monitor Step 3: Creating Trading Signals","Create","Add Signal","Signal List", "Signal Monitor Edit Signal","Indicator Type","1.Indicator Settings","Applied Price", "Enter the indicator path","Enter indicator parameters separated by commas", "2.Signal Settings","Rule","Label","Value","Text","Label Color","Use Background","Use Border","Use Tooltip", "Use Image","Timeframes","Add","Cancel","Signal Monitor","Buffer number","Save" }; //--- Russian if(m_language==0) ArrayCopy(m_lang,rus); //--- English else ArrayCopy(m_lang,eng); }
Después de eso, podemos observar los resultados del trabajo no sólo en inglés, sino también en español (Fig. 9).
Fig. 9. Resultado del trabajo de la localización de la interfaz.
Posibilidades adicionales
En este apartado, me gustaría hacer un par de adiciones que mejoren la parte visual del monitoreo, así como, incluir la posibilidad del paso rápido al gráfico del símbolo con la señal comercial que ha aparecido. Bajo la parte visual se entiende una simple extensión de los bloques de las señales, porque se ha notado que su representación actual es algo pequeña. Para eso, aumentamos el tamaño de los bloques de las señales en el método CreateSignalButton() , y corregimos la posición de sus elementos dentro y de los propios bloques respecto uno a otro en el método To_Monitor().
button.XSize(60);
button.YSize(30);
button.IconXGap(2);
button.IconYGap(11);
button.LabelXGap(19);
button.LabelYGap(10);
//--- Símbolos int sy=ArraySize(m_symbols); ArrayResize(m_symbol_label,sy); for(int i=0; i<sy; i++) { if(!CreateSymbolLabel(m_symbol_label[i],5,m_step_window.CaptionHeight()+40+i*35,m_symbols[i])) return; m_symbol_label[i].Update(true); } //--- Timeframes int tf=ArraySize(m_timeframes); ArrayResize(m_timeframe_label,tf); //--- for(int i=0; i<tf; i++) { if(!CreateTimeframeLabel(m_timeframe_label[i],110+65*i,m_step_window.CaptionHeight()+3,m_timeframes[i])) return; m_timeframe_label[i].Update(true); } //-- Bloques de señales int k=0; ArrayResize(m_signal_button,sy*tf); for(int j=0; j<sy; j++) { for(int i=0; i<tf; i++) { if(!CreateSignalButton(m_signal_button[k],m_timeframe_label[i].XGap()+m_timeframe_label[i].XSize()/2,m_step_window.CaptionHeight()+35+j*35)) return; m_signal_button[k].Update(true); k++; } } //--- m_current_step=4; //--- Cambiamos las dimensiones de la ventana AutoResize(m_timeframe_label[tf-1].XGap()+m_timeframe_label[tf-1].XSize()+15,m_symbol_label[sy-1].YGap()+m_symbol_label[sy-1].YSize()+10);
Como resultado, obtenemos la implementación del monitoreo más conveniente para la observación, tal como se muestra en la Fig. 10.
Fig. 10 Cambio de las dimensiones de los bloques de las señales y corrección de la interfaz del monitoreo.
Nos queda escribir el código para que se abra el gráfico con el símbolo y timeframe correspondientes a la señal en cuyo bloque pulsamos. Para eso, en el método OnEvent()de la sección Evento de la pulsación del botón (los bloques de señales no son más que los botones), añadimos el siguiente código:
//--- PULSACIÓN EN EL BLOQUE DE LA SEÑAL for(int i=0; i<ArraySize(m_signal_button); i++) { if(lparam==m_signal_button[i].Id()) ChartOpen(GetSymbol(i),GetTimeframe(i)); }
Todo es bastante fácil. Pues, aquí terminamos la fase actual del desarrollo. En la siguiente parte, continuaremos mejorando el sistema de la búsqueda de señales, introduciremos el concepto «Señal compuesta» y aumentaremos las posibilidades para gestionar el monitoreo.
Conclusión
Al final del articulo se adjunta el archivo comprimido con todos los ficheros mencionados, ordenados por carpetas. Por eso, para un trabajo correcto basta con colocar la carpeta MQL5 en la raíz del terminal. Para abrir el directorio raíz del terminal que contiene la carpeta MQL5, pulse en la combinación Ctrl+Shift+D o utilice el menú contextual, tal como se muestra en la imagen 11.
Fig. 11 Abrir la carpeta MQL5 en el directorio raíz del terminal MetaTrader 5
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/7678
![Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XXXI): Solicitudes comerciales pendientes - Apertura de posiciones según condiciones](https://c.mql5.com/2/37/MQL5-avatar-doeasy__19.png)
![Monitoreo multidivisas de las señales comerciales (Parte 3): Introduciendo los algoritmos para la búsqueda](https://c.mql5.com/2/38/Article_Logo.png)
![Optimización móvil continua (Parte 4): Programa de control de la optimización (optimizador automático)](https://c.mql5.com/2/38/MQL5-avatar-continuous_optimization.png)
![El enfoque econométrico en la búsqueda de leyes de mercado: autocorrelación, mapas de calor y diagramas de dispersión](https://c.mql5.com/2/37/jlp_0d3zw11j.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