Monitoreo multidivisas de las señales comerciales (Parte 4): Mejorando la funcionalidad y el sistema de búsqueda de las señales

Alexander Fedosov | 6 abril, 2020

Contenido

Introducción

En la tercera parte, creamos el sistema básico de la búsqueda de señales comerciales, pero este sistema se basaba en una reducida gama de indicadores y en una elección lacónica de las reglas para la búsqueda. Además, los lectores manifestaron sus deseos y formularon las sugerencias respecto a la mejora de la usabilidad de la parte visual del monitoreo comercial. Por tanto, continuaremos desarrollando nuestra aplicación de acuerdo con los puntos listados en el Contenido.

Indicador personalizado para configurar la señal comercial

Un complemento evidente a la creación y edición de las señales comerciales es la ampliación de la posibilidad de elegir no sólo los indicadores del conjunto estándar de MetaTrader 5 que figuran en el monitoreo, sino también poder usar la parte computacional de sus propios indicadores. Por eso, será bastante lógico añadir esta posibilidad durante la creación de una señal comercial. Vamos a basarnos en el proyecto desde el artículo anterior (puede descargarlo al final del presente artículo). Merece la pena aclarar enseguida que al ampliar el proyecto de la tercera parte, algunos ajustes de los controles de la interfaz gráfica, así como, los algoritmos del funcionamiento de los métodos en la clase base, van a modificarse y completarse (se indicará en ello aparte).

En primer lugar, añadimos la posibilidad de elegir y usar un indicador personalizado en la ventana de añadir/editar la señal. Recordaré que la implementación de la interfaz de esta ventana se encuentra en el archivo SetWindow.mqh de nuestro proyecto. Lo abrimos y buscamos el método CreateIndicatorType(). Tenemos que modificar precisamente este método.

//+------------------------------------------------------------------+
//| Crea el menú desplegable con la selección del tipo del indicador |
//+------------------------------------------------------------------+
bool CProgram::CreateIndicatorType(const int x_gap,const int y_gap)
{
//--- Transferir el objeto del panel
   m_indicator_type.MainPointer(m_set_window);
//---
#define SIZE 10
//--- Array de valores de los elementos en la lista
   string pattern_names[SIZE]=
   {
      "ATR","CCI","DeMarker","Force Ind","WPR","RSI","Momentum","ADX","ADX Wilder","Custom"
   };
//--- Establecemos las propiedades antes de la creación
   m_indicator_type.XSize(200);
   m_indicator_type.YSize(26);
   m_indicator_type.LabelYGap(4);
   m_indicator_type.ItemsTotal(SIZE);
   m_indicator_type.Font(m_base_font);
   m_indicator_type.FontSize(m_base_font_size);
   m_indicator_type.BackColor(m_background);
   m_indicator_type.GetButtonPointer().Font(m_base_font);
   m_indicator_type.GetButtonPointer().FontSize(m_base_font_size);
   m_indicator_type.GetButtonPointer().BackColor(clrWhite);
   m_indicator_type.GetButtonPointer().XGap(100);
   m_indicator_type.GetButtonPointer().XSize(100);
   m_indicator_type.GetListViewPointer().Font(m_base_font);
   m_indicator_type.GetListViewPointer().FontSize(m_base_font_size);
   m_indicator_type.GetListViewPointer().ItemYSize(25);
   m_indicator_type.GetListViewPointer().YSize(200);
//--- Guardamos los valores de los elementos en la lista del cuadro combinado (combobox)
   for(int i=0; i<SIZE; i++)
      m_indicator_type.SetValue(i,pattern_names[i]);
//--- Obtenemos el puntero de la lista
   CListView *lv=m_indicator_type.GetListViewPointer();
//--- Establecemos las propiedades de la lista
   lv.LightsHover(true);
   m_indicator_type.SelectItem(1);
//--- Creamos el control
   if(!m_indicator_type.CreateComboBox("Indicator Type",x_gap,y_gap))
      return(false);
//--- Añadimos el objeto al array general de los grupos de objetos
   CWndContainer::AddToElementsArray(1,m_indicator_type);
   return(true);
}

Ahora vamos a analizar qué es lo que ha cambiado en comparación con la etapa anterior. Primero, ha sido añadida la macro sustitución SIZE que va a indicar en el número de los elementos en la lista desplegable de la selección. Es necesaria para tener la posibilidad de modificar este número en un solo lugar (sin tener que hacer estos cambios en todas las partes del código). Luego, de acuerdo con el nuevo tamaño, añadimos el nuevo elemento de la lista Custom al final. Podemos observar esta modificación en la Fig. 1.

Fig. 1 Adición del elemento de la selección del indicador personalizado.  

Ahora, hay que añadir nuevos elementos de la interfaz que van a servir para configurar el uso del indicador. Tenemos que ajustarnos a los argumentos de la función iCustom() para poder usar la parte de cálculo de nuestro indicador personalizado. Se trata del nombre del símbolo, período, ruta hacia el indicador compilado con la extensión *.ex5 y la lista de sus parámetros de entrada separados con coma.

int  iCustom(
   string           symbol,     // nombre del símbolo
   ENUM_TIMEFRAMES  period,     // período
   string           name        // carpeta/nombre_del_usuario del indicador
   ...                          // lista de parámetros de entrada del indicador
   );

El nombre del símbolo y el timeframe van a colocarse según los valores seleccionados durante los dos primeros pasos de la configuración primaria de la aplicación, mientras que la ruta hacia el indicador y la lista de los parámetros tendrán que definirse por el usuario. Para eso, hay que añadir dos campos de edición. Por tanto, añadimos dos nuevas variables y un método en la clase base CProgram para la implementación:

   CTextEdit         m_custom_path;
   CTextEdit         m_custom_param;

   bool              CreateCustomEdit(CTextEdit &text_edit,const int x_gap,const int y_gap,const string default_text);

Puesto que el método va a aplicarse en la ventana para crear/editar la señal comercial, lo vamos a implementar en el archivo SetWindow.mqh:

//+------------------------------------------------------------------+
//| Campo de edición para el indicador personalizado                 |
//+------------------------------------------------------------------+
bool CProgram::CreateCustomEdit(CTextEdit &text_edit,const int x_gap,const int y_gap,const string default_text)
{
//--- Guardamos el puntero al control principal
   text_edit.MainPointer(m_set_window);
//--- Propiedades
   text_edit.XSize(100);
   text_edit.YSize(24);
   text_edit.Font(m_base_font);
   text_edit.FontSize(m_base_font_size);
   text_edit.GetTextBoxPointer().AutoSelectionMode(true);
   text_edit.GetTextBoxPointer().XGap(1);
   text_edit.GetTextBoxPointer().XSize(325);
   text_edit.GetTextBoxPointer().DefaultTextColor(clrSilver);
   text_edit.GetTextBoxPointer().DefaultText(default_text);
   text_edit.GetTextBoxPointer().BorderColor(clrBlack);
//--- Creamos el control
   if(!text_edit.CreateTextEdit("",x_gap,y_gap))
      return(false);
   text_edit.IsLocked(true);
//--- Añadimos el objeto al array general de los grupos de objetos
   CWndContainer::AddToElementsArray(1,text_edit);
   return(true);
}

Usando el método CreateCustomEdit(), creamos dos campos de edición para nuestra tarea. Para eso, en el cuerpo del método CreateSetWindow() del mismo archivo, buscamos la sección Ajustes del indicador seleccionado y la completamos con el siguiente código:

   if(!CreateCustomEdit(m_custom_path,240,22+10+2*(25+10),"Enter the indicator path"))
      return(false);
   if(!CreateCustomEdit(m_custom_param,240,22+10+3*(25+10),"Enter indicator parameters separated by commas"))
      return(false);

Como resultado, dos campos para introducir los datos aparecen en la ventana de configuraciones (véase Fig. 2).

Fig. 2 Adición de los campos de edición para los ajustes del indicador personalizado.

No obstante, estarán inactivos en esta fase del desarrollo. Eso está relacionado con el hecho de que su disponibilidad tiene que depender de la selección del Tipo del indicador (Indicator type), es decir, estarán disponibles sólo si tenemos seleccionada la línea Custom en la lista desplegable. Por eso, para implementar esta tarea, accedemos al método RebuildParameters() y lo actualizamos. Pero para empezar, vamos a la sección Evento de la selección del elemento de la lista desplegable en el método OnEvent() y añadimos una verificación del evento para la lista necesaria de la selección del tipo del indicador.

//--- Selección de un elemento en la lista desplegable del combobox
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
   {
      //--- Tipo del indicador
      if(lparam==m_indicator_type.Id())
         RebuildParameters(m_indicator_type.GetListViewPointer().SelectedItemIndex());
   }

Ahora, vamos a modificar el método RebuildParameters() de tal manera que se muestren los ajustes actuales para cada uno de los indicadores disponibles que se seleccione, y que, además de eso, se queden activos los campos de edición de la ruta y los parámetros para nuestro indicador personalizado.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::RebuildParameters(int index)
{
   switch(index)
   {
   case  0:
      m_period_edit.LabelText("ATR Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  1:
      m_period_edit.LabelText("CCI Period");
      m_applied_price.Show();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  2:
      m_period_edit.LabelText("DeMarker Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  3:
      m_period_edit.LabelText("Force Index Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  4:
      m_period_edit.LabelText("WPR Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  5:
      m_period_edit.LabelText("RSI Period");
      m_applied_price.Show();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  6:
      m_period_edit.LabelText("Momentum Period");
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  7:
      m_period_edit.LabelText("ADX Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  8:
      m_period_edit.LabelText("ADXW Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  9:
      m_period_edit.LabelText("Buffer Number");
      m_applied_price.Hide();
      m_custom_param.IsLocked(false);
      m_custom_path.IsLocked(false);
      break;
   default:
      m_period_edit.LabelText("Ind Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   }
   m_period_edit.Update(true);
}

Si ahora compilamos el proyecto, obtenemos el siguiente resultado:

Fig. 3 Adición de los campos de edición para el indicador personalizado.

A continuación, tendremos que completar el Evento de la pulsación en el botón Añadir señal (Add Signal). Al pulsarlo, los valores de la selección y los ajustes del indicador se establecen por defecto.

      //--- Clic en el botón Añadir señal
      if(lparam==m_add_signal.Id())
      {
         if(m_total_signals>4)
         {
            MessageBox("Maximum number of signals is 5","Signal Monitor");
            return;
         }
         m_set_window.OpenWindow();
         RebuildParameters(1);
         m_number_signal=-1;
         RebuildTimeframes();
         m_new_signal.LabelText("Add");
         m_new_signal.Update(true);
         m_indicator_type.SelectItem(1);
         m_indicator_type.GetButtonPointer().Update(true);
      }

Antes de adaptar nuevos controles en forma de los campos de edición al algoritmo del guardado de los ajustes de la señal ya existente, vamos a ampliar el sistema de las reglas para la búsqueda de la señal comercial. Porque llevará a la adición de nuevos controles de la interfaz, y es contraproducente cambiar cada vez el algoritmo del guardado de los conjuntos al completar los ajustes en el sistema. Sería lógico añadir todos los controles y elementos de las configuraciones, y luego completar el método del guardado del conjunto de los ajustes para la búsqueda de la señal según el sistema.


Ampliación del sistema de las reglas para la búsqueda de la señal comercial

En este momento, el monitoreo comercial es capaz de crear señales a base de la desigualdad. Es decir, más, menos o igual al valor definido. No obstante, esta selección no siempre refleja lo que nos gustaría ver durante la búsqueda de la señal. Por ejemplo, al usar un indicador del tipo oscilador, a veces tiene sentido definir un determinado intervalo del valor, en vez de usar el límite superior o inferior. Pues, he decidido agregar esta posibilidad. Primero, hay que añadir un conmutador entre el sistema del ajuste de la regla de la búsqueda anterior y el sistema nuevo. Para eso, añadimos una nueva lista desplegable con dos tipos de la definición de la regla: Comparación (Compare) e Intervalo (Interval). 

Vamos a la clase base CProgram y añadimos una nueva variable, instancia de la clase CСombobox, y creamos el método que implementa el control de la interfaz:

CComboBox         m_rule_interval;

bool              CreateRuleInterval(const int x_gap,const int y_gap);

Añadimos la implementación del método al archivo SetWindow.mqh porque esta lista desplegable pertenece a la ventana de configuraciones.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateRuleInterval(const int x_gap,const int y_gap)
{
//--- Transferir el objeto del panel
   m_rule_interval.MainPointer(m_set_window);
//--- Array de valores de los elementos en la lista
   string pattern_names[2]=
   {
      "Compare","Interval",
   };
//--- Establecemos las propiedades antes de la creación
   m_rule_interval.XSize(160);
   m_rule_interval.YSize(26);
   m_rule_interval.LabelYGap(4);
   m_rule_interval.ItemsTotal(2);
   m_rule_interval.Font(m_base_font);
   m_rule_interval.FontSize(m_base_font_size);
   m_rule_interval.BackColor(m_background);
   m_rule_interval.GetButtonPointer().Font(m_base_font);
   m_rule_interval.GetButtonPointer().FontSize(m_base_font_size);
   m_rule_interval.GetButtonPointer().BackColor(clrWhite);
   m_rule_interval.GetButtonPointer().XGap(90);
   m_rule_interval.GetButtonPointer().XSize(80);
   m_rule_interval.GetListViewPointer().Font(m_base_font);
   m_rule_interval.GetListViewPointer().FontSize(m_base_font_size);
   m_rule_interval.GetListViewPointer().ItemYSize(26);
//--- Guardamos los valores de los elementos en la lista del cuadro combinado (combobox)
   for(int i=0; i<2; i++)
      m_rule_interval.SetValue(i,pattern_names[i]);
//--- Obtenemos el puntero de la lista
   CListView *lv=m_rule_interval.GetListViewPointer();
//--- Establecemos las propiedades de la lista
   lv.LightsHover(true);
   m_rule_interval.SelectItem(0);
//--- Creamos el control
   if(!m_rule_interval.CreateComboBox("Rule",x_gap,y_gap))
      return(false);
//--- Añadimos el objeto al array general de los grupos de objetos
   CWndContainer::AddToElementsArray(1,m_rule_interval);
   return(true);
}

Puesto que el nuevo modo de la definición de la regla Intervalo tiene que tener un límite inferior y superior, vamos a añadir un valor más aparte del campo de edición del valor numérico que ya existe. Lo que era antes, va a representar el límite superior del intervalo; y lo que añadimos, va a representar el límite inferior. Está claro que, a la hora de usar los indicadores, es necesario considerar qué valores se encuentran por debajo de cero, como en el caso del indicador WPR. Por tanto, en este caso, los conceptos del límite superior e inferior van a alternarse. Para no tener que crear un método separado de implementación del campo de edición para el límite inferior del intervalo, simplemente vamos a modificar la variable actual que se encarga del campo de edición existente, así como, el método CreateRule(). La variable se convertirá en un array:

CTextEdit         m_rule_value[2];

Dentro del método, añadimos un nuevo argumento que recibe la referencia a la instancia de la clase CTextEdit.

bool              CreateRuleValue(CTextEdit &text_edit,const int x_gap,const int y_gap);

Claro que también modificaremos la implementación de este método.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateRuleValue(CTextEdit &text_edit,const int x_gap,const int y_gap)
{
//--- Guardamos el puntero al control principal
   text_edit.MainPointer(m_set_window);
//--- Propiedades
   text_edit.XSize(80);
   text_edit.YSize(24);
   text_edit.GetTextBoxPointer().XGap(1);
   text_edit.LabelColor(C'0,100,255');
   text_edit.Font(m_base_font);
   text_edit.FontSize(m_base_font_size);
   text_edit.MaxValue(999);
   text_edit.StepValue(0.1);
   text_edit.MinValue(-999);
   text_edit.SetDigits(3);
   text_edit.SpinEditMode(true); 
//--- Creamos el control
   if(!text_edit.CreateTextEdit("",x_gap,y_gap))
      return(false);
   text_edit.SetValue(string(5));
   text_edit.GetTextBoxPointer().AutoSelectionMode(true);
//--- Añadimos el objeto al array general de los grupos de objetos
   CWndContainer::AddToElementsArray(1,text_edit);
   return(true);
}

Además, hay que modificar algunos valores del método ya existente CreateRule():

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateRule(const int x_gap,const int y_gap)
{
//--- Transferir el objeto del panel
   m_rule_type.MainPointer(m_set_window);
//--- Array de valores de los elementos en la lista
   string pattern_names[5]=
   {
      ">",">=","==","<","<="
   };
//--- Establecemos las propiedades antes de la creación
   m_rule_type.XSize(80);
   m_rule_type.YSize(26);
   m_rule_type.LabelYGap(4);
   m_rule_type.ItemsTotal(5);
   m_rule_type.Font(m_base_font);
   m_rule_type.FontSize(m_base_font_size);
   m_rule_type.BackColor(m_background);
   m_rule_type.GetButtonPointer().Font(m_base_font);
   m_rule_type.GetButtonPointer().FontSize(m_base_font_size);
   m_rule_type.GetButtonPointer().BackColor(clrWhite);
   m_rule_type.GetButtonPointer().XGap(1);
   m_rule_type.GetButtonPointer().XSize(80);
   m_rule_type.GetListViewPointer().Font(m_base_font);
   m_rule_type.GetListViewPointer().FontSize(m_base_font_size);
   m_rule_type.GetListViewPointer().ItemYSize(26);
//--- Guardamos los valores de los elementos en la lista del cuadro combinado (combobox)
   for(int i=0; i<5; i++)
      m_rule_type.SetValue(i,pattern_names[i]);
//--- Obtenemos el puntero de la lista
   CListView *lv=m_rule_type.GetListViewPointer();
//--- Establecemos las propiedades de la lista
   lv.LightsHover(true);
   m_rule_type.SelectItem(0);
//--- Creamos el control
   if(!m_rule_type.CreateComboBox("",x_gap,y_gap))
      return(false);
//--- Añadimos el objeto al array general de los grupos de objetos
   CWndContainer::AddToElementsArray(1,m_rule_type);
   return(true);
}

Ahora, se puede encontrar la sección Ajustes de condiciones en el método de la creación de la ventana de ajustes CreateSetWindow() y alterar su código con el siguiente:

//--- Ajustes de las condiciones
   if(!CreateRuleValue(m_rule_value[0],200,22+10+5*(25+10)))
      return(false);
   if(!CreateRuleValue(m_rule_value[1],300,22+10+5*(25+10)))
      return(false);
   if(!CreateRule(200,22+10+5*(25+10)))
      return(false);
   if(!CreateRuleInterval(10,22+10+5*(25+10)))
      return(false);

Esta modificación permitirá reajustar la posición de los controles existentes de la interfaz y añadir los nuevos. Si todo ha sido hecho correctamente, el resultado será como en la Fig. 4, pero todavía no funciona nada si tratamos de cambiar el modo de la regla de Compare a Interval. Habrá que corregirlo.

Fig. 4 Añadir la selección del modo de la búsqueda de señales.

Para eso, en el método OnEvent(), buscamos la sección responsable del Evento de la Selección de un elemento en la lista desplegable y añadimos el código que permite visualizar los controles correctos de la interfaz dependiendo de la selección del modo de las reglas.

//--- Selección de un elemento en la lista desplegable del combobox
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
   {
      ...
      //--- Tipo de la regla
      if(lparam==m_rule_interval.Id())
      {
         switch(m_rule_interval.GetListViewPointer().SelectedItemIndex())
         {
         case  0:
            m_rule_value[0].Hide();
            m_rule_type.Show();
            break;
         case  1:
            m_rule_value[0].Show();
            m_rule_type.Hide();
            break;
         default:
            break;
         }
      }
   }

La siguiente modificación se refiere al traslado de algunos eventos durante la carga de la interfaz en una sección separada del método OnEvent(). Para eso, vamos a crear la sección del eventoTerminar la creación de la interfaz y trasladar el código desde el método CreateGUI(). Como resultado, en este método se quedará el siguiente código:

//+------------------------------------------------------------------+
//| Crea la interfaz gráfica del programa                          |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//--- Cargando el idioma
   ChangeLanguage();
//--- Paso 1-3. Ventana de la selección de símbolos.
   if(!CreateStepWindow(m_lang[0]))
      return(false);
//---
   if(!CreateSetWindow(m_lang[17]))
      return(false);
//--- Creando el formulario 2 para la paleta de colores
   if(!CreateColorWindow("Color Picker"))
      return(false);
//--- Terminando la creación de GUI
   CWndEvents::CompletedGUI();
   return(true);
}

Es la nueva sección:

//--- Terminando la creación de la interfaz
   if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI)
   {
      m_back_button.Hide();
      m_add_signal.Hide();
      m_signal_header.Hide();
      m_label_button[1].IsPressed(true);
      m_label_button[1].Update(true);
      for(int i=0; i<5; i++)
         m_signal_editor[i].Hide();
      m_rule_value[0].Hide();
   }

Aquí, llamaré su atención a lo siguiente: al cargar la aplicación, ha sido añadida una acción nueva, es decir, ocultar el campo recientemente creado para introducir el límite inferior del interval.

Una vez formados los controles nuevos de la interfaz y los parámetros nuevos, se puede proceder a la modificación del algoritmo para cargar y guardar los conjuntos de los ajustes de las señales comerciales. Para eso, iremos al cuerpo del método SaveSignalSet() y lo adaptaremos según las modificaciones actuales.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::SaveSignalSet(int index)
{
//---
   int h=FileOpen("Signal Monitor\\signal_"+string(index)+".bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
        MessageBox("Fallo al crear el archivo de la configuración","Monitoreo de señales");
      else
         MessageBox("Failed to create configuration file","Signal Monitor");
      return(false);
   }
   if(index>4)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("El número máximo de las señales no debe ser más de 5","Monitoreo de señales");
      else
         MessageBox("Maximum number of signals is 5","Signal Monitor");
      return(false);
   }
//--- Guardar la selección
//--- Tipo del indicador
   m_signal_set[index].ind_type=m_indicator_type.GetListViewPointer().SelectedItemIndex();
 //--- Período del indicador
   if(m_signal_set[index].ind_type!=9)
   {
      m_signal_set[index].ind_period=(int)m_period_edit.GetValue();
       //--- Tipo del precio aplicado
      m_signal_set[index].app_price=m_applied_price.GetListViewPointer().SelectedItemIndex();
   }
   else
   {
      string path=m_custom_path.GetValue();
      string param=m_custom_param.GetValue();
      if(path=="")
      {
         if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
            MessageBox("Por favor, introduzca la ruta hacia el indicador","Monitoreo de señales");
         else
            MessageBox("Enter the indicator path","Signal Monitor");
         FileClose(h);
         return(false);
      }
      if(param=="")
      {
         if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
            MessageBox(" Por favor, introduzca los parámetros separándolos con coma","Monitoreo de señales");
         else
            MessageBox("Enter indicator parameters separated by commas","Signal Monitor");
         FileClose(h);
         return(false);
      }
      StringToCharArray(path,m_signal_set[index].custom_path);
      StringToCharArray(param,m_signal_set[index].custom_val);
      m_signal_set[index].ind_period=(int)m_period_edit.GetValue();
   }
//--- Tipo de la regla
   m_signal_set[index].rule_int=m_rule_interval.GetListViewPointer().SelectedItemIndex();
//--- Tipo de la comparación
   m_signal_set[index].rule_type=m_rule_type.GetListViewPointer().SelectedItemIndex();
//--- Valor de la regla
   m_signal_set[index].rule_value1=(double)m_rule_value[0].GetValue();
   m_signal_set[index].rule_value2=(double)m_rule_value[1].GetValue();
//--- Tipo de la visualización de la etiqueta de texto
   m_signal_set[index].label_type=m_label_button[0].IsPressed()?0:1;
//--- Guardamos el valor del campo de edición en caso del segundo tipo
   if(m_label_button[1].IsPressed())
      StringToCharArray(StringSubstr(m_text_box.GetValue(),0,3),m_signal_set[index].label_value);
//--- Color de la etiqueta de texto
   m_signal_set[index].label_color=m_color_button[0].CurrentColor();
//--- Color del fondo
   if(m_set_param[0].IsPressed())
      m_signal_set[index].back_color=m_color_button[1].CurrentColor();
   else
      m_signal_set[index].back_color=clrNONE;
//--- Color del borde
   if(m_set_param[1].IsPressed())
      m_signal_set[index].border_color=m_color_button[2].CurrentColor();
   else
      m_signal_set[index].border_color=clrNONE;
//--- Valor de la ayuda
   m_signal_set[index].tooltip=m_set_param[2].IsPressed();
   if(m_signal_set[index].tooltip)
      StringToCharArray(m_tooltip_text.GetValue(),m_signal_set[index].tooltip_text);
//--- Imagen seleccionada
   m_signal_set[index].image=m_set_param[3].IsPressed();
   if(m_signal_set[index].image)
      m_signal_set[index].img_index=m_pictures_slider.GetRadioButtonsPointer().SelectedButtonIndex();
//--- Timeframes seleccionados
   int tf=0;
   for(int i=0; i<21; i++)
   {
      if(!m_tf_button[i].IsLocked() && m_tf_button[i].IsPressed())
      {
         m_signal_set[index].timeframes[i]=true;
         StringToCharArray(m_tf_button[i].LabelText(),m_signal_set[index].tf_name[i].tf);
         tf++;
      }
      else
         m_signal_set[index].timeframes[i]=false;
   }
//---
   if(tf<1)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
        MessageBox(" No hay timeframes seleccionados","Monitoreo de señales");
      else
         MessageBox("No timeframes selected","Signal Monitor");
      FileClose(h);
      return(false);
   }
//---
   FileWriteStruct(h,m_signal_set[index]);
   FileClose(h);
   Print(" La configuración signal_"+string(index)+" ha sido guardada con éxito");
//---
   return(true);
}

Aquí hay muchas modificaciones, así que es necesario analizar las más importantes. El primer cambio es la verificación de que si está seleccionado un indicador estándar o personalizado antes del guardado. Al seleccionar un indicador personalizado, escribiremos un algoritmo del guardado de la ruta hacia el indicador y sus parámetros, así como, el valor en el campo de edición del período, pero en caso del indicador personalizado este campo nos sirve para obtener el número del búfer del indicador.

Dado que hemos cambiado el número de parámetros guardados, hay que reflejar eso en la estructura SIGNAL que sirve del elemento a través del cual todo se guarda en un archivo binario. Por eso, vamos a completarla con nuevas variables:

struct SIGNAL
{
   int               ind_type;
   int               ind_period;
   int               app_price;
   int               rule_int;
   int               rule_type;
   double            rule_value1;
   double            rule_value2;
   int               label_type;
   uchar             label_value[10];
   color             label_color;
   color             back_color;
   color             border_color;
   bool              tooltip;
   uchar             tooltip_text[100];
   bool              image;
   int               img_index;
   bool              timeframes[21];
   TFNAME            tf_name[21];
   uchar             custom_path[100];
   uchar             custom_val[100];
};

Ahora, el campo de edición del valor del límite durante la comparación rule_value se sustituye por rule_value 1 y rule_value2 para los campos del límite inferior y superior en el modo Intervalo. Además, han sido añadidos custom_path y custom_val para almacenar datos sobre la ruta del indicador personalizado y sus parámetros. Por tanto, también hay que modificar el método de la carga del conjunto de los parámetros de la señal comercial desde el archivo LoadSignalSet():

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::LoadSignalSet(int index)
{
   int h=FileOpen("Signal Monitor\\signal_"+string(index)+".bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      MessageBox("Configuración no encontrada","Signal Monitor");
      return(false);
   }
   ZeroMemory(m_signal_set[index]);
   FileReadStruct(h,m_signal_set[index]);
//--- Cargando el tipo del indicador
   m_indicator_type.SelectItem(m_signal_set[index].ind_type);
   RebuildParameters(m_signal_set[index].ind_type);
   m_indicator_type.GetButtonPointer().Update(true);
   if(m_signal_set[index].ind_type!=9)
   {
      //--- Cargando el período del indicador
      m_period_edit.SetValue((string)m_signal_set[index].ind_period);
      m_period_edit.GetTextBoxPointer().Update(true);
      //--- Cargando el precio aplicado, si hay
      if(!m_applied_price.IsLocked())
      {
         m_applied_price.SelectItem(m_signal_set[index].app_price);
         m_applied_price.GetButtonPointer().Update(true);
      }
   }
   else
   {
      m_period_edit.SetValue((string)m_signal_set[index].ind_period);
      m_custom_path.SetValue(CharArrayToString(m_signal_set[index].custom_path));
      m_custom_param.SetValue(CharArrayToString(m_signal_set[index].custom_val));
      m_custom_path.GetTextBoxPointer().Update(true);
      m_custom_param.GetTextBoxPointer().Update(true);
   }
//--- Cargando la regla de la señal
   m_rule_interval.SelectItem(m_signal_set[index].rule_int);
   m_rule_interval.GetButtonPointer().Update(true);
   m_rule_type.SelectItem(m_signal_set[index].rule_type);
   m_rule_type.GetButtonPointer().Update(true);
   m_rule_value[0].SetValue((string)m_signal_set[index].rule_value1);
   m_rule_value[0].GetTextBoxPointer().Update(true);
   m_rule_value[1].SetValue((string)m_signal_set[index].rule_value2);
   m_rule_value[1].GetTextBoxPointer().Update(true);
//--- Cargando la etiqueta de texto
   if(m_signal_set[index].label_type==0)
   {
      m_label_button[0].IsPressed(true);
      m_label_button[0].Update(true);
      m_label_button[1].IsPressed(false);
      m_label_button[1].Update(true);
      m_text_box.IsLocked(true);
   }
   else
   {
      m_label_button[0].IsPressed(false);
      m_label_button[0].Update(true);
      m_label_button[1].IsPressed(true);
      m_label_button[1].Update(true);
      m_text_box.IsLocked(false);
      m_text_box.ClearTextBox();
      m_text_box.AddText(0,CharArrayToString(m_signal_set[index].label_value));
      m_text_box.Update(true);
   }
//--- Cargando el color de la etiqueta de texto
   m_color_button[0].CurrentColor(m_signal_set[index].label_color);
   m_color_button[0].Update(true);
//--- Cargando el color del fondo
   if(m_signal_set[index].back_color==clrNONE)
   {
      m_set_param[0].IsPressed(false);
      m_set_param[0].Update(true);
      m_color_button[1].IsLocked(true);
      m_color_button[1].GetButtonPointer().Update(true);
   }
   else
   {
      m_set_param[0].IsPressed(true);
      m_set_param[0].Update(true);
      m_color_button[1].IsLocked(false);
      m_color_button[1].CurrentColor(m_signal_set[index].back_color);
      m_color_button[1].GetButtonPointer().Update(true);
   }
//--- Cargando el color del borde
   if(m_signal_set[index].border_color==clrNONE)
   {
      m_set_param[1].IsPressed(false);
      m_set_param[1].Update(true);
      m_color_button[2].IsLocked(true);
      m_color_button[2].GetButtonPointer().Update(true);
   }
   else
   {
      m_set_param[1].IsPressed(true);
      m_set_param[1].Update(true);
      m_color_button[2].IsLocked(false);
      m_color_button[2].CurrentColor(m_signal_set[index].border_color);
      m_color_button[2].GetButtonPointer().Update(true);
   }
//--- Cargando el valor de la ayuda
   if(!m_signal_set[index].tooltip)
   {
      m_set_param[2].IsPressed(false);
      m_set_param[2].Update(true);
      m_tooltip_text.IsLocked(true);
      m_tooltip_text.Update(true);
   }
   else
   {
      m_set_param[2].IsPressed(true);
      m_set_param[2].Update(true);
      m_tooltip_text.IsLocked(false);
      m_tooltip_text.ClearTextBox();
      m_tooltip_text.AddText(0,CharArrayToString(m_signal_set[index].tooltip_text));
      m_tooltip_text.Update(true);
   }
//--- Cargando la imagen
   if(!m_signal_set[index].image)
   {
      m_set_param[3].IsPressed(false);
      m_set_param[3].Update(true);
      m_pictures_slider.IsLocked(true);
      m_pictures_slider.GetRadioButtonsPointer().Update(true);
   }
   else
   {
      m_set_param[3].IsPressed(true);
      m_set_param[3].Update(true);
      m_pictures_slider.IsLocked(false);
      m_pictures_slider.GetRadioButtonsPointer().SelectButton(m_signal_set[index].img_index);
      m_pictures_slider.GetRadioButtonsPointer().Update(true);
   }
//--- Cargando timeframes seleccionados
   for(int i=0; i<21; i++)
   {
      if(!m_tf_button[i].IsLocked())
      {
         m_tf_button[i].IsPressed(m_signal_set[index].timeframes[i]);
         m_tf_button[i].Update(true);
      }
   }
//---
   FileClose(h);
   return(true);
}

Aquí, igual como en el método del guardado, se verifica si ha sido seleccionado un indicador estándar de la lista o un indicador personalizado, y dependiendo de eso se cargan los datos necesarios en la interfaz de la ventana de ajustes para la edición.

Después de configurar el guardado y la carga del conjunto de configuraciones de la señal comercial, podemos proceder a completar el algoritmo de la búsqueda de la señal. Para eso, vamos al método GetSignal(), buscamos la sección del código Verificar condiciones y la reemplazamos por lo siguiente:

//--- Verificando condiciones
   int s=0;
   if(signal_set.rule_int==0)
   {
      double r_value=signal_set.rule_value2;
      double c_value=val[0];
      m_ind_value=c_value;
      switch(signal_set.rule_type)
      {
      case  0:
         if(c_value>r_value)
            s=1;
         break;
      case  1:
         if(c_value>=r_value)
            s=1;
         break;
      case  2:
         if(c_value==r_value)
            s=1;
         break;
      case  3:
         if(c_value<r_value)
            s=1;
         break;
      case  4:
         if(c_value<=r_value)
            s=1;
         break;
      default:
         s=0;
         break;
      }
   }
   else if(signal_set.rule_int==1)
   {
      double r_value_min=signal_set.rule_value1;
      double r_value_max=signal_set.rule_value2;
      double c_value=val[0];
      m_ind_value=c_value;
      if(c_value>=r_value_min && c_value<=r_value_max)
         s=1;
   }

Además, incluimos los indicadores agregados en la sección Obteniendo el handle del indicador seleccionado:

//--- Obteniendo el handle del indicador seleccionado
   string str[],name;
   double arr[];
   switch(signal_set.ind_type)
   {
   case  0:
      h=iATR(sy,tf,signal_set.ind_period);
      break;
   case  1:
      h=iCCI(sy,tf,signal_set.ind_period,app_price);
      break;
   case  2:
      h=iDeMarker(sy,tf,signal_set.ind_period);
      break;
   case  3:
      h=iForce(sy,tf,signal_set.ind_period,MODE_SMA,VOLUME_TICK);
      break;
   case  4:
      h=iWPR(sy,tf,signal_set.ind_period);
      break;
   case  5:
      h=iRSI(sy,tf,signal_set.ind_period,app_price);
      break;
   case  6:
      h=iMomentum(sy,tf,signal_set.ind_period,app_price);
      break;
   case  7:
      h=iADX(sy,tf,signal_set.ind_period);
      break;
   case  8:
      h=iADXWilder(sy,tf,signal_set.ind_period);
      break;
   case  9:
      StringSplit(m_custom_param.GetValue(),StringGetCharacter(",",0),str);
      ArrayResize(arr,ArraySize(str));
      for(int i=0; i<ArraySize(str); i++)
         arr[i]=StringToDouble(str[i]);
      name=m_custom_path.GetValue();
      h=GetCustomValue(tf,name,arr);
      break;
   default:
      break;
   }

Ahora, este bloque contiene la verificación del tipo del modo de la búsqueda (Comparación o Intervalo), y dependiendo de eso, su propio método para verificar la correspondencia del valor del indicador a las condiciones establecidas.


Transformación de la lista de símbolos en forma de tabla

La visualización de la posibilidad de seleccionar los símbolos en forma del nombre y casilla de verificación (chekbox) al lado era bastante suficiente para una cantidad no demasiado grande de los mismos en la cuenta comercial. Sin embargo, si hay más de cien símbolos, se puede observar un aumento considerable de la altura de la ventana de la aplicación, y como podemos recordar, ella está vinculada a la cantidad de las líneas con los símbolos y se redimensiona en función de ello. Por tanto, se ha decidido sustituir esta forma de la representación de los símbolos por una visualización en forma de la tabla. La ventaja de esta sustitución consiste en lo siguiente: si hay muchos símbolos en la cuenta comercial, todos los símbolos que se encuentran en las líneas que exceden la altura de la tabla se ocultan, y una barra de deslizamiento aparece a la derecha. Pero no se puede eliminar los checkbox por completo, porque también se utilizan al seleccionar los timeframes de trabajo. Por esa razón, tenemos que resolver algunos problemas más:

Primero, tenemos que quitar la visualización de los checkbox antiguos en el primer paso de la configuración de la aplicación. Para eso, los ocultamos en el evento Terminando la creación de la interfaz. Ahora, ya sabemos que el número de los checkbox siempre se queda inalterable y es igual a 21, es decir, a la cantidad total de los timeframes disponibles en el terminal. Por eso, convertimos el array dinámico m_checkbox[] en estático con el tamaño 21.

      //--- Ocultando los checkbox de los timeframes
      for(int i=0; i<21; i++)
         m_checkbox[i].Hide();

No olvidemos que el modo de la creación de los checkbox también tiene que ser corregido debido a su destinación cambiada. Por eso, entramos en el cuerpo del método CreateStepWindow() y reemplazamos la sección Checkbox por la siguiente:

//--- Checkbox
   int k=0;
   string timeframe_names[21]=
   {
      "M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30",
      "H1","H2","H3","H4","H6","H8","H12","D1","W1","MN"
   };
   for(int j=0; j<=3; j++)
   {
      for(int i=0; i<7; i++)
      {
         if(k<21)
            if(!CreateCheckBox(m_checkbox[k],10+80*i,m_step_window.CaptionHeight()+70+j*25,timeframe_names[k]))
               return(false);
         k++;
      }
   }

También quitamos la línea del cálculo de la altura de la ventana basándose en la cantidad de los símbolos en la Observación del mercado.

m_step_window.ChangeWindowHeight(m_checkbox[m_all_symbols-1].YGap()+30+30);

Hacemos que la altura de la ventana sea estática:

m_step_window.YSize(500);

Ahora, tenemos que crear un objeto base de la tabla para su llenado posterior con los datos desde la Observación del mercado. Para ello, creamos una instancia de la clase CTable y el método de la implementación de la tabla.

//--- Tabla dibujada
   CTable            m_table;

   bool              CreateTable(const int x_gap,const int y_gap);

Lo implementamos en el archivo de la ventana principal StepWindow.mqh:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateTable(const int x_gap,const int y_gap)
{
#define COLUMNS1_TOTAL 7
#define ROWS1_TOTAL int(MathCeil(m_all_symbols/7))
//--- Guardamos el puntero al control principal
   m_table.MainPointer(m_step_window);
//--- Array del ancho de columnas
   int width[COLUMNS1_TOTAL];
   ::ArrayInitialize(width,80);
//--- Array del margen del texto en las columnas por el eje X
   int text_x_offset[COLUMNS1_TOTAL];
   ::ArrayInitialize(text_x_offset,25);
//--- Array de alineación del texto dentro las columnas
   ENUM_ALIGN_MODE align[COLUMNS1_TOTAL];
   ::ArrayInitialize(align,ALIGN_LEFT);
//--- Array del margen de las imágenes en las columnas por el eje X
   int image_x_offset[COLUMNS1_TOTAL];
   ::ArrayInitialize(image_x_offset,5);
//--- Array del margen de las imágenes en las columnas por el eje Y
   int image_y_offset[COLUMNS1_TOTAL];
   ::ArrayInitialize(image_y_offset,4);
//--- Propiedades
   m_table.XSize(560);
   m_table.YSize(190);
   m_table.Font(m_base_font);
   m_table.FontSize(m_base_font_size);
   m_table.CellYSize(20);
   m_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
   m_table.TextAlign(align);
   m_table.ColumnsWidth(width);
   m_table.TextXOffset(text_x_offset);
   m_table.ImageXOffset(image_x_offset);
   m_table.ImageYOffset(image_y_offset);
   m_table.LabelXGap(5);
   m_table.LabelYGap(4);
   m_table.IconXGap(7);
   m_table.IconYGap(4);
   m_table.MinColumnWidth(0);
   m_table.LightsHover(true);
   m_table.SelectableRow(false);
   m_table.IsWithoutDeselect(false);
   m_table.ColumnResizeMode(true);
   m_table.IsZebraFormatRows(clrWhiteSmoke);
   m_table.AutoXResizeMode(true);
   m_table.AutoXResizeRightOffset(10);
   m_table.AutoYResizeMode(true);
   m_table.AutoYResizeBottomOffset(50);
//--- Llenamos la tabla con datos
   InitializingTable();
//--- Creamos el control
   if(!m_table.CreateTable(x_gap,y_gap))
      return(false);
//--- Añadimos el objeto al array general de los grupos de objetos
   CWndContainer::AddToElementsArray(0,m_table);
   return(true);
}

Pero antes de usar este método para crear la tabla, hay que llenarla con datos, a saber, con la lista de todos los símbolos de la Observación del mercado para la cuenta actual. El método InitializingTable() será responsable de ello; lo añadimos a la sección privada de la clase base y lo implementamos:

//+------------------------------------------------------------------+
//| Inicialización de la tabla                                            |
//+------------------------------------------------------------------+
void CProgram::InitializingTable(void)
{
//--- Array de imágenes 1
   string image_array1[2]=
   {
      "Images\\EasyAndFastGUI\\Controls\\checkbox_off.bmp",
      "Images\\EasyAndFastGUI\\Controls\\checkbox_on_g.bmp"
   };
//---
   int k=0;
   for(int c=0; c<COLUMNS1_TOTAL; c++)
   {
      //---
      for(int r=0; r<ROWS1_TOTAL; r++)
      {
         if(k<m_all_symbols)
         {
            //--- Establecemos el tipo de la celda como Checkbox
            m_table.CellType(c,r,CELL_CHECKBOX);
            m_table.SetImages(c,r,image_array1);
            //--- Definimos el texto
            m_table.SetValue(c,r,SymbolName(k,false));
         }
         k++;
      }
   }
}

Ahora, usamos las técnicas obtenidas en el cuerpo del método CreateStepWindow() para crear y llenar la tabla. Como resultado, obtenemos una lista completa de todos los símbolos disponibles en la Observación del mercado (Fig. 5), y al mismo tiempo, con el mismo tipo de la selección a través del chekbox, pero en forma de la tabla.

Fig. 5 Resultado de la transformación de la lista de símbolos en forma de tabla.

El siguiente paso consiste en vincular la tabla recién creada con las mismas interacciones que había con el conjunto anterior de los checkbox. Es decir, para que se pueda:

Para implementar el primer punto, es necesario sustituir las secciones con los nombres de las plantillas por el siguiente código en el cuerpo del método OnEvent():

//--- All
         if(lparam==m_currency_set[0].Id() && m_currency_set[0].IsPressed())
         {
            m_currency_set[1].IsPressed(false);
            m_currency_set[2].IsPressed(false);
            m_currency_set[1].Update(true);
            m_currency_set[2].Update(true);
            //---
            int k=0;
            for(int c=0; c<7; c++)
            {
               //---
               for(int r=0; r<MathCeil(m_all_symbols/7); r++)
               {
                  if(k<m_all_symbols)
                     m_table.ChangeImage(c,r,1);
                  k++;
               }
            }
            m_table.Update(true);
         }
         //--- Majors
         else if(lparam==m_currency_set[1].Id() && m_currency_set[1].IsPressed())
         {
            m_currency_set[0].IsPressed(false);
            m_currency_set[2].IsPressed(false);
            m_currency_set[0].Update(true);
            m_currency_set[2].Update(true);
            //---
            string pairs[4]= {"EURUSD","GBPUSD","USDCHF","USDJPY"};
            //--- Quitar la selección
            int k=0;
            for(int c=0; c<7; c++)
            {
               //---
               for(int r=0; r<MathCeil(m_all_symbols/7); r++)
               {
                  if(k<m_all_symbols)
                     m_table.ChangeImage(c,r,0);
                  k++;
               }
            }
            //---
            k=0;
            for(int c=0; c<7; c++)
            {
               //---
               for(int r=0; r<MathCeil(m_all_symbols/7); r++)
               {
                  if(k<m_all_symbols)
                  {
                     for(int j=0; j<4; j++)
                     {
                        if(m_table.GetValue(c,r)==pairs[j])
                           m_table.ChangeImage(c,r,1);
                     }
                  }
                  k++;
               }
            }
            m_table.Update(true);
         }
         //--- Crosses
         else if(lparam==m_currency_set[2].Id() && m_currency_set[2].IsPressed())
         {
            m_currency_set[0].IsPressed(false);
            m_currency_set[1].IsPressed(false);
            m_currency_set[0].Update(true);
            m_currency_set[1].Update(true);
            //---
            string pairs[20]=
            {
               "EURUSD","GBPUSD","USDCHF","USDJPY","USDCAD","AUDUSD","AUDNZD","AUDCAD","AUDCHF","AUDJPY",
               "CHFJPY","EURGBP","EURAUD","EURCHF","EURJPY","EURCAD","EURNZD","GBPCHF","GBPJPY","CADCHF"
            };
            //--- Quitar la selección
            int k=0;
            for(int c=0; c<7; c++)
            {
               //---
               for(int r=0; r<MathCeil(m_all_symbols/7); r++)
               {
                  if(k<m_all_symbols)
                     m_table.ChangeImage(c,r,0);
                  k++;
               }
            }
            //---
            k=0;
            for(int c=0; c<7; c++)
            {
               //---
               for(int r=0; r<MathCeil(m_all_symbols/7); r++)
               {
                  if(k<m_all_symbols)
                  {
                     for(int j=0; j<20; j++)
                     {
                        if(m_table.GetValue(c,r)==pairs[j])
                           m_table.ChangeImage(c,r,1);
                     }
                  }
                  k++;
               }
            }
            m_table.Update(true);
         }
         //---
         if((lparam==m_currency_set[0].Id() && !m_currency_set[0].IsPressed())      ||
               (lparam==m_currency_set[1].Id() && !m_currency_set[1].IsPressed())   ||
               (lparam==m_currency_set[2].Id() && !m_currency_set[2].IsPressed())
           )
         {
            //--- Quitar la selección
            int k=0;
            for(int c=0; c<7; c++)
            {
               //---
               for(int r=0; r<MathCeil(m_all_symbols/7); r++)
               {
                  if(k<m_all_symbols)
                     m_table.ChangeImage(c,r,0);
                  k++;
               }
            }
            m_table.Update(true);
         }

Para guardar y cargar los conjuntos seleccionados de los símbolos, en nuestra aplicación se usaban los métodos SaveSymbolSet() y LoadSymbolSet(), respectivamente. Por eso, precisamente dentro de ellos es necesario corregir aquella parte del código donde los datos se cogen de la lista de los checkbox por los datos desde la tabla recién creada, y luego también se cargan en ella.

//+------------------------------------------------------------------+
//| Guardando la plantilla en el archivo                             |
//+------------------------------------------------------------------+
bool CProgram::SaveSymbolSet(string file_name)
{
   if(file_name=="")
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
        MessageBox("Por favor, elija el nombre de la plantilla para el guardado","Monitoreo de señales");
      else
         MessageBox("Choose a name for the template to save","Signal Monitor");
      return(false);
   }
   int h=FileOpen("Signal Monitor\\"+file_name+".bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
        MessageBox("Fallo al crear el archivo de la configuración","Monitoreo de señales");
      else
         MessageBox("Failed to create configuration file","Signal Mo
nitor");
      return(false);
   }
   else
      MessageBox("La configuración "+file_name+" ha sido guardada con éxito","Signal Monitor");
//--- Guardar la selección de símbolos
   int k=0;
   for(int c=0; c<7; c++)
   {
      //---
      for(int r=0; r<MathCeil(m_all_symbols/7); r++)
      {
         if(k<m_all_symbols)
            m_save.tf[k]=m_table.SelectedImageIndex(c,r)>0?true:false;
         k++;
      }
   }
//---
   FileWriteStruct(h,m_save);
   FileClose(h);
//---
   return(true);
}
//+------------------------------------------------------------------+
//| Cargando los datos en el panel                                   |
//+------------------------------------------------------------------+
bool CProgram::LoadSymbolSet(string file_name)
{
   if(file_name=="")
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
        MessageBox("Por favor, elija el nombre de la plantilla para la carga","Monitoreo de señales");
      else
         MessageBox("Choose a name for the template to load","Signal Monitor");
      return(false);
   }
   int h=FileOpen("Signal Monitor\\"+file_name+".bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      MessageBox("La configuración "+file_name+" no ha sido encontrada","Signal Monitor");
      return(false);
   }
   ZeroMemory(m_save);
   FileReadStruct(h,m_save);
//--- Cargar la selección de símbolos
   int k=0;
   for(int c=0; c<7; c++)
   {
      //---
      for(int r=0; r<MathCeil(m_all_symbols/7); r++)
      {
         if(k<m_all_symbols)
         {
            if(m_save.tf[k])
               m_table.ChangeImage(c,r,1);
            else
               m_table.ChangeImage(c,r,0);
         }
         k++;
      }
   }
   m_table.Update(true);
//---
   FileClose(h);
//---
   return(true);
}

Ahora, marcaremos el bloque donde se realiza la recopilación de los datos y su memorización en la estructura que será guardada en el archivo. Así como, la carga de los datos desde el archivo en la estructura y su traspaso posterior a la tabla.

Después de todos los cambios y adiciones, el resultado es la transformación visual que se observa en la Fig. 6. Pero el objetivo principal era proporcionar la posibilidad de trabajar más cómodamente con un gran número de símbolos que no cabían en la ventana de la aplicación en cuanto a la altura.

Fig. 6 Resultado de la adición de la tabla y sus interacciones con los controles de la interfaz.

A continuación, tenemos que editar los métodos del traspaso entre el Paso 1 y el Paso 2. Es que hemos cambiado el modo de obtener los datos sobre los símbolos seleccionados. Tenemos que modificar dos métodos que se responsabilizan del cambio entre los pasos de la configuración. Hay que modificar el método To_Step1() de tal manera que se oculte la posibilidad de seleccionar los timeframes y se muestre nuestra tabla al cambiar del Paso 2 al Paso 1.

//+------------------------------------------------------------------+
//| Ir al paso 1                                                     |
//+------------------------------------------------------------------+
void CProgram::ToStep_1(void)
{
//--- Cambiamos el encabezado
   m_step_window.LabelText("Signal Monitor Step 1: Choose Symbols");
   m_step_window.Update(true);
//--- Ocultamos el botón Atrás
   m_back_button.Hide();
//--- Mostramos la tabla
   m_table.Show();
//--- Ocultamos timeframes
   for(int i=0; i<21; i++)
      m_checkbox[i].Hide();
   string names[3]= {"All","Majors","Crosses"};
//--- Cambiamos los nombres de los botones de los conjuntos
   for(int i=0; i<3; i++)
   {
      m_currency_set[i].IsPressed(false);
      m_currency_set[i].LabelText(names[i]);
      m_currency_set[i].Update(true);
   }
//--- Mostramos el bloque del trabajo con las plantillas
   m_text_edit.Show();
   m_load_button.Show();
   m_save_button.Show();
//--- Establecemos el paso actual de la configuración
   m_current_step=1;
}

En el método To_Step2(), hacemos lo contrario, es decir, ocultamos la tabla, mostramos la selección de los timeframes y recordamos la selección de los símbolos en el primer paso para los cálculos posteriores. 

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::ToStep_2(void)
{
//--- Comprobando si ha sido seleccionado por lo menos un símbolo
   int cnt=0;
//---
   for(int c=0; c<7; c++)
   {
      for(int r=0; r<MathCeil(m_all_symbols/7); r++)
      {
         if(m_table.SelectedImageIndex(c,r)>0)
            cnt++;
      }
   }
//---
   if(cnt<1)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("¡No hay símbolos seleccionados!","Atención");
      else
         MessageBox("No symbols selected!","Warning");
      return;
   }
//--- Ocultamos la tabla
   m_table.Hide();
//--- Mostramos timeframes
   for(int i=0; i<21; i++)
      m_checkbox[i].Show();
//--- Calculamos el número de los símbolos seleccionados
   ArrayResize(m_symbols,cnt);
   cnt=0;
//--- Guardamos los símbolos seleccionados en el array
   for(int c=0; c<7; c++)
   {
      for(int r=0; r<MathCeil(m_all_symbols/7); r++)
      {
         if(m_table.SelectedImageIndex(c,r)>0)
         {
            m_symbols[cnt]=m_table.GetValue(c,r);
            cnt++;
         }
      }
   }
//--- Establecer símbolos seleccionados en la Observación del mercado
   for(int c=0; c<7; c++)
   {
      for(int r=0; r<MathCeil(m_all_symbols/7); r++)
      {
         if(m_table.SelectedImageIndex(c,r)>0)
            SymbolSelect(m_table.GetValue(c,r),true);
         else
            SymbolSelect(m_table.GetValue(c,r),false);
      }
   }
//---
   if(m_current_step==3)
   {
      m_add_signal.Hide();
      m_signal_header.Hide();
      m_next_button.LabelText("Next");
      m_next_button.Update(true);
      for(int i=0; i<5; i++)
         m_signal_editor[i].Hide();
      ClearSaves();
   }
//--- Cambiamos el encabezado
   m_step_window.LabelText("Signal Monitor Step 2: Choose Timeframes");
   m_step_window.Update(true);
   string names[3]= {"All","Junior","Senior"};
//--- Cambiamos los nombres de los botones de los conjuntos
   for(int i=0; i<3; i++)
   {
      m_currency_set[i].LabelText(names[i]);
      m_currency_set[i].IsPressed(false);
      if(m_current_step==3)
         m_currency_set[i].Show();
      m_currency_set[i].Update(true);
   }
//--- Ocultamos el bloque del trabajo con las plantillas
   m_text_edit.Hide();
   m_load_button.Hide();
   m_save_button.Hide();
//--- Mostramos el botón Atrás
   m_back_button.Show();
//---
   m_current_step=2;
}

El último momento en la configuración de nueva visualización en forma de tabla será la corrección de la interacción entre los botones de la selección de los conjuntos definidos y la lista de los checkbox. Dado que la lista se ha quedado intacta, hay que introducir las modificaciones aquí:

//--- All
         if(lparam==m_currency_set[0].Id() && m_currency_set[0].IsPressed())
         {
            m_currency_set[1].IsPressed(false);
            m_currency_set[2].IsPressed(false);
            m_currency_set[1].Update(true);
            m_currency_set[2].Update(true);
            //---
            for(int i=0; i<21; i++)
            {
               m_checkbox[i].IsPressed(true);
               m_checkbox[i].Update(true);
            }
         }
         //--- Junior Timeframes
         else if(lparam==m_currency_set[1].Id() && m_currency_set[1].IsPressed())
         {
            m_currency_set[0].IsPressed(false);
            m_currency_set[2].IsPressed(false);
            m_currency_set[0].Update(true);
            m_currency_set[2].Update(true);
            //---
            string pairs[11]=
            {
               "M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30"
            };
            //--- Quitar la selección
            for(int i=0; i<21; i++)
            {
               m_checkbox[i].IsPressed(false);
               m_checkbox[i].Update(true);
            }
            //---
            for(int i=0; i<21; i++)
            {
               for(int j=0; j<11; j++)
                  if(m_checkbox[i].LabelText()==pairs[j])
                  {
                     m_checkbox[i].IsPressed(true);
                     m_checkbox[i].Update(true);
                  }
            }
         }
         //--- Senior Timeframes
         else if(lparam==m_currency_set[2].Id() && m_currency_set[2].IsPressed())
         {
            m_currency_set[0].IsPressed(false);
            m_currency_set[1].IsPressed(false);
            m_currency_set[0].Update(true);
            m_currency_set[1].Update(true);
            //---
            string pairs[10]=
            {
               "H1","H2","H3","H4","H6","H8","H12","D1","W1","MN"
            };
            //--- Quitar la selección
            for(int i=0; i<21; i++)
            {
               m_checkbox[i].IsPressed(false);
               m_checkbox[i].Update(true);
            }
            //---
            for(int i=0; i<21; i++)
            {
               for(int j=0; j<10; j++)
                  if(m_checkbox[i].LabelText()==pairs[j])
                  {
                     m_checkbox[i].IsPressed(true);
                     m_checkbox[i].Update(true);
                  }
            }
         }
         //---
         if((lparam==m_currency_set[0].Id() && !m_currency_set[0].IsPressed())      ||
               (lparam==m_currency_set[1].Id() && !m_currency_set[1].IsPressed())   ||
               (lparam==m_currency_set[2].Id() && !m_currency_set[2].IsPressed())
           )
         {
            //--- Quitar la selección
            for(int i=0; i<21; i++)
            {
               m_checkbox[i].IsPressed(false);
               m_checkbox[i].Update(true);
            }
         }


Edición rápida de las reglas para la búsqueda desde el monitoreo

En el proceso del monitoreo de señales comerciales, puede surgir la necesidad de cambiar las condiciones de entrada de una de las señales creadas anteriormente. Actualmente, habrá que reiniciar la aplicación y volver a configurar cada señal del monitoreo, aparte de corregir la señal necesaria. Se debe reconocer que nos muy conveniente, ¿verdad? Así que necesitamos tener la posibilidad de editar las señales comerciales hechas desde el sistema del monitoreo. Para eso, vamos a completar la interfaz del monitoreo con un botón que va a abrir una pequeña ventana de diálogo, tal como se muestra en la Fig. 7, con la lista de las señales creadas, y al pulsar en cualquiera de ellas, tendremos la posibilidad de editar una señal ya creada.

Fig. 7 Esquema de la edición de la señal desde el monitoreo.

Vamos a empezar su implementación. Para visualizar el botón que abre la ventana con la lista de señales comerciales, vamos a escribir una propiedad adicional en el cuerpo del método CreateStepWindow():

m_step_window.TooltipsButtonIsUsed(true);

Lo desactivamos inmediatamente en el evento Terminando la creación de la interfaz para que este botón no aparezca en las fases de la configuración primaria de la aplicación, sino que se muestre en el paso cuando todas las señales ya están creadas y el monitoreo ya está iniciado:

//--- Terminando la creación de la interfaz
   if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI)
   {
      ...
      m_step_window.GetTooltipButtonPointer().Hide();
   }

Que se muestre sólo cuando va a cargarse el monitoreo.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::AutoResize(const int x_size,const int y_size)
{
   ...
   m_step_window.GetTooltipButtonPointer().Show();
}

Ahora, creamos la nueva ventana de diálogo donde va a mostrarse la lista de las señales creadas. Para eso, vamos a crear una variable-instancia de la clase CWindow y el método que implementa la creación de la ventana CreateFastEdit(), así como el método CreateFastEditor() para crear los botones al pulsar los cuales se realizara la edición de la señal.

   CWindow           m_fast_edit;

   bool              CreateFastEdit(const string caption_text);
   bool              CreateFastEditor(CButton &button,string text,const int x_gap,const int y_gap);

Vamos a implementar estos métodos:

//+------------------------------------------------------------------+
//| Crea la ventana para generar y editar señales comerciales        |
//+------------------------------------------------------------------+
bool CProgram::CreateFastEdit(const string caption_text)
{
//--- Añadimos el puntero de la ventana al array de ventanas
   CWndContainer::AddWindow(m_fast_edit);
//--- Propiedades
   m_fast_edit.XSize(180);
   m_fast_edit.YSize(280);
//--- Coordenadas
   int x=m_step_window.XGap()+m_step_window.XSize()+10;
   int y=m_step_window.YGap();
//---
   m_fast_edit.CaptionHeight(22);
   m_fast_edit.IsMovable(true);
   m_fast_edit.CaptionColor(m_caption);
   m_fast_edit.CaptionColorLocked(m_caption);
   m_fast_edit.CaptionColorHover(m_caption);
   m_fast_edit.BackColor(m_background);
   m_fast_edit.FontSize(m_base_font_size);
   m_fast_edit.Font(m_base_font);
   m_fast_edit.WindowType(W_DIALOG);
//--- Creando el formulario
   if(!m_fast_edit.CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
//---
   for(int i=0; i<5; i++)
   {
      if(!CreateFastEditor(m_fast_editor[i],"Signal_"+string(i),10,40*i+40))
         return(false);
   }
   return(true);
}
//+------------------------------------------------------------------+
//| Crea un botón con imagen                                         |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\settings_light.bmp"
bool CProgram::CreateFastEditor(CButton &button,string text,const int x_gap,const int y_gap)
{
//---
   color baseclr=C'70,180,70';
   color pressed=C'70,170,70';
//--- Guardamos el puntero a la ventana
   button.MainPointer(m_fast_edit);
//--- Establecemos las propiedades antes de la creación
   button.XSize(110);
   button.YSize(30);
   button.Font(m_base_font);
   button.FontSize(m_base_font_size);
   button.IconXGap(3);
   button.IconYGap(7);
   button.IconFile("Images\\EasyAndFastGUI\\Icons\\bmp16\\settings_light.bmp");
   button.BackColor(baseclr);
   button.BackColorHover(baseclr);
   button.BackColorPressed(pressed);
   button.BorderColor(baseclr);
   button.BorderColorHover(baseclr);
   button.BorderColorPressed(pressed);
   button.LabelColor(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.LabelColorHover(clrWhite);
   button.IsCenterText(true);
//--- Creamos el control
   if(!button.CreateButton(text,x_gap,y_gap))
      return(false);
//--- Añadimos el puntero al control a la base
   CWndContainer::AddToElementsArray(3,button);
   return(true);
}

Y llamamos al método CreateFastEdit() en el cuerpo del método CreateGUI().

//+------------------------------------------------------------------+
//| Crea la interfaz gráfica del programa                            |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//--- Paso 1-3. Ventana para seleccionar símbolos.
   if(!CreateStepWindow("Signal Monitor Step 1: Choose Symbols"))
      return(false);
//---
   if(!CreateSetWindow("Signal Monitor Edit Signal"))
      return(false);
//--- Creando el formulario 2 para la paleta de colores
   if(!CreateColorWindow("Color Picker"))
      return(false);
//--- Creando formulario de edición rápida
   if(!CreateFastEdit("Fast Signal Editor"))
      return(false);
//--- Terminando la creación de GUI
   CWndEvents::CompletedGUI();
   return(true);
}

Ahora, necesitamos que se abra nuestra ventana de diálogo con señales al pulsar en el botón Configuración en el monitoreo. Para eso, escribiremos el siguiente código en la sección Evento de la pulsación en el botón del método OnEvent():

      //--- ABRIR LA VENTANA DE LA CONFIGURACIÓN
      if(lparam==m_step_window.GetTooltipButtonPointer().Id())
      {
        //--- Coordenadas
         int x=m_step_window.X()+m_step_window.XSize()+10;
         int y=m_step_window.Y();
         m_fast_edit.X(x);
         m_fast_edit.Y(y);
         m_fast_edit.OpenWindow();
      }

Si compilamos el proyecto ahora, obtenemos el siguiente resultado:

Fig. 8 Añadir la ventana de edición rápida de señales comerciales

No obstante, ahora se muestran todos los botones para editar las señales, mientras que nuestra tarea consiste en hacer que se vean solamente las señales creadas. Por eso, es necesario comprobar la cantidad actual de señales disponibles. Podemos hacerlo a través del evento Apertura de la ventana de diálogo:

//--- Apertura de la ventana de diálogo
   if(id==CHARTEVENT_CUSTOM+ON_OPEN_DIALOG_BOX)
   {
      if(m_current_step<4)
         return;
      for(int i=0; i<5; i++)
      {
         if(!FileIsExist("Signal Monitor\\signal_"+string(i)+".bin"))
            m_fast_editor[i].Hide();
      }
   }

Aquí, se comprueba si existen los archivos que contienen los ajustes de las señales comerciales, y muestra sólo las señales creadas anteriormente. Por último, hagamos que se abra la ventana de la edición de la señal creada si pulsemos en el botón de esta señal. Lo vamos a hacer en la sección Evento de la pulsación en el botón.

      Edición de la señal comercial
      for(int i=0; i<5; i++)
      {
         if(lparam==m_fast_editor[i].Id())
         {
            m_fast_edit.CloseDialogBox();
            LoadSignalSet(i);
            m_new_signal.LabelText("Save");
            m_new_signal.Update(true);
            RebuildParameters(m_indicator_type.GetListViewPointer().SelectedItemIndex());
            m_set_window.OpenWindow();
            m_number_signal=i;
         }
      }

Al hacer clic en una de las señales en la ventana de edición rápida, podremos abrir la Ventana de Ajustes, cargar ahí los datos guardados previamente para esta señal comercial y continuar el trabajo con ella, cambiando lo necesario y guardando de nuevo en el archivo. En este caso, ahora no hace falta realizar la configuración completa del monitoreo comercial.

Localización de la aplicación

Para resolver el problema de la localización, hay que determinar todos los elementos de la interfaz que requieren la traducción y dejar intactos cuyas designaciones o nombres son de uso general. Para implementar esta tarea, usaremos un mecanismo bastante simple: crearemos un array string con datos que van a usarse para la substitución en los elementos de la interfaz de acuerdo con el idioma definido en los ajustes. Tendremos dos idiomas: ruso (español) e inglés. Procedemos a la implementación. Para empezar, en el archivo SignalMonitor.mq5, creamos una enumeración que nos permitirá seleccionar el idioma de la interfaz necesario al iniciar la aplicación, así como, cambiaremos los nombres de otros parámetros según el estándar, es decir, en inglés.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
enum UPDATE
{
   MINUTE,        // 1 minute
   MINUTE_15,     // 15 minutes
   MINUTE_30,     // 30 minutes
   HOUR,          // 1 hour
   HOUR_4         // 4 hour
};
enum LANG
{
   RUSSIAN,       // Russian
   ENGLISH        // English
};
//+------------------------------------------------------------------+
//| Parámetros de entrada del EA                                     |
//+------------------------------------------------------------------+
input int                  Inp_BaseFont      =  10;                  // Base Font
input color                Caption           =  C'0,130,225';        // Caption Color
input color                Background        =  clrWhiteSmoke;       // Back color
input LANG                 Language          =  ENGLISH;             // Interface language
input UPDATE               Update            =  MINUTE;              // Update interval

Para transmitir la información sobre el idioma seleccionado por el usuario a la interfaz, crearemos una variable en la sección pública de la clase base CProgram.

   //---
   int               m_language;

Se le asignará un número de orden del idioma seleccionado durante la inicialización de la aplicación.

   program.m_language=Language;

Luego, en la sección privada de la clase base, crearemos un array para recibir datos para la substitución en la interfaz (según el idioma seleccionado), y un método que va a cargar los datos en la interfaz.

   string            m_lang[];

   void              ChangeLanguage(void);

Ahora, vamos a implementar el método declarado en el archivo Program.mqh y colocaremos los valores en los campos correspondientes de cada uno de los elementos de la interfaz, según la lista de los valores del conjunto de idioma.

//+------------------------------------------------------------------+
//| Cambiar el idioma de la interfaz                                       |
//+------------------------------------------------------------------+
void CProgram::ChangeLanguage(void)
{
//---
#define ITEMS 40
   ArrayResize(m_lang,ITEMS);
   string rus[ITEMS]=
   {
      "Monitoreo de señales Paso 1: Seleccionar símbolos","Todas","Major","Crosses",
      "Atrás","Siguiente","Cargar (L)","Guardar (S)",«Nombre de plantilla","Monitoreo de señales Paso 2: Seleccionar timeframes",
      "Todos","Menores","Mayores",
      "Monitoreo de Señales Paso 3: Creación de señales comerciales","Crear","Añadir señal","Lista de señales",
      "Editor de señales comerciales","Tipo del indicador","1. Ajustes del indicador","Precio aplicado",
      "Por favor, introduzca la ruta del indicador","Por favor, introduzca los parámetros separándolos con coma"",
      "2. Ajuste de señal","Regla","Etiqueta","Valor","Texto","Color de la etiqueta","Fondo","Borde","Ayuda",
      "Imagen","Timeframes","Añadir"," Cancelar","Monitoreo de señales comerciales","Número del búfer","Guardar"
   };
   string eng[ITEMS]=
   {
      "Signal Monitor Step 1: Choose Symbols","ALL","Major","Crosses",
      "Back","Next","Load(L)","Save(S)","Template name","Signal Monitor Step 2: Choose Timeframes",
      "ALL","Junior","Senior",
      "Signal Monitor Step 3: Creating Trading Signals","Create","Add Signal","Signal List",
      "Signal Monitor Edit Signal","Indicator Type","1.Indicator Settings","Applied Price",
      "Enter the indicator path","Enter indicator parameters separated by commas",
      "2.Signal Settings","Rule","Label","Value","Text","Label Color","Use Background","Use Border","Use Tooltip",
      "Use Image","Timeframes","Add","Cancel","Signal Monitor","Buffer number","Save"
   };
//--- Russian
   if(m_language==0)
      ArrayCopy(m_lang,rus);
//--- English
   else
      ArrayCopy(m_lang,eng);
}

Después de eso, podemos observar los resultados del trabajo no sólo en inglés, sino también en español (Fig. 9).

Fig. 9. Resultado del trabajo de la localización de la interfaz.


Posibilidades adicionales

En este apartado, me gustaría hacer un par de adiciones que mejoren la parte visual del monitoreo, así como, incluir la posibilidad del paso rápido al gráfico del símbolo con la señal comercial que ha aparecido. Bajo la parte visual se entiende una simple extensión de los bloques de las señales, porque se ha notado que su representación actual es algo pequeña. Para eso, aumentamos el tamaño de los bloques de las señales en el método CreateSignalButton() , y corregimos la posición de sus elementos dentro y de los propios bloques respecto uno a otro en el método To_Monitor().

   button.XSize(60);
   button.YSize(30);
   button.IconXGap(2);
   button.IconYGap(11);
   button.LabelXGap(19);
   button.LabelYGap(10);

//--- Símbolos
   int sy=ArraySize(m_symbols);
   ArrayResize(m_symbol_label,sy);
   for(int i=0; i<sy; i++)
   {
      if(!CreateSymbolLabel(m_symbol_label[i],5,m_step_window.CaptionHeight()+40+i*35,m_symbols[i]))
         return;
      m_symbol_label[i].Update(true);
   }
//--- Timeframes
   int tf=ArraySize(m_timeframes);
   ArrayResize(m_timeframe_label,tf);
//---
   for(int i=0; i<tf; i++)
   {
      if(!CreateTimeframeLabel(m_timeframe_label[i],110+65*i,m_step_window.CaptionHeight()+3,m_timeframes[i]))
         return;
      m_timeframe_label[i].Update(true);
   }
//-- Bloques de señales
   int k=0;
   ArrayResize(m_signal_button,sy*tf);
   for(int j=0; j<sy; j++)
   {
      for(int i=0; i<tf; i++)
      {
         if(!CreateSignalButton(m_signal_button[k],m_timeframe_label[i].XGap()+m_timeframe_label[i].XSize()/2,m_step_window.CaptionHeight()+35+j*35))
            return;
         m_signal_button[k].Update(true);
         k++;
      }
   }
//---
   m_current_step=4;
//--- Cambiamos las dimensiones de la ventana
   AutoResize(m_timeframe_label[tf-1].XGap()+m_timeframe_label[tf-1].XSize()+15,m_symbol_label[sy-1].YGap()+m_symbol_label[sy-1].YSize()+10);

Como resultado, obtenemos la implementación del monitoreo más conveniente para la observación, tal como se muestra en la Fig. 10.

Fig. 10 Cambio de las dimensiones de los bloques de las señales y corrección de la interfaz del monitoreo.

Nos queda escribir el código para que se abra el gráfico con el símbolo y timeframe correspondientes a la señal en cuyo bloque pulsamos. Para eso, en el método OnEvent()de la sección Evento de la pulsación del botón (los bloques de señales no son más que los botones), añadimos el siguiente código:

      //--- PULSACIÓN EN EL BLOQUE DE LA SEÑAL
      for(int i=0; i<ArraySize(m_signal_button); i++)
      {
         if(lparam==m_signal_button[i].Id())
            ChartOpen(GetSymbol(i),GetTimeframe(i));
      }

Todo es bastante fácil. Pues, aquí terminamos la fase actual del desarrollo. En la siguiente parte, continuaremos mejorando el sistema de la búsqueda de señales, introduciremos el concepto «Señal compuesta» y aumentaremos las posibilidades para gestionar el monitoreo.


Conclusión

Al final del articulo se adjunta el archivo comprimido con todos los ficheros mencionados, ordenados por carpetas. Por eso, para un trabajo correcto basta con colocar la carpeta MQL5  en la raíz del terminal. Para abrir el directorio raíz del terminal que contiene la carpeta MQL5, pulse en la combinación   Ctrl+Shift+D o utilice el menú contextual, tal como se muestra en la imagen 11.


Fig. 11 Abrir la carpeta MQL5 en el directorio raíz del terminal MetaTrader 5