Monitoramento de sinais de negociação multimoeda (Parte 2): Implementação da parte visual do aplicativo

Alexander Fedosov | 26 maio, 2020

Índice

Introdução

No estágio anterior, nós desenvolvemos a estrutura geral do monitor de sinais de negociação multimoeda. Nesta parte, nós implementaremos sequencialmente os estágios passo a passo relacionados à configuração inicial do aplicativo e criaremos a interação básica dos elementos que compõem a interface.


Etapa 1 da instalação: Símbolos

De acordo com a estrutura do aplicativo, a primeira etapa de configuração do aplicativo durante o primeiro lançamento implica a criação de uma interface para a seleção de símbolos que serão utilizados posteriormente para procurar os sinais de negociação criados. No final do artigo anterior, nós criamos uma estrutura do aplicativo, que se baseia em todo o nosso trabalho seguinte. Vamos prosseguir para o desenvolvimento do aplicativo. Primeiro, nós definiremos os principais grupos de elementos necessários para a implementação desta parte do aplicativo:

A estrutura de arquivos criada anteriormente deve ficar assim:

Fig. 1 Estrutura de arquivos do aplicativo.

Primeiro, abrimos o aplicativo SignalMonitor.mq5 e adicionamos os parâmetros de entrada a ele. Você poderá configurar os parâmetros ao executar o aplicativo diretamente da plataforma MetaTrader 5. Além disso, declaramos a instância do arquivo da classe CProgram criada anteriormente e inicializamos algumas das variáveis. Editamos o arquivo da seguinte maneira:

//+------------------------------------------------------------------+
//|                                                SignalMonitor.mq5 |
//|                                Copyright 2019, Alexander Fedosov |
//|                           https://www.mql5.com/en/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, Alexander Fedosov"
#property link      "https://www.mql5.com/en/users/alex2356"
#property version   "1.00"
//--- Include application class
#include "Program.mqh"
//+------------------------------------------------------------------+
//| Expert Advisor input parameters                                  |
//+------------------------------------------------------------------+
input int                  Inp_BaseFont      =  10;                  // Basic font
input color                Caption           =  C'0,130,225';        // Caption color
input color                Background        =  clrWhiteSmoke;       // Background color
//---
CProgram program;
ulong tick_counter;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
{
//---
   tick_counter=GetTickCount();
//--- Initialize class variables
   program.OnInitEvent();
   program.m_base_font_size=Inp_BaseFont;
   program.m_background=Background;
   program.m_caption=Caption;
//--- Set up the trading panel
   if(!program.CreateGUI())
   {
      Print(__FUNCTION__," > Failed to create graphical interface!");
      return(INIT_FAILED);
   }
//--- Initialization completed successfully
   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 podemos ver no código acima, foram adicionados três parâmetros de entrada:

Em seguida, declaramos a instância de classe CProgram denominada program e a variável tick_counter (ela é necessária apenas para exibir as informações sobre o tempo de inicialização do aplicativo). Além disso, no método OnInit() nós inicializamos as variáveis de instância de classe atribuindo a elas os valores dos parâmetros de entrada do aplicativo. Além disso, nós chamamos o método CreateGUI() que iniciará o app.

No entanto, se você tentar compilar o arquivo aberto agora, você receberá erros de compilação, informando que as variáveis m_base_font_size, m_background, m_caption e o método CreateGUI() não foram encontrados no método da classe CProgram. Portanto, vamos abrir o arquivo Program.mqh para implementar a classe CProgram. Primeiro, adicionamos as variáveis mencionadas acima e o método, além de outros métodos necessários para a inicialização correta do aplicativo. Após adicionarmos todos os elementos necessários, a classe CProgram ficará da seguinte maneira:

//+------------------------------------------------------------------+
//| Class for creating an application                                |
//+------------------------------------------------------------------+
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);
   //--- Initialization/deinitialization
   void              OnInitEvent(void);
   void              OnDeinitEvent(const int reason);
   //--- Timer
   void              OnTimerEvent(void);
   //--- Chart event handler
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Create the graphical interface of the program
   bool              CreateGUI(void);
};

A implementação do método que cria a interface ainda está vazia:

//+------------------------------------------------------------------+
//| Creates the graphical interface of the program                   |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//---

//--- Finish the creation of GUI
   CWndEvents::CompletedGUI();
   return(true);
}
//+------------------------------------------------------------------+

Preste atenção que também nós adicionamos a variável do tipo string m_base_font que é responsável pelo nome da fonte no aplicativo. Ela é inicializada em nosso construtor de classe:

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

Agora vamos continuar criando a primeira janela do aplicativo. Para esse fim, nós declaramos na classe a nova variável m_step_window que é uma instância de classe CWindow. Declare também um método que criará a primeira janela e nomeamos ela de CreateStepWindow(). O código da classe terá o seguinte aspecto:

class CProgram : public CWndEvents
{
public:
//--- Application windows
   CWindow           m_step_window;
...
protected:
   //--- forms
   bool              CreateStepWindow(const string caption_text);

Nós determinamos anteriormente que a implementação da parte da interface responsável pela configuração passo a passo do lançamento inicial estará localizada no arquivo de inclusão StepWindow.mqh. Então, abrimos e damos início à implementação do método CreateStepWindow():

#include "Program.mqh"
//+------------------------------------------------------------------+
//| Creates a form for the selection of symbols                      |
//+------------------------------------------------------------------+
bool CProgram::CreateStepWindow(const string text)
{
//--- Add the pointer to the window array
   CWndContainer::AddWindow(m_step_window);
//--- Properties
   m_step_window.XSize(600);
   m_step_window.YSize(200);
//--- Coordinates
   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);
//--- Creating a form
   if(!m_step_window.CreateWindow(m_chart_id,m_subwin,text,x,y))
      return(false);
   //---
   return(true);
}
//+------------------------------------------------------------------+

Não se esqueça de adicionar o seguinte código ao método CreateGUI():

//+------------------------------------------------------------------+
//| Creates the graphical interface of the program                   |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//--- Step 1-3
   if(!CreateStepWindow("Signal Monitor Step 1: Choose Symbols"))
      return(false);
//--- Finish the creation of GUI
   CWndEvents::CompletedGUI();
   return(true);
}
//+------------------------------------------------------------------+

Se a sequência de ações estiver correta, você verá o formulário recém-criado depois de compilar e executar o arquivo SignalMonitor.mq5 na plataforma:

Fig. 2 A primeira janela do aplicativo

Os primeiros elementos da janela criada incluem um grupo de botões que permitem a seleção rápida de conjuntos de símbolos predefinidos na plataforma: forex.all, forex.crosses, forex.major. No arquivo Program.mqh, nós adicionamos um array das instâncias da classe CButton com o valor da dimensão igual a três, bem como um método universal CreateSymbolSet() para a criação dos botões:

//+------------------------------------------------------------------+
//| Class for creating an application                                |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
{
public:
//--- Application windows
   CWindow           m_step_window;
//--- Simple buttons
   CButton           m_currency_set[3];
...
   //--- Buttons
   bool              CreateSymbolSet(CButton &button,string text,const int x_gap,const int y_gap);

Agora, abrimos o arquivo StepWindow.mqh e adicionamos a implementação do método acima.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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';
//--- Save the window pointer
   button.MainPointer(m_step_window);
//--- Set properties before creation
   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);
//--- Create a control
   if(!button.CreateButton(text,x_gap,y_gap))
      return(false);
//--- Add a pointer to element to the base
   CWndContainer::AddToElementsArray(0,button);
   return(true);
}
//+------------------------------------------------------------------+

Agora, nós precisamos apenas adicionar os três botões usando esse método com os diferentes valores de coordenadas e rótulos de texto no método básico CreateStepWindow() da janela após a criação do formulário:

...
//--- Creating a form
   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);
...

Após a compilação, o resultado será o seguinte:

Fig. 3 Adicionando os botões para a seleção rápida de grupos de símbolos.

Em seguida, nós adicionamos um campo de entrada para o nome do grupo de símbolos selecionado, que pode ser salvo e carregado usando dois botões: Save e Load. Para fazer isso nós adicionamos uma instância de classe para criar o campo de entrada CTextEdit e mais duas instâncias de classe para a criação dos botões CButton. Como os botões Save e Load diferem apenas em seus nomes, criamos o método universal CreateButton1() e, para o campo de entrada, incluímos o método CreateEditValue() à classe CProgram:

//+------------------------------------------------------------------+
//| Class for creating an application                                |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
{
public:
//--- Application windows
   CWindow           m_step_window;
//--- Simple buttons
   CButton           m_currency_set[3];
   CButton           m_load_button;
   CButton           m_save_button;
   //--- Input fields
   CTextEdit         m_text_edit;
...
   bool              CreateButton1(CButton &button,string text,const int x_gap,const int y_gap);
   //--- Input field
   bool              CreateEditValue(CTextEdit &text_edit,const int x_gap,const int y_gap);

Voltamos para o arquivo StepWindow.mqh e adicionamos a implementação dos métodos criados no final do arquivo.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateEditValue(CTextEdit &text_edit,const int x_gap,const int y_gap)
{
//--- Store the pointer to the main control
   text_edit.MainPointer(m_step_window);
//--- Properties
   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");
//--- Create a control
   if(!text_edit.CreateTextEdit("",x_gap,y_gap))
      return(false);
//--- Add an object to the common array of object groups
   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';
//--- Save the window pointer
   button.MainPointer(m_step_window);
//--- Set properties before creation
   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);
//--- Create a control
   if(!button.CreateButton(text,x_gap,y_gap))
      return(false);
//--- Add a pointer to element to the base
   CWndContainer::AddToElementsArray(0,button);
   return(true);
}

 Então, voltamos para a classe CreateStepWindow() para adicionar os dois botões e um campo de entrada na janela do aplicativo.

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

Compilamos novamente o arquivo SignalMonitor.mq5. Aqui está o resultado:

Fig.4 Adicionamos um campo de entrada para os grupos de símbolos e os botões Save/Load.

Agora, vamos prosseguir com a visualização e a capacidade de selecionar todos os símbolos disponíveis para a conta selecionada na plataforma MetaTrader 5. Observamos que, se você exibir todos os símbolos disponíveis, a altura da janela do aplicativo não será suficiente. Uma boa solução é permitir o ajuste automático da altura da janela, dependendo dos dados. A adição do número total de símbolos é semelhante: adicionamos um array de instâncias de classe para criar as caixas de seleção CCheckBox e os métodos universais que os criam (pois eles diferem apenas no nome).

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

A dimensão do array m_checkbox[] não é especificada porque não se sabe com antecedência quantos símbolos estão presentes na conta selecionada na plataforma. Portanto, vamos criar duas variáveis na seção private da classe CProgram e atribuir a eles o número total de símbolos disponíveis e o número de símbolos atualmente selecionados na Observação do Mercado.

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

No construtor da classe, atribuímos a eles os valores necessários e definimos a dimensão apropriada para o 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);
}

Adicionamos esta implementação do método ao final do arquivo StepWindow.mqh:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateCheckBox(CCheckBox &checkbox,const int x_gap,const int y_gap,const string text)
{
//--- Store the pointer to the main control
   checkbox.MainPointer(m_step_window);
//--- Properties
   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');
//--- Create a control
   if(!checkbox.CreateCheckBox(text,x_gap,y_gap))
      return(false);
//--- Add a pointer to element to the base
   CWndContainer::AddToElementsArray(0,checkbox);
   return(true);
}

Adicionamos as caixas de seleção no método CreateStepWindow(). No código abaixo, a lista inteira de símbolos disponíveis possui 7 colunas. Além disso, a altura da janela é alterada de acordo com o número de linhas recebidas.

   //--- Checkboxes
   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 as adições obtidas

Fig.5 Adição das caixas de seleção com todos os símbolos disponíveis.

O último elemento desta parte do aplicativo inclui os botões de navegação para alternar entre as etapas de configuração. Eles podem ser facilmente adicionados: adicionamos duas instâncias da classe CButton nomeada m_next_button e m_back_button, usamos o método de criação CreateButton1(), que foi criado anteriormente. Adicionamos o seguinte no método de criação da janela CreateStepWindow():

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

Agora nós só precisamos configurar a operação dos botões usando os conjuntos de símbolos predefinidos que podem ser selecionados. Vamos ao arquivo Program.mqh, encontramos o OnEvent() e adicionamos o seguinte código:

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
{
//--- Pressing the button event
   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"};
         //--- Clear the selection
         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"
         };
         //--- Clear the selection
         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())
        )
      {
         //--- Clear the selection
         for(int i=0; i<m_all_symbols; i++)
         {
            m_checkbox[i].IsPressed(false);
            m_checkbox[i].Update(true);
         }
      }
   }
}

A ideia desta implementação é a seguinte:

<d

É assim que ele se parece:

Fig.6 Implementação da interação básica dos elementos.

São necessárias duas pequenas adições para concluir a implementação visual. Você pode ver na figura 5 que a janela contém o botão Back criado anteriormente. Mas este é o passo 1, portanto, não deve haver esse botão. Ele deve estar oculto e ser exibido apenas nas etapas 2 e 3. Adicionamos a seguinte linha ao método CreateGUI():

bool CProgram::CreateGUI(void)
{
//--- Step 1-3
   if(!CreateStepWindow("Signal Monitor Step 1: Choose Symbols"))
      return(false);
//--- Finish the creation of GUI
   CWndEvents::CompletedGUI();
   m_back_button.Hide();
   return(true);
}

Além disso, nós precisamos monitorar a seleção do usuário. A mudança para a etapa 2 não deve ser permitida se o usuário não tiver selecionado pelo menos um símbolo. A alternância entre as etapas é realizada usando os botões Back e Next. Portanto, para resolver a tarefa, adicione três novos métodos à seção private da classe CProgram. Os métodos processarão as informações selecionadas durante cada uma das três etapas e, assim, executarão a configuração inicial do aplicativo. Além disso, adicionamos também a variável m_current_step: ao clicar em Back/Next, o aplicativo saberá em que etapa estamos atualmente.

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

Depois disso, no construtor da classe, definimos o valor do primeiro passo para a variável criada, ou seja, 1. Para definir a navegação entre as três etapas de configuração, adicionamos o código abaixo no evento de clique no botão no OnEvent():

      //--- Navigation
      if(lparam==m_back_button.Id())
      {
         //--- Return to Step 1
         if(m_current_step==2)
            ToStep_1();
         //--- Return to Step 2
         else if(m_current_step==3)
            ToStep_2();
      }
      //--- Go to Step 2
      if(lparam==m_next_button.Id())
      {
         //--- Go to Step 2
         if(m_current_step==1)
            ToStep_2();
         //--- Go to Step 3
         else if(m_current_step==2)
            ToStep_3();
      }

Se você tentar compilar o projeto nesta etapa, o compilador retornará um erro de que os três métodos foram criados, são usados, mas não têm implementação: 

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

Para corrigi-lo, criamos a implementação dessas classes no arquivo Program.mqh. No entanto, vamos deixá-lo em branco por enquanto para os métodos ToStep_1() e ToStep_3(). Eles serão preenchidos mais tarde. Agora nós estamos interessados no método de alternar para o segundo passo ToStep_2(). Adicionamos uma verificação se pelo menos um símbolo está selecionado:

//+------------------------------------------------------------------+
//| Go to Step 1                                                     |
//+------------------------------------------------------------------+
void CProgram::ToStep_1(void)
{
//---
}
//+------------------------------------------------------------------+
//| Go to Step 2                                                 |
//+------------------------------------------------------------------+
void CProgram::ToStep_2(void)
{
//--- Check whether at least one symbol is selected
   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;
   }
}
//+------------------------------------------------------------------+
//| Move to Step 3 3                                                 |
//+------------------------------------------------------------------+
void CProgram::ToStep_3(void)
{
//---
}

Se o usuário pressionar Next acidentalmente sem selecionar um símbolo, será exibido um aviso de que pelo menos um símbolo deve ser selecionado.


Etapa 2 da instalação: Período gráfico

Na segunda etapa de configuração do aplicativo, o usuário deve selecionar os períodos nos quais os sinais de negociação serão pesquisados. Nós mencionamos os elementos de interface do usuário necessários no primeiro artigo:

Vamos usar os objetos existentes na implementação visual da Etapa 1 e ajustá-la para a seleção de períodos gráficos. Vamos para o corpo do ToStep_2() que nós editamos recentemente e adicionamos as funcionalidades adicionais a ele. Primeiramente, lembre-se de selecionar os símbolos na Etapa 1 e de exibí-los no Observador de Mercado na MetaTrader 5:

//--- Set selected symbols in Market Watch
   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);
   }

Em seguida, transformamos a interface da Etapa 1 na segunda:

//--- Change header
   m_step_window.LabelText("Signal Monitor Step 2: Choose Timeframes");
   m_step_window.Update(true);
//--- Hide elements of Step 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"};
//--- Change names of selection buttons
   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);
   }
//--- Hide block for working with templates
   m_text_edit.Hide();
   m_load_button.Hide();
   m_save_button.Hide();
//--- Show all 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);
   }
//--- Show Back button
   m_back_button.Show();
//---
   m_current_step=2;

A implementação é bem simples. No final, atribuímos o valor 2 à variável m_current_step (Etapa 2). Agora, nós precisamos fornecer a exibição correta dos conjuntos do período gráfico selecionados All, Junior, Senior pela interface alterada. Abrimos o Program.mqh e alteramos o código no método OnEvent(). A modificação é necessária na seção do evento "button click". Do ponto de vista do objeto, os botões de seleção rápida nas etapas 1 e 2 são semelhantes. Por isso, é necessário definir a etapa de configuração atual no evento de clique no botão:

 //--- Step 1
      if(m_current_step==1)
      {
       ...
      }
      //--- Step 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"
            };
            //--- Clear the selection
            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"
            };
            //--- Clear the selection
            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);
                  }
            }
         }

O último elemento da interface do usuário a ser implementado na segunda etapa da configuração é o botão Back, que retorna à Etapa 1. Isso é feito pela etapa criada, mas ainda vazia ToStep_1(). Retornamos a interface anterior e configuramos o wrapper anterior para processar os eventos de clique no botão de seleção dos conjuntos.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::ToStep_1(void)
{
//--- Change header
   m_step_window.LabelText("Signal Monitor Step 1: Choose Symbols");
   m_step_window.Update(true);
//--- Hide the Back button
   m_back_button.Hide();
//--- Clear selection
   for(int i=0; i<21; i++)
   {
      m_checkbox[i].IsPressed(false);
      m_checkbox[i].Update(true);
   }
//--- Show elements of Step 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"};
//--- Change names of selection buttons
   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);
   }
//--- Show block for working with templates
   m_text_edit.Show();
   m_load_button.Show();
   m_save_button.Show();
//--- Set the current setup step
   m_current_step=1;
}

Agora, compilamos o projeto. Se tudo for adicionado corretamente, o resultado será como na Fig.7.

Fig.7 Implementação da Etapa 2 da configuração do aplicativo.

Etapa 3 da instalação: Adição de sinais

A próxima etapa é a Etapa 3: Interface de adição de sinais. Ela é bem simples e consiste em um botão de adição de sinal e um cabeçalho para a lista de sinais adicionados. Abrimos o Program.mqh e declaramos duas novas variáveis na СPrograma:

   CButton           m_add_signal;
   //---
   CTextLabel        m_signal_header;

Métodos que implementam as variáveis:

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

Adicionamos sua implementação no final do arquivo StepWindow.mqh.

//+------------------------------------------------------------------+
//| Creates a button with an image                                   |
//+------------------------------------------------------------------+
#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';
//--- Save the window pointer
   button.MainPointer(m_step_window);
//--- Set properties before creation
   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);
//--- Create a control
   if(!button.CreateButton(text,x_gap,y_gap))
      return(false);
//--- Add a pointer to element to the base
   CWndContainer::AddToElementsArray(0,button);
   return(true);
}
//+------------------------------------------------------------------+
//| Creates the text label                                           |
//+------------------------------------------------------------------+
bool CProgram::CreateLabel(CTextLabel &text_label,const int x_gap,const int y_gap,string label_text)
{
//--- Save the window pointer
   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);
//--- Create the button
   if(!text_label.CreateTextLabel(label_text,x_gap,y_gap))
      return(false);
//--- Add a pointer to element to the base
   CWndContainer::AddToElementsArray(0,text_label);
   return(true);
}
//+------------------------------------------------------------------+

Adicionamos o seguinte à janela CreateStepWindow() para que sejam criados no lançamento do aplicativo.

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

Agora, para desativar sua exibição no lançamento, ou seja, na primeira etapa, imediatamente após a criação da interface pela chamada de CreateGUI(), adicionamos duas linhas ocultando os elementos no final do corpo do método.

//+------------------------------------------------------------------+
//| Creates the graphical interface of the program                   |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//--- Step 1-3
   if(!CreateStepWindow("Signal Monitor Step 1: Choose Symbols"))
      return(false);
//--- Finish the creation of GUI
   CWndEvents::CompletedGUI();
   m_back_button.Hide();
   m_add_signal.Hide();
   m_signal_header.Hide();
   return(true);
}

Agora, implementamos o método ToStep_3(), que foi adicionado anteriormente, e que limpará a visualização na etapa anterior e exibirá os elementos que criamos:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::ToStep_3(void)
{
//--- Check whether at least one timeframe is selected
   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);
//--- Hide elements of Step 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;
}

Compilamos novamente o projeto e vamos para a Etapa 3 clicando duas vezes no botão Next. Não se esqueça de selecionar os elementos nas duas primeiras etapas, caso contrário, o aplicativo não nos permitirá ir para a terceira etapa.

Fig.8 Implementação da Etapa 3 da configuração do aplicativo.

Janela de criação e edição de sinais de negociação

O componente visual relacionado ao trabalho com os sinais de negociação estará localizado no arquivo SetWindow.mqh, então vamos abri-lo. Agora ele possui apenas o arquivo de inclusão Program.mqh que é conectado via linha de comando #include. Primeiro, criamos uma janela separada, que será básica para todos os outros elementos de criação e configuração. Abrimos o Program.mqh e declaramos na classe a variável m_set_window que é uma instância da classe CWindow. Adicionamos também o método CreateSetWindow() para criar a janela:

   CWindow           m_set_window;

   bool              CreateSetWindow(const string caption_text);

Depois disso, voltamos para o SetWindow.mqh e implementamos o método criado.

//+------------------------------------------------------------------+
//| Creates a window for creating and editing trading signals        |
//+------------------------------------------------------------------+
bool CProgram::CreateSetWindow(const string text)
{
//--- Add the pointer to the window array
   CWndContainer::AddWindow(m_set_window);
//--- Properties
   m_set_window.XSize(568);
   m_set_window.YSize(555);
//--- Coordinates
   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);
//--- Creating a form
   if(!m_set_window.CreateWindow(m_chart_id,m_subwin,text,x,y))
      return(false);
   return(true);
}
//+------------------------------------------------------------------+

Agora vamos vincular a janela recém-criada aos elementos já disponíveis. Primeiramente, adicionamos a chamada do método na criação de interface CreateGUI(). A janela deve abrir com um clique no botão "Add Signal" na etapa 3.

//+------------------------------------------------------------------+
//| Creates the graphical interface of the program                   |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//--- Step 1-3
   if(!CreateStepWindow("Signal Monitor Step 1: Choose Symbols"))
      return(false);
//--- Creation and editing window
   if(!CreateSetWindow("Signal Monitor Edit Signal"))
      return(false);
//--- Finish the creation of GUI
   CWndEvents::CompletedGUI();
   m_back_button.Hide();
   m_add_signal.Hide();
   m_signal_header.Hide();
   return(true);
}

No evento de clique em OnEvent():

      //--- Click on the "Add Signal" button
      if(lparam==m_add_signal.Id())
      {
         m_set_window.OpenWindow();
      }

Compilamos o projeto e verificamos o resultado: Ao clicar no botão "Add Signal" na etapa 3, uma janela adicional de criação e edição é aberta.

Fig. 9 Implementação da janela de criação e edição do sinal de negociação.

O primeiro elemento da janela é a seleção do tipo de indicador que será usado na geração de um sinal de negociação. O procedimento de adição do elemento é o mesmo: criamos uma instância de classe e criamos um método implementando a instância.

   //--- Drop-down menu
   CComboBox         m_indicator_type;
   //--- Creates a drop-down method
   bool              CreateIndicatorType(const int x_gap,const int y_gap);

A implementação do método estará localizada no mesmo arquivo que se encontra a janela criada anteriormente. 

//+------------------------------------------------------------------+
//| Creates a drop-down menu with indicator types                    |
//+------------------------------------------------------------------+
bool CProgram::CreateIndicatorType(const int x_gap,const int y_gap)
{
//--- Pass the object to the panel
   m_indicator_type.MainPointer(m_set_window);
//--- Array of the item values in the list view
   string pattern_names[7]=
   {
      "ATR","CCI","DeMarker","Force Ind","WPR","RSI","Momentum"
   };
//--- Set properties before creation
   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);
//--- Store the item values in the combo box list view
   for(int i=0; i<7; i++)
      m_indicator_type.SetValue(i,pattern_names[i]);
//--- Get the list pointer
   CListView *lv=m_indicator_type.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   m_indicator_type.SelectItem(5);
//--- Create a control
   if(!m_indicator_type.CreateComboBox("Indicator Type",x_gap,y_gap))
      return(false);
//--- Add an object to the common array of object groups
   CWndContainer::AddToElementsArray(1,m_indicator_type);
   return(true);
}

A única adição aqui é que, no final do corpo do método CreateSetWindow(), nós chamamos um método para criar a opção de seleção do tipo de indicador CreateIndicatorType().

...
//--- Creating a form
   if(!m_set_window.CreateWindow(m_chart_id,m_subwin,text,x,y))
      return(false);
//--- Indicator type
   if(!CreateIndicatorType(10,22+10))
      return(false);

O resultado é o elemento da interface do usuário que permite selecionar entre 7 indicadores padrão do tipo oscilador.

Fig. 10 Elemento para selecionar o tipo de indicador.

A seguir, vamos considerar os conjuntos de elementos agrupados em duas seções: Configurações do Indicador (Indicator Settings) e Configurações do Sinal (Signal Settings). Todos os indicadores selecionados do conjunto padrão têm configurações comuns, como Período e Preço Aplicado. Portanto, é necessário o seguinte para a primeira seção: um rótulo de texto, um campo de entrada para o período e um menu suspenso para selecionar o preço usado para o cálculo do indicador. Adicionamos a variável necessária e seus métodos de criação na classe CProgram.

//--- Text label
   CTextLabel        m_set_header[5];
//--- Input fields
   CTextEdit         m_period_edit;
//--- Drop-down menu
   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 os métodos adicionados e chamamos eles no final do corpo do método CreateSetWindow(). Agora vamos adicionar um mecanismo devido ao qual os elementos criados mudarão o conjunto de configurações disponíveis, dependendo do tipo de indicador selecionado. Para fazer isso, adicionamos OnEvent() uma seção com um evento de clicar no item do menu suspenso e definir o conjunto individual de configurações para cada um dos indicadores:

//--- Item selection in the combobox list
   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 o projeto e vemos o resultado:

Fig. 11 Implementação das configurações do indicador.

Em seguida, vamos para a segunda seção da edição de sinais. Ela é composta pelo cabeçalho e oito configurações:

Para adicionar um cabeçalho para esta seção, adicionamos o seguinte código no final do corpo do CreateSetWindow() (nós criamos anteriormente um método para visualizar o cabeçalho, que pode ser usado novamente com diferentes valores de argumento):

//--- Signal settings
   if(!CreateSetLabel(m_set_header[1],10,22+10+4*(25+10),"2.Signal Settings"))
      return(false);

A regra do sinal consiste em dois elementos: um menu suspenso e um campo de entrada para um valor numérico. Adicionamos as instâncias de classe e os métodos de implementação à classe 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);

Adicionamos a implementação deles ao SetWindow.mqh e chamamos eles no corpo do método CreateSetWindow().

   //--- Condition settings
   if(!CreateRuleValue(130,22+10+5*(25+10)))
      return(false);
   if(!CreateRule(10,22+10+5*(25+10)))
      return(false);

Além disso, adicionamos cada uma das configurações da mesma maneira. Segue abaixo a implementação completa do método CreateSetWindow():

//+------------------------------------------------------------------+
//| Creates a window for creating and editing trading signals        |
//+------------------------------------------------------------------+
bool CProgram::CreateSetWindow(const string text)
{
//--- Add the pointer to the window array
   CWndContainer::AddWindow(m_set_window);
//--- Properties
   m_set_window.XSize(568);
   m_set_window.YSize(575);
//--- Coordinates
   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);
//--- Creating a form
   if(!m_set_window.CreateWindow(m_chart_id,m_subwin,text,x,y))
      return(false);
//--- Indicator type
   if(!CreateIndicatorType(10,22+10))
      return(false);
//--- Settings of the selected indicator
   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);
//--- Signal settings
   if(!CreateSetLabel(m_set_header[1],10,22+10+4*(25+10),"2.Signal Settings"))
      return(false);
//--- Condition settings
   if(!CreateRuleValue(130,22+10+5*(25+10)))
      return(false);
   if(!CreateRule(10,22+10+5*(25+10)))
      return(false);
//--- Label display settings
   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);
//--- Label color display settings
   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);
//--- Timeframe selection
   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);
}

A lista completa de adições e sua implementação estão disponíveis nos anexos abaixo. Depois de adicionar todas as partes necessárias, a janela de criação e edição fica assim:

Fig. 12 Implementação dos elementos da interface do usuário da janela de edição do sinal.

Como você pode ver na figura, os botões de seleção do período gráfico estão vazios. Nós também precisamos configurar as interações básicas dos elementos:

Para implementar a saída dos períodos gráficos selecionados, vamos criar o método RebulidTimeframes() na seção privada da classe básica e implementar este método:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::RebuildTimeframes(void)
{
//--- Count the number of selected timeframes
   int cnt=0;
   for(int i=0; i<21; i++)
   {
      if(m_checkbox[i].IsPressed())
         cnt++;
   }
   ArrayResize(m_timeframes,cnt);
   cnt=0;
//--- Remember the selected timeframe to the 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);
}

Agora vamos adicionar o seguinte ao código que chama a janela de edição com um clique em Add Signal.

      //--- Click on the "Add Signal" button
      if(lparam==m_add_signal.Id())
      {
         m_set_window.OpenWindow();
         if(m_set_window.IsAvailable())
            RebuildTimeframes();
      }

Vamos para o próximo momento relacionado à configuração da interação dos botões Value e Text. Adicionamos o seguinte código na OnEvent():

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

A seguinte condição é atendida aqui: se um dos botões for pressionado, o outro deverá ser pressionado. Se o Text é solto, ocultamos o campo de edição. Os cliques no botão da paleta de cores também são implementados aqui. Nós temos quatro botões, um array de quatro elementos foi declarado, portanto, o acesso a eles pode ser escrito em um loop.

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

E a última interação é bloquear os elementos quando as caixas de seleção não são pressionadas. Para isso, temos que adicionar o monitoramento de cliques da caixa de seleção na OnEvent() e implementar as interações.

//--- Click on the 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());
   }

Compilamos o projeto novamente e visualizamos o seu resultado.

Fig. 13 Implementações da interação dos elementos de interface do usuário da janela de edição do sinal.


Monitoramento dos sinais de negociação

O último passo nesta fase de desenvolvimento é criar uma janela do futuro monitor de sinais de negociação. Nós também devemos levar em consideração as configurações básicas que já estão implementadas na versão atual. Antes de sua criação, vamos definir algumas tarefas para que o leitor entenda para que finalidade os elementos são criados:

Para permitir a criação dos rótulos de texto de períodos gráficos e símbolos, nós criamos dois arrays das instâncias de classe CTextLabel e adicione dois métodos de implementação na classe 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);

Agora, implementamos os métodos criados no arquivo MainWindow.mqh:

//+------------------------------------------------------------------+
//| Creates the text label                                           |
//+------------------------------------------------------------------+
bool CProgram::CreateTimeframeLabel(CTextLabel &text_label,const int x_gap,const int y_gap,string label_text)
{
//--- Save the window pointer
   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);
//--- Create the button
   if(!text_label.CreateTextLabel(label_text,x_gap,y_gap))
      return(false);
//--- Add a pointer to element to the base
   CWndContainer::AddToElementsArray(0,text_label);
   return(true);
}
//+------------------------------------------------------------------+
//| Creates the text label                                           |
//+------------------------------------------------------------------+
bool CProgram::CreateSymbolLabel(CTextLabel &text_label,const int x_gap,const int y_gap,string label_text)
{
//--- Save the window pointer
   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);
//--- Create the button
   if(!text_label.CreateTextLabel(label_text,x_gap,y_gap))
      return(false);
//--- Add a pointer to element to the base
   CWndContainer::AddToElementsArray(0,text_label);
   return(true);
}

Antes de prosseguir com a visualização da interface da janela, nós precisamos criar duas variáveis importantes na seção private, bem como mais dois métodos:

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

A variável m_total_signals é necessária para verificar se pelo menos um sinal de negociação foi criado. Essa verificação é realizada antes de criar a janela do monitor. O array m_symbols[] conterá uma seleção de símbolos da primeira etapa da configuração. O método ToMonitor() implementará a criação da interface do monitor, enquanto o AutoResize() ajustará a janela de tamanho de acordo com os elementos criados. Aqui está a implementação dos métodos declarados:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::ToMonitor(void)
{
//--- Check if there is at least one signal
   if(m_total_signals<1)
   {
      MessageBox("No signals created!","Warning");
      return;
   }
//--- Hide Step 3
   m_add_signal.Hide();
   m_signal_header.Hide();
   m_back_button.Hide();
   m_next_button.Hide();
//--- Change window header
   m_step_window.LabelText("Signal Monitor");
   m_step_window.Update(true);
//--- Symbols
   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);
   }
//--- Resize window
   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);
}

Como pode ser visto no código acima, os dados de m_symbols são usados na seção Símbolos. Mas esses dados não são coletados ou preparados. Vamos corrigir isso. Vá para o método ToStep_2() e depois de verificar se pelo menos um símbolo foi selecionado, lembre-se dos símbolos selecionados na primeira etapa em nosso array:

//--- Count the number of selected symbols
   ArrayResize(m_symbols,cnt);
   cnt=0;
//--- Remember the selected timeframe to the array
   for(int i=0; i<m_all_symbols; i++)
   {
      if(m_checkbox[i].IsPressed())
      {
         m_symbols[cnt]=m_checkbox[i].LabelText();
         cnt++;
      }
   }

Agora, crie o método de dimensionamento automático. 

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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 o projeto, definimos a variável m_total_signals para zero no construtor da CProgram. Outro ponto importante é a adição no método OnEvent(), no evento de clique no botão. 

      //--- Navigation
      if(lparam==m_back_button.Id())
      {
         //--- Go back
         if(m_current_step==2)
            ToStep_1();
         //--- Return to Step 2
         else if(m_current_step==3)
            ToStep_2();
      }
      //--- Go forward
      if(lparam==m_next_button.Id())
      {
         //--- Go to Step 2
         if(m_current_step==1)
            ToStep_2();
         //--- Go to Step 3
         else if(m_current_step==2)
            ToStep_3();
         //--- Go to Monitor
         else if(m_current_step==3)
            ToMonitor();
      }

Aqui, nós adicionamos a chamada do método criado ToMonitor() ao clicar no botão que pula para o próxima etapa. Este botão é chamado de "Create" na etapa 3. Agora, compilamos o projeto e iniciamos o aplicativo:

Fig. 14 Configuração básica do monitor

No próximo artigo, nós consideraremos a implementação de um algoritmo que buscará os sinais de negociação configurados nas condições criadas durante o lançamento inicial.

Conclusão

O arquivo anexado contém todos os arquivos listados, localizados nas pastas apropriadas. Para o seu bom funcionamento, você só precisa salvar a pasta MQL5 na pasta do terminal. Para abrir o diretório raiz da plataforma, no qual a pasta MQL5 está localizada, pressione a combinação de teclas Ctrl + Shift + D na plataforma MetaTrader 5 ou use o menu de contexto como é mostrado abaixo na Fig. 15.


Fig. 15. Abrindo a pasta MQL5 no diretório raiz da plataforma MetaTrader 5