English Русский 中文 Español Deutsch 日本語
preview
Ferramentas de negociação em MQL5 (Parte 2): Aprimorando o assistente de negociação interativo com visualização dinâmican

Ferramentas de negociação em MQL5 (Parte 2): Aprimorando o assistente de negociação interativo com visualização dinâmican

MetaTrader 5Negociação |
25 2
Allan Munene Mutiiria
Allan Munene Mutiiria

Introdução

No artigo anterior, Parte 1, criamos a ferramenta Trade Assistant Tool em MetaQuotes Language 5 (MQL5) para MetaTrader 5, com o objetivo de simplificar o envio de ordens pendentes. Agora vamos avançar, tornando-a mais interativa por meio de feedback visual dinâmico. Implementaremos recursos como um painel de controle arrastável, efeitos ao passar o cursor para tornar a navegação mais intuitiva e validação das ordens em tempo real, garantindo que nossas configurações de negociação sejam precisas e estejam de acordo com as condições do mercado. Veremos essas melhorias nas seguintes seções:

  1. Aprimoramentos conceituais para aumentar a interatividade
  2. Implementação em MQL5
  3. Teste com dados históricos
  4. Conclusão

Essas seções nos ajudarão a criar uma ferramenta de negociação mais responsiva, intuitiva e fácil de usar.


Aprimoramentos conceituais para aumentar a interatividade

Nosso objetivo é aprimorar a ferramenta Trade Assistant, tornando-a mais intuitiva e adaptável. Começaremos com um painel de controle arrastável, que poderá ser posicionado livremente no gráfico. Essa flexibilidade permitirá ajustar a interface ao nosso fluxo de operação, tanto ao trabalhar com vários gráficos quanto ao nos concentrarmos na configuração de uma única operação. Além disso, implementaremos efeitos ao passar o cursor para destacar botões e elementos do gráfico quando o ponteiro estiver sobre eles, fornecendo feedback visual imediato, facilitando a navegação e reduzindo a possibilidade de erros.

A validação das ordens em tempo real será outro aprimoramento importante, garantindo, antes da execução, que os níveis de entrada, Stop Loss e Take Profit estejam logicamente coerentes com os preços atuais de mercado. Esse recurso aumentará nossa confiança ao impedir configurações de negociação inválidas, sem comprometer a simplicidade da ferramenta e com maior precisão. Em conjunto, essas melhorias permitirão criar uma ferramenta adaptável e voltada ao usuário, capaz de auxiliar nossas decisões de negociação e estabelecer uma base para futuros aprimoramentos, como recursos de gestão de risco. Em resumo, a visualização abaixo mostra o que pretendemos alcançar.

VISUALIZAÇÃO DOS OBJETIVOS


Implementação em MQL5

Para atingir esses objetivos em MQL5, primeiro precisaremos definir alguns objetos adicionais do painel, além de variáveis de estado para o arraste e o hover do cursor do mouse. Esses elementos serão usados para monitorar a interação do usuário tanto com o painel quanto com a ferramenta de preço, conforme mostrado a seguir.

// Control panel object names
#define PANEL_BG        "PANEL_BG"        //--- Define constant for panel background object name
#define PANEL_HEADER    "PANEL_HEADER"    //--- Define constant for panel header object name
#define LOT_EDIT        "LOT_EDIT"        //--- Define constant for lot size edit field object name
#define PRICE_LABEL     "PRICE_LABEL"     //--- Define constant for price label object name
#define SL_LABEL        "SL_LABEL"        //--- Define constant for stop-loss label object name
#define TP_LABEL        "TP_LABEL"        //--- Define constant for take-profit label object name
#define BUY_STOP_BTN    "BUY_STOP_BTN"    //--- Define constant for buy stop button object name
#define SELL_STOP_BTN   "SELL_STOP_BTN"   //--- Define constant for sell stop button object name
#define BUY_LIMIT_BTN   "BUY_LIMIT_BTN"   //--- Define constant for buy limit button object name
#define SELL_LIMIT_BTN  "SELL_LIMIT_BTN"  //--- Define constant for sell limit button object name
#define PLACE_ORDER_BTN "PLACE_ORDER_BTN" //--- Define constant for place order button object name
#define CANCEL_BTN      "CANCEL_BTN"      //--- Define constant for cancel button object name
#define CLOSE_BTN       "CLOSE_BTN"       //--- Define constant for close button object name

// Variables for dragging panel
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

// Button and rectangle hover states
bool buy_stop_hovered    = false;         //--- Buy Stop button hover state
bool sell_stop_hovered   = false;         //--- Sell Stop button hover state
bool buy_limit_hovered   = false;         //--- Buy Limit button hover state
bool sell_limit_hovered  = false;         //--- Sell Limit button hover state
bool place_order_hovered = false;         //--- Place Order button hover state
bool cancel_hovered      = false;         //--- Cancel button hover state
bool close_hovered       = false;         //--- Close button hover state
bool header_hovered      = false;         //--- Header hover state
bool rec1_hovered        = false;         //--- REC1 (TP) hover state
bool rec3_hovered        = false;         //--- REC3 (Entry) hover state
bool rec5_hovered        = false;         //--- REC5 (SL) hover state

Começamos a implementar recursos avançados de interatividade em nossa ferramenta, definindo as principais variáveis que permitirão usar o arraste do painel e os efeitos ao passar o cursor do mouse na interface do MetaTrader 5. Usamos a diretiva #define para criar a constante "PANEL_HEADER", correspondente ao objeto usado como cabeçalho do painel, que funcionará como a área arrastável do painel de controle. Para dar suporte ao arraste, declaramos "panel_dragging" como um sinalizador lógico que indica se o painel está sendo movido. Também usamos os inteiros "panel_drag_x" e "panel_drag_y" para armazenar as coordenadas do mouse no início do arraste, enquanto "panel_start_x" e "panel_start_y" registram a posição inicial do painel. Com esses valores, podemos calcular sua nova posição durante o deslocamento.

Também introduzimos variáveis lógicas para controlar o estado de passagem do cursor sobre os botões e retângulos do gráfico. Entre elas estão "buy_stop_hovered", "sell_stop_hovered", "buy_limit_hovered", "sell_limit_hovered", "place_order_hovered", "cancel_hovered", "close_hovered" e "header_hovered", referentes aos respectivos botões e ao cabeçalho do painel, além de "rec1_hovered", "rec3_hovered" e "rec5_hovered", associadas aos retângulos de Take Profit, entrada e Stop Loss. Essas variáveis nos permitirão detectar quando o cursor estiver sobre esses elementos e ativar feedback visual, como a mudança de cor. Isso facilita a navegação e a interação com a interface da ferramenta. Em seguida, precisamos obter os valores da ferramenta de preço e verificar se formam uma configuração válida para a ordem.

//+------------------------------------------------------------------+
//| Check if order setup is valid                                    |
//+------------------------------------------------------------------+
bool isOrderValid() {
   if(!tool_visible) return true;                                     //--- No validation needed if tool is not visible
   double current_price = SymbolInfoDouble(Symbol(), SYMBOL_BID);     //--- Get current bid price
   double entry_price   = Get_Price_d(PR_HL);                         //--- Get entry price
   double sl_price      = Get_Price_d(SL_HL);                         //--- Get stop-loss price
   double tp_price      = Get_Price_d(TP_HL);                         //--- Get take-profit price

   if(selected_order_type == "BUY_STOP") {
      //--- Buy Stop: Entry must be above current price, TP above entry, SL below entry
      if(entry_price <= current_price || tp_price <= entry_price || sl_price >= entry_price) {
         return false;
      }
   }
   else if(selected_order_type == "SELL_STOP") {
      //--- Sell Stop: Entry must be below current price, TP below entry, SL above entry
      if(entry_price >= current_price || tp_price >= entry_price || sl_price <= entry_price) {
         return false;
      }
   }
   else if(selected_order_type == "BUY_LIMIT") {
      //--- Buy Limit: Entry must be below current price, TP above entry, SL below entry
      if(entry_price >= current_price || tp_price <= entry_price || sl_price >= entry_price) {
         return false;
      }
   }
   else if(selected_order_type == "SELL_LIMIT") {
      //--- Sell Limit: Entry must be above current price, TP below entry, SL above entry
      if(entry_price <= current_price || tp_price >= entry_price || sl_price <= entry_price) {
         return false;
      }
   }
   return true;                                                       //--- Order setup is valid
}

Aqui implementamos a função "isOrderValid" para aprimorar a ferramenta. Ela valida as configurações das ordens em tempo real e verifica se as operações estão de acordo com as condições de mercado. Primeiro verificamos se "tool_visible" é false. Nesse caso, retornamos true para ignorar a validação quando a ferramenta não estiver ativa. Em seguida, obtemos o preço atual de mercado com a função SymbolInfoDouble e a propriedade SYMBOL_BID. Os preços de entrada ("entry_price"), Stop Loss ("sl_price") e Take Profit ("tp_price") são obtidos com a função "Get_Price_d" para "PR_HL", "SL_HL" e "TP_HL".

Para "BUY_STOP", verificamos se "entry_price" está acima de "current_price", se "tp_price" está acima de "entry_price" e se "sl_price" está abaixo de "entry_price". Para "SELL_STOP", "entry_price" deve estar abaixo de "current_price", "tp_price" abaixo de "entry_price" e "sl_price" acima de "entry_price". Para "BUY_LIMIT", "entry_price" deve estar abaixo de "current_price", "tp_price" acima de "entry_price" e "sl_price" abaixo de "entry_price". Já para "SELL_LIMIT", "entry_price" deve estar acima de "current_price", "tp_price" abaixo de "entry_price" e "sl_price" acima de "entry_price". Se qualquer uma dessas condições não for atendida, a função retorna false; caso contrário, retorna true. Depois disso, podemos atualizar as cores dos retângulos de acordo com a validade da configuração da ordem.

//+------------------------------------------------------------------+
//| Update rectangle colors based on order validity and hover        |
//+------------------------------------------------------------------+
void updateRectangleColors() {
   if(!tool_visible) return;                                                             //--- Skip if tool is not visible
   bool is_valid = isOrderValid();                                                       //--- Check order validity

   if(!is_valid) {
      //--- Gray out REC1 and REC5 if order is invalid, with hover effect
      ObjectSetInteger(0, REC1, OBJPROP_BGCOLOR, rec1_hovered ? C'100,100,100' : clrGray);
      ObjectSetInteger(0, REC5, OBJPROP_BGCOLOR, rec5_hovered ? C'100,100,100' : clrGray);
   }
   else {
      //--- Restore original colors based on order type and hover state
      if(selected_order_type == "BUY_STOP" || selected_order_type == "BUY_LIMIT") {
         ObjectSetInteger(0, REC1, OBJPROP_BGCOLOR, rec1_hovered ? C'0,100,0'   : clrGreen); //--- TP rectangle (dark green on hover)
         ObjectSetInteger(0, REC5, OBJPROP_BGCOLOR, rec5_hovered ? C'139,0,0'   : clrRed);   //--- SL rectangle (dark red on hover)
      }
      else {
         ObjectSetInteger(0, REC1, OBJPROP_BGCOLOR, rec1_hovered ? C'0,100,0'   : clrGreen); //--- TP rectangle (dark green on hover)
         ObjectSetInteger(0, REC5, OBJPROP_BGCOLOR, rec5_hovered ? C'139,0,0'   : clrRed);   //--- SL rectangle (dark red on hover)
      }
   }

   ObjectSetInteger(0, REC3, OBJPROP_BGCOLOR, rec3_hovered ? C'105,105,105' : clrLightGray); //--- Entry rectangle (darker gray on hover)
   ChartRedraw(0);                                                                          //--- Redraw chart
}

Implementamos a função "updateRectangleColors" para aprimorar o feedback visual da ferramenta, atualizando as cores dos retângulos do gráfico de acordo com a validade da configuração da ordem e com o estado de passagem do cursor do mouse. Se "tool_visible" for false, a função não faz nenhuma alteração. Caso contrário, verificamos a validade da configuração com "isOrderValid" e usamos ObjectSetInteger para definir as cores de "REC1" (TP) e "REC5" (SL). Quando a configuração da ordem não for válida, esses retângulos ficam em cinza ("clrGray" ou "C'100,100,100'" quando "rec1_hovered"/"rec5_hovered" estiver ativo). Se a configuração for válida, usamos verde para ordens "BUY_STOP"/"BUY_LIMIT" e vermelho para ordens de venda, com "clrGreen"/"clrRed" ou, ao passar o cursor, "C'0,100,0'"/"C'139,0,0'". Para "REC3" (entrada), usamos cinza-claro ("clrLightGray" ou "C'105,105,105'" quando "rec3_hovered" estiver ativo). Ao final, chamamos ChartRedraw para atualizar o gráfico.

Depois disso, precisamos obter os estados de passagem do cursor sobre os botões, conforme mostrado abaixo.

//+------------------------------------------------------------------+
//| Update button and header hover state                             |
//+------------------------------------------------------------------+
void updateButtonHoverState(int mouse_x, int mouse_y) {
   // Define button names and their properties
   string buttons[] = {BUY_STOP_BTN, SELL_STOP_BTN, BUY_LIMIT_BTN, SELL_LIMIT_BTN, PLACE_ORDER_BTN, CANCEL_BTN, CLOSE_BTN};
   bool hover_states[] = {buy_stop_hovered, sell_stop_hovered, buy_limit_hovered, sell_limit_hovered, place_order_hovered, cancel_hovered, close_hovered};
   color normal_colors[] = {clrForestGreen, clrFireBrick, clrForestGreen, clrFireBrick, clrDodgerBlue, clrSlateGray, clrCrimson};
   color hover_color = clrDodgerBlue;                      //--- Bluish color for hover
   color hover_border = clrBlue;                           //--- Bluish border for hover

   for(int i = 0; i < ArraySize(buttons); i++) {
      int x = (int)ObjectGetInteger(0, buttons[i], OBJPROP_XDISTANCE);
      int y = (int)ObjectGetInteger(0, buttons[i], OBJPROP_YDISTANCE);
      int width = (int)ObjectGetInteger(0, buttons[i], OBJPROP_XSIZE);
      int height = (int)ObjectGetInteger(0, buttons[i], OBJPROP_YSIZE);

      bool is_hovered = (mouse_x >= x && mouse_x <= x + width && mouse_y >= y && mouse_y <= y + height);

      if(is_hovered && !hover_states[i]) {
         // Mouse entered button
         ObjectSetInteger(0, buttons[i], OBJPROP_BGCOLOR, hover_color);
         ObjectSetInteger(0, buttons[i], OBJPROP_BORDER_COLOR, hover_border);
         hover_states[i] = true;
      }
      else if(!is_hovered && hover_states[i]) {
         // Mouse left button
         ObjectSetInteger(0, buttons[i], OBJPROP_BGCOLOR, normal_colors[i]);
         ObjectSetInteger(0, buttons[i], OBJPROP_BORDER_COLOR, clrBlack);
         hover_states[i] = false;
      }
   }

   // Update header hover state
   int header_x = (int)ObjectGetInteger(0, PANEL_HEADER, OBJPROP_XDISTANCE);
   int header_y = (int)ObjectGetInteger(0, PANEL_HEADER, OBJPROP_YDISTANCE);
   int header_width = (int)ObjectGetInteger(0, PANEL_HEADER, OBJPROP_XSIZE);
   int header_height = (int)ObjectGetInteger(0, PANEL_HEADER, OBJPROP_YSIZE);

   bool is_header_hovered = (mouse_x >= header_x && mouse_x <= header_x + header_width && mouse_y >= header_y && mouse_y <= header_y + header_height);

   if(is_header_hovered && !header_hovered) {
      ObjectSetInteger(0, PANEL_HEADER, OBJPROP_BGCOLOR, C'030,030,030'); //--- Darken header
      header_hovered = true;
   }
   else if(!is_header_hovered && header_hovered) {
      ObjectSetInteger(0, PANEL_HEADER, OBJPROP_BGCOLOR, C'050,050,050'); //--- Restore header color
      header_hovered = false;
   }

   // Update tool rectangle hover states
   if(tool_visible) {
      int x1 = (int)ObjectGetInteger(0, REC1, OBJPROP_XDISTANCE);
      int y1 = (int)ObjectGetInteger(0, REC1, OBJPROP_YDISTANCE);
      int width1 = (int)ObjectGetInteger(0, REC1, OBJPROP_XSIZE);
      int height1 = (int)ObjectGetInteger(0, REC1, OBJPROP_YSIZE);

      int x3 = (int)ObjectGetInteger(0, REC3, OBJPROP_XDISTANCE);
      int y3 = (int)ObjectGetInteger(0, REC3, OBJPROP_YDISTANCE);
      int width3 = (int)ObjectGetInteger(0, REC3, OBJPROP_XSIZE);
      int height3 = (int)ObjectGetInteger(0, REC3, OBJPROP_YSIZE);

      int x5 = (int)ObjectGetInteger(0, REC5, OBJPROP_XDISTANCE);
      int y5 = (int)ObjectGetInteger(0, REC5, OBJPROP_YDISTANCE);
      int width5 = (int)ObjectGetInteger(0, REC5, OBJPROP_XSIZE);
      int height5 = (int)ObjectGetInteger(0, REC5, OBJPROP_YSIZE);

      bool is_rec1_hovered = (mouse_x >= x1 && mouse_x <= x1 + width1 && mouse_y >= y1 && mouse_y <= y1 + height1);
      bool is_rec3_hovered = (mouse_x >= x3 && mouse_x <= x3 + width3 && mouse_y >= y3 && mouse_y <= y3 + height3);
      bool is_rec5_hovered = (mouse_x >= x5 && mouse_x <= x5 + width5 && mouse_y >= y5 && mouse_y <= y5 + height5);

      if(is_rec1_hovered != rec1_hovered || is_rec3_hovered != rec3_hovered || is_rec5_hovered != rec5_hovered) {
         rec1_hovered = is_rec1_hovered;
         rec3_hovered = is_rec3_hovered;
         rec5_hovered = is_rec5_hovered;
         updateRectangleColors();                            //--- Update colors based on hover state
      }
   }

   // Update hover state variables
   buy_stop_hovered = hover_states[0];
   sell_stop_hovered = hover_states[1];
   buy_limit_hovered = hover_states[2];
   sell_limit_hovered = hover_states[3];
   place_order_hovered = hover_states[4];
   cancel_hovered = hover_states[5];
   close_hovered = hover_states[6];

   ChartRedraw(0);                                           //--- Redraw chart
}

Para aumentar a interatividade da ferramenta por meio dos efeitos ao passar o cursor sobre os botões e elementos do gráfico, implementamos a função "updateButtonHoverState". Definimos o array "buttons" com os nomes dos botões, de "BUY_STOP_BTN" a "CLOSE_BTN"; o array "hover_states" com os respectivos sinalizadores de passagem do cursor, de "buy_stop_hovered" a "close_hovered"; e o array "normal_colors" com as cores padrão. Para o estado de passagem do cursor, usamos "hover_color" (clrDodgerBlue) e "hover_border" ("clrBlue").

Para cada botão, usamos ObjectGetInteger para obter sua posição e dimensões e verificamos se "mouse_x" e "mouse_y" estão dentro de seus limites. Em seguida, usamos ObjectSetInteger para atualizar "OBJPROP_BGCOLOR" e "OBJPROP_BORDER_COLOR" com "hover_color" ou com a cor padrão correspondente e ajustamos o respectivo valor em "hover_states".

Para "PANEL_HEADER", realizamos uma verificação semelhante, mediante ObjectSetInteger, e escurecemos o cabeçalho para "C'030,030,030'" quando o cursor estiver sobre ele, restaurando "C'050,050,050'" quando o cursor sair da área. Quando "tool_visible" estiver ativo, também verificamos os limites de "REC1", "REC3" e "REC5", atualizamos "rec1_hovered", "rec3_hovered" e "rec5_hovered" e chamamos "updateRectangleColors" sempre que algum desses estados mudar. Em seguida, atualizamos as variáveis de estado de passagem do cursor, de "buy_stop_hovered" a "close_hovered", com os valores armazenados em "hover_states" e chamamos ChartRedraw para redesenhar o gráfico. Com isso, podemos chamar essas funções no manipulador de eventos OnChartEvent e atualizar a interface em tempo real.

//+------------------------------------------------------------------+
//| Expert onchart event function                                    |
//+------------------------------------------------------------------+
void OnChartEvent(
   const int id,          //--- Event ID
   const long& lparam,    //--- Long parameter (e.g., x-coordinate for mouse)
   const double& dparam,  //--- Double parameter (e.g., y-coordinate for mouse)
   const string& sparam   //--- String parameter (e.g., object name)
) {
   if(id == CHARTEVENT_OBJECT_CLICK) {                           //--- Handle object click events
      // Handle order type buttons
      if(sparam == BUY_STOP_BTN) {                               //--- Check if Buy Stop button clicked
         selected_order_type = "BUY_STOP";                       //--- Set order type to Buy Stop
         showTool();                                             //--- Show trading tool
         update_Text(PLACE_ORDER_BTN, "Place Buy Stop");         //--- Update place order button text
         updateRectangleColors();                                //--- Update rectangle colors
      }
      else if(sparam == SELL_STOP_BTN) {                         //--- Check if Sell Stop button clicked
         selected_order_type = "SELL_STOP";                      //--- Set order type to Sell Stop
         showTool();                                             //--- Show trading tool
         update_Text(PLACE_ORDER_BTN, "Place Sell Stop");        //--- Update place order button text
         updateRectangleColors();                                //--- Update rectangle colors
      }
      else if(sparam == BUY_LIMIT_BTN) {                         //--- Check if Buy Limit button clicked
         selected_order_type = "BUY_LIMIT";                      //--- Set order type to Buy Limit
         showTool();                                             //--- Show trading tool
         update_Text(PLACE_ORDER_BTN, "Place Buy Limit");        //--- Update place order button text
         updateRectangleColors();                                //--- Update rectangle colors
      }
      else if(sparam == SELL_LIMIT_BTN) {                        //--- Check if Sell Limit button clicked
         selected_order_type = "SELL_LIMIT";                     //--- Set order type to Sell Limit
         showTool();                                             //--- Show trading tool
         update_Text(PLACE_ORDER_BTN, "Place Sell Limit");       //--- Update place order button text
         updateRectangleColors();                                //--- Update rectangle colors
      }
      else if(sparam == PLACE_ORDER_BTN) {                       //--- Check if Place Order button clicked
         if(isOrderValid()) {
            placeOrder();                                        //--- Execute order placement
            deleteObjects();                                     //--- Delete tool objects
            showPanel();                                         //--- Show control panel
         }
         else {
            Print("Cannot place order: Invalid price setup for ", selected_order_type);
         }
      }
      else if(sparam == CANCEL_BTN) {                            //--- Check if Cancel button clicked
         deleteObjects();                                        //--- Delete tool objects
         showPanel();                                            //--- Show control panel
      }
      else if(sparam == CLOSE_BTN) {                             //--- Check if Close button clicked
         deleteObjects();                                        //--- Delete tool objects
         deletePanel();                                          //--- Delete control panel
         ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);      //--- Disable mouse move events
      }
      ObjectSetInteger(0, sparam, OBJPROP_STATE, false);         //--- Reset button state click
      ChartRedraw(0);                                            //--- Redraw chart
   }

   if(id == CHARTEVENT_MOUSE_MOVE) {                             //--- Handle mouse move events
      int MouseD_X = (int)lparam;                                //--- Get mouse x-coordinate
      int MouseD_Y = (int)dparam;                                //--- Get mouse y-coordinate
      int MouseState = (int)sparam;                              //--- Get mouse state

      // Update button and rectangle hover states
      updateButtonHoverState(MouseD_X, MouseD_Y);

      // Handle panel dragging
      int header_xd = (int)ObjectGetInteger(0, PANEL_HEADER, OBJPROP_XDISTANCE);
      int header_yd = (int)ObjectGetInteger(0, PANEL_HEADER, OBJPROP_YDISTANCE);
      int header_xs = (int)ObjectGetInteger(0, PANEL_HEADER, OBJPROP_XSIZE);
      int header_ys = (int)ObjectGetInteger(0, PANEL_HEADER, OBJPROP_YSIZE);

      if(prevMouseState == 0 && MouseState == 1) {               //--- Mouse button down
         if(MouseD_X >= header_xd && MouseD_X <= header_xd + header_xs &&
            MouseD_Y >= header_yd && MouseD_Y <= header_yd + header_ys) {
            panel_dragging = true;                               //--- Start dragging
            panel_drag_x = MouseD_X;                             //--- Store mouse x-coordinate
            panel_drag_y = MouseD_Y;                             //--- Store mouse y-coordinate
            panel_start_x = header_xd;                           //--- Store panel x-coordinate
            panel_start_y = header_yd;                           //--- Store panel y-coordinate
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);       //--- Disable chart scrolling
         }
      }

      if(panel_dragging && MouseState == 1) {                    //--- Dragging panel
         int dx = MouseD_X - panel_drag_x;                       //--- Calculate x displacement
         int dy = MouseD_Y - panel_drag_y;                       //--- Calculate y displacement
         panel_x = panel_start_x + dx;                           //--- Update panel x-position
         panel_y = panel_start_y + dy;                           //--- Update panel y-position

         // Update all panel objects' positions
         ObjectSetInteger(0, PANEL_BG, OBJPROP_XDISTANCE, panel_x);
         ObjectSetInteger(0, PANEL_BG, OBJPROP_YDISTANCE, panel_y);
         ObjectSetInteger(0, PANEL_HEADER, OBJPROP_XDISTANCE, panel_x);
         ObjectSetInteger(0, PANEL_HEADER, OBJPROP_YDISTANCE, panel_y+2);
         ObjectSetInteger(0, CLOSE_BTN, OBJPROP_XDISTANCE, panel_x + 209);
         ObjectSetInteger(0, CLOSE_BTN, OBJPROP_YDISTANCE, panel_y + 1);
         ObjectSetInteger(0, LOT_EDIT, OBJPROP_XDISTANCE, panel_x + 70);
         ObjectSetInteger(0, LOT_EDIT, OBJPROP_YDISTANCE, panel_y + 40);
         ObjectSetInteger(0, PRICE_LABEL, OBJPROP_XDISTANCE, panel_x + 10);
         ObjectSetInteger(0, PRICE_LABEL, OBJPROP_YDISTANCE, panel_y + 70);
         ObjectSetInteger(0, SL_LABEL, OBJPROP_XDISTANCE, panel_x + 10);
         ObjectSetInteger(0, SL_LABEL, OBJPROP_YDISTANCE, panel_y + 95);
         ObjectSetInteger(0, TP_LABEL, OBJPROP_XDISTANCE, panel_x + 130);
         ObjectSetInteger(0, TP_LABEL, OBJPROP_YDISTANCE, panel_y + 95);
         ObjectSetInteger(0, BUY_STOP_BTN, OBJPROP_XDISTANCE, panel_x + 10);
         ObjectSetInteger(0, BUY_STOP_BTN, OBJPROP_YDISTANCE, panel_y + 140);
         ObjectSetInteger(0, SELL_STOP_BTN, OBJPROP_XDISTANCE, panel_x + 130);
         ObjectSetInteger(0, SELL_STOP_BTN, OBJPROP_YDISTANCE, panel_y + 140);
         ObjectSetInteger(0, BUY_LIMIT_BTN, OBJPROP_XDISTANCE, panel_x + 10);
         ObjectSetInteger(0, BUY_LIMIT_BTN, OBJPROP_YDISTANCE, panel_y + 180);
         ObjectSetInteger(0, SELL_LIMIT_BTN, OBJPROP_XDISTANCE, panel_x + 130);
         ObjectSetInteger(0, SELL_LIMIT_BTN, OBJPROP_YDISTANCE, panel_y + 180);
         ObjectSetInteger(0, PLACE_ORDER_BTN, OBJPROP_XDISTANCE, panel_x + 10);
         ObjectSetInteger(0, PLACE_ORDER_BTN, OBJPROP_YDISTANCE, panel_y + 240);
         ObjectSetInteger(0, CANCEL_BTN, OBJPROP_XDISTANCE, panel_x + 130);
         ObjectSetInteger(0, CANCEL_BTN, OBJPROP_YDISTANCE, panel_y + 240);

         ChartRedraw(0);                                                   //--- Redraw chart
      }

      if(MouseState == 0) {                                                //--- Mouse button released
         if(panel_dragging) {
            panel_dragging = false;                                        //--- Stop dragging
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true);                  //--- Re-enable chart scrolling
         }
      }

      if(tool_visible) {                                                   //--- Handle tool movement
         int XD_R1 = (int)ObjectGetInteger(0, REC1, OBJPROP_XDISTANCE);    //--- Get REC1 x-distance
         int YD_R1 = (int)ObjectGetInteger(0, REC1, OBJPROP_YDISTANCE);    //--- Get REC1 y-distance
         int XS_R1 = (int)ObjectGetInteger(0, REC1, OBJPROP_XSIZE);        //--- Get REC1 x-size
         int YS_R1 = (int)ObjectGetInteger(0, REC1, OBJPROP_YSIZE);        //--- Get REC1 y-size

         int XD_R2 = (int)ObjectGetInteger(0, REC2, OBJPROP_XDISTANCE);    //--- Get REC2 x-distance
         int YD_R2 = (int)ObjectGetInteger(0, REC2, OBJPROP_YDISTANCE);    //--- Get REC2 y-distance
         int XS_R2 = (int)ObjectGetInteger(0, REC2, OBJPROP_XSIZE);        //--- Get REC2 x-size
         int YS_R2 = (int)ObjectGetInteger(0, REC2, OBJPROP_YSIZE);        //--- Get REC2 y-size

         int XD_R3 = (int)ObjectGetInteger(0, REC3, OBJPROP_XDISTANCE);    //--- Get REC3 x-distance
         int YD_R3 = (int)ObjectGetInteger(0, REC3, OBJPROP_YDISTANCE);    //--- Get REC3 y-distance
         int XS_R3 = (int)ObjectGetInteger(0, REC3, OBJPROP_XSIZE);        //--- Get REC3 x-size
         int YS_R3 = (int)ObjectGetInteger(0, REC3, OBJPROP_YSIZE);        //--- Get REC3 y-size

         int XD_R4 = (int)ObjectGetInteger(0, REC4, OBJPROP_XDISTANCE);    //--- Get REC4 x-distance
         int YD_R4 = (int)ObjectGetInteger(0, REC4, OBJPROP_YDISTANCE);    //--- Get REC4 y-distance
         int XS_R4 = (int)ObjectGetInteger(0, REC4, OBJPROP_XSIZE);        //--- Get REC4 x-size
         int YS_R4 = (int)ObjectGetInteger(0, REC4, OBJPROP_YSIZE);        //--- Get REC4 y-size

         int XD_R5 = (int)ObjectGetInteger(0, REC5, OBJPROP_XDISTANCE);    //--- Get REC5 x-distance
         int YD_R5 = (int)ObjectGetInteger(0, REC5, OBJPROP_YDISTANCE);    //--- Get REC5 y-distance
         int XS_R5 = (int)ObjectGetInteger(0, REC5, OBJPROP_XSIZE);        //--- Get REC5 x-size
         int YS_R5 = (int)ObjectGetInteger(0, REC5, OBJPROP_YSIZE);        //--- Get REC5 y-size

         if(prevMouseState == 0 && MouseState == 1 && !panel_dragging) {   //--- Check for mouse button down, avoid dragging conflict
            mlbDownX1 = MouseD_X;                                          //--- Store mouse x-coordinate for REC1
            mlbDownY1 = MouseD_Y;                                          //--- Store mouse y-coordinate for REC1
            mlbDownXD_R1 = XD_R1;                                          //--- Store REC1 x-distance
            mlbDownYD_R1 = YD_R1;                                          //--- Store REC1 y-distance
            
            mlbDownX2 = MouseD_X;                                          //--- Store mouse x-coordinate for REC2
            mlbDownY2 = MouseD_Y;                                          //--- Store mouse y-coordinate for REC2
            mlbDownXD_R2 = XD_R2;                                          //--- Store REC2 x-distance
            mlbDownYD_R2 = YD_R2;                                          //--- Store REC2 y-distance

            mlbDownX3 = MouseD_X;                                          //--- Store mouse x-coordinate for REC3
            mlbDownY3 = MouseD_Y;                                          //--- Store mouse y-coordinate for REC3
            mlbDownXD_R3 = XD_R3;                                          //--- Store REC3 x-distance
            mlbDownYD_R3 = YD_R3;                                          //--- Store REC3 y-distance
            
            mlbDownX4 = MouseD_X;                                          //--- Store mouse x-coordinate for REC4
            mlbDownY4 = MouseD_Y;                                          //--- Store mouse y-coordinate for REC4
            mlbDownXD_R4 = XD_R4;                                          //--- Store REC4 x-distance
            mlbDownYD_R4 = YD_R4;                                          //--- Store REC4 y-distance

            mlbDownX5 = MouseD_X;                                          //--- Store mouse x-coordinate for REC5
            mlbDownY5 = MouseD_Y;                                          //--- Store mouse y-coordinate for REC5
            mlbDownXD_R5 = XD_R5;                                          //--- Store REC5 x-distance
            mlbDownYD_R5 = YD_R5;                                          //--- Store REC5 y-distance

            if(MouseD_X >= XD_R1 && MouseD_X <= XD_R1 + XS_R1 &&           //--- Check if mouse is within REC1 bounds
               MouseD_Y >= YD_R1 && MouseD_Y <= YD_R1 + YS_R1) {
               movingState_R1 = true;                                      //--- Enable REC1 movement
               ChartSetInteger(0, CHART_MOUSE_SCROLL, false);              //--- Disable chart scrolling
            }
            if(MouseD_X >= XD_R3 && MouseD_X <= XD_R3 + XS_R3 &&           //--- Check if mouse is within REC3 bounds
               MouseD_Y >= YD_R3 && MouseD_Y <= YD_R3 + YS_R3) {
               movingState_R3 = true;                                      //--- Enable REC3 movement
               ChartSetInteger(0, CHART_MOUSE_SCROLL, false);              //--- Disable chart scrolling
            }
            if(MouseD_X >= XD_R5 && MouseD_X <= XD_R5 + XS_R5 &&           //--- Check if mouse is within REC5 bounds
               MouseD_Y >= YD_R5 && MouseD_Y <= YD_R5 + YS_R5) {
               movingState_R5 = true;                                      //--- Enable REC5 movement
               ChartSetInteger(0, CHART_MOUSE_SCROLL, false);              //--- Disable chart scrolling
            }
         }
         if(movingState_R1) {                                                                           //--- Handle REC1 (TP) movement
            bool canMove = false;                                                                       //--- Flag to check if movement is valid
            if(selected_order_type == "BUY_STOP" || selected_order_type == "BUY_LIMIT") {               //--- Check for buy orders
               if(YD_R1 + YS_R1 < YD_R3) {                                                              //--- Ensure TP is above entry for buy orders
                  canMove = true;                                                                       //--- Allow movement
                  ObjectSetInteger(0, REC1, OBJPROP_YDISTANCE, mlbDownYD_R1 + MouseD_Y - mlbDownY1);    //--- Update REC1 y-position
                  ObjectSetInteger(0, REC2, OBJPROP_YDISTANCE, YD_R1 + YS_R1);                          //--- Update REC2 y-position
                  ObjectSetInteger(0, REC2, OBJPROP_YSIZE, YD_R3 - (YD_R1 + YS_R1));                    //--- Update REC2 y-size
               }
            }
            else {                                                                                      //--- Handle sell orders
               if(YD_R1 > YD_R3 + YS_R3) {                                                              //--- Ensure TP is below entry for sell orders
                  canMove = true;                                                                       //--- Allow movement
                  ObjectSetInteger(0, REC1, OBJPROP_YDISTANCE, mlbDownYD_R1 + MouseD_Y - mlbDownY1);    //--- Update REC1 y-position
                  ObjectSetInteger(0, REC4, OBJPROP_YDISTANCE, YD_R3 + YS_R3);                          //--- Update REC4 y-position
                  ObjectSetInteger(0, REC4, OBJPROP_YSIZE, YD_R1 - (YD_R3 + YS_R3));                    //--- Update REC4 y-size
               }
            }
            
            if(canMove) {                                                                               //--- If movement is valid
               datetime dt_TP = 0;                                                                      //--- Variable for TP time
               double price_TP = 0;                                                                     //--- Variable for TP price
               int window = 0;                                                                          //--- Chart window
               
               ChartXYToTimePrice(0, XD_R1, YD_R1 + YS_R1, window, dt_TP, price_TP);                    //--- Convert chart coordinates to time and price
               ObjectSetInteger(0, TP_HL, OBJPROP_TIME, dt_TP);                                         //--- Update TP horizontal line time
               ObjectSetDouble(0, TP_HL, OBJPROP_PRICE, price_TP);                                      //--- Update TP horizontal line price
               
               update_Text(REC1, "TP: " + DoubleToString(MathAbs((Get_Price_d(TP_HL) - Get_Price_d(PR_HL)) / _Point), 0) + " Points | " + Get_Price_s(TP_HL)); //--- Update REC1 text
               update_Text(TP_LABEL, "TP: " + Get_Price_s(TP_HL));                                      //--- Update TP label text
            }

            updateRectangleColors();                                                                    //--- Update rectangle colors
            ChartRedraw(0);                                                                             //--- Redraw chart
         }
         
         if(movingState_R5) {                                                                           //--- Handle REC5 (SL) movement
            bool canMove = false;                                                                       //--- Flag to check if movement is valid
            if(selected_order_type == "BUY_STOP" || selected_order_type == "BUY_LIMIT") {               //--- Check for buy orders
               if(YD_R5 > YD_R4) {                                                                      //--- Ensure SL is below entry for buy orders
                  canMove = true;                                                                       //--- Allow movement
                  ObjectSetInteger(0, REC5, OBJPROP_YDISTANCE, mlbDownYD_R5 + MouseD_Y - mlbDownY5);    //--- Update REC5 y-position
                  ObjectSetInteger(0, REC4, OBJPROP_YDISTANCE, YD_R3 + YS_R3);                          //--- Update REC4 y-position
                  ObjectSetInteger(0, REC4, OBJPROP_YSIZE, YD_R5 - (YD_R3 + YS_R3));                    //--- Update REC4 y-size
               }
            }
            else {                                                                                      //--- Handle sell orders
               if(YD_R5 + YS_R5 < YD_R3) {                                                              //--- Ensure SL is above entry for sell orders
                  canMove = true;                                                                       //--- Allow movement
                  ObjectSetInteger(0, REC5, OBJPROP_YDISTANCE, mlbDownYD_R5 + MouseD_Y - mlbDownY5);    //--- Update REC5 y-position
                  ObjectSetInteger(0, REC2, OBJPROP_YDISTANCE, YD_R5 + YS_R5);                          //--- Update REC2 y-position
                  ObjectSetInteger(0, REC2, OBJPROP_YSIZE, YD_R3 - (YD_R5 + YS_R5));                    //--- Update REC2 y-size
               }
            }
            
            if(canMove) {                                                                               //--- If movement is valid
               datetime dt_SL = 0;                                                                      //--- Variable for SL time
               double price_SL = 0;                                                                     //--- Variable for SL price
               int window = 0;                                                                          //--- Chart window
               
               ChartXYToTimePrice(0, XD_R5, YD_R5 + YS_R5, window, dt_SL, price_SL);                    //--- Convert chart coordinates to time and price
               ObjectSetInteger(0, SL_HL, OBJPROP_TIME, dt_SL);                                         //--- Update SL horizontal line time
               ObjectSetDouble(0, SL_HL, OBJPROP_PRICE, price_SL);                                      //--- Update SL horizontal line price
               
               update_Text(REC5, "SL: " + DoubleToString(MathAbs((Get_Price_d(PR_HL) - Get_Price_d(SL_HL)) / _Point), 0) + " Points | " + Get_Price_s(SL_HL)); //--- Update REC5 text
               update_Text(SL_LABEL, "SL: " + Get_Price_s(SL_HL));                                      //--- Update SL label text
            }

            updateRectangleColors();                                                                    //--- Update rectangle colors
            ChartRedraw(0);                                                                             //--- Redraw chart
         }
         
         if(movingState_R3) { //--- Handle REC3 (Entry) movement
            ObjectSetInteger(0, REC3, OBJPROP_XDISTANCE, mlbDownXD_R3 + MouseD_X - mlbDownX3); //--- Update REC3 x-position
            ObjectSetInteger(0, REC3, OBJPROP_YDISTANCE, mlbDownYD_R3 + MouseD_Y - mlbDownY3); //--- Update REC3 y-position
            
            ObjectSetInteger(0, REC1, OBJPROP_XDISTANCE, mlbDownXD_R1 + MouseD_X - mlbDownX1); //--- Update REC1 x-position
            ObjectSetInteger(0, REC1, OBJPROP_YDISTANCE, mlbDownYD_R1 + MouseD_Y - mlbDownY1); //--- Update REC1 y-position
            
            ObjectSetInteger(0, REC2, OBJPROP_XDISTANCE, mlbDownXD_R2 + MouseD_X - mlbDownX2); //--- Update REC2 x-position
            ObjectSetInteger(0, REC2, OBJPROP_YDISTANCE, mlbDownYD_R2 + MouseD_Y - mlbDownY2); //--- Update REC2 y-position

            ObjectSetInteger(0, REC4, OBJPROP_XDISTANCE, mlbDownXD_R4 + MouseD_X - mlbDownX4); //--- Update REC4 x-position
            ObjectSetInteger(0, REC4, OBJPROP_YDISTANCE, mlbDownYD_R4 + MouseD_Y - mlbDownY4); //--- Update REC4 y-position

            ObjectSetInteger(0, REC5, OBJPROP_XDISTANCE, mlbDownXD_R5 + MouseD_X - mlbDownX5); //--- Update REC5 x-position
            ObjectSetInteger(0, REC5, OBJPROP_YDISTANCE, mlbDownYD_R5 + MouseD_Y - mlbDownY5); //--- Update REC5 y-position

            datetime dt_PRC = 0, dt_SL1 = 0, dt_TP1 = 0;                                       //--- Variables for time
            double price_PRC = 0, price_SL1 = 0, price_TP1 = 0;                                //--- Variables for price
            int window = 0;                                                                    //--- Chart window
            
            ChartXYToTimePrice(0, XD_R3, YD_R3 + YS_R3, window, dt_PRC, price_PRC);            //--- Convert REC3 coordinates to time and price
            ChartXYToTimePrice(0, XD_R5, YD_R5 + YS_R5, window, dt_SL1, price_SL1);            //--- Convert REC5 coordinates to time and price
            ChartXYToTimePrice(0, XD_R1, YD_R1 + YS_R1, window, dt_TP1, price_TP1);            //--- Convert REC1 coordinates to time and price

            ObjectSetInteger(0, PR_HL, OBJPROP_TIME, dt_PRC);                                  //--- Update entry horizontal line time
            ObjectSetDouble(0, PR_HL, OBJPROP_PRICE, price_PRC);                               //--- Update entry horizontal line price
            
            ObjectSetInteger(0, TP_HL, OBJPROP_TIME, dt_TP1);                                  //--- Update TP horizontal line time
            ObjectSetDouble(0, TP_HL, OBJPROP_PRICE, price_TP1);                               //--- Update TP horizontal line price
            
            ObjectSetInteger(0, SL_HL, OBJPROP_TIME, dt_SL1);                                  //--- Update SL horizontal line time
            ObjectSetDouble(0, SL_HL, OBJPROP_PRICE, price_SL1);                               //--- Update SL horizontal line price

            update_Text(REC1, "TP: " + DoubleToString(MathAbs((Get_Price_d(TP_HL) - Get_Price_d(PR_HL)) / _Point), 0) + " Points | " + Get_Price_s(TP_HL)); //--- Update REC1 text
            update_Text(REC3, selected_order_type + ": | Lot: " + DoubleToString(lot_size, 2) + " | " + Get_Price_s(PR_HL));                                //--- Update REC3 text
            update_Text(REC5, "SL: " + DoubleToString(MathAbs((Get_Price_d(PR_HL) - Get_Price_d(SL_HL)) / _Point), 0) + " Points | " + Get_Price_s(SL_HL)); //--- Update REC5 text
            update_Text(PRICE_LABEL, "Entry: " + Get_Price_s(PR_HL));                                                                                       //--- Update entry label text
            update_Text(SL_LABEL, "SL: " + Get_Price_s(SL_HL));                                                                                             //--- Update SL label text
            update_Text(TP_LABEL, "TP: " + Get_Price_s(TP_HL));                                                                                             //--- Update TP label text

            updateRectangleColors(); //--- Update rectangle colors
            ChartRedraw(0); //--- Redraw chart
         }

         if(MouseState == 0) {                               //--- Check if mouse button is released
            movingState_R1 = false;                          //--- Disable REC1 movement
            movingState_R3 = false;                          //--- Disable REC3 movement
            movingState_R5 = false;                          //--- Disable REC5 movement
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true);    //--- Enable chart scrolling
         }
      }
      prevMouseState = MouseState;                           //--- Update previous mouse state
   }
}

Como a função OnChartEvent já foi definida, vamos nos concentrar apenas na lógica adicional que incorporamos para dar suporte aos novos recursos interativos, como o arraste do painel, a atualização dos estados de passagem do cursor e a validação das ordens. Para CHARTEVENT_OBJECT_CLICK, ampliamos o tratamento dos cliques nos botões "BUY_STOP_BTN", "SELL_STOP_BTN", "BUY_LIMIT_BTN" e "SELL_LIMIT_BTN", chamando "updateRectangleColors" para refletir visualmente a validade da configuração da ordem. Para "PLACE_ORDER_BTN", adicionamos uma validação com "isOrderValid"; se a configuração não for válida, registramos uma mensagem de erro com Print e impedimos o envio de uma ordem inválida, conforme mostrado abaixo.

VALIDADE DA ORDEM

Também chamamos a função "updateButtonHoverState" após os cliques para atualizar os efeitos de passagem do cursor, usando "lparam" e "dparam" para obter as coordenadas do mouse. Para CHARTEVENT_MOUSE_MOVE, adicionamos o arraste do painel. Primeiro verificamos se o clique do mouse ocorreu dentro dos limites de "PANEL_HEADER", obtidos com ObjectGetInteger. Nesse caso, definimos "panel_dragging" como true, armazenamos as coordenadas em "panel_drag_x", "panel_drag_y", "panel_start_x" e "panel_start_y" e desativamos a rolagem do gráfico com ChartSetInteger.

Durante o arraste, quando "panel_dragging" estiver ativo e "MouseState" for igual a 1, calculamos o deslocamento ("dx", "dy") e atualizamos "panel_x" e "panel_y". Em seguida, reposicionamos todos os objetos do painel, como "PANEL_BG", "PANEL_HEADER", "CLOSE_BTN" e "LOT_EDIT", usando ObjectSetInteger. Em seguida, chamamos ChartRedraw para atualizar o gráfico. Quando o botão do mouse é liberado, definimos "panel_dragging" como false e reativamos a rolagem. Também evitamos conflitos entre o arraste dos retângulos ("REC1", "REC3", "REC5") e o arraste do painel usando a condição "!panel_dragging". Durante "movingState_R1", "movingState_R5" e "movingState_R3", chamamos "updateRectangleColors" para atualizar as cores de acordo com o estado de passagem do cursor e a validade da configuração da ordem.

Destacamos alguns trechos importantes que merecem atenção. A visualização abaixo mostra esses pontos.

ARRASTE DO PAINEL E EFEITOS AO PASSAR O CURSOR

Além disso, como passamos a usar o objeto de cabeçalho do painel, vejamos como ele é criado.

//+------------------------------------------------------------------+
//| Create control panel                                             |
//+------------------------------------------------------------------+
void createControlPanel() {
   // Background rectangle
   ObjectCreate(0, PANEL_BG, OBJ_RECTANGLE_LABEL, 0, 0, 0);                   //--- Create panel background rectangle
   ObjectSetInteger(0, PANEL_BG, OBJPROP_XDISTANCE, panel_x);                 //--- Set background x-position
   ObjectSetInteger(0, PANEL_BG, OBJPROP_YDISTANCE, panel_y);                 //--- Set background y-position
   ObjectSetInteger(0, PANEL_BG, OBJPROP_XSIZE, 250);                         //--- Set background width
   ObjectSetInteger(0, PANEL_BG, OBJPROP_YSIZE, 280);                         //--- Set background height
   ObjectSetInteger(0, PANEL_BG, OBJPROP_BGCOLOR, C'070,070,070');            //--- Set background color
   ObjectSetInteger(0, PANEL_BG, OBJPROP_BORDER_COLOR, clrWhite);             //--- Set border color
   ObjectSetInteger(0, PANEL_BG, OBJPROP_BACK, false);                        //--- Set background to foreground

   // Header rectangle (inside panel)
   
   createButton(PANEL_HEADER,"",panel_x+2,panel_y+2,250-4,28-3,clrBlue,C'050,050,050',12,C'050,050,050',false);
   
   createButton(CLOSE_BTN, CharToString(203), panel_x + 209, panel_y + 1, 40, 25, clrWhite, clrCrimson, 12, clrBlack, false, "Wingdings"); //--- Create close button

//---

}

Na função "createControlPanel", adicionamos o botão "PANEL_HEADER" ao painel de controle da ferramenta por meio da função "createButton". Ele é posicionado em "panel_x+2", "panel_y+2", com dimensões de 246x25, texto azul (clrBlue), fundo e borda em cinza-escuro ("C'050,050,050'") e sem rótulo. Esse objeto funciona como a área usada para arrastar o painel em OnChartEvent. Também precisamos garantir que esse novo elemento seja removido corretamente quando o painel for excluído, conforme mostrado abaixo.

//+------------------------------------------------------------------+
//| Delete control panel objects |
//+------------------------------------------------------------------+
void deletePanel() {
   ObjectDelete(0, PANEL_BG);        //--- Delete panel background
   ObjectDelete(0, PANEL_HEADER);    //--- Delete panel header
   ObjectDelete(0, LOT_EDIT);        //--- Delete lot edit field
   ObjectDelete(0, PRICE_LABEL);     //--- Delete price label
   ObjectDelete(0, SL_LABEL);        //--- Delete SL label
   ObjectDelete(0, TP_LABEL);        //--- Delete TP label
   ObjectDelete(0, BUY_STOP_BTN);    //--- Delete Buy Stop button
   ObjectDelete(0, SELL_STOP_BTN);   //--- Delete Sell Stop button
   ObjectDelete(0, BUY_LIMIT_BTN);   //--- Delete Buy Limit button
   ObjectDelete(0, SELL_LIMIT_BTN);  //--- Delete Sell Limit button
   ObjectDelete(0, PLACE_ORDER_BTN); //--- Delete Place Order button
   ObjectDelete(0, CANCEL_BTN);      //--- Delete Cancel button
   ObjectDelete(0, CLOSE_BTN);       //--- Delete Close button
   ChartRedraw(0);                   //--- Redraw chart
}

Aqui atualizamos a função "deletePanel" para garantir a remoção completa do painel de controle da ferramenta, excluindo todos os objetos associados, inclusive o novo cabeçalho que acabamos de adicionar. Usamos ObjectDelete para remover do gráfico do MetaTrader 5 o fundo do painel ("PANEL_BG"), o cabeçalho ("PANEL_HEADER"), o campo de entrada do tamanho do lote ("LOT_EDIT"), os rótulos ("PRICE_LABEL", "SL_LABEL", "TP_LABEL") e os botões ("BUY_STOP_BTN", "SELL_STOP_BTN", "BUY_LIMIT_BTN", "SELL_LIMIT_BTN", "PLACE_ORDER_BTN", "CANCEL_BTN", "CLOSE_BTN").

Por fim, chamamos ChartRedraw para atualizar o gráfico e manter a interface limpa após a remoção dos objetos. Além disso, ao exibir a ferramenta, precisamos levar em conta os efeitos de passagem do cursor para garantir que continuem visíveis, conforme mostrado abaixo.

//+------------------------------------------------------------------+
//| Show control panel                                               |
//+------------------------------------------------------------------+
void showPanel() {
   // Ensure panel is in foreground
   ObjectSetInteger(0, PANEL_BG,        OBJPROP_BACK, false); //--- Show panel background
   ObjectSetInteger(0, PANEL_HEADER,    OBJPROP_BACK, false); //--- Show panel header
   ObjectSetInteger(0, LOT_EDIT,        OBJPROP_BACK, false); //--- Show lot edit field
   ObjectSetInteger(0, PRICE_LABEL,     OBJPROP_BACK, false); //--- Show price label
   ObjectSetInteger(0, SL_LABEL,        OBJPROP_BACK, false); //--- Show SL label
   ObjectSetInteger(0, TP_LABEL,        OBJPROP_BACK, false); //--- Show TP label
   ObjectSetInteger(0, BUY_STOP_BTN,    OBJPROP_BACK, false); //--- Show Buy Stop button
   ObjectSetInteger(0, SELL_STOP_BTN,   OBJPROP_BACK, false); //--- Show Sell Stop button
   ObjectSetInteger(0, BUY_LIMIT_BTN,   OBJPROP_BACK, false); //--- Show Buy Limit button
   ObjectSetInteger(0, SELL_LIMIT_BTN,  OBJPROP_BACK, false); //--- Show Sell Limit button
   ObjectSetInteger(0, PLACE_ORDER_BTN, OBJPROP_BACK, false); //--- Show Place Order button
   ObjectSetInteger(0, CANCEL_BTN,      OBJPROP_BACK, false); //--- Show Cancel button
   ObjectSetInteger(0, CLOSE_BTN,       OBJPROP_BACK, false); //--- Show Close button
   
   // Reset button hover states
   buy_stop_hovered     = false;
   sell_stop_hovered    = false;
   buy_limit_hovered    = false;
   sell_limit_hovered   = false;
   place_order_hovered  = false;
   cancel_hovered       = false;
   close_hovered        = false;
   header_hovered       = false;

   // Reset button colors
   ObjectSetInteger(0, BUY_STOP_BTN,    OBJPROP_BGCOLOR,       clrForestGreen);
   ObjectSetInteger(0, BUY_STOP_BTN,    OBJPROP_BORDER_COLOR,  clrBlack);
   ObjectSetInteger(0, SELL_STOP_BTN,   OBJPROP_BGCOLOR,       clrFireBrick);
   ObjectSetInteger(0, SELL_STOP_BTN,   OBJPROP_BORDER_COLOR,  clrBlack);
   ObjectSetInteger(0, BUY_LIMIT_BTN,   OBJPROP_BGCOLOR,       clrForestGreen);
   ObjectSetInteger(0, BUY_LIMIT_BTN,   OBJPROP_BORDER_COLOR,  clrBlack);
   ObjectSetInteger(0, SELL_LIMIT_BTN,  OBJPROP_BGCOLOR,       clrFireBrick);
   ObjectSetInteger(0, SELL_LIMIT_BTN,  OBJPROP_BORDER_COLOR,  clrBlack);
   ObjectSetInteger(0, PLACE_ORDER_BTN, OBJPROP_BGCOLOR,       clrDodgerBlue);
   ObjectSetInteger(0, PLACE_ORDER_BTN, OBJPROP_BORDER_COLOR,  clrBlack);
   ObjectSetInteger(0, CANCEL_BTN,      OBJPROP_BGCOLOR,       clrSlateGray);
   ObjectSetInteger(0, CANCEL_BTN,      OBJPROP_BORDER_COLOR,  clrBlack);
   ObjectSetInteger(0, CLOSE_BTN,       OBJPROP_BGCOLOR,       clrCrimson);
   ObjectSetInteger(0, CLOSE_BTN,       OBJPROP_BORDER_COLOR,  clrBlack);
   ObjectSetInteger(0, PANEL_HEADER,    OBJPROP_BGCOLOR,       C'050,050,050');

   // Reset panel state
   update_Text(PRICE_LABEL,     "Entry: -");        //--- Reset entry label text
   update_Text(SL_LABEL,        "SL: -");           //--- Reset SL label text
   update_Text(TP_LABEL,        "TP: -");           //--- Reset TP label text
   update_Text(PLACE_ORDER_BTN, "Place Order");     //--- Reset Place Order button text
   selected_order_type = "";                        //--- Clear selected order type
   tool_visible        = false;                     //--- Hide tool
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); //--- Ensure mouse move events are enabled
   ChartRedraw(0);                                   //--- Redraw chart
}

Aprimoramos a função "showPanel" para controlar a exibição e a redefinição do estado do painel de controle da ferramenta, incorporando o novo "PANEL_HEADER" e o gerenciamento dos estados de passagem do cursor do mouse, introduzidos para ampliar a interatividade. Primeiro, garantimos que todos os elementos do painel permaneçam visíveis usando ObjectSetInteger para definir a propriedade OBJPROP_BACK como false no fundo do painel ("PANEL_BG"), no cabeçalho recém-adicionado ("PANEL_HEADER"), no campo de entrada do tamanho do lote ("LOT_EDIT"), nos rótulos relacionados ao preço ("PRICE_LABEL", "SL_LABEL", "TP_LABEL") e em todos os botões ("BUY_STOP_BTN", "SELL_STOP_BTN", "BUY_LIMIT_BTN", "SELL_LIMIT_BTN", "PLACE_ORDER_BTN", "CANCEL_BTN", "CLOSE_BTN"), trazendo esses objetos para o primeiro plano do gráfico.

Para manter uma interface limpa e previsível, redefinimos os estados de passagem do cursor atribuindo false às variáveis lógicas "buy_stop_hovered", "sell_stop_hovered", "buy_limit_hovered", "sell_limit_hovered", "place_order_hovered", "cancel_hovered", "close_hovered" e "header_hovered". Assim, evitamos que efeitos visuais residuais permaneçam ativos quando o painel for exibido novamente.

Em seguida, restauramos a aparência padrão dos botões e do cabeçalho com "ObjectSetInteger", definindo OBJPROP_BGCOLOR com suas cores originais: "clrForestGreen" para "BUY_STOP_BTN" e "BUY_LIMIT_BTN", "clrFireBrick" para "SELL_STOP_BTN" e "SELL_LIMIT_BTN", "clrDodgerBlue" para "PLACE_ORDER_BTN", "clrSlateGray" para "CANCEL_BTN", "clrCrimson" para "CLOSE_BTN" e cinza-escuro ("C'050,050,050'") para "PANEL_HEADER".

Também definimos OBJPROP_BORDER_COLOR como "clrBlack" em todos os botões, mantendo uma aparência uniforme quando o cursor não estiver sobre eles.

Para redefinir o estado funcional do painel, chamamos a função "update_Text" e definimos "PRICE_LABEL" como "Entry: -", "SL_LABEL" como "SL: -", "TP_LABEL" como "TP: -" e "PLACE_ORDER_BTN" como "Place Order", eliminando qualquer informação anterior da configuração da operação. Também limpamos o valor de "selected_order_type" para garantir que nenhum tipo de ordem permaneça previamente selecionado, definimos "tool_visible" como false para ocultar a ferramenta de preço no gráfico e usamos ChartSetInteger para habilitar os eventos CHART_EVENT_MOUSE_MOVE, deixando o painel pronto para responder à passagem do cursor e ao arraste.

Por fim, chamamos ChartRedraw para atualizar o gráfico e devolver o painel ao estado padrão, totalmente preparado para a próxima interação. Após a compilação, obtemos o seguinte resultado.

RESULTADO FINAL

Como podemos ver na visualização, agora é possível validar dinamicamente as ordens por meio da ferramenta de preço e alterar suas cores para alertar o usuário quando os preços estiverem fora dos limites permitidos. Além disso, o painel e a ferramenta de preço podem ser arrastados dinamicamente. Ao passar o cursor sobre os botões, podemos identificar seus limites e alterar suas cores de acordo com a posição do ponteiro, alcançando assim o objetivo proposto. Agora resta testar a interatividade do projeto, como abordado na seção anterior.


Teste com dados históricos

Realizamos o teste e, abaixo, apresentamos o visualização compilada em um único arquivo de imagem raster no formato Graphics Interchange Format (GIF).

GIF DO BACKTEST


Conclusão

Em resumo, aprimoramos nossa ferramenta Trade Assistant Tool em MQL5 adicionando feedback visual dinâmico, um painel arrastável, efeitos ao passar o cursor e validação das ordens em tempo real, tornando o envio de ordens pendentes mais intuitivo e preciso. Demonstramos o desenvolvimento e a implementação dessas melhorias e confirmamos sua confiabilidade por meio de testes criteriosos com dados históricos, adaptados às nossas necessidades de negociação. Você pode ajustar a ferramenta ao seu próprio estilo de operação e aumentar significativamente a eficiência no envio de ordens diretamente pelos gráficos.

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

Últimos Comentários | Ir para discussão (2)
Zhang Yi
Zhang Yi | 28 jan. 2026 em 06:04

Descobri um bug: quando o TP ou o SL ficam bem colados à borda da entrada, eles não conseguem mais se mover. Esse é um grande bug. Será que o autor já percebeu esse problema?

a

Mikhail Ostashov
Mikhail Ostashov | 28 fev. 2026 em 05:51
Zhang Yi #:

Descobri um erro: quando o TP ou o SL se aproxima da borda de entrada, ele não consegue avançar mais. É um grande erro. Será que o autor já identificou esse problema?


Aparentemente, o bloqueio ocorre na linha de interseção. Existe uma solução, mas será que ela é necessária?
Recursos do Assistente MQL5 que você precisa conhecer (Parte 63): Uso de padrões dos canais DeMarker e Envelopes Recursos do Assistente MQL5 que você precisa conhecer (Parte 63): Uso de padrões dos canais DeMarker e Envelopes
O oscilador DeMarker (DeMarker Oscillator) e o Envelopes são ferramentas que identificam o momentum e os níveis de suporte/resistência e podem ser combinadas no desenvolvimento de um EA. Por isso, analisamos cada padrão separadamente para determinar o que pode ser útil e o que, potencialmente, deve ser evitado. Como sempre, usamos um EA criado no Assistente, juntamente com as funções de padrões integradas à classe de sinais do EA (Expert Signal).
Ferramentas de negociação em MQL5 (Parte 1): Assistente visual interativo para ordens pendentes Ferramentas de negociação em MQL5 (Parte 1): Assistente visual interativo para ordens pendentes
Neste artigo, apresentamos o desenvolvimento da ferramenta interativa Trade Assistant Tool em MQL5, criada para simplificar o envio de ordens pendentes no mercado Forex. Descrevemos a concepção da ferramenta, com atenção especial a uma interface gráfica intuitiva que permite definir visualmente no gráfico os níveis de entrada, Stop Loss e Take Profit. Além disso, detalhamos a implementação em MQL5 e os testes com dados históricos para confirmar a confiabilidade da ferramenta, estabelecendo a base para a inclusão de recursos mais avançados nas próximas partes da série.
Recursos do Assistente MQL5 que você precisa conhecer (Parte 64): Uso de padrões dos canais DeMarker e Envelopes com kernel de ruído branco Recursos do Assistente MQL5 que você precisa conhecer (Parte 64): Uso de padrões dos canais DeMarker e Envelopes com kernel de ruído branco
O oscilador DeMarker (DeMarker Oscillator) e o Envelopes são ferramentas que identificam o momentum e os níveis de suporte/resistência e podem ser combinadas no desenvolvimento de um EA. No artigo anterior, apresentamos esses dois indicadores. Agora, acrescentaremos machine learning a essa combinação. Utilizamos uma rede neural recorrente que aplica um kernel de ruído branco (white-noise kernel) para processar os sinais vetorizados desses dois indicadores. Essa lógica é implementada em um arquivo personalizado da classe de sinais (signal class), que interage com o Assistente MQL5 para criar o EA.
Desenvolvimento do Kit de Ferramentas de Análise de Price Action (Parte 20): Fluxo Externo (IV) — Correlation Pathfinder Desenvolvimento do Kit de Ferramentas de Análise de Price Action (Parte 20): Fluxo Externo (IV) — Correlation Pathfinder
O Correlation Pathfinder oferece uma nova abordagem para compreender a dinâmica dos pares de moedas como parte da Série de Desenvolvimento do Kit de Ferramentas de Análise de Price Action. Essa ferramenta automatiza a coleta e a análise de dados, fornecendo insights sobre como pares como EUR/USD e GBP/USD interagem. Aprimore sua estratégia de trading com informações práticas e em tempo real que ajudam você a gerenciar riscos e identificar oportunidades com mais eficiência.