English Русский 中文 Deutsch 日本語
preview
Herramientas de trading de MQL5 (Parte 2): Mejora del asistente interactivo de trading con retroalimentación visual dinámica

Herramientas de trading de MQL5 (Parte 2): Mejora del asistente interactivo de trading con retroalimentación visual dinámica

MetaTrader 5Trading |
30 2
Allan Munene Mutiiria
Allan Munene Mutiiria

Introducción

En nuestro artículo anterior, la primera parte, creamos una herramienta de asistencia para el trading en MetaQuotes Language 5 (MQL5) para MetaTrader 5 con el fin de simplificar la colocación de órdenes pendientes. Ahora, vamos un paso más allá mejorando su interactividad con retroalimentación visual dinámica. Introducimos funciones como un panel de control arrastrable, efectos al pasar el cursor para una navegación intuitiva y validación de órdenes en tiempo real para garantizar que nuestras configuraciones de trading sean precisas y acordes al mercado. Abordamos estos avances a través de los siguientes subtemas:

  1. Mejoras conceptuales para una mayor interactividad
  2. Implementación en MQL5
  3. Backtesting
  4. Conclusión

Estas secciones nos acercan a una herramienta de trading más intuitiva, ágil y fácil de usar.


Mejoras conceptuales para una mayor interactividad

Nos esforzamos por mejorar nuestra herramienta de asistencia al trading haciéndola más intuitiva y adaptable. Comenzamos con un panel de control que podemos arrastrar y posicionar libremente en el gráfico de trading. Esta flexibilidad nos permitirá personalizar la interfaz para adaptarla a nuestro flujo de trabajo, ya sea que estemos gestionando varios gráficos o centrándonos en una única configuración de trading. Además, integraremos efectos al pasar el cursor para resaltar los botones y los elementos del gráfico a medida que el cursor se desplace sobre ellos, proporcionando una retroalimentación visual instantánea que agiliza la navegación y minimiza los errores.

La validación de órdenes en tiempo real será otra mejora clave, que garantizará que nuestros niveles de entrada, stop-loss y take-profit estén lógicamente alineados con los precios actuales del mercado antes de la ejecución. Esta función aumentará nuestra confianza al evitar configuraciones de órdenes no válidas y al mantener la simplicidad a la vez que mejora la precisión. En conjunto, estas mejoras crearán una herramienta intuitiva y centrada en el usuario que respaldará nuestras decisiones de trading y sentará las bases para futuros avances, como las funciones de gestión de riesgos. En resumen, a continuación se muestra una representación visual de lo que pretendemos lograr.

VISUALIZACIÓN DE OBJETIVOS


Implementación en MQL5

Para alcanzar nuestros objetivos en MQL5, primero tendremos que definir algunos objetos de panel adicionales, así como variables de estado para el arrastre y el paso del ratón, que utilizaremos para realizar un seguimiento de las interacciones del usuario con el panel o con la herramienta de precios, tal y como se muestra a continuación.

// 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

Comenzamos a implementar las funciones de interactividad mejoradas de nuestra herramienta definiendo las variables clave que permiten arrastrar paneles y aplicar efectos al pasar el cursor en la interfaz de MetaTrader 5. Utilizamos la directiva #define para crear una constante «PANEL_HEADER» para el objeto de encabezado del panel, que actúa como la zona arrastrable del panel de control. Para permitir el arrastre, declaramos «panel_dragging» como un indicador booleano para detectar cuándo se está moviendo el panel, y las variables enteras «panel_drag_x» y «panel_drag_y» para almacenar las coordenadas del ratón al inicio del arrastre, así como «panel_start_x» y «panel_start_y» para registrar la posición inicial del panel, lo que nos permite calcular su nueva posición durante el movimiento.

También introducimos variables booleanas para gestionar los estados al pasar el cursor sobre los botones y los rectángulos de los gráficos, entre las que se incluyen «buy_stop_hovered», «sell_stop_hovered», «buy_limit_hovered», «sell_limit_hovered», «place_order_hovered», «cancel_hovered», «close_hovered» y «header_hovered» para los botones y el encabezado del panel respectivos, así como «rec1_hovered», «rec3_hovered» y «rec5_hovered» para los rectángulos de take-profit, entrada y stop-loss. Estas variables nos permitirán detectar cuándo el cursor pasa por encima de estos elementos, lo que activará una respuesta visual, como cambios de color, para mejorar la navegación y la interacción dentro de la interfaz de la herramienta. A continuación, necesitamos obtener los valores de la herramienta de precios y validarlos para operar.

//+------------------------------------------------------------------+
//| 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
}

En este caso, implementamos la función «isOrderValid» para mejorar nuestra herramienta mediante la validación de la configuración de la orden en tiempo real, garantizando así que nuestras operaciones se ajusten a las condiciones del mercado. Empezamos comprobando si «tool_visible» es falso, devolviendo «true» para omitir la validación cuando la herramienta no está activa. Recuperamos el precio de mercado actual mediante la función SymbolInfoDouble con SYMBOL_BID, y obtenemos los precios de entrada («entry_price»), stop-loss («sl_price») y take-profit («tp_price») mediante la función «Get_Price_d» para «PR_HL», «SL_HL» y «TP_HL».

Para «BUY_STOP», verificamos que «entry_price» sea superior a «current_price», que «tp_price» sea superior a «entry_price» y que «sl_price» sea inferior a «entry_price»; para «SELL_STOP», que «entry_price» sea inferior a «current_price», que «tp_price» sea inferior a «entry_price» y que «sl_price» sea superior a «entry_price»; para «BUY_LIMIT», que «entry_price» sea inferior a «current_price», que «tp_price» sea superior a «entry_price» y que «sl_price» sea inferior a «entry_price»; y para «SELL_LIMIT», que «entry_price» sea superior a «current_price», que «tp_price» sea inferior a «entry_price» y que «sl_price» sea superior a «entry_price», devolviendo falso si falla alguna condición, o verdadero si la configuración es válida. De este modo, podremos actualizar los colores de los rectángulos según la validez de la orden.

//+------------------------------------------------------------------+
//| 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 la función «updateRectangleColors» para mejorar la retroalimentación visual de nuestra herramienta, actualizando los colores de los rectángulos del gráfico en función de la validez de la orden y los estados al pasar el cursor por encima. Si «tool_visible» es falso, nos saltamos este paso; a continuación, comprobamos la validez mediante la función «isOrderValid» y utilizamos la función ObjectSetInteger para establecer «REC1» (TP) y «REC5» (SL) en gris («clrGray» o «C'100,100,100'» si «rec1_hovered»/«rec5_hovered») si no es válido, o en verde/rojo («clrGreen»/«clrRed» o «C'0,100,0'»/«C'139,0,0'» al pasar el cursor) si la orden es válida, para órdenes «BUY_STOP», «BUY_LIMIT» y de venta, y «REC3» (entrada) a gris claro («clrLightGray» o «C'105,105,105'» si «rec3_hovered»), llamando a ChartRedraw para actualizar el gráfico.

A continuación, debemos configurar los estados al pasar el cursor por encima de los botones tal y como se muestra a continuación.

//+------------------------------------------------------------------+
//| 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 mejorar la interactividad de nuestra herramienta mediante la gestión de los efectos al pasar el cursor por los botones y los elementos de los gráficos, implementamos la función «updateButtonHoverState». Definimos las matrices «buttons» para los nombres de los botones («BUY_STOP_BTN» a «CLOSE_BTN»), «hover_states» para sus indicadores de estado al pasar el cursor («buy_stop_hovered» a «close_hovered») y «normal_colors» para los colores predeterminados, con «hover_color» (clrDodgerBlue) y «hover_border» («clrBlue») para los estados al pasar el cursor.

Para cada botón, utilizamos la función ObjectGetInteger para obtener la posición y el tamaño, comprobamos si «mouse_x» y «mouse_y» se encuentran dentro de los límites, y actualizamos «OBJPROP_BGCOLOR» y «OBJPROP_BORDER_COLOR» a «hover_color» o «normal_colors» mediante ObjectSetInteger, activando o desactivando «hover_states».

En el caso del «PANEL_HEADER», comprobamos igualmente el estado al pasar el cursor por encima, oscureciéndolo a «C'030,030,030» o restableciéndolo a «C'050,050,050» mediante ObjectSetInteger. Cuando «tool_visible» está activado, comprobamos los límites de «REC1», «REC3» y «REC5», actualizamos «rec1_hovered», «rec3_hovered» y «rec5_hovered», y llamamos a «updateRectangleColors» si se han producido cambios. Sincronizamos los estados de «buy_stop_hovered» a «close_hovered» mediante «hover_states» y llamamos a ChartRedraw para actualizar el gráfico. A continuación, podemos llamar a estas funciones dentro del controlador de eventos OnChartEvent para obtener actualizaciones en tiempo 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
   }
}

Dado que ya habíamos definido la función OnChartEvent, nos centraremos únicamente en señalar la lógica mejorada que hemos añadido para incorporar las nuevas funciones de interactividad, tales como el arrastre de paneles, las actualizaciones del estado al pasar el cursor y la validación de pedidos. Para CHARTEVENT_OBJECT_CLICK, ampliamos el manejo de los clics en los botones «BUY_STOP_BTN», «SELL_STOP_BTN», «BUY_LIMIT_BTN» y «SELL_LIMIT_BTN» llamando a la función «updateRectangleColors» para reflejar visualmente la validez de la orden, y para «PLACE_ORDER_BTN», añadimos una comprobación con la función «isOrderValid», registrando un error mediante la función Print si no es válida, lo que evita operaciones erróneas, tal y como se muestra a continuación.

VIGENCIA DE LA ORDEN

También incorporamos la función «updateButtonHoverState» tras los clics para actualizar los efectos al pasar el cursor, utilizando «lparam» y «dparam» para las coordenadas del ratón. Para CHARTEVENT_MOUSE_MOVE, añadimos la función de arrastre del panel comprobando si el clic del ratón se produce dentro de los límites de «PANEL_HEADER» (obtenidos mediante ObjectGetInteger), estableciendo «panel_dragging» en «true», almacenando las coordenadas en «panel_drag_x», «panel_drag_y», «panel_start_x» y «panel_start_y», y desactivando el desplazamiento con ChartSetInteger.

Mientras se arrastra («panel_dragging» y «MouseState» = 1), calculamos el desplazamiento («dx», «dy»), actualizamos «panel_x» y «panel_y», y reposicionamos todos los objetos del panel («PANEL_BG», «PANEL_HEADER», «CLOSE_BTN», «LOT_EDIT», etc.) utilizando ObjectSetInteger, y llamamos a ChartRedraw para actualizar el gráfico. Al soltar el botón del ratón, restablecemos «panel_dragging» y volvemos a habilitar el desplazamiento. Nos aseguramos de que el arrastre de rectángulos (para «REC1», «REC3» y «REC5») no entre en conflicto con el arrastre de paneles comprobando «!panel_dragging», y actualizamos los colores con «updateRectangleColors» durante «movingState_R1», «movingState_R5» y «movingState_R3» para reflejar los estados de desplazamiento del cursor y de validez.

Hemos destacado algunos de los fragmentos que es fundamental tener en cuenta. Aquí tiene una representación gráfica.

ARRASTRE Y PASO DEL CURSOR SOBRE EL PANEL

Además, dado que hemos utilizado el objeto del panel de cabecera, a continuación le mostramos cómo crearlo.

//+------------------------------------------------------------------+
//| 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

//---

}

En la función «createControlPanel», añadimos un botón «PANEL_HEADER» al panel de control de nuestra herramienta mediante la función «createButton», situado en «panel_x+2», «panel_y+2» con un tamaño de 246x25, con texto azul (clrBlue), fondo y borde gris oscuro («C'050,050,050'») y sin etiqueta, lo que permite arrastrar el panel en OnChartEvent. Otra cosa que debemos hacer es desmontar el panel tal y como se indica a continuación.

//+------------------------------------------------------------------+
//| 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
}

En este punto, actualizamos la función «deletePanel» para garantizar que el panel de control de nuestra herramienta se limpie correctamente, eliminando todos los objetos asociados, incluido el nuevo encabezado que hemos introducido recientemente. Utilizamos la función ObjectDelete para eliminar el fondo del panel («PANEL_BG»), el encabezado recién añadido («PANEL_HEADER»), el campo de introducción del tamaño del lote («LOT_EDIT»), las etiquetas («PRICE_LABEL», «SL_LABEL», «TP_LABEL») y los botones («BUY_STOP_BTN», «SELL_STOP_BTN», «BUY_LIMIT_BTN», «SELL_LIMIT_BTN», «PLACE_ORDER_BTN», «CANCEL_BTN» y «CLOSE_BTN») del gráfico de MetaTrader 5.

Por último, llamamos a la función ChartRedraw para actualizar el gráfico, garantizando así una interfaz limpia tras la eliminación. Además, al mostrar la herramienta, debemos tener en cuenta los efectos al pasar el cursor para garantizar que sigan siendo visibles, tal y como se muestra a continuación.

//+------------------------------------------------------------------+
//| 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
}

Hemos mejorado la función «showPanel» para gestionar la visualización y el restablecimiento del estado del panel de control de nuestra herramienta, incorporando el nuevo «PANEL_HEADER» y la gestión del estado al pasar el cursor, introducidos para mejorar la interactividad de la herramienta. Comenzamos por asegurarnos de que todos los elementos del panel estén visibles utilizando la función ObjectSetInteger para establecer la propiedad OBJPROP_BACK en «false» para el fondo del panel («PANEL_BG»), el encabezado recién añadido («PANEL_HEADER»), el campo de introducción del tamaño del lote («LOT_EDIT»), las etiquetas relacionadas con el precio («PRICE_LABEL», «SL_LABEL», «TP_LABEL») y todos los botones («BUY_STOP_BTN», «SELL_STOP_BTN», «BUY_LIMIT_BTN», «SELL_LIMIT_BTN», «PLACE_ORDER_BTN», «CANCEL_BTN» y «CLOSE_BTN»), situándolos en primer plano en el gráfico.

Para mantener una interfaz clara y predecible, restablecemos los estados de desplazamiento del cursor estableciendo las variables booleanas «buy_stop_hovered», «sell_stop_hovered», «buy_limit_hovered», «sell_limit_hovered», «place_order_hovered», «cancel_hovered», «close_hovered» y «header_hovered» en «false», lo que garantiza que no persistan efectos residuales de desplazamiento al pasar el cursor cuando se muestra el panel.

A continuación, restablecemos el aspecto visual predeterminado de los botones y el encabezado utilizando la función «ObjectSetInteger» para establecer OBJPROP_BGCOLOR en sus colores originales: «clrForestGreen» para «BUY_STOP_BTN» y «BUY_LIMIT_BTN», «clrFireBrick» para «SELL_STOP_BTN» y «SELL_LIMIT_BTN», «clrDodgerBlue» para «PLACE_ORDER_BTN», «clrSlateGray» para «CANCEL_BTN», «clrCrimson» para «CLOSE_BTN» y un gris oscuro («C'050,050,050'») para «PANEL_HEADER».

Además, hemos establecido OBJPROP_BORDER_COLOR en «clrBlack» para todos los botones, con el fin de garantizar un aspecto uniforme cuando no se pasa el cursor por encima.

Para restablecer el estado funcional del panel, llamamos a la función «update_Text» para establecer «PRICE_LABEL» en «Entry: -», «SL_LABEL» en «SL: -», «TP_LABEL» en «TP: -» y «PLACE_ORDER_BTN» en «Realizar pedido», borrando así cualquier información previa sobre la configuración de la operación. Borramos la variable «selected_order_type» para asegurarnos de que no haya ningún tipo de orden preseleccionado, establecemos «tool_visible» en «false» para ocultar la herramienta del gráfico y utilizamos la función ChartSetInteger para habilitar los eventos CHART_EVENT_MOUSE_MOVE, garantizando así que el panel esté listo para las interacciones de desplazamiento del cursor y arrastre.

Por último, llamamos a la función ChartRedraw para actualizar el gráfico, lo que devuelve el panel a su estado predeterminado, totalmente preparado para nuestra próxima interacción. Tras la compilación, este es el resultado.

RESULTADO FINAL

A partir de la visualización, podemos observar que es posible validar dinámicamente los pedidos mediante la herramienta de precios y cambiar sus colores para alertar al usuario de que los precios se salen de los límites establecidos. Además, podemos observar que es posible arrastrar el panel y la herramienta de precios de forma dinámica y que, al pasar el cursor por encima de los botones, se muestran sus rangos y los colores cambian dinámicamente en función de dichos rangos, con lo que se logra nuestro objetivo. Ahora solo queda comprobar la interactividad del proyecto, lo cual se aborda en la sección anterior.


Backtesting

Hemos realizado las pruebas y a continuación se muestra la visualización recopilada en un único formato de imagen de mapa de bits Graphics Interchange Format (GIF).

GIF DE BACKTEST


Conclusión

En conclusión, hemos mejorado nuestra herramienta Trade Assistant en MQL5 con indicaciones visuales dinámicas, incorporando un panel arrastrable, efectos al pasar el cursor y validación de órdenes en tiempo real para que la colocación de nuestras órdenes pendientes resulte más intuitiva y precisa. Hemos demostrado el diseño y la implementación de estas mejoras, garantizando su fiabilidad mediante exhaustivas pruebas retrospectivas adaptadas a nuestras necesidades de negociación. Puede personalizar esta herramienta para adaptarla a su estilo, lo que mejorará considerablemente la eficiencia al colocar órdenes en los gráficos.

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/17972

Zhang Yi
Zhang Yi | 28 ene 2026 en 06:04

He encontrado un bug, cuando tp o sl está cerca del borde de entrada, ya no se puede mover, esto es un gran bug, ¿ha encontrado el autor este problema?

a

Mikhail Ostashov
Mikhail Ostashov | 28 feb 2026 en 05:51
Zhang Yi #:

He encontrado un error, cuando tp o sl se acerca al borde de la entrada, no puede moverse más allá, esto es un gran error, ¿ha encontrado el autor este problema?


Aparentemente el bloqueo se produce en el borde de la intersección. Hay una posible solución, pero ¿es necesaria)
Características del Wizard MQL5 que debe conocer (Parte 63): Uso de los patrones de DeMarker y los canales de envolvente Características del Wizard MQL5 que debe conocer (Parte 63): Uso de los patrones de DeMarker y los canales de envolvente
El oscilador DeMarker y el indicador de envolvente son herramientas de impulso y de soporte/resistencia que pueden combinarse al desarrollar un asesor experto. Por lo tanto, examinamos patrón por patrón qué podría ser útil y qué podría evitarse. Como siempre, estamos utilizando un Asesor Experto creado mediante un asistente, junto con las funciones de uso de patrones integradas en la clase Expert Signal.
Simulación de mercado: Iniciando SQL en MQL5 (III) Simulación de mercado: Iniciando SQL en MQL5 (III)
En el artículo anterior vimos cómo podríamos desarrollar una clase en MQL5 capaz de darnos cierto soporte. Su finalidad es precisamente permitirnos colocar el código SQL dentro de un archivo de script. De este modo, no necesitaríamos escribir ese mismo código SQL como un string dentro del código MQL5. Aunque esa solución es funcional, contiene algunos detalles que podemos y debemos mejorar.
Características del Wizard MQL5 que debe conocer (Parte 64): Uso de los patrones de DeMarker y los canales de envolvente con el núcleo de ruido blanco Características del Wizard MQL5 que debe conocer (Parte 64): Uso de los patrones de DeMarker y los canales de envolvente con el núcleo de ruido blanco
El oscilador DeMarker y el indicador de envolventes son herramientas de impulso y de soporte/resistencia que pueden combinarse al desarrollar un asesor experto. Retomamos el punto de nuestro artículo anterior, en el que presentamos este par de indicadores, añadiendo ahora el aprendizaje automático a la ecuación. Estamos utilizando una red neuronal recurrente que emplea un núcleo de ruido blanco para procesar señales vectorizadas procedentes de estos dos indicadores. Esto se realiza en un archivo de clase de señal personalizado que funciona con el asistente MQL5 para ensamblar un Asesor Experto.
Del básico al intermedio: Objetos (III) Del básico al intermedio: Objetos (III)
En este artículo veremos cómo podemos implementar un sistema de interacción muy atractivo e interesante, sobre todo para quienes están empezando a practicar programación en MQL5. No se trata de algo realmente nuevo. La forma en que abordaré el tema hará que todo sea mucho más fácil de entender, ya que veremos, en la práctica, cómo se desarrolla una programación estructural con un objetivo bastante divertido.