Monitoramento de sinais de negociação multimoeda (Parte 4): Aprimoramento das funcionalidades e melhorias no sistema de busca de sinais

21 julho 2020, 10:30
Alexander Fedosov
0
1 839

Conteúdo

Introdução

Na terceira parte, nós criamos um sistema básico de busca de sinais, que, no entanto, foi baseado em um pequeno conjunto de indicadores e em um conjunto simples de regras de busca. Além disso, eu recebi sugestões de melhorias de usabilidade que poderiam ser feitas na parte visual do monitor de negociação. É isso que nós vamos implementar nesta parte.

Indicador personalizado para gerar um sinal de negociação

Uma adição lógica à criação e edição dos sinais de negociação é a expansão do conjunto de indicadores disponíveis. Anteriormente, nós só podíamos trabalhar com o conjunto de indicadores padrão da MetaTrader 5. Agora, nós podemos adicionar a possibilidade de usar a parte de cálculo dos indicadores personalizados. Vamos usar o projeto a partir da parte do artigo anterior como base. Ele pode ser baixado do anexo do artigo. Nesta parte, nós teremos que alterar os algoritmos operacionais dos métodos da classe base que nós consideramos na Parte 3. Todas as emendas e adições serão fornecidas com suas explicações correspondentes.

Vamos começar com a possibilidade de selecionar um indicador personalizado na janela de adição e edição do sinal. Essa implementação da janela é fornecida no arquivo SetWindow.mqh do nosso projeto. Ao abrir este arquivo, nós encontramos o método CreateIndicatorType(). As alterações devem ser implementadas exatamente neste arquivo.

//+------------------------------------------------------------------+
//| Creates a drop-down menu with indicator types                    |
//+------------------------------------------------------------------+
bool CProgram::CreateIndicatorType(const int x_gap,const int y_gap)
{
//--- Pass the object to the panel
   m_indicator_type.MainPointer(m_set_window);
//---
#define SIZE 10
//--- Array of the item values in the list view
   string pattern_names[SIZE]=
   {
      "ATR","CCI","DeMarker","Force Ind","WPR","RSI","Momentum","ADX","ADX Wilder","Custom"
   };
//--- Set up properties before creation
   m_indicator_type.XSize(200);
   m_indicator_type.YSize(26);
   m_indicator_type.LabelYGap(4);
   m_indicator_type.ItemsTotal(SIZE);
   m_indicator_type.Font(m_base_font);
   m_indicator_type.FontSize(m_base_font_size);
   m_indicator_type.BackColor(m_background);
   m_indicator_type.GetButtonPointer().Font(m_base_font);
   m_indicator_type.GetButtonPointer().FontSize(m_base_font_size);
   m_indicator_type.GetButtonPointer().BackColor(clrWhite);
   m_indicator_type.GetButtonPointer().XGap(100);
   m_indicator_type.GetButtonPointer().XSize(100);
   m_indicator_type.GetListViewPointer().Font(m_base_font);
   m_indicator_type.GetListViewPointer().FontSize(m_base_font_size);
   m_indicator_type.GetListViewPointer().ItemYSize(25);
   m_indicator_type.GetListViewPointer().YSize(200);
//--- Save the item values in the combobox list view
   for(int i=0; i<SIZE; i++)
      m_indicator_type.SetValue(i,pattern_names[i]);
//--- Get the list view pointer
   CListView *lv=m_indicator_type.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   m_indicator_type.SelectItem(1);
//--- Create the control
   if(!m_indicator_type.CreateComboBox("Indicator Type",x_gap,y_gap))
      return(false);
//--- Add the object to the common array of the object groups
   CWndContainer::AddToElementsArray(1,m_indicator_type);
   return(true);
}

Agora vamos considerar o que mudou em comparação com a versão anterior. Primeiro, nós adicionamos a substituição de macro SIZE, que significa o número de elementos na lista suspensa. Assim, nós podemos alterar o comprimento da lista de um único local, sem a necessidade de fazer substituições em todas as partes do código. Em seguida, foi adicionado um novo item da lista no final: Custom. As alterações são mostradas na Figura 1 abaixo.

Fig.1 Adicionando um item para selecionar um indicador personalizado.  

Agora, vamos adicionar novos elementos de interface para configurar e usar o indicador. Nós precisamos fazer as alterações de acordo com os argumentos da função iCustom(), para usar a parte calculada do seu próprio indicador. Isso inclui o nome do símbolo, o período, o caminho para o arquivo do indicador *.ex5 compilado e uma lista separada por vírgula dos parâmetros do indicador.

int  iCustom(
   string           symbol,     // symbol name
   ENUM_TIMEFRAMES  period,     // period
   string           name        // folder/custom indicator_name
   ...                          // the list of indicator input parameters
   );

O nome do símbolo e o período serão substituídos pelos valores selecionados nas duas primeiras etapas da configuração inicial do aplicativo. No entanto, os usuários terão que definir o caminho do indicador e a própria lista de parâmetros. Para esse fim, dois campos adicionais precisam ser adicionados. Adicionamos duas novas variáveis e um método à classe base CProgram:

   CTextEdit         m_custom_path;
   CTextEdit         m_custom_param;

   bool              CreateCustomEdit(CTextEdit &text_edit,const int x_gap,const int y_gap,const string default_text);

Como o método é aplicado na janela de criação/edição do sinal de negociação, implementamos ele no arquivo SetWindow.mqh:

//+------------------------------------------------------------------+
//| Input field for a custom indicator                               |
//+------------------------------------------------------------------+
bool CProgram::CreateCustomEdit(CTextEdit &text_edit,const int x_gap,const int y_gap,const string default_text)
{
//--- Save the pointer to the main control
   text_edit.MainPointer(m_set_window);
//--- Properties
   text_edit.XSize(100);
   text_edit.YSize(24);
   text_edit.Font(m_base_font);
   text_edit.FontSize(m_base_font_size);
   text_edit.GetTextBoxPointer().AutoSelectionMode(true);
   text_edit.GetTextBoxPointer().XGap(1);
   text_edit.GetTextBoxPointer().XSize(325);
   text_edit.GetTextBoxPointer().DefaultTextColor(clrSilver);
   text_edit.GetTextBoxPointer().DefaultText(default_text);
   text_edit.GetTextBoxPointer().BorderColor(clrBlack);
//--- Create the control
   if(!text_edit.CreateTextEdit("",x_gap,y_gap))
      return(false);
   text_edit.IsLocked(true);
//--- Add the object to the common array of the object groups
   CWndContainer::AddToElementsArray(1,text_edit);
   return(true);
}

Criamos dois campos de entrada usando o método CreateCustomEdit(). No mesmo arquivo, no corpo do método CreateSetWindow(), encontramos a seção Configurações do indicador selecionado e adicionamos o seguinte código a ele:

   if(!CreateCustomEdit(m_custom_path,240,22+10+2*(25+10),"Enter the indicator path"))
      return(false);
   if(!CreateCustomEdit(m_custom_param,240,22+10+3*(25+10),"Enter indicator parameters separated by commas"))
      return(false);

 Como resultado, dois campos de entrada aparecerão na janela de configurações, como é mostrado na figura 2.

Fig. 2 Adicionando os campos de entrada para as configurações personalizadas dos indicadores.

Eles estão inativos neste estágio de desenvolvimento. Isso ocorre porque sua disponibilidade está estritamente sujeita ao tipo de indicador selecionado, ou seja, ela estará disponível apenas se Custom estiver selecionado na lista suspensa. Para implementar esta tarefa, vamos revisar o método RebuildParameters(). Mas primeiro, vamos para a seção Evento de seleção de itens da lista suspensa no método da OnEvent() e adicionamos uma verificação do evento da lista desejada com a seleção do tipo do indicador.

//--- Selecting an item in the combobox drop-down list
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
   {
      //--- Indicator type
      if(lparam==m_indicator_type.Id())
         RebuildParameters(m_indicator_type.GetListViewPointer().SelectedItemIndex());
   }

Agora, mudamos o método RebuildParameters() para que, quando cada um dos indicadores disponíveis for selecionado, suas configurações relevantes sejam exibidas. Além disso, para o indicador personalizado isso ativaria o caminho e os parâmetros do campo de entrada.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::RebuildParameters(int index)
{
   switch(index)
   {
   case  0:
      m_period_edit.LabelText("ATR Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  1:
      m_period_edit.LabelText("CCI Period");
      m_applied_price.Show();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  2:
      m_period_edit.LabelText("DeMarker Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  3:
      m_period_edit.LabelText("Force Index Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  4:
      m_period_edit.LabelText("WPR Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  5:
      m_period_edit.LabelText("RSI Period");
      m_applied_price.Show();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  6:
      m_period_edit.LabelText("Momentum Period");
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  7:
      m_period_edit.LabelText("ADX Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  8:
      m_period_edit.LabelText("ADXW Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  9:
      m_period_edit.LabelText("Buffer Number");
      m_applied_price.Hide();
      m_custom_param.IsLocked(false);
      m_custom_path.IsLocked(false);
      break;
   default:
      m_period_edit.LabelText("Ind Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   }
   m_period_edit.Update(true);
}

Agora, a compilação do projeto deve produzir o seguinte resultado:

Fig. 3 Adicionando os campos de entrada para o indicador personalizado.

O próximo passo é suplementar o Evento de clique no botão de Adicionar o Sinal. Quando pressionado, definimos a seleção e as configurações do indicador para o padrão.

      //--- Add Signal button click event
      if(lparam==m_add_signal.Id())
      {
         if(m_total_signals>4)
         {
            MessageBox("Maximum number of signals is 5","Signal Monitor");
            return;
         }
         m_set_window.OpenWindow();
         RebuildParameters(1);
         m_number_signal=-1;
         RebuildTimeframes();
         m_new_signal.LabelText("Add");
         m_new_signal.Update(true);
         m_indicator_type.SelectItem(1);
         m_indicator_type.GetButtonPointer().Update(true);
      }

Antes de adaptar os novos controles (campos de entrada) para um algoritmo existente que salva um conjunto de configurações de sinal, nós vamos expandir o sistema de regras de busca de sinal de negociação. Isso causará a adição de novos elementos de interface. No entanto, não é prático alterar o algoritmo de armazenamento do conjunto de campos de entrada sempre que expandirmos o sistema de configurações. Uma solução mais lógica é adicionar todos os novos elementos de configuração e controle e, em seguida, alterar o método, salvando o conjunto de parâmetros para a busca do sinal.


Expansão do sistema de regras de busca de sinais de negociação

No momento, o monitor de negociação pode criar sinais com base na desigualdade. Significando uma condição maior que, menor que ou igual a um determinado número. No entanto, essa escolha nem sempre reflete com precisão os sinais desejados. Por exemplo, os indicadores do oscilador às vezes são mais apropriados para uso com uma faixa de valor específica. É isso que nós vamos implementar agora. Primeiro, é necessário adicionar uma alternância entre o sistema de configuração de regras anterior e o novo. Uma nova lista suspensa com dois tipos de configuração de regra deve ser adicionada: Compare e Interval

Vamos para a classe base CProgram e adicionamos uma nova variável, uma instância da classe CСombobox, e criamos o método que implementa o elemento da interface do usuário:

CComboBox         m_rule_interval;

bool              CreateRuleInterval(const int x_gap,const int y_gap);

A implementação do método deve ser adicionada ao arquivo SetWindow.mqh, porque esta lista suspensa pertence à janela de configurações.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateRuleInterval(const int x_gap,const int y_gap)
{
//--- Pass the object to the panel
   m_rule_interval.MainPointer(m_set_window);
//--- Array of the item values in the list view
   string pattern_names[2]=
   {
      "Compare","Interval",
   };
//--- Set up properties before creation
   m_rule_interval.XSize(160);
   m_rule_interval.YSize(26);
   m_rule_interval.LabelYGap(4);
   m_rule_interval.ItemsTotal(2);
   m_rule_interval.Font(m_base_font);
   m_rule_interval.FontSize(m_base_font_size);
   m_rule_interval.BackColor(m_background);
   m_rule_interval.GetButtonPointer().Font(m_base_font);
   m_rule_interval.GetButtonPointer().FontSize(m_base_font_size);
   m_rule_interval.GetButtonPointer().BackColor(clrWhite);
   m_rule_interval.GetButtonPointer().XGap(90);
   m_rule_interval.GetButtonPointer().XSize(80);
   m_rule_interval.GetListViewPointer().Font(m_base_font);
   m_rule_interval.GetListViewPointer().FontSize(m_base_font_size);
   m_rule_interval.GetListViewPointer().ItemYSize(26);
//--- Save the item values in the combobox list view
   for(int i=0; i<2; i++)
      m_rule_interval.SetValue(i,pattern_names[i]);
//--- Get the list view pointer
   CListView *lv=m_rule_interval.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   m_rule_interval.SelectItem(0);
//--- Create the control
   if(!m_rule_interval.CreateComboBox("Rule",x_gap,y_gap))
      return(false);
//--- Add the object to the common array of the object groups
   CWndContainer::AddToElementsArray(1,m_rule_interval);
   return(true);
}

A nova regra de intervalo deve ter um limite inferior e superior, portanto, nós adicionamos um campo extra para inserir um valor numérico. O valor anterior será usado para o valor limite superior, o novo campo será usado para o valor inferior. Também é necessário prever a possibilidade de especificar os valores negativos para os indicadores como, por exemplo, o WPR. Nesse caso, os limites superior e inferior serão alterados. Para evitar a necessidade de criar um método separado para a implementação de um campo de entrada por um período inferior, basta modificar a variável atual responsável pelo campo de entrada existente e o método CreateRule(). A variável se tornará uma matriz:

CTextEdit         m_rule_value[2];

No método, nós adicionamos um novo argumento recebendo uma referência à instância de classe CTextEdit.

bool              CreateRuleValue(CTextEdit &text_edit,const int x_gap,const int y_gap);

Alteramos a implementação do método abaixo.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateRuleValue(CTextEdit &text_edit,const int x_gap,const int y_gap)
{
//--- Save the pointer to the main control
   text_edit.MainPointer(m_set_window);
//--- Properties
   text_edit.XSize(80);
   text_edit.YSize(24);
   text_edit.GetTextBoxPointer().XGap(1);
   text_edit.LabelColor(C'0,100,255');
   text_edit.Font(m_base_font);
   text_edit.FontSize(m_base_font_size);
   text_edit.MaxValue(999);
   text_edit.StepValue(0.1);
   text_edit.MinValue(-999);
   text_edit.SetDigits(3);
   text_edit.SpinEditMode(true); 
//--- Create the control
   if(!text_edit.CreateTextEdit("",x_gap,y_gap))
      return(false);
   text_edit.SetValue(string(5));
   text_edit.GetTextBoxPointer().AutoSelectionMode(true);
//--- Add the object to the common array of the object groups
   CWndContainer::AddToElementsArray(1,text_edit);
   return(true);
}

Além disso, alteramos alguns dos valores do método CreateRule() já existente:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateRule(const int x_gap,const int y_gap)
{
//--- Pass the object to the panel
   m_rule_type.MainPointer(m_set_window);
//--- Array of the item values in the list view
   string pattern_names[5]=
   {
      ">",">=","==","<","<="
   };
//--- Set up properties before creation
   m_rule_type.XSize(80);
   m_rule_type.YSize(26);
   m_rule_type.LabelYGap(4);
   m_rule_type.ItemsTotal(5);
   m_rule_type.Font(m_base_font);
   m_rule_type.FontSize(m_base_font_size);
   m_rule_type.BackColor(m_background);
   m_rule_type.GetButtonPointer().Font(m_base_font);
   m_rule_type.GetButtonPointer().FontSize(m_base_font_size);
   m_rule_type.GetButtonPointer().BackColor(clrWhite);
   m_rule_type.GetButtonPointer().XGap(1);
   m_rule_type.GetButtonPointer().XSize(80);
   m_rule_type.GetListViewPointer().Font(m_base_font);
   m_rule_type.GetListViewPointer().FontSize(m_base_font_size);
   m_rule_type.GetListViewPointer().ItemYSize(26);
//--- Save the item values in the combobox list view
   for(int i=0; i<5; i++)
      m_rule_type.SetValue(i,pattern_names[i]);
//--- Get the list view pointer
   CListView *lv=m_rule_type.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   m_rule_type.SelectItem(0);
//--- Create the control
   if(!m_rule_type.CreateComboBox("",x_gap,y_gap))
      return(false);
//--- Add the object to the common array of the object groups
   CWndContainer::AddToElementsArray(1,m_rule_type);
   return(true);
}

Agora, encontramos a seção Configurações de condição do método CreateSetWindow() e alteramos o código da seguinte maneira:

//--- Condition settings
   if(!CreateRuleValue(m_rule_value[0],200,22+10+5*(25+10)))
      return(false);
   if(!CreateRuleValue(m_rule_value[1],300,22+10+5*(25+10)))
      return(false);
   if(!CreateRule(200,22+10+5*(25+10)))
      return(false);
   if(!CreateRuleInterval(10,22+10+5*(25+10)))
      return(false);

Essa alteração permitirá reconfigurar a posição dos elementos de interface existentes e adicionar outros novos. O resultado deve ser como é mostrado na Fig. 4. No entanto, nada está funcionando no momento, se você tentar mudar o modo de regra de Compare para Interval. Vamos consertar isso.

Fig. 4 Adicionando a seleção do modo para as regras de busca de sinal.

Para fazer isso, abrimos o método OnEvent() e encontramos a seção responsável por um evento de seleção de item na lista suspensa e adicionamos um código que permite exibir os elementos de interface corretos, dependendo do modo selecionado.

//--- Selecting an item in the combobox drop-down list
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
   {
      ...
      //--- Rule type
      if(lparam==m_rule_interval.Id())
      {
         switch(m_rule_interval.GetListViewPointer().SelectedItemIndex())
         {
         case  0:
            m_rule_value[0].Hide();
            m_rule_type.Show();
            break;
         case  1:
            m_rule_value[0].Show();
            m_rule_type.Hide();
            break;
         default:
            break;
         }
      }
   }

Em seguida, vamos mover alguns dos eventos relacionados ao carregamento da interface para uma seção separada do método OnEvent(). Para fazer isso, criamos o evento Conclusão da criação da Interface e vamos para o código a partir do método CreateGUI(). O código a seguir será deixado na CreateGUI:

//+------------------------------------------------------------------+
//| Creates the graphical interface of the program                   |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//--- Loading the language
   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("Color Picker"))
      return(false);
//--- Finishing the creation of GUI
   CWndEvents::CompletedGUI();
   return(true);
}

A nova seção terá a seguinte aparência:

// --- GUI creation completion
   if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI)
   {
      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();
      m_rule_value[0].Hide();
   }

Preste atenção à nova ação no carregamento do aplicativo — ocultando o campo recém-criado para inserir o limite do intervalo inferior.

Após criar os novos elementos e os parâmetros da interface do usuário, nós podemos prosseguir com a modificação do algoritmo de carregamento e com o salvamento dos conjuntos de configurações do sinal de negociação. Vamos ao corpo do método SaveSignalSet() e ajustemos ele com as alterações mais recentes.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::SaveSignalSet(int index)
{
//---
   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>4)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Максимальное число сигналов не должно быть больше 5","Монитор сигналов");
      else
         MessageBox("Maximum number of signals is 5","Signal Monitor");
      return(false);
   }
//--- Save the selection
//--- 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();
//--- 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)
   {
      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 signal_"+string(index)+" has been successfully saved");
//---
   return(true);
}

O código acima contém muitas alterações. Vamos considerar em detalhes as principais mudanças. A primeira é verificar se um indicador padrão ou personalizado está selecionado. Quando um indicador personalizado é selecionado, adicionamos o algoritmo de armazenamento do caminho do indicador e seus parâmetros, bem como um valor do campo de entrada do período - para um indicador personalizado, esse campo permite obter o número do buffer do indicador.

Nós mudamos o número de parâmetros armazenados. Portanto, nós precisamos alterar a estrutura SIGNAL através da qual tudo é salvo em um arquivo binário. Adicionamos novas variáveis a ele:

struct SIGNAL
{
   int               ind_type;
   int               ind_period;
   int               app_price;
   int               rule_int;
   int               rule_type;
   double            rule_value1;
   double            rule_value2;
   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];
   uchar             custom_path[100];
   uchar             custom_val[100];
};

O campo de entrada do valor limiar foi alterado de rule_value para Comparation e alterado de rule_value 1 e rule_value 2 para os limites upper e lower no modo Interval. As variáveis custom_path e custom_val foram incluídas para armazenar os dados sobre o caminho do indicador customizado e seus parâmetros. Além disso, alteramos o método que carrega o conjunto de parâmetros do sinal de negociação de um arquivo: LoadSignalSet():

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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);
   if(m_signal_set[index].ind_type!=9)
   {
      //--- 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);
      }
   }
   else
   {
      m_period_edit.SetValue((string)m_signal_set[index].ind_period);
      m_custom_path.SetValue(CharArrayToString(m_signal_set[index].custom_path));
      m_custom_param.SetValue(CharArrayToString(m_signal_set[index].custom_val));
      m_custom_path.GetTextBoxPointer().Update(true);
      m_custom_param.GetTextBoxPointer().Update(true);
   }
//--- Loading signal rule
   m_rule_interval.SelectItem(m_signal_set[index].rule_int);
   m_rule_interval.GetButtonPointer().Update(true);
   m_rule_type.SelectItem(m_signal_set[index].rule_type);
   m_rule_type.GetButtonPointer().Update(true);
   m_rule_value[0].SetValue((string)m_signal_set[index].rule_value1);
   m_rule_value[0].GetTextBoxPointer().Update(true);
   m_rule_value[1].SetValue((string)m_signal_set[index].rule_value2);
   m_rule_value[1].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);
}

Uma verificação é implementada se um indicador padrão de um arquivo foi selecionado ou se um indicador personalizado é utilizado. Consequentemente, os dados necessários são carregados na interface da janela de configurações para posterior edição.

O armazenamento e o carregamento de um conjunto de parâmetros do sinal de negociação estão prontos. Agora, refinamos o algoritmo de busca do sinal. Abrimos o método GetSignal() e encontramos a seção Verificação das condições. Substituímos o seguinte:

//--- Check the condition
   int s=0;
   if(signal_set.rule_int==0)
   {
      double r_value=signal_set.rule_value2;
      double c_value=val[0];
      m_ind_value=c_value;
      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;
      }
   }
   else if(signal_set.rule_int==1)
   {
      double r_value_min=signal_set.rule_value1;
      double r_value_max=signal_set.rule_value2;
      double c_value=val[0];
      m_ind_value=c_value;
      if(c_value>=r_value_min && c_value<=r_value_max)
         s=1;
   }

Além disso, incluímos os indicadores adicionados à seção Obtendo o manipulador do indicador selecionado:

//--- Get the handle of the selected indicator
   string str[],name;
   double arr[];
   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;
   case  7:
      h=iADX(sy,tf,signal_set.ind_period);
      break;
   case  8:
      h=iADXWilder(sy,tf,signal_set.ind_period);
      break;
   case  9:
      StringSplit(m_custom_param.GetValue(),StringGetCharacter(",",0),str);
      ArrayResize(arr,ArraySize(str));
      for(int i=0; i<ArraySize(str); i++)
         arr[i]=StringToDouble(str[i]);
      name=m_custom_path.GetValue();
      h=GetCustomValue(tf,name,arr);
      break;
   default:
      break;
   }

Este bloco agora inclui a verificação do modo de busca, Comparison ou Interval. A verificação das condições é aplicada em conformidade.


Conversão da lista de símbolos em uma forma de tabela

Se não houver muitos símbolos em uma conta de negociação, a possibilidade de selecioná-los implementados como um nome e uma caixa de seleção ao lado pode ser suficiente. Porém, ao trabalhar com as centenas de símbolos, a altura da janela do aplicativo aumenta bastante (conforme ela é dimensionada de acordo com o número de linhas com os símbolos). É por isso que essa visão foi substituída por uma forma tabular. Além disso, se houver muitos símbolos, alguns deles serão ocultados e uma barra de rolagem será adicionada à direita. No entanto, nós ainda precisamos de caixas de seleção para selecionar os períodos de tempo de funcionamento. Portanto, nós precisamos resolver vários problemas:

  • Criamos uma tabela para exibir os mesmos nomes do símbolo nas caixas de seleção.
  • Simplificamos as caixas de seleção existentes para selecionar apenas os períodos de funcionamento.
  • Adaptamos todas as alterações da interface do usuário para se ajustarem ao algoritmo existente, selecionando os símbolos e períodos de funcionamento para a busca adicional dos sinais de negociação.

Em primeiro lugar, vamos remover a exibição das caixas de seleção antigas. Para fazer isso, ocultamos elas no evento Conclusão de criação da GUI. Agora nós podemos saber que o número de caixas de verificação é constante: 21, que é igual ao número total de períodos possíveis na plataforma. Portanto, transformamos o array dinâmico m_checkbox[] em um array estático de tamanho 21.

      //--- Hide timeframe checkboxes
      for(int i=0; i<21; i++)
         m_checkbox[i].Hide();

Além disso, o método de criação da caixa de seleção precisa ser ajustado devido à sua finalidade, que é bem clara. Vamos para o corpo do método CreateStepWindow() e substituímos a seção de caixas de seleção da seguinte maneira:

//--- Checkboxes
   int k=0;
   string timeframe_names[21]=
   {
      "M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30",
      "H1","H2","H3","H4","H6","H8","H12","D1","W1","MN"
   };
   for(int j=0; j<=3; j++)
   {
      for(int i=0; i<7; i++)
      {
         if(k<21)
            if(!CreateCheckBox(m_checkbox[k],10+80*i,m_step_window.CaptionHeight()+70+j*25,timeframe_names[k]))
               return(false);
         k++;
      }
   }

Removemos também a janela de cálculo da altura da janela (era necessário calcular a altura de acordo com o número de símbolos na Observação do Mercado).

m_step_window.ChangeWindowHeight(m_checkbox[m_all_symbols-1].YGap()+30+30);

Deixamos a altura da janela estática:

m_step_window.YSize(500);

Agora, nós precisamos criar o objeto básico de uma tabela que será preenchida com os dados do Market Watch. Criamos a instância de classe CTable e o método de implementação de tabela.

//--- Rendered table
   CTable            m_table;

   bool              CreateTable(const int x_gap,const int y_gap);

Implementamos ela no arquivo da janela principal StepWindow.mqh:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateTable(const int x_gap,const int y_gap)
{
#define COLUMNS1_TOTAL 7
#define ROWS1_TOTAL int(MathCeil(m_all_symbols/7))
//--- Save the pointer to the main control
   m_table.MainPointer(m_step_window);
//--- Array of column widths
   int width[COLUMNS1_TOTAL];
   ::ArrayInitialize(width,80);
//--- Array of text offset along the X axis in the columns
   int text_x_offset[COLUMNS1_TOTAL];
   ::ArrayInitialize(text_x_offset,25);
//--- Array of text alignment in columns
   ENUM_ALIGN_MODE align[COLUMNS1_TOTAL];
   ::ArrayInitialize(align,ALIGN_LEFT);
//--- Array of column image offsets along the X axis
   int image_x_offset[COLUMNS1_TOTAL];
   ::ArrayInitialize(image_x_offset,5);
//--- Array of column image offsets along the Y axis
   int image_y_offset[COLUMNS1_TOTAL];
   ::ArrayInitialize(image_y_offset,4);
//--- Properties
   m_table.XSize(560);
   m_table.YSize(190);
   m_table.Font(m_base_font);
   m_table.FontSize(m_base_font_size);
   m_table.CellYSize(20);
   m_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
   m_table.TextAlign(align);
   m_table.ColumnsWidth(width);
   m_table.TextXOffset(text_x_offset);
   m_table.ImageXOffset(image_x_offset);
   m_table.ImageYOffset(image_y_offset);
   m_table.LabelXGap(5);
   m_table.LabelYGap(4);
   m_table.IconXGap(7);
   m_table.IconYGap(4);
   m_table.MinColumnWidth(0);
   m_table.LightsHover(true);
   m_table.SelectableRow(false);
   m_table.IsWithoutDeselect(false);
   m_table.ColumnResizeMode(true);
   m_table.IsZebraFormatRows(clrWhiteSmoke);
   m_table.AutoXResizeMode(true);
   m_table.AutoXResizeRightOffset(10);
   m_table.AutoYResizeMode(true);
   m_table.AutoYResizeBottomOffset(50);
//--- Populate the table with data
   InitializingTable();
//--- Create the control
   if(!m_table.CreateTable(x_gap,y_gap))
      return(false);
//--- Add the object to the common array of the object groups
   CWndContainer::AddToElementsArray(0,m_table);
   return(true);
}

Antes de usar esse método para criar uma tabela, ele deve ser preenchido com os seguintes dados: a lista de todos os símbolos da Observação do mercado para a conta de negociação atual. Isso será feito pelo método InitializingTable(): adicionamos ele à seção private da classe base:

//+------------------------------------------------------------------+
//| Initialize the table                                             |
//+------------------------------------------------------------------+
void CProgram::InitializingTable(void)
{
//--- Array of icons 1
   string image_array1[2]=
   {
      "Images\\EasyAndFastGUI\\Controls\\checkbox_off.bmp",
      "Images\\EasyAndFastGUI\\Controls\\checkbox_on_g.bmp"
   };
//---
   int k=0;
   for(int c=0; c<COLUMNS1_TOTAL; c++)
   {
      //---
      for(int r=0; r<ROWS1_TOTAL; r++)
      {
         if(k<m_all_symbols)
         {
            //--- Set the cell type to Checkbox
            m_table.CellType(c,r,CELL_CHECKBOX);
            m_table.SetImages(c,r,image_array1);
            //--- Set the text
            m_table.SetValue(c,r,SymbolName(k,false));
         }
         k++;
      }
   }
}

Agora, nós usamos as preparações acima no corpo do método CreateStepWindow() para criar e preencher a tabela. Como resultado, nós obteremos uma lista de todos os símbolos da Observação do Mercado (Fig.5), que também se baseia no tipo de seleção da caixa de seleção, mas agora é apresentado em forma de tabela.

Fig. 5 Resultado da conversão da lista de símbolos em forma de tabela.

A próxima etapa é vincular a tabela recém-criada às interações disponíveis para o conjunto de caixas de seleção anterior. As seguintes possibilidades devem ser fornecidas:

  • Selecionar conjuntos predefinidos: ALL, Major e Crosses.
  • Salvar e carregar modelos de um conjunto personalizado de símbolos.

Para implementar a primeira possibilidade, substituímos as seções pelos nomes dos modelos na OnEvent() com o seguinte código:

//--- All
         if(lparam==m_currency_set[0].Id() && m_currency_set[0].IsPressed())
         {
            m_currency_set[1].IsPressed(false);
            m_currency_set[2].IsPressed(false);
            m_currency_set[1].Update(true);
            m_currency_set[2].Update(true);
            //---
            int k=0;
            for(int c=0; c<7; c++)
            {
               //---
               for(int r=0; r<MathCeil(m_all_symbols/7); r++)
               {
                  if(k<m_all_symbols)
                     m_table.ChangeImage(c,r,1);
                  k++;
               }
            }
            m_table.Update(true);
         }
         //--- Majors
         else if(lparam==m_currency_set[1].Id() && m_currency_set[1].IsPressed())
         {
            m_currency_set[0].IsPressed(false);
            m_currency_set[2].IsPressed(false);
            m_currency_set[0].Update(true);
            m_currency_set[2].Update(true);
            //---
            string pairs[4]= {"EURUSD","GBPUSD","USDCHF","USDJPY"};
            //--- Clear the selection
            int k=0;
            for(int c=0; c<7; c++)
            {
               //---
               for(int r=0; r<MathCeil(m_all_symbols/7); r++)
               {
                  if(k<m_all_symbols)
                     m_table.ChangeImage(c,r,0);
                  k++;
               }
            }
            //---
            k=0;
            for(int c=0; c<7; c++)
            {
               //---
               for(int r=0; r<MathCeil(m_all_symbols/7); r++)
               {
                  if(k<m_all_symbols)
                  {
                     for(int j=0; j<4; j++)
                     {
                        if(m_table.GetValue(c,r)==pairs[j])
                           m_table.ChangeImage(c,r,1);
                     }
                  }
                  k++;
               }
            }
            m_table.Update(true);
         }
         //--- Crosses
         else if(lparam==m_currency_set[2].Id() && m_currency_set[2].IsPressed())
         {
            m_currency_set[0].IsPressed(false);
            m_currency_set[1].IsPressed(false);
            m_currency_set[0].Update(true);
            m_currency_set[1].Update(true);
            //---
            string pairs[20]=
            {
               "EURUSD","GBPUSD","USDCHF","USDJPY","USDCAD","AUDUSD","AUDNZD","AUDCAD","AUDCHF","AUDJPY",
               "CHFJPY","EURGBP","EURAUD","EURCHF","EURJPY","EURCAD","EURNZD","GBPCHF","GBPJPY","CADCHF"
            };
            //--- Clear the selection
            int k=0;
            for(int c=0; c<7; c++)
            {
               //---
               for(int r=0; r<MathCeil(m_all_symbols/7); r++)
               {
                  if(k<m_all_symbols)
                     m_table.ChangeImage(c,r,0);
                  k++;
               }
            }
            //---
            k=0;
            for(int c=0; c<7; c++)
            {
               //---
               for(int r=0; r<MathCeil(m_all_symbols/7); r++)
               {
                  if(k<m_all_symbols)
                  {
                     for(int j=0; j<20; j++)
                     {
                        if(m_table.GetValue(c,r)==pairs[j])
                           m_table.ChangeImage(c,r,1);
                     }
                  }
                  k++;
               }
            }
            m_table.Update(true);
         }
         //---
         if((lparam==m_currency_set[0].Id() && !m_currency_set[0].IsPressed())      ||
               (lparam==m_currency_set[1].Id() && !m_currency_set[1].IsPressed())   ||
               (lparam==m_currency_set[2].Id() && !m_currency_set[2].IsPressed())
           )
         {
            //--- Clear the selection
            int k=0;
            for(int c=0; c<7; c++)
            {
               //---
               for(int r=0; r<MathCeil(m_all_symbols/7); r++)
               {
                  if(k<m_all_symbols)
                     m_table.ChangeImage(c,r,0);
                  k++;
               }
            }
            m_table.Update(true);
         }

O armazenamento e carregamento dos conjuntos de símbolos selecionados foram realizados usando os métodos SaveSymbolSet() e LoadSymbolSet(), respectivamente. Aqui, nós precisamos alterar a parte do código na qual os dados são obtidos de uma caixa de seleção, pois os dados devem ser obtidos de uma tabela recém-criada. Assim, os dados devem ser carregados na mesma tabela.

//+------------------------------------------------------------------+
//| Save template to a file                                          |
//+------------------------------------------------------------------+
bool CProgram::SaveSymbolSet(string file_name)
{
   if(file_name=="")
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Выберите имя шаблона для записи","Монитор сигналов");
      else
         MessageBox("Choose a name for the template to save","Signal Monitor");
      return(false);
   }
   int h=FileOpen("Signal Monitor\\"+file_name+".bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Не удалось создать файл конфигурации","Монитор сигналов");
      else
         MessageBox("Failed to create configuration file","Signal Mo
nitor");
      return(false);
   }
   else
      MessageBox("The "+file_name+" configuration has been successfully saved","Signal Monitor");
//--- Save symbol selection
   int k=0;
   for(int c=0; c<7; c++)
   {
      //---
      for(int r=0; r<MathCeil(m_all_symbols/7); r++)
      {
         if(k<m_all_symbols)
            m_save.tf[k]=m_table.SelectedImageIndex(c,r)>0?true:false;
         k++;
      }
   }
//---
   FileWriteStruct(h,m_save);
   FileClose(h);
//---
   return(true);
}
//+------------------------------------------------------------------+
//| Load data to a panel                                             |
//+------------------------------------------------------------------+
bool CProgram::LoadSymbolSet(string file_name)
{
   if(file_name=="")
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Выберите имя шаблона для загрузки","Монитор сигналов");
      else
         MessageBox("Choose a name for the template 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);
//--- Load symbol selection
   int k=0;
   for(int c=0; c<7; c++)
   {
      //---
      for(int r=0; r<MathCeil(m_all_symbols/7); r++)
      {
         if(k<m_all_symbols)
         {
            if(m_save.tf[k])
               m_table.ChangeImage(c,r,1);
            else
               m_table.ChangeImage(c,r,0);
         }
         k++;
      }
   }
   m_table.Update(true);
//---
   FileClose(h);
//---
   return(true);
}

Agora, vamos marcar o bloco em que os dados são coletados e lembrados em uma estrutura que será salva em um arquivo. Além disso, aqui os dados são carregados de um arquivo em uma estrutura e os dados dele são adicionados à tabela.

O resultado visual de todas as alterações e adições é mostrado na figura 6, mas o objetivo principal era permitir uma operação conveniente com um grande número de símbolos, que não se encaixavam na janela.

Fig.6 O resultado da adição de uma tabela e sua interação com os elementos da interface do usuário.

Além disso, nós precisamos editar os métodos de transição entre as etapas 1 e 2, porque nós alteramos a maneira como nós obtemos as informações sobre os símbolos selecionados. As transições entre as etapas de configuração são realizadas por dois métodos, que precisam ser alterados. O método To_Step1() deve ser modificado da seguinte maneira: quando pulamos da Etapa 2 para a Etapa 1, a possibilidade de selecionar os períodos de tempo deve ser ocultada e a tabela deve ser mostrada.

//+------------------------------------------------------------------+
//| Go to Step 1                                                     |
//+------------------------------------------------------------------+
void CProgram::ToStep_1(void)
{
//--- Change header
   m_step_window.LabelText("Signal Monitor Step 1: Choose Symbols");
   m_step_window.Update(true);
//--- Hide the Back button
   m_back_button.Hide();
//--- Show the table
   m_table.Show();
//--- Hide timeframes
   for(int i=0; i<21; i++)
      m_checkbox[i].Hide();
   string names[3]= {"All","Majors","Crosses"};
//--- Change names of selection buttons
   for(int i=0; i<3; i++)
   {
      m_currency_set[i].IsPressed(false);
      m_currency_set[i].LabelText(names[i]);
      m_currency_set[i].Update(true);
   }
//--- Show block for working with templates
   m_text_edit.Show();
   m_load_button.Show();
   m_save_button.Show();
//--- Set the current setup step
   m_current_step=1;
}

No método To_Step2(), ocultamos a tabela, exibimos a seleção do período de tempo e lembramos dos símbolos selecionados na primeira etapa.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::ToStep_2(void)
{
//--- Check whether at least one symbol is selected
   int cnt=0;
//---
   for(int c=0; c<7; c++)
   {
      for(int r=0; r<MathCeil(m_all_symbols/7); r++)
      {
         if(m_table.SelectedImageIndex(c,r)>0)
            cnt++;
      }
   }
//---
   if(cnt<1)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Не выбран ни один символ!","Внимание");
      else
         MessageBox("No symbols selected!","Warning");
      return;
   }
//--- Hide the table
   m_table.Hide();
//--- Display timeframes
   for(int i=0; i<21; i++)
      m_checkbox[i].Show();
//--- Count the number of selected symbols
   ArrayResize(m_symbols,cnt);
   cnt=0;
//--- Remember the selected symbols in the array
   for(int c=0; c<7; c++)
   {
      for(int r=0; r<MathCeil(m_all_symbols/7); r++)
      {
         if(m_table.SelectedImageIndex(c,r)>0)
         {
            m_symbols[cnt]=m_table.GetValue(c,r);
            cnt++;
         }
      }
   }
//--- Set selected symbols in Market Watch
   for(int c=0; c<7; c++)
   {
      for(int r=0; r<MathCeil(m_all_symbols/7); r++)
      {
         if(m_table.SelectedImageIndex(c,r)>0)
            SymbolSelect(m_table.GetValue(c,r),true);
         else
            SymbolSelect(m_table.GetValue(c,r),false);
      }
   }
//---
   if(m_current_step==3)
   {
      m_add_signal.Hide();
      m_signal_header.Hide();
      m_next_button.LabelText("Next");
      m_next_button.Update(true);
      for(int i=0; i<5; i++)
         m_signal_editor[i].Hide();
      ClearSaves();
   }
//--- Change header
   m_step_window.LabelText("Signal Monitor Step 2: Choose Timeframes");
   m_step_window.Update(true);
   string names[3]= {"All","Junior","Senior"};
//--- Change names of selection buttons
   for(int i=0; i<3; i++)
   {
      m_currency_set[i].LabelText(names[i]);
      m_currency_set[i].IsPressed(false);
      if(m_current_step==3)
         m_currency_set[i].Show();
      m_currency_set[i].Update(true);
   }
//--- Hide block for working with templates
   m_text_edit.Hide();
   m_load_button.Hide();
   m_save_button.Hide();
//--- Show Back button
   m_back_button.Show();
//---
   m_current_step=2;
}

Agora, nós precisamos ajustar a interação dos botões, selecionando os conjuntos do período de tempo predefinido com a lista de caixas de seleção. Como a lista é constante agora, as mudanças apropriadas devem ser feitas no código:

//--- All
         if(lparam==m_currency_set[0].Id() && m_currency_set[0].IsPressed())
         {
            m_currency_set[1].IsPressed(false);
            m_currency_set[2].IsPressed(false);
            m_currency_set[1].Update(true);
            m_currency_set[2].Update(true);
            //---
            for(int i=0; i<21; i++)
            {
               m_checkbox[i].IsPressed(true);
               m_checkbox[i].Update(true);
            }
         }
         //--- Junior Timeframes
         else if(lparam==m_currency_set[1].Id() && m_currency_set[1].IsPressed())
         {
            m_currency_set[0].IsPressed(false);
            m_currency_set[2].IsPressed(false);
            m_currency_set[0].Update(true);
            m_currency_set[2].Update(true);
            //---
            string pairs[11]=
            {
               "M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30"
            };
            //--- Clear the selection
            for(int i=0; i<21; i++)
            {
               m_checkbox[i].IsPressed(false);
               m_checkbox[i].Update(true);
            }
            //---
            for(int i=0; i<21; i++)
            {
               for(int j=0; j<11; j++)
                  if(m_checkbox[i].LabelText()==pairs[j])
                  {
                     m_checkbox[i].IsPressed(true);
                     m_checkbox[i].Update(true);
                  }
            }
         }
         //--- Senior Timeframes
         else if(lparam==m_currency_set[2].Id() && m_currency_set[2].IsPressed())
         {
            m_currency_set[0].IsPressed(false);
            m_currency_set[1].IsPressed(false);
            m_currency_set[0].Update(true);
            m_currency_set[1].Update(true);
            //---
            string pairs[10]=
            {
               "H1","H2","H3","H4","H6","H8","H12","D1","W1","MN"
            };
            //--- Clear the selection
            for(int i=0; i<21; i++)
            {
               m_checkbox[i].IsPressed(false);
               m_checkbox[i].Update(true);
            }
            //---
            for(int i=0; i<21; i++)
            {
               for(int j=0; j<10; j++)
                  if(m_checkbox[i].LabelText()==pairs[j])
                  {
                     m_checkbox[i].IsPressed(true);
                     m_checkbox[i].Update(true);
                  }
            }
         }
         //---
         if((lparam==m_currency_set[0].Id() && !m_currency_set[0].IsPressed())      ||
               (lparam==m_currency_set[1].Id() && !m_currency_set[1].IsPressed())   ||
               (lparam==m_currency_set[2].Id() && !m_currency_set[2].IsPressed())
           )
         {
            //--- Clear the selection
            for(int i=0; i<21; i++)
            {
               m_checkbox[i].IsPressed(false);
               m_checkbox[i].Update(true);
            }
         }


Edição rápida das regras de busca no monitor

Durante o monitoramento do sinal, o usuário pode precisar alterar as condições de um sinal de negociação criado anteriormente. Atualmente, isso pode ser feito reiniciando o aplicativo e reconfigurando todos os sinais do monitor, além do necessário. Esta solução não é conveniente. Portanto, vamos fornecer a possibilidade de editar os sinais de negociação prontos a partir do próprio monitor. Vamos adicionar um botão à interface do monitor, permitindo abrir uma pequena caixa de diálogo, como mostra a figura 7. A caixa conterá uma lista de todos os sinais de negociação criados. Um clique em um sinal abrirá ele para edição.

Fig. 7 Edição de um sinal criado anteriormente a partir do monitor.

Vamos prosseguir com a implementação. Para exibir o botão que abre a janela com a lista de sinais de negociação, adicionamos a seguinte propriedade no corpo do método CreateStepWindow():

m_step_window.TooltipsButtonIsUsed(true);

 E depois desative-o no evento Conclusão da criação da GUI - portanto, o botão não será exibido na etapa de configuração inicial do aplicativo e somente será exibido após todos os sinais terem sido criados e o monitor ter sido iniciado:

// --- GUI creation completion
   if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI)
   {
      ...
      m_step_window.GetTooltipButtonPointer().Hide();
   }

Ativamos ele ao carregar o monitor.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::AutoResize(const int x_size,const int y_size)
{
   ...
   m_step_window.GetTooltipButtonPointer().Show();
}

Agora, criamos uma nova caixa de diálogo na qual a lista de sinais criados será exibida. Criamos uma variável da instância de classe CWindow e o método CreateFastEdit() que implementa a criação da janela, bem como o método CreateFastEditor() para criar os botões (a edição do sinal será realizada clicando nesses botões).

   CWindow           m_fast_edit;

   bool              CreateFastEdit(const string caption_text);
   bool              CreateFastEditor(CButton &button,string text,const int x_gap,const int y_gap);

Implementação destes métodos:

//+------------------------------------------------------------------+
//| Creates a window for creating and editing trading signals        |
//+------------------------------------------------------------------+
bool CProgram::CreateFastEdit(const string caption_text)
{
//--- Add the window pointer to the window array
   CWndContainer::AddWindow(m_fast_edit);
//--- Properties
   m_fast_edit.XSize(180);
   m_fast_edit.YSize(280);
//--- Coordinates
   int x=m_step_window.XGap()+m_step_window.XSize()+10;
   int y=m_step_window.YGap();
//---
   m_fast_edit.CaptionHeight(22);
   m_fast_edit.IsMovable(true);
   m_fast_edit.CaptionColor(m_caption);
   m_fast_edit.CaptionColorLocked(m_caption);
   m_fast_edit.CaptionColorHover(m_caption);
   m_fast_edit.BackColor(m_background);
   m_fast_edit.FontSize(m_base_font_size);
   m_fast_edit.Font(m_base_font);
   m_fast_edit.WindowType(W_DIALOG);
//--- Creating the form
   if(!m_fast_edit.CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
//---
   for(int i=0; i<5; i++)
   {
      if(!CreateFastEditor(m_fast_editor[i],"Signal_"+string(i),10,40*i+40))
         return(false);
   }
   return(true);
}
//+------------------------------------------------------------------+
//| Creates a button with an image                                   |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\settings_light.bmp"
bool CProgram::CreateFastEditor(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_fast_edit);
//--- 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(3,button);
   return(true);
}

Chamamos o método CreateFastEdit() no corpo do 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);
//--- Creating a quick edit form
   if(!CreateFastEdit("Fast Signal Editor"))
      return(false);
//--- Finishing the creation of GUI
   CWndEvents::CompletedGUI();
   return(true);
}

Agora, um clique no botão Settings no monitor deve abrir a caixa de diálogo com os sinais. Para fazer isso, adicionamos o seguinte código na seção do Evento de clique no botão do método OnEvent():

      //--- OPEN THE SETTING WINDOW
      if(lparam==m_step_window.GetTooltipButtonPointer().Id())
      {
         //--- Coordinates
         int x=m_step_window.X()+m_step_window.XSize()+10;
         int y=m_step_window.Y();
         m_fast_edit.X(x);
         m_fast_edit.Y(y);
         m_fast_edit.OpenWindow();
      }

O seguinte resultado será obtido se você compilar o projeto agora:

Fig. 8 Adicionando uma janela para edição rápida dos sinais de negociação.

Agora, a caixa apresenta todos os botões de edição de sinal, mas a ideia é mostrar apenas os sinais criados. Então, vamos adicionar uma verificação do número atual de sinais disponíveis. Isso pode ser feito pelo evento Abertura da caixa de diálogo:

//--- Opening a dialog window
   if(id==CHARTEVENT_CUSTOM+ON_OPEN_DIALOG_BOX)
   {
      if(m_current_step<4)
         return;
      for(int i=0; i<5; i++)
      {
         if(!FileIsExist("Signal Monitor\\signal_"+string(i)+".bin"))
            m_fast_editor[i].Hide();
      }
   }

Aqui é realizada uma verificação da existência dos arquivos com um sinal de negociação. Somente os sinais criados anteriormente serão exibidos. Agora, um clique no botão com o sinal criado deve abrir a janela de edição desse sinal. Isso é feito na seção Evento de clique no botão.

      //--- Trading signal editing
      for(int i=0; i<5; i++)
      {
         if(lparam==m_fast_editor[i].Id())
         {
            m_fast_edit.CloseDialogBox();
            LoadSignalSet(i);
            m_new_signal.LabelText("Save");
            m_new_signal.Update(true);
            RebuildParameters(m_indicator_type.GetListViewPointer().SelectedItemIndex());
            m_set_window.OpenWindow();
            m_number_signal=i;
         }
      }

Um clique em um dos sinais na janela de edição rápida abrirá a janela Settings e carregará os dados salvos anteriormente desse sinal de negociação. Depois que os dados necessários forem alterados, as novas configurações deverão ser gravadas em um arquivo. Nesse caso, nós não precisamos concluir todo o procedimento de configuração do monitor.

Localização do aplicativo

Para resolver a tarefa de localização, é necessário determinar todos os elementos da GUI que podem ser traduzidos, enquanto alguns deles devem ser deixados como estão, pois seus nomes geralmente são aceitos. Nós usaremos um mecanismo simples: nós criaremos um array de strings com os dados que serão usados para substituição nos elementos da interface do usuário, dependendo do idioma selecionado. Nós teremos dois idiomas: russo e inglês. Em primeiro lugar, vamos criar no arquivo SignalMonitor.mq5 uma enumeração, o que permitirá selecionar o idioma da interface do usuário desejado na inicialização. Os nomes de alguns dos elementos serão definidos de acordo com os padrões em inglês.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
enum UPDATE
{
   MINUTE,        // 1 minute
   MINUTE_15,     // 15 minutes
   MINUTE_30,     // 30 minutes
   HOUR,          // 1 hour
   HOUR_4         // 4 hour
};
enum LANG
{
   RUSSIAN,       // Russian
   ENGLISH        // English
};
//+------------------------------------------------------------------+
//| Expert Advisor input parameters                                  |
//+------------------------------------------------------------------+
input int                  Inp_BaseFont      =  10;                  // Base Font
input color                Caption           =  C'0,130,225';        // Caption Color
input color                Background        =  clrWhiteSmoke;       // Back color
input LANG                 Language          =  ENGLISH;             // Interface language
input UPDATE               Update            =  MINUTE;              // Update interval

Para passar as informações sobre o idioma selecionado para a interface, nós criamos uma variável na seção pública da classe base CProgram.

   //---
   int               m_language;

O índice do idioma selecionado será atribuído à variável durante a inicialização do aplicativo.

   program.m_language=Language;

Em seguida, criamos um array na seção private da classe base, que servirá como um receptor de dados a serem substituídos na interface de acordo com o idioma selecionado. Crie também um método que carregará os dados na interface.

   string            m_lang[];

   void              ChangeLanguage(void);

Agora, implementamos o método declarado na Program.mqh e definimos os valores do idioma para os campos apropriados de cada elemento da GUI.

//+------------------------------------------------------------------+
//| Changing the interface language                                  |
//+------------------------------------------------------------------+
void CProgram::ChangeLanguage(void)
{
//---
#define ITEMS 40
   ArrayResize(m_lang,ITEMS);
   string rus[ITEMS]=
   {
      "Монитор Сигналов Шаг 1: Выбор Символов","Все","Мажоры","Кроссы",
      "Назад","Далее","Загрузка(L)","Сохранить(S)","Имя шаблона","Монитор сигналов Шаг 2: Выбор таймфреймов",
      "Все","Младшие","Старшие",
      "Монитор Сигналов Шаг 3: Создание торговых сигналов","Создать","Добавить сигнал","Список сигналов",
      "Редактор торговых сигналов","Тип индикатора","1.Настройки индикатора","Примен. цена",
      "Введите путь индикатора","Введите параметры индикатора через запятую",
      "2.Настройка сигнала","Правило","Метка","Значение","Текст","Цвет метки","Фон","Кант","Подсказка",
      "Изображение","Таймфреймы","Добавить","Отмена","Монитор торговых сигналов","Номер буфера","Сохранить"
   };
   string eng[ITEMS]=
   {
      "Signal Monitor Step 1: Choose Symbols","ALL","Major","Crosses",
      "Back","Next","Load(L)","Save(S)","Template name","Signal Monitor Step 2: Choose Timeframes",
      "ALL","Junior","Senior",
      "Signal Monitor Step 3: Creating Trading Signals","Create","Add Signal","Signal List",
      "Signal Monitor Edit Signal","Indicator Type","1.Indicator Settings","Applied Price",
      "Enter the indicator path","Enter indicator parameters separated by commas",
      "2.Signal Settings","Rule","Label","Value","Text","Label Color","Use Background","Use Border","Use Tooltip",
      "Use Image","Timeframes","Add","Cancel","Signal Monitor","Buffer number","Save"
   };
//--- Russian
   if(m_language==0)
      ArrayCopy(m_lang,rus);
//--- English
   else
      ArrayCopy(m_lang,eng);
}

Assim, implementamos adicionalmente o idioma russo (fig.9). Você também pode adicionar seu idioma preferido

Fig. 9 resultado da localização da GUI.


Características adicionais

Alguns recursos adicionais melhorarão a parte visual do monitor e permitirão mudar rapidamente para a tabela de símbolos na qual um sinal surgiu. A parte visual é a extensão do bloco de sinal, porque a forma atualmente usada parece pequena. Encontramos o método CreateSignalButton(), aumentamos o tamanho dos blocos de sinal, ajustamos a posição dos elementos dentro desses blocos e a disposição dos blocos entre eles no método To_Monitor().

   button.XSize(60);
   button.YSize(30);
   button.IconXGap(2);
   button.IconYGap(11);
   button.LabelXGap(19);
   button.LabelYGap(10);

//--- Symbols
   int sy=ArraySize(m_symbols);
   ArrayResize(m_symbol_label,sy);
   for(int i=0; i<sy; i++)
   {
      if(!CreateSymbolLabel(m_symbol_label[i],5,m_step_window.CaptionHeight()+40+i*35,m_symbols[i]))
         return;
      m_symbol_label[i].Update(true);
   }
//--- Timeframes
   int tf=ArraySize(m_timeframes);
   ArrayResize(m_timeframe_label,tf);
//---
   for(int i=0; i<tf; i++)
   {
      if(!CreateTimeframeLabel(m_timeframe_label[i],110+65*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()+35+j*35))
            return;
         m_signal_button[k].Update(true);
         k++;
      }
   }
//---
   m_current_step=4;
//--- Resize window
   AutoResize(m_timeframe_label[tf-1].XGap()+m_timeframe_label[tf-1].XSize()+15,m_symbol_label[sy-1].YGap()+m_symbol_label[sy-1].YSize()+10);

Essa implementação do monitor é muito mais conveniente para o monitoramento.

Fig. 10 Redimensionando os blocos de sinal e ajustando a interface do monitor.

Agora, vamos implementar a abertura de um gráfico para o símbolo e o período em que o sinal foi encontrado. O gráfico deve ser aberto com um clique no bloco correspondente. Adicionamos o seguinte no método OnEvent(), na seção Evento de clique no botão (porque os blocos de sinal são botões):

      //--- CLICKING ON THE SIGNAL BLOCK
      for(int i=0; i<ArraySize(m_signal_button); i++)
      {
         if(lparam==m_signal_button[i].Id())
            ChartOpen(GetSymbol(i),GetTimeframe(i));
      }

Tudo é bem simples. Neste ponto, a atual fase de desenvolvimento está concluída. Na próxima parte, nós continuaremos a melhorar o sistema de busca de sinais, apresentaremos o conceito de Sinal Composto e expandiremos os recursos de controle do monitor.


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 abaixo na Fig. 11.


Fig. 11. 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/7678

Arquivos anexados |
MQL5.zip (501.31 KB)
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.

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.

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

Neste artigo continuaremos testando um novo conceito, em particular a descrição da interface de programas MQL usando as construções da linguagem MQL. A criação automática de GUIs com base no layout MQL fornece funcionalidade adicional para armazenamento em cache e geração dinâmica de elementos, gerenciamento de estilos e novos esquemas de manipulação de eventos. Incluímos uma versão aprimorada da biblioteca de controles padrão.

Otimização Walk Forward contínua (parte 6): Lógica e estrutura do otimizador automático Otimização Walk Forward contínua (parte 6): Lógica e estrutura do otimizador automático

Anteriormente, nós consideramos a criação da otimização walk forward automática. Desta vez, nós prosseguiremos para a estrutura interna da ferramenta de otimização automática. O artigo será útil para todos aqueles que desejam continuar trabalhando com o projeto criado e modificá-lo, bem como para aqueles que desejam entender a lógica do programa. O artigo atual contém diagramas UML que apresentam a estrutura interna do projeto e os relacionamentos entre seus objetos. Ele também descreve o processo de início da otimização, mas não contém a descrição do processo de implementação do otimizador.