Monitoreo multidivisas de las señales comerciales (Parte 5): Señales compuestas
Contenido
Introducción
En la parte anterior de nuestra serie de artículos dedicada al desarrollo del monitoreo multidivisas de las señales comerciales, introducimos la posibilidad de usar los indicadores personalizados, además de ampliar el sistema de configuración de reglas lógicas para filtrar las opciones de la búsqueda. Pero como saben tanto los traders como los desarrolladores, los sistemas comerciales normalmente no se construyen a base de un solo indicador. Cada uno de los tipos de indicadores, sean del tipo tendencial, lateral o oscilatorio, tiene sus inconvenientes en determinados mercados.
Por ejemplo, los indicadores de tendencia a menudo saltan muchas señales falsas en caso de un movimiento lateral duradero. Por eso, es necesario confirmar o bien rechazarlas por medio de algo. Por esa razón, aparte de un indicador base para entrar en el merado, el sistema comercial recibe otros cuantos, que actúan como una especie de filtros para el proveedor principal de señales. O bien, se crea un sistema comercial a base de un conjunto de indicadores que muestran los mismos estados del mercado. En este caso, las confirmaciones simultáneas de parte de varios indicadores utilizados se considerarán como una señal para entrar en el mercado.
Por tanto, en el proceso del desarrollo del monitoreo comercial, el siguiente paso será la introducción del concepto de las señales diseñadas a base de varias señales simples ya existentes. Para eso, vamos a introducir el concepto de una señal compuesta. Nos basaremos en el proyecto desde el artículo anterior (puede descargarlo al final de aquel artículo).
Señales compuestas
Para empezar, hay que definir el concepto de la «señal compuesta» en el marco del desarrollo del monitoreo comercial. Como recuerda, antes usábamos en nuestra aplicación las señales simples que se creaban al aplicar unas determinadas condiciones a la fuente obtenida representada por varios indicadores, como por ejemplo RSI, WPR, CCI, también podíamos usar nuestro propio indicador personalizado.
Una señal compuesta es aquélla que se forma de dos o más señales simples relacionadas entre sí a través de los operadores lógicos AND o OR.
Basándose en esta definición, es necesario comprender que una señal compuesta va a incluir varias señales simples creadas previamente, que van a interactuar entre sí por medio de operadores lógicos. Es decir, se podrá crear una señal compuesta que incluya la condición de que haya dos o tres señales simples simultáneamente en este período de tiempo. Es precisamente lo que permite alcanzar nuestro objetivo respecto a la creación de un sistema comercial a base de la señal principal y filtro para ella. Mientras que el operador booleano OR permitirá buscar la señal comercial en varias direcciones a la vez, abarcando así un área aún más extensa dentro del análisis de los estados actuales del mercado.
Para tenerlo más claro, vamos a hilvanar un plan de actualización y renovación para nuestra aplicación. Primero, es necesario completar y actualizar la interfaz de la aplicación, agregando los siguientes controles.
Fig. 1 Añadiendo controles de la interfaz de usuario
Ya conocemos la parte izquierda de la imagen 1. Contiene el botón para añadir una señal, además incluye abajo una lista de las señales simples ya agregadas. Ahora tendremos que crear el botón para añadir una señal compuesta (1), la lista de señales compuestas agregadas (2), así como el tercer grupo de botones conmutadores (3) que son necesarios para asignar el indicio de la aplicación para las señales simples:
- S (Simple) representa una señal simple. En el monitoreo, se usa como una señal separada y no puede ser seleccionada para construir una señal compuesta.
- C (Composite) indica en una señal simple que se usará sólo para construir una señal compuesta. No se usa como una señal separada en el monitoreo.
- B (Both) permite ambas aplicaciones. Este tipo de la señal se busca en el monitoreo como una señal separada, y también puede usarse como un elemento de la señal compuesta.
En la figura 2, se muestra el esquema de la herramienta para construir una señal compuesta. Los elementos Simple 1 y Simple 2 representan la lista de las señales simples ya creadas que pueden participar en la construcción de una señal más compleja. Volviendo a los indicios del uso de las señales simples: las señales establecidas en la figura 2 deben tener la marca "C" o "B", de lo contrario, no van a figurar en esta lista.
Fig. 2 Esquema de la ventana para crear y editar una señal compuesta
Luego, vemos la sección Rule (Regla) en la que se construye una señal comercial. Se puede insertar una señal de la lista de arriba en cada una de las tres células (Slot). Usando los botones conmutadores, se puede definir los operadores lógicos necesarios entre ellas, creando así una regla para esta señal compuesta. Por ejemplo, si ponemos Simple 1 AND Simple 2, la señal compuesta va a mostrarse en el monitoreo sólo si hay ambas señales simples en este momento de tiempo.
Implementando la funcionalidad
Antes de introducir nuevas adiciones en nuestra aplicación, tenemos que preparar los elementos existentes para la futura funcionalidad. Para eso, hay que modificar los elementos de la interfaz de la lista de señales simples. En la figura 3, se puede observar que en vez de un botón simple con el nombre de la señal simple vamos a usar una etiqueta de texto con el nombre de la señal definido por el usuario, acompañado por el botón Edit y con el botón conmutador «Indicio de la aplicación de esta señal».
Fig. 3 Modificación del control de la edición de la señal
Ahora entremos en el proyecto descargado del artículo anterior y procedamos a las adiciones. Primero, vamos a eliminar el método CreateSignalEditor() y el array m_signal_editor[] usado para crear los botones de edición. Antes de crear nuevos elementos y métodos, introducimos dos macro sustituciones que van a definir el número máximo posible de las señales simples y compuestas.
#define SIMPLE_SIGNALS 10 #define COMPOSITE_SIGNALS 5
Luego vamos al final del cuerpo del método de la creación de la ventana principal CreateStepWindow() y en vez del código:
for(int i=0; i<5; i++) { if(!CreateSignalEditor(m_signal_editor[i],"Signal_"+string(i),10,40*i+90)) return(false); }
añadimos la nueva implementación de la visualización y edición de una señal simple.
for(int i=0; i<SIMPLE_SIGNALS; i++) { if(!CreateLabel(m_signal_label[i],10,40*i+95,"Signal_"+string(i))) return(false); if(!CreateSignalSet(m_signal_ind[i],"Edit",C'255,195,50',150,40*i+90)) return(false); if(!CreateSignalSet(m_signal_type[i],"S",C'75,190,240',150+35,40*i+90)) return(false); }
Aquí vemos dos arrays nuevos de las instancias de la clase CButton m_signal_ind[] y m_signal_type[], así como el array CTextLabel m_signal_label[]. Vamos a añadirlos a la clase del proyecto CProgram.
CTextLabel m_signal_label[SIMPLE_SIGNALS]; CButton m_signal_ind[SIMPLE_SIGNALS]; CButton m_signal_type[SIMPLE_SIGNALS];
Además, vamos a declarar e implementar el método nuevo CreateSignalSet().
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::CreateSignalSet(CButton &button,string text,color baseclr,const int x_gap,const int y_gap) { //--- Guardamos el puntero a la ventana button.MainPointer(m_step_window); //--- Establecemos las propiedades antes de la creación button.XSize(30); button.YSize(30); button.Font(m_base_font); button.FontSize(m_base_font_size); button.BackColor(baseclr); button.BackColorHover(baseclr); button.BackColorPressed(baseclr); button.BorderColor(baseclr); button.BorderColorHover(baseclr); button.BorderColorPressed(baseclr); 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(0,button); return(true); }
Así acabamos de formar una nueva lista de señales simples con una interfaz actualizada de interacción como se muestra en la figura 3. Insertamos las variables m_c_signal_label[] y m_c_signal_ind[] en la clase base, así como el código de abajo en el método CreateStepWindow() para añadir la lista de señales compuestas.
CTextLabel m_c_signal_label[COMPOSITE_SIGNALS];
CButton m_c_signal_ind[COMPOSITE_SIGNALS];
//--- for(int i=0; i<COMPOSITE_SIGNALS; i++) { if(!CreateLabel(m_c_signal_label[i],300,40*i+95,"Signal_"+string(i))) return(false); if(!CreateSignalSet(m_c_signal_ind[i],"Edit",C'255,195,50',150+290,40*i+90)) return(false); }
Después de rediseñar la herramienta para visualizar y editar las señales simples y compuestas, hay que añadir el botón para crear las señales compuestas y el encabezado de texto en el Paso 3 de la configuración primaria. Para eso, convertimos la variable m_add_signal que representa la instancia de la clase CButton y se usa en la adición del botón AddSignal (Añadir señal) en el array estático m_add_signal[2]. Reemplazamos el código en el cuerpo del método CreateStepWindow()
if(!CreateIconButton(m_add_signal,m_lang[15],10,30)) return(false);
por uno nuevo, con la adición del botón de la creación de una señal compuesta:
if(!CreateIconButton(m_add_signal[0],m_lang[15],10,30)) return(false); if(!CreateIconButton(m_add_signal[1],m_lang[43],300,30)) return(false);
Para visualizar los encabezados de las listas de señales simples y compuestas, hay que convertir la variable m_signal_header en el array estático m_signal_header[2]. Es decir, reemplazar el código actual:
if(!CreateLabel(m_signal_header,10,30+30+10,m_lang[16])) return(false);
por uno nuevo con dos encabezados:
if(!CreateLabel(m_signal_header[0],10,30+30+10,m_lang[16])) return(false); if(!CreateLabel(m_signal_header[1],300,30+30+10,m_lang[44])) return(false);
Tras estas modificaciones, obtenemos la siguiente actualización de la interfaz en el Paso 3:
Fig. 4 Añadir y actualizar botones para crear señales comerciales
Ahora es necesario vincular los objetos de la lista de señales simples creados anteriormente con el evento de la creación y adición. Voy a hacer una breve digresión: puesto que el número de los elementos de la interfaz va creciendo, y por tanto el número de diferentes interacciones con el usuario también, el cuerpo del método del manejador de eventos OnEvent() se hace muy grande y se reduce la comprensión de la pertenencia de un evento (grupo de eventos) a uno u otro elemento. Por esa razón, fue tomada la decisión de envolver todas las interacciones principales con la interfaz en los métodos correspondientes. Eso nos ofrece dos ventajas: OnEvent() va a contener la lista de eventos principales y será más fácil acceder a la lógica y al código de cada uno de ellos.
Por eso, buscamos el fragmento del código responsable de la adición de una nueva señal comercial simple, lo completamos y pasamos todo eso en el método nuevo AddSimpleSignal():
//+------------------------------------------------------------------+ //| Añade una nueva señal simple | //+------------------------------------------------------------------+ void CProgram::AddSimpleSignal(long lparam) { if(lparam==m_new_signal.Id()) { if(m_number_signal<0) { if(SaveSignalSet(m_total_signals)) { m_set_window.CloseDialogBox(); if(m_total_signals<SIMPLE_SIGNALS) { m_total_signals++; m_signal_label[m_total_signals-1].Show(); m_signal_label[m_total_signals-1].LabelText(m_signal_name.GetValue()); m_signal_label[m_total_signals-1].Update(true); m_signal_type[m_total_signals-1].Show(); m_signal_ind[m_total_signals-1].Show(); } else MessageBox("Maximum number of signals is "+string(SIMPLE_SIGNALS),"Signal Monitor"); //--- if(m_total_signals>1) { m_add_signal[1].IsLocked(false); m_add_signal[1].Update(true); } } } else { if(SaveSignalSet(m_number_signal,false)) { m_set_window.CloseDialogBox(); m_signal_label[m_number_signal].LabelText(m_signal_name.GetValue()); m_signal_label[m_number_signal].Update(true); } } } }
Lo vamos a invocar en el manejador de eventos OnEvent(), permitiendo así reducir el tamaño del cuerpo de este método y estructurar la parte de su implementación. Como ha sido dicho antes y mostrado en la figura 3, ahora la nueva visualización de una señal añadida debe incluir la posibilidad de añadir también el nombre propio de la señal. Para eso, hay que añadir este campo a la ventana de la creación y edición de la señal simple. Vamos a crear el método nuevo CreateSignalName() que va a añadir el campo para introducir el nombre de la señal.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::CreateSignalName(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(110); text_edit.YSize(24); text_edit.Font(m_base_font); text_edit.FontSize(m_base_font_size); text_edit.GetTextBoxPointer().XGap(110); text_edit.GetTextBoxPointer().XSize(200); text_edit.GetTextBoxPointer().DefaultTextColor(clrSilver); text_edit.GetTextBoxPointer().DefaultText(m_lang[44]); //--- Creamos el control if(!text_edit.CreateTextEdit(m_lang[44],x_gap,y_gap)) return(false); //--- Añadimos el objeto al array general de los grupos de objetos CWndContainer::AddToElementsArray(1,text_edit); return(true); }
No hay que olvidar que al añadir el campo nuevo para el nombre de una señal simple, también tenemos que añadirlo a la estructura SIGNAL.
uchar signal_name[50];
Además, hay que completar los métodos SaveSignalSet() y LoadSignalSet(). Así, aparte del conjunto de ajustes existentes, se podrá guardar también el nombre de la señal. No obstante, al añadir una señal nueva, no debe ocurrir la situación cuando esta nueva señal se cree con el nombre de una señal ya existente. Tampoco debe surgir la situación cuando se realice el guardado en el archivo del conjunto de parámetros editados de una señal ya creada. Para eso, añadimos el segundo argumento first_save del método SaveSignalSet(). Nos va a servir del indicio del primer guardado de la señal o del guardado de los parámetros editados de una señal ya creada.
bool SaveSignalSet(int index,bool first_save=true);
La implementación completa del método completado viene a continuación. Vamos a ver sus principales modificaciones.
bool CProgram::SaveSignalSet(int index,bool first_save=true) { //--- if(first_save && !CheckSignalNames(m_signal_name.GetValue())) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Este nombre ya se usa","Monitoreo de señales"); else MessageBox("This name is already in use","Signal Monitor"); return(false); } //--- 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>SIMPLE_SIGNALS-1) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("El número máximo de las señales no debe ser más de "+string(SIMPLE_SIGNALS),"Monitoreo de señales"); else MessageBox("Maximum number of signals is "+string(SIMPLE_SIGNALS),"Signal Monitor"); return(false); } //--- Guardar la selección //--- Nombre del indicador if(m_signal_name.GetValue()=="") { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Introduzca el nombre de la señal","Monitoreo de señales"); else MessageBox("Enter the Signal Name","Signal Monitor"); FileClose(h); return(false); } else if(StringLen(m_signal_name.GetValue())<3) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("El nombre de la señal debe tener más de 3 caracteres","Monitoreo de señales"); else MessageBox("Signal Name must be at least 3 letters","Signal Monitor"); FileClose(h); return(false); } else StringToCharArray(m_signal_name.GetValue(),m_signal_set[index].signal_name); //--- 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("Introduzca la ruta para 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 del indicador 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 de "+m_signal_name.GetValue()+" guardada con éxito"); //--- return(true); }
En el código de arriba, he llamado su atención en la verificación del indicio de la nueva señal, así como en el método usado CheckSignalNames() que comprueba si ya existe una señal con el nombre definido. Más abajo, se comprueba la existencia del nombre de la señal simple y se verifica si su longitud no es menos de tres caracteres.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::CheckSignalNames(string name) { //--- Buscando señales establecidas SIGNAL signal_set[]; int cnt=0; for(int i=0; i<SIMPLE_SIGNALS; i++) { if(FileIsExist("Signal Monitor\\signal_"+string(i)+".bin")) cnt++; } if(cnt<1) return(true); //--- ArrayResize(signal_set,cnt); ZeroMemory(signal_set); //--- for(int i=0; i<cnt; i++) { int h=FileOpen("Signal Monitor\\signal_"+string(i)+".bin",FILE_READ|FILE_BIN); if(h==INVALID_HANDLE) { MessageBox("Configuración no encontrada","Signal Monitor"); return(false); } FileReadStruct(h,signal_set[i]); if(CharArrayToString(m_signal_set[i].signal_name)==name) { FileClose(h); return(false); } FileClose(h); } return(true); }
En esta fase del desarrollo de la aplicación, tenemos la posibilidad de crear señales comerciales simples usando un formato nuevo (como podemos observar en la captura de abajo). Ahora, tenemos que «animar» los botones creados para editar las señales creadas y mostrar cómo funciona la asignación del indicio de la aplicación para una señal comercial simple.
Fig. 5 Nuevo formato para las señales comerciales simples
Para poder editar señales simples, crearemos el método nuevo EditSimpleSignal() y lo llamaremos en el manejador de eventos OnEvent().
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CProgram::EditSimpleSignal(long lparam) { for(int i=0; i<SIMPLE_SIGNALS; i++) { if(lparam==m_signal_ind[i].Id()) { LoadSignalSet(i); m_new_signal.LabelText(m_lang[38]); m_new_signal.Update(true); m_set_window.OpenWindow(); m_number_signal=i; } } }
Hacemos lo mismo para conmutar el indicio de la aplicación, es decir, creamos el método nuevo SignalSwitch() y lo invocamos en el manejador.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CProgram::SignalSwitch(long lparam) { for(int i=0; i<SIMPLE_SIGNALS; i++) { //--- if(lparam==m_signal_type[i].Id()) { if(m_signal_type[i].LabelText()=="S") SetButtonParam(m_signal_type[i],"C",clrMediumOrchid); else if(m_signal_type[i].LabelText()=="C") SetButtonParam(m_signal_type[i],"B",C'240,120,0'); else if(m_signal_type[i].LabelText()=="B") SetButtonParam(m_signal_type[i],"S",C'75,190,240'); } } }
La conmutación se visualizará tal como se muestra en la figura 6. Simplemente hacemos clic en el botón.
Fig. 6 Conmutación visual del indicio de la aplicación de una señal comercial simple
Ahora, las señales comerciales simples están preparadas completamente para ser usadas como elementos lógicos para crear las señales comerciales compuestas.
Antes ya hemos creado el botón para añadir una nueva señal compuesta Add Composite Signal. Ahora, hay que implementar la ventana de diálogo con la posibilidad de crear, configurar y editar las señales compuestas. Recordaré que durante la creación de la ventana de edición de una señal simple creábamos el archivo de inclusión SetWindow.mqh. Por eso, creamos el archivo CSetWindow.mqh de manera semejante. Dentro de este archivo, vamos a crear la interfaz del nuevo editor de señales compuestas (según el plano predefinido en la imagen 2). En el archivo recién creado, vamos a incluir Program.mqh para acceder a la clase base CProgram.
//+------------------------------------------------------------------+ //| CSetWindow.mqh | //| Copyright 2020, Alexander Fedosov | //| https://www.mql5.com/ru/users/alex2356 | //+------------------------------------------------------------------+ #include "Program.mqh"
En el archivo Program.mqh, incluimos CSetWindow.mqh junto con otros controles:
//+------------------------------------------------------------------+ //| Añadir controles | //+------------------------------------------------------------------+ #include "MainWindow.mqh" #include "SetWindow.mqh" #include "StepWindow.mqh" #include "CSetWindow.mqh"
Para crear la ventana de diálogo, introducimos el nuevo método CreateCompositeEdit() en la clase base, y lo implementamos en el archivo de inclusión recién creado.
protected: //--- Formularios bool CreateStepWindow(const string caption_text); bool CreateSetWindow(const string caption_text); bool CreateColorWindow(const string caption_text); bool CreateFastEdit(const string caption_text); bool CreateCompositeEdit(const string caption_text);
//+------------------------------------------------------------------+ //| Crea la ventana para generar y editar señales compuestas | //+------------------------------------------------------------------+ bool CProgram::CreateCompositeEdit(const string caption_text) { //--- Añadimos el puntero de la ventana al array de ventanas CWndContainer::AddWindow(m_composite_edit); //--- Propiedades m_composite_edit.XSize(590); m_composite_edit.YSize(590); //--- Coordenadas int x=10; int y=10; //--- m_composite_edit.CaptionHeight(22); m_composite_edit.IsMovable(true); m_composite_edit.CaptionColor(m_caption); m_composite_edit.CaptionColorLocked(m_caption); m_composite_edit.CaptionColorHover(m_caption); m_composite_edit.BackColor(m_background); m_composite_edit.FontSize(m_base_font_size); m_composite_edit.Font(m_base_font); m_composite_edit.WindowType(W_DIALOG); //--- Creando el formulario if(!m_composite_edit.CreateWindow(m_chart_id,m_subwin,caption_text,x,y)) return(false); return(true); }
Ahora añadimos su llamada al método principal de creación de la interfaz.
bool CProgram::CreateGUI(void) { //--- ChangeLanguage(); //--- Paso 1-3. Ventana para seleccionar 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(m_lang[39])) return(false); //--- Creando formulario de edición rápida if(!CreateFastEdit(m_lang[40])) return(false); //--- if(!CreateCompositeEdit(m_lang[46])) return(false); //--- Terminando la creación de GUI CWndEvents::CompletedGUI(); return(true); }
Puesto que la ventana creada es de diálogo, entonces debe abrirse cuando ocurre un determinado evento. En nuestro caso, cuando se pulsa el botón Add Composite Signal (Añadir señal compuesta). Para implementar eso, vamos a crear el método OpenCompositeSignalEditor(). Con fines demostrativos, por ahora vamos a escribir en su cuerpo sólo un comando: abrir la ventana de diálogo. Está claro que hay que invocarlo en el manejador de eventos.
void CProgram::OpenCompositeSignalEditor(long lparam) { if(lparam==m_add_signal[1].Id()) { m_composite_edit.OpenWindow(); } }
El resultado de la adición de la ventana de diálogo y su apertura con la pulsación en el botón se muestra en la imagen 7.
Fig. 7 Adición de la ventana de diálogo para crear señales compuestas
Ahora tenemos que llenar la ventana de diálogo con los controles de la interfaz mostrados en la imagen 2:
- Campo de edición del nombre de una nueva señal compuesta.
- Lista de señales simples disponibles para crear una señal compuesta.
- Interfaz para crear una regla lógica de una señal compuesta.
Vamos a añadir todos los métodos nuevos en la clase base CProgram, implementarlos en el archivo CSetWindow.mqh, y aplicarlos ahí mismo en el cuerpo del método de la ventana de diálogo creada. Primero, vamos a crear el método del campo de edición CreateCSignalName(), lo implementamos y añadimos a la ventana de diálogo.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::CreateCSignalName(CTextEdit &text_edit,const int x_gap,const int y_gap) { //--- Guardamos el puntero al control principal text_edit.MainPointer(m_composite_edit); //--- Propiedades text_edit.XSize(110); text_edit.YSize(24); text_edit.Font(m_base_font); text_edit.FontSize(m_base_font_size); text_edit.GetTextBoxPointer().XGap(110); text_edit.GetTextBoxPointer().XSize(200); text_edit.GetTextBoxPointer().DefaultTextColor(clrSilver); text_edit.GetTextBoxPointer().DefaultText(m_lang[45]); //--- Creamos el control if(!text_edit.CreateTextEdit(m_lang[45],x_gap,y_gap)) return(false); //--- Añadimos el objeto al array general de los grupos de objetos CWndContainer::AddToElementsArray(4,text_edit); return(true); }Ahora, usando el nuevo método CreateSimpleSignal(), vamos a crear el conjunto de botones con dos estados. Van a ejecutar la función de seleccionar señales simples para añadir a las células (fig. 2) durante la formación de una señal compuesta.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::CreateSimpleSignal(CButton &button,const int x_gap,const int y_gap) { //--- color baseclr=clrDodgerBlue; color pressed=clrCrimson; //--- Guardamos el puntero a la ventana button.MainPointer(m_composite_edit); //--- Establecemos las propiedades antes de la creación button.XSize(110); button.YSize(40); button.Font(m_base_font); button.FontSize(m_base_font_size); button.BackColor(baseclr); button.BackColorHover(baseclr); button.BackColorPressed(pressed); button.BackColorLocked(clrWhiteSmoke); button.BorderColor(baseclr); button.BorderColorHover(baseclr); button.BorderColorPressed(pressed); button.BorderColorLocked(clrWhiteSmoke); button.LabelColor(clrWhite); button.LabelColorPressed(clrWhite); button.LabelColorHover(clrWhite); button.LabelColorLocked(clrWhiteSmoke); button.IsCenterText(true); button.TwoState(true); //--- Creamos el control if(!button.CreateButton(" — ",x_gap,y_gap)) return(false); //--- Añadimos el puntero al control a la base CWndContainer::AddToElementsArray(4,button); return(true); }
A continuación, completamos el método CreateCompositeEdit() y comprobamos qué es lo que ha salido en esta fase.
.... //--- Nombre de la señal if(!CreateCSignalName(m_c_signal_name,10,40)) return(false); //--- Crear la lista de selección de señales simples for(int i=0; i<SIMPLE_SIGNALS; i++) { if(i<5) { if(!CreateSimpleSignal(m_simple_signal[i],10+115*i,90)) return(false); } else { if(!CreateSimpleSignal(m_simple_signal[i],10+115*(i-5),90+45)) return(false); } } ...
En la siguiente imagen vemos que los botones tienen dos estados: la señal simple que se usa (color rojo) y la señal que no se usa (color azul). Pero por ahora se trata de una plantilla.
Fig. 8 Plantilla para seleccionar señales comerciales simples
En el siguiente paso, vamos a aplicar la siguiente lógica a esta plantilla:
- Al pulsar el botón Add Composite Signal (Añadir señal compuesta), es necesario comprobar que haya por lo menos dos señales creadas.
- Además, es necesario que en la lista de señales simples haya como mínimo dos señales con el indicio de aplicación C (Composite) o B (Both).
- Si estas condiciones se cumplen, mostramos las señales simples disponibles para la formación de una señal compuesta respecto a la plantilla creada de botones conmutadores.
Para cumplir todas estas condiciones, vamos a crear el método de verificación especial LoadForCompositeSignal() que va a mostrar sólo las señales seleccionadas correctamente.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::LoadForCompositeSignal(void) { int cnt=0; //--- for(int i=0; i<SIMPLE_SIGNALS; i++) { m_simple_signal[i].IsLocked(true); m_simple_signal[i].Update(true); } //--- Verificación de la presencia de por lo menos de dos señales con el indicio correcto for(int i=0; i<SIMPLE_SIGNALS; i++) if(m_signal_label[i].IsVisible() && (m_signal_type[i].LabelText()=="C" || m_signal_type[i].LabelText()=="B")) cnt++; //--- if(cnt<2) return(false); else cnt=0; //--- for(int i=0; i<SIMPLE_SIGNALS; i++) { if(m_signal_label[i].IsVisible() && (m_signal_type[i].LabelText()=="C" || m_signal_type[i].LabelText()=="B")) { m_simple_signal[cnt].IsLocked(false); cnt++; } } //--- cnt=0; for(int i=0; i<SIMPLE_SIGNALS; i++) { if(m_signal_label[i].IsVisible() && (m_signal_type[i].LabelText()=="C" || m_signal_type[i].LabelText()=="B")) { m_simple_signal[cnt].LabelText(m_signal_label[i].LabelText()); m_simple_signal[cnt].Update(true); cnt++; } } return(true); }
Hay que aplicarlo en el método OpenCompositeSignalEditor(): creado anteriormente
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CProgram::OpenCompositeSignalEditor(long lparam) { if(lparam==m_add_signal[1].Id()) { if(!LoadForCompositeSignal()) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("!Hay que tener por lo menos dos señales simples para crear una compuesta!","Atención"); else MessageBox("You need at least two simple signals to create a composite!","Warning"); } else { m_composite_edit.X(m_add_signal[1].X()); m_composite_edit.Y(m_add_signal[1].Y()+40); m_composite_edit.OpenWindow(); //--- m_c_signal_name.SetValue(""); m_c_signal_name.Update(true); //--- Quitar la selección de señales for(int i=0; i<SIMPLE_SIGNALS; i++) { m_simple_signal[i].IsPressed(false); m_simple_signal[i].Update(true); } } } }
Como resultado, obtenemos (fig. 9) la selección correcta de señales que satisfacen las condiciones. Se ve que la señal simple con el nombre Test 2 no se selecciona para la creación de la señal compuesta porque tienen el indicio de aplicación S (Simple).
Fig. 9 Ejemplo de la selección de señales simples para agregarlas a la señal compuesta
Es el momento más oportuno para añadir el propio sistema de formación de una señal compuesta a base de las señales seleccionadas. Primero, vamos a crear los controles de la interfaz. Serán dos:
- Células. Tres células para colocar las señales simples seleccionadas.
- Operadores lógicos. Dos botones conmutadores con tres estados AND, OR y (OR).
Aquí tenemos que entender la diferencia entre dos operadores lógicos parecidos: OR y (OR). Se sabe que el operador lógico OR en aplicación con el operador AND puede interpretarse de maneras diferentes: si se usa en pareja, es decir, une tres expresiones. En la imagen 10, se muestra un ejemplo donde el resultado de la expresión lógica puede ser diferente dependiendo de las paréntesis.
Fig. 10 Diferencia del operador OR de (OR)
Añadimos las células y operadores lógicos a la ventana para generar y editar señales compuestas. Para crear las células, vamos a implementar el método CreateRuleSlot():
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::CreateRuleSlot(CButton &button,string text,const int x_gap,const int y_gap) { //--- //color baseclr=C'70,180,70'; color baseclr=clrLightSteelBlue; //--- Guardamos el puntero a la ventana button.MainPointer(m_composite_edit); //--- Establecemos las propiedades antes de la creación button.XSize(110); button.YSize(40); button.Font(m_base_font); button.FontSize(m_base_font_size); button.BackColor(baseclr); button.BackColorHover(baseclr); button.BackColorPressed(baseclr); button.BorderColor(baseclr); button.BorderColorHover(baseclr); button.BorderColorPressed(baseclr); 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(4,button); return(true); }
Creamos el método CreateRuleSelector() para los operadores lógicos:
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::CreateRuleSelector(CButton &button,const int x_gap,const int y_gap) { //--- color baseclr=C'75,190,240'; //--- Guardamos el puntero a la ventana button.MainPointer(m_composite_edit); //--- Establecemos las propiedades antes de la creación button.XSize(40); button.YSize(40); button.Font(m_base_font); button.FontSize(m_base_font_size); button.BackColor(baseclr); button.BackColorHover(baseclr); button.BackColorPressed(baseclr); button.BorderColor(baseclr); button.BorderColorHover(baseclr); button.BorderColorPressed(baseclr); button.LabelColor(clrWhite); button.LabelColorPressed(clrWhite); button.LabelColorHover(clrWhite); button.IsCenterText(true); //--- Creamos el control if(!button.CreateButton("AND",x_gap,y_gap)) return(false); //--- Añadimos el puntero al control a la base CWndContainer::AddToElementsArray(4,button); return(true); }
Añadimos ambos métodos para la implementación de la ventana de diálogo al método CreateCompositeEdit().
... //--- Encabezado Regla if(!CreateSetLabel1(m_c_set_header[0],10,90+45*2,"1."+m_lang[24])) return(false); //--- Crear células para generar señal compuesta for(int i=0; i<3; i++) if(!CreateRuleSlot(m_rule_element[i],"Slot "+string(i+1),57+10+173*i,int(90+45*2.5))) return(false); //--- Crear operadores lógicos for(int i=0; i<2; i++) if(!CreateRuleSelector(m_rule_selector[i],189+173*i,int(90+45*2.5))) return(false); ...
En esta fase, la parte visual de la sección para construir la lógica de la señal compuesta es la siguiente:
Fig. 11 Parte visual de la interfaz para formar la lógica de la señal compuesta
Para «animar» los conmutadores lógicos, introducimos el método LogicSwitch(), lo implementamos y lo añadimos al manejador de eventos.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CProgram::LogicSwitch(long lparam) { for(int i=0; i<2; i++) { if(lparam==m_rule_selector[i].Id()) { if(m_rule_selector[i].LabelText()=="OR") SetButtonParam(m_rule_selector[i],"(OR)",clrTomato); else if(m_rule_selector[i].LabelText()=="(OR)") SetButtonParam(m_rule_selector[i],"AND",C'75,190,240'); else if(m_rule_selector[i].LabelText()=="AND") SetButtonParam(m_rule_selector[i],"OR",clrOrangeRed); } } }
Ahora, pasamos a un momento importante relacionado con el sistema de llenado de las células visualizadas anteriormente. ¿Cómo podemos llenarlas en sucesión necesaria? Para eso, hay que seleccionar la primera señal de la lista de señales disponibles y pulsarla. La primera célula cambiará su color por el azul, además, se mostrará el nombre de la señal añadida. Cuando pulsemos la segunda señal, se llenará la segunda célula. No obstante, cuando las células están llenadas completa o parcialmente y nosotros volvemos a pulsar en una señal de la lista de señales simples que ya ha sido añadida a alguna célula, se librará precisamente aquella célula que estaba ocupada por esta señal. Para implementar este mecanismo, crearemos el método SignalSelection() y lo añadiremos al manejador de eventos:
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CProgram::SignalSelection(long lparam) { for(int i=0; i<SIMPLE_SIGNALS; i++) { if(lparam==m_simple_signal[i].Id()) { //--- Si la señal está seleccionada if(m_simple_signal[i].IsPressed()) { //--- Buscamos la primera célula libre for(int j=0; j<3; j++) { if(m_rule_element[j].BackColor()==clrLightSteelBlue) { SetButtonParam(m_rule_element[j],m_simple_signal[i].LabelText(),clrDodgerBlue); break; } } } //--- Si la señal no está seleccionada else { for(int j=0; j<3; j++) { if(m_rule_element[j].LabelText()==m_simple_signal[i].LabelText()) { SetButtonParam(m_rule_element[j],"Slot "+string(j+1),clrLightSteelBlue); break; } } } } } }
En la imagen 12 eso se ve más claramente en comparación con la descripción de arriba.
Fig. 12 Ejemplo de la construcción de una señal comercial compuesta a base de las simples
En la ventana de la señal compuesta, nos queda agregar herramientas para configurar su visualización en el monitoreo. Para ello, vamos a crear una sección compuesta de los elementos, igual como en la ventana de la creación de una señal simple. Consideramos el principio de la creación de un conjunto de ajustes en la segunda parte de esta serie de artículos. Por eso, no vamos a reiterarnos. El resultado final del aspecto exterior de la ventana para editar una señal compuesta se muestra en la figura 13.
Fig. 13 Versión final de la ventana para crear una señal compuesta
Después de terminar con la parte visual, tenemos que integrar el componente computacional en la aplicación. Para eso, es necesario disponer de un mecanismo para guardar y editar la señal compuesta. Una vez configurados todos los ajustes, como se observa en la imagen 13, pulsamos el botón Add (Añadir) y el conjunto establecido se guarda en el archivo. Por eso, vamos a agregar una función que permita conseguirlo. Vamos a llamarla SaveCompositeSignalSet() e implementar.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::SaveCompositeSignalSet(int index,bool first_save=true) { //--- if(first_save && !CheckCSignalNames(m_c_signal_name.GetValue())) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Este nombre ya se usa","Monitoreo de señales"); else MessageBox("This name is already in use","Signal Monitor"); return(false); } //--- int h=FileOpen("Signal Monitor\\c_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>COMPOSITE_SIGNALS-1) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("El número máximo de las señales no debe ser más de "+string(COMPOSITE_SIGNALS),"Monitoreo de señales"); else MessageBox("Maximum number of signals is "+string(COMPOSITE_SIGNALS),"Signal Monitor"); return(false); } //--- Guardar la selección //--- Nombre del indicador if(m_c_signal_name.GetValue()=="") { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Introduzca el nombre de la señal","Monitoreo de señales"); else MessageBox("Enter the Signal Name","Signal Monitor"); FileClose(h); return(false); } else if(StringLen(m_c_signal_name.GetValue())<3) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("El nombre de la señal debe tener más de 3 caracteres","Monitoreo de señales"); else MessageBox("Signal Name must be at least 3 letters","Signal Monitor"); FileClose(h); return(false); } else StringToCharArray(m_c_signal_name.GetValue(),m_c_signal_set[index].signal_name); //--- Valores de las células if(!CheckCorrectSlots(m_rule_element[0].LabelText(),m_rule_element[1].LabelText(),m_rule_element[2].LabelText())) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Regla incorrecta","Monitoreo de señales"); else MessageBox("Invalid rule","Signal Monitor"); FileClose(h); return(false); } StringToCharArray(m_rule_element[0].LabelText(),m_c_signal_set[index].slot_1); StringToCharArray(m_rule_element[1].LabelText(),m_c_signal_set[index].slot_2); StringToCharArray(m_rule_element[2].LabelText(),m_c_signal_set[index].slot_3); //--- Valor de operadores lógicos for(int i=0; i<2; i++) { if(m_rule_selector[i].LabelText()=="AND") m_c_signal_set[index].logics[i]=1; else if(m_rule_selector[i].LabelText()=="OR") m_c_signal_set[index].logics[i]=2; else if(m_rule_selector[i].LabelText()=="(OR)") m_c_signal_set[index].logics[i]=3; } //--- Valor de la etiqueta de texto StringToCharArray(StringSubstr(m_c_text_box.GetValue(),0,3),m_c_signal_set[index].label_value); //--- Color de la etiqueta de texto m_c_signal_set[index].label_color=m_c_color_button[0].CurrentColor(); //--- Color del fondo if(m_c_set_param[0].IsPressed()) m_c_signal_set[index].back_color=m_c_color_button[1].CurrentColor(); else m_c_signal_set[index].back_color=clrNONE; //--- Color del borde if(m_c_set_param[1].IsPressed()) m_c_signal_set[index].border_color=m_c_color_button[2].CurrentColor(); else m_c_signal_set[index].border_color=clrNONE; //--- Valor de la ayuda m_c_signal_set[index].tooltip=m_c_set_param[2].IsPressed(); if(m_c_signal_set[index].tooltip) StringToCharArray(m_c_tooltip_text.GetValue(),m_c_signal_set[index].tooltip_text); //--- Imagen seleccionada m_c_signal_set[index].image=m_c_set_param[3].IsPressed(); if(m_c_signal_set[index].image) m_c_signal_set[index].img_index=m_c_pictures_slider.GetRadioButtonsPointer().SelectedButtonIndex(); //--- FileWriteStruct(h,m_c_signal_set[index]); FileClose(h); Print("Configuración "+m_c_signal_name.GetValue()+" guardada con éxito"); //--- return(true); }
No obstante, si compilamos el proyecto ahora, aparecerán tres errores principales: es la falta de la variable m_c_signal_set y de dos métodos de verificación: CheckCSignalNames() y CheckCorrectSlots(). El tipo de la variable será la estructura nueva C_SIGNAL que ha sido creada para almacenar el conjunto de ajustes de la señal compuesta:
struct C_SIGNAL { uchar slot_1[50]; uchar slot_2[50]; uchar slot_3[50]; int logics[2]; uchar signal_name[50]; uchar label_value[10]; color label_color; color back_color; color border_color; bool tooltip; uchar tooltip_text[100]; bool image; int img_index; };
El método CheckCSignalNames() parece mucho al método CheckSignalNames() y también verifica si el nombre introducido es único. Mientras que el método nuevo CheckCorrectSlots() verifica la integridad y la corrección de la construcción lógica de la señal compuesta:
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::CheckCorrectSlots(string name1,string name2,string name3) { bool slot1=(name1=="Slot 1")?true:false; bool slot2=(name2=="Slot 2")?true:false; bool slot3=(name3=="Slot 3")?true:false; int cnt=0; //--- if(slot1) return(false); if(slot2 && !slot3) return(false); //--- if(!slot1) cnt++; if(!slot2) cnt++; if(!slot3) cnt++; if(cnt<2) return(false); return(true); }
Después de preparar la funcionalidad necesaria para crear una nueva señal compuesta, vamos a generar el método AddCompositeSignal():
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CProgram::AddCompositeSignal(long lparam) { if(lparam==m_c_new_signal.Id()) { if(m_c_number_signal<0) { if(SaveCompositeSignalSet(m_c_total_signals)) { m_composite_edit.CloseDialogBox(); if(m_c_total_signals<COMPOSITE_SIGNALS) { m_c_total_signals++; m_c_signal_label[m_c_total_signals-1].Show(); m_c_signal_label[m_c_total_signals-1].LabelText(m_c_signal_name.GetValue()); m_c_signal_label[m_c_total_signals-1].Update(true); m_c_signal_ind[m_c_total_signals-1].Show(); } else MessageBox("Maximum number of composite signals is "+string(COMPOSITE_SIGNALS),"Signal Monitor"); } } else { if(SaveCompositeSignalSet(m_c_number_signal)) { m_composite_edit.CloseDialogBox(); m_c_signal_label[m_c_number_signal].LabelText(m_c_signal_name.GetValue()); m_c_signal_label[m_c_number_signal].Update(true); } } } }
Antes de diseñar la funcionalidad para editar la señal comercial creada, hay que añadir el mecanismo para cargar el conjunto de configuraciones desde el archivo a la ventana de edición de la señal compuesta. Para eso, introducimos el método LoadCompositeSignalSet():
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::LoadCompositeSignalSet(int index) { int h=FileOpen("Signal Monitor\\c_signal_"+string(index)+".bin",FILE_READ|FILE_BIN); if(h==INVALID_HANDLE) { MessageBox("Configuración no encontrada","Signal Monitor"); return(false); } ZeroMemory(m_c_signal_set[index]); FileReadStruct(h,m_c_signal_set[index]); //--- Cargando el nombre del indicador m_c_signal_name.SetValue(CharArrayToString(m_c_signal_set[index].signal_name)); m_c_signal_name.GetTextBoxPointer().Update(true); //--- Valores de las células string slot_1=CharArrayToString(m_c_signal_set[index].slot_1); string slot_2=CharArrayToString(m_c_signal_set[index].slot_2); string slot_3=CharArrayToString(m_c_signal_set[index].slot_3); color back=clrDodgerBlue; if(slot_1=="Slot 1") back=clrLightSteelBlue; else back=clrDodgerBlue; SetButtonParam(m_rule_element[0],slot_1,back); if(slot_2=="Slot 2") back=clrLightSteelBlue; else back=clrDodgerBlue; SetButtonParam(m_rule_element[1],slot_2,back); if(slot_3=="Slot 3") back=clrLightSteelBlue; else back=clrDodgerBlue; SetButtonParam(m_rule_element[2],slot_3,back); //--- Valor de operadores lógicos for(int i=0; i<2; i++) { switch(m_c_signal_set[index].logics[i]) { case 1: SetButtonParam(m_rule_selector[i],"AND",C'75,190,240'); break; case 2: SetButtonParam(m_rule_selector[i],"(OR)",clrTomato); break; case 3: SetButtonParam(m_rule_selector[i],"OR",clrOrangeRed); break; default: break; } } //--- Cargando la etiqueta de texto m_c_text_box.ClearTextBox(); m_c_text_box.AddText(0,CharArrayToString(m_c_signal_set[index].label_value)); m_c_text_box.Update(true); //--- Cargando el color de la etiqueta de texto m_c_color_button[0].CurrentColor(m_c_signal_set[index].label_color); m_c_color_button[0].Update(true); //--- Cargando el color del fondo if(m_c_signal_set[index].back_color==clrNONE) { m_c_set_param[0].IsPressed(false); m_c_set_param[0].Update(true); m_c_color_button[1].IsLocked(true); m_c_color_button[1].GetButtonPointer().Update(true); } else { m_c_set_param[0].IsPressed(true); m_c_set_param[0].Update(true); m_c_color_button[1].IsLocked(false); m_c_color_button[1].CurrentColor(m_c_signal_set[index].back_color); m_c_color_button[1].GetButtonPointer().Update(true); } //--- Cargando el color del borde if(m_c_signal_set[index].border_color==clrNONE) { m_c_set_param[1].IsPressed(false); m_c_set_param[1].Update(true); m_c_color_button[2].IsLocked(true); m_c_color_button[2].GetButtonPointer().Update(true); } else { m_c_set_param[1].IsPressed(true); m_c_set_param[1].Update(true); m_c_color_button[2].IsLocked(false); m_c_color_button[2].CurrentColor(m_c_signal_set[index].border_color); m_c_color_button[2].GetButtonPointer().Update(true); } //--- Cargando el valor de la ayuda if(!m_c_signal_set[index].tooltip) { m_c_set_param[2].IsPressed(false); m_c_set_param[2].Update(true); m_c_tooltip_text.IsLocked(true); m_c_tooltip_text.Update(true); } else { m_set_param[2].IsPressed(true); m_set_param[2].Update(true); m_c_tooltip_text.IsLocked(false); m_c_tooltip_text.ClearTextBox(); m_c_tooltip_text.AddText(0,CharArrayToString(m_c_signal_set[index].tooltip_text)); m_c_tooltip_text.Update(true); } //--- Cargando la imagen if(!m_c_signal_set[index].image) { m_c_set_param[3].IsPressed(false); m_c_set_param[3].Update(true); m_c_pictures_slider.IsLocked(true); m_c_pictures_slider.GetRadioButtonsPointer().Update(true); } else { m_c_set_param[3].IsPressed(true); m_c_set_param[3].Update(true); m_c_pictures_slider.IsLocked(false); m_c_pictures_slider.GetRadioButtonsPointer().SelectButton(m_c_signal_set[index].img_index); m_c_pictures_slider.GetRadioButtonsPointer().Update(true); } //--- FileClose(h); return(true); }
Pues, ahora es el momento para añadir la posibilidad de editar la señal creada a través del método nuevo EditCompositeSignal():
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CProgram::EditCompositeSignal(long lparam) { for(int i=0; i<COMPOSITE_SIGNALS; i++) { if(lparam==m_c_signal_ind[i].Id()) { LoadCompositeSignalSet(i); m_c_new_signal.LabelText(m_lang[38]); m_c_new_signal.Update(true); m_composite_edit.OpenWindow(); m_c_number_signal=i; for(int j=0; j<SIMPLE_SIGNALS; j++) m_simple_signal[j].IsLocked(true); } } }
Bien, aquí terminamos la implementación informática de la creación, guardado, edición y la carga de la señal comercial compuesta. Nos queda realizar la última fase del desarrollo de la aplicación. Se trata de la adición de las señales compuestas creadas al propio monitoreo. Antes para eso se usaba el método SearchSignals(). Habrá que actualizarlo. A pesar de que la actualización de este método fue realizada de tal manera que el código tuviera la máxima sencillez en la comprensión y la claridad gracias a la introducción de los métodos auxiliares adicionales, hay que dividirlo en dos bloques lógicos: la búsqueda de señales simples y la búsqueda de señales compuestas. Vamos a analizar su primera parte (la búsqueda de señales simples), también prestaremos la atención en los cambios realizados en él.
//+------------------------------------------------------------------+ //| Búsqueda de señales | //+------------------------------------------------------------------+ bool CProgram::SearchSignals(void) { //--- Búsqueda del número de señales simples creadas int cnt1=0; for(int i=0; i<SIMPLE_SIGNALS; i++) { if(FileIsExist("Signal Monitor\\signal_"+string(i)+".bin")) cnt1++; } //--- Búsqueda de señales simples SIGNAL signal_set; ZeroMemory(signal_set); for(int i=0; i<cnt1; i++) { //--- Omitimos la señal si está definida sólo para las señales compuestas if(m_signal_type[i].LabelText()=="C") continue; //--- if(GetSimpleSignal(signal_set,i)) return(false); //--- for(int j=0; j<ArraySize(m_signal_button); j++) { //--- string sy=GetSymbol(j); ENUM_TIMEFRAMES tf=GetTimeframe(j); //--- if(!CheckTimeframe(tf,signal_set)) continue; //--- if(GetSignal(sy,tf,signal_set)) SetVisualSignal(signal_set,j); else SetDefaultVisual(j); } } .....
Debido a que han sido introducidos los indicios de aplicación de señales simples, hemos tenido que agregar la filtración de las señales simples que deben usarse solamente como una parte de una señal compuesta, y no como una señal separada autosuficiente. Además, hemos agregado el método GetSimpleSignal(), cuyo propósito consiste en obtener un conjunto de ajustes desde el archivo establecido para cada señal simple y grabarlos en una estructura. Otros dos métodos nuevos, SetVisualSignal() y SetDefaultVisual(), se encargarán de visualizar los ajustes visuales establecidos según la señal actual encontrada, y si no hay ninguna señal, devolverán la visualización del bloque de la señal por defecto. Si comparamos la implementación anterior y actual de la búsqueda de señales simples, veremos que la versión nueva es más intuitiva, siendo casi tres veces más pequeña.
Ahora iremos a la segunda parte del método de la búsqueda de señales que detecta las señales compuestas.
..... //--- Búsqueda de señales compuestas C_SIGNAL c_signal_set; ZeroMemory(c_signal_set); int cnt2=0; int signal_number[3]; ArrayInitialize(signal_number,-1); //--- Búsqueda del número de señales compuestas creadas for(int i=0; i<COMPOSITE_SIGNALS; i++) { if(FileIsExist("Signal Monitor\\c_signal_"+string(i)+".bin")) cnt2++; } //-- Salimos si no hay ninguna if(cnt2<1) return(true); //--- Buscar configuraciones con señales compuestas for(int i=0; i<cnt2; i++) { //--- int h=FileOpen("Signal Monitor\\c_signal_"+string(i)+".bin",FILE_READ|FILE_BIN); if(h==INVALID_HANDLE) { MessageBox("Configuración no encontrada","Signal Monitor"); return(false); } ZeroMemory(c_signal_set); FileReadStruct(h,c_signal_set); FileClose(h); //--- Buscamos señales simples usadas en la señal compuesta seleccionada (С o В) for(int m=0; m<cnt1; m++) { if(m_signal_type[m].LabelText()!="S") GetSimpleSignal(signal_set,m); //--- if(CharArrayToString(signal_set.signal_name)==CharArrayToString(c_signal_set.slot_1)) signal_number[0]=m; else if(CharArrayToString(signal_set.signal_name)==CharArrayToString(c_signal_set.slot_2)) signal_number[1]=m; else if(CharArrayToString(signal_set.signal_name)==CharArrayToString(c_signal_set.slot_3)) signal_number[2]=m; } ArrayPrint(signal_number); } //--- int used_slots=GetUsedSlots(CharArrayToString(c_signal_set.slot_1),CharArrayToString(c_signal_set.slot_2),CharArrayToString(c_signal_set.slot_3)); //--- for(int j=0; j<ArraySize(m_signal_button); j++) { //--- string sy=GetSymbol(j); ENUM_TIMEFRAMES tf=GetTimeframe(j); //--- GetSimpleSignal(signal_set,signal_number[0]); bool sig1=GetSignal(sy,tf,signal_set); GetSimpleSignal(signal_set,signal_number[1]); bool sig2=GetSignal(sy,tf,signal_set); if(used_slots==2) { //--- AND if(c_signal_set.logics[0]==1) if(sig1 && sig2) SetVisualCompositeSignal(c_signal_set,j); //--- OR if(c_signal_set.logics[0]>1) if(sig1 || sig2) SetVisualCompositeSignal(c_signal_set,j); } else if(used_slots==3) { GetSimpleSignal(signal_set,signal_number[2]); bool sig3=GetSignal(sy,tf,signal_set); //--- AND OR if(c_signal_set.logics[0]==1 && c_signal_set.logics[1]==2) { if((sig1 && sig2) || sig3) SetVisualCompositeSignal(c_signal_set,j); } //--- AND (OR) else if(c_signal_set.logics[0]==1 && c_signal_set.logics[1]==3) { if(sig1 && (sig2 || sig3)) SetVisualCompositeSignal(c_signal_set,j); } //--- OR AND else if(c_signal_set.logics[0]==2 && c_signal_set.logics[1]==1) { if(sig1 || (sig2 && sig3)) SetVisualCompositeSignal(c_signal_set,j); } //--- (OR) AND else if(c_signal_set.logics[0]==3 && c_signal_set.logics[1]==1) { if((sig1 || sig2) && sig3) SetVisualCompositeSignal(c_signal_set,j); } //--- AND AND else if(c_signal_set.logics[0]==1 && c_signal_set.logics[1]==1) { if(sig1 && sig2 && sig3) SetVisualCompositeSignal(c_signal_set,j); } //--- OR OR else if(c_signal_set.logics[0]>1 && c_signal_set.logics[1]>1) { if(sig1 || sig2 || sig3) SetVisualCompositeSignal(c_signal_set,j); } } } return(true); }
Durante la búsqueda de señales compuestas, también hemos necesitado usar métodos adicionales, tanto para las verificaciones, como para la visualización en el propio monitoreo. El primero de ellos es el método de la verificación GetUsedSlots() que detecta con qué tipo de la señal compuesta se ve el sistema de la búsqueda: se compone de dos o de tres señales simples.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CProgram::GetUsedSlots(string name1,string name2,string name3) { int cnt=0; if(name1!="Slot 1") cnt++; if(name2!="Slot 2") cnt++; if(name3!="Slot 3") cnt++; return(cnt); }
El segundo método, SetVisualCompositeSignal(), al encontrar una señal compuesta, la muestra en el bloque de la señal como un conjunto de ajustes visuales de la señal compuesta actual.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CProgram::SetVisualCompositeSignal(C_SIGNAL &signal_set,int block) { //--- SetLabel(block,CharArrayToString(signal_set.label_value),signal_set.label_color); //--- if(signal_set.back_color!=clrNONE) SetBackground(block,signal_set.back_color); //--- if(signal_set.border_color!=clrNONE) SetBorderColor(block,signal_set.border_color); else SetBorderColor(block,signal_set.back_color); //--- if(signal_set.tooltip) SetTooltip(block,CharArrayToString(signal_set.tooltip_text)); //--- if(signal_set.image) SetIcon(block,signal_set.img_index); else SetIcon(block,-1); }
Pues bien, hemos llegado al final del desarrollo de nuestro Monitoreo de las señales comerciales. Puede que en el futuro los usuarios tengan nuevas ideas o propongan sus sugerencias respecto a las posibles mejoras de la aplicación.
Artículos anteriores de esta serie:
- Monitoreo multidivisas de las señales comerciales (Parte 1): Desarrollando la estructura de la aplicación
- Monitoreo multidivisas de las señales comerciales (Parte 2): Implementando la parte visual
- Monitoreo multidivisas de las señales comerciales (Parte 3): Introduciendo los algoritmos para la búsqueda
- Monitoreo multidivisas de las señales comerciales (Parte 4): Mejorando la funcionalidad y el sistema de búsqueda de las señales
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 14.
Fig. 14 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/7759
- 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