English Русский 中文 Español Deutsch 日本語
preview
Ferramentas de negociação em MQL5 (Parte 4): Aprimorando o painel do scanner multitimeframe com posicionamento dinâmico e recursos de recolhimento e expansão

Ferramentas de negociação em MQL5 (Parte 4): Aprimorando o painel do scanner multitimeframe com posicionamento dinâmico e recursos de recolhimento e expansão

MetaTrader 5 — Negociação |
29 2
Allan Munene Mutiiria
Allan Munene Mutiiria

Introdução

No artigo anterior (Parte 3), desenvolvemos um painel de scanner multitimeframe em MetaQuotes Language 5 (MQL5), capaz de exibir indicadores como o Índice de Força Relativa (RSI), o Estocástico, o Índice de Canal de Commodities (CCI), o Índice de Movimento Direcional Médio (ADX) e o Awesome Oscillator (Oscilador Incrível, AO) em diferentes timeframes, permitindo identificar sinais de negociação para o instrumento negociado no gráfico atual. Nesta Parte 4, vamos aprimorar o painel com posicionamento dinâmico, permitindo arrastá-lo pelo gráfico, além de um recurso para alternar entre os estados recolhido e expandido. Com isso, teremos uma interface mais prática e um melhor aproveitamento do espaço na tela. Abordaremos os seguintes tópicos:

  1. Arquitetura do posicionamento dinâmico e da alternância entre os modos de exibição
  2. Implementação em MQL5
  3. Testes com dados históricos
  4. Conclusão

Ao final, você terá um painel avançado em MQL5, com posicionamento flexível e recursos para recolher e expandir a interface, pronto para testes e novos ajustes. Vamos começar!


Arquitetura do posicionamento dinâmico e da alternância entre os modos de exibição

Vamos aprimorar o painel do nosso scanner multitimeframe, permitindo arrastá-lo pelo gráfico e alternar entre os estados recolhido e expandido. Esses recursos tornam a interface mais prática, evitam o excesso de elementos visuais no gráfico e permitem aproveitar melhor o espaço disponível para uma análise mais eficiente voltada às decisões de negociação. Esses recursos tornam a interface mais prática, evitam o excesso de elementos visuais no gráfico e permitem aproveitar melhor o espaço disponível para uma análise mais eficiente voltada às decisões de negociação. Para isso, implementaremos o arraste do painel com o mouse e um botão que permitirá alternar entre a visualização compacta e a completa, garantindo a atualização contínua dos indicadores para apoiar decisões de negociação mais eficazes. A ilustração a seguir apresenta, de forma resumida, a arquitetura que pretendemos implementar.

POSITIONING & DRAG STATES ARCHITECTURE


Implementação em MQL5

Para implementar essas melhorias em MQL5, vamos definir um objeto adicional que funcionará como botão de alternância (toggle button), que será utilizado posteriormente para alternar entre os estados expandido e recolhido do painel. Já a detecção da passagem do cursor do mouse sobre o objeto header e o arraste do painel serão tratados nesse mesmo objeto, presente na implementação anterior.

// Define identifiers and properties for UI elements
#define MAIN_PANEL              "PANEL_MAIN"                     //--- Main panel rectangle identifier

//--- THE REST OF THE EXISTING OBJECTS

#define TOGGLE_BUTTON           "BUTTON_TOGGLE"                  //--- Toggle (minimize/maximize) button identifier

//--- THE REST OF THE EXISTING OBJECTS

#define COLOR_DARK_GRAY         C'105,105,105'                   //--- Dark gray color for indicator backgrounds

Começaremos atualizando as definições dos elementos da interface do usuário (UI), adicionando um identificador para o botão responsável por recolher e expandir o painel. As definições existentes serão mantidas, preservando a estrutura principal da interface. A mudança central será a inclusão do identificador "TOGGLE_BUTTON", definido como "BUTTON_TOGGLE", que permitirá criar o botão responsável por alternar o painel entre os estados recolhido e expandido.

Essa adição é importante porque permitirá alternar entre a visualização recolhida, que economiza espaço na tela, e a expandida, que exibe todas as informações do painel. Assim, melhoramos a usabilidade sem alterar a lógica existente de exibição dos indicadores. Em seguida, precisaremos de algumas variáveis globais adicionais para armazenar e controlar os estados do painel.

bool panel_minimized = false;                                    //--- Flag to control minimized state
int panel_x = 632, panel_y = 40;                                 //--- Panel position coordinates
bool panel_dragging = false;                                     //--- Flag to track if panel is being dragged
int panel_drag_x = 0, panel_drag_y = 0;                          //--- Mouse coordinates when drag starts
int panel_start_x = 0, panel_start_y = 0;                        //--- Panel coordinates when drag starts
int prev_mouse_state = 0;                                        //--- Variable to track previous mouse state
bool header_hovered = false;                                     //--- Header hover state
bool toggle_hovered = false;                                     //--- Toggle button hover state
bool close_hovered = false;                                      //--- Close button hover state
int last_mouse_x = 0, last_mouse_y = 0;                          //--- Track last mouse position for optimization
bool prev_header_hovered = false;                                //--- Track previous header hover state
bool prev_toggle_hovered = false;                                //--- Track previous toggle hover state
bool prev_close_hovered = false;                                 //--- Track previous close button hover state

Para acompanhar esses estados, vamos adicionar diversas variáveis globais. A variável "panel_minimized" indicará se o painel está recolhido, enquanto "panel_x" e "panel_y" definirão sua posição. Já "panel_dragging", "panel_drag_x", "panel_drag_y", "panel_start_x" e "panel_start_y" serão utilizadas para controlar o arraste. Também adicionaremos "prev_mouse_state", "header_hovered", "toggle_hovered", "close_hovered", "last_mouse_x", "last_mouse_y", "prev_header_hovered", "prev_toggle_hovered" e "prev_close_hovered" para gerenciar os eventos do mouse e os estados associados à passagem do cursor sobre os elementos da interface. Com essas variáveis, poderemos criar um painel interativo que poderá ser arrastado pelo gráfico, recolhido ou expandido.

Agora podemos adicionar o botão de alternância ao painel. Para controlar os estados recolhido e expandido, precisaremos de funções separadas para criar cada uma dessas versões da interface. Vamos começar pelo painel expandido.

//+------------------------------------------------------------------+
//| Create full dashboard UI                                         |
//+------------------------------------------------------------------+
void create_full_dashboard() {
   create_rectangle(MAIN_PANEL, panel_x, panel_y, 617, 374, C'30,30,30', BORDER_FLAT); //--- Create main panel background
   create_rectangle(HEADER_PANEL, panel_x, panel_y, 617, 27, C'60,60,60', BORDER_FLAT); //--- Create header panel background
   create_label(HEADER_PANEL_ICON, CharToString(91), panel_x - 12, panel_y + 14, 18, clrAqua, "Wingdings"); //--- Create header icon
   create_label(HEADER_PANEL_TEXT, "TimeframeScanner", panel_x - 105, panel_y + 12, 13, COLOR_WHITE); //--- Create header title
   create_label(CLOSE_BUTTON, CharToString('r'), panel_x - 600, panel_y + 14, 18, clrYellow, "Webdings"); //--- Create close button
   create_label(TOGGLE_BUTTON, CharToString('r'), panel_x - 570, panel_y + 14, 18, clrYellow, "Wingdings"); //--- Create minimize button (-)

   // Create header rectangle and label
   create_rectangle(SYMBOL_RECTANGLE, panel_x - 2, panel_y + 35, WIDTH_TIMEFRAME, HEIGHT_RECTANGLE, clrGray); //--- Create symbol rectangle
   create_label(SYMBOL_TEXT, _Symbol, panel_x - 47, panel_y + 45, 11, COLOR_WHITE); //--- Create symbol label
   
   // Create summary and indicator headers (rectangles and labels)
   string header_names[] = {"BUY", "SELL", "RSI", "STOCH", "CCI", "ADX", "AO"}; //--- Define header titles
   for(int header_index = 0; header_index < ArraySize(header_names); header_index++) { //--- Loop through headers
      int x_offset = panel_x - WIDTH_TIMEFRAME - (header_index < 2 ? header_index * WIDTH_SIGNAL : 2 * WIDTH_SIGNAL + (header_index - 2) * WIDTH_INDICATOR) + (1 + header_index); //--- Calculate x position
      int width = (header_index < 2 ? WIDTH_SIGNAL : WIDTH_INDICATOR); //--- Set width based on header type
      create_rectangle(HEADER_RECTANGLE + IntegerToString(header_index), x_offset, panel_y + 35, width, HEIGHT_RECTANGLE, clrGray); //--- Create header rectangle
      create_label(HEADER_TEXT + IntegerToString(header_index), header_names[header_index], x_offset - width/2, panel_y + 45, 11, COLOR_WHITE); //--- Create header label
   }
   
   // Create timeframe rectangles and labels, and summary/indicator cells
   for(int timeframe_index = 0; timeframe_index < ArraySize(timeframes_array); timeframe_index++) { //--- Loop through timeframes
      // Highlight current timeframe
      color timeframe_background = (timeframes_array[timeframe_index] == _Period) ? clrLimeGreen : clrGray; //--- Set background color for current timeframe
      color timeframe_text_color = (timeframes_array[timeframe_index] == _Period) ? COLOR_BLACK : COLOR_WHITE; //--- Set text color for current timeframe
      
      create_rectangle(TIMEFRAME_RECTANGLE + IntegerToString(timeframe_index), panel_x - 2, (panel_y + 35 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index), WIDTH_TIMEFRAME, HEIGHT_RECTANGLE, timeframe_background); //--- Create timeframe rectangle
      create_label(TIMEFRAME_TEXT + IntegerToString(timeframe_index), truncate_timeframe_name(timeframe_index), panel_x - 47, (panel_y + 45 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index), 11, timeframe_text_color); //--- Create timeframe label
                  
      // Create summary and indicator cells
      for(int header_index = 0; header_index < ArraySize(header_names); header_index++) { //--- Loop through headers for cells
         string cell_rectangle_name, cell_text_name;              //--- Declare cell name and label variables
         color cell_background = (header_index < 2) ? COLOR_LIGHT_GRAY : COLOR_BLACK; //--- Set cell background color
         switch(header_index) {                                   //--- Select cell type
            case 0: cell_rectangle_name = BUY_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = BUY_TEXT + IntegerToString(timeframe_index); break; //--- Buy cell
            case 1: cell_rectangle_name = SELL_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = SELL_TEXT + IntegerToString(timeframe_index); break; //--- Sell cell
            case 2: cell_rectangle_name = RSI_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = RSI_TEXT + IntegerToString(timeframe_index); break; //--- RSI cell
            case 3: cell_rectangle_name = STOCH_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = STOCH_TEXT + IntegerToString(timeframe_index); break; //--- Stochastic cell
            case 4: cell_rectangle_name = CCI_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = CCI_TEXT + IntegerToString(timeframe_index); break; //--- CCI cell
            case 5: cell_rectangle_name = ADX_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = ADX_TEXT + IntegerToString(timeframe_index); break; //--- ADX cell
            case 6: cell_rectangle_name = AO_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = AO_TEXT + IntegerToString(timeframe_index); break; //--- AO cell
         }
         int x_offset = panel_x - WIDTH_TIMEFRAME - (header_index < 2 ? header_index * WIDTH_SIGNAL : 2 * WIDTH_SIGNAL + (header_index - 2) * WIDTH_INDICATOR) + (1 + header_index); //--- Calculate x position
         int width = (header_index < 2 ? WIDTH_SIGNAL : WIDTH_INDICATOR); //--- Set width based on cell type
         create_rectangle(cell_rectangle_name, x_offset, (panel_y + 35 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index), width, HEIGHT_RECTANGLE, cell_background); //--- Create cell rectangle
         create_label(cell_text_name, "-/-", x_offset - width/2, (panel_y + 45 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index), 10, COLOR_WHITE); //--- Create cell label
      }
   }
   ChartRedraw(0);
}

Para implementar os diferentes estados do painel, criaremos a função "create_full_dashboard", transferindo para ela o código de criação do painel, anteriormente baseado em coordenadas fixas, e adaptando-o para permitir o posicionamento dinâmico e a alternância entre os modos de exibição. A principal mudança será utilizar as variáveis "panel_x" e "panel_y" para posicionar todos os elementos da interface, permitindo mover o painel para qualquer região do gráfico. Criaremos o painel principal com a função "create_rectangle", utilizando "MAIN_PANEL", "panel_x" e "panel_y" para definir sua posição e mantendo as dimensões de 617x374. Da mesma forma, posicionaremos o painel do cabeçalho, o ícone e o título por meio das funções "create_rectangle" e "create_label", associadas a "HEADER_PANEL", "HEADER_PANEL_ICON" e "HEADER_PANEL_TEXT", ajustando suas coordenadas nos eixos x e y em relação a "panel_x" e "panel_y".

Adicionaremos o novo botão "TOGGLE_BUTTON" com a função "create_label", posicionando-o nas coordenadas "panel_x - 570" e "panel_y + 14" e exibindo o símbolo de recolhimento ('r' na fonte Webdings). O retângulo e o rótulo do instrumento financeiro ("SYMBOL_RECTANGLE" e "SYMBOL_TEXT"), assim como os retângulos e rótulos do cabeçalho ("HEADER_RECTANGLE" e "HEADER_TEXT"), também terão suas posições calculadas a partir de "panel_x" e "panel_y". Dessa forma, todos esses elementos acompanharão o movimento do painel. Para cada timeframe definido em "timeframes_array", criaremos os retângulos e rótulos correspondentes ("TIMEFRAME_RECTANGLE" e "TIMEFRAME_TEXT"), além das células dos indicadores ("RSI_RECTANGLE", "STOCH_RECTANGLE" etc.), com posições calculadas em relação a "panel_x" e "panel_y". Isso preservará a disposição original dos elementos, mas permitirá movimentar toda a interface. Em seguida, chamaremos ChartRedraw para atualizar a exibição no gráfico. Diferentemente da versão anterior, que utilizava coordenadas fixas (como 632, 40), essa função trabalhará com coordenadas dinâmicas, permitindo arrastar o painel, além de incorporar o botão para recolher ou expandir a interface. O resultado será semelhante ao exemplo apresentado a seguir.

ESTADO EXPANDIDO

Em seguida, podemos criar uma função para montar a versão recolhida do painel.

//+------------------------------------------------------------------+
//| Create minimized dashboard UI                                    |
//+------------------------------------------------------------------+
void create_minimized_dashboard() {
   create_rectangle(HEADER_PANEL, panel_x, panel_y, 617, 27, C'60,60,60', BORDER_FLAT); //--- Create header panel background
   create_label(HEADER_PANEL_ICON, CharToString(91), panel_x - 12, panel_y + 14, 18, clrAqua, "Wingdings"); //--- Create header icon
   create_label(HEADER_PANEL_TEXT, "TimeframeScanner", panel_x - 105, panel_y + 12, 13, COLOR_WHITE); //--- Create header title
   create_label(CLOSE_BUTTON, CharToString('r'), panel_x - 600, panel_y + 14, 18, clrYellow, "Webdings"); //--- Create close button
   create_label(TOGGLE_BUTTON, CharToString('o'), panel_x - 570, panel_y + 14, 18, clrYellow, "Wingdings"); //--- Create maximize button (+)
   ChartRedraw(0);
}

Para permitir a alternância para a visualização compacta, vamos definir a função "create_minimized_dashboard". Criaremos o painel do cabeçalho com "create_rectangle", utilizando "HEADER_PANEL" nas coordenadas "panel_x" e "panel_y". Em seguida, adicionaremos "HEADER_PANEL_ICON" e "HEADER_PANEL_TEXT" por meio de "create_label", para exibir o ícone e o título. Também incluiremos "CLOSE_BUTTON" e adicionaremos "TOGGLE_BUTTON" com o símbolo de expansão ('o' na fonte Webdings), posicionado nas coordenadas "panel_x - 570" e "panel_y + 14". Chamaremos "ChartRedraw" para atualizar a exibição do gráfico, agora com o painel recolhido e a possibilidade de movimentá-lo. Como você deve ter percebido, escolhemos a fonte Wingdings para os botões de expandir e recolher, mantendo a consistência visual da interface. Você pode escolher os símbolos que preferir. No nosso caso, utilizamos os caracteres 'r' e 'o', respectivamente. Veja abaixo como eles são exibidos.

CARACTERES DA FONTE WINGDINGS

Ao recolher o painel, teremos o seguinte resultado.

ESTADO RECOLHIDO

Com essas funções prontas, podemos escolher qual delas executar de acordo com o comando do usuário. Como a interface contém vários objetos, vamos centralizar sua exclusão em uma única função, já que precisaremos removê-los e recriá-los automaticamente.

//+------------------------------------------------------------------+
//| Delete all dashboard objects                                     |
//+------------------------------------------------------------------+
void delete_all_objects() {
   ObjectDelete(0, MAIN_PANEL);                                  //--- Delete main panel
   ObjectDelete(0, HEADER_PANEL);                                //--- Delete header panel
   ObjectDelete(0, HEADER_PANEL_ICON);                           //--- Delete header icon
   ObjectDelete(0, HEADER_PANEL_TEXT);                           //--- Delete header title
   ObjectDelete(0, CLOSE_BUTTON);                                //--- Delete close button
   ObjectDelete(0, TOGGLE_BUTTON);                               //--- Delete toggle button

   ObjectsDeleteAll(0, SYMBOL_RECTANGLE);                        //--- Delete all symbol rectangles
   ObjectsDeleteAll(0, SYMBOL_TEXT);                             //--- Delete all symbol labels
   ObjectsDeleteAll(0, TIMEFRAME_RECTANGLE);                     //--- Delete all timeframe rectangles
   ObjectsDeleteAll(0, TIMEFRAME_TEXT);                          //--- Delete all timeframe labels
   ObjectsDeleteAll(0, HEADER_RECTANGLE);                        //--- Delete all header rectangles
   ObjectsDeleteAll(0, HEADER_TEXT);                             //--- Delete all header labels
   ObjectsDeleteAll(0, RSI_RECTANGLE);                           //--- Delete all RSI rectangles
   ObjectsDeleteAll(0, RSI_TEXT);                                //--- Delete all RSI labels
   ObjectsDeleteAll(0, STOCH_RECTANGLE);                         //--- Delete all Stochastic rectangles
   ObjectsDeleteAll(0, STOCH_TEXT);                              //--- Delete all Stochastic labels
   ObjectsDeleteAll(0, CCI_RECTANGLE);                           //--- Delete all CCI rectangles
   ObjectsDeleteAll(0, CCI_TEXT);                                //--- Delete all CCI labels
   ObjectsDeleteAll(0, ADX_RECTANGLE);                           //--- Delete all ADX rectangles
   ObjectsDeleteAll(0, ADX_TEXT);                                //--- Delete all ADX labels
   ObjectsDeleteAll(0, AO_RECTANGLE);                            //--- Delete all AO rectangles
   ObjectsDeleteAll(0, AO_TEXT);                                 //--- Delete all AO labels
   ObjectsDeleteAll(0, BUY_RECTANGLE);                           //--- Delete all buy rectangles
   ObjectsDeleteAll(0, BUY_TEXT);                                //--- Delete all buy labels
   ObjectsDeleteAll(0, SELL_RECTANGLE);                          //--- Delete all sell rectangles
   ObjectsDeleteAll(0, SELL_TEXT);                               //--- Delete all sell labels
}

Para controlar melhor quando os objetos serão removidos, vamos criar e atualizar a função "delete_all_objects", incluindo nela a exclusão do novo "TOGGLE_BUTTON", necessário para o funcionamento do recurso de alternância. Adicionaremos uma chamada à função ObjectDelete para "TOGGLE_BUTTON" à rotina de exclusão, garantindo que o botão seja removido corretamente quando o usuário fechar o painel ou alternar entre os modos de exibição. Também manteremos a exclusão dos demais objetos, como "MAIN_PANEL", "HEADER_PANEL", "HEADER_PANEL_ICON", "HEADER_PANEL_TEXT" e "CLOSE_BUTTON", além de todos os retângulos e rótulos dos instrumentos, timeframes, cabeçalhos, indicadores e sinais, utilizando a função ObjectsDeleteAll. Com essa alteração, todos os componentes do painel, incluindo o novo botão de alternância, serão removidos quando necessário. Assim, evitaremos que elementos da interface permaneçam no gráfico ao ocultar ou reinicializar o painel.

Para aprimorar o posicionamento dinâmico, precisaremos de uma função para criar e atualizar os elementos do painel conforme a posição do cursor. A seguir, apresentamos a lógica que utilizaremos para isso.

//+------------------------------------------------------------------+
//| Update panel object positions                                    |
//+------------------------------------------------------------------+
void update_panel_positions() {
   // Update header and buttons
   ObjectSetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE, panel_x); //--- Set header panel x position
   ObjectSetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE, panel_y); //--- Set header panel y position
   ObjectSetInteger(0, HEADER_PANEL_ICON, OBJPROP_XDISTANCE, panel_x - 12); //--- Set header icon x position
   ObjectSetInteger(0, HEADER_PANEL_ICON, OBJPROP_YDISTANCE, panel_y + 14); //--- Set header icon y position
   ObjectSetInteger(0, HEADER_PANEL_TEXT, OBJPROP_XDISTANCE, panel_x - 105); //--- Set header title x position
   ObjectSetInteger(0, HEADER_PANEL_TEXT, OBJPROP_YDISTANCE, panel_y + 12); //--- Set header title y position
   ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE, panel_x - 600); //--- Set close button x position
   ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE, panel_y + 14); //--- Set close button y position
   ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE, panel_x - 570); //--- Set toggle button x position
   ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE, panel_y + 14); //--- Set toggle button y position

   if (!panel_minimized) {
      // Update main panel
      ObjectSetInteger(0, MAIN_PANEL, OBJPROP_XDISTANCE, panel_x); //--- Set main panel x position
      ObjectSetInteger(0, MAIN_PANEL, OBJPROP_YDISTANCE, panel_y); //--- Set main panel y position

      // Update symbol rectangle and label
      ObjectSetInteger(0, SYMBOL_RECTANGLE, OBJPROP_XDISTANCE, panel_x - 2); //--- Set symbol rectangle x position
      ObjectSetInteger(0, SYMBOL_RECTANGLE, OBJPROP_YDISTANCE, panel_y + 35); //--- Set symbol rectangle y position
      ObjectSetInteger(0, SYMBOL_TEXT, OBJPROP_XDISTANCE, panel_x - 47); //--- Set symbol text x position
      ObjectSetInteger(0, SYMBOL_TEXT, OBJPROP_YDISTANCE, panel_y + 45); //--- Set symbol text y position

      // Update header rectangles and labels
      string header_names[] = {"BUY", "SELL", "RSI", "STOCH", "CCI", "ADX", "AO"};
      for(int header_index = 0; header_index < ArraySize(header_names); header_index++) { //--- Loop through headers
         int x_offset = panel_x - WIDTH_TIMEFRAME - (header_index < 2 ? header_index * WIDTH_SIGNAL : 2 * WIDTH_SIGNAL + (header_index - 2) * WIDTH_INDICATOR) + (1 + header_index); //--- Calculate x position
         ObjectSetInteger(0, HEADER_RECTANGLE + IntegerToString(header_index), OBJPROP_XDISTANCE, x_offset); //--- Set header rectangle x position
         ObjectSetInteger(0, HEADER_RECTANGLE + IntegerToString(header_index), OBJPROP_YDISTANCE, panel_y + 35); //--- Set header rectangle y position
         ObjectSetInteger(0, HEADER_TEXT + IntegerToString(header_index), OBJPROP_XDISTANCE, x_offset - (header_index < 2 ? WIDTH_SIGNAL/2 : WIDTH_INDICATOR/2)); //--- Set header text x position
         ObjectSetInteger(0, HEADER_TEXT + IntegerToString(header_index), OBJPROP_YDISTANCE, panel_y + 45); //--- Set header text y position
      }

      // Update timeframe rectangles, labels, and cells
      for(int timeframe_index = 0; timeframe_index < ArraySize(timeframes_array); timeframe_index++) { //--- Loop through timeframes
         int y_offset = (panel_y + 35 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index); //--- Calculate y position
         ObjectSetInteger(0, TIMEFRAME_RECTANGLE + IntegerToString(timeframe_index), OBJPROP_XDISTANCE, panel_x - 2); //--- Set timeframe rectangle x position
         ObjectSetInteger(0, TIMEFRAME_RECTANGLE + IntegerToString(timeframe_index), OBJPROP_YDISTANCE, y_offset); //--- Set timeframe rectangle y position
         ObjectSetInteger(0, TIMEFRAME_TEXT + IntegerToString(timeframe_index), OBJPROP_XDISTANCE, panel_x - 47); //--- Set timeframe text x position
         ObjectSetInteger(0, TIMEFRAME_TEXT + IntegerToString(timeframe_index), OBJPROP_YDISTANCE, y_offset + 10); //--- Set timeframe text y position

         for(int header_index = 0; header_index < ArraySize(header_names); header_index++) { //--- Loop through cells
            string cell_rectangle_name, cell_text_name;
            switch(header_index) { //--- Select cell type
               case 0: cell_rectangle_name = BUY_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = BUY_TEXT + IntegerToString(timeframe_index); break; //--- Buy cell
               case 1: cell_rectangle_name = SELL_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = SELL_TEXT + IntegerToString(timeframe_index); break; //--- Sell cell
               case 2: cell_rectangle_name = RSI_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = RSI_TEXT + IntegerToString(timeframe_index); break; //--- RSI cell
               case 3: cell_rectangle_name = STOCH_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = STOCH_TEXT + IntegerToString(timeframe_index); break; //--- Stochastic cell
               case 4: cell_rectangle_name = CCI_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = CCI_TEXT + IntegerToString(timeframe_index); break; //--- CCI cell
               case 5: cell_rectangle_name = ADX_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = ADX_TEXT + IntegerToString(timeframe_index); break; //--- ADX cell
               case 6: cell_rectangle_name = AO_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = AO_TEXT + IntegerToString(timeframe_index); break; //--- AO cell
            }
            int x_offset = panel_x - WIDTH_TIMEFRAME - (header_index < 2 ? header_index * WIDTH_SIGNAL : 2 * WIDTH_SIGNAL + (header_index - 2) * WIDTH_INDICATOR) + (1 + header_index); //--- Calculate x position
            int width = (header_index < 2 ? WIDTH_SIGNAL : WIDTH_INDICATOR); //--- Set cell width
            ObjectSetInteger(0, cell_rectangle_name, OBJPROP_XDISTANCE, x_offset); //--- Set cell rectangle x position
            ObjectSetInteger(0, cell_rectangle_name, OBJPROP_YDISTANCE, y_offset); //--- Set cell rectangle y position
            ObjectSetInteger(0, cell_text_name, OBJPROP_XDISTANCE, x_offset - width/2); //--- Set cell text x position
            ObjectSetInteger(0, cell_text_name, OBJPROP_YDISTANCE, y_offset + 10); //--- Set cell text y position
         }
      }
   }
   ChartRedraw(0);                                               //--- Redraw chart
}

Para permitir o posicionamento dinâmico do painel, vamos criar a função "update_panel_positions". Essa função ajustará a posição de todos os elementos da interface com base nas coordenadas atuais de "panel_x" e "panel_y", permitindo arrastar o painel pelo gráfico. Atualizaremos as posições do painel do cabeçalho, do ícone, do título, do botão de fechamento e do botão de alternância ("HEADER_PANEL", "HEADER_PANEL_ICON", "HEADER_PANEL_TEXT", "CLOSE_BUTTON" e "TOGGLE_BUTTON") por meio da função ObjectSetInteger, utilizando as propriedades OBJPROP_XDISTANCE e OBJPROP_YDISTANCE para definir suas coordenadas em relação a "panel_x" e "panel_y".

Quando "panel_minimized" for false, também atualizaremos as posições do painel principal ("MAIN_PANEL"), do retângulo e do rótulo do instrumento financeiro ("SYMBOL_RECTANGLE" e "SYMBOL_TEXT"), dos retângulos e rótulos dos cabeçalhos ("HEADER_RECTANGLE" e "HEADER_TEXT"), além dos retângulos e rótulos dos timeframes e das células dos indicadores ("TIMEFRAME_RECTANGLE", "TIMEFRAME_TEXT", "BUY_RECTANGLE", "RSI_RECTANGLE" etc.). Todas essas posições serão calculadas a partir dos deslocamentos definidos em relação a "panel_x" e "panel_y". Em seguida, chamaremos ChartRedraw para atualizar a exibição no gráfico. Dessa forma, todos os elementos acompanharão o movimento do painel durante o arraste, garantindo o funcionamento do posicionamento dinâmico. Com essa funcionalidade implementada, podemos começar os testes. Para isso, chamaremos a função de criação do painel expandido no manipulador de eventos OnInit e a função de remoção dos objetos no manipulador OnDeinit.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()                                                     //--- Initialize EA
{
   create_full_dashboard();                                      //--- Create full dashboard
   ArraySetAsSeries(rsi_values, true);                           //--- Set RSI array as timeseries
   ArraySetAsSeries(stochastic_values, true);                    //--- Set Stochastic array as timeseries
   ArraySetAsSeries(cci_values, true);                           //--- Set CCI array as timeseries
   ArraySetAsSeries(adx_values, true);                           //--- Set ADX array as timeseries
   ArraySetAsSeries(ao_values, true);                            //--- Set AO array as timeseries
    
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);             //--- Enable mouse move events
   return(INIT_SUCCEEDED);                                       //--- Return initialization success
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)                                  //--- Deinitialize EA
{
   delete_all_objects();                                         //--- Delete all objects
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);            //--- Disable mouse move events
   ChartRedraw(0);                                               //--- Redraw chart
}

Nesta etapa, apenas chamaremos as funções correspondentes durante a inicialização e a desinicialização do programa, para verificar o comportamento básico do painel antes de avançar. É sempre recomendável compilar o programa por etapas para verificar, a cada etapa, se ele continua funcionando conforme o esperado. Após a compilação, obteremos o seguinte resultado.

INIT AND DEINIT GIF

A demonstração mostra que conseguimos inicializar e remover o painel corretamente. Agora vamos torná-lo interativo e capaz de responder às ações do usuário. Primeiro, precisaremos obter a posição do cursor para identificar se ele está sobre o cabeçalho ou sobre algum dos botões e, assim, identificar quais ações o usuário pode querer realizar. Também vamos acompanhar a trajetória de movimento do botão para identificar quando o cursor estiver sobre ele e quando o botão estiver pressionado, indicando esses estados por mudanças de cor. Antes disso, porém, vamos definir uma função para verificar a posição do cursor em relação aos elementos do painel.

//+------------------------------------------------------------------+
//| Check if cursor is inside header or buttons                      |
//+------------------------------------------------------------------+
bool is_cursor_in_header_or_buttons(int mouse_x, int mouse_y) {
   int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   
   // Header panel bounds
   int header_x = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE);
   int header_y = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE);
   int header_width = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XSIZE);
   int header_height = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YSIZE);
   int header_left = chart_width - header_x;
   int header_right = header_left + header_width;
   bool in_header = (mouse_x >= header_left && mouse_x <= header_right && 
                     mouse_y >= header_y && mouse_y <= header_y + header_height);

   // Close button bounds
   int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE);
   int close_y = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE);
   int close_width = 20;
   int close_height = 20;
   int close_left = chart_width - close_x;
   int close_right = close_left + close_width;
   bool in_close = (mouse_x >= close_left && mouse_x <= close_right && 
                    mouse_y >= close_y && mouse_y <= close_y + close_height);

   // Toggle button bounds
   int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE);
   int toggle_y = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE);
   int toggle_width = 20;
   int toggle_height = 20;
   int toggle_left = chart_width - toggle_x;
   int toggle_right = toggle_left + toggle_width;
   bool in_toggle = (mouse_x >= toggle_left && mouse_x <= toggle_right && 
                     mouse_y >= toggle_y && mouse_y <= toggle_y + toggle_height);

   return in_header || in_close || in_toggle;
}

Começaremos criando a função "is_cursor_in_header_or_buttons", que dará suporte aos recursos de posicionamento dinâmico e alternância entre os modos de exibição. Essa função verificará se o cursor do mouse está sobre o cabeçalho, o botão de fechamento ou o botão de alternância, permitindo tratar as interações de arraste e clique. Primeiro, obteremos a largura do gráfico por meio de ChartGetInteger, utilizando o parâmetro CHART_WIDTH_IN_PIXELS. Para o cabeçalho, obteremos os valores de "header_x", "header_y", "header_width" e "header_height" com ObjectGetInteger, consultando as propriedades OBJPROP_XDISTANCE, OBJPROP_YDISTANCE, OBJPROP_XSIZE e OBJPROP_YSIZE do objeto "HEADER_PANEL". A partir desses valores, calcularemos "header_left" e "header_right", levando em consideração a largura do gráfico. Em seguida, verificaremos se as coordenadas "mouse_x" e "mouse_y" estão dentro dos limites do cabeçalho. Se estiverem, definiremos "in_header" como true.

Para o botão de fechamento, obteremos as coordenadas "close_x" e "close_y" do objeto "CLOSE_BUTTON", definiremos uma área de 20x20 pixels e calcularemos "close_left" e "close_right". Se o cursor estiver dentro dessa área, definiremos "in_close" como true. Da mesma forma, para o botão de alternância, obteremos "toggle_x" e "toggle_y" do objeto "TOGGLE_BUTTON", definiremos uma área de 20x20 pixels e atribuiremos true a "in_toggle" quando o cursor estiver dentro de seus limites. A função retornará true se o cursor estiver em qualquer uma dessas regiões ("in_header", "in_close" ou "in_toggle"). Essa verificação permitirá reconhecer as ações de arraste do cabeçalho e os cliques nos botões de fechamento e alternância. Em seguida, vamos atualizar os estados associados à passagem do cursor, utilizando mudanças de cor para facilitar sua identificação.

//+------------------------------------------------------------------+
//| Update button hover states                                       |
//+------------------------------------------------------------------+
void update_button_hover_states(int mouse_x, int mouse_y) {
   int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   
   // Close button hover
   int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE);
   int close_y = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE);
   int close_width = 20;
   int close_height = 20;
   int close_left = chart_width - close_x;
   int close_right = close_left + close_width;
   bool is_close_hovered = (mouse_x >= close_left && mouse_x <= close_right && 
                            mouse_y >= close_y && mouse_y <= close_y + close_height);

   if (is_close_hovered != prev_close_hovered) {
      ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_COLOR, is_close_hovered ? clrWhite : clrYellow);
      ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_BGCOLOR, is_close_hovered ? clrDodgerBlue : clrNONE);
      prev_close_hovered = is_close_hovered;
      ChartRedraw(0);
   }

   // Toggle button hover
   int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE);
   int toggle_y = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE);
   int toggle_width = 20;
   int toggle_height = 20;
   int toggle_left = chart_width - toggle_x;
   int toggle_right = toggle_left + toggle_width;
   bool is_toggle_hovered = (mouse_x >= toggle_left && mouse_x <= toggle_right && 
                             mouse_y >= toggle_y && mouse_y <= toggle_y + toggle_height);

   if (is_toggle_hovered != prev_toggle_hovered) {
      ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_COLOR, is_toggle_hovered ? clrWhite : clrYellow);
      ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_BGCOLOR, is_toggle_hovered ? clrDodgerBlue : clrNONE);
      prev_toggle_hovered = is_toggle_hovered;
      ChartRedraw(0);
   }
}

//+------------------------------------------------------------------+
//| Update header hover state                                        |
//+------------------------------------------------------------------+
void update_header_hover_state(int mouse_x, int mouse_y) {
   int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   int header_x = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE);
   int header_y = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE);
   int header_width = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XSIZE);
   int header_height = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YSIZE);
   int header_left = chart_width - header_x;
   int header_right = header_left + header_width;

   // Exclude button areas from header hover
   int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE);
   int close_y = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE);
   int close_width = 20;
   int close_height = 20;
   int close_left = chart_width - close_x;
   int close_right = close_left + close_width;

   int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE);
   int toggle_y = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE);
   int toggle_width = 20;
   int toggle_height = 20;
   int toggle_left = chart_width - toggle_x;
   int toggle_right = toggle_left + toggle_width;

   bool is_header_hovered = (mouse_x >= header_left && mouse_x <= header_right && 
                             mouse_y >= header_y && mouse_y <= header_y + header_height &&
                             !(mouse_x >= close_left && mouse_x <= close_right && 
                               mouse_y >= close_y && mouse_y <= close_y + close_height) &&
                             !(mouse_x >= toggle_left && mouse_x <= toggle_right && 
                               mouse_y >= toggle_y && mouse_y <= toggle_y + toggle_height));

   if (is_header_hovered != prev_header_hovered && !panel_dragging) {
      ObjectSetInteger(0, HEADER_PANEL, OBJPROP_BGCOLOR, is_header_hovered ? clrRed : C'60,60,60');
      prev_header_hovered = is_header_hovered;
      ChartRedraw(0);
   }

   update_button_hover_states(mouse_x, mouse_y);
}

Agora vamos criar duas funções, "update_button_hover_states" e "update_header_hover_state", responsáveis por fornecer feedback visual durante as interações do usuário com o painel, tornando a interface mais prática. Começaremos com "update_button_hover_states", que utilizará as coordenadas "mouse_x" e "mouse_y" para identificar quando o cursor estiver sobre os botões de fechamento e alternância. Para "CLOSE_BUTTON", obteremos "close_x" e "close_y" por meio de ObjectGetInteger, consultando as propriedades "OBJPROP_XDISTANCE" e "OBJPROP_YDISTANCE". Em seguida, calcularemos os limites de uma área de 20x20 pixels, considerando a largura do gráfico obtida com ChartGetInteger e o parâmetro "CHART_WIDTH_IN_PIXELS". Se o cursor estiver dentro dessa área, definiremos "is_close_hovered" como true.

Quando "is_close_hovered" for diferente de "prev_close_hovered", atualizaremos as propriedades de "CLOSE_BUTTON" por meio de "ObjectSetInteger". Se o cursor estiver sobre o botão, definiremos "OBJPROP_COLOR" como "clrWhite" e OBJPROP_BGCOLOR como "clrDodgerBlue". Caso contrário, utilizaremos "clrYellow" e "clrNONE", respectivamente. Depois, atualizaremos "prev_close_hovered" e chamaremos ChartRedraw para refletir as alterações no gráfico. Aplicaremos a mesma lógica a "TOGGLE_BUTTON": obteremos "toggle_x" e "toggle_y", verificaremos se o cursor está dentro da área de 20x20 pixels e, quando "is_toggle_hovered" mudar, atualizaremos as cores do botão e o valor de "prev_toggle_hovered". Assim, o botão fornecerá feedback visual imediato às interações do usuário.

Em seguida, criaremos a função "update_header_hover_state", que também utilizará "mouse_x" e "mouse_y". Obteremos "header_x", "header_y", "header_width" e "header_height" do objeto "HEADER_PANEL" por meio de "ObjectGetInteger" e calcularemos os limites do cabeçalho. Ao verificar se o cursor está sobre o cabeçalho, desconsideraremos as áreas de "CLOSE_BUTTON" e "TOGGLE_BUTTON", cada uma com 20x20 pixels, para evitar que a mesma posição seja interpretada como interação com o cabeçalho e com um dos botões. Se "is_header_hovered" for diferente de "prev_header_hovered" e "panel_dragging" estiver definido como false, atualizaremos a propriedade "OBJPROP_BGCOLOR" de "HEADER_PANEL" para "clrRed" quando o cursor estiver sobre o cabeçalho, ou para "C'60,60,60'" quando estiver fora dele. Depois, atualizaremos "prev_header_hovered" e chamaremos "ChartRedraw". Por fim, executaremos "update_button_hover_states" para manter atualizados os estados visuais dos botões. Com essas funções, o painel fornecerá indicações visuais das áreas disponíveis para arraste e das interações com os botões, tornando a interface mais intuitiva. Agora podemos integrar essas funções ao manipulador de eventos OnChartEvent para implementar todas as interações. A seguir, apresentamos a lógica que utilizaremos.

//+------------------------------------------------------------------+
//| Expert chart event handler                                       |
//+------------------------------------------------------------------+
void OnChartEvent(const int event_id, const long& long_param, const double& double_param, const string& string_param)
{
   if (event_id == CHARTEVENT_OBJECT_CLICK) {                    //--- Handle object click event
      if (string_param == CLOSE_BUTTON) {                        //--- Check if close button clicked
         Print("Closing the panel now");                         //--- Log panel closure
         PlaySound("alert.wav");                                 //--- Play alert sound
         panel_is_visible = false;                               //--- Hide panel
         delete_all_objects();                                   //--- Delete all objects
         ChartRedraw(0);                                         //--- Redraw chart
      } else if (string_param == TOGGLE_BUTTON) {                //--- Toggle button clicked
         delete_all_objects();                                   //--- Delete current UI
         panel_minimized = !panel_minimized;                     //--- Toggle minimized state
         if (panel_minimized) {
            Print("Minimizing the panel");                       //--- Log minimization
            create_minimized_dashboard();                        //--- Create minimized UI
         } else {
            Print("Maximizing the panel");                       //--- Log maximization
            create_full_dashboard();                             //--- Create full UI
         }
         // Reset hover states after toggle
         prev_header_hovered = false;
         prev_close_hovered = false;
         prev_toggle_hovered = false;
         ObjectSetInteger(0, HEADER_PANEL, OBJPROP_BGCOLOR, C'60,60,60');
         ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_COLOR, clrYellow);
         ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_BGCOLOR, clrNONE);
         ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_COLOR, clrYellow);
         ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_BGCOLOR, clrNONE);
         ChartRedraw(0);
      }
   }
   else if (event_id == CHARTEVENT_MOUSE_MOVE && panel_is_visible) { //--- Handle mouse move events
      int mouse_x = (int)long_param;                             //--- Get mouse x-coordinate
      int mouse_y = (int)double_param;                           //--- Get mouse y-coordinate
      int mouse_state = (int)string_param;                       //--- Get mouse state

      if (mouse_x == last_mouse_x && mouse_y == last_mouse_y && !panel_dragging) { //--- Skip redundant updates
         return;
      }
      last_mouse_x = mouse_x;                                    //--- Update last mouse x position
      last_mouse_y = mouse_y;                                    //--- Update last mouse y position

      update_header_hover_state(mouse_x, mouse_y);               //--- Update header and button hover states

      int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
      int header_x = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE);
      int header_y = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE);
      int header_width = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XSIZE);
      int header_height = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YSIZE);
      int header_left = chart_width - header_x;
      int header_right = header_left + header_width;

      int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE);
      int close_width = 20;
      int close_left = chart_width - close_x;
      int close_right = close_left + close_width;

      int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE);
      int toggle_width = 20;
      int toggle_left = chart_width - toggle_x;
      int toggle_right = toggle_left + toggle_width;

      if (prev_mouse_state == 0 && mouse_state == 1) {           //--- Detect mouse button down
         if (mouse_x >= header_left && mouse_x <= header_right && 
             mouse_y >= header_y && mouse_y <= header_y + header_height &&
             !(mouse_x >= close_left && mouse_x <= close_right) &&
             !(mouse_x >= toggle_left && mouse_x <= toggle_right)) { //--- Exclude button areas
            panel_dragging = true;                              //--- Start dragging
            panel_drag_x = mouse_x;                             //--- Store mouse x-coordinate
            panel_drag_y = mouse_y;                             //--- Store mouse y-coordinate
            panel_start_x = header_x;                           //--- Store panel x-coordinate
            panel_start_y = header_y;                           //--- Store panel y-coordinate
            ObjectSetInteger(0, HEADER_PANEL, OBJPROP_BGCOLOR, clrMediumBlue); //--- Set header to blue on drag start
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);      //--- Disable chart scrolling
         }
      }

      if (panel_dragging && mouse_state == 1) {                  //--- Handle dragging
         int dx = mouse_x - panel_drag_x;                        //--- Calculate x displacement
         int dy = mouse_y - panel_drag_y;                        //--- Calculate y displacement
         panel_x = panel_start_x - dx;                           //--- Update panel x-position (inverted for CORNER_RIGHT_UPPER)
         panel_y = panel_start_y + dy;                           //--- Update panel y-position

         int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get chart width
         int chart_height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Get chart height
         panel_x = MathMax(617, MathMin(chart_width, panel_x));  //--- Keep panel within right edge
         panel_y = MathMax(0, MathMin(chart_height - (panel_minimized ? 27 : 374), panel_y)); //--- Adjust height based on state

         update_panel_positions();                               //--- Update all panel object positions
         ChartRedraw(0);                                         //--- Redraw chart during dragging
      }

      if (mouse_state == 0 && prev_mouse_state == 1) {           //--- Detect mouse button release
         if (panel_dragging) {
            panel_dragging = false;                              //--- Stop dragging
            update_header_hover_state(mouse_x, mouse_y);          //--- Update hover state immediately after drag
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true);        //--- Re-enable chart scrolling
            ChartRedraw(0);                                      //--- Redraw chart
         }
      }

      prev_mouse_state = mouse_state;                            //--- Update previous mouse state
   }
}

Nesta etapa, vamos aprimorar o programa atualizando o manipulador de eventos OnChartEvent para oferecer suporte ao posicionamento dinâmico e à alternância entre os modos de exibição, ampliando os recursos da versão anterior. Manteremos o tratamento de CHARTEVENT_OBJECT_CLICK para "CLOSE_BUTTON", que registra o fechamento no log por meio de Print, reproduz um som com PlaySound, define "panel_is_visible" como false, chama a função "delete_all_objects" e redesenha o gráfico.

Também passaremos a tratar os cliques em "TOGGLE_BUTTON". Ao clicar nesse botão, chamaremos "delete_all_objects", inverteremos o valor de "panel_minimized" e criaremos o painel recolhido com "create_minimized_dashboard" (registrando "Recolhimento do painel") ou o painel expandido com "create_full_dashboard" (registrando "Expansão do painel"). Em seguida, redefiniremos as variáveis que armazenam os estados anteriores de passagem do cursor ("prev_header_hovered", "prev_close_hovered" e "prev_toggle_hovered") como false, restauraremos as cores padrão de "HEADER_PANEL", "CLOSE_BUTTON" e "TOGGLE_BUTTON" por meio de ObjectSetInteger e redesenharemos o gráfico.

Para permitir o posicionamento dinâmico, acrescentaremos o tratamento do evento CHARTEVENT_MOUSE_MOVE quando "panel_is_visible" for true. Obteremos os valores de "mouse_x", "mouse_y" e "mouse_state" dos parâmetros do evento. Se as coordenadas coincidirem com "last_mouse_x" e "last_mouse_y" e "panel_dragging" for false, ignoraremos a atualização redundante. Caso contrário, atualizaremos as coordenadas armazenadas. Chamaremos "update_header_hover_state" para atualizar os efeitos visuais quando o cursor passar sobre o cabeçalho. Se "prev_mouse_state" for 0 e "mouse_state" for 1, verificaremos, por meio de ObjectGetInteger e ChartGetInteger, se o cursor está sobre "HEADER_PANEL", desconsiderando as áreas de "CLOSE_BUTTON" e "TOGGLE_BUTTON". Nesse caso, definiremos "panel_dragging" como true, armazenaremos as coordenadas em "panel_drag_x", "panel_drag_y", "panel_start_x" e "panel_start_y", alteraremos a cor de "HEADER_PANEL" para "clrMediumBlue" e desativaremos a rolagem do gráfico por meio de ChartSetInteger.

Enquanto "panel_dragging" estiver ativo e "mouse_state" for 1, calcularemos o deslocamento, atualizaremos "panel_x" e "panel_y" respeitando os limites do gráfico, chamaremos "update_panel_positions" e redesenharemos o gráfico. Ao soltar o botão do mouse, encerraremos o arraste, atualizaremos o estado de passagem do cursor, reativaremos a rolagem do gráfico e o redesenharemos. Por fim, atualizaremos "prev_mouse_state". Essas alterações permitirão arrastar o painel e alternar entre os modos de exibição, diferentemente da versão anterior, que tratava apenas os cliques do mouse. Após a compilação, obteremos o seguinte resultado.

DRAG, HOVER AND VISIBILITY STATES

A demonstração mostra que o painel agora responde às interações do usuário, mas ainda precisamos evitar conflitos entre os manipuladores de eventos. Para melhorar o desempenho e evitar conflitos, precisaremos estabelecer qual manipulador de eventos terá prioridade de acesso durante o tratamento das interações. Por exemplo, quando o cursor estiver sobre um elemento da interface, durante o arraste ou mesmo com o painel recolhido, precisaremos estabelecer a prioridade do tratamento dos eventos no gráfico. Nos estados passivo ou expandido, também será necessário definir quais atualizações do painel devem ter prioridade. Faremos isso ajustando o tratamento dos eventos de tick, pois é nessa rotina que atualizamos o painel.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()                                                     //--- Handle tick events
{
   if (panel_is_visible && !panel_minimized && !is_cursor_in_header_or_buttons(last_mouse_x, last_mouse_y)) { //--- Update indicators only if panel is visible, not minimized, and cursor is not in header/buttons
      updateIndicators();                                        //--- Update indicators
   }
}

Vamos atualizar o manipulador de eventos OnTick para otimizar a atualização dos indicadores. Na versão anterior, a função "updateIndicators" era chamada apenas com base no valor de "panel_is_visible". Agora, acrescentaremos condições para que os indicadores sejam atualizados somente quando "panel_is_visible" for true, "panel_minimized" for false e o cursor não estiver sobre o cabeçalho ou os botões. Essa última condição será verificada pela função "is_cursor_in_header_or_buttons", utilizando as coordenadas "last_mouse_x" e "last_mouse_y". Com essa alteração, a atualização dos indicadores será temporariamente suspensa quando o painel estiver recolhido ou quando o usuário interagir com o cabeçalho, o botão de fechamento ou o botão de alternância. Isso reduzirá o processamento desnecessário e melhorará o desempenho durante o arraste do painel e a alternância entre os modos de exibição. Após a compilação, obteremos o seguinte resultado.

IMPROVED PERFORMANCE GIF

A demonstração mostra que o desempenho do painel melhorou e que todos os objetivos propostos foram alcançados. Resta apenas testar o funcionamento do projeto, conforme abordado na seção anterior.


Testes com dados históricos

Realizamos os testes e apresentamos abaixo uma demonstração dos resultados, reunida em uma animação no formato GIF (Graphics Interchange Format).

DASHBOARD BACKTEST


Conclusão

Neste artigo, aprimoramos o painel do scanner multitimeframe em MQL5, dando continuidade ao desenvolvimento iniciado na Parte 3. Adicionamos posicionamento dinâmico, permitindo arrastar o painel pelo gráfico, um botão para alternar entre os estados recolhido e expandido e efeitos visuais interativos quando o cursor passa sobre os elementos da interface, proporcionando maior controle ao usuário. Demonstramos como implementar esses recursos com funções como "create_minimized_dashboard" e "update_header_hover_state", integrando-os à grade de indicadores existente para acompanhar as informações de negociação em tempo real. Você também pode personalizar o painel de acordo com suas necessidades de negociação, tornando o acompanhamento dos sinais de mercado em diferentes timeframes mais eficiente.

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

Últimos Comentários | Ir para discussão (2)
linfo2
linfo2 | 17 jul. 2025 em 17:38
Obrigado, Allan. Isso é muito legal, está bem documentado e aborda recursos que eu não conhecia. Agradeço muito.
Allan Munene Mutiiria
Allan Munene Mutiiria | 17 jul. 2025 em 18:58
linfo2 #:
Obrigado, Allan. Isso é muito legal, está bem documentado e aborda recursos que eu não conhecia. Agradeço muito.
@linfo2, de nada. Muito obrigado.
Implementação em MQL5 de módulos práticos de outras linguagens (Parte 02): criação de uma biblioteca REQUESTS como a do Python Implementação em MQL5 de módulos práticos de outras linguagens (Parte 02): criação de uma biblioteca REQUESTS como a do Python
Neste artigo, vamos apresentar a implementação de um módulo semelhante ao requests do Python, com o objetivo de simplificar o envio de requisições web e o recebimento de respostas no MetaTrader 5 usando MQL5.
Desenvolvimento de ferramentas para análise de Price Action (Parte 31): Módulo de reconhecimento de padrões de candles em Python (I): reconhecimento manual Desenvolvimento de ferramentas para análise de Price Action (Parte 31): Módulo de reconhecimento de padrões de candles em Python (I): reconhecimento manual
Os padrões de candles são fundamentais para a negociação baseada em Price Action e fornecem sinais valiosos de uma possível reversão do mercado ou da continuação da tendência. Imagine uma ferramenta confiável que monitora continuamente cada nova barra, identifica formações importantes, como padrões de engolfo, martelos, dojis e estrelas, e emite um alerta imediato sempre que detecta um setup operacional relevante. Foi exatamente essa funcionalidade que desenvolvemos. Seja você iniciante ou trader experiente, o sistema fornece alertas em tempo real sobre padrões de candles, permitindo abrir operações com mais confiança e eficiência. Continue a leitura para entender como ele funciona e de que forma pode aprimorar sua estratégia de negociação.
Recursos do Assistente MQL5 que você precisa conhecer (Parte 75): Utilização do Awesome Oscillator e do canal Envelopes Recursos do Assistente MQL5 que você precisa conhecer (Parte 75): Utilização do Awesome Oscillator e do canal Envelopes
O Awesome Oscillator de Bill Williams e o canal Envelopes (Envelopes Channel) são dois indicadores que podem se complementar em um EA desenvolvido em MQL5. Utilizamos o Awesome Oscillator para identificar tendências, enquanto o canal Envelopes ajuda a determinar níveis de suporte e resistência. Como de costume, recorremos ao Assistente MQL5 para desenvolver padrões de sinal e testar o potencial dessa combinação de indicadores.
Automatização de estratégias de negociação em MQL5 (Parte 23): Recuperação por zonas com trailing stop e lógica de cestas Automatização de estratégias de negociação em MQL5 (Parte 23): Recuperação por zonas com trailing stop e lógica de cestas
Neste artigo, vamos aprimorar nosso sistema de recuperação por zonas (Zone Recovery System) com a implementação de trailing stops e suporte à negociação com múltiplas cestas. Veremos como a arquitetura aprimorada utiliza trailing stops dinâmicos para proteger os lucros e um mecanismo de gerenciamento de cestas para processar com eficiência múltiplos sinais de negociação. Ao longo da implementação e dos testes com dados históricos, demonstraremos um sistema de negociação mais robusto, capaz de se adaptar às diferentes condições de mercado.