Monitoramento de sinais de negociação multimoeda (Parte 5): Sinais compostos

Alexander Fedosov | 2 setembro, 2020

Conteúdo

Introdução

Na parte anterior da série de artigos, dedicada à criação de um monitor de sinal de negociação multimoeda, nós introduzimos a possibilidade de usar os indicadores personalizados e estendemos o sistema de regras lógicas que permite a filtragem dos dados. No entanto, muitos traders e desenvolvedores usam sistemas de negociação com base em vários indicadores. Cada um dos tipos de indicadores, incluindo os de tendência, lateralização e osciladores, possuem suas desvantagens em certos mercados.

Por exemplo, durante um movimento lateral prolongado, os indicadores de tendência frequentemente produzem muitos sinais falsos. Portanto, eles precisam ser confirmados por algumas outras ferramentas. Isso pode ser feito adicionando mais indicadores, além do principal, que servem como filtros para os sinais gerados pelo indicador principal. Opcionalmente, os traders podem criar um sistema de negociação com base em um conjunto de indicadores que mostram os mesmos estados do mercado. Nesse caso, o sinal de entrada no mercado pode ser gerado por confirmações simultâneas de vários indicadores.

Portanto, o próximo passo lógico no desenvolvimento de nosso indicador é a introdução de sinais compostos feitos de sinais simples disponíveis. Assim, nós trabalharemos com o conceito de sinal composto. Nós usaremos um projeto da parte anterior como base. O projeto está disponível no final do artigo anterior.


Sinais Compostos

Para começar, vamos definir o conceito de "sinal composto", que será utilizado em nosso projeto. Nas versões anteriores, nós utilizávamos os sinais simples, que eram criados aplicando-se condições específicas à fonte recebida, que podiam ser apresentados por vários indicadores, como RSI, WPR e CCI. Além disso, foi implementado a possibilidade de usar indicadores personalizados.

Um sinal composto é um sinal que consiste em dois ou mais sinais simples que são conectados entre si por operadores lógicos E/OU.

Assim, o sinal composto incluirá vários sinais simples criados anteriormente, que interagirão entre si por meio de operadores lógicos. Será possível criar um sinal complexo contendo a condição para a presença de dois ou três sinais simples simultâneos em um determinado período de tempo. Assim, o sistema de negociação terá um sinal principal e um filtro. O operador lógico OR nos permitirá pesquisar um sinal de negociação em várias direções ao mesmo tempo e, assim, cobrir uma área maior na análise dos estados atuais do mercado.

Para uma explicação melhor, aqui está um plano para atualizar e aprimorar nossas aplicações. Em primeiro lugar, nós precisamos atualizar a interface do aplicativo e adicionar novos controles.


Fig. 1. Adicionando elementos da Interface do Usuário (UI)

A parte esquerda da Figura 1 já é familiar para você. É um botão para adicionar um sinal, seguido dos sinais simples já adicionados. Agora é necessário criar um botão para adicionar um sinal composto (1), uma lista de sinais compostos adicionados (2) e um terceiro grupo de botões comutadores (3) permitindo definir a flag de uso para os sinais simples:

Um esquema de criação do sinal composto é mostrado abaixo na figura 2. Os elementos denominados Simple 1 e Simple 2 mostram uma lista de sinais simples já criados que podem ser usados na criação de um sinal complexo. Quanto aos atributos de utilização do sinal, os sinais apresentados na figura 2 deveriam ter um atributo “C” ou “B”, caso contrário não seriam apresentados na lista.

Fig.2 O esquema da janela de criação e edição do sinal composto

Isso é seguido por uma seção chamada de Rule, na qual o sinal composto é criado. Um sinal das listas acima pode ser adicionado a qualquer um dos três slots (vagas). Os operadores lógicos entre os slots são selecionados por meio de botões. Isso define uma regra para este sinal composto. Por exemplo, se você definir Simple 1 E Simple 2, um sinal composto só será exibido quando esses dois sinais simples aparecerem ao mesmo tempo.


Implementando a Funcionalidade

Antes de introduzir os novos acréscimos no aplicativo, é necessário preparar os elementos existentes para serem usados com as novas funções. Os elementos de UI da lista de sinais simples precisam ser alterados. A figura 3 mostra que um botão simples com o nome do sistema de um sinal simples será substituído por um rótulo de texto com um nome de sinal especificado pelo usuário, acompanhado do botão Edit e do botão para a flag de uso do sinal.


Fig. 3. Atualizando o controle de edição do sinal

Agora, abrimos o projeto baixado do artigo anterior e prosseguimos com as adições. Em primeiro lugar, nós removemos o método CreateSignalEditor() e o array m_signal_editor[] que é usado para criar os botões de edição. Antes de prosseguir com a criação de novos elementos e métodos, vamos introduzir duas substituições de macro que determinarão o número máximo permitido de sinais simples e compostos.

#define SIMPLE_SIGNALS 10
#define COMPOSITE_SIGNALS 5

 Em seguida, nós vamos para o final da janela principal CreateStepWindow() criando o corpo do método e em vez do seguinte código:

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

adicionamos uma nova implementação para a exibição e edição de um novo sinal.

   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(!CreateLabel(m_signal_label[i],10,40*i+95,"Signal_"+string(i)))
         return(false);
      if(!CreateSignalSet(m_signal_ind[i],"Edit",C'255,195,50',150,40*i+90))
         return(false);
      if(!CreateSignalSet(m_signal_type[i],"S",C'75,190,240',150+35,40*i+90))
         return(false);
   }

A implementação tem dois novos arrays das instâncias de classe CButton: m_signal_ind[] e m_signal_type[], bem como o array CTextLabel m_signal_label[]. Vamos adicioná-los à classe base do projeto CProgram.

   CTextLabel        m_signal_label[SIMPLE_SIGNALS];
   CButton           m_signal_ind[SIMPLE_SIGNALS];
   CButton           m_signal_type[SIMPLE_SIGNALS];

Além disso, declaramos e implementamos o novo método CreateSignalSet().

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateSignalSet(CButton &button,string text,color baseclr,const int x_gap,const int y_gap)
{
//--- Store the window pointer
   button.MainPointer(m_step_window);
//--- Set properties before creation
   button.XSize(30);
   button.YSize(30);
   button.Font(m_base_font);
   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(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.LabelColorHover(clrWhite);
   button.IsCenterText(true);
//--- Create a control element
   if(!button.CreateButton(text,x_gap,y_gap))
      return(false);
//--- Add a pointer to the element to the database
   CWndContainer::AddToElementsArray(0,button);
   return(true);
}

Assim, nós formamos uma lista de sinais simples com a interface de interação atualizada da figura 3. Uma lista de sinais compostos é adicionada por uma simples adição das variáveis m_c_signal_label[] e m_c_signal_ind[] à classe base e o código abaixo é destinado para o método CreateStepWindow().

CTextLabel        m_c_signal_label[COMPOSITE_SIGNALS];
CButton           m_c_signal_ind[COMPOSITE_SIGNALS];
//---
   for(int i=0; i<COMPOSITE_SIGNALS; i++)
   {
      if(!CreateLabel(m_c_signal_label[i],300,40*i+95,"Signal_"+string(i)))
         return(false);
      if(!CreateSignalSet(m_c_signal_ind[i],"Edit",C'255,195,50',150+290,40*i+90))
         return(false);
   }

A ferramenta para exibir e editar os sinais simples e compostos foi criada novamente. Agora, na Etapa 3 da configuração inicial do aplicativo, adicionamos um botão para criar os sinais compostos, junto com um cabeçalho de texto. Isso será feito criando um array estático m_add_signal[2] da variável m_add_signal, que é uma instância da classe CButton e ela é usada no botão AddSignal. Substituímos o seguinte código no corpo do método CreateStepWindow()

   if(!CreateIconButton(m_add_signal,m_lang[15],10,30))
      return(false);

com um novo método contendo o botão de criação de sinal composto:

   if(!CreateIconButton(m_add_signal[0],m_lang[15],10,30))
      return(false);
   if(!CreateIconButton(m_add_signal[1],m_lang[43],300,30))
      return(false);

Para exibir os cabeçalhos das listas de sinais simples e compostos, nós transformamos as variáveis m_signal_header no array estático m_signal_header[2]. O código anterior:

   if(!CreateLabel(m_signal_header,10,30+30+10,m_lang[16]))
      return(false);

deve ser substituído por um novo com dois cabeçalhos:

   if(!CreateLabel(m_signal_header[0],10,30+30+10,m_lang[16]))
      return(false);
   if(!CreateLabel(m_signal_header[1],300,30+30+10,m_lang[44]))
      return(false);

Depois de todas as mudanças acima, a interface atualizada na Etapa 3 é a seguinte:

Fig.4 Adição e atualização dos botões para a criação de sinais de negociação 

Agora, nós precisamos vincular os objetos criados anteriormente da lista de sinais de negociação simples com o evento de criação e edição. Aqui eu adiciono um breve aviso. Uma vez que o número de elementos de UI está crescendo, fazendo com que o número de possíveis interações do usuário também aumente, assim, o corpo do manipulador OnEvent() ficará muito longo e difícil de entender qual evento ou grupo de eventos o elemento pertence. É por isso que eu decidi envolver todas as principais interações com a UI em métodos apropriados. Isso oferece duas vantagens: a OnEvent() terá a lista dos principais eventos, de forma que a lógica e o código de cada um deles possam ser facilmente acessados.

Encontramos o fragmento de código responsável pela adição de um novo sinal de negociação simples, adicionamos o código necessário e movemos-o para o método AddSimpleSignal():

//+------------------------------------------------------------------+
//| Adds a new simple trading signal                                 |
//+------------------------------------------------------------------+
void CProgram::AddSimpleSignal(long lparam)
{
   if(lparam==m_new_signal.Id())
   {
      if(m_number_signal<0)
      {
         if(SaveSignalSet(m_total_signals))
         {
            m_set_window.CloseDialogBox();
            if(m_total_signals<SIMPLE_SIGNALS)
            {
               m_total_signals++;
               m_signal_label[m_total_signals-1].Show();
               m_signal_label[m_total_signals-1].LabelText(m_signal_name.GetValue());
               m_signal_label[m_total_signals-1].Update(true);
               m_signal_type[m_total_signals-1].Show();
               m_signal_ind[m_total_signals-1].Show();
            }
            else
               MessageBox("Maximum number of signals is "+string(SIMPLE_SIGNALS),"Signal Monitor");
            //---
            if(m_total_signals>1)
            {
               m_add_signal[1].IsLocked(false);
               m_add_signal[1].Update(true);
            }
         }
      }
      else
      {
         if(SaveSignalSet(m_number_signal,false))
         {
            m_set_window.CloseDialogBox();
            m_signal_label[m_number_signal].LabelText(m_signal_name.GetValue());
            m_signal_label[m_number_signal].Update(true);
         }
      }
   }
}

Chamamos ele para o manipulador OnEvent(). Assim, nós reduzimos o tamanho do corpo do método e estruturaremos sua parte de implementação. A Figura 3 mostra que a nova implementação deve suportar a possibilidade de definir um nome de sinal personalizado. Isso pode ser feito adicionando o seguinte campo a janela de criação e edição do sinal simples. Nós criamos um novo método chamado CreateSignalName(), que adicionará um campo de entrada para o nome do sinal.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateSignalName(CTextEdit &text_edit,const int x_gap,const int y_gap)
{
//--- Store the pointer to the main control
   text_edit.MainPointer(m_set_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(110);
   text_edit.GetTextBoxPointer().XSize(200);
   text_edit.GetTextBoxPointer().DefaultTextColor(clrSilver);
   text_edit.GetTextBoxPointer().DefaultText(m_lang[44]);
//--- Create a control element
   if(!text_edit.CreateTextEdit(m_lang[44],x_gap,y_gap))
      return(false);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(1,text_edit);
   return(true);
}

Além disso, não se esqueça de adicionar o campo de entrada para o nome do sinal simples à estrutura SIGNAL.

uchar             signal_name[50];

Os métodos SaveSignalSet() e LoadSignalSet() também devem ser atualizados, para que gravem o nome do sinal, além das configurações existentes. Ao criar um novo sinal, nós devemos evitar a situação em que um novo nome de sinal corresponda a um sinal criado anteriormente. Outra situação a ser evitada é quando os parâmetros são salvos no arquivo de um sinal criado anteriormente. Portanto, nós adicionamos o segundo argumento first_save do método SaveSignalSet(). Ele servirá como uma indicação se um sinal é salvo pela primeira vez ou se os parâmetros editados de um sinal criado anteriormente são salvos.

bool              SaveSignalSet(int index,bool first_save=true);

A implementação completa do método é a seguinte:

bool CProgram::SaveSignalSet(int index,bool first_save=true)
{
//---
   if(first_save && !CheckSignalNames(m_signal_name.GetValue()))
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Это имя уже используется","Монитор сигналов");
      else
         MessageBox("This name is already in use","Signal Monitor");
      return(false);
   }
//---
   int h=FileOpen("Signal Monitor\\signal_"+string(index)+".bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Не удалось создать файл конфигурации","Монитор сигналов");
      else
         MessageBox("Failed to create configuration file","Signal Monitor");
      return(false);
   }
   if(index>SIMPLE_SIGNALS-1)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Максимальное число сигналов не должно быть больше "+string(SIMPLE_SIGNALS),"Монитор сигналов");
      else
         MessageBox("Maximum number of signals is "+string(SIMPLE_SIGNALS),"Signal Monitor");
      return(false);
   }
//--- Save the selection
//--- Indicator name
   if(m_signal_name.GetValue()=="")
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Введите Имя Сигнала","Монитор сигналов");
      else
         MessageBox("Enter the Signal Name","Signal Monitor");
      FileClose(h);
      return(false);
   }
   else if(StringLen(m_signal_name.GetValue())<3)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Имя Сигнала должно быть не менее 3 букв","Монитор сигналов");
      else
         MessageBox("Signal Name must be at least 3 letters","Signal Monitor");
      FileClose(h);
      return(false);
   }
   else
      StringToCharArray(m_signal_name.GetValue(),m_signal_set[index].signal_name);
//--- Indicator type
   m_signal_set[index].ind_type=m_indicator_type.GetListViewPointer().SelectedItemIndex();
//--- Indicator period
   if(m_signal_set[index].ind_type!=9)
   {
      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();
   }
   else
   {
      string path=m_custom_path.GetValue();
      string param=m_custom_param.GetValue();
      if(path=="")
      {
         if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
            MessageBox("Введите путь к индикатору","Монитор сигналов");
         else
            MessageBox("Enter the indicator path","Signal Monitor");
         FileClose(h);
         return(false);
      }
      if(param=="")
      {
         if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
            MessageBox("Введите параметры индикатора через запятую","Монитор сигналов");
         else
            MessageBox("Enter indicator parameters separated by commas","Signal Monitor");
         FileClose(h);
         return(false);
      }
      StringToCharArray(path,m_signal_set[index].custom_path);
      StringToCharArray(param,m_signal_set[index].custom_val);
      m_signal_set[index].ind_period=(int)m_period_edit.GetValue();
   }
//--- Rule type
   m_signal_set[index].rule_int=m_rule_interval.GetListViewPointer().SelectedItemIndex();
//--- Comparison type
   m_signal_set[index].rule_type=m_rule_type.GetListViewPointer().SelectedItemIndex();
//--- Rule value
   m_signal_set[index].rule_value1=(double)m_rule_value[0].GetValue();
   m_signal_set[index].rule_value2=(double)m_rule_value[1].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();
//--- Backdrop 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;
//--- Hint 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)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Не выбран ни один таймфрейм","Монитор сигналов");
      else
         MessageBox("No timeframes selected","Signal Monitor");
      FileClose(h);
      return(false);
   }
//---
   FileWriteStruct(h,m_signal_set[index]);
   FileClose(h);
   Print("Configuration "+m_signal_name.GetValue()+" successfully saved");
//---
   return(true);
}

No código acima, eu destaquei a verificação do sinal de um novo sinal e o método CheckSignalNames() que verifica se um sinal com o nome especificado já existe. O código posterior verifica se há um nome de sinal e se o seu tamanho é de pelo menos três caracteres.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CheckSignalNames(string name)
{
//--- Search for set signals
   SIGNAL signal_set[];
   int cnt=0;
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(FileIsExist("Signal Monitor\\signal_"+string(i)+".bin"))
         cnt++;
   }
   if(cnt<1)
      return(true);
   //---
   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]);
      if(CharArrayToString(m_signal_set[i].signal_name)==name)
      {
         FileClose(h);
         return(false);
      }
      FileClose(h);
   }
   return(true);
}

Agora, nós podemos criar os sinais de negociação simples no novo formato, como é mostrado na imagem abaixo. Vamos ativar os botões de edição do sinal criados e ver como um sinal é atribuído a um sinal simples.

Fig.5 Novo formato dos sinais de negociação simples 

Para habilitar a edição dos sinais simples, criamos um novo método EditSimpleSignal() e chamamos ele no manipulador OnEvent()

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::EditSimpleSignal(long lparam)
{
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(lparam==m_signal_ind[i].Id())
      {
         LoadSignalSet(i);
         m_new_signal.LabelText(m_lang[38]);
         m_new_signal.Update(true);
         m_set_window.OpenWindow();
         m_number_signal=i;
      }
   }
}

Da mesma forma, para mudar a flag de uso, criamos um novo método SignalSwitch() e chamamos ele no manipulador.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::SignalSwitch(long lparam)
{
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      //---
      if(lparam==m_signal_type[i].Id())
      {
         if(m_signal_type[i].LabelText()=="S")
            SetButtonParam(m_signal_type[i],"C",clrMediumOrchid);
         else if(m_signal_type[i].LabelText()=="C")
            SetButtonParam(m_signal_type[i],"B",C'240,120,0');
         else if(m_signal_type[i].LabelText()=="B")
            SetButtonParam(m_signal_type[i],"S",C'75,190,240');
      }
   }
}

A opção é mostrada na figura 6 abaixo. Ele é executado com um simples clique de botão.

Fig.6 Mudança visual da flag de uso do sinal simples

Os sinais de negociação agora estão prontos para uso como elementos lógicos de sinais de negociação compostos.

Anteriormente, nós adicionamos um botão para criar um novo sinal composto - 'Add Composite Signal'. Agora nós precisamos implementar uma janela de diálogo que permite criar, configurar e editar os sinais compostos. Anteriormente, ao criar uma janela de edição de sinal simples, nós criamos um arquivo de inclusão SetWindow.mqh. Então agora nós criamos o CSetWindow.mqh, em que a UI do futuro editor de sinais compostos será criada. Abrimos o arquivo recém-criado e adicionamos o Program.mqh para acessar a classe base CProgram.

//+------------------------------------------------------------------+
//|                                                   CSetWindow.mqh |
//|                                Copyright 2020, Alexander Fedosov |
//|                           https://www.mql5.com/en/users/alex2356 |
//+------------------------------------------------------------------+
#include "Program.mqh"

No arquivo Program.mqh, incluímos o CSetWindow.mqh aos controles existentes:

//+------------------------------------------------------------------+
//| Add Controls                                                     |
//+------------------------------------------------------------------+
#include "MainWindow.mqh"
#include "SetWindow.mqh"
#include "StepWindow.mqh"
#include "CSetWindow.mqh"

Para criar uma janela de diálogo, nós introduzimos um novo método CreateCompositeEdit() na classe base e implementamos ele no arquivo de inclusão recém-criado.

protected:
   //--- Forms
   bool              CreateStepWindow(const string caption_text);
   bool              CreateSetWindow(const string caption_text);
   bool              CreateColorWindow(const string caption_text);
   bool              CreateFastEdit(const string caption_text);
   bool              CreateCompositeEdit(const string caption_text);
//+------------------------------------------------------------------+
//| Creates a window for creating and editing composite signals      |
//+------------------------------------------------------------------+
bool CProgram::CreateCompositeEdit(const string caption_text)
{
//--- Add a window pointer to the window array
   CWndContainer::AddWindow(m_composite_edit);
//--- Properties
   m_composite_edit.XSize(590);
   m_composite_edit.YSize(590);
//--- Coordinates
   int x=10;
   int y=10;
//---
   m_composite_edit.CaptionHeight(22);
   m_composite_edit.IsMovable(true);
   m_composite_edit.CaptionColor(m_caption);
   m_composite_edit.CaptionColorLocked(m_caption);
   m_composite_edit.CaptionColorHover(m_caption);
   m_composite_edit.BackColor(m_background);

   m_composite_edit.FontSize(m_base_font_size);
   m_composite_edit.Font(m_base_font);
   m_composite_edit.WindowType(W_DIALOG);
//--- Create the form
   if(!m_composite_edit.CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
   return(true);
}

Agora, nós adicionamos a chamada do método no método de criação da interface principal.

bool CProgram::CreateGUI(void)
{
//---
   ChangeLanguage();
//--- Step 1-3. Symbol selection window.
   if(!CreateStepWindow(m_lang[0]))
      return(false);
//---
   if(!CreateSetWindow(m_lang[17]))
      return(false);
//--- Creating form 2 for the color picker
   if(!CreateColorWindow(m_lang[39]))
      return(false);
//--- Creating a quick edit form
   if(!CreateFastEdit(m_lang[40]))
      return(false);
//---
   if(!CreateCompositeEdit(m_lang[46]))
      return(false);
//--- Complete GUI creation
   CWndEvents::CompletedGUI();
   return(true);
}

A janela criada é um diálogo e, portanto, ela deve abrir em um determinado evento, que é um clique no botão 'Add Composite Signal'. Nós criamos o método OpenCompositeSignalEditor() e adicionamos um comando ao seu corpo para fins de demonstração. O método deve ser chamado no manipulador de eventos.

void CProgram::OpenCompositeSignalEditor(long lparam)
{
   if(lparam==m_add_signal[1].Id())
   {
      m_composite_edit.OpenWindow();
   }
}

A Figura 7 mostra a caixa de diálogo adicionada que abre com um clique no botão.

Fig.7 Adicionando uma janela de diálogo para criar os sinais compostos

Agora, nós precisamos preencher a janela de diálogo com os elementos da UI mostrados na figura 2:

Todos os novos métodos serão adicionados à classe base CProgram e serão implementados no arquivo CSetWindow.mqh. O método será aplicado no mesmo arquivo, no corpo do método de criação da caixa de diálogo. Em primeiro lugar, nós criamos o método de campo de entrada CreateCSignalName(), implementamos e adicionamos ele à janela de diálogo.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateCSignalName(CTextEdit &text_edit,const int x_gap,const int y_gap)
{
//--- Store the pointer to the main control
   text_edit.MainPointer(m_composite_edit);
//--- 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(110);
   text_edit.GetTextBoxPointer().XSize(200);
   text_edit.GetTextBoxPointer().DefaultTextColor(clrSilver);
   text_edit.GetTextBoxPointer().DefaultText(m_lang[45]);
//--- Create a control element
   if(!text_edit.CreateTextEdit(m_lang[45],x_gap,y_gap))
      return(false);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(4,text_edit);
   return(true);
}
Usando o novo método CreateSimpleSignal(), nós criamos um conjunto de botões de dois estados. Eles serão usados para selecionar os sinais simples a serem adicionados aos slots (fig.2) para um sinal composto.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateSimpleSignal(CButton &button,const int x_gap,const int y_gap)
{
//---
   color baseclr=clrDodgerBlue;
   color pressed=clrCrimson;
//--- Store the window pointer
   button.MainPointer(m_composite_edit);
//--- Set properties before creation
   button.XSize(110);
   button.YSize(40);
   button.Font(m_base_font);
   button.FontSize(m_base_font_size);
   button.BackColor(baseclr);
   button.BackColorHover(baseclr);
   button.BackColorPressed(pressed);
   button.BackColorLocked(clrWhiteSmoke);
   button.BorderColor(baseclr);
   button.BorderColorHover(baseclr);
   button.BorderColorPressed(pressed);
   button.BorderColorLocked(clrWhiteSmoke);
   button.LabelColor(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.LabelColorHover(clrWhite);
   button.LabelColorLocked(clrWhiteSmoke);
   button.IsCenterText(true);
   button.TwoState(true);
//--- Create a control element
   if(!button.CreateButton(" — ",x_gap,y_gap))
      return(false);
//--- Add a pointer to the element to the database
   CWndContainer::AddToElementsArray(4,button);
   return(true);
}

O método CreateCompositeEdit() é o seguinte:

....
//--- Signal name
   if(!CreateCSignalName(m_c_signal_name,10,40))
      return(false);
//--- Create a simple signal selection list
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(i<5)
      {
         if(!CreateSimpleSignal(m_simple_signal[i],10+115*i,90))
            return(false);
      }
      else
      {
         if(!CreateSimpleSignal(m_simple_signal[i],10+115*(i-5),90+45))
            return(false);
      }
   }
...

Os botões têm dois estados: uma flag simples indicando que ele foi usado (vermelho) e outro não usado (azul). Mas este ainda é um modelo.

Fig. 8 Modelo para selecionar os sinais de negociação simples

A próxima etapa é aplicar a seguinte lógica ao modelo:

Para cumprir com todas essas condições, nós criamos um método de verificação especial LoadForCompositeSignal(), que exibirá apenas os sinais selecionados corretamente.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::LoadForCompositeSignal(void)
{
   int cnt=0;
//---
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      m_simple_signal[i].IsLocked(true);
      m_simple_signal[i].Update(true);
   }
//--- Check if there are at least two available signals with the required attributes
   for(int i=0; i<SIMPLE_SIGNALS; i++)
      if(m_signal_label[i].IsVisible() && (m_signal_type[i].LabelText()=="C" || m_signal_type[i].LabelText()=="B"))
         cnt++;
//---
   if(cnt<2)
      return(false);
   else
      cnt=0;
//---
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(m_signal_label[i].IsVisible() && (m_signal_type[i].LabelText()=="C" || m_signal_type[i].LabelText()=="B"))
      {
         m_simple_signal[cnt].IsLocked(false);
         cnt++;
      }
   }
//---
   cnt=0;
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(m_signal_label[i].IsVisible() && (m_signal_type[i].LabelText()=="C" || m_signal_type[i].LabelText()=="B"))
      {
         m_simple_signal[cnt].LabelText(m_signal_label[i].LabelText());
         m_simple_signal[cnt].Update(true);
         cnt++;
      }
   }
   return(true);
}

Ele deve ser aplicado no método OpenCompositeSignalEditor() criado anteriormente:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::OpenCompositeSignalEditor(long lparam)
{
   if(lparam==m_add_signal[1].Id())
   {
      if(!LoadForCompositeSignal())
      {
         if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
            MessageBox("Необходимо минимум два простых сигнала для создания составного!","Внимание");
         else
            MessageBox("You need at least two simple signals to create a composite!","Warning");
      }
      else
      {
         m_composite_edit.X(m_add_signal[1].X());
         m_composite_edit.Y(m_add_signal[1].Y()+40);
         m_composite_edit.OpenWindow();
         //---
         m_c_signal_name.SetValue("");
         m_c_signal_name.Update(true);
         //--- Clear signal selection
         for(int i=0; i<SIMPLE_SIGNALS; i++)
         {
            m_simple_signal[i].IsPressed(false);
            m_simple_signal[i].Update(true);
         }
      }
   }
}

Como resultado (fig. 9), nós temos uma seleção correta dos sinais que atendem às nossas condições. Como pode ser visto, o sinal simples denominado Test 2 não é selecionado para a criação de um sinal composto, pois possui o atributo S (Simple).

Fig. 9 Seleção de sinais simples para serem usados em um composto

Agora, nós precisamos adicionar o sistema que forma um sinal composto a partir dos sinais simples selecionados. Vamos começar com os elementos de UI. Haverá dois tipos de elementos: 

Os dois operadores semelhantes, OR e (OR), são realmente diferentes. O operador lógico OR combinado com AND pode ser interpretado de maneiras diferentes, quando conectam três expressões. A Figura 10 mostra um exemplo, onde o resultado de uma expressão lógica pode ser diferente dependendo dos colchetes.


Fig. 10 A diferença entre OR e (OR)

Vamos adicionar os slots e operadores lógicos à janela de criação e edição dos sinais compostos. Implementação do método CreateRuleSlot() para criar os slots:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateRuleSlot(CButton &button,string text,const int x_gap,const int y_gap)
{
//---
   //color baseclr=C'70,180,70';
   color baseclr=clrLightSteelBlue;
//--- Store the window pointer
   button.MainPointer(m_composite_edit);
//--- Set properties before creation
   button.XSize(110);
   button.YSize(40);
   button.Font(m_base_font);
   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(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.LabelColorHover(clrWhite);
   button.IsCenterText(true);
//--- Create a control element
   if(!button.CreateButton(text,x_gap,y_gap))
      return(false);
//--- Add a pointer to the element to the database
   CWndContainer::AddToElementsArray(4,button);
   return(true);
}

Para os operadores lógicos, nós criamos o método CreateRuleSelector():

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateRuleSelector(CButton &button,const int x_gap,const int y_gap)
{
//---
   color baseclr=C'75,190,240';
//--- Store the window pointer
   button.MainPointer(m_composite_edit);
//--- Set properties before creation
   button.XSize(40);
   button.YSize(40);
   button.Font(m_base_font);
   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(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.LabelColorHover(clrWhite);
   button.IsCenterText(true);
//--- Create a control element
   if(!button.CreateButton("AND",x_gap,y_gap))
      return(false);
//--- Add a pointer to the element to the database
   CWndContainer::AddToElementsArray(4,button);
   return(true);
}

Adicionamos esses dois métodos à implementação da janela de diálogo no método CreateCompositeEdit().

...
//--- Header Rule
   if(!CreateSetLabel1(m_c_set_header[0],10,90+45*2,"1."+m_lang[24]))
      return(false);
//--- Create slots for creating a composite signal
   for(int i=0; i<3; i++)
      if(!CreateRuleSlot(m_rule_element[i],"Slot "+string(i+1),57+10+173*i,int(90+45*2.5)))
         return(false);
//--- Create logical operators
   for(int i=0; i<2; i++)
      if(!CreateRuleSelector(m_rule_selector[i],189+173*i,int(90+45*2.5)))
         return(false);
...

Nesta etapa, a parte visual do bloco que cria a lógica do sinal composto se parece com isto:

Fig. 11 Parte visual da interface para criação da lógica do sinal composto

Para habilitar os comutadores lógicos, inserimos o método LogicSwitch(), implementamos e adicionamos ele ao manipulador de eventos. 

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::LogicSwitch(long lparam)
{
   for(int i=0; i<2; i++)
   {
      if(lparam==m_rule_selector[i].Id())
      {
         if(m_rule_selector[i].LabelText()=="OR")
            SetButtonParam(m_rule_selector[i],"(OR)",clrTomato);
         else if(m_rule_selector[i].LabelText()=="(OR)")
            SetButtonParam(m_rule_selector[i],"AND",C'75,190,240');
         else if(m_rule_selector[i].LabelText()=="AND")
            SetButtonParam(m_rule_selector[i],"OR",clrOrangeRed);
      }
   }
}

Agora, vamos passar para outro ponto importante relacionado ao preenchimento dos slots acima. Como preenchê-los na sequência necessária? Escolhemos um sinal simples da lista de sinais disponíveis e clicamos nele. A cor do primeiro slot mudará para azul e exibirá o nome do sinal adicionado. Um clique no segundo sinal preencherá o segundo slot. Se os slots estiverem preenchidos e você clicar no sinal já adicionado na lista, o slot ocupado por ele será liberado. Para implementar este mecanismo, nós criamos o método SignalSelection() e adicionamos ele ao manipulador de eventos:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::SignalSelection(long lparam)
{
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(lparam==m_simple_signal[i].Id())
      {
         //--- If the signal is selected
         if(m_simple_signal[i].IsPressed())
         {
            //--Find the first free slot
            for(int j=0; j<3; j++)
            {
               if(m_rule_element[j].BackColor()==clrLightSteelBlue)
               {
                  SetButtonParam(m_rule_element[j],m_simple_signal[i].LabelText(),clrDodgerBlue);
                  break;
               }
            }
         }
         //--- If no signal is selected
         else
         {
            for(int j=0; j<3; j++)
            {
               if(m_rule_element[j].LabelText()==m_simple_signal[i].LabelText())
               {
                  SetButtonParam(m_rule_element[j],"Slot "+string(j+1),clrLightSteelBlue);
                  break;
               }
            }
         }
      }
   }
}

As ações acima são mostradas visualmente na figura 12. 

Fig. 12 Um exemplo de criação de um sinal de negociação composto usando os sinais simples

Agora, nós precisamos adicionar as opções para configurar a exibição do sinal composto no monitor. As opções serão adicionadas à janela de criação do sinal composto. Nós criaremos uma seção de elementos semelhante à janela de criação do sinal simples. Os princípios de criação das configurações foram considerados no segundo artigo. A Fig. 13 abaixo mostra a aparência final da janela de edição do sinal composto.

Fig. 13 A versão final da janela de criação do sinal composto

Nós preparamos a parte visual. Agora nós precisamos integrar o componente de cálculo, para o qual usaremos o mecanismo de edição e armazenamento do sinal composto. Uma vez que os parâmetros necessários são definidos (como mostrado na Fig.13), um clique no botão Add salva os parâmetros especificados em um arquivo. A função que implementa a funcionalidade acima é adicionada abaixo. Ela é chamada de SaveCompositeSignalSet().

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::SaveCompositeSignalSet(int index,bool first_save=true)
{
//---
   if(first_save && !CheckCSignalNames(m_c_signal_name.GetValue()))
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Это имя уже используется","Монитор сигналов");
      else
         MessageBox("This name is already in use","Signal Monitor");
      return(false);
   }
//---
   int h=FileOpen("Signal Monitor\\c_signal_"+string(index)+".bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Не удалось создать файл конфигурации","Монитор сигналов");
      else
         MessageBox("Failed to create configuration file","Signal Monitor");
      return(false);
   }
   if(index>COMPOSITE_SIGNALS-1)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Максимальное число сигналов не должно быть больше "+string(COMPOSITE_SIGNALS),"Монитор сигналов");
      else
         MessageBox("Maximum number of signals is "+string(COMPOSITE_SIGNALS),"Signal Monitor");
      return(false);
   }
//--- Save the selection
//--- Indicator name
   if(m_c_signal_name.GetValue()=="")
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Введите Имя Сигнала","Монитор сигналов");
      else
         MessageBox("Enter the Signal Name","Signal Monitor");
      FileClose(h);
      return(false);
   }
   else if(StringLen(m_c_signal_name.GetValue())<3)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Имя Сигнала должно быть не менее 3 букв","Монитор сигналов");
      else
         MessageBox("Signal Name must be at least 3 letters","Signal Monitor");
      FileClose(h);
      return(false);
   }
   else
      StringToCharArray(m_c_signal_name.GetValue(),m_c_signal_set[index].signal_name);
//--- Slot values
   if(!CheckCorrectSlots(m_rule_element[0].LabelText(),m_rule_element[1].LabelText(),m_rule_element[2].LabelText()))
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Неверно установлено правило","Монитор сигналов");
      else
         MessageBox("Invalid rule","Signal Monitor");
      FileClose(h);
      return(false);
   }
   StringToCharArray(m_rule_element[0].LabelText(),m_c_signal_set[index].slot_1);
   StringToCharArray(m_rule_element[1].LabelText(),m_c_signal_set[index].slot_2);
   StringToCharArray(m_rule_element[2].LabelText(),m_c_signal_set[index].slot_3);
//--- Values of logical operators
   for(int i=0; i<2; i++)
   {
      if(m_rule_selector[i].LabelText()=="AND")
         m_c_signal_set[index].logics[i]=1;
      else if(m_rule_selector[i].LabelText()=="OR")
         m_c_signal_set[index].logics[i]=2;
      else if(m_rule_selector[i].LabelText()=="(OR)")
         m_c_signal_set[index].logics[i]=3;
   }
//--- Text label value
   StringToCharArray(StringSubstr(m_c_text_box.GetValue(),0,3),m_c_signal_set[index].label_value);
//--- Color of the text label
   m_c_signal_set[index].label_color=m_c_color_button[0].CurrentColor();
//--- Backdrop color
   if(m_c_set_param[0].IsPressed())
      m_c_signal_set[index].back_color=m_c_color_button[1].CurrentColor();
   else
      m_c_signal_set[index].back_color=clrNONE;
//--- Border color
   if(m_c_set_param[1].IsPressed())
      m_c_signal_set[index].border_color=m_c_color_button[2].CurrentColor();
   else
      m_c_signal_set[index].border_color=clrNONE;
//--- Hint value
   m_c_signal_set[index].tooltip=m_c_set_param[2].IsPressed();
   if(m_c_signal_set[index].tooltip)
      StringToCharArray(m_c_tooltip_text.GetValue(),m_c_signal_set[index].tooltip_text);
//--- Selected image
   m_c_signal_set[index].image=m_c_set_param[3].IsPressed();
   if(m_c_signal_set[index].image)
      m_c_signal_set[index].img_index=m_c_pictures_slider.GetRadioButtonsPointer().SelectedButtonIndex();
//---
   FileWriteStruct(h,m_c_signal_set[index]);
   FileClose(h);
   Print("Конфигурация "+m_c_signal_name.GetValue()+" успешно сохранена");
//---
   return(true);
}

Uma tentativa de compilar o projeto agora irá produzir três erros principais: a ausência da variável m_c_signal_set e de dois métodos de verificação — CheckCSignalNames() e CheckCorrectSlots(). O tipo de variável é a nova estrutura C_SIGNAL criada para armazenar um conjunto de parâmetros do sinal composto:

struct C_SIGNAL
{
   uchar             slot_1[50];
   uchar             slot_2[50];
   uchar             slot_3[50];
   int               logics[2];
   uchar             signal_name[50];
   uchar             label_value[10];
   color             label_color;
   color             back_color;
   color             border_color;
   bool              tooltip;
   uchar             tooltip_text[100];
   bool              image;
   int               img_index;
};

O método CheckCSignalNames(), muito semelhante ao CheckSignalNames(): ele verifica se o nome do sinal composto especificado é único. O novo método CheckCorrectSlots() verifica a integridade e exatidão da construção lógica criada do sinal composto:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CheckCorrectSlots(string name1,string name2,string name3)
{
   bool slot1=(name1=="Slot 1")?true:false;
   bool slot2=(name2=="Slot 2")?true:false;
   bool slot3=(name3=="Slot 3")?true:false;
   int cnt=0;
   //---
   if(slot1)
      return(false);
   if(slot2 && !slot3)
      return(false);
   //---
   if(!slot1)
      cnt++;
   if(!slot2)
      cnt++;
   if(!slot3)
      cnt++;
   if(cnt<2)
      return(false);
   return(true);
}

A funcionalidade necessária para criar um novo sinal composto está pronta, então agora vamos desenvolver o método AddCompositeSignal():

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::AddCompositeSignal(long lparam)
{
   if(lparam==m_c_new_signal.Id())
   {
      if(m_c_number_signal<0)
      {
         if(SaveCompositeSignalSet(m_c_total_signals))
         {
            m_composite_edit.CloseDialogBox();
            if(m_c_total_signals<COMPOSITE_SIGNALS)
            {
               m_c_total_signals++;
               m_c_signal_label[m_c_total_signals-1].Show();
               m_c_signal_label[m_c_total_signals-1].LabelText(m_c_signal_name.GetValue());
               m_c_signal_label[m_c_total_signals-1].Update(true);
               m_c_signal_ind[m_c_total_signals-1].Show();
            }
            else
               MessageBox("Maximum number of composite signals is "+string(COMPOSITE_SIGNALS),"Signal Monitor");
         }
      }
      else
      {
         if(SaveCompositeSignalSet(m_c_number_signal))
         {
            m_composite_edit.CloseDialogBox();
            m_c_signal_label[m_c_number_signal].LabelText(m_c_signal_name.GetValue());
            m_c_signal_label[m_c_number_signal].Update(true);
         }
      }
   }
}

Antes de criar a funcionalidade para editar o sinal de negociação criado, vamos adicionar um mecanismo para enviar as configurações para a janela de edição do sinal composto a partir de um arquivo. Isso será feito pelo método LoadCompositeSignalSet():

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::LoadCompositeSignalSet(int index)
{
   int h=FileOpen("Signal Monitor\\c_signal_"+string(index)+".bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      MessageBox("Configuration not found","Signal Monitor");
      return(false);
   }
   ZeroMemory(m_c_signal_set[index]);
   FileReadStruct(h,m_c_signal_set[index]);
//--- Loading the indicator name
   m_c_signal_name.SetValue(CharArrayToString(m_c_signal_set[index].signal_name));
   m_c_signal_name.GetTextBoxPointer().Update(true);
//--- Slot values
   string slot_1=CharArrayToString(m_c_signal_set[index].slot_1);
   string slot_2=CharArrayToString(m_c_signal_set[index].slot_2);
   string slot_3=CharArrayToString(m_c_signal_set[index].slot_3);
   color back=clrDodgerBlue;
   if(slot_1=="Slot 1")
      back=clrLightSteelBlue;
   else
      back=clrDodgerBlue;
   SetButtonParam(m_rule_element[0],slot_1,back);
   if(slot_2=="Slot 2")
      back=clrLightSteelBlue;
   else
      back=clrDodgerBlue;
   SetButtonParam(m_rule_element[1],slot_2,back);
   if(slot_3=="Slot 3")
      back=clrLightSteelBlue;
   else
      back=clrDodgerBlue;
   SetButtonParam(m_rule_element[2],slot_3,back);
//--- Values of logical operators
   for(int i=0; i<2; i++)
   {
      switch(m_c_signal_set[index].logics[i])
      {
      case  1:
         SetButtonParam(m_rule_selector[i],"AND",C'75,190,240');
         break;
      case  2:
         SetButtonParam(m_rule_selector[i],"(OR)",clrTomato);
         break;
      case  3:
         SetButtonParam(m_rule_selector[i],"OR",clrOrangeRed);
         break;
      default:
         break;
      }
   }
//--- Loading a text label
   m_c_text_box.ClearTextBox();
   m_c_text_box.AddText(0,CharArrayToString(m_c_signal_set[index].label_value));
   m_c_text_box.Update(true);
//--- Loading the color of the text label
   m_c_color_button[0].CurrentColor(m_c_signal_set[index].label_color);
   m_c_color_button[0].Update(true);
//--- Loading the background color
   if(m_c_signal_set[index].back_color==clrNONE)
   {
      m_c_set_param[0].IsPressed(false);
      m_c_set_param[0].Update(true);
      m_c_color_button[1].IsLocked(true);
      m_c_color_button[1].GetButtonPointer().Update(true);
   }
   else
   {
      m_c_set_param[0].IsPressed(true);
      m_c_set_param[0].Update(true);
      m_c_color_button[1].IsLocked(false);
      m_c_color_button[1].CurrentColor(m_c_signal_set[index].back_color);
      m_c_color_button[1].GetButtonPointer().Update(true);
   }
//--- Loading the border color
   if(m_c_signal_set[index].border_color==clrNONE)
   {
      m_c_set_param[1].IsPressed(false);
      m_c_set_param[1].Update(true);
      m_c_color_button[2].IsLocked(true);
      m_c_color_button[2].GetButtonPointer().Update(true);
   }
   else
   {
      m_c_set_param[1].IsPressed(true);
      m_c_set_param[1].Update(true);
      m_c_color_button[2].IsLocked(false);
      m_c_color_button[2].CurrentColor(m_c_signal_set[index].border_color);
      m_c_color_button[2].GetButtonPointer().Update(true);
   }
//--- Loading the tooltip value
   if(!m_c_signal_set[index].tooltip)
   {
      m_c_set_param[2].IsPressed(false);
      m_c_set_param[2].Update(true);
      m_c_tooltip_text.IsLocked(true);
      m_c_tooltip_text.Update(true);
   }
   else
   {
      m_set_param[2].IsPressed(true);
      m_set_param[2].Update(true);
      m_c_tooltip_text.IsLocked(false);
      m_c_tooltip_text.ClearTextBox();
      m_c_tooltip_text.AddText(0,CharArrayToString(m_c_signal_set[index].tooltip_text));
      m_c_tooltip_text.Update(true);
   }
//--- Loading the image
   if(!m_c_signal_set[index].image)
   {
      m_c_set_param[3].IsPressed(false);
      m_c_set_param[3].Update(true);
      m_c_pictures_slider.IsLocked(true);
      m_c_pictures_slider.GetRadioButtonsPointer().Update(true);
   }
   else
   {
      m_c_set_param[3].IsPressed(true);
      m_c_set_param[3].Update(true);
      m_c_pictures_slider.IsLocked(false);
      m_c_pictures_slider.GetRadioButtonsPointer().SelectButton(m_c_signal_set[index].img_index);
      m_c_pictures_slider.GetRadioButtonsPointer().Update(true);
   }
//---
   FileClose(h);
   return(true);
}

Agora nós podemos adicionar a possibilidade de editar o sinal criado usando o novo método EditCompositeSignal():

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::EditCompositeSignal(long lparam)
{
   for(int i=0; i<COMPOSITE_SIGNALS; i++)
   {
      if(lparam==m_c_signal_ind[i].Id())
      {
         LoadCompositeSignalSet(i);
         m_c_new_signal.LabelText(m_lang[38]);
         m_c_new_signal.Update(true);
         m_composite_edit.OpenWindow();
         m_c_number_signal=i;
         for(int j=0; j<SIMPLE_SIGNALS; j++)
            m_simple_signal[j].IsLocked(true);
      }
   }
}

A implementação de software da funcionalidade de criação, armazenamento, edição e carregamento do sinal composto está pronta. Resta a última etapa de desenvolvimento do aplicativo: adicionar os sinais compostos criados ao monitor. Em versões anteriores, isso era realizado pelo método SearchSignals(), devemos realizar uma atualização. O código do método foi projetado para garantir o máximo de facilidade de compreensão e clareza — isso foi implementado pela introdução dos métodos auxiliares adicionais. No entanto, ele será dividido em dois blocos lógicos: busca por sinais simples e busca por sinais compostos. Consideremos a primeira parte que se dedica à busca dos sinais simples. Preste atenção às mudanças no código.

//+------------------------------------------------------------------+
//| Signal search                                                    |
//+------------------------------------------------------------------+
bool CProgram::SearchSignals(void)
{
//--- Search for the number of created simple signals
   int cnt1=0;
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(FileIsExist("Signal Monitor\\signal_"+string(i)+".bin"))
         cnt1++;
   }
//--- Search for simple signals
   SIGNAL signal_set;
   ZeroMemory(signal_set);
   for(int i=0; i<cnt1; i++)
   {
      //--- Skip a signal if it is only set for use in composite signals
      if(m_signal_type[i].LabelText()=="C")
         continue;
      //---
      if(GetSimpleSignal(signal_set,i))
         return(false);
      //---
      for(int j=0; j<ArraySize(m_signal_button); j++)
      {
         //---
         string sy=GetSymbol(j);
         ENUM_TIMEFRAMES tf=GetTimeframe(j);
         //---
         if(!CheckTimeframe(tf,signal_set))
            continue;
         //---
         if(GetSignal(sy,tf,signal_set))
            SetVisualSignal(signal_set,j);
         else
            SetDefaultVisual(j);
      }
   }
.....

Nós introduzimos anteriormente os atributos de uso de sinal simples, portanto, nós precisamos adicionar a filtragem dos sinais simples para pular aqueles que devem ser usados apenas como parte de um sinal composto. O sinal GetSimpleSignal() foi adicionado para obter as configurações para cada sinal simples do arquivo especificado e para gravá-los na estrutura. Mais dois métodos novos, SetVisualSignal() e SetDefaultVisual() implementam a exibição da configuração visual especificada de acordo com o sinal encontrado atual. Se não houver sinal, o bloco de sinal padrão será exibido. Se nós compararmos a nova implementação de busca do sinal simples com a antiga, a nova versão se tornou mais fácil de entender e ficou quase três vezes mais curta.

Agora, vamos passar para a segunda parte do método de busca de sinal, que procura os sinais de negociação compostos.

.....
//--- Search for composite signals
   C_SIGNAL c_signal_set;
   ZeroMemory(c_signal_set);
   int cnt2=0;
   int signal_number[3];
   ArrayInitialize(signal_number,-1);
//--- Search for the number of created composite signals
   for(int i=0; i<COMPOSITE_SIGNALS; i++)
   {
      if(FileIsExist("Signal Monitor\\c_signal_"+string(i)+".bin"))
         cnt2++;
   }
//-- Exit if there are no signals
   if(cnt2<1)
      return(true);
//--- Search for configurations with composite signals
   for(int i=0; i<cnt2; i++)
   {
      //---
      int h=FileOpen("Signal Monitor\\c_signal_"+string(i)+".bin",FILE_READ|FILE_BIN);
      if(h==INVALID_HANDLE)
      {
         MessageBox("Configuration not found","Signal Monitor");
         return(false);
      }
      ZeroMemory(c_signal_set);
      FileReadStruct(h,c_signal_set);
      FileClose(h);
      //--- Search for simple signals used in the composite one (C or B)
      for(int m=0; m<cnt1; m++)
      {
         if(m_signal_type[m].LabelText()!="S")
            GetSimpleSignal(signal_set,m);
         //---
         if(CharArrayToString(signal_set.signal_name)==CharArrayToString(c_signal_set.slot_1))
            signal_number[0]=m;
         else if(CharArrayToString(signal_set.signal_name)==CharArrayToString(c_signal_set.slot_2))
            signal_number[1]=m;
         else if(CharArrayToString(signal_set.signal_name)==CharArrayToString(c_signal_set.slot_3))
            signal_number[2]=m;
      }
      ArrayPrint(signal_number);
   }
//---
   int used_slots=GetUsedSlots(CharArrayToString(c_signal_set.slot_1),CharArrayToString(c_signal_set.slot_2),CharArrayToString(c_signal_set.slot_3));
//---
   for(int j=0; j<ArraySize(m_signal_button); j++)
   {
      //---
      string sy=GetSymbol(j);
      ENUM_TIMEFRAMES tf=GetTimeframe(j);
      //---
      GetSimpleSignal(signal_set,signal_number[0]);
      bool sig1=GetSignal(sy,tf,signal_set);
      GetSimpleSignal(signal_set,signal_number[1]);
      bool sig2=GetSignal(sy,tf,signal_set);
      if(used_slots==2)
      {
         //--- AND
         if(c_signal_set.logics[0]==1)
            if(sig1 && sig2)
               SetVisualCompositeSignal(c_signal_set,j);
         //--- OR
         if(c_signal_set.logics[0]>1)
            if(sig1 || sig2)
               SetVisualCompositeSignal(c_signal_set,j);
      }
      else if(used_slots==3)
      {
         GetSimpleSignal(signal_set,signal_number[2]);
         bool sig3=GetSignal(sy,tf,signal_set);
         //--- AND OR
         if(c_signal_set.logics[0]==1 && c_signal_set.logics[1]==2)
         {
            if((sig1 && sig2) || sig3)
               SetVisualCompositeSignal(c_signal_set,j);
         }
         //--- AND (OR)
         else if(c_signal_set.logics[0]==1 && c_signal_set.logics[1]==3)
         {
            if(sig1 && (sig2 || sig3))
               SetVisualCompositeSignal(c_signal_set,j);
         }
         //--- OR AND
         else if(c_signal_set.logics[0]==2 && c_signal_set.logics[1]==1)
         {
            if(sig1 || (sig2 && sig3))
               SetVisualCompositeSignal(c_signal_set,j);
         }
         //--- (OR) AND
         else if(c_signal_set.logics[0]==3 && c_signal_set.logics[1]==1)
         {
            if((sig1 || sig2) && sig3)
               SetVisualCompositeSignal(c_signal_set,j);
         }
         //--- AND AND
         else if(c_signal_set.logics[0]==1 && c_signal_set.logics[1]==1)
         {
            if(sig1 && sig2 && sig3)
               SetVisualCompositeSignal(c_signal_set,j);
         }
         //--- OR OR
         else if(c_signal_set.logics[0]>1 && c_signal_set.logics[1]>1)
         {
            if(sig1 || sig2 || sig3)
               SetVisualCompositeSignal(c_signal_set,j);
         }
      }
   }
   return(true);
}

A busca por sinais compostos também exigia métodos adicionais tanto para as verificações quanto para a visualização no monitor. O método de verificação é o GetUsedSlots(). Ele determina o tipo de sinal composto usado no sistema de busca: se consiste em dois ou três sinais simples.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CProgram::GetUsedSlots(string name1,string name2,string name3)
{
   int cnt=0;
   if(name1!="Slot 1")
      cnt++;
   if(name2!="Slot 2")
      cnt++;
   if(name3!="Slot 3")
      cnt++;
   return(cnt);
}

O segundo método SetVisualCompositeSignal() exibe o sinal composto encontrado no bloco de sinal como um conjunto de parâmetros visuais do sinal composto atual.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::SetVisualCompositeSignal(C_SIGNAL &signal_set,int block)
{
   //---
   SetLabel(block,CharArrayToString(signal_set.label_value),signal_set.label_color);
   //---
   if(signal_set.back_color!=clrNONE)
      SetBackground(block,signal_set.back_color);
   //---
   if(signal_set.border_color!=clrNONE)
      SetBorderColor(block,signal_set.border_color);
   else
      SetBorderColor(block,signal_set.back_color);
   //---
   if(signal_set.tooltip)
      SetTooltip(block,CharArrayToString(signal_set.tooltip_text));
   //---
   if(signal_set.image)
      SetIcon(block,signal_set.img_index);
   else
      SetIcon(block,-1);
}

O Monitor de Sinais agora está completamente pronto. Ele pode ser alterado ou refinado no futuro para implementar sugestões de usuários ou para adicionar recursos extras.

Artigos anteriores desta série:

Conclusão

O arquivo anexado contém todos os arquivos listados, localizados nas pastas apropriadas. Para sua operação adequada, você só precisa salvar a pasta MQL5 na pasta 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 abaixo na Fig. 14.


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