Como adicionar rapidamente paneis de controle a indicadores e conselheiros (EA)

Vladimir Karputov | 30 agosto, 2016

Para que se precisa de um painel gráfico?

Seu programa MQL4/MQL5 — indicador ou conselheiro — pode ser um dos melhores e realizar completamente as tarefas que lhe são confiadas. Mas, você sempre pode melhorá-lo um pouco. Na maioria dos casos, para qualquer alteração de parâmetros de entrada do programa, o usuário tem de entrar nas respectivas configurações. No entanto, esta etapa pode ser evitada.

Você pode fazer isso criando o seu próprio painel de controle com base nas classes da biblioteca padrão. Isso permitirá alterar as configurações sem reiniciar o programa. Além disso, essa abordagem faz com que seu programa seja mais atraente destacando-se dos concorrentes. Confira exemplos de painéis gráficos no Mercado.

Neste artigo, vou mostrar como adicionar um painel simples ao seu programa MQL4/MQL5. Você vai aprender como ensinar ao programa a ler os parâmetros de entrada e a reagir às alterações dos seus valores.

 

1. Ligamos o indicador e o painel


1.1. Indicador

O indicador "NewBar.mq5" executa só uma ação: ao aparecer uma barra nova, imprime uma mensagem no diário do EA do terminal. A serguir, você vai encontrar o código do indicador:

//+------------------------------------------------------------------+
//|                                                       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);
  }
//+------------------------------------------------------------------+

Agora vou falar um pouco sobre como funciona o indicador "NewBar.mq5".

Na função OnCalculate(), é declarada a variável estatística "prev_time", nesta variável, é armazenado o tempo de abertura "time[0]". No passo seguinte, é comparado o tempo de abertura "time[0]" com a variável "prev_time", quer dizer, acontece a comparação entre o tempo de abertura "time[0]" no tick atual e o tempo de abertura "time[0]" no tick anterior. No caso de se cumprir a condição

if(time[0]>prev_time)

consideramos a aparição de uma nova barra.

No exemplo a seguir, analisamos em detalhe como o indicador "NewBar.mq5" encontra uma nova barra:


Fig. 1. Processo de detecção da nova barra no indicador

Consideremos 10 ticks em um mercado muito tranqüilo.

Ticks 1-3: o tempo de abertura da barra com o índice "0" (time[0]) é igual ao tempo armazenado na variável estatística prev_time, isto indica que a não existe uma nova barra.

Tick 4: este tick veio para a barra nova. Ao entrar na função OnCalculate(), no time[0], estará o tempo de abertura da barra (2015.12.01 00:02:00), enquanto a variável prev_time ainda armazena o tempo a partir do tick anterior (2015.12.01 00:01:00). Por isso, ao verificar as condições time[0]>prev_time, encontraremos uma nova barra. Antes de sair da OnCalculate(), na variável prev_time, será gravado o tempo a partir do time[0] (2015.12.01 00:02:00).

Ticks 5-8: o tempo de abertura da barra com o índice "0" (time[0]) é igual ao tempo armazenado na variável estatística prev_time, isto indica que a não existe uma nova barra.

Tick 9: isto indica que não existe uma nova barra. Ao entrar na função OnCalculate(), no time[0], estará o tempo de abertura da barra (2015.12.01 00:03:00), enquanto a variável prev_time ainda armazena o tempo a partir do tick anterior (2015.12.01 00:02:00). Por isso, ao verificar as condições time[0]>prev_time, encontraremos uma nova barra. Antes de sair da OnCalculate(), na variável prev_time, será gravado o tempo a partir do time[0] (2015.12.01 00:03:00).

Tick 10: o tempo de abertura da barra com o índice "0" (time[0]) é igual ao tempo armazenado na variável estatística prev_time, isto indica que a não existe uma nova barra.


1.2. Painel

Todos os parâmetros de construção do painel (quantidade, dimensões e coordenadas dos controles) estão concentradas no arquivo de inclusão "PanelDialog.mqh", ele é a classe de implementação do painel.

O painel tem a seguinte forma:

Panel

Fig. 2. Painel

A seguir, você vai encontrar o código do arquivo de inclusão "PanelDialog.mqh":

//+------------------------------------------------------------------+
//|                                                  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 você pode ver, na classe de nosso painel, não há métodos de instalação e leitura do estado dos interruptores com fixação independente.

Nosso objetivo: fazer com que o indicador "NewBar.mq5" seja o arquivo básico e adicionar os parâmetros de entrada, por exemplo, usando os métodos ("Mail", "Push" ou "Alert") informar ao usuário que foi encontrada uma barra. Além disso, precisamos adicionar ao arquivo de inclusão "PanelDialog.mqh" os métodos de instalação e de leitura das propriedades do estado dos interruptores com fixação independente "Mail", "Push" e "Alert".


1.3. Alteramos o indicador

Nota: todas as alterações introduzidas serão marcadas com uma cor.

Primeiro, é necessário implementarmos o arquivo de inclusão "PanelDialog.mqh":

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

Em seguida, adicionamos os 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 o indicador (F7 no MetaEditor) e verificamos a exibição de parâmetros de entrada no terminal:


Fig. 3. Parâmetros de entrada do indicador


1.4. Alteramos o painel

Precisamos de adicionar ao painel os métodos de instalação e de leitura das propriedades do estado dos interruptores com fixação independente "Mail", "Push" e "Alert".

Adicionamos os novos métodos à classe do painel:

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);

Implementação desses 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. Fase final da ligação do indicador e o painel

No indicador "NewBar.mq5", no bloque das variáveis globais, anunciamos a variável da classe do nosso painel:

#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

e adicionamos ao final do indicador a função OnChartEvent():

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

Na função OnInit() do indicador "NewBar.mq5", criamos o painel e pressionamos de modo programático as caixas de verificação de acordo com os 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);
  }

Assim, a ligação do indicador e do painel fica concluída. Na classe do painel, implementamos o método de definição de estado da caixa de verificação pressionado/liberado (SetCheck) e o método de obtenção do estado da caixa de verificação (GetCheck).

 

2. Ligamos o conselheiro e o painel


2.1. Conselheiro

Tomamos a base a partir da entrega padrão ...\MQL5\Experts\Examples\MACD\MACD Sample.mq5.


2.2. Painel

O painel "PanelDialog2.mqh" final terá a seguinte aparência:


Fig. 4. Painel número dois

O que obteremos após ligar conselheiro "MACD Sample.mq5" ao painel "PanelDialog2.mqh"? No timeframe atual, no qual será executado o conselheiro, será possível alterar rapidamente os parâmetros do conselheiro ("Lots", "Trailing Stop Level (in pips)" e outros), bem como os parâmetros de notificação sobre os eventos de negociação do conselheiro ("Mail", "Push", "Alert").

Os parâmetros alterados do conselheiro ("Lots", "Trailing Stop Level (in pips)" e outros) são aplicados após pressionar o botão "Aplicar alterações". A alteração dos parâmetros de notificação sobre os eventos de negociação do conselheiro ("Mail", "Push", "Alert") são aplicados automaticamente, não é preciso pressionar o botão "Aplicar alterações".


2.3. Tanto o conselheiro quanto o painel precisam de comunicação


Fig. 5. Comunicação do conselheiro e do painel

Ao ser executado, o conselheiro deve apresentar seus parâmetros no painel. Após clicar no botão "Aplicar alterações" e os dados terem sido alterados, o painel também deve retornar os parâmetros alterados para o conselheiro, para a sua inicialização com novos parâmetros.


2.4. Primeiro passo. Alterando o conselheiro

Tome o conselheiro a partir da entrega padrão ...\MQL5\Experts\Examples\MACD\MACD Sample.mq5 e cole-o na sua pasta. Por exemplo, é possível criar a pasta "Notification" e nela colar o conselheiro:


Fig. 6. Criação de uma nova pasta


No bloque das variáveis globais do conselheiro (não as confunda com as variáveis globais do terminal), anunciamos novas variáveis que podem responder pelo modo de envio de notificações sobre as ações de negociação do conselheiro. Note-se que estas variáveis e outras variáveis ​​externas têm o prefixo "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)

Abaixo adicionamos duplicados de todas as variáveis externas do conselheiro. Os duplicados terão o prefixo "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                                         |
//+------------------------------------------------------------------+

Na função OnInit(), definimos a cópia de valores a partir das variáveis externas para os valores das variáveis-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())

Nesta fase, nas funções do conselheiro CSampleExpert::InitIndicators, CSampleExpert::InitCheckParameters e CSampleExpert::Init usam-se variáveis externas do conselheiro com o prefixo "Inp". Nestas funções, precisamos de substituir as variáveis externas pelos seus duplicados (nossos duplicados tem o prefixo "Ext"). Eu sugiro fazer isto de uma forma bastante original:


Após a substituição, é necessário verificar a validade da ação, isto é, executar a compilação do arquivo. Não deve haver nenhum erro.


2.5. Passo dois. Alterando o painel

O painel mostrado na Fig. 4 é o projeto. Nele ainda não existe nem a função para "contato" com o conselheiro nem a função de processamento de dados de entrada. Copie o arquivo do projeto do painel "PanelDialog2Original.mqh" e cole na pasta "Notification".

Adicionamos à classe do painel as variáveis internas nas quais mais tarde vamos armazenar o estado de todos os dados introduzidos. Observe a variável "mModification", sobre ela discutiremos no ponto 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                                                   |

Inicializamos as variáveis internas no construtor de classe do painel abaixo:

//+------------------------------------------------------------------+
//| 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                                                       |

Adicionamos à função CControlsDialog::Create a instalação do grupo de elementos-interruptor de acordo com as variáveis internas:

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

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

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

 

2.6. Passo três. Introduzindo alterações no conselheiro

Até agora o conselheiro e o painel têm sido dois arquivos distintos e independentes. Ligamo-los e anunciamos a variável da classe do nosso painel "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 o painel funcionar e se tornar visível, é necessário criá-lo e executá-lo. Também é necessário adicionar a função OnChartEvent(), ela é o manipulador de eventos ChartEvent, e a função OnDeinit(). A função OnInit() no conselheiro adota a seguinte forma:

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);
  }

Na função OnDeinit(), destruímos o nosso painel, enquanto criamos a função OnDeinit() após a função 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)

Adicionamos a função OnChartEvent() ao final do conselheiro (após a função 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);
  }
//+------------------------------------------------------------------+

Agora é possível compilar o conselheiro e verificá-lo no gráfico. O conselheiro será executado junto com o painel:


Fig. 7. Conselheiro junto com o painel


2.7. Passo quatro. Introduzindo alterações no painel. A grande integração

Primeiramente, o conselheiro se executa, a seguir, o usuário define seus parâmetros de entrada, e só depois disto é que o painel se executa. É por esta razão que, no painel, é necessário prever as funções para troca de dados entre ele e conselheiro.

Adicionamos o método Initialization(), ele aceita os parâmetros e inicializa, com esses parâmetros, as variáveis internas do painel. Anúncio:

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);

Corpo do método (colocamo-lo antes da função 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)

Como as variáveis internas do painel foram inicializadas pelos dados, agora é necessário preencher controles do painel, isto é, a caixa de edição Como temos seis caixas de edição, vou dar um exemplo baseado em m_edit1. A cadeia de caracteres na qual foi atribuído o texto estava assim:

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

e tornou-se o seguinte:

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

Assim, cada caixa de edição tem sua própria variável interna.

O seguinte método — GetValues() — responde pelo retorno dos valores das variáveis 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);

Colocamos seu corpo após 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)

Como é precisamente o painel quem responde pelo envio de quaisquer ações de negociação do conselheiro, nele é necessário um método especial que responda por isto. Declaramo-lo:

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);

Colocamos seu corpo após 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 lembrar se as alterações no painel foram efetuadas, foi introduzida a variável bandeira "mModification" (ela foi referida no ponto 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);

Vamos controlar as alterações no "CControlsDialog::OnClickButtonOK", isto é, o manipulador de evento de pressão no botão "Aplicar alterações":

//+------------------------------------------------------------------+
//| 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;
     }
  }

Além disso, o painel verifica os dados inseridos nos manipuladores:

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

Omitirei sua descrição.

2.8. Passo cinco. Introduzindo alterações no conselheiro. Última edição

Atualmente, o painel não funciona no testador de estratégias, por isso você precisa de se proteger introduzindo uma variável interna, nomeadamente, a bandeira "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

Introduzimos alterações na função OnInit(), para nos proteger da execução no testador de estratégias, e, antes da visualização do painel, analisamos seus 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);
  }

Na função OnChartEvent(), verificamos se houveram alterações nos parâmetros do painel. No caso de existirem alterações, será necessário executar a inicialização do conselheiro com novos parâmetros:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   ExtDialog.ChartEvent(id,lparam,dparam,sparam);
// Perguntamos à variável bool, no painel, se foram alterados os parâmetros
// Se os parâmetros estiverem alterados, perguntaremos aos parâmetros do painel e chamamos
// 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 alterados, ",ExtLots,", ",ExtTakeProfit,", ",ExtTrailingStop,", ",
               ExtMACDOpenLevel,", ",ExtMACDCloseLevel,", ",ExtMATrendPeriod);
        }
      else
        {
         ExtDialog.Modification(false);
         Print("Erro de alteração de parâmetros");
        }
     }
  }
//+------------------------------------------------------------------+

 

Conclusão

Ligar o painel e o indicador acabou por ser fácil. Para fazer isso, é necessário, por um lado, na classe do painel, implementar toda a funcionalidade (dimensões e colocação dos controles, reação aos eventos) e, por outro lado, no indicador, declarar a variável da classe de nosso painel e adicionar a função OnChartEvent().

Por sua vez, ligar o conselheiro ao painel mais complexo se tornou uma tarefa mais séria, principalmente pela necessidade de "contato" do conselheiro e do painel. A complexidade da tarefa depende, em grande medida, de quão pronto está o painel para a ligação. Em outras palavras, quanto maior for o número inicial de funções implementadas e de recursos de integração com outros programas, no painel, mais fácil será ligá-lo com outro programa (indicador ou conselheiro).

Ao artigo foram anexados os arquivos:

  • NewBarOriginal.mq5 — arquivo inicial do indicador.
  • PanelDialogOriginal.mqh — arquivo inicial do painel.
  • NewBar.mq5 — arquivo alterado do indicador.
  • PanelDialog.mqh — arquivo alterado do painel.
  • PanelDialog2Original.mqh — arquivo inicial do segundo painel.
  • PanelDialog2.mqh — arquivo alterado do segundo painel.
  • MACD Sample.mq5 — arquivo alterado do conselheiro.