Monitoramento de sinais de negociação multimoeda (Parte 3): Introdução de algoritmos de busca

10 julho 2020, 10:46
Alexander Fedosov
0
1 249

Conteúdo

Introdução

Nos artigos anteriores, nós criamos a estrutura do aplicativo para o monitoramento de sinais de negociação. Nós também implementamos a interface do aplicativo com os recursos básicos de interação. Agora é hora de preencher a parte visual do algoritmo para configurar e buscar os símbolos. Nós vamos usar o projeto do artigo anterior como base e continuará adicionando novas funcionalidades a cada passo.

Sistema de armazenamento do conjunto de símbolos

Na última vez, na primeira etapa de configuração do aplicativo, nós criamos as ferramentas para selecionar os símbolos na Observação de Mercado. Os símbolos podem ser selecionados de três maneiras diferentes:

  • Manual. Tendo que marcar os símbolos necessários e clicar em Next.
  • Um conjunto predefinido. Um clique em All, Major ou Crosses selecionará automaticamente um conjunto de símbolos predefinido específico.
  • Conjunto salvo. Um conjunto de símbolos previamente preparado, configurado usando os dois primeiros métodos e salvo em um arquivo com um nome específico.

Fig.1 Etapa 1 de configuração do aplicativo e configurações do conjunto salvo.

As duas primeiras maneiras são bastante simples e foram criadas anteriormente. A terceira maneira precisa ser criada. Agora vamos definir com mais precisão o que vamos fazer. Nós iremos nos aprofundar na interação dos elementos no quadro vermelho na Fig.1, o que implica o seguinte:

  • Os símbolos desejados são marcados com as marcas de seleção, o usuário digita o nome no campo 'Template name' e clica em Save ou pressiona a tecla de atalho S. Se salvo com sucesso, uma mensagem apropriada será exibida.
  • Para acessar um modelo configurado e salvo anteriormente, o nome do modelo deve ser digitado no campo e carregado ou, de modo alternativo, pressionar a tecla de atalho L.

Abrimos o projeto, encontramos a classe base CProgram e adicionamos dois métodos em sua seção private. Os métodos serão responsáveis por carregar e salvar o modelo de símbolos.

   bool              SaveSymbolSet(string file_name);
   bool              LoadSymbolSet(string file_name);

Abaixo encontramos como esses métodos são implementados. 

//+------------------------------------------------------------------+
//| Save template to a file                                          |
//+------------------------------------------------------------------+
bool CProgram::SaveSymbolSet(string file_name)
{
   if(file_name=="")
   {
      MessageBox("Select the template name to record","Signal Monitor");
      return(false);
   }
   int h=FileOpen("Signal Monitor\\"+file_name+".bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      MessageBox("Failed to create a configuration file","Signal Monitor");
      return(false);
   }
   else
      MessageBox("The "+file_name+" configuration has been successfully saved","Signal Monitor");
//--- Save the selection of timeframes and patterns
   for(int i=0; i<m_all_symbols; i++)
      m_save.tf[i]=m_checkbox[i].IsPressed();
//---
   FileWriteStruct(h,m_save);
   FileClose(h);
//---
   return(true);
}
//+------------------------------------------------------------------+
//| Load data to a panel                                             |
//+------------------------------------------------------------------+
bool CProgram::LoadSymbolSet(string file_name)
{
   if(file_name=="")
   {
      MessageBox("Select the template name to load","Signal Monitor");
      return(false);
   }
   int h=FileOpen("Signal Monitor\\"+file_name+".bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      MessageBox("Configuration "+file_name+" not found","Signal Monitor");
      return(false);
   }
   ZeroMemory(m_save);
   FileReadStruct(h,m_save);
//--- Loading timeframes
   for(int i=0; i<m_all_symbols; i++)
   {
      m_checkbox[i].IsPressed(m_save.tf[i]);
      m_checkbox[i].Update(true);
   }
//---
   FileClose(h);
//---
   return(true);
}

No entanto, se você tentar compilar o projeto agora, isso disparará um erro conectado à variável m_save. Essa estrutura possui um parâmetro do tipo bool chamado tf. Ela lembra a escolha do usuário em um arquivo. Portanto, nós criamos essa estrutura na classe do aplicativo e adicionamos a sua instância à classe base.

//+------------------------------------------------------------------+
//| Class for creating the application                               |
//+------------------------------------------------------------------+
struct SAVE
{
   bool     tf[100];
};
class CProgram : public CWndEvents
{
...
        SAVE            m_save;

Vamos para o método da OnEvent(), inserimos a seção relacionada ao evento de clique no botão e adicionamos o seguinte código na condição de Etapa 1:

         //--- Save the template
         if(lparam==m_save_button.Id())
         {
            SaveSymbolSet(m_text_edit.GetValue());
         }
         //--- Load the template
         if(lparam==m_load_button.Id())
         {
            LoadSymbolSet(m_text_edit.GetValue());
         }

Além disso, nós implementamos o uso das teclas de atalho para os botões acima. No mesmo método, nós adicionamos uma verificação para o evento de pressionamento de tecla e adicionamos o código para as teclas usadas.

//--- Key press
   if(id==CHARTEVENT_KEYDOWN)
   {
      if(m_current_step==1)
      {
         short sym=TranslateKey((int)lparam);
         //--- if the entered character is successfully converted to Unicode
         if(sym>0)
         {
            if(ShortToString(sym)=="l" || ShortToString(sym)=="д")
               LoadSymbolSet(m_text_edit.GetValue());
            if(ShortToString(sym)=="s" || ShortToString(sym)=="ы")
               SaveSymbolSet(m_text_edit.GetValue());
         }
      }
   }

Compilamos o projeto. A compilação bem sucedida produzirá o seguinte resultado.

Fig.2 Salvando e carregando um modelo de usuário

Adição e edição de um sinal de negociação

Agora, vamos para a parte principal do aplicativo, responsável pela criação e edição dos sinais de negociação, bem como pelo monitoramento adicional no monitor. Abaixo está como é a criação e edição do sinal. 

Fig.3 Janela de criação e edição do sinal.

No estágio atual, a janela aparece como um conjunto de elementos da GUI que controlam vários parâmetros. No entanto, essas configurações não são usadas em nenhum lugar. Começamos com a adição de dois botões na interface. Eles adicionam/salvam um sinal de negociação. Outro botão cancela a criação/edição. Abrimos o arquivo Program.mqh e adicionamos o método de implementação para esses dois botões à classe base:

bool              CreateButton3(CButton &button,string text,const int x_gap,const int y_gap);

Duas instâncias de botão CButton:

   CButton           m_new_signal;
   CButton           m_cancel_button;

Agora vamos para o arquivo SetWindow.mqh e implementamos esse método.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateButton3(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_set_window);
//--- Set up properties before creation
   button.XSize(60);
   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 the control
   if(!button.CreateButton(text,x_gap,y_gap))
      return(false);
//--- Add the element pointer to the base
   CWndContainer::AddToElementsArray(1,button);
   return(true);
}

Para fazer esses dois botões aparecerem na interface da janela de negociação e adicionando um sinal, nós inserimos as seguintes linhas no final do corpo do método CreateSetWindow():

//--- Add/Cancel Buttons
   if(!CreateButton3(m_new_signal,"Add",m_set_window.XSize()-2*(60+10),m_set_window.YSize()-(30+10)))
      return(false);
   if(!CreateButton3(m_cancel_button,"Cancel",m_set_window.XSize()-(60+10),m_set_window.YSize()-(30+10)))
      return(false);

Após a compilação do projeto, dois botões aparecerão na parte inferior da janela de criação do sinal de negociação. 

Fig.4 Adicionando os botões de criação e cancelamento de sinal.

Agora, nós precisamos adicionar os eventos que ocorrerão com o clique do botão. O efeito do botão Cancel é óbvio: ele não salva nenhuma ação e configuração na janela especificada e fecha a janela sem adicionar um sinal. Vamos considerar o botão Add com mais detalhes.

Primeiro, vamos determinar a sequência de ações a serem executadas quando clicarmos em Add.

  1. Um clique no botão salva os parâmetros selecionados usando os elementos da interface do usuário na janela de criação do sinal de negociação.
  2. Após salvá-lo com êxito, a janela é fechada e o primeiro registro com o nome do sinal aparece na lista de sinais na janela principal.
  3. Ao clicar no registro, o conjunto salvo anteriormente é aplicado aos elementos da interface do usuário da configuração do sinal e o botão Add é convertido em Save.

Para habilitar o salvamento das configurações em um arquivo, nós precisamos criar um conjunto universal de configurações que serão usadas para a exibição visual na janela de edição, bem como para a busca subsequente de sinais. Portanto, vamos criar uma estrutura e chamá-la de SIGNAL. A configuração das configurações na janela de criação e edição será gravada nessa estrutura.

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

Vamos ver cada um dos elementos na estrutura:

  • ind_type — contém o tipo do indicador que foi selecionado como base de busca do sinal. Ele é exibido como Indicator Type na interface.
  • ind_period — o período do indicador selecionado.
  • app_price — preço usado para o cálculo do indicador. Este valor não está disponível para todos os indicadores e, portanto, ele é gravado apenas quando aplicável. Por exemplo, ele é usado para o RSI, mas não para o WPR.
  • rule_type — define o tipo de regra a ser usada ao buscar um sinal de negociação. Ele aparece na interface como um menu suspenso contendo os caracteres como ==,>=,<= e outros.
  • rule_value — um valor do limiar do indicador selecionado na qual a regra de busca deve ser aplicada.
  • label_type — este elemento armazenará o tipo de exibição do rótulo de texto. Ele é o valor atual do indicador ou um rótulo personalizado com até 3 caracteres.
  • label_value — se o segundo tipo de exibição do rótulo de texto for selecionado, esse parâmetro armazenará o texto do rótulo personalizado especificado pelo usuário.
  • label_color — armazena a cor do rótulo do texto.
  • back_color — armazena a cor de fundo do bloco de sinal no monitor se esta opção estiver selecionada.
  • border_color — armazena a cor da borda do bloco de sinal no monitor se esta opção estiver selecionada.
  • tooltip — contém uma indicação se uma dica de ferramenta é usada.
  • tooltip_text — se a dica de ferramenta for usada, esse parâmetro conterá o texto.
  • image — uma indicação do uso da imagem.
  • img_index — salva o número da sequência da imagem, se usada.
  • timeframes — matriz contendo informações sobre as configurações do período selecionado na segunda etapa.
  • tf_name — salva os períodos nos quais o sinal de negociação será pesquisado. 

Agora, declaramos na classe base uma matriz de estruturas para salvar as configurações dos sinais criados.

SIGNAL            m_signal_set[5];

Além disso, nós criamos dois métodos na área private da CProgram para salvar um conjunto de parâmetros em um arquivo e carregá-lo do arquivo na estrutura.

   bool              SaveSignalSet(int index);
   bool              LoadSignalSet(int index);

Aqui está a implementação:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::SaveSignalSet(int index)
{
//---
   int h=FileOpen("Signal Monitor\\signal_"+string(index)+".bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      MessageBox("Failed to create a configuration file","Signal Monitor");
      return(false);
   }
//--- Save the selection
   //--- Indicator type
   m_signal_set[index].ind_type=m_indicator_type.GetListViewPointer().SelectedItemIndex();
   //--- Indicator period
   m_signal_set[index].ind_period=(int)m_period_edit.GetValue();
   //--- Type of applied price
   m_signal_set[index].app_price=m_applied_price.GetListViewPointer().SelectedItemIndex();
   //--- Rule type
   m_signal_set[index].rule_type=m_rule_type.GetListViewPointer().SelectedItemIndex();
   //--- Rule value
   m_signal_set[index].rule_value=(double)m_rule_value.GetValue();
   //--- Text label display type
   m_signal_set[index].label_type=m_label_button[0].IsPressed()?0:1;
   //--- Save the value of the text field for the second type
   if(m_label_button[1].IsPressed())
      StringToCharArray(StringSubstr(m_text_box.GetValue(),0,3),m_signal_set[index].label_value);
   //--- Color of the text label
   m_signal_set[index].label_color=m_color_button[0].CurrentColor();
   //--- Background color
   if(m_set_param[0].IsPressed())
      m_signal_set[index].back_color=m_color_button[1].CurrentColor();
   else
      m_signal_set[index].back_color=clrNONE;
   //--- Border color
   if(m_set_param[1].IsPressed())
      m_signal_set[index].border_color=m_color_button[2].CurrentColor();
   else
      m_signal_set[index].border_color=clrNONE;
   //--- Tooltip value
   m_signal_set[index].tooltip=m_set_param[2].IsPressed();
   if(m_signal_set[index].tooltip)
      StringToCharArray(m_tooltip_text.GetValue(),m_signal_set[index].tooltip_text);
   //--- Selected image
   m_signal_set[index].image=m_set_param[3].IsPressed();
   if(m_signal_set[index].image)
      m_signal_set[index].img_index=m_pictures_slider.GetRadioButtonsPointer().SelectedButtonIndex();
   //--- Selected timegrames
   int tf=0;
   for(int i=0; i<21; i++)
   {
      if(!m_tf_button[i].IsLocked() && m_tf_button[i].IsPressed())
      {
         m_signal_set[index].timeframes[i]=true;
         StringToCharArray(m_tf_button[i].LabelText(),m_signal_set[index].tf_name[i].tf);
         tf++;
      }
      else
         m_signal_set[index].timeframes[i]=false;
   }
   //---
   if(tf<1)
   {
      MessageBox("No timeframes selected","Signal Monitor");
      FileClose(h);
      return(false);
   }
//---
   FileWriteStruct(h,m_signal_set[index]);
   FileClose(h);
   Print("Configuration signal_"+string(index)+" has been successfully saved");
//---
   return(true);
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::LoadSignalSet(int index)
{
   int h=FileOpen("Signal Monitor\\signal_"+string(index)+".bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      MessageBox("Configuration not found","Signal Monitor");
      return(false);
   }
   ZeroMemory(m_signal_set[index]);
   FileReadStruct(h,m_signal_set[index]);
//--- Loading indicator type
   m_indicator_type.SelectItem(m_signal_set[index].ind_type);
   RebuildParameters(m_signal_set[index].ind_type);
   m_indicator_type.GetButtonPointer().Update(true);
//--- Loading indicator period
   m_period_edit.SetValue((string)m_signal_set[index].ind_period);
   m_period_edit.GetTextBoxPointer().Update(true);
//--- Loading applied price
   if(!m_applied_price.IsLocked())
   {
      m_applied_price.SelectItem(m_signal_set[index].app_price);
      m_applied_price.GetButtonPointer().Update(true);
   }
//--- Loading signal rule
   m_rule_type.SelectItem(m_signal_set[index].rule_type);
   m_rule_type.GetButtonPointer().Update(true);
   m_rule_value.SetValue((string)m_signal_set[index].rule_value);
   m_rule_value.GetTextBoxPointer().Update(true);
//--- Loading a text label
   if(m_signal_set[index].label_type==0)
   {
      m_label_button[0].IsPressed(true);
      m_label_button[0].Update(true);
      m_label_button[1].IsPressed(false);
      m_label_button[1].Update(true);
      m_text_box.IsLocked(true);
   }
   else
   {
      m_label_button[0].IsPressed(false);
      m_label_button[0].Update(true);
      m_label_button[1].IsPressed(true);
      m_label_button[1].Update(true);
      m_text_box.IsLocked(false);
      m_text_box.ClearTextBox();
      m_text_box.AddText(0,CharArrayToString(m_signal_set[index].label_value));
      m_text_box.Update(true);
   }
//--- Loading the color of the text label
   m_color_button[0].CurrentColor(m_signal_set[index].label_color);
   m_color_button[0].Update(true);
//--- Loading the background color
   if(m_signal_set[index].back_color==clrNONE)
   {
      m_set_param[0].IsPressed(false);
      m_set_param[0].Update(true);
      m_color_button[1].IsLocked(true);
      m_color_button[1].GetButtonPointer().Update(true);
   }
   else
   {
      m_set_param[0].IsPressed(true);
      m_set_param[0].Update(true);
      m_color_button[1].IsLocked(false);
      m_color_button[1].CurrentColor(m_signal_set[index].back_color);
      m_color_button[1].GetButtonPointer().Update(true);
   }
//--- Loading the border color
   if(m_signal_set[index].border_color==clrNONE)
   {
      m_set_param[1].IsPressed(false);
      m_set_param[1].Update(true);
      m_color_button[2].IsLocked(true);
      m_color_button[2].GetButtonPointer().Update(true);
   }
   else
   {
      m_set_param[1].IsPressed(true);
      m_set_param[1].Update(true);
      m_color_button[2].IsLocked(false);
      m_color_button[2].CurrentColor(m_signal_set[index].border_color);
      m_color_button[2].GetButtonPointer().Update(true);
   }
//--- Loading the tooltip value
   if(!m_signal_set[index].tooltip)
   {
      m_set_param[2].IsPressed(false);
      m_set_param[2].Update(true);
      m_tooltip_text.IsLocked(true);
      m_tooltip_text.Update(true);
   }
   else
   {
      m_set_param[2].IsPressed(true);
      m_set_param[2].Update(true);
      m_tooltip_text.IsLocked(false);
      m_tooltip_text.ClearTextBox();
      m_tooltip_text.AddText(0,CharArrayToString(m_signal_set[index].tooltip_text));
      m_tooltip_text.Update(true);
   }
//--- Loading the image
   if(!m_signal_set[index].image)
   {
      m_set_param[3].IsPressed(false);
      m_set_param[3].Update(true);
      m_pictures_slider.IsLocked(true);
      m_pictures_slider.GetRadioButtonsPointer().Update(true);
   }
   else
   {
      m_set_param[3].IsPressed(true);
      m_set_param[3].Update(true);
      m_pictures_slider.IsLocked(false);
      m_pictures_slider.GetRadioButtonsPointer().SelectButton(m_signal_set[index].img_index);
      m_pictures_slider.GetRadioButtonsPointer().Update(true);
   }
//--- Loading selected timeframes
   for(int i=0; i<21; i++)
   {
      if(!m_tf_button[i].IsLocked())
      {
         m_tf_button[i].IsPressed(m_signal_set[index].timeframes[i]);
         m_tf_button[i].Update(true);
      }
   }
//---
   FileClose(h);
   return(true);
}

Assim, a primeira ação com o algoritmo de salvar/carregar é concluída. Agora, nós precisamos criar o objeto que servirá como registros dos sinais criados. Ao clicar nesses objetos, nós poderemos editar os parâmetros do sinal de negociação criado anteriormente. Para implementar esses objetos, nós criamos uma matriz das instâncias de classe CButton.

CButton           m_signal_editor[5];

Além disso, adicionamos um método criando os objetos.

bool              CreateSignalEditor(CButton &button,string text,const int x_gap,const int y_gap);

Implementamos esse método no arquivo StepWindow.mqh, pois esses objetos pertencem à janela principal.

//+------------------------------------------------------------------+
//| Creates a button with an image                                   |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\settings_light.bmp"

bool CProgram::CreateSignalEditor(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 up 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\\settings_light.bmp");
   button.BackColor(baseclr);
   button.BackColorHover(baseclr);
   button.BackColorPressed(pressed);
   button.BorderColor(baseclr);
   button.BorderColorHover(baseclr);
   button.BorderColorPressed(pressed);
   button.LabelColor(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.LabelColorHover(clrWhite);
   button.IsCenterText(true);
//--- Create the control
   if(!button.CreateButton(text,x_gap,y_gap))
      return(false);
//--- Add the element pointer to the base
   CWndContainer::AddToElementsArray(0,button);
   return(true);
}

Usando esse método, nós adicionamos cinco objetos ao corpo do CreateStepWindow(), que serão os objetos na lista de sinais.

//---
   for(int i=0; i<5; i++)
   {
      if(!CreateSignalEditor(m_signal_editor[i],"Signal_"+string(i),10,40*i+90))
         return(false);
   }

Para desativar a exibição desses elementos após o lançamento do aplicativo, esconda-o no método CreateGUI().

//+------------------------------------------------------------------+
//| Creates the graphical interface of the program                   |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//--- Step 1-3. Symbol selection window.
   if(!CreateStepWindow("Signal Monitor Step 1: Choose Symbols"))
      return(false);
//---
   if(!CreateSetWindow("Signal Monitor Edit Signal"))
      return(false);
//--- Creating form 2 for the color picker
   if(!CreateColorWindow("Color Picker"))
      return(false);
//--- Finishing the creation of GUI
   CWndEvents::CompletedGUI();
   m_back_button.Hide();
   m_add_signal.Hide();
   m_signal_header.Hide();
   m_label_button[1].IsPressed(true);
   m_label_button[1].Update(true);
   for(int i=0; i<5; i++)
      m_signal_editor[i].Hide();
   return(true);
}

A próxima coisa a fazer antes da compilação do projeto é criar um método que excluirá todos os dados salvos anteriormente durante a configuração inicial. Para fazer isso, criamos o método ClearSaves() e chamamos ele no construtor da classe CProgram.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::ClearSaves(void)
{
   for(int i=0; i<5; i++)
      FileDelete("Signal Monitor\\signal_"+string(i)+".bin");
   m_total_signals=0;
   return(true);
}

Agora, adicionamos o seguinte ao evento de clique no botão de sinal Add:

//--- Add Signal button click event
      if(lparam==m_add_signal.Id())
      {
         m_set_window.OpenWindow();
         m_number_signal=-1;
         RebuildTimeframes();
         m_new_signal.LabelText("Add");
         m_new_signal.Update(true);
      }

O mecanismo para adicionar um novo sinal de negociação está pronto após a compilação do projeto. O próximo passo é a implementação da edição de um sinal criado anteriormente.

Fig.5 Adicionando um novo sinal de negociação.

Vamos resumir tudo. Como é mostrado na figura 5, nós implementamos a possibilidade de adicionar os sinais com um clique no botão Add Signal. Além disso, um botão com o novo nome do sinal é adicionado a Signal List. Atualmente, ele é um valor predefinido sem a possibilidade de editá-lo. No entanto, nada acontece com o clique em Signal_0, então vamos corrigir isso. Vamos ativar a reabertura da janela de configurações com o carregamento na interface exatamente daquelas configurações que foram salvas anteriormente para o sinal selecionado. Outra ideia é implementar a possibilidade de editar as configurações carregadas e salvá-las.

Abrimos o corpo do OnEvent() na classe base CProgram e localizamos a parte com o evento de clique no botão. Adicionamos o seguinte código a ele:

//---
      for(int i=0; i<5; i++)
      {
         if(lparam==m_signal_editor[i].Id())
         {
            LoadSignalSet(i);
            m_new_signal.LabelText("Save");
            m_new_signal.Update(true);
            m_set_window.OpenWindow();
            m_number_signal=i;
         }
      }

Aqui, nós determinamos qual botão de sinal criado foi pressionado. Sabendo disso, nós carregamos os dados salvos anteriormente na interface da janela de configurações usando o método LoadSignalSet(), alteramos o nome do botão de Add para Save e abrimos a janela de configurações.


Agora que as ferramentas para criar e editar os sinais de negociação estão prontas, é hora de vinculá-lo à parte do aplicativo responsável pela pesquisa e exibição dos sinais. Nós já criamos uma base para o monitoramento de sinais. Ele possui uma exibição tabular com os nomes de linhas (símbolos selecionados na primeira etapa da configuração) e colunas (períodos selecionados na segunda etapa da configuração).

Fig.6 Botão criando um monitor de sinais de negociação.

A sequência de ações após a criação de pelo menos um sinal de negociação é simples. Um clique em Create aciona a formação de um monitor de sinais de negociação com base em todo o array de configurações que foram definidas anteriormente. Antes de prosseguir com a programação deste sistema, nós precisamos complementar o método ToMonitor() que é chamado depois de pressionar Create.

//--- Hide Step 3
   m_add_signal.Hide();

   m_signal_header.Hide();
   m_back_button.Hide();
   m_next_button.Hide();
   for(int i=0; i<5; i++)
      m_signal_editor[i].Hide();

Como nós temos os objetos de botão que permitem exibir e editar os sinais de negociação criados atualmente, esses botões também devem estar ocultos ao pular para a janela do monitor, da mesma forma que todos os controles da Etapa 3 anterior.

No primeiro artigo, quando nós desenvolvemos a estrutura do aplicativo, um dos elementos do monitor era o bloco de indicação mostrado na figura 5 do primeiro artigo. Seu objetivo é exibir em tempo real a presença de um dos sinais de negociação criados anteriormente. Portanto, o primeiro passo é criar um objeto que será usado como um bloco de indicação. Isso pode ser feito implementando o método CreateSignalButton() na classe CProgram

bool              CreateSignalButton(CButton &button,const int x_gap,const int y_gap);

Além disso, adicionamos um array das instâncias de classe CButton necessárias para a criação do conjunto completo de blocos de indicação.

CButton           m_signal_button[];

Agora, abrimos o StepWindow.mqh e adicionamos a implementação do método criado no final do arquivo:

//+------------------------------------------------------------------+
//| Creates an indication block                                      |
//+------------------------------------------------------------------+
bool CProgram::CreateSignalButton(CButton &button,const int x_gap,const int y_gap)
{
//---
   color baseclr=C'220,225,235';
//--- Save the window pointer
   button.MainPointer(m_step_window);
//--- Set up properties before creation
   button.TwoState(false);
   button.XSize(40);
   button.YSize(20);
   button.IconXGap(2);
   button.IconYGap(button.YSize()/2-8);
   button.LabelXGap(19);
   button.LabelYGap(2);
   button.FontSize(m_base_font_size);
   button.BackColor(baseclr);
   button.BackColorHover(baseclr);
   button.BackColorPressed(baseclr);
   button.BorderColor(baseclr);
   button.BorderColorHover(baseclr);
   button.BorderColorPressed(baseclr);
   button.LabelColor(clrBlack);
   button.LabelColorPressed(clrSlateGray);
   button.IconFile("");
   button.IconFileLocked("");
   button.IsDoubleBorder(true);
//--- Create the control
   if(!button.CreateButton("",x_gap-button.XSize()/2,y_gap))
      return(false);
//--- Add the element pointer to the base
   CWndContainer::AddToElementsArray(0,button);
   return(true);
}

Agora aplicamos-o no método de criação ToMonitor(). Para fazer isso, localizamos a seção Timeframes no corpo do método e adicionamos o código ao método como mostrado abaixo:

//--- 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);
   }
//-- Signal blocks
   int k=0;
   ArrayResize(m_signal_button,sy*tf);
   for(int j=0; j<sy; j++)
   {
      for(int i=0; i<tf; i++)
      {
         if(!CreateSignalButton(m_signal_button[k],m_timeframe_label[i].XGap()+m_timeframe_label[i].XSize()/2,m_step_window.CaptionHeight()+25+j*25))
            return;
         m_signal_button[k].Update(true);
         k++;
      }
   }
//--- 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);

Compilamos o projeto e obtemos um layout pronto para a exibição futura dos sinais de negociação.


Fig.7 Um layout pronto dos sinais de negociação.

Agora lembre-se de quais elementos do bloco de indicação podem ser configurados para exibir certos sinais.

  • Cor de fundo.
  • A presença e cor da borda do bloco de indicação.
  • A cor e o valor do rótulo do texto.
  • Presença de um ícone.
  • Presença de uma dica de ferramenta.

Para gerenciar essas propriedades, nós definimos os seguintes métodos na seção privada de nossa classe base CProgram:

   void              SetBorderColor(int index, color clr);
   void              SetLabel(int index, string text,color clr=clrBlack);
   void              SetIcon(int index,int number);
   void              SetBackground(int index,color clr);
   void              SetTooltip(int index,string text="\n");

Sua implementação:

//+------------------------------------------------------------------+
//| Set the border color                                             |
//+------------------------------------------------------------------+
void CProgram::SetBorderColor(int index, color clr)
{
   m_signal_button[index].BorderColor(clr);
   m_signal_button[index].BorderColorHover(clr);
   m_signal_button[index].BorderColorPressed(clr);
   m_signal_button[index].Update(true);
}
//+------------------------------------------------------------------+
//| Set the label text                                               |
//+------------------------------------------------------------------+
void CProgram::SetLabel(int index, string text,color clr=clrBlack)
{
   m_signal_button[index].LabelColor(clr);
   m_signal_button[index].LabelColorHover(clr);
   m_signal_button[index].LabelColorPressed(clr);
   m_signal_button[index].LabelText(text);
   m_signal_button[index].Update(true);
}
//+------------------------------------------------------------------+
//| Set the background                                               |
//+------------------------------------------------------------------+
void CProgram::SetBackground(int index,color clr)
{
   m_signal_button[index].BackColor(clr);
   m_signal_button[index].BackColorHover(clr);
   m_signal_button[index].Update(true);
}
//+------------------------------------------------------------------+
//| Set the icon                                                     |
//+------------------------------------------------------------------+
void CProgram::SetIcon(int index,int number)
{
   //---
   string image[]=
   {
      "Images\\EasyAndFastGUI\\Icons\\bmp16\\arrow_up.bmp",
      "Images\\EasyAndFastGUI\\Icons\\bmp16\\arrow_down.bmp"
   };
   string path=(number>=0)?image[number]:"";
   if(number<0)
      m_signal_button[index].IsCenterText(true);
   else
      m_signal_button[index].IsCenterText(false);
   m_signal_button[index].IconFile(path);
   m_signal_button[index].IconFilePressed(path);
   m_signal_button[index].Update(true);
}
//+------------------------------------------------------------------+
//| Set the tooltip                                                  |
//+------------------------------------------------------------------+
void CProgram::SetTooltip(int index,string text="\n")
{
   m_signal_button[index].Tooltip(text);
   m_signal_button[index].ShowTooltip(true);
}

Em seguida, nós precisamos criar alguns métodos auxiliares necessários para os cálculos adicionais, exibição correta e, o mais importante, para a correspondência de cada um dos blocos de indicação criados para uma linha específica (símbolo selecionado) e coluna (período de tempo). Primeiro, criamos os métodos para determinar a linha e a coluna do bloco de indicação com base no índice na tabela.

   int               GetRow(int index,int row_size);
   int               GetCol(int index,int row_size);
//+------------------------------------------------------------------+
//| Determining a row by the index                                   |
//+------------------------------------------------------------------+
int CProgram::GetRow(int index,int row_size)
{
   return(int(MathFloor(index/row_size)+1));
}
//+------------------------------------------------------------------+
//| Determining a column by the index                                |
//+------------------------------------------------------------------+
int CProgram::GetCol(int index,int row_size)
{
   return(int(MathMod(index,row_size)+1));
}

Nós também precisamos obter os dados necessários a partir da interface. Ou seja, nós precisamos converter a exibição de texto dos períodos no tipo de enumeração do período. Nós também precisamos descobrir com base no índice do bloco de indicação, a qual símbolo e período ele corresponde na tabela.

//+------------------------------------------------------------------+
//| Return timeframe by row                                          |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES CProgram::StringToTimeframe(const string timeframe)
{
   if(timeframe=="M1")  return(PERIOD_M1);
   if(timeframe=="M2")  return(PERIOD_M2);
   if(timeframe=="M3")  return(PERIOD_M3);
   if(timeframe=="M4")  return(PERIOD_M4);
   if(timeframe=="M5")  return(PERIOD_M5);
   if(timeframe=="M6")  return(PERIOD_M6);
   if(timeframe=="M10") return(PERIOD_M10);
   if(timeframe=="M12") return(PERIOD_M12);
   if(timeframe=="M15") return(PERIOD_M15);
   if(timeframe=="M20") return(PERIOD_M20);
   if(timeframe=="M30") return(PERIOD_M30);
   if(timeframe=="H1")  return(PERIOD_H1);
   if(timeframe=="H2")  return(PERIOD_H2);
   if(timeframe=="H3")  return(PERIOD_H3);
   if(timeframe=="H4")  return(PERIOD_H4);
   if(timeframe=="H6")  return(PERIOD_H6);
   if(timeframe=="H8")  return(PERIOD_H8);
   if(timeframe=="H12") return(PERIOD_H12);
   if(timeframe=="D1")  return(PERIOD_D1);
   if(timeframe=="W1")  return(PERIOD_W1);
   if(timeframe=="MN")  return(PERIOD_MN1);
//--- The default value
   return(::Period());
}
//+------------------------------------------------------------------+
//| Determine the timeframe                                          |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES CProgram::GetTimeframe(int index)
{
   int tf=ArraySize(m_timeframes);
   return(StringToTimeframe((m_timeframe_label[GetCol(index,tf)-1].LabelText())));
}
//+------------------------------------------------------------------+
//| Determine the symbol                                             |
//+------------------------------------------------------------------+
string CProgram::GetSymbol(int index)
{
   int tf=ArraySize(m_timeframes);
   return(m_symbol_label[GetRow(index,tf)-1].LabelText());
}

O próximo método está diretamente relacionado ao algoritmo de busca de sinal: ele procura o conjunto de parâmetros dos sinais criados anteriormente no símbolo e período de tempo especificados.

bool              GetSignal(string sy,ENUM_TIMEFRAMES tf,SIGNAL &signal_set);

As configurações são passadas pela estrutura SIGNAL a partir de um conjunto de parâmetros. 

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::GetSignal(string sy,ENUM_TIMEFRAMES tf,SIGNAL &signal_set)
{
//--- Getting the indicator handle
   int h=INVALID_HANDLE;
   ENUM_APPLIED_PRICE app_price;
   switch(signal_set.app_price)
   {
   case  0:
      app_price=PRICE_CLOSE;
      break;
   case  1:
      app_price=PRICE_OPEN;
      break;
   case  2:
      app_price=PRICE_HIGH;
      break;
   case  3:
      app_price=PRICE_LOW;
      break;
   case  4:
      app_price=PRICE_MEDIAN;
      break;
   case  5:
      app_price=PRICE_TYPICAL;
      break;
   case  6:
      app_price=PRICE_WEIGHTED;
      break;
   default:
      app_price=PRICE_CLOSE;
      break;
   }
//---
   switch(signal_set.ind_type)
   {
   case  0:
      h=iATR(sy,tf,signal_set.ind_period);
      break;
   case  1:
      h=iCCI(sy,tf,signal_set.ind_period,app_price);
      break;
   case  2:
      h=iDeMarker(sy,tf,signal_set.ind_period);
      break;
   case  3:
      h=iForce(sy,tf,signal_set.ind_period,MODE_SMA,VOLUME_TICK);
      break;
   case  4:
      h=iWPR(sy,tf,signal_set.ind_period);
      break;
   case  5:
      h=iRSI(sy,tf,signal_set.ind_period,app_price);
      break;
   case  6:
      h=iMomentum(sy,tf,signal_set.ind_period,app_price);
      break;
   default:
      break;
   }
   if(h==INVALID_HANDLE)
   {
      Print(sy+". Failed to get handle");
      Print("Handle = ",h,"  error = ",GetLastError());
      return(false);
   }
   //---
   double arr[1];
   if(CopyBuffer(h,0,
    0,1,arr)!=1)
   {
      Print("sy= ",sy,"tf= ",EnumToString(tf)," Failed to get handle data ",GetLastError());
      return(false);
   }
   IndicatorRelease(h);
//--- Check the condition
   double r_value=signal_set.rule_value;
   double c_value=arr[0];
   m_ind_value=c_value;
   int s=0;
   switch(signal_set.rule_type)
   {
   case  0:
      if(c_value>r_value)
         s=1;
      break;
   case  1:
      if(c_value>=r_value)
         s=1;
      break;
   case  2:
      if(c_value==r_value)
         s=1;
      break;
   case  3:
      if(c_value<r_value)
         s=1;
      break;
   case  4:
      if(c_value<=r_value)
         s=1;
      break;
   default:
      s=0;
      break;
   }
//---
   if(s>0)
      return(true);
   return(false);
}

O método GetSignal() recebe da estrutura SIGNAL as informações sobre quais indicadores disponíveis foram selecionados para gerar os sinais de negociação, quais configurações foram selecionadas para o indicador e qual regra de busca foi ela definida. Não esqueça que a filtragem pelos períodos pode ser realizada duas vezes para cada sinal. A primeira vez que ela é realizada na segunda etapa de configuração, é possível filtrar os valores selecionados na janela de criação do sinal de negociação, conforme mostrado na figura 8 abaixo.

Fig.8 Selecionando os períodos para o sinal criado.

Para o nosso algoritmo considerar esse filtro e não procurar por sinais fora dos períodos especificados, a especificação dos períodos deve ser verificada para cada um dos sinais de negociação criados. Então, criamos o método CheckTimeframe() na classe base. O método servirá como um filtro.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CheckTimeframe(ENUM_TIMEFRAMES tf,SIGNAL &signal_set)
{
   for(int i=0; i<21; i++)
   {
      if(StringToTimeframe(CharArrayToString(signal_set.tf_name[i].tf))==tf)
         return(true);
   }
   return(false);
}

Agora, é hora de criar o mecanismo de busca do sinal de negociação. Para fazer isso, adicionamos um método à seção public da classe CProgram: SearchSignal().

bool              SearchSignals(void);

Vamos analisar sua implementação passo a passo com mais detalhes e ver o objetivo dos métodos auxiliares criados anteriormente.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::SearchSignals(void)
{
//--- Search for set signals
   SIGNAL signal_set[];
   int cnt=0;
   for(int i=0; i<5; i++)
   {
      if(FileIsExist("Signal Monitor\\signal_"+string(i)+".bin"))
         cnt++;
   }
//---
   ArrayResize(signal_set,cnt);
   ZeroMemory(signal_set);
//---
   for(int i=0; i<cnt; i++)
   {
      int h=FileOpen("Signal Monitor\\signal_"+string(i)+".bin",FILE_READ|FILE_BIN);
      if(h==INVALID_HANDLE)
      {
         MessageBox("Configuration not found","Signal Monitor");
         return(false);
      }
      FileReadStruct(h,signal_set[i]);
      FileClose(h);
      for(int j=0; j<ArraySize(m_signal_button); j++)
      {
         //---
         string sy=GetSymbol(j);
         ENUM_TIMEFRAMES tf=GetTimeframe(j);
         //---
         if(!CheckTimeframe(tf,signal_set[i]))
            continue;
         //---
         if(GetSignal(sy,tf,signal_set[i]))
         {
            //---
            if(signal_set[i].label_type==1)
               SetLabel(j,CharArrayToString(signal_set[i].label_value),signal_set[i].label_color);
            else
               SetLabel(j,DoubleToString(m_ind_value,3),signal_set[i].label_color);
            //---
            if(signal_set[i].back_color!=clrNONE)
               SetBackground(j,signal_set[i].back_color);
            //---
            if(signal_set[i].border_color!=clrNONE)
               SetBorderColor(j,signal_set[i].border_color);
            else
               SetBorderColor(j,signal_set[i].back_color);
            //---
            if(signal_set[i].tooltip)
               SetTooltip(j,CharArrayToString(signal_set[i].tooltip_text));
            //---
            if(signal_set[i].image)
               SetIcon(j,signal_set[i].img_index);
            else
               SetIcon(j,-1);
         }
      }
   }
   return(true);
}

Na primeira etapa da operação, o método de busca coleta os dados sobre o número total de sinais de negociação criados e configurados. Em seguida, o método executa um loop através dos arquivos que conectam as informações sobre as configurações de sinal e lê esses dados em uma estrutura. O mecanismo determina para cada bloco de indicação o símbolo e o período apropriado aos quais o bloco corresponde na forma de tabela. Com base nesses dados, verificamos se um sinal de negociação precisa ser pesquisado no período selecionado. Se o período corresponder, buscamos um sinal. Se um sinal for encontrado, colorimos o bloco de indicação de acordo com a configuração do sinal. Agora, o método criado pode ser aplicado. O método deve ser chamado no final do corpo do método ToMonitor().

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

Agora, vamos tentar ativar uma pesquisa repetida após um determinado intervalo de tempo. Abrimos o arquivo SignalMonitor.mq5 e criamos uma enumeração no início do arquivo:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
enum UPDATE
{
   MINUTE,        // 1 Minute
   MINUTE_15,     // 15 Minutes
   MINUTE_30,     // 30 Minutes
   HOUR,          // 1 Hour
   HOUR_4         // 4 Hours
};

Uma nova configuração agora pode ser facilmente adicionada às entradas:

input UPDATE               Update            =  HOUR;                // Update interval

Criamos duas variáveis para os cálculos.

int cnts=0;
datetime update;

Adicionamos as seguintes linhas na inicialização do expert:

//---
   switch(Update)
   {
   case MINUTE:
      cnts=60;
      break;
   case MINUTE_15:
      cnts=60*15;
      break;
   case MINUTE_30:
      cnts=60*30;
      break;
   case HOUR:
      cnts=3600;
      break;
   case HOUR_4:
      cnts=3600*4;
      break;
   default:
      cnts=1;
      break;
   }
   update=TimeLocal()+cnts;

Assim, nós determinamos o intervalo de atualização e definimos o próximo tempo de atualização. No corpo da função OnTick(), adicionamos a verificação de tempo: se o intervalo de tempo especificado tiver passado, buscamos os sinais de negociação novamente.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   if(TimeLocal()>update)
   {
      program.SearchSignals();
      update=TimeLocal()+cnts;
   }
}

Compilamos o projeto e criamos um conjunto de seus próprios símbolos. Nós podemos adicionar um sinal para demonstrar a operação do monitor.

 

Na próxima parte desta série de artigos, nós continuaremos expandindo a funcionalidade atual para uma configuração mais flexível dos sinais de negociação, e também melhoraremos alguns dos recursos existentes.


Conclusão

O arquivo anexado abaixo contém todos os arquivos descritos adequadamente organizados em pastas. Para uma operação correta, você deve salvar a pasta MQL5 na pasta raiz da plataforma. 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 na Fig. 9 abaixo.


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


Traduzido do russo pela MetaQuotes Software Corp.
Artigo original: https://www.mql5.com/ru/articles/7600

Arquivos anexados |
MQL5.zip (1706.81 KB)
Trabalhando com séries temporais na biblioteca DoEasy (Parte 38): coleção de séries temporais - atualização em tempo real e acesso aos dados do programa Trabalhando com séries temporais na biblioteca DoEasy (Parte 38): coleção de séries temporais - atualização em tempo real e acesso aos dados do programa

No artigo, consideraremos a atualização em tempo real dos dados das séries temporais, bem como o envio de mensagens sobre o evento "Nova Barra" para o gráfico do programa de controle, a partir de todas as séries temporais de todos os símbolos, a fim de processar estes eventos nos programa. Para determinar se necessário atualizar séries temporais para símbolos e períodos inativos, usaremos a classe "Novo tick".

Otimização Walk Forward Contínua (parte 5): Panorama do Projeto Otimizador Automático e Criação da Interface Gráfica Otimização Walk Forward Contínua (parte 5): Panorama do Projeto Otimizador Automático e Criação da Interface Gráfica

Este artigo fornece uma descrição mais detalhada da otimização walk-forward na plataforma MetaTrader 5. Nos artigos anteriores, nós consideramos os métodos para gerar e filtrar o relatório de otimização e começar a analisar a estrutura interna do aplicativo responsável pelo processo de otimização. O Otimizador Automático é implementado como uma aplicação em C# e possui sua própria interface gráfica. O quinto artigo é dedicado à criação dessa interface gráfica.

Linguagem MQL como um meio de marcação da interface gráfica de programas MQL. Parte 1 Linguagem MQL como um meio de marcação da interface gráfica de programas MQL. Parte 1

O artigo propõe uma nova ideia para descrever a interface de programas MQL com ajuda das construções da linguagem MQL. As classes especiais transformam o esquema visual MQL em elementos da GUI, permitem gerenciá-los de maneira unificada, configurar propriedades e processar eventos. Além disso, apresenta exemplos de uso de layouts para caixas de diálogo e elementos da biblioteca padrão.

Trabalhando com séries temporais na biblioteca DoEasy (Parte 39): indicadores com base na biblioteca - preparação de dados e eventos das séries temporais Trabalhando com séries temporais na biblioteca DoEasy (Parte 39): indicadores com base na biblioteca - preparação de dados e eventos das séries temporais

No artigo, consideramos o uso da biblioteca DoEasy para criar indicadores multissímbolos e multiperíodos. Prepararemos as classes da biblioteca, para trabalhar como parte dos indicadores, e testaremos a criação correta de séries temporais para usá-los como fontes de dados em indicadores. Realizaremos a criação e o envio de eventos de séries temporais.