Monitoreo multidivisas de las señales comerciales (Parte 5): Señales compuestas

Alexander Fedosov | 31 agosto, 2020

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:

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:

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:

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: 

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()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:

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