English Русский 中文 Español Deutsch 日本語
preview
Negociando com o Calendário Econômico do MQL5 (Parte 5): Aprimorando o Painel com Controles Responsivos e Botões de Filtro

Negociando com o Calendário Econômico do MQL5 (Parte 5): Aprimorando o Painel com Controles Responsivos e Botões de Filtro

MetaTrader 5Negociação |
244 1
Allan Munene Mutiiria
Allan Munene Mutiiria

Introdução

Neste artigo, daremos continuidade ao trabalho anterior na Parte 4 da série MetaQuotes Language 5 (MQL5), onde adicionamos atualizações em tempo real ao painel do Calendário Econômico do MQL5. Aqui, nosso foco é tornar o painel mais interativo, adicionando botões que nos permitam controlar diretamente os filtros de pares de moedas, níveis de importância e intervalos de tempo — tudo a partir do próprio painel, sem precisar alterar as configurações no código. Também incluiremos um botão “Cancelar” que limpa os filtros selecionados e remove os componentes do painel, dando-nos controle total sobre a exibição. Por fim, melhoraremos a experiência do usuário tornando os botões responsivos a cliques, garantindo que funcionem de forma suave e forneçam feedback imediato. Os tópicos que abordaremos neste artigo incluem:

  1. Criação de Botões e Controles de Filtro
  2. Automatização e Adição de Responsividade aos Botões
  3. Testando o Painel Aprimorado
  4. Conclusão

Essas adições melhorarão significativamente a usabilidade do nosso painel, tornando-o mais flexível e dinâmico para interação em tempo real. Com esses elementos interativos, podemos facilmente filtrar e gerenciar os dados de notícias exibidos sem precisar modificar o código-base a cada vez. Vamos começar criando os botões de filtro e integrando-os ao layout existente do painel.


Criação de Botões e Controles de Filtro

Nesta seção, focaremos na criação dos botões de filtro que nos permitirão controlar diferentes aspectos do painel, como o filtro de pares de moedas, o filtro de nível de importância e o filtro de intervalo de tempo, diretamente no painel. Ao adicionar esses botões, facilitaremos a interação com o painel, sem precisar acessar ou modificar o código toda vez que quisermos alterar uma configuração de filtro. O objetivo é projetar uma interface de usuário intuitiva que ofereça flexibilidade, mantendo o layout simples e limpo.

Primeiro, definiremos as posições e propriedades de cada um dos botões de filtro. Colocaremos esses botões dentro do painel do dashboard, permitindo alternar entre diferentes configurações de pares de moedas, níveis de importância e filtros de tempo. Por exemplo, colocaremos os botões de filtro de moeda no canto superior direito do painel, os botões de seleção de filtro de moeda, importância e tempo na seção de cabeçalho, e o botão de cancelamento logo após sua definição. Cada botão corresponderá a uma configuração de filtro específica, e poderemos clicar nesses botões para aplicar os filtros desejados. Aqui está uma imagem mostrando o layout inicial dos botões de filtro dentro do painel:

PLANTA DO LAYOUT

Como visto na imagem, os botões de filtro são organizados dentro do painel para facilitar o acesso e o gerenciamento. Cada botão é projetado para lidar com uma função específica — seja ativar os pares de moedas disponíveis, definir o nível de importância dos eventos ou filtrar por tempo. Os botões também serão visualmente distintos entre si para que possamos facilmente diferenciar os diferentes grupos de controle.

Para implementar isso, precisaremos definir constantes para os objetos e controles extras que vamos incluir no painel.

#define FILTER_LABEL "FILTER_LABEL"  //--- Define the label for the filter section on the dashboard

#define FILTER_CURR_BTN "FILTER_CURR_BTN"  //--- Define the button for filtering by currency pair
#define FILTER_IMP_BTN "FILTER_IMP_BTN"  //--- Define the button for filtering by event importance
#define FILTER_TIME_BTN "FILTER_TIME_BTN"  //--- Define the button for filtering by time range

#define CANCEL_BTN "CANCEL_BTN"  //--- Define the cancel button to reset or close the filters

#define CURRENCY_BTNS "CURRENCY_BTNS"  //--- Define the collection of currency buttons for user selection

Aqui, definimos um conjunto de constantes do tipo string que representam os nomes dos diversos elementos do painel que adicionaremos, principalmente botões e rótulos, usando a palavra-chave #define. Usaremos essas constantes para criar e gerenciar componentes da interface do usuário, como botões de filtro e um botão de cancelamento. Primeiro, definimos “FILTER_LABEL”, que representa o rótulo da seção de filtro no painel.

Em seguida, definimos três botões: “FILTER_CURR_BTN” para filtrar por par de moedas, “FILTER_IMP_BTN” para filtrar pela importância do evento e “FILTER_TIME_BTN” para filtrar pelo intervalo de tempo do evento. Também definimos um “CANCEL_BTN” para permitir redefinir ou fechar os filtros ativos e excluir os componentes do painel e, por fim, “CURRENCY_BTNS” representa uma coleção de botões de moedas que nos permite selecionar pares específicos. Essas definições nos ajudam a criar um painel dinâmico e interativo, no qual podemos controlar os dados exibidos diretamente pela interface. Para aumentar a dinamicidade, removemos o array de moedas definidas de dentro das funções e o colocamos no escopo global, conforme a seguir:

string curr_filter[] = {"AUD","CAD","CHF","EUR","GBP","JPY","NZD","USD"};
string curr_filter_selected[];

Aqui, aprimoramos a flexibilidade e a dinamicidade do filtro de moedas removendo a lista pré-definida de moedas de dentro das funções e colocando-a em um escopo global. Agora, definimos um novo array global “curr_filter” para conter a lista de possíveis moedas, como “AUD”, “CAD”, “CHF”, “EUR”, “GBP”, “JPY”, “NZD” e “USD”. Além disso, criamos um array vazio “curr_filter_selected”, que armazenará dinamicamente, durante a execução, as moedas selecionadas pelo usuário. Por fim, criamos uma variável que acompanhará a necessidade de atualizações, já que, desta vez, não precisaremos delas após removermos o painel por meio do botão de cancelamento. Simples assim.

bool isDashboardUpdate = true;

Definimos apenas uma variável boolean chamada “isDashboardUpdate” e a inicializamos como verdadeira. Usaremos essa variável para rastrear se o painel precisa ser atualizado. Ao defini-la como verdadeira, podemos sinalizar que houve uma alteração ou ação (como a seleção de um filtro ou o clique em um botão) que requer a atualização do painel com novos dados ou configurações. Da mesma forma, defini-la como falsa significará que não precisamos realizar o processo de atualização, ajudando a gerenciar o estado do painel de forma eficiente, garantindo que ele só seja atualizado quando necessário e evitando renderizações desnecessárias.

A partir do escopo global, podemos ir para a seção de inicialização e mapear nossos componentes de filtro extras no painel. Começaremos pelos botões superiores, que são os habilitadores de filtro.

createLabel(FILTER_LABEL,370,55,"Filters:",clrYellow,16,"Impact"); //--- Create a label for the "Filters" section in the dashboard

//--- Define the text, color, and state for the Currency filter button
string filter_curr_text = enableCurrencyFilter ? ShortToString(0x2714)+"Currency" : ShortToString(0x274C)+"Currency"; //--- Set text based on filter state
color filter_curr_txt_color = enableCurrencyFilter ? clrLime : clrRed; //--- Set text color based on filter state
bool filter_curr_state = enableCurrencyFilter ? true : false; //--- Set button state (enabled/disabled)
createButton(FILTER_CURR_BTN,430,55,110,26,filter_curr_text,filter_curr_txt_color,12,clrBlack); //--- Create Currency filter button
ObjectSetInteger(0,FILTER_CURR_BTN,OBJPROP_STATE,filter_curr_state); //--- Set the state of the Currency button

Aqui, estamos adicionando um rótulo e um botão para o filtro de moedas no painel. Primeiro, usamos a função "createLabel" para posicionar um rótulo intitulado "Filters:" no gráfico, nas coordenadas (370, 55), com a cor amarela e tamanho de fonte 16. Esse rótulo servirá como o cabeçalho da seção de filtros, indicando claramente ao usuário onde as opções de filtro estão localizadas.

Em seguida, definimos e configuramos o botão para o filtro de “Currency”. Verificamos o estado da variável "enableCurrencyFilter" e, com base em seu valor, definimos dinamicamente o texto do botão usando a variável "filter_curr_text". Se o filtro de moedas estiver ativado ("enableCurrencyFilter" for verdadeiro), o botão exibirá uma marca de verificação ("0x2714") com o texto "Currency", indicando que o filtro está ativo; se estiver desativado, uma cruz ("0x274C") será exibida, sinalizando que o filtro está inativo. Isso é feito com o uso de um operador ternário, que funciona de forma semelhante ao operador if, mas é menor, mais simples e direto.

Para refletir visualmente o estado do filtro, definimos a cor do texto do botão usando a variável "filter_curr_txt_color". Se o filtro estiver ativo, o texto aparecerá em verde; se estiver inativo, aparecerá em vermelho. Também usamos a variável booleana "filter_curr_state" para controlar o estado real do botão, que determina se ele está habilitado ou desabilitado.

Em seguida, criamos o próprio botão usando a função "createButton", posicionando-o em (430, 55) no gráfico, com o rótulo apropriado ("filter_curr_text"), cor do texto ("filter_curr_txt_color") e fundo preto. Por fim, usamos a função ObjectSetInteger para definir o estado do botão (habilitado ou desabilitado), referenciando a variável "filter_curr_state". Isso garante que a aparência e a funcionalidade do botão correspondam às configurações atuais do filtro. Após a compilação, temos a seguinte saída.

FILTRO DE MOEDA E RÓTULO

Isso foi um sucesso. Agora podemos prosseguir para adicionar os botões de filtro usando a mesma lógica.

//--- Define the text, color, and state for the Importance filter button
string filter_imp_text = enableImportanceFilter ? ShortToString(0x2714)+"Importance" : ShortToString(0x274C)+"Importance"; //--- Set text based on filter state
color filter_imp_txt_color = enableImportanceFilter ? clrLime : clrRed; //--- Set text color based on filter state
bool filter_imp_state = enableImportanceFilter ? true : false; //--- Set button state (enabled/disabled)
createButton(FILTER_IMP_BTN,430+110,55,120,26,filter_imp_text,filter_imp_txt_color,12,clrBlack); //--- Create Importance filter button
ObjectSetInteger(0,FILTER_IMP_BTN,OBJPROP_STATE,filter_imp_state); //--- Set the state of the Importance button

//--- Define the text, color, and state for the Time filter button
string filter_time_text = enableTimeFilter ? ShortToString(0x2714)+"Time" : ShortToString(0x274C)+"Time"; //--- Set text based on filter state
color filter_time_txt_color = enableTimeFilter ? clrLime : clrRed; //--- Set text color based on filter state
bool filter_time_state = enableTimeFilter ? true : false; //--- Set button state (enabled/disabled)
createButton(FILTER_TIME_BTN,430+110+120,55,70,26,filter_time_text,filter_time_txt_color,12,clrBlack); //--- Create Time filter button
ObjectSetInteger(0,FILTER_TIME_BTN,OBJPROP_STATE,filter_time_state); //--- Set the state of the Time button

//--- Create a Cancel button to reset all filters
createButton(CANCEL_BTN,430+110+120+79,51,50,30,"X",clrWhite,17,clrRed,clrNONE); //--- Create the Cancel button with an "X"

//--- Redraw the chart to update the visual elements
ChartRedraw(0); //--- Redraw the chart to reflect all changes made above

Aqui, configuramos os botões para os filtros “Importance”, “Time” e “Cancel” no painel. Para o filtro “Importance”, primeiro definimos o texto do botão com a variável "filter_imp_text". Com base no valor de "enableImportanceFilter", se o filtro estiver ativo, exibimos uma marca de verificação ("0x2714") junto ao texto "Importance", indicando que o filtro está habilitado; caso contrário, mostramos uma cruz ("0x274C") com o mesmo texto, indicando que o filtro está desabilitado. Também definimos a cor do texto do botão usando "filter_imp_txt_color", que é verde quando ativo e vermelha quando desativado. A variável booleana "filter_imp_state" controla se o botão está habilitado ou desabilitado.

Em seguida, usamos a função "createButton" para criar o botão do filtro “Importance” e posicioná-lo na coordenada (430+110, 55) com o texto, cor e estado apropriados. Depois, usamos ObjectSetInteger para definir o estado do botão ("OBJPROP_STATE") com base em "filter_imp_state", garantindo que o botão reflita corretamente o status.

Seguimos um processo semelhante para o botão do filtro “Time”. Definimos seu texto em "filter_time_text" e ajustamos a cor com "filter_time_txt_color" de acordo com o valor de "enableTimeFilter". O botão é criado na posição (430+110+120, 55) e o estado é definido adequadamente usando "filter_time_state".

Por fim, criamos o botão “Cancel” usando a função "createButton", que redefinirá todos os filtros e excluirá o painel ao ser clicado. Esse botão é posicionado em (430+110+120+79, 51), com um “X” branco sobre fundo vermelho para indicar sua função. Finalmente, chamamos a função ChartRedraw para atualizar o gráfico e refletir visualmente os novos botões e alterações. Ao executar, obtemos o seguinte resultado:

BOTÕES DE FILTRO COMPLETOS

Isso foi um sucesso. Agora adicionamos todos os botões de filtro ao painel. No entanto, durante a criação dos textos, usamos uma concatenação de caracteres especiais chamados caracteres Unicode. Vamos analisá-los em detalhes.

ShortToString(0x2714);
ShortToString(0x274C);

Aqui, usamos as funções "ShortToString(0x2714)" e "ShortToString(0x274C)" para representar caracteres Unicode em MQL5, e os valores "0x2714" e "0x274C" se referem a símbolos específicos no conjunto de caracteres Unicode.

  • "0x2714" é o ponto de código Unicode para o símbolo “CHECK MARK”. Ele é usado para indicar que algo está habilitado, concluído ou correto. No contexto dos botões de filtro, usamos esse símbolo para mostrar que um filtro (como o de Moeda ou Importância) está ativo ou habilitado.
  • "0x274C" é o ponto de código Unicode para o símbolo “CROSS MARK”. Ele é usado para representar algo desabilitado, não concluído ou incorreto. Aqui, o utilizamos para mostrar que um filtro está inativo ou desabilitado.

Você também pode usar seus próprios caracteres, desde que sejam compatíveis com o ambiente de codificação MQL5. Um conjunto de exemplos de caracteres é mostrado abaixo:

EXEMPLO DE UNICODE DE MARCA DE VERIFICAÇÃO

No código, a função ShortToString converte esses pontos de código Unicode em suas representações de caractere correspondentes. Esses caracteres são então adicionados ao texto dos botões de filtro para mostrar visualmente se um filtro está ativo ou não. Em seguida, podemos criar dinamicamente os botões de filtro de moedas.

int curr_size = 51;               //--- Button width
int button_height = 22;           //--- Button height
int spacing_x = 0;               //--- Horizontal spacing
int spacing_y = 3;               //--- Vertical spacing
int max_columns = 4;              //--- Number of buttons per row

for (int i = 0; i < ArraySize(curr_filter); i++){
   int row = i / max_columns;                              //--- Determine the row
   int col = i % max_columns;                             //--- Determine the column

   int x_pos = 575 + col * (curr_size + spacing_x);        //--- Calculate X position
   int y_pos = 83 + row * (button_height + spacing_y);    //--- Calculate Y position
   
   //--- Create button with dynamic positioning
   createButton(CURRENCY_BTNS+IntegerToString(i),x_pos,y_pos,curr_size,button_height,curr_filter[i],clrBlack);
}

if (enableCurrencyFilter == true){
   ArrayFree(curr_filter_selected);
   ArrayCopy(curr_filter_selected,curr_filter);
   Print("CURRENCY FILTER ENABLED");
   ArrayPrint(curr_filter_selected);
   
   for (int i = 0; i < ArraySize(curr_filter_selected); i++) {
      // Set the button to "clicked" (selected) state by default
      ObjectSetInteger(0, CURRENCY_BTNS + IntegerToString(i), OBJPROP_STATE, true);  // true means clicked
   }
}

Aqui, estamos criando dinamicamente os botões de filtro de moedas e gerenciando seu layout e estado com base em se o filtro está habilitado ou não. Começamos definindo os parâmetros de layout dos botões. Definimos a variável integer "curr_size" como 51, que determina a largura de cada botão, e "button_height" como 22, que define a altura dos botões. As variáveis "spacing_x" e "spacing_y" são definidas como 0 e 3, respectivamente, para controlar o espaçamento entre os botões na direção horizontal e vertical. Também definimos "max_columns" como 4, limitando o número de botões por linha a quatro.

Em seguida, usamos um for loop para percorrer o array "curr_filter", que contém os códigos de pares de moedas como “AUD”, “CAD” e assim por diante. Para cada iteração, calculamos a linha e a coluna onde o botão deve ser colocado. As linhas são calculadas como "i / max_columns" para determinar o número da linha, e as colunas como "i % max_columns" para determinar a posição dentro da linha. Usando esses valores, calculamos as posições X ("x_pos") e Y ("y_pos") dos botões na tela. Depois, chamamos a função "createButton" para criar dinamicamente cada botão, definindo seu rótulo com a moeda correspondente do array "curr_filter" e a cor como preta.

Após criar os botões, verificamos se o filtro está habilitado avaliando o valor de "enableCurrencyFilter". Se o filtro estiver ativo, limpamos o array de moedas selecionadas usando a função ArrayFree e copiamos o conteúdo de "curr_filter" para "curr_filter_selected" usando a função ArrayCopy. Isso copia todas as moedas para o array de selecionadas. Em seguida, exibimos “CURRENCY FILTER ENABLED” e mostramos o array de filtros selecionados usando a função ArrayPrint. Por fim, percorremos as moedas selecionadas no array "curr_filter_selected" e definimos o estado de cada botão correspondente como selecionado usando a função ObjectSetInteger, passando os parâmetros apropriados. Usamos a função IntegerToString para concatenar o índice da seleção e adicioná-lo à macro "CURRENCY_BTNS", definindo o estado como verdadeiro, o que marca visualmente o botão como clicado.

Após a compilação, obtemos os seguintes resultados:

RESULTADO FINAL DOS BOTÕES

Pela imagem, podemos ver que adicionamos com sucesso os botões de filtro ao painel. O que precisamos fazer agora é atualizar a função responsável por destruir o painel para que ela também considere os novos componentes adicionados.

//+------------------------------------------------------------------+
//|      Function to destroy the Dashboard panel                     |
//+------------------------------------------------------------------+

void destroy_Dashboard(){
   
   //--- Delete the main rectangle that defines the dashboard background
   ObjectDelete(0,"MAIN_REC");
   
   //--- Delete the sub-rectangles that separate sections in the dashboard
   ObjectDelete(0,"SUB_REC1");
   ObjectDelete(0,"SUB_REC2");
   
   //--- Delete the header label that displays the title of the dashboard
   ObjectDelete(0,"HEADER_LABEL");
   
   //--- Delete the time and impact labels from the dashboard
   ObjectDelete(0,"TIME_LABEL");
   ObjectDelete(0,"IMPACT_LABEL");

   //--- Delete all calendar-related objects
   ObjectsDeleteAll(0,"ARRAY_CALENDAR");
   
   //--- Delete all news-related objects
   ObjectsDeleteAll(0,"ARRAY_NEWS");

   //--- Delete all data holder objects (for storing data within the dashboard)
   ObjectsDeleteAll(0,"DATA_HOLDERS");
   
   //--- Delete the impact label objects (impact-related elements in the dashboard)
   ObjectsDeleteAll(0,"IMPACT_LABEL");

   //--- Delete the filter label that identifies the filter section
   ObjectDelete(0,"FILTER_LABEL");
   
   //--- Delete the filter buttons for Currency, Importance, and Time
   ObjectDelete(0,"FILTER_CURR_BTN");
   ObjectDelete(0,"FILTER_IMP_BTN");
   ObjectDelete(0,"FILTER_TIME_BTN");
   
   //--- Delete the cancel button that resets or closes the filters
   ObjectDelete(0,"CANCEL_BTN");
   
   //--- Delete all currency filter buttons dynamically created
   ObjectsDeleteAll(0,"CURRENCY_BTNS");
   
   //--- Redraw the chart to reflect the removal of all dashboard components
   ChartRedraw(0);
   
}

Também precisamos atualizar a lógica do manipulador de evento OnTick para que ele execute as atualizações apenas enquanto o sinalizador de atualização estiver verdadeiro. Conseguimos isso por meio da seguinte lógica.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
//---

   if (isDashboardUpdate){
      update_dashboard_values(curr_filter_selected);
   }   
}

Aqui, estamos verificando se a condição “isDashboardUpdate” é verdadeira, o que indica que o painel deve ser atualizado com os dados mais recentes. Se essa condição for atendida, chamamos a função “update_dashboard_values” para atualizar os valores exibidos no painel usando os filtros de moedas selecionados armazenados no array “curr_filter_selected”.

O array “curr_filter_selected” contém as moedas que foram escolhidas para filtragem, e ao passá-lo para a função “update_dashboard_values”, garantimos que o painel reflita as seleções de filtro mais atuais. Se a variável sinalizadora for falsa, nenhuma atualização será considerada. Isso é tudo o que precisamos considerar para criar os botões de filtro. Agora só precisamos prosseguir e adicionar responsividade aos botões criados, o que será tratado na próxima seção.


Automatização e Adição de Responsividade aos Botões

Para adicionar responsividade ao painel, precisaremos incluir um ouvinte de eventos que rastreie os cliques e tome ações com base neles. Para isso, usaremos o manipulador de eventos interno OnChartEvent do MQL5.

//+------------------------------------------------------------------+
//|    OnChartEvent handler function                                 |
//+------------------------------------------------------------------+
void  OnChartEvent(
   const int       id,       // event ID  
   const long&     lparam,   // long type event parameter 
   const double&   dparam,   // double type event parameter 
   const string&   sparam    // string type event parameter 
){

//---

}

Esta é a função responsável por reconhecer atividades no gráfico, como alterações, cliques, criações de objetos e muitas outras. No entanto, estamos interessados apenas nos cliques no gráfico, já que queremos ouvir apenas os cliques dos botões. Começaremos do mais simples ao mais complexo. O botão de cancelamento é o mais simples.

if (id == CHARTEVENT_OBJECT_CLICK){ //--- Check if the event is a click on an object
   
   if (sparam == CANCEL_BTN){ //--- If the Cancel button is clicked
      isDashboardUpdate = false; //--- Set dashboard update flag to false
      destroy_Dashboard(); //--- Call the function to destroy the dashboard
   }
}

Aqui, lidamos com o cenário em que o evento detectado é um clique em um objeto, detectado quando o ID do evento é CHARTEVENT_OBJECT_CLICK. Se o evento for um clique, verificamos se o objeto clicado é o “CANCEL_BTN”, avaliando se o parâmetro de string “sparam” é “CANCEL_BTN”. Se essa condição for atendida, isso significa que o botão Cancelar no painel foi clicado. Em resposta, definimos o sinalizador global “isDashboardUpdate” como falso, desativando efetivamente futuras atualizações do painel. Em seguida, chamamos a função “destroy_Dashboard” para remover todos os elementos gráficos associados ao painel do gráfico, limpando-o. Isso garante que a interface seja redefinida e limpa quando o botão Cancelar for clicado. Aqui está sua ilustração.

CANCELAR GIF

Isso foi um sucesso. Agora podemos aplicar a mesma lógica para automatizar os outros botões. Vamos automatizar agora o botão de filtro de moedas. Usamos a seguinte lógica em um trecho de código para isso.

if (sparam == FILTER_CURR_BTN){ //--- If the Currency filter button is clicked
   bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); //--- Get the button state (clicked/unclicked)
   enableCurrencyFilter = btn_state; //--- Update the Currency filter flag
   Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableCurrencyFilter); //--- Log the state
   string filter_curr_text = enableCurrencyFilter ? ShortToString(0x2714)+"Currency" : ShortToString(0x274C)+"Currency"; //--- Set button text based on state
   color filter_curr_txt_color = enableCurrencyFilter ? clrLime : clrRed; //--- Set button text color based on state
   ObjectSetString(0,FILTER_CURR_BTN,OBJPROP_TEXT,filter_curr_text); //--- Update the button text
   ObjectSetInteger(0,FILTER_CURR_BTN,OBJPROP_COLOR,filter_curr_txt_color); //--- Update the button text color
   Print("Success. Changes updated! State: "+(string)enableCurrencyFilter); //--- Log success
   ChartRedraw(0); //--- Redraw the chart to reflect changes
}

Aqui, tratamos o comportamento quando o botão “FILTER_CURR_BTN” (botão de filtro de moeda) é clicado. Quando essa condição é atendida, recuperamos o estado atual do botão (clicado ou não) usando a função ObjectGetInteger com a propriedade OBJPROP_STATE e armazenamos na variável “btn_state”. Em seguida, atualizamos o sinalizador “enableCurrencyFilter” com o valor de “btn_state” para refletir se o filtro de moeda está ativo.

Depois, registramos o estado do botão e o valor atualizado do sinalizador para fornecer feedback usando a função Print. Com base no estado do filtro de moeda, definimos dinamicamente o texto do botão usando um símbolo de verificação ou cruz com a função ShortToString e atualizamos sua cor para verde quando ativo ou vermelho quando inativo. Essas atualizações são aplicadas ao botão usando ObjectSetString para o texto e ObjectSetInteger para a propriedade de cor.

Por fim, registramos uma mensagem de sucesso para confirmar que as alterações foram aplicadas e chamamos a função ChartRedraw para atualizar o gráfico, garantindo que as mudanças visuais do botão sejam exibidas imediatamente. Essa interação nos permite alternar dinamicamente o filtro de moedas e refletir as alterações no painel. O mesmo processo se aplica aos outros botões de filtro.

if (sparam == FILTER_IMP_BTN){ //--- If the Importance filter button is clicked
   bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); //--- Get the button state
   enableImportanceFilter = btn_state; //--- Update the Importance filter flag
   Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableImportanceFilter); //--- Log the state
   string filter_imp_text = enableImportanceFilter ? ShortToString(0x2714)+"Importance" : ShortToString(0x274C)+"Importance"; //--- Set button text
   color filter_imp_txt_color = enableImportanceFilter ? clrLime : clrRed; //--- Set button text color
   ObjectSetString(0,FILTER_IMP_BTN,OBJPROP_TEXT,filter_imp_text); //--- Update the button text
   ObjectSetInteger(0,FILTER_IMP_BTN,OBJPROP_COLOR,filter_imp_txt_color); //--- Update the button text color
   Print("Success. Changes updated! State: "+(string)enableImportanceFilter); //--- Log success
   ChartRedraw(0); //--- Redraw the chart
}
if (sparam == FILTER_TIME_BTN){ //--- If the Time filter button is clicked
   bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); //--- Get the button state
   enableTimeFilter = btn_state; //--- Update the Time filter flag
   Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableTimeFilter); //--- Log the state
   string filter_time_text = enableTimeFilter ? ShortToString(0x2714)+"Time" : ShortToString(0x274C)+"Time"; //--- Set button text
   color filter_time_txt_color = enableTimeFilter ? clrLime : clrRed; //--- Set button text color
   ObjectSetString(0,FILTER_TIME_BTN,OBJPROP_TEXT,filter_time_text); //--- Update the button text
   ObjectSetInteger(0,FILTER_TIME_BTN,OBJPROP_COLOR,filter_time_txt_color); //--- Update the button text color
   Print("Success. Changes updated! State: "+(string)enableTimeFilter); //--- Log success
   ChartRedraw(0); //--- Redraw the chart
}

Aqui, definimos o comportamento para lidar com cliques nos botões “FILTER_IMP_BTN” (botão de filtro de importância) e “FILTER_TIME_BTN” (botão de filtro de tempo), permitindo alternância dinâmica desses filtros. Para o “FILTER_IMP_BTN”, ao ser clicado, primeiro recuperamos seu estado atual usando ObjectGetInteger com a propriedade OBJPROP_STATE e armazenamos na variável “btn_state”. Em seguida, atualizamos o sinalizador “enableImportanceFilter” para refletir se o filtro de importância está ativo. Usando a função Print, registramos o estado do botão e o valor atualizado do sinalizador. Dependendo do estado, definimos o texto do botão como uma marca de seleção ou cruz via a função ShortToString e atualizamos sua cor para verde quando ativo ou vermelho quando inativo. Essas mudanças são aplicadas usando ObjectSetString para o texto e ObjectSetInteger para as funções de propriedade de cor. Por fim, registramos uma mensagem de sucesso e chamamos a função ChartRedraw para garantir que as atualizações sejam aplicadas visualmente.

Da mesma forma, para o “FILTER_TIME_BTN”, seguimos o mesmo processo. Recuperamos o estado do botão usando a função ObjectGetInteger e atualizamos o sinalizador “enableTimeFilter” de acordo. Registramos o estado e as atualizações do sinalizador para feedback. O texto e a cor do botão são atualizados dinamicamente para refletir o estado (ativo/inativo), usando as funções ShortToString, ObjectSetString e ObjectSetInteger, respectivamente. Após confirmar as atualizações com um log, redesenhamos o gráfico com a função ChartRedraw. Esse processo garante a responsividade em tempo real dos botões de filtro, proporcionando uma experiência contínua ao alternar suas funcionalidades. Aqui está o resultado visual.

GIF DE BOTÕES DE FILTRO

Isso foi um sucesso. Agora podemos prosseguir para automatizar também os botões de moeda.

if (StringFind(sparam,CURRENCY_BTNS) >= 0){ //--- If a Currency button is clicked
   string selected_curr = ObjectGetString(0,sparam,OBJPROP_TEXT); //--- Get the text of the clicked button
   Print("BTN NAME = ",sparam,", CURRENCY = ",selected_curr); //--- Log the button name and currency
   
   bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); //--- Get the button state
   
   if (btn_state == false){ //--- If the button is unselected
      Print("BUTTON IS IN UN-SELECTED MODE.");
      //--- Loop to find and remove the currency from the array
      for (int i = 0; i < ArraySize(curr_filter_selected); i++) {
         if (curr_filter_selected[i] == selected_curr) {
            //--- Shift elements to remove the selected currency
            for (int j = i; j < ArraySize(curr_filter_selected) - 1; j++) {
               curr_filter_selected[j] = curr_filter_selected[j + 1];
            }
            ArrayResize(curr_filter_selected, ArraySize(curr_filter_selected) - 1); //--- Resize the array
            Print("Removed from selected filters: ", selected_curr); //--- Log removal
            break;
         }
      }
   }
   else if (btn_state == true){ //--- If the button is selected
      Print("BUTTON IS IN SELECTED MODE. TAKE ACTION");
      //--- Check for duplicates
      bool already_selected = false;
      for (int j = 0; j < ArraySize(curr_filter_selected); j++) {
         if (curr_filter_selected[j] == selected_curr) {
            already_selected = true;
            break;
         }
      }

      //--- If not already selected, add to the array
      if (!already_selected) {
         ArrayResize(curr_filter_selected, ArraySize(curr_filter_selected) + 1); //--- Resize array
         curr_filter_selected[ArraySize(curr_filter_selected) - 1] = selected_curr; //--- Add the new currency
         Print("Added to selected filters: ", selected_curr); //--- Log addition
      }
      else {
         Print("Currency already selected: ", selected_curr); //--- Log already selected
      }
      
   }
   Print("SELECTED ARRAY SIZE = ",ArraySize(curr_filter_selected)); //--- Log the size of the selected array
   ArrayPrint(curr_filter_selected); //--- Print the selected array
   
   update_dashboard_values(curr_filter_selected); //--- Update the dashboard with the selected filters
   Print("SUCCESS. DASHBOARD UPDATED"); //--- Log success
   
   ChartRedraw(0); //--- Redraw the chart to reflect changes
}

Aqui, lidamos com cliques nos botões de filtro de moeda para gerenciar dinamicamente as moedas selecionadas e atualizar o painel de acordo. Quando um botão é clicado, primeiro determinamos se ele pertence ao grupo “CURRENCY_BTNS” usando a função StringFind. Se verdadeiro, recuperamos o rótulo de texto do botão usando a função ObjectGetString com a propriedade “OBJPROP_TEXT” para identificar a moeda que ele representa. Em seguida, verificamos o estado do botão usando ObjectGetInteger com a propriedade “OBJPROP_STATE” para determinar se o botão está selecionado (true) ou não (false).

Se o botão estiver em estado não selecionado, removemos a moeda correspondente do array “curr_filter_selected”. Para isso, percorremos o array para localizar a moeda correspondente, deslocamos todos os elementos subsequentes para a esquerda para sobrescrevê-la e, em seguida, redimensionamos o array usando a função ArrayResize para remover a última posição. Cada remoção é registrada para confirmar a ação. Por outro lado, se o botão estiver em estado selecionado, verificamos duplicatas para evitar adicionar a mesma moeda várias vezes. Se a moeda ainda não estiver no array, redimensionamos o array usando a função “ArrayResize”, adicionamos a nova moeda na última posição e registramos a adição. Se a moeda já estiver selecionada, uma mensagem é registrada indicando que nenhuma ação adicional é necessária.

Após atualizar o array “curr_filter_selected”, registramos seu tamanho e conteúdo usando a função ArrayPrint para visibilidade. Em seguida, chamamos a função “update_dashboard_values” para atualizar o painel com os filtros recém-selecionados. Para garantir que todas as mudanças sejam refletidas visualmente, concluímos chamando a função ChartRedraw e atualizando a interface do gráfico em tempo real.

Como agora temos diferentes filtros de moeda após a interação do usuário, precisamos atualizar a função responsável pelas atualizações com as moedas mais recentes selecionadas pelo usuário.

//+------------------------------------------------------------------+
//| Function to update dashboard values                              |
//+------------------------------------------------------------------+
void update_dashboard_values(string &curr_filter_array[]){

//---

      //--- Check if the event’s currency matches any in the filter array (if the filter is enabled)
      bool currencyMatch = false;
      if (enableCurrencyFilter) {
         for (int j = 0; j < ArraySize(curr_filter_array); j++) {
            if (country.currency == curr_filter_array[j]) {
               currencyMatch = true;
               break;
            }
         }
         
         //--- If no match found, skip to the next event
         if (!currencyMatch) {
            continue;
         }
      }

//---

}

Aqui, atualizamos a função “update_dashboard_values” introduzindo uma melhoria crucial ao utilizar o parâmetro “curr_filter_array”, passado por referência usando o símbolo “&”. Esse design nos permite manipular e trabalhar diretamente com o array de filtros de moeda selecionados, garantindo que o painel permaneça sincronizado com as preferências do usuário.

Aqui está o que queremos dizer com “Passagem por Referência (&)”. O parâmetro “curr_filter_array” é passado por referência para a função. Isso significa que a função acessa o array real na memória, e não uma cópia. As alterações no array dentro da função (se houver) afetam diretamente o array original fora da função. Essa abordagem melhora a eficiência, especialmente para arrays maiores, e mantém a consistência com as seleções de filtro atuais do usuário. Mais adiante, em vez de usar o array inicial, substituímos pelo mais recente, passado por referência e contendo as preferências do usuário. Destacamos as alterações em amarelo para maior clareza. Na compilação, obtemos o seguinte resultado.

GIF DE BOTÕES DE MOEDA

Pela visualização, podemos ver que o painel é atualizado a cada vez que clicamos em qualquer um dos botões de moeda, o que é um sucesso. No entanto, atualmente, há uma dependência do botão de filtro de moeda em relação às moedas, pois após clicarmos em um único filtro de moeda, o painel não é atualizado. Para resolver isso, basta chamarmos a função de atualização em cada botão de filtro, conforme abaixo.

if (sparam == FILTER_CURR_BTN){ //--- If the Currency filter button is clicked
   bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); //--- Get the button state (clicked/unclicked)
   enableCurrencyFilter = btn_state; //--- Update the Currency filter flag
   Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableCurrencyFilter); //--- Log the state
   string filter_curr_text = enableCurrencyFilter ? ShortToString(0x2714)+"Currency" : ShortToString(0x274C)+"Currency"; //--- Set button text based on state
   color filter_curr_txt_color = enableCurrencyFilter ? clrLime : clrRed; //--- Set button text color based on state
   ObjectSetString(0,FILTER_CURR_BTN,OBJPROP_TEXT,filter_curr_text); //--- Update the button text
   ObjectSetInteger(0,FILTER_CURR_BTN,OBJPROP_COLOR,filter_curr_txt_color); //--- Update the button text color
   update_dashboard_values(curr_filter_selected);
   Print("Success. Changes updated! State: "+(string)enableCurrencyFilter); //--- Log success
   ChartRedraw(0); //--- Redraw the chart to reflect changes
}
if (sparam == FILTER_IMP_BTN){ //--- If the Importance filter button is clicked
   bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); //--- Get the button state
   enableImportanceFilter = btn_state; //--- Update the Importance filter flag
   Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableImportanceFilter); //--- Log the state
   string filter_imp_text = enableImportanceFilter ? ShortToString(0x2714)+"Importance" : ShortToString(0x274C)+"Importance"; //--- Set button text
   color filter_imp_txt_color = enableImportanceFilter ? clrLime : clrRed; //--- Set button text color
   ObjectSetString(0,FILTER_IMP_BTN,OBJPROP_TEXT,filter_imp_text); //--- Update the button text
   ObjectSetInteger(0,FILTER_IMP_BTN,OBJPROP_COLOR,filter_imp_txt_color); //--- Update the button text color
   update_dashboard_values(curr_filter_selected);
   Print("Success. Changes updated! State: "+(string)enableImportanceFilter); //--- Log success
   ChartRedraw(0); //--- Redraw the chart
}
if (sparam == FILTER_TIME_BTN){ //--- If the Time filter button is clicked
   bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); //--- Get the button state
   enableTimeFilter = btn_state; //--- Update the Time filter flag
   Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableTimeFilter); //--- Log the state
   string filter_time_text = enableTimeFilter ? ShortToString(0x2714)+"Time" : ShortToString(0x274C)+"Time"; //--- Set button text
   color filter_time_txt_color = enableTimeFilter ? clrLime : clrRed; //--- Set button text color
   ObjectSetString(0,FILTER_TIME_BTN,OBJPROP_TEXT,filter_time_text); //--- Update the button text
   ObjectSetInteger(0,FILTER_TIME_BTN,OBJPROP_COLOR,filter_time_txt_color); //--- Update the button text color
   update_dashboard_values(curr_filter_selected);
   Print("Success. Changes updated! State: "+(string)enableTimeFilter); //--- Log success
   ChartRedraw(0); //--- Redraw the chart
}

Aqui, apenas garantimos que a função de atualização seja chamada para aumentar a independência dos eventos do painel. Destacamos as alterações em amarelo para maior clareza. Assim, com essas mudanças, os botões passam a ter independência. Aqui está uma visualização rápida.

BOTÕES INDEPENDÊNCIA GIF

Até este ponto, conseguimos integrar os filtros de moeda. Usando o mesmo procedimento, podemos seguir adiante e integrar também os filtros de importância. Isso será um pouco mais complexo, pois lidaremos com os botões existentes para adicionar o efeito de filtro e exigirá que tenhamos dois arrays para os níveis de importância: um principal e outro auxiliar (de strings), que usaremos para fazer as comparações. Primeiro, traremos todos os arrays de importância para o escopo global, para que possamos acessá-los de qualquer parte do código.

//--- Define labels for impact levels and size of impact display areas
string impact_labels[] = {"None","Low","Medium","High"};
string impact_filter_selected[];

// Define the levels of importance to filter (low, moderate, high)
ENUM_CALENDAR_EVENT_IMPORTANCE allowed_importance_levels[] = {CALENDAR_IMPORTANCE_NONE,CALENDAR_IMPORTANCE_LOW, CALENDAR_IMPORTANCE_MODERATE, CALENDAR_IMPORTANCE_HIGH};
ENUM_CALENDAR_EVENT_IMPORTANCE imp_filter_selected[];

Aqui, definimos o array de strings "impact_labels", que contém os diferentes níveis de impacto disponíveis para seleção pelo usuário. Os rótulos são "None" (Nenhum), "Low" (Baixo), "Medium" (Médio) e "High" (Alto). Usamos esse array para apresentar essas opções ao usuário, a fim de filtrar eventos do calendário com base em seu impacto percebido.

Em seguida, introduzimos o array "impact_filter_selected", que armazenará os rótulos efetivamente selecionados pelo usuário a partir do array "impact_labels". Sempre que o usuário interagir com a interface e escolher um nível de impacto, adicionamos o rótulo correspondente a esse array. Ele está em formato de string para que possamos interpretar facilmente os níveis selecionados, além da lista enumerada. Isso nos permite rastrear dinamicamente as preferências de filtro do usuário.

Em seguida, definimos o array "allowed_importance_levels", que contém os valores enumerados do tipo ENUM_CALENDAR_EVENT_IMPORTANCE. Esses valores enumerados estão associados aos níveis de impacto: "CALENDAR_IMPORTANCE_NONE", "CALENDAR_IMPORTANCE_LOW", "CALENDAR_IMPORTANCE_MODERATE" e "CALENDAR_IMPORTANCE_HIGH". Já os havíamos definido, apenas os movemos para o escopo global. Esses valores serão usados para filtrar eventos do calendário com base em sua importância.

Também definimos o array "imp_filter_selected", onde armazenamos os níveis de importância correspondentes aos rótulos de impacto selecionados pelo usuário. À medida que o usuário seleciona rótulos do "impact_labels", associamos cada rótulo ao nível de importância correspondente de "allowed_importance_levels" e armazenamos o resultado em "imp_filter_selected". Esse array será usado para filtrar eventos do calendário, garantindo que apenas os eventos com os níveis de importância selecionados sejam exibidos.

Agora, passando para a função de inicialização, atualizaremos os botões para maior clareza, pois agora os estamos usando não apenas como orientação sobre os resultados dos eventos, mas também para o processo de filtragem. Assim, queremos mostrar seu estado ativo ou inativo quando forem clicados.

if (enableImportanceFilter == true) { 
   ArrayFree(imp_filter_selected); //--- Clear the existing selections in the importance filter array
   ArrayCopy(imp_filter_selected, allowed_importance_levels); //--- Copy all allowed importance levels as default selections
   ArrayFree(impact_filter_selected);
   ArrayCopy(impact_filter_selected, impact_labels);
   Print("IMPORTANCE FILTER ENABLED"); //--- Log that importance filter is enabled
   ArrayPrint(imp_filter_selected); //--- Print the current selection of importance levels
   ArrayPrint(impact_filter_selected);
   
   // Loop through the importance levels and set their buttons to "selected" state
   for (int i = 0; i < ArraySize(imp_filter_selected); i++) {
      string btn_name = IMPACT_LABEL+IntegerToString(i); //--- Dynamically name the button for each importance level
      ObjectSetInteger(0, btn_name, OBJPROP_STATE, true); //--- Set the button state to "clicked" (selected)
      ObjectSetInteger(0, btn_name, OBJPROP_BORDER_COLOR, clrNONE); //--- Set the button state to "clicked" (selected)
   }
}

Aqui, primeiro verificamos se a variável "enableImportanceFilter" está definida como verdadeira. Se estiver, prosseguimos para configurar o sistema de filtro de importância. Começamos limpando as seleções existentes no array "imp_filter_selected" usando a função ArrayFree. Em seguida, copiamos todos os valores do array "allowed_importance_levels" para "imp_filter_selected", definindo, essencialmente, todos os níveis de importância como seleções padrão. Isso significa que, por padrão, todos os níveis de importância estão inicialmente selecionados para filtragem.

Depois, limpamos o array "impact_filter_selected" com a função <a0>ArrayFree</a0>, garantindo que quaisquer seleções anteriores no array de rótulos de impacto sejam removidas. Seguimos copiando todos os valores do array "impact_labels" para "impact_filter_selected". Isso garante que os rótulos representando os níveis de impacto ("None", "Low", "Medium" e "High") estejam disponíveis para o filtro. Após configurar os arrays, imprimimos uma mensagem de log: "IMPORTANCE FILTER ENABLED", para confirmar que o filtro de importância está ativo. Também imprimimos o conteúdo dos arrays "imp_filter_selected" e "impact_filter_selected" para exibir as seleções atuais.

Por fim, percorremos o array "imp_filter_selected", que contém os níveis de importância atualmente selecionados, e configuramos dinamicamente o estado dos botões para cada nível correspondente. Para cada nível de importância, criamos um nome de botão dinamicamente usando "IMPACT_LABEL" e o índice do nível atual, por meio da função IntegerToString. Em seguida, definimos o estado do botão como "true" (selecionado) usando a função ObjectSetInteger. Além disso, removemos qualquer cor de borda definindo a propriedade OBJPROP_BORDER_COLOR como nenhuma, para destacar visualmente que o botão está selecionado. Isso é tudo o que precisamos para a inicialização. Agora passamos à função de escuta de eventos, onde rastreamos os cliques nos botões de importância e agimos de acordo. Aqui, usamos uma lógica semelhante à dos botões de filtro de moeda.

if (StringFind(sparam, IMPACT_LABEL) >= 0) { //--- If an Importance button is clicked
   string selected_imp = ObjectGetString(0, sparam, OBJPROP_TEXT); //--- Get the importance level of the clicked button
   ENUM_CALENDAR_EVENT_IMPORTANCE selected_importance_lvl = get_importance_level(impact_labels,allowed_importance_levels,selected_imp);
   Print("BTN NAME = ", sparam, ", IMPORTANCE LEVEL = ", selected_imp,"(",selected_importance_lvl,")"); //--- Log the button name and importance level

   bool btn_state = ObjectGetInteger(0, sparam, OBJPROP_STATE); //--- Get the button state
   
   color color_border = btn_state ? clrNONE : clrBlack;
   
   if (btn_state == false) { //--- If the button is unselected
      Print("BUTTON IS IN UN-SELECTED MODE.");
      //--- Loop to find and remove the importance level from the array
      for (int i = 0; i < ArraySize(imp_filter_selected); i++) {
         if (impact_filter_selected[i] == selected_imp) {
            //--- Shift elements to remove the unselected importance level
            for (int j = i; j < ArraySize(imp_filter_selected) - 1; j++) {
               imp_filter_selected[j] = imp_filter_selected[j + 1];
               impact_filter_selected[j] = impact_filter_selected[j + 1];
            }
            ArrayResize(imp_filter_selected, ArraySize(imp_filter_selected) - 1); //--- Resize the array
            ArrayResize(impact_filter_selected, ArraySize(impact_filter_selected) - 1); //--- Resize the array
            Print("Removed from selected importance filters: ", selected_imp,"(",selected_importance_lvl,")"); //--- Log removal
            break;
         }
      }
   } 
   else if (btn_state == true) { //--- If the button is selected
      Print("BUTTON IS IN SELECTED MODE. TAKE ACTION");
      //--- Check for duplicates
      bool already_selected = false;
      for (int j = 0; j < ArraySize(imp_filter_selected); j++) {
         if (impact_filter_selected[j] == selected_imp) {
            already_selected = true;
            break;
         }
      }

      //--- If not already selected, add to the array
      if (!already_selected) {
         ArrayResize(imp_filter_selected, ArraySize(imp_filter_selected) + 1); //--- Resize the array
         imp_filter_selected[ArraySize(imp_filter_selected) - 1] = selected_importance_lvl; //--- Add the new importance level
         
         ArrayResize(impact_filter_selected, ArraySize(impact_filter_selected) + 1); //--- Resize the array
         impact_filter_selected[ArraySize(impact_filter_selected) - 1] = selected_imp; //--- Add the new importance level
         Print("Added to selected importance filters: ", selected_imp,"(",selected_importance_lvl,")"); //--- Log addition
      } 
      else {
         Print("Importance level already selected: ", selected_imp,"(",selected_importance_lvl,")"); //--- Log already selected
      }
   }
   Print("SELECTED ARRAY SIZE = ", ArraySize(imp_filter_selected)," >< ",ArraySize(impact_filter_selected)); //--- Log the size of the selected array
   ArrayPrint(imp_filter_selected); //--- Print the selected array
   ArrayPrint(impact_filter_selected);
   
   update_dashboard_values(curr_filter_selected,imp_filter_selected); //--- Update the dashboard with the selected filters
   
   ObjectSetInteger(0,sparam,OBJPROP_BORDER_COLOR,color_border);
   Print("SUCCESS. DASHBOARD UPDATED"); //--- Log success

   ChartRedraw(0); //--- Redraw the chart to reflect changes
}

Aqui, primeiro identificamos se um botão de filtro de importância foi clicado, usando a função StringFind. Essa função verifica se o nome do botão clicado (representado por "sparam") contém a string "IMPACT_LABEL". Se contiver, prosseguimos para recuperar o nível de importância associado ao botão, chamando a função ObjectGetString e passando a propriedade de texto, que obtém o texto (como "Low", "Medium" etc.) do botão clicado. Em seguida, convertemos esse texto em seu valor de enumeração correspondente (como CALENDAR_IMPORTANCE_LOW) passando o rótulo selecionado para a função "get_importance_level(impact_labels, allowed_importance_levels, selected_imp)". Essa função recebe o array de rótulos, os valores enumerados permitidos e o rótulo de texto selecionado para retornar o nível de importância apropriado como enum. Veremos essa função personalizada mais adiante.

Em seguida, verificamos o estado do botão usando a função ObjectGetInteger e passando a propriedade de estado, que determina se o botão está em um estado selecionado (true) ou não selecionado (false). Com base nesse estado, adicionamos ou removemos o nível de importância selecionado dos arrays de filtro. Se o botão não estiver selecionado, percorremos o array "imp_filter_selected" e removemos o nível de importância, redimensionando o array com a função ArrayResize. Fazemos o mesmo para o array "impact_filter_selected" para garantir que ambos permaneçam sincronizados. Se o botão estiver selecionado, primeiro verificamos se o nível de importância já está presente nos arrays de filtro usando um loop. Se não estiver, o adicionamos a ambos os arrays, redimensionando-os e anexando o novo valor.

Depois que os arrays são atualizados, usamos a função ArrayPrint para registrar o conteúdo atual dos arrays de filtro para depuração. Em seguida, atualizamos o painel com as novas seleções de filtro chamando "update_dashboard_values(curr_filter_selected, imp_filter_selected)", o que reflete as alterações nos arrays de filtro no painel. Atualizamos a função para receber agora os dois parâmetros de array, conforme as preferências do usuário. Veremos isso mais adiante. Por fim, a aparência do botão clicado é atualizada definindo sua cor de borda com a cor calculada, onde a cor é determinada com base em o botão estar selecionado ou não. Em seguida, redesenhamos o gráfico usando a função ChartRedraw para refletir visualmente as alterações.

Agora, vejamos a função personalizada que cuida de obter a enumeração correspondente ao nível de importância.

//+------------------------------------------------------------------+
//| Function to get the importance level based on the selected label |
//+------------------------------------------------------------------+
ENUM_CALENDAR_EVENT_IMPORTANCE get_importance_level(string &impact_label[], ENUM_CALENDAR_EVENT_IMPORTANCE &importance_levels[], string selected_label) {
    // Loop through the impact_labels array to find the matching label
    for (int i = 0; i < ArraySize(impact_label); i++) {
        if (impact_label[i] == selected_label) {
            // Return the corresponding importance level
            return importance_levels[i];
        }
    }
    
    // If no match found, return CALENDAR_IMPORTANCE_NONE as the default
    return CALENDAR_IMPORTANCE_NONE;
}

Aqui, definimos uma função de enumeração chamada "get_importance_level", usada para determinar o nível de importância com base no rótulo selecionado. A função recebe três parâmetros:

  • "impact_label": Este é um array de strings que contém os diferentes rótulos para os níveis de impacto (como "None", "Low", "Medium" e "High").
  • "importance_levels": Este array contém os níveis de importância correspondentes como valores enumerados (como CALENDAR_IMPORTANCE_NONE, CALENDAR_IMPORTANCE_LOW, etc.).
  • "selected_label": Este é o rótulo (uma string) passado para a função que representa o nível de importância selecionado pelo usuário (por exemplo, "Medium").

Dentro da função, percorremos o array "impact_label" usando um for loop. A cada iteração, verificamos se o item atual do array corresponde ao "selected_label". Se uma correspondência for encontrada, retornamos o nível de importância correspondente do array "importance_levels" no mesmo índice. Se nenhuma correspondência for encontrada após verificar todos os rótulos, a função retorna, por padrão, CALENDAR_IMPORTANCE_NONE. Precisamos dessa função para nos ajudar a converter uma string que representa o nível de impacto selecionado (como "Medium") em seu respectivo nível de importância (como "CALENDAR_IMPORTANCE_MODERATE").

As outras alterações que fizemos foram passar os novos dados do array filtrado de importância para a função de atualização como referência, para que os filtros tenham efeito dinâmico conforme as preferências atualizadas do usuário. A declaração da função agora se assemelha à mostrada no trecho de código abaixo.

//+------------------------------------------------------------------+
//| Function to update dashboard values                              |
//+------------------------------------------------------------------+
void update_dashboard_values(string &curr_filter_array[], ENUM_CALENDAR_EVENT_IMPORTANCE &imp_filter_array[]){

//---      

}

Após a atualização da função, também precisamos atualizar as funções semelhantes existentes para incluir o array de filtro de importância. Por exemplo, a chamada da função manipuladora do evento "OnInit" ficará semelhante ao mostrado abaixo.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
//---

   if (isDashboardUpdate){
      update_dashboard_values(curr_filter_selected,imp_filter_selected);
   }
   
}

Isso é tudo o que precisamos para incorporar tanto os filtros de moeda quanto os de importância. Vamos dar uma olhada no marco atual para confirmar que os filtros de importância também estão funcionando conforme o esperado.

IMPORTANCE FILTER GIF

Pela visualização, podemos ver que alcançamos nosso objetivo de adicionar a responsividade do filtro de importância. Por fim, uma coisa que podemos atualizar é o total de eventos exibidos. Podemos ter o número de eventos filtrados, o total possível de eventos que podem ser exibidos no gráfico e o total de eventos considerados. Aqui está a lógica que podemos usar para alcançar isso. No escopo global, podemos definir algumas variáveis de controle.

int totalEvents_Considered = 0;
int totalEvents_Filtered = 0;
int totalEvents_Displayable = 0;

Aqui, definimos três variáveis inteiras: "totalEvents_Considered", "totalEvents_Filtered" e "totalEvents_Displayable". Essas variáveis servirão como contadores para rastrear o status dos eventos durante o processamento:

  • "totalEvents_Considered": Essa variável acompanhará o número total de eventos inicialmente considerados durante a etapa de processamento. Ela representa o ponto de partida, onde todos os eventos são levados em conta antes de qualquer filtragem ser aplicada.
  • "totalEvents_Filtered": Essa variável contará o número total de eventos que são excluídos ou filtrados com base nas condições aplicadas, como moeda, importância ou filtros de tempo. Ela indica quantos eventos foram removidos do conjunto de dados.
  • "totalEvents_Displayable": Essa variável acompanhará o número total de eventos que permanecem após a filtragem e são elegíveis para exibição no painel. Ela representa o conjunto final de eventos que atendem a todos os critérios de filtragem e podem ser exibidos.

Usando esses contadores, podemos monitorar e analisar o fluxo de processamento de eventos, garantindo que a lógica de filtragem funcione conforme o esperado e fornecendo uma visão geral do fluxo de dados. Em seguida, antes que qualquer ação de filtragem de dados ocorra, definimos todos como 0.

totalEvents_Displayable = 0;
totalEvents_Filtered = 0;
totalEvents_Considered = 0;

Antes do primeiro loop, substituímos os valores anteriores pelos mais recentes para considerar todos os eventos. Aqui está um exemplo.

//--- Loop through each calendar value up to the maximum defined total
for (int i = 0; i < allValues; i++){

//---

}

Em vez de aplicar as condições de restrição antecipadamente, podemos ver que ainda consideramos todos os eventos. Isso ajudará a nos mostrar os dados de transbordamento. Assim, dentro do loop, teremos a seguinte lógica de atualização.

   //--- Loop through each calendar value up to the maximum defined total
   for (int i = 0; i < allValues; i++){
   
      MqlCalendarEvent event; //--- Declare event structure
      CalendarEventById(values[i].event_id,event); //--- Retrieve event details by ID
   
      //--- Other declarations
      
      totalEvents_Considered++;
      
      //--- Check if the event’s currency matches any in the filter array (if the filter is enabled)
      bool currencyMatch = false;
      if (enableCurrencyFilter) {
         for (int j = 0; j < ArraySize(curr_filter); j++) {
            if (country.currency == curr_filter[j]) {
               currencyMatch = true;
               break;
            }
         }
         
         //--- If no match found, skip to the next event
         if (!currencyMatch) {
            continue;
         }
      }
      
      //--- Other filters

      //--- If we reach here, the filters passed
      totalEvents_Filtered++;
      
      //--- Restrict the number of displayable events to a maximum of 11
      if (totalEvents_Displayable >= 11) {
        continue; // Skip further processing if display limit is reached
      }
      
      //--- Increment total displayable events
      totalEvents_Displayable++;
      
      //--- Set alternating colors for data holders
      color holder_color = (totalEvents_Displayable % 2 == 0) ? C'213,227,207' : clrWhite;
      
      //--- Create rectangle label for the data holder
      createRecLabel(DATA_HOLDERS + string(totalEvents_Displayable), 62, startY - 1, 716, 26 + 1, holder_color, 1, clrNONE);

      //--- Initialize starting x-coordinate for each data entry
      int startX = 65;
      
      //--- Loop through calendar data columns
      for (int k=0; k<ArraySize(array_calendar); k++){
         
         //--- Prepare news data array with time, country, and other event details
         string news_data[ArraySize(array_calendar)];
         news_data[0] = TimeToString(values[i].time,TIME_DATE); //--- Event date
         news_data[1] = TimeToString(values[i].time,TIME_MINUTES); //--- Event time
         news_data[2] = country.currency; //--- Event country currency
      
         //--- Other fills and creations
      }
      
      ArrayResize(current_eventNames_data,ArraySize(current_eventNames_data)+1);
      current_eventNames_data[ArraySize(current_eventNames_data)-1] = event.name;
      
      //--- Increment y-coordinate for the next row of data
      startY += 25;
      
   }
   Print("CURRENT EVENT NAMES DATA SIZE = ",ArraySize(current_eventNames_data));
   //--- Other logs
      
   updateLabel(TIME_LABEL,"Server Time: "+TimeToString(TimeCurrent(),
              TIME_DATE|TIME_SECONDS)+"   |||   Total News: "+
              IntegerToString(totalEvents_Displayable)+"/"+IntegerToString(totalEvents_Filtered)+"/"+IntegerToString(totalEvents_Considered));
//---

Aqui, apenas atualizamos os armazenadores de número de evento conforme necessário. Um ponto crucial a observar é o trecho de código que destacamos em azul-claro. Sua lógica funciona da mesma forma que os filtros — quando o limite total de exibição é atingido, pulamos o processamento. Por fim, atualizamos o rótulo para conter todas as 3 contagens de eventos, o que nos ajuda a saber o número de transbordamentos de dados a longo prazo. Em seguida, aplicamos a mesma lógica à função de atualizações para garantir que ela também se sincronize em tempo real. Ao executar o sistema, temos o seguinte resultado.

RESULTADO DE EXIBIÇÃO

A partir da imagem, podemos ver que agora temos 3 contagens de notícias. O primeiro número, 11 neste caso, mostra o número total de notícias exibíveis; o segundo, 24, mostra o número total de eventos filtrados — apenas não podemos exibir todos no painel; e o terceiro, 539, mostra o total de notícias consideradas para processamento. Com tudo isso, para habilitar o filtro de tempo, podemos ter os intervalos de tempo desejados em formatos de entrada, para que possamos defini-los ao inicializar o programa. Aqui está a lógica para alcançar isso.

sinput group "General Calendar Settings"
input ENUM_TIMEFRAMES start_time = PERIOD_H12;
input ENUM_TIMEFRAMES end_time = PERIOD_H12;
input ENUM_TIMEFRAMES range_time = PERIOD_H8;

Aqui, definimos um grupo chamado “Configurações Gerais de Calendário” para fornecer opções configuráveis para o gerenciamento de funcionalidades relacionadas ao calendário. Usamos três entradas do tipo ENUM_TIMEFRAMES para controlar os parâmetros de tempo dentro dos quais os eventos do calendário são filtrados ou analisados. Primeiro, definimos “start_time”, que especifica o início do intervalo de tempo dos eventos do calendário, com padrão de 12 horas ("PERIOD_H12").

Em seguida, introduzimos “end_time”, que marca o final desse intervalo de tempo, também definido como “PERIOD_H12” por padrão. Por fim, usamos “range_time” para definir a duração ou o intervalo de interesse para filtragem ou cálculos do calendário, com valor padrão de 8 horas ("PERIOD_H8"). Ao fazer isso, garantimos que o programa opere de forma flexível com base nos períodos de tempo definidos pelo usuário, permitindo adaptar os dados do calendário a intervalos específicos de interesse. Essas configurações permitem filtragem dinâmica e fornecem controle ao usuário sobre o escopo temporal dos eventos exibidos ou analisados.

Para efetivar as alterações, adicionamos esses parâmetros às entradas de controle de suas respectivas funções e lógicas, conforme mostrado a seguir.

//--- Define start and end time for calendar event retrieval
datetime startTime = TimeTradeServer() - PeriodSeconds(start_time);
datetime endTime = TimeTradeServer() + PeriodSeconds(end_time);

//--- Define time range for filtering news events based on daily period
datetime timeRange = PeriodSeconds(range_time);
datetime timeBefore = TimeTradeServer() - timeRange;
datetime timeAfter = TimeTradeServer() + timeRange;

Isso é tudo. Após a compilação, temos a seguinte saída.

RESULTADO DAS ENTRADAS

A partir da imagem, podemos ver que agora conseguimos acessar os parâmetros de entrada e escolher as configurações de tempo a partir da lista suspensa habilitada. Até este ponto, nosso painel está totalmente funcional e responsivo. Só precisamos testá-lo em diferentes condições e ambientes para garantir que funcione muito bem sem falhas — e, caso alguma seja encontrada, corrigi-la. Isso será feito na próxima seção.


Testando o Painel Aprimorado

Nesta seção, realizamos testes no painel aprimorado que desenvolvemos. O objetivo é garantir que todos os filtros, rastreamento de eventos e mecanismos de exibição de dados funcionem conforme o esperado. Implementamos uma série de filtros, incluindo moeda, importância e tempo, que nos permitem visualizar e analisar eventos do calendário de forma mais eficaz.

O processo de teste envolve simular interações do usuário com o painel, ativando e desativando filtros, e garantindo que os eventos do calendário sejam atualizados dinamicamente com base nos critérios selecionados. Também verificamos a exibição correta dos dados dos eventos, como país, moeda, nível de importância e hora do evento, dentro do intervalo de tempo definido.

Ao executar vários cenários de teste, confirmamos a funcionalidade de cada recurso, como filtrar eventos com base em importância ou moeda, limitar o número de eventos exibidos e garantir que os rótulos de dados sejam atualizados conforme o esperado. O vídeo abaixo demonstra esses testes em ação.



Conclusão

Em conclusão, desenvolvemos e testamos com sucesso o painel aprimorado do Calendário Econômico MQL5, garantindo que os filtros, exibições de dados e sistemas de rastreamento de eventos funcionem perfeitamente. Este painel fornece uma interface intuitiva para rastrear eventos econômicos, aplicando filtros com base em moeda, importância e tempo, o que nos ajuda a permanecer informados e tomar decisões orientadas por dados de mercado.

Seguindo em frente, na próxima parte desta série, construiremos sobre essa base para integrar a geração de sinais e entradas de negociação. Aproveitando os dados do painel aprimorado, desenvolveremos um sistema que poderá gerar automaticamente sinais de negociação com base em eventos econômicos e condições de mercado, permitindo estratégias de trading mais eficientes e informadas. Fique atento.

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/16404

Arquivos anexados |
Últimos Comentários | Ir para discussão (1)
Petr Zharuk
Petr Zharuk | 25 jul. 2025 em 13:39
Muito útil, obrigado.
Mas, por mais que eu tenha visto artigos sobre interfaces, tudo parece ser de 2005.
Como publicar código no CodeBase: Guia prático Como publicar código no CodeBase: Guia prático
Neste artigo, vamos analisar, com exemplos reais, como publicar diferentes tipos de programas para o terminal na Biblioteca de códigos-fonte em linguagem MQL5.
Otimização com neuroboids — Neuroboids Optimization AlgorithmN 2 (NOA2) Otimização com neuroboids — Neuroboids Optimization AlgorithmN 2 (NOA2)
O novo algoritmo autoral de otimização NOA2 (Neuroboids Optimization Algorithm 2) combina os princípios da inteligência de enxame com controle baseado em redes neurais. O NOA2 funde a mecânica do comportamento coletivo dos neuroboids com um sistema neural adaptativo, que permite aos agentes ajustar seu comportamento de forma autônoma durante o processo de busca pelo ótimo. O algoritmo está em fase ativa de desenvolvimento e demonstra potencial para resolver tarefas complexas de otimização.
Simulação de mercado: Position View (XV) Simulação de mercado: Position View (XV)
Neste artigo, tentarei explicar da forma o mais simples possível como você pode fazer uso de troca de mensagens entre aplicações. Isto para que consiga de fato, desenvolver algo realmente funcional e de maneira o mais simples e eficaz quando for possível ser feito. Não sei se de fato conseguirei passar a ideia por detrás do conceito. Já que ele não é tão simples de ser entendido e compreendido por parte de quem o está vendo pela primeira vez. Aproveitando mostrarei como você pode fazer, para conseguir modificar o sistema de replay/simulador, a fim de poder depurar um Expert Advisor ou um outro código qualquer que você esteja criando. Isto de maneira igualmente simples e direta.
Desenvolvimento de estratégias de trading de tendência baseadas em aprendizado de máquina Desenvolvimento de estratégias de trading de tendência baseadas em aprendizado de máquina
Neste artigo é proposto um método original para o desenvolvimento de estratégias de tendência. Você aprenderá como é possível fazer a anotação dos exemplos de treinamento e treinar classificadores com base neles. O resultado final são sistemas de trading prontos para uso, operando sob o controle do terminal MetaTrader 5.