Cómo añadir rápidamente un panel de control a un indicador o asesor

Vladimir Karputov | 26 febrero, 2016

Para qué es necesario un panel gráfico

Su programa - indicador o asesor - MQL4/MQL5 puede ser uno de los mejores y ejecutar eficazmente todas las tareas que se le encarguen. Pero siempre hay forma de mejorarlo un poco. Normalmente, en el 99% de los casos, para realizar cualquier cambio en los parámetros de entrada de un programa, el usuario tiene que entrar en sus ajustes. ¿Quiere arreglárselas sin hacer esto?

Es posible hacerlo creando su propio panel de control basado en las clases de la Biblioteca estándar. Esto le permitirá cambiar los ajustes sin reiniciar el programa. Además, este enfoque revitalizará el programa, distinguiéndolo de otros de forma positiva. Podrá ver ejemplos de paneles gráficos en el Mercado.

En este artículo le enseñaré a añadir sin ayuda de nadie un sencillo panel a un programa MQL4/MQL5. Usted ya sabe cómo enseñar a un programa a leer los parámetros de entrada y a reaccionar a los cambios de sus valores.

 

1. Cómo unir un indicador y un panel


1.1. Indicador

El indicador "NewBar.mq5" ejecuta una acción: al aparecer una nueva barra, realiza una nueva entrada en el registro de expertos del terminal. El código del indicador se muestra más abajo:

//+------------------------------------------------------------------+
//|                                                       NewBar.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property description "The indicator identifies a new bar"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   static datetime prev_time;
//--- revert access to array time[] - do it like in timeseries 
   ArraySetAsSeries(time,true);
//--- first calculation or number of bars was changed
   if(prev_calculated==0)// first calculation
     {
      prev_time=time[0];
      return(rates_total);
     }
//---
   if(time[0]>prev_time)
      Print("New bar!");
//---
   prev_time=time[0];
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Hablemos con un poco más de detalle sobre el funcionamiento del indicador "NewBar.mq5".

En la función OnCalculate() se declara la variable estática "prev_time", en esta variable se guarda la hora de apertura "time[0]". En la siguiente pasada se compara la hora de apertura"time[0]" con la variable "prev_time", es decir, tiene lugar la comparación de la hora de apertura "time[0]" en el tick actual con la hora de apertura "time[0]" en el tick anterior. En caso de que se cumpla la condición

if(time[0]>prev_time)

consideraremos que se ha detectado una nueva barra.

En el siguiente ejemplo analizaremos con detalle cómo el indicador "NewBar.mq5" detecta una nueva barra:

New bar

Fig. 1. Proceso de detección de una nueva barra en el indicador

Estudiaremos 10 ticks en un mercado muy tranquilo.

Ticks del 1 al 3 inclusive: la hora de apertura de la barra con el índice "0" (time[0]) es igual a la hora que se ha guardado en la variable estática prev_time, entonces significa que no hay barras nuevas.

Tick №4: este tick ha llegado en la nueva barra. Al entrar en la función OnCalculate() en time[0] estará la hora de apertura de la barra (2015.12.01 00:02:00), y la variable prev_time todavía guarda la hora del tick anterior (2015.12.01 00:01:00). Por eso, al comprobar las condiciones de time[0]>prev_time detectamos una nueva barra. Antes de salir de OnCalculate(), en la variable prev_time se guardará la hora time[0] (2015.12.01 00:02:00).

Ticks del 5 al 8 inclusive: la hora de apertura de la barra con el índice "0" (time[0]) es igual a la hora que se ha guardado en la variable estática prev_time, entonces significa que no hay barras nuevas.

Tick №9: este tick ha llegado en la nueva barra. Al entrar en la función OnCalculate() en time[0] estará la hora de apertura de la barra (2015.12.01 00:03:00), y la variable prev_time todavía guarda la hora del tick anterior (2015.12.01 00:02:00). Por eso, al comprobar las condiciones de time[0]>prev_time detectamos una nueva barra. Antes de salir de OnCalculate(), en la variable prev_time se guardará la hora time[0] (2015.12.01 00:03:00).

Ticks 10: la hora de apertura de la barra con el índice "0" (time[0]) es igual a la hora que se ha guardado en la variable estática prev_time, entonces significa que no hay barras nuevas.


1.2. Panel

Todas los parámetros de construcción del panel: la cantidad, las dimensiones y las coordenadas de los elementos de control se concentran en el archivo de inclusión "PanelDialog.mqh". El archivo "PanelDialog.mqh" es la clase de implementación del panel.

El panel tiene el aspecto siguiente:

Panel

Fig. 2. Panel

El código del archivo de inclusión "PanelDialog.mqh" se muestra más abajo:

//+------------------------------------------------------------------+
//|                                                  PanelDialog.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include <Controls\Dialog.mqh>
#include <Controls\CheckGroup.mqh>
//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
//--- indents and gaps
#define INDENT_LEFT                         (11)      // indent from left (with allowance for border width)
#define INDENT_TOP                          (11)      // indent from top (with allowance for border width)
#define INDENT_BOTTOM                       (11)      // indent from bottom (with allowance for border width)
//--- for buttons
#define BUTTON_WIDTH                        (100)     // size by X coordinate
//+------------------------------------------------------------------+
//| Class CControlsDialog                                            |
//| Usage: main dialog of the Controls application                   |
//+------------------------------------------------------------------+
class CControlsDialog : public CAppDialog
  {
private:
   CCheckGroup       m_check_group;                   // CCheckGroup object

public:
                     CControlsDialog(void);
                    ~CControlsDialog(void);
   //--- create
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   //--- chart event handler
   virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);

protected:
   //--- create dependent controls
   bool              CreateCheckGroup(void);
   //--- handlers of the dependent controls events
   void              OnChangeCheckGroup(void);
  };
//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CControlsDialog)
ON_EVENT(ON_CHANGE,m_check_group,OnChangeCheckGroup)
EVENT_MAP_END(CAppDialog)
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CControlsDialog::CControlsDialog(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CControlsDialog::~CControlsDialog(void)
  {
  }
//+------------------------------------------------------------------+
//| Create                                                           |
//+------------------------------------------------------------------+
bool CControlsDialog::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2)
  {
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
      return(false);
//--- create dependent controls
   if(!CreateCheckGroup())
      return(false);
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Create the "CheckGroup" element                                  |
//+------------------------------------------------------------------+
bool CControlsDialog::CreateCheckGroup(void)
  {
//--- coordinates
   int x1=INDENT_LEFT;
   int y1=INDENT_TOP;
   int x2=x1+BUTTON_WIDTH;
   int y2=ClientAreaHeight()-INDENT_BOTTOM;
//--- create
   if(!m_check_group.Create(m_chart_id,m_name+"CheckGroup",m_subwin,x1,y1,x2,y2))
      return(false);
   if(!Add(m_check_group))
      return(false);
   m_check_group.Alignment(WND_ALIGN_HEIGHT,0,y1,0,INDENT_BOTTOM);
//--- fill out with strings
   if(!m_check_group.AddItem("Mail",1<<0))
      return(false);
   if(!m_check_group.AddItem("Push",1<<1))
      return(false);
   if(!m_check_group.AddItem("Alert",1<<2))
      return(false);
   Comment(__FUNCTION__+" : Value="+IntegerToString(m_check_group.Value()));
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CControlsDialog::OnChangeCheckGroup(void)
  {
   Comment(__FUNCTION__+" : Value="+IntegerToString(m_check_group.Value()));
  }
//+------------------------------------------------------------------+

Como puede ver en la clase de nuestro panel no hay métodos para establecer y leer las propiedades del estado de los interruptores con fijación independiente.

Nuestra tarea será: convertir el indicador "NewBar.mq5" en el archivo principal y añadir los parámetros de entrada, por ejemplo, con la ayuda de qué métodos ("Mail", "Push" o "Alert") comunicaremos al usuario que se ha encontrado una nueva barra. Además, deberemos añadir en el archivo de inclusión "PanelDialog.mqh" los métodos para establecer y leer las propiedades del estado de los interruptores con fijación independiente "Mail", "Push" y "Alert".


1.3. Cambiar el indicador

Advertencia: todos los cambios introducidos se destacarán a color.

Primero debemos conectar el archivo de inclusión "PanelDialog.mqh":

#property indicator_chart_window
#property indicator_plots 0
#include "PanelDialog.mqh"
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()

A continuación, añadimos los parámetros de entrada:

#property indicator_chart_window
#property indicator_plots 0
#include "PanelDialog.mqh"
//--- input parameters
input bool     bln_mail=false;      // Notify by email
input bool     bln_push=false;      // Notify by push
input bool     bln_alert=true;      // Notify by alert
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()

Compilamos el indicador (F7 en MetaEditor) y comprobamos la representación de los parámetros de entrada en el terminal:

Input parameters

Fig. 3. Parámetros de entrada del indicador


1.4. Cambiar el panel

En el panel se deben añadir métodos para establecer y leer las propiedades del estado de los interruptores con fijación independiente "Mail", "Push" y "Alert".

Añadimos a la clase del panel los nuevos métodos:

class CControlsDialog : public CAppDialog
  {
private:
   CCheckGroup       m_check_group;                   // CCheckGroup object

public:
                     CControlsDialog(void);
                    ~CControlsDialog(void);
   //--- create
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   //--- chart event handler
   virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- set check for element
   virtual bool      SetCheck(const int idx,const int value);
   //--- get check for element
   virtual int       GetCheck(const int idx) const;

protected:
   //--- create dependent controls
   bool              CreateCheckGroup(void);

Implementación de estos métodos:

//+------------------------------------------------------------------+
//| Set check for element                                            |
//+------------------------------------------------------------------+
bool CControlsDialog::SetCheck(const int idx,const bool check)
  {
   return(m_check_group.Check(idx,check));
  }
//+------------------------------------------------------------------+
//| Get check for element                                            |
//+------------------------------------------------------------------+
int CControlsDialog::GetCheck(const int idx)
  {
   return(m_check_group.Check(idx));
  }


1.5. Etapa final de la unión del indicador y el panel

En el indicador "NewBar.mq5", en la zona donde se declaran las variables globales, declaramos la variable de clase de nuestro panel:

#property indicator_chart_window
#property indicator_plots 0
#include "PanelDialog.mqh"
//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CControlsDialog ExtDialog;
//--- input parameters
input bool     bln_mail=false;      // Notify by email
input bool     bln_push=false;      // Notify by push
input bool     bln_alert=true;      // Notify by alert

y añadimos al final del indicador la función OnChartEvent():

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   ExtDialog.ChartEvent(id,lparam,dparam,sparam);
  }

En la función OnInit() del indicador "NewBar.mq5", creamos el panel y pulsamos de forma programática las casillas de verificación de acuerdo con los parámetros de entrada:

int OnInit()
  {
//--- indicator buffers mapping
//--- create application dialog
   if(!ExtDialog.Create(0,"Notification",0,50,50,180,160))
      return(INIT_FAILED);
//--- run application
   if(!ExtDialog.Run())
      return(INIT_FAILED);
//---
   ExtDialog.SetCheck(0,bln_mail);
   ExtDialog.SetCheck(1,bln_push);
   ExtDialog.SetCheck(2,bln_alert);
//---
   return(INIT_SUCCEEDED);
  }

Con esto damos por terminada la unión del indicador y el panel. En la clase del panel hemos implementado un método para establecer el estado de la casilla de verificación pulsado/sin pulsar (SetCheck) y un método para obtener el estado de la casilla de verificación (GetCheck).

 

2. Conectar el asesor y el panel


2.1. Asesor

Se ha tomado como base un asesor del paquete estándar ...\MQL5\Experts\Examples\MACD\MACD Sample.mq5.


2.2. Panel

Aspecto del panel "PanelDialog2.mqh" después de introducir los cambios definitivos:

Panel number two

Fig. 4. Panel número dos

¿Qué obtenemos tras unir el asesor "MACD Sample.mq5" y el panel "PanelDialog2.mqh"? En el marco temporal actual, en el que funcionará el asesor, se pueden cambiar de forma operativa los parámetros del asesor ("Lots", "Trailing Stop Level (in pips)" y otros), así como los parámetros de notificación sobre los eventos comerciales del asesor ("Mail", "Push", "Alert").

Los parámetros modificados del asesor ("Lots", "Trailing Stop Level (in pips)" y otros) se aplican después de pulsar el botón "Aplicar cambios".". El cambio de los parámetros de notificación sobre los eventos comerciales del asesor ("Mail", "Push", "Alert") se aplica de forma automática, no es necesario pulsar el botón "Aplicar cambios".


2.3. El asesor y el panel pueden comunicarse

Communication EA and panels

Fig. 5. Comunicación del asesor y el panel

Al producirse el inicio, el asesor debe transmitir sus parámetros al panel. El panel, después de clicar en el botón "Aplicar cambios", y si los datos han sido modificados, también deberá retornar los parámetros modificados al asesor para que este se inicialice con los nuevos parámetros.


2.4. Primer paso. Introducir los cambios en el asesor.

El plan de acción es el siguiente: tome el asesor del paquete estándar ...\MQL5\Experts\Examples\MACD\MACD Sample.mq5 y cópielo en su carpeta. Por ejemplo, se puede crear la carpeta "Notification" y copiamos en ella el asesor:

Create a new folder

Fig. 6. Crear una nueva carpeta


En la zona de las variables globales del asesor (no las confunda con las variables globales del terminal) declaramos las nuevas variables, que serán responsables del modo de envío de las notificaciones y de las acciones comerciales del asesor. Preste atención al hecho de que las variables globales, al igual que otras variables externas, tienen el prefijo "Inp":

#include <Trade\PositionInfo.mqh>
#include <Trade\AccountInfo.mqh>
//--- input parameters
input bool     InpMail=false;          // Notify by email
input bool     InpPush=false;          // Notify by push
input bool     InpAlert=true;          // Notify by alert
//---
input double InpLots          =0.1; // Lots
input int    InpTakeProfit    =50;  // Take Profit (in pips)

Un poco más abajo añadimos los duplicados de todas las variables externas del asesor. Los duplicados tendrán el prefijo "Ext":

input int    InpMACDCloseLevel=2;   // MACD close level (in pips)
input int    InpMATrendPeriod =26;  // MA trend period
//--- ext variables
bool           ExtMail;
bool           ExtPush;
bool           ExtAlert;

double         ExtLots;
int            ExtTakeProfit;
int            ExtTrailingStop;
int            ExtMACDOpenLevel;
int            ExtMACDCloseLevel;
int            ExtMATrendPeriod;
//---
int ExtTimeOut=10; // time out in seconds between trade operations
//+------------------------------------------------------------------+
//| MACD Sample expert class                                         |
//+------------------------------------------------------------------+

En OnInit() escribimos la copia de los valores de las variables externas del asesor en los valores de las variables-duplicados:

//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
   ExtMail=InpMail;
   ExtPush=InpPush;
   ExtAlert=InpAlert;

   ExtLots=InpLots;
   ExtTakeProfit=InpTakeProfit;
   ExtTrailingStop=InpTrailingStop;
   ExtMACDOpenLevel=InpMACDOpenLevel;
   ExtMACDCloseLevel=InpMACDCloseLevel;
   ExtMATrendPeriod=InpMATrendPeriod;
//--- create all necessary objects
   if(!ExtExpert.Init())

En esta etapa, en las funciones del asesor CSampleExpert::InitIndicators, CSampleExpert::InitCheckParameters y CSampleExpert::Init se usan las variables externas del asesor con el prefijo "Inp". Necesitamos sustituir en estas funciones las variables externas por sus duplicados (los duplicados tienen en nuestro caso el prefijo "Ext"). Propongo hacer esto con un método bastante original:


Después de realizar la sustitución, se deberá comprobar que las acciones sean correctas, hay que compilar el archivo. No debería haber errores.


2.5. Segundo paso. Introducir los cambios en el panel.

El panel representado en la fig. 4, es una especie de plantilla. En ella todavía no hay funciones para la "comunicación" con el asesor, ni funciones de procesamiento de los datos introducidos. El archivo de la plantilla "PanelDialog2Original.mqh" deberá copiarlo también en "Notification".

Añadimos a la clase del panel las variables internas, en las que guardaremos el estado de todos los datos introducidos. Preste atención a la variable "mModification", se hablará sobre ella en el punto 2.7.

private:
   //--- get check for element
   virtual int       GetCheck(const int idx);
   //---
   bool              mMail;
   bool              mPush;
   bool              mAlert_;
   double            mLots;               // Lots
   int               mTakeProfit;         // Take Profit (in pips)
   int               mTrailingStop;       // Trailing Stop Level (in pips)
   int               mMACDOpenLevel;      // MACD open level (in pips)
   int               mMACDCloseLevel;     // MACD close level (in pips)
   int               mMATrendPeriod;      // MA trend period
   //---
   bool              mModification;       // Values have changed
  };
//+------------------------------------------------------------------+
//| Event Handling                                                   |

Inmediatamente después inicializamos las variables internas en el constructor de clase del panel:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CControlsDialog::CControlsDialog(void) : mMail(false),
                                         mPush(false),
                                         mAlert_(true),
                                         mLots(0.1),
                                         mTakeProfit(50),
                                         mTrailingStop(30),
                                         mMACDOpenLevel(3),
                                         mMACDCloseLevel(2),
                                         mMATrendPeriod(26),
                                         mModification(false)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |

A la función CControlsDialog::Create le añadimos la instalación de un grupo de elementos interruptores de acuerdo con las variables internas:

if(!CreateButtonOK())
      return(false);

//---
   SetCheck(0,mMail);
   SetCheck(1,mPush);
   SetCheck(2,mAlert_);

//--- succeed
   return(true);
  }

 

2.6. Tercer paso. Introducir los cambios en el asesor.

Hasta ahora, el asesor y el panel eran dos archivos independientes el uno del otro. Los uniremos y declararemos la variable de clase de nuestro panel "ExtDialog":

#include <Trade\PositionInfo.mqh>
#include <Trade\AccountInfo.mqh>
#include "PanelDialog2Original.mqh"
//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CControlsDialog ExtDialog;
//--- input parameters
input bool     InpMail=false;          // Notify by email
input bool     InpPush=false;          // Notify by push

Para que el panel funcione y sea visible, hay que crearlo e iniciarlo para su ejecución. También es necesario añadir la función OnChartEvent(), que es el manejador de eventos ChartEvent, y la función OnDeinit(). En el asesor, OnInit() tendrá el aspecto que sigue:

int OnInit(void)
  {
   ExtMail=InpMail;
   ExtPush=InpPush;
   ExtAlert=InpAlert;

   ExtLots=InpLots;
   ExtTakeProfit=InpTakeProfit;
   ExtTrailingStop=InpTrailingStop;
   ExtMACDOpenLevel=InpMACDOpenLevel;
   ExtMACDCloseLevel=InpMACDCloseLevel;
   ExtMATrendPeriod=InpMATrendPeriod;
//--- create all necessary objects
   if(!ExtExpert.Init())
      return(INIT_FAILED);
//--- create application dialog
   if(!ExtDialog.Create(0,"Notification",0,100,100,360,380))
      return(INIT_FAILED);
//--- run application
   if(!ExtDialog.Run())
      return(INIT_FAILED);
//--- succeed
   return(INIT_SUCCEEDED);
  }

En OnDeinit() eliminamos nuestro panel, y la propia función OnDeinit() la escribiremos justo después de OnInit():

//--- succeed
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 
   Comment("");
//--- destroy dialog
   ExtDialog.Destroy(reason);
  }
//+------------------------------------------------------------------+
//| Expert new tick handling function                                |
//+------------------------------------------------------------------+
void OnTick(void)

La función OnChartEvent() la añadiremos al final del asesor (después de la función OnTick):

//--- change limit time by timeout in seconds if processed
         if(ExtExpert.Processing())
            limit_time=TimeCurrent()+ExtTimeOut;
        }
     }
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   ExtDialog.ChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

Ahora ya podemos compilar el asesor y comprobarlo en el gráfico. El asesor se iniciará junto con el panel:

EA and panel

Fig. 7. Asesor junto con el panel


2.7. Tercer paso. Introducir las cambios en el panel. La gran integración

Primero se inicia el asesor, después el usuario establece sus parámetros de entrada, y solo después de ello se inicia el panel. Precisamente por este motivo, en el panel se debe contemplar la función de intercambio de datos entre él y su asesor.

Añadimos el método Initialization() , este método recibe los parámetros e inicializa con estos las variables internas del panel. Declaración:

virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
      //--- initialization
   virtual bool      Initialization(const bool Mail,const bool Push,const bool Alert_,
                                    const double Lots,const int TakeProfit,
                                    const int  TrailingStop,const int MACDOpenLevel,
                                    const int  MACDCloseLevel,const int MATrendPeriod);

protected:
   //--- create dependent controls
   bool              CreateCheckGroup(void);

El propio cuerpo del método (lo ponemos delante de CControlsDialog::GetCheck):

//+------------------------------------------------------------------+
//| Initialization                                                   |
//+------------------------------------------------------------------+
bool CControlsDialog::Initialization(const bool Mail,const bool Push,const bool Alert_,
                                     const double Lots,const int TakeProfit,
                                     const int  TrailingStop,const int MACDOpenLevel,
                                     const int  MACDCloseLevel,const int MATrendPeriod)
  {
   mMail=Mail;
   mPush=Push;
   mAlert_=Alert_;

   mLots=Lots;
   mTakeProfit=TakeProfit;
   mTrailingStop=TrailingStop;
   mMACDOpenLevel=MACDOpenLevel;
   mMACDCloseLevel=MACDCloseLevel;
   mMATrendPeriod=MATrendPeriod;
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Get check for element                                            |
//+------------------------------------------------------------------+
int CControlsDialog::GetCheck(const int idx)

Puesto que las variables internas del panel son analizadas por los datos, ahora tenemos que rellenar correctamente los elementos de control del panel, los campos de búsqueda. Como tenemos seis campos de búsqueda, pondré un ejemplo basado en m_edit1. La línea a la que se adjudicaba el texto era de esta forma:

...
   if(!m_edit1.Text("Edit1"))
...

y ahora es así:

...
   if(!m_edit1.Text(DoubleToString(mLots,2)))
...

De esta forma a cada campo de búsqueda le corresponde su variable interna.

El siguiente método, GetValues() es responsable del retorno de los valores de las variables internas:

virtual bool      Initialization(const bool Mail,const bool Push,const bool Alert_,
                                    const double Lots,const int TakeProfit,
                                    const int  TrailingStop,const int MACDOpenLevel,
                                    const int  MACDCloseLevel,const int MATrendPeriod);
   //--- get values
   virtual void      GetValues(bool &Mail,bool &Push,bool &Alert_,
                               double &Lots,int &TakeProfit,
                               int &TrailingStop,int &MACDOpenLevel,
                               int &MACDCloseLevel,int &MATrendPeriod);

protected:
   //--- create dependent controls
   bool              CreateCheckGroup(void);

El cuerpo del mismo lo ponemos después de CControlsDialog::Initialization()):

//+------------------------------------------------------------------+
//| Get values                                                       |
//+------------------------------------------------------------------+
void CControlsDialog::GetValues(bool &Mail,bool &Push,bool &Alert_,
                                double &Lots,int &TakeProfit,
                                int &TrailingStop,int &MACDOpenLevel,
                                int &MACDCloseLevel,int &MATrendPeriod)
  {
   Mail=mMail;
   Push=mPush;
   Alert_=mAlert_;

   Lots=mLots;
   TakeProfit=mTakeProfit;
   TrailingStop=mTrailingStop;
   MACDOpenLevel=mMACDOpenLevel;
   MACDCloseLevel=mMACDCloseLevel;
   MATrendPeriod=mMATrendPeriod;
  }
//+------------------------------------------------------------------+
//| Get check for element                                            |
//+------------------------------------------------------------------+
int CControlsDialog::GetCheck(const int idx)

Dado que precisamente el panel es el responsable del envío de mensajes en respuesta a cualquier acción comercial del asesor, entonces necesitaremos un método especial que será responsable de ello. Lo declaramos:

virtual void      GetValues(bool &Mail,bool &Push,bool &Alert_,
                               double &Lots,int &TakeProfit,
                               int &TrailingStop,int &MACDOpenLevel,
                               int &MACDCloseLevel,int &MATrendPeriod);   //--- send notifications
   virtual void      Notifications(const string text);

protected:
   //--- create dependent controls
   bool              CreateCheckGroup(void);

El cuerpo lo pegaremos después de CControlsDialog::GetValues()):

//+------------------------------------------------------------------+
//|  Send notifications                                              |
//+------------------------------------------------------------------+
void CControlsDialog::Notifications(const string text)
  {
   int i=m_check_group.ControlsTotal();
   if(GetCheck(0))
      SendMail(" ",text);
   if(GetCheck(1))
      SendNotification(text);
   if(GetCheck(2))
      Alert(text);
  }
//+------------------------------------------------------------------+
//| Get check for element                                            |
//+------------------------------------------------------------------+
int CControlsDialog::GetCheck(const int idx)

Para recordar si se han realizado cambios en los parámetros del panel, se ha introducido la variable interna - bandera "mModification" se habló de ella anteriormente, en el punto 2.5.).

virtual void      Notifications(const string text);
   //---
   virtual bool      Modification(void) const { return(mModification);          }
   virtual void      Modification(bool value) { mModification=value;            }

protected:
   //--- create dependent controls
   bool              CreateCheckGroup(void);

El control de los cambios lo vamos a realizar en "CControlsDialog::OnClickButtonOK", el manejador de la pulsación del botón "Aplicar cambios":

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CControlsDialog::OnClickButtonOK(void)
  {
//--- verifying changes
   if(m_check_group.Check(0)!=mMail)
      mModification=true;
   if(m_check_group.Check(1)!=mPush)
      mModification=true;
   if(m_check_group.Check(2)!=mAlert_)
      mModification=true;

   if(StringToDouble(m_edit1.Text())!=mLots)
     {
      mLots=StringToDouble(m_edit1.Text());
      mModification=true;
     }
   if(StringToInteger(m_edit2.Text())!=mTakeProfit)
     {
      mTakeProfit=(int)StringToDouble(m_edit2.Text());
      mModification=true;
     }
   if(StringToInteger(m_edit3.Text())!=mTrailingStop)
     {
      mTrailingStop=(int)StringToDouble(m_edit3.Text());
      mModification=true;
     }
   if(StringToInteger(m_edit4.Text())!=mMACDOpenLevel)
     {
      mMACDOpenLevel=(int)StringToDouble(m_edit4.Text());
      mModification=true;
     }
   if(StringToInteger(m_edit5.Text())!=mMACDCloseLevel)
     {
      mMACDCloseLevel=(int)StringToDouble(m_edit5.Text());
      mModification=true;
     }
   if(StringToInteger(m_edit6.Text())!=mMATrendPeriod)
     {
      mMATrendPeriod=(int)StringToDouble(m_edit6.Text());
      mModification=true;
     }
  }

Asimismo, el panel comprueba los datos introducidos en los manejadores:

void              OnChangeCheckGroup(void);
   void              OnChangeEdit1(void);
   void              OnChangeEdit2(void);
   void              OnChangeEdit3(void);
   void              OnChangeEdit4(void);
   void              OnChangeEdit5(void);
   void              OnChangeEdit6(void);
   void              OnClickButtonOK(void);

Voy a omitir su descripción.

2.8. Quinto paso. Introducir los cambios en el asesor. Última edición

En este momento, el panel no funciona en el simulador de estrategias, por eso hay que protegerse, introduciendo una variable interna, precisamente la bandera "bool_tester".

//---
int ExtTimeOut=10; // time out in seconds between trade operations
bool           bool_tester=false;      // true - mode tester
//+------------------------------------------------------------------+
//| MACD Sample expert class                                         |
//+------------------------------------------------------------------+
class CSampleExpert

Introducimos los cambios en OnInit(), nos protegemos del inicio del simulador de estrategias, asimismo, antes de visualizar el panel, inicializamos sus parámetros:

//--- create all necessary objects
   if(!ExtExpert.Init())
      return(INIT_FAILED);
//--- 
   if(!MQLInfoInteger(MQL_TESTER))
     {
      bool_tester=false;
      //---
      ExtDialog.Initialization(ExtMail,ExtPush,ExtAlert,
                               ExtLots,ExtTakeProfit,ExtTrailingStop,
                               ExtMACDOpenLevel,ExtMACDCloseLevel,ExtMATrendPeriod);
      //--- create application dialog
      if(!ExtDialog.Create(0,"Notification",0,100,100,360,380))
         return(INIT_FAILED);
      //--- run application
      if(!ExtDialog.Run())
         return(INIT_FAILED);
     }
   else
      bool_tester=true;
//--- secceed
   return(INIT_SUCCEEDED);
  }

En OnChartEvent() comprobamos si han sido cambiados los parámetros en el panel. Si los parámetros han sido modificados, es necesario inicializar el asesor con los parámetros nuevos:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   ExtDialog.ChartEvent(id,lparam,dparam,sparam);
// Luego preguntamos a la variable bool en el panel si han cambiado los parámetros
// Si los parámetros han cambiado, preguntamos los parámetros del panel y llamamos
// CSampleExpert::Init(void)
   if(ExtDialog.Modification())
     {
      ExtDialog.GetValues(ExtMail,ExtPush,ExtAlert,
                          ExtLots,ExtTakeProfit,ExtTrailingStop,
                          ExtMACDOpenLevel,ExtMACDCloseLevel,ExtMATrendPeriod);
      if(ExtExpert.Init())
        {
         ExtDialog.Modification(false);
         Print("Parámetros modificados, ",ExtLots,", ",ExtTakeProfit,", ",ExtTrailingStop,", ",
               ExtMACDOpenLevel,", ",ExtMACDCloseLevel,", ",ExtMATrendPeriod);
        }
      else
        {
         ExtDialog.Modification(false);
         Print("Error en el cambio de los parámetros");
        }
     }
  }
//+------------------------------------------------------------------+

 

Conclusión

Unir un asesor y un panel ha resultado ser muy sencillo. Para ello hay que implementar en la clase del panel todo el funcional (dimensiones y ubicación de los elementos de control, reacciones a los eventos), declarar en el indicador la variable de clase de nuestro panel y añadir la función OnChartEvent().

Unir el asesor y un panel más complejo ha resultado más complicado, principalmente debido a la necesidad de "comunicación" entre el asesor y el panel. La laboriosidad de la tarea depende en gran medida de lo preparado que esté el propio panel para la conexión. En otras palabras, cuanto mejor implementadas estén en el panel desde un primer momento las funciones y capacidades de integración con otros programas, más sencillo será conectar ese panel con otro programa (indicador o asesor).

El artículo se presenta con los archivos adjuntos: