Monitoreo multidivisas de las señales comerciales (Parte 2): Implementando la parte visual de la aplicación

12 febrero 2020, 10:59
Alexander Fedosov
0
759

Contenido

Introducción

La fase anterior fue dedicada al desarrollo de la estructura general de la aplicación Monitoreo multidivisas de señales comerciales. En esta parte, nos centraremos en el desarrollo consecutivo de las etapas de la configuración primaria paso a paso de la aplicación, así como crearemos la interacción base entre los controles que componen la interfaz.


El primer paso de la configuración: Símbolos

Cuando nos recurrimos a nuestra estructura de la aplicación, notamos que el primer paso en el inicio primario es la tarea de crear una interfaz para seleccionar los símbolos que serán usados en la búsqueda de las señales comerciales creadas. Al final del artículo anterior, fue creada una plantilla de la aplicación en la que se basará todo nuestro trabajo siguiente. Ahora, pasaremos paso a paso a su diseño. Pues bien, vamos a definir los grupos principales de los controles necesarios para la implementación de esta parte de la aplicación.

  • Ventana de la aplicación.
  • Selección rápida de los grupos de símbolos.
  • Campo para introducir el grupo personalizado.
  • Botones Guardar y Cargar el grupo personalizado de símbolos.
  • Lista completa de todos los símbolos disponibles en forma de las casillas de verificación (chekbox) con la etiqueta de texto del nombre del símbolo.
  • Botón Next (Siguiente) para ir al segundo paso de los ajustes: Selección de timeframes.

Recordemos que la estructura de los archivos creada anteriormente debe ser así:

Fig. 1 Estructura de archivos de la aplicación.

Para empezar, abramos el archivo de la propia aplicación SignalMonitor.mq5 y agreguemos los siguientes parámetros de entrada que podamos ajustar al iniciar la aplicación en el terminal MetaTrader 5, así como declaremos una instancia de la clase CProgram creada anteriormente, e iniciemos algunas variables. Después de la adición, el archivo abierto se cambia de la siguiente manera:

//+------------------------------------------------------------------+
//|                                                SignalMonitor.mq5 |
//|                                Copyright 2019, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, Alexander Fedosov"
#property link      "https://www.mql5.com/ru/users/alex2356"
#property version   "1.00"
//--- Inclusión de la clase de la aplicación
#include "Program.mqh"
//+------------------------------------------------------------------+
//| Parámetros de entrada del EA                                     |
//+------------------------------------------------------------------+
input int                  Inp_BaseFont      =  10;                  // Fuente base
input color                Caption           =  C'0,130,225';        // Color del encabezado
input color                Background        =  clrWhiteSmoke;       // Color del fondo
//---
CProgram program;
ulong tick_counter;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
{
//---
   tick_counter=GetTickCount();
//--- Inicialización de las variables de la clase
   program.OnInitEvent();
   program.m_base_font_size=Inp_BaseFont;
   program.m_background=Background;
   program.m_caption=Caption;
//--- Definimos el panel de trading
   if(!program.CreateGUI())
   {
      Print(__FUNCTION__," > Fallo al crear la interfaz gráfica");
      return(INIT_FAILED);
   }
//--- Inicialización con éxito
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   program.OnDeinitEvent(reason);
}
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer(void)
{
   program.OnTimerEvent();
}
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int    id,
                  const long   &lparam,
                  const double &dparam,
                  const string &sparam)
{
   program.ChartEvent(id,lparam,dparam,sparam);
   //---
   if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI)
   {
      Print("End in ",GetTickCount()-tick_counter," ms");
   }
}
//+------------------------------------------------------------------+

 Como se puede observar, hemos añadido tres parámetros, a saber:

  • Tamaño de la fuente.
  • Color del encabezado de las ventanas de la aplicación.
  • Color del fondo para las ventanas y controles de la aplicación.

A continuación, declaramos una instancia de la clase CProgram con el nombre program y la variable tick_counter (la necesitamos solamente para visualizar la información sobre la hora del inicio de la aplicación). A continuación, en el método OnInit(), inicializamos las variables de la instancia de la clase asignándoles los valores de los parámetros de entrada de la aplicación. Así como, llamamos al método base CreateGUI() que va a iniciar la propia aplicación.

Sin embargo, si ahora compilamos el archivo abierto, obtendremos los errores de compilación con un mensaje diciendo que las variables m_base_font_size, m_background, m_caption, ni el método CreateGUI(), no han sido encontrados en la clase CProgram. Por tanto, vamos a abrir el archivo Program.mqh y proceder a la implementación de la clase CProgram, añadiendo primero al archivo anterior, las variables y el método, así como otros métodos necesarios para un correcto funcionamiento inicial de la aplicación. Después de añadir todo eso, la clase CProgram será así:

//+------------------------------------------------------------------+
//| Clase para crear la aplicación                                   |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
{
public:
//---
   int               m_base_font_size;
//---
   string            m_base_font;
//---
   color             m_background;
   color             m_caption;
public:
   CProgram(void);
   ~CProgram(void);
   //--- Inicialización/deinicialización
   void              OnInitEvent(void);
   void              OnDeinitEvent(const int reason);
   //--- Temporizador
   void              OnTimerEvent(void);
   //--- Manejador del evento del gráfico
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   // Crea la interfaz gráfica del programa
   bool              CreateGUI(void);
};

Mientras que la implementación del método que crea la interfaz todavía está vacía:

//+------------------------------------------------------------------+
//| Crea la interfaz gráfica del programa                            |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//---

//--- Terminar la creación de GUI
   CWndEvents::CompletedGUI();
   return(true);
}
//+------------------------------------------------------------------+

Nótese que ha sido añadida la variable de cadena m_base_font, entre las demás, que responde del nombre de la fuente en la aplicación. La inicializamos dentro del constructor de nuestra clase:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CProgram::CProgram(void)
{
   m_base_font="Trebuchet MS";
}

Pues, procedemos a la creación de la primera ventana de la aplicación. Con este fin, en nuestra clase, declaramos la nueva variable m_step_window, que representa una instancia de la clase CWindow. Así como declaramos el método que va a crear nuestra primera ventana, asignándole el nombre CreateStepWindow(). En el listado de nuestra clase, eso tendrá el siguiente aspecto:

class CProgram : public CWndEvents
{
public:
//--- Ventanas de la aplicación
   CWindow           m_step_window;
...
protected:
   //--- Formularios
   bool              CreateStepWindow(const string caption_text);

Como ha sido determinado anteriormente, la implementación de una parte de la interfaz que se encarga de la configuración consecutiva del inicio primario va a ubicarse en el archivo de inclusión StepWindow.mqh. Por eso, vamos a abrirlo y proceder a la creación de la ventana, a saber, la implementación del método  CreateStepWindow():

#include "Program.mqh"
//+------------------------------------------------------------------+
//| Crea el formulario para seleccionar símbolos                     |
//+------------------------------------------------------------------+
bool CProgram::CreateStepWindow(const string text)
{
//--- Añadimos el puntero de la ventana al array de ventanas
   CWndContainer::AddWindow(m_step_window);
//--- Propiedades
   m_step_window.XSize(600);
   m_step_window.YSize(200);
//--- Coordenadas
   int x=int(ChartGetInteger(m_chart_id,CHART_WIDTH_IN_PIXELS)-m_step_window.XSize())/2;
   int y=10;
   m_step_window.CaptionHeight(22);
   m_step_window.IsMovable(true);
   m_step_window.CaptionColor(m_caption);
   m_step_window.CaptionColorLocked(m_caption);
   m_step_window.CaptionColorHover(m_caption);
   m_step_window.BackColor(m_background);
   m_step_window.FontSize(m_base_font_size);
   m_step_window.Font(m_base_font);
//--- Creando el formulario
   if(!m_step_window.CreateWindow(m_chart_id,m_subwin,text,x,y))
      return(false);
   //---
   return(true);
}
//+------------------------------------------------------------------+

No olvidemos añadir el método CreateGUI():

//+------------------------------------------------------------------+
//| Crea la interfaz gráfica del programa                            |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//--- Paso 1-3
   if(!CreateStepWindow("Signal Monitor Step 1: Choose Symbols"))
      return(false);
//--- Terminar la creación de GUI
   CWndEvents::CompletedGUI();
   return(true);
}
//+------------------------------------------------------------------+

Si la secuencia de acciones ha sido realizada correctamente, entonces, después de compilar el archivo SignalMonitor.mq5 e iniciarlo en el terminal, verá el formulario creado:

Fig. 2 Primera ventana de la aplicación

Los primeros controles de esta ventana van a representar un grupo de botones cuyo cometido consiste en una rápida selección de los conjuntos predefinidos de símbolos del terminal: forex.all, forex.crosses, forex.major. Para empezar, el archivo Program.mqh recibirá el array de las instancias de la clase CButton con la dimensión de tres, así como el método universal CreateSymbolSet() para crear los botones:

//+------------------------------------------------------------------+
//| Clase para crear la aplicación                                   |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
{
public:
//--- Ventanas de la aplicación
   CWindow           m_step_window;
//--- Botones simples
   CButton           m_currency_set[3];
...
   //--- Botones
   bool              CreateSymbolSet(CButton &button,string text,const int x_gap,const int y_gap);

Ahora, vamos a ir al archivo StepWindow.mqh y añadir la implementación del método creado anteriormente:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateSymbolSet(CButton &button,string text,const int x_gap,const int y_gap)
{
//---
   color baseclr=C'220,225,235';
   color pressed=C'55,160,250';
//--- Guardamos el puntero a la ventana
   button.MainPointer(m_step_window);
//--- Establecemos las propiedades antes de la creación
   button.TwoState(true);
   button.XSize(80);
   button.YSize(30);
   button.LabelXGap(19);
   button.LabelYGap(2);
   button.Font(m_base_font);
   button.FontSize(m_base_font_size);
   button.BackColor(baseclr);
   button.BackColorHover(baseclr);
   button.BackColorPressed(pressed);
   button.BorderColor(baseclr);
   button.BorderColorHover(baseclr);
   button.BorderColorPressed(pressed);
   button.LabelColor(clrBlack);
   button.LabelColorPressed(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);
}
//+------------------------------------------------------------------+

Nos queda añadir tres botones con diferentes valores de las coordenadas y etiquetas de texto a nuestra ventana, en el método base CreateStepWindow() de la propia ventana, tras la creación del formulario:

...
//--- Creando el formulario
   if(!m_step_window.CreateWindow(m_chart_id,m_subwin,text,x,y))
      return(false);
//---
   if(!CreateSymbolSet(m_currency_set[0],"ALL",10,30))
      return(false);
   if(!CreateSymbolSet(m_currency_set[1],"Major",10+100,30))
      return(false);
   if(!CreateSymbolSet(m_currency_set[2],"Crosses",10+2*(100),30))
      return(false);
...

Verá el siguiente resultado tras la compilación:

Fig. 3 Añadiendo botones de la selección rápida de grupos de símbolos.

A continuación, añadimos el campo de edición para el nombre de nuestro grupo seleccionado de símbolos que podremos guardar y cargar a través de los botones Save y Load. Para eso, añadimos la instancia de la clase para crear el campo de edición CTextEdit y dos instancias de la clase par crear los botones CButton. Puesto que los botones de guardar y cargar van a diferenciarse sólo en sus nombres, crearemos un método universal CreateButton1(), y añadimos CreateEditValue() a la clase CProgram para el campo de edición:

//+------------------------------------------------------------------+
//| Clase para crear la aplicación                                   |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
{
public:
//--- Ventanas de la aplicación
   CWindow           m_step_window;
//--- Botones simples
   CButton           m_currency_set[3];
   CButton           m_load_button;
   CButton           m_save_button;
   //--- Campos de edición
   CTextEdit         m_text_edit;
...
   bool              CreateButton1(CButton &button,string text,const int x_gap,const int y_gap);
   //--- Campo de edición
   bool              CreateEditValue(CTextEdit &text_edit,const int x_gap,const int y_gap);

Volvamos al archivo StepWindow.mqh y agreguemos la implementación de los métodos creados al final del archivo.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateEditValue(CTextEdit &text_edit,const int x_gap,const int y_gap)
{
//--- Guardamos el puntero al control principal
   text_edit.MainPointer(m_step_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(1);
   text_edit.GetTextBoxPointer().XSize(110);
   text_edit.GetTextBoxPointer().DefaultTextColor(clrSilver);
   text_edit.GetTextBoxPointer().DefaultText("Template name");
//--- Creamos el control
   if(!text_edit.CreateTextEdit("",x_gap,y_gap))
      return(false);
//--- Añadimos el objeto al array general de los grupos de objetos
   CWndContainer::AddToElementsArray(0,text_edit);
   return(true);
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateButton1(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_step_window);
//--- Establecemos las propiedades antes de la creación
   button.XSize(80);
   button.YSize(30);
   button.Font(m_base_font);
   button.FontSize(m_base_font_size);
   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(0,button);
   return(true);
}

 Después de eso, volvemos al método CreateStepWindow() y añadimos ambos botones y el campo de edición a la ventana de la aplicación dentro de él.

//---
   if(!CreateEditValue(m_text_edit,300,m_step_window.CaptionHeight()+10))
      return(false);
//---
   if(!CreateButton1(m_load_button,"Load(L)",m_step_window.XSize()-2*(80+10),m_step_window.CaptionHeight()+10))
      return(false);
   if(!CreateButton1(m_save_button,"Save(S)",m_step_window.XSize()-(80+10),m_step_window.CaptionHeight()+10))
      return(false);

Volvemos a compilar el archivo SignalMonitor.mq5 y obtenemos la siguiente imagen:

Fig. 4 Añadimos el campo de edición de un grupo de símbolos y los botones para guardar/salvar.

Ahora, procedemos a la visualización y la posibilidad de seleccionar todos los símbolos disponibles para la cuenta seleccionada en el terminal MetaTrader 5. Aquí, hay que tener en cuenta lo siguiente: al visualizar todos los símbolos disponibles, la altura de la ventana no va a ser suficiente. Por eso, merece la pena pensar en la posibilidad del cambio automático de la altura de la ventana ajustándola al volumen de datos necesario. La adición del número general de los símbolos va a seguir el mismo principio, es decir, añadimos un array de instancias de la clase para la creación de los checkbox CCheckBox y un método universal para su creación, ya que van a diferenciarse sólo en el nombre.

...
   //--- Checkbox
   CCheckBox         m_checkbox[];
...
   //--- Checkbox
   bool              CreateCheckBox(CCheckBox &checkbox,const int x_gap,const int y_gap,const string text);

Pero como se puede ver, la dimensionalidad del array Checkbox m_checkbox[] no está especificada porque no se sabe de antemano el número de los símbolos disponibles que están presentes en la cuenta seleccionada en el terminal. Por este motivo, en la sección privada de la clase CProgram, creamos dos variables a los cuales se les asignan los valores del número total de los símbolos disponibles y el número de los símbolos seleccionados en este momento en la Observación del mercado.

private:
//---
   int               m_symbol_total;
   int               m_all_symbols;

Les asignamos los valores necesarios en el constructor y establecemos la dimensionalidad apropiada para el array m_checkbox[]:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CProgram::CProgram(void)
{
   m_base_font="Trebuchet MS";
   m_symbol_total=SymbolsTotal(true);
   m_all_symbols=SymbolsTotal(false);
   ArrayResize(m_checkbox,m_all_symbols);
}

Añadimos la implementación del método creado anteriormente al final del archivo StepWindow.mqh :

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateCheckBox(CCheckBox &checkbox,const int x_gap,const int y_gap,const string text)
{
//--- Guardamos el puntero al control principal
   checkbox.MainPointer(m_step_window);
//--- Propiedades
   checkbox.GreenCheckBox(true);
   checkbox.IsPressed(false);
   checkbox.Font(m_base_font);
   checkbox.FontSize(m_base_font_size);
   checkbox.BackColor(m_background);
   checkbox.LabelColorHover(C'55,160,250');
//--- Creamos el control
   if(!checkbox.CreateCheckBox(text,x_gap,y_gap))
      return(false);
//--- Añadimos el puntero al control a la base
   CWndContainer::AddToElementsArray(0,checkbox);
   return(true);
}

Añadimos los checkbox el método CreateStepWindow(). En el listado de abajo está hecho que toda la lista de símbolos disponibles tiene 7 columnas en ancho, así como consideramos el cambio de altura del precio.

   //--- Checkbox
   int k=0;
   for(int j=0; j<=MathCeil(m_all_symbols/7); j++)
   {
      for(int i=0; i<7; i++)
      {
         if(k<m_all_symbols)
            if(!CreateCheckBox(m_checkbox[k],10+80*i,m_step_window.CaptionHeight()+70+j*25,SymbolName(k,false)))
               return(false);
         k++;
      }
   }
   m_step_window.ChangeWindowHeight(m_checkbox[m_all_symbols-1].YGap()+30+30);

Compilamos las adiciones obtenidos:

Fig. 5 Añadimos los checkbox con todos los símbolos disponibles.

El último elemento de esta parte de la aplicación describe los botones de navegación entre los pasos de la configuración. Es bastante fácil de insertarlos: añadimos dos instancias de la clase CButton con los nombres m_next_button y m_back_button, prestando el método para su creación desde CreateButton1() ya existente. Vamos al método de la creación de la ventana CreateStepWindow() y añadimos dentro lo siguiente:

//---
   if(!CreateButton1(m_back_button,"Back",m_step_window.XSize()-2*(80+10),m_step_window.YSize()-(30+10)))
      return(false);
   if(!CreateButton1(m_next_button,"Next",m_step_window.XSize()-(80+10),m_step_window.YSize()-(30+10)))
      return(false);

Nos queda sólo configurar el trabajo de los botones mediante los cuales se seleccionan los conjuntos predefinidos de los símbolos. Para ello, vamos al archivo Program.mqh, encontramos el método OnEvent() y añadimos el siguiente código dentro de él:

//+------------------------------------------------------------------+
//| Manejador de eventos del gráfico                                 |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
{
//--- Evento del clic en el botón
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
   {
      //--- 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<m_all_symbols; i++)
         {
            m_checkbox[i].IsPressed(true);
            m_checkbox[i].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"};
         //--- Quitamos la selección
         for(int i=0; i<m_all_symbols; i++)
         {
            m_checkbox[i].IsPressed(false);
            m_checkbox[i].Update(true);
         }
         //---
         for(int i=0; i<m_all_symbols; i++)
         {
            for(int j=0; j<4; j++)
               if(m_checkbox[i].LabelText()==pairs[j])
               {
                  m_checkbox[i].IsPressed(true);
                  m_checkbox[i].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"
         };
         //--- Quitamos la selección
         for(int i=0; i<m_all_symbols; i++)
         {
            m_checkbox[i].IsPressed(false);
            m_checkbox[i].Update(true);
         }
         //---
         for(int i=0; i<m_all_symbols; i++)
         {
            for(int j=0; j<20; 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())
        )
      {
         //--- Quitamos la selección
         for(int i=0; i<m_all_symbols; i++)
         {
            m_checkbox[i].IsPressed(false);
            m_checkbox[i].Update(true);
         }
      }
   }
}

La idea de esta implementación consiste en lo siguiente:

  • Al pulsar el botón ALL, se seleccionan todos los símbolos.
  • Al pulsar el botón Major, se quita la selección anterior y se establece el conjunto de los símbolos como en el terminal forex.major.
  • Al pulsar el botón Crosses, se quita la selección anterior y se establece el conjunto de los símbolos como en el terminal crosses.major.
  • Cuando los tres botones están desapretados, la selección se quita.
<d

Eso se representa de la siguiente manera:

Fig. 6 Implementación de la interacción base de los controles.

Para finalizar la implementación visual, nos quedan dos pequeñas adiciones: como podemos ver en la figura 5, ahí hay el botón Back (Atrás) que hemos creado, pero tomando en cuenta que es el paso №1, no tiene que haber ahí. Por eso, hay que ocultarlo y mostrarlo sólo en el paso 2 y 3. Por eso, añadimos la siguiente línea con el código al método CreateGUI():

bool CProgram::CreateGUI(void)
{
//--- Paso 1-3
   if(!CreateStepWindow("Signal Monitor Step 1: Choose Symbols"))
      return(false);
//--- Terminar la creación de GUI
   CWndEvents::CompletedGUI();
   m_back_button.Hide();
   return(true);
}

Segundo, es necesario monitorear la selección del usuario y no permitir ir al paso №2 si falta la selección de por lo menos uno de los símbolos disponibles. La navegación entre los pasos se realiza a través de los botones Back (Atrás) y Next (Siguiente). Por tanto, para resolver el problema, introducimos tres nuevos métodos en la sección privada de la clase CProgram. Ellos van a procesar la información seleccionada en cada uno de los tres pasos, realizando así el ajuste primario de la aplicación. Además, añadimos la variable m_current_step para que la aplicación sepa en que paso de los ajustes nos encontramos cuando se pulsan los botones de navegación Atrás/Siguiente.

private:
//---
   int               m_symbol_total;
   int               m_all_symbols;
   int               m_current_step;
   //---
   void              ToStep_1(void);
   void              ToStep_2(void);
   void              ToStep_3(void);

Después de eso, dentro del constructor de la clase, asignamos el valor del primer paso a la variable creada, es decir 1. Para configurar la navegación entre tres pasos de la configuración, añadimos el siguiente código al evento del clic del botón en el método OnEvent():

      //--- Navegación
      if(lparam==m_back_button.Id())
      {
         //--- Volver al Paso 1
         if(m_current_step==2)
            ToStep_1();
         //--- Volver al Paso 2
         else if(m_current_step==3)
            ToStep_2();
      }
      //--- Ir al Paso 2
      if(lparam==m_next_button.Id())
      {
         //--- Ir al Paso 2
         if(m_current_step==1)
            ToStep_2();
         //--- Ir al Paso 3
         else if(m_current_step==2)
            ToStep_3();
      }

Si compilamos el proyecto en esta fase, el compilador nos saltará un error diciendo que tres métodos han sido creados, se usan, pero no tienen implementación: 

function 'CProgram::ToStep_1' must have a body Program.mqh 60 22

Por tanto, para corregir eso, vamos a crear la implementación de estas clases en el archivo Program.mqh, pero en caso de los métodos ToStep_1() y ToStep_3(), por ahora la dejaremos vacía. Los vamos a llenar un poco más tarde. En este momento nos interesa el método para ir al segundo paso ToStep_2() Dentro de él, añadimos una verificación de la selección de un símbolo como mínimo:

//+------------------------------------------------------------------+
//| Ir al paso 1                                                     |
//+------------------------------------------------------------------+
void CProgram::ToStep_1(void)
{
//---
}
//+------------------------------------------------------------------+
//| Ir al paso 2                                                     |
//+------------------------------------------------------------------+
void CProgram::ToStep_2(void)
{
//--- Comprobando la selección de un símbolo como mínimo
   int cnt=0;
   for(int i=0; i<m_all_symbols; i++)
   {
      if(m_checkbox[i].IsPressed())
         cnt++;
   }
   if(cnt<1)
   {
      MessageBox("No symbols selected!","Warning");
      return;
   }
}
//+------------------------------------------------------------------+
//| Ir al paso 3                                                     |
//+------------------------------------------------------------------+
void CProgram::ToStep_3(void)
{
//---
}

Si el usuario no hace la selección por alguna razón y pulsa en Next (Siguiente), se le mostrará una ayuda con la información de que es necesario seleccionar por lo menos uno de los símbolos propuestos.


El segundo paso de la configuración: Timeframes

En el segundo paso de la configuración de la aplicación, nos hace falta ofrecer al usuario la posibilidad de seleccionar los timeframes en los que va a realizarse la búsqueda de las señales comerciales creadas posteriormente. Vamos a recordar del primer artículo qué controles de la interfaz tenderemos que añadir:

  • Grupo de botones para una selección rápida de los timeframes
  • Lista de timeframes en forma de los checkbox
  • Botón Back (Atrás) para poder volver al Paso 1.

Para no acumular una multitud de objetos parecidos en su funcionalidad, vamos a coger los ya existentes de la implementación visual del Paso 1 y lo ajustaremos para la selección de los timeframes. Vamos al cuerpo del método ToStep_2() que acabamos de editar, y empezamos a completarlo. Primero, recordamos la selección de los símbolos marcados en el Paso 1 y los mostramos en la Observación del mercado del terminal MetaTrader 5:

//--- Establecer símbolos seleccionados en la Observación del mercado
   for(int i=0; i<m_all_symbols; i++)
   {
      if(m_checkbox[i].IsPressed())
         SymbolSelect(m_checkbox[i].LabelText(),true);
      else
         SymbolSelect(m_checkbox[i].LabelText(),false);
   }

A continuación, transformamos la interfaz del primer paso en el segundo:

//--- Cambiamos el encabezado
   m_step_window.LabelText("Signal Monitor Step 2: Choose Timeframes");
   m_step_window.Update(true);
//--- Ocultamos los controles del Paso 1
   for(int i=0; i<m_all_symbols; i++)
   {
      m_checkbox[i].IsLocked(false);
      m_checkbox[i].IsPressed(false);
      m_checkbox[i].Hide();
   }
   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 todos los timeframes
   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 i=0; i<21; i++)
   {
      m_checkbox[i].LabelText(timeframe_names[i]);
      m_checkbox[i].Show();
      m_checkbox[i].Update(true);
   }
//--- Mostramos el botón Atrás
   m_back_button.Show();
//---
   m_current_step=2;

Todo se implementa bastante fácil de acuerdo con los comentarios del listado de arriba. Al final de la transformación, asignamos el valor del segundo paso de la configuración, es decir 2, a la variable m_current_step. Ahora, es necesario que la interfaz modificada visualice la selección de los conjuntos de timeframes All, Junior, Senior de forma correcta. Para eso, hace falta ir al archivo Program.mqh y modificar el código en la sección Evento «clic del botón», en el método OnEvent(). Como objeto, los botones de la selección rápida de los conjuntos son iguales tanto para el primer paso, como para el segundo, sólo tienen nombres diferentes. Por eso, en caso del evento de su pulsación, es necesario precisar en qué paso de la configuración nos encontramos:

 //--- Paso 1
      if(m_current_step==1)
      {
       ...
      }
      //--- Paso 2
      else if(m_current_step==2)
      {
         //--- 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"
            };
             //--- Quitamos 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"
            };
             //--- Quitamos la selección
            for(int i=0; i<m_all_symbols; i++)
            {
               m_checkbox[i].IsPressed(false);
               m_checkbox[i].Update(true);
            }
            //---
            for(int i=0; i<m_all_symbols; 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);
                  }
            }
         }

El último control de la interfaz del segundo paso de la configuración que hace falta implementar es el botón Back para volver al Paso 1. De eso se encarga el método creado ToStep_1(), que esta vacío por ahora. Dentro de él, hay que devolver la interfaz anterior y definir el procesamiento antiguo de eventos para la pulsación de los botones de la selección de los conjuntos.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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();
//--- Quitamos la selección
   for(int i=0; i<21; i++)
   {
      m_checkbox[i].IsPressed(false);
      m_checkbox[i].Update(true);
   }
//--- Mostramos los controles del Paso 1
   for(int i=0; i<m_all_symbols; i++)
   {
      m_checkbox[i].Show();
      m_checkbox[i].LabelText(SymbolName(i,false));
      m_checkbox[i].Update(true);
   }
   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;
}

Ahora, compilamos el proyecto, y si hemos añadido todo correctamente, el resultado será como se muestra a continuación en la figura 7.

Fig. 7 Implementación del segundo paso de los ajustes de la aplicación.

El tercer paso de la configuración: Añadiendo las señales

La siguiente etapa es el tercer paso de la configuración, o sea, la interfaz para añadir señales. Es bastante sencillo y se compone del botón para añadir señales y del encabezado de la lista de señales añadidas. Iremos al archivo Program.mqh y declararemos dos nuevas variables en la clase СProgram:

   CButton           m_add_signal;
   //---
   CTextLabel        m_signal_header;

Y los métodos que los implementan:

   bool              CreateIconButton(CButton &button,string text,const int x_gap,const int y_gap);
   //--- Etiqueta de texto
   bool              CreateLabel(CTextLabel &text_label,const int x_gap,const int y_gap,string label_text);

Ahora, añadimos su implementación al final del archivo StepWindow.mqh.

//+------------------------------------------------------------------+
//| Crea un botón con imagen                                         |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\plus.bmp"
bool CProgram::CreateIconButton(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_step_window);
//--- 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\\plus.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(0,button);
   return(true);
}
//+------------------------------------------------------------------+
//| Crea la etiqueta de texto                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateLabel(CTextLabel &text_label,const int x_gap,const int y_gap,string label_text)
{
//--- Guardamos el puntero a la ventana
   text_label.MainPointer(m_step_window);
//---
   text_label.Font(m_base_font);
   text_label.FontSize(m_base_font_size);
   text_label.XSize(120);
   text_label.BackColor(m_background);
//--- Creación del botón
   if(!text_label.CreateTextLabel(label_text,x_gap,y_gap))
      return(false);
//--- Añadimos el puntero al control a la base
   CWndContainer::AddToElementsArray(0,text_label);
   return(true);
}
//+------------------------------------------------------------------+

Completamos el método CreateStepWindow() para que sean creadas al iniciar la aplicación.

//---
   if(!CreateIconButton(m_add_signal,"Add Signal",10,30))
      return(false);
   if(!CreateLabel(m_signal_header,10,30+30+10,"Signal List"))
      return(false);   

Ahora, para que no se muestren en el inicio, es decir, en el primer paso, después de crear la interfaz mediante la llamada al método CreateGUI(), añadimos dos líneas que ocultan estos controles al final del cuerpo de este método.

//+------------------------------------------------------------------+
//| Crea la interfaz gráfica del programa                            |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//--- Paso 1-3
   if(!CreateStepWindow("Signal Monitor Step 1: Choose Symbols"))
      return(false);
//--- Terminar la creación de GUI
   CWndEvents::CompletedGUI();
   m_back_button.Hide();
   m_add_signal.Hide();
   m_signal_header.Hide();
   return(true);
}

Ahora, implementamos el método ToStep_3() añadido anteriormente que limpia la visualización del paso anterior y muestra los controles creados:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::ToStep_3(void)
{
//--- Comprobando la selección de un timeframe como mínimo
   int cnt=0;
   for(int i=0; i<21; i++)
   {
      if(m_checkbox[i].IsPressed())
         cnt++;
   }
   if(cnt<1)
   {
      MessageBox("No timeframes selected!","Warning");
      return;
   }
//---
   m_step_window.LabelText("Signal Monitor Step 3: Create Signals");
   m_step_window.Update(true);
   m_next_button.LabelText("Create");
   m_next_button.Update(true);
//--- Ocultamos los controles del Paso 2
   for(int i=0; i<21; i++)
   {
      if(i<3)
         m_currency_set[i].Hide();
      m_checkbox[i].Hide();
   }
//---
   m_add_signal.Show();
   m_signal_header.Show();
//---
   m_current_step=3;
}

Volvemos a compilar el proyecto y vamos al paso 3 pulsando el botón Next dos veces. Y no olvide hacer la selección en dos primeros pasos, de lo contrario, la aplicación no nos dejará ir al tercer paso.

Fig. 8 Implementación del segundo paso de los ajustes de la aplicación.

La ventana para generar y editar señales comerciales

Todos los componentes visuales para trabajar con señales comerciales van a encontrarse en el archivo SetWindow.mqh, por eso, vamos a abrirlo. Por ahora tiene incluido sólo el archivo Program.mqh a través de la línea de comando #include. Primero, hay que crear una ventana separada que servirá de base para todos los demás controles de la creación y configuración. Para eso, vamos a Program.mqh y en la propia clase declaramos la variable m_set_window, que es una instancia de la clase CWindow, y añadimos el método para crear la ventana CreateSetWindow():

   CWindow           m_set_window;

   bool              CreateSetWindow(const string caption_text);

Después de eso, volvemos al archivo SetWindow.mqh e implementamos el método creado.

//+------------------------------------------------------------------+
Crea la ventana para generar y editar señales comerciales            |
//+------------------------------------------------------------------+
bool CProgram::CreateSetWindow(const string text)
{
//--- Añadimos el puntero de la ventana al array de ventanas
   CWndContainer::AddWindow(m_set_window);
//--- Propiedades
   m_set_window.XSize(568);
   m_set_window.YSize(555);
//--- Coordenadas
   int x=int(ChartGetInteger(m_chart_id,CHART_WIDTH_IN_PIXELS)-m_set_window.XSize())/2;
   int y=30;
//---
   m_set_window.CaptionHeight(22);
   m_set_window.IsMovable(true);
   m_set_window.CaptionColor(m_caption);
   m_set_window.CaptionColorLocked(m_caption);
   m_set_window.CaptionColorHover(m_caption);
   m_set_window.BackColor(m_background);
   m_set_window.FontSize(m_base_font_size);
   m_set_window.Font(m_base_font);
   m_set_window.WindowType(W_DIALOG);
//--- Creando el formulario
   if(!m_set_window.CreateWindow(m_chart_id,m_subwin,text,x,y))
      return(false);
   return(true);
}
//+------------------------------------------------------------------+

Ahora, vinculamos la ventana recién creada con los controles ya existentes en la aplicación. Primero, añadimos la llamada a este método en la creación de la interfaz CreateGUI(). Y al pulsar en el botón del tercer paso Add Signal, debe abrirse nuestra ventana.

//+------------------------------------------------------------------+
//| Crea la interfaz gráfica del programa                            |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//--- Paso 1-3
   if(!CreateStepWindow("Signal Monitor Step 1: Choose Symbols"))
      return(false);
//--- Ventana para crear y editar
   if(!CreateSetWindow("Signal Monitor Edit Signal"))
      return(false);
//--- Terminar la creación de GUI
   CWndEvents::CompletedGUI();
   m_back_button.Hide();
   m_add_signal.Hide();
   m_signal_header.Hide();
   return(true);
}

En el método OnEvent(), en el evento de la pulsación del botón:

      //--- Pulsación en el botón Añadir señal
      if(lparam==m_add_signal.Id())
      {
         m_set_window.OpenWindow();
      }

Compilamos el proyecto y vemos el resultado: al ir al paso 3 y al pulsar en el botón Add Signal, se abre una ventana de diálogo para crear y editar.

Fig. 9 Implementación de la ventana para crear y editar una señal comercial.

El primer control de la ventana será la selección del tipo del indicador cuyos valores se usarán en la formación de la señal comercial. El orden de la adición del control es el mismo: creamos la instancia de la clase y creamos el método de su implementación.

   //--- Menú desplegable
   CComboBox         m_indicator_type;
   //--- Crea el menú desplegable
   bool              CreateIndicatorType(const int x_gap,const int y_gap);

La implementación del método va a ubicarse en el mismo archivo donde se ubica la ventana creada anteriormente. 

//+------------------------------------------------------------------+
//| 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);
//--- Array de valores de los puntos en la lista
   string pattern_names[7]=
   {
      "ATR","CCI","DeMarker","Force Ind","WPR","RSI","Momentum"
   };
//--- 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(7);
   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(26);
//--- Guardamos los valores de los puntos en la lista del cuadro combinado (combobox)
   for(int i=0; i<7; 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(5);
//--- 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);
}

La única adición es que al final del cuerpo del método de la creación de la ventana CreateSetWindow(), llamamos al método de la creación de la selección del tipo del indicador CreateIndicatorType().

...
//--- Creando el formulario
   if(!m_set_window.CreateWindow(m_chart_id,m_subwin,text,x,y))
      return(false);
//--- Tipo del indicador
   if(!CreateIndicatorType(10,22+10))
      return(false);

Como resultado, obtenemos un control de la interfaz que permite hacer una selección entre 7 indicadores estándar del tipo oscilador.

Fig. 10 Control para seleccionar el tipo del indicador.

Yo creo que la consideración detallada de la adición de cada control de la interfaz es bastante obvia. Por tanto, a continuación, vamos a considerar los conjuntos de los controles agrupados en dos secciones: Ajustes del indicador (Indicator Settings) y Ajustes de la señal (Signal Settings). Todos los indicadores seleccionados desde el conjunto estándar tienen los ajustes comunes, como el período (Period) y el precio aplicado (Applied Price). Por eso, para la primera sección vamos a necesitar una etiqueta de texto, campo de edición y menú desplegable de la selección del precio aplicado para calcular los valores del indicador. Añadimos las variables necesarias y los métodos para su creación a la clase CProgram.

//--- Etiqueta de texto
   CTextLabel        m_set_header[5];
//--- Campos de edición
   CTextEdit         m_period_edit;
//--- Menú desplegable
   CComboBox         m_applied_price;
...
   bool              CreateSetLabel(CTextLabel &text_label,const int x_gap,const int y_gap,string label_text);
   bool              CreatePeriodEdit(const int x_gap,const int y_gap);
   bool              CreateAppliedPrice(const int x_gap,const int y_gap);

Implementamos los métodos añadidos e, igual como en el caso con el tipo del indicador, llamamos a los métodos al final del cuerpo del método CreateSetWindow(). Ahora, nos hace falta añadir un mecanismo que haga que los controles creados cambien el conjunto de los ajustes propuestos cuando se selecciona uno de los tipos del indicador. Para este propósito, añadimos una sección con el evento de la pulsación de un elemento del menú desplegable al método OnEvent(), y configuramos su propio conjunto de ajustes para cada indicador:

//--- Selección de un elemento en la lista del combobox
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
   {
      int index=m_indicator_type.GetListViewPointer().SelectedItemIndex();
      switch(index)
      {
      case  0:
         m_period_edit.LabelText("ATR Period");
         m_applied_price.Hide();
         break;
      case  1:
         m_period_edit.LabelText("CCI Period");
         m_applied_price.Show();
         break;
      case  2:
         m_period_edit.LabelText("DeMarker Period");
         m_applied_price.Hide();
         break;
      case  3:
         m_period_edit.LabelText("Force Index Period");
         m_applied_price.Show();
         break;
      case  4:
         m_period_edit.LabelText("WPR Period");
         m_applied_price.Hide();
         break;
      case  5:
         m_period_edit.LabelText("RSI Period");
         m_applied_price.Show();
         break;
      case  6:
         m_period_edit.LabelText("Momentum Period");
         m_applied_price.Hide();
         break;
      default:
         m_period_edit.LabelText("RSI Period");
         m_applied_price.Hide();
         break;
      }
      m_period_edit.Update(true);
   }

Compilamos el proyecto y vemos el resultado:

Fig. 11 Implementación de los ajustes del indicador.

A continuación, vamos a la segunda sección de la edición de la señal. Se compone del encabezado y ocho ajustes:

  • Regla de la señal.
  • Ajuste del valor de la etiqueta de texto en el bloque de la señal.
  • Ajuste del color de la etiquetas de texto.
  • Uso y ajuste del color del fondo.
  • Uso y ajuste del color del borde.
  • Uso, ajuste del color y valor de la ayuda encima del bloque de la señal.
  • Uso de la etiqueta gráfica y su aspecto en el bloque de la señal.
  • Selección de los timeframes disponibles para buscar la señal especificada.

Para añadir el encabezado de esta sección, basta con añadir el siguiente código al final del cuerpo del método CreateSetWindow(), porque antes ya hemos creado un método para visualizar el encabezado, y viene bastante bien para crear otro tanto, pero con otros valores de los argumentos:

//--- Ajustes de la señal
   if(!CreateSetLabel(m_set_header[1],10,22+10+4*(25+10),"2.Signal Settings"))
      return(false);

La regla de la señal se compone de dos controles: el menú desplegable y el campo de edición de un valor numérico. Por eso, añadimos las instancias de las clases y los métodos que las implementan a la clase CProgram:

CTextEdit         m_rule_value;
CComboBox         m_rule_type;
...
bool              CreateRuleValue(const int x_gap,const int y_gap);
bool              CreateRule(const int x_gap,const int y_gap);

Añadimos su implementación a SetWindow.mqh, y las llamamos en el cuerpo del método CreateSetWindow().

   //--- Ajustes de las condiciones
   if(!CreateRuleValue(130,22+10+5*(25+10)))
      return(false);
   if(!CreateRule(10,22+10+5*(25+10)))
      return(false);

Luego, siguiendo la lista, añadimos cada uno de los ajustes de la misma manera. Como resultado, la implementación completa del método CreateSetWindow() será la siguiente:

//+------------------------------------------------------------------+
Crea la ventana para generar y editar señales comerciales
//+------------------------------------------------------------------+
bool CProgram::CreateSetWindow(const string text)
{
//--- Añadimos el puntero de la ventana al array de ventanas
   CWndContainer::AddWindow(m_set_window);
//--- Propiedades
   m_set_window.XSize(568);
   m_set_window.YSize(575);
//--- Coordenadas
   int x=int(ChartGetInteger(m_chart_id,CHART_WIDTH_IN_PIXELS)-m_set_window.XSize())/2;
   int y=30;
//---
   m_set_window.CaptionHeight(22);
   m_set_window.IsMovable(true);
   m_set_window.CaptionColor(m_caption);
   m_set_window.CaptionColorLocked(m_caption);
   m_set_window.CaptionColorHover(m_caption);
   m_set_window.BackColor(m_background);
   m_set_window.FontSize(m_base_font_size);
   m_set_window.Font(m_base_font);
   m_set_window.WindowType(W_DIALOG);
//--- Creando el formulario
   if(!m_set_window.CreateWindow(m_chart_id,m_subwin,text,x,y))
      return(false);
//--- Tipo del indicador
   if(!CreateIndicatorType(10,22+10))
      return(false);
//--- Ajustes del indicador seleccionado
   if(!CreateSetLabel(m_set_header[0],10,22+10+26+10,"1.Indicator Settings"))
      return(false);
   if(!CreatePeriodEdit(10,22+10+2*(25+10)))
      return(false);
   if(!CreateAppliedPrice(10,22+10+3*(25+10)))
      return(false);
//--- Ajustes de la señal
   if(!CreateSetLabel(m_set_header[1],10,22+10+4*(25+10),"2.Signal Settings"))
      return(false);
//--- Ajustes de las condiciones
   if(!CreateRuleValue(130,22+10+5*(25+10)))
      return(false);
   if(!CreateRule(10,22+10+5*(25+10)))
      return(false);
//--- Ajustes de la visualización de la etiqueta
   if(!CreateSetLabel(m_set_header[2],10,22+10+6*(25+10),"Label"))
      return(false);
   if(!CreateButton2(m_label_button[0],"Value",100,22+7+6*(25+10)))
      return(false);
   if(!CreateButton2(m_label_button[1],"Text",100+80,22+7+6*(25+10)))
      return(false);
//--- Ajustes de la visualización del color de la etiqueta
   if(!CreateColorButton(m_color_button[0],10,22+10+7*(25+10),"Label Color"))
      return(false);
   if(!CreateTextBox(180+80+10,22+7+6*(25+10)))
      return(false);
//---
   if(!CreateColorButton(m_color_button[1],25,22+10+8*(25+10),""))
      return(false);
   if(!CreateSetCheckBox(m_set_param[0],10,22+10+8*(25+10),"Use Background"))
      return(false);
   if(!CreateColorButton(m_color_button[2],25,22+10+9*(25+10),""))
      return(false);
   if(!CreateSetCheckBox(m_set_param[1],10,22+10+9*(25+10),"Use Border"))
      return(false);
   if(!CreateColorButton(m_color_button[3],25,22+10+10*(25+10),""))
      return(false);
   if(!CreateSetCheckBox(m_set_param[2],10,22+10+10*(25+10),"Use Tooltip"))
      return(false);
   if(!CreateTooltipText(240,22+10+10*(25+10)))
      return(false);
   if(!CreateSetCheckBox(m_set_param[3],10,22+10+11*(25+10),"Use Image"))
      return(false);
   if(!CreateImageSlider(125,22+10+11*(25+10)))
      return(false);
//--- Selección de timeframes
   if(!CreateSetLabel(m_set_header[4],10,22+10+12*(25+10),"Timeframes"))
      return(false);
//---
   y=22+10+13*(25+10);
   int k=0;
   for(int i=0; i<21; i++)
   {
      if(i==11)
      {
         y=22+20+14*(25+10);
         k=0;
      }
      if(!CreateTfButton(m_tf_button[i],40*k+10,y))
         return(false);
      k++;
   }
   return(true);
}

Puede encontrar la lista completa de las adiciones y la implementación en los archivos adjuntos al final del artículo. Hemos decidido no introducir las acciones similares de la adición de cada control, porque se puede perder el hilo de nuestras acciones. Pues bien, una vez hechas todas las adiciones, la apariencia de la ventana para crear y editar será la siguiente:

Fig. 12 Implementación de los controles de la interfaz de la ventana para editar las señales.

Como podemos observar, los botones de la selección de los timeframes están en blanco, también hay que ajustar las interacciones básicas de los controles:

  • Los botones de los timeframes tienen que mostrar solamente aquel número que ha sido seleccionado en el Paso 2.
  • Al seleccionar el botón Valor (Value), el botón Texto tiene que soltarse (Text) y el campo de edición de la etiqueta de texto tiene que desaparecer.
  • Al pulsar en el botón de la selección del color, debe aparecer la ventana con la paleta de colores.
  • Al quitar las marcas del botón de la llamada a la paleta, el campo de la ayuda y la selección de la etiqueta gráfica debe desactivarse.

Para implementar la tarea de la visualización de los timeframes seleccionados, creamos el método RebulidTimeframes() en la sección privada de nuestra clase base, y lo implementamos:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::RebuildTimeframes(void)
{
//--- Calculamos el número de los timeframes seleccionados
   int cnt=0;
   for(int i=0; i<21; i++)
   {
      if(m_checkbox[i].IsPressed())
         cnt++;
   }
   ArrayResize(m_timeframes,cnt);
   cnt=0;
//--- Guardamos los timeframes seleccionados en el array
   for(int i=0; i<21; i++)
   {
      if(m_checkbox[i].IsPressed())
      {
         m_timeframes[cnt]=m_checkbox[i].LabelText();
         cnt++;
      }
   }
//---
   for(int i=0; i<cnt; i++)
      m_tf_button[i].IsLocked(false);
//---
   for(int i=0; i<cnt; i++)
   {
      m_tf_button[i].LabelText(m_timeframes[i]);
      m_tf_button[i].Update(true);
   }
   //---
   for(int i=cnt; i<21; i++)
      m_tf_button[i].IsLocked(true);
}

Ahora, completamos el código de la ventana de edición al pulsar en el botón Add Signal.

      //--- Pulsación en el botón Añadir señal
      if(lparam==m_add_signal.Id())
      {
         m_set_window.OpenWindow();
         if(m_set_window.IsAvailable())
            RebuildTimeframes();
      }

Vamos al siguiente momento cuando ajustamos la interacción de los botones Value y Text. Para eso, en el método OnEvent(), en el evento de la pulsación del botón, añadimos el siguiente código:

//---
      if(lparam==m_label_button[0].Id())
      {
         if(m_label_button[0].IsPressed())
         {
            m_label_button[1].IsPressed(false);
            m_label_button[1].Update(true);
         }
         m_text_box.Hide();
      }
      if(lparam==m_label_button[1].Id())
      {
         if(m_label_button[1].IsPressed())
         {
            m_label_button[0].IsPressed(false);
            m_label_button[0].Update(true);
         }
         m_text_box.Show();
      }

Ahí se cumple la siguiente condición: si uno de los botones está pulsado, el otro tiene que estar soltado. En este caso, si el botón Text está soltado, ocultamos el campo de edición. La pulsación en el botón de la llamada a la paleta de colores y su visualización se completan en el mismo sitio, ya que eso también forma parte del evento de la pulsación en el botón. Tenemos cuatro botones y tenemos declarado un array de cuatro elementos, por eso, podemos escribir el acceso a ellos en el ciclo.

      //---
      for(int i=0; i<4; i++)
      {
         if(lparam==m_color_button[i].Id())
         {
            m_color_picker.ColorButtonPointer(m_color_button[i]);
            return;
         }
      }

La última interacción será el bloqueo de los controles al desmarcar los checkbox. Para eso, añadimos el monitoreo de los eventos de la pulsación en el checkbox al método OnEvent(), e implementamos ahí las interacciones planeadas.

//--- Pulsación en el checkbox
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_CHECKBOX)
   {
      //---
      for(int i=0; i<3; i++)
      {
         if(lparam==m_set_param[i].Id())
         {
            m_color_button[i+1].IsLocked(!m_set_param[i].IsPressed());
            if(m_set_param[2].IsPressed())
               m_tooltip_text.Show();
            else
               m_tooltip_text.Hide();
         }
      }
      //---
      if(lparam==m_set_param[3].Id())
         m_pictures_slider.IsLocked(!m_set_param[3].IsPressed());
   }

Bien, compilamos de nuevo el proyecto y veremos el resultado.

Fig. 13 Implementación de la interacción de los controles de la interfaz de la ventana de edición de las señales.


Monitoreo de señales comerciales

El último paso de esta etapa del desarrollo será la creación de la ventana del futuro monitoreo de señales comerciales. Aquí, es necesario tomar en consideración los ajustes básicos que ya han sido introducidos en la versión actual. Antes de la creación, es necesario plantear algunas tareas, para que al lector se le quede claro para qué propósito se crean unos u otros controles:

  • Crear las líneas con etiquetas de texto de los símbolos seleccionados en el primer paso para el trabajo.
  • Crear las columnas de los encabezados con las etiquetas de texto de los timeframes seleccionados en el segundo paso.
  • Ajustar los tamaños de la ventana para los controles creados según las líneas y columnas. Se trata en cierto modo del autoajuste.

Para crear las etiquetas de texto de los timeframes y los símbolos, vamos a crear dos arrays de las instancias de la clase CTextLabel, y añadimos dos métodos que los implementan a la clase CProgram.

   CTextLabel        m_timeframe_label[];
   CTextLabel        m_symbol_label[];
   bool              CreateTimeframeLabel(CTextLabel &text_label,const int x_gap,const int y_gap,string label_text);
   bool              CreateSymbolLabel(CTextLabel &text_label,const int x_gap,const int y_gap,string label_text);

Ahora, implementamos los métodos creados en el archivo MainWindow.mqh:

//+------------------------------------------------------------------+
//| Crea la etiqueta de texto                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateTimeframeLabel(CTextLabel &text_label,const int x_gap,const int y_gap,string label_text)
{
//--- Guardamos el puntero a la ventana
   text_label.MainPointer(m_step_window);
//---
   text_label.Font(m_base_font);
   text_label.FontSize(m_base_font_size);
   text_label.XSize(40);
   text_label.BackColor(m_background);
//--- Creación del botón
   if(!text_label.CreateTextLabel(label_text,x_gap,y_gap))
      return(false);
//--- Añadimos el puntero al control a la base
   CWndContainer::AddToElementsArray(0,text_label);
   return(true);
}
//+------------------------------------------------------------------+
//| Crea la etiqueta de texto                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateSymbolLabel(CTextLabel &text_label,const int x_gap,const int y_gap,string label_text)
{
//--- Guardamos el puntero a la ventana
   text_label.MainPointer(m_step_window);
//---
   text_label.Font(m_base_font);
   text_label.FontSize(m_base_font_size);
   text_label.XSize(100);
   text_label.BackColor(m_background);
//--- Creación del botón
   if(!text_label.CreateTextLabel(label_text,x_gap,y_gap))
      return(false);
//--- Añadimos el puntero al control a la base
   CWndContainer::AddToElementsArray(0,text_label);
   return(true);
}

Antes de proceder a la visualización de la interfaz de la ventana, es necesario crear dos variables importantes en la sección privada, así como dos métodos:

  int               OnInit()
   string            m_symbols[];
   void              ToMonitor(void);
   void              AutoResize(const int x_size,const int y_size);

La variable m_total_signals se precisa para comprobar la creación por lo menos de una señal antes de crear la ventana del monitoreo. El array m_symbols[] va a contener la selección de los símbolos desde el primer paso de la configuración. El método ToMonitor() se encargará de la construcción de la interfaz del monitoreo, mientras que AutoResize() ajustará los tamaños de la ventana para los controles creados. Vamos a implementar los métodos declarados:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::ToMonitor(void)
{
//--- Comprobando la presencia por lo menos de una señal
   if(m_total_signals<1)
   {
      MessageBox("No signals created!","Warning");
      return;
   }
//--- Ocultamos el paso 3
   m_add_signal.Hide();
   m_signal_header.Hide();
   m_back_button.Hide();
   m_next_button.Hide();
//--- Cambiamos el encabezado de la ventana
   m_step_window.LabelText("Signal Monitor");
   m_step_window.Update(true);
//--- 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()+25+i*25,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+50*i,m_step_window.CaptionHeight()+3,m_timeframes[i]))
         return;
      m_timeframe_label[i].Update(true);
   }
//--- Cambiamos las dimensiones de la ventana
   AutoResize(m_timeframe_label[tf-1].XGap()+m_timeframe_label[tf-1].XSize()+5,m_symbol_label[sy-1].YGap()+m_symbol_label[sy-1].YSize()+5);
}

En el listado podemos observar que los datos del array m_symbols se usan en la sección Símbolos, pero estos datos no se recopilan y no se preparan. Habrá que corregirlo. Vamos al método ToStep_2(), y tras comprobar la selección de un símbolo como mínimo, recordamos la selección de los símbolos en el primer paso en nuestro array:

//--- Calculamos el número de los símbolos seleccionados
   ArrayResize(m_symbols,cnt);
   cnt=0;
//--- Guardamos los timeframes seleccionados en el array
   for(int i=0; i<m_all_symbols; i++)
   {
      if(m_checkbox[i].IsPressed())
      {
         m_symbols[cnt]=m_checkbox[i].LabelText();
         cnt++;
      }
   }

Ahora, creamos el método para autoajustar la ventana. 

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

Antes de verificar la capacidad funcional de nuestro proyecto, hay que asignar un cero a la variable m_total_signals dentro del constructor de la clase CProgram. Otro momento importante es el complemento en el método OnEvent(), en la sección del evento de la pulsación del botón. 

      //--- Navegación
      if(lparam==m_back_button.Id())
      {
         //--- Ir atrás
         if(m_current_step==2)
            ToStep_1();
         //--- Volver al Paso 2
         else if(m_current_step==3)
            ToStep_2();
      }
      //--- Ir adelante
      if(lparam==m_next_button.Id())
      {
         //--- Ir al Paso 2
         if(m_current_step==1)
            ToStep_2();
         //--- Ir al Paso 3
         else if(m_current_step==2)
            ToStep_3();
         //--- Ir al monitoreo
         else if(m_current_step==3)
            ToMonitor();
      }

Aquí, añadimos la llamada al método creado ToMonitor() al pulsar en el botón Siguiente. En el paso 3, va a tener el nombre Create (Crear). Ahora, compilamos el proyecto e iniciamos la aplicación:

  • En el primer paso, seleccionamos Crosses.
  • En el segundo paso, seleccionamos el conjunto Senior.
  • En el tercer paso, pulsamos en el botón Add Signal. 
  • Después de eso, cerramos la ventana de la creación de la señal y pulsamos Create.

Fig. 14 Configuración base del monitoreo.

En el siguiente artículo, estudiaremos la implementación del propio algoritmo de la búsqueda de las señales comerciales ajustadas en las condiciones creadas durante el inicio primario.

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 15.


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

Traducción del ruso hecha por MetaQuotes Software Corp.
Artículo original: https://www.mql5.com/ru/articles/7528

Archivos adjuntos |
MQL5.zip (1645.42 KB)
Implementando OLAP en la negociación (Parte 3): analizando las cotizaciones con el fin de desarrollar las estrategias comerciales Implementando OLAP en la negociación (Parte 3): analizando las cotizaciones con el fin de desarrollar las estrategias comerciales

En este artículo, continuaremos analizando la tecnología OLAP en aplicación al trading, ampliando la funcionalidad representada en dos artículos anteriores. Esta vez, al análisis operativo se le someterán las cotizaciones. Mostraremos cómo se hacen y se comprueban las hipótesis sobre las estrategias comerciales a base de los indicadores agregados del historial. Además, presentaremos los Asesores Expertos para analizar las regularidades barra por barra y el trading adaptativo.

Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XXVI): Trabajando con las solicitudes comerciales pendientes - primera implementación (apertura de posiciones) Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XXVI): Trabajando con las solicitudes comerciales pendientes - primera implementación (apertura de posiciones)

En el presente artículo, vamos a organizar el guardado de ciertos datos en el valor del número mágico de las órdenes y posiciones, y también implementaremos las solicitudes comerciales. Para comprobar el concepto, crearemos una primera solicitud pendiente de prueba para abrir posiciones de mercado al recibir del servidor un error que requiera la espera y el envío de una solicitud repetida.

SQLite: trabajo nativo con bases de datos en SQL en MQL5 SQLite: trabajo nativo con bases de datos en SQL en MQL5

El desarrollo de estrategias comerciales está relacionado con el procesamiento de grandes volúmenes de datos. Ahora, usted podrá trabajar directamente en MQL5 con bases de datos con la ayuda de solicitudes SQL basadas en SQLite. Una ventaja importante de este motor es que toda la base de datos se encuentra en un único archivo estándar, ubicado en la computadora del usuario.

Optimización móvil continua (Parte 2): Mecanismo de creación de informes de optimización para cualquier robot Optimización móvil continua (Parte 2): Mecanismo de creación de informes de optimización para cualquier robot

Si el primer artículo de la serie estaba dedicado a la creación de la biblioteca DLL que utilizaremos en nuestro optimizador automático y en el robot, este estará completamente dedicado al lenguaje MQL5.