![Monitoramento de sinais de negociação multimoeda (Parte 4): Aprimoramento das funcionalidades e melhorias no sistema de busca de sinais](https://c.mql5.com/i/articles/overlay.png)
Monitoramento de sinais de negociação multimoeda (Parte 4): Aprimoramento das funcionalidades e melhorias no sistema de busca de sinais
Conteúdo
- Introdução
- Indicador personalizado para gerar um sinal de negociação
- Expansão do sistema de regras de busca de sinais de negociação
- Conversão da lista de símbolos em uma forma de tabela
- Edição rápida das regras de busca no monitor
- Localização do aplicativo
- Características adicionais
- Conclusão
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 Ltd.
Artigo original: https://www.mql5.com/ru/articles/7678
![Linguagem MQL como um meio de marcação da interface gráfica de programas MQL. Parte II](https://c.mql5.com/2/38/MQL5-avatar-dialog_form.png)
![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](https://c.mql5.com/2/38/MQL5-avatar-doeasy-library__4.png)
![Otimização Walk Forward contínua (parte 6): Lógica e estrutura do otimizador automático](https://c.mql5.com/2/38/MQL5-avatar-continuous_optimization__3.png)
![Linguagem MQL como um meio de marcação da interface gráfica de programas MQL. Parte 1](https://c.mql5.com/2/38/MQL5-avatar-dialog_form__1.png)
![MQL5 - Linguagem para estratégias de negociação inseridas no terminal do cliente MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso