English Русский 中文 Deutsch 日本語
preview
Herramientas de trading de MQL5 (Parte 4): Mejorando el panel del escáner multitemporal con posicionamiento dinámico y funciones de alternancia

Herramientas de trading de MQL5 (Parte 4): Mejorando el panel del escáner multitemporal con posicionamiento dinámico y funciones de alternancia

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

Introducción

En nuestro artículo anterior (Parte 3), desarrollamos un panel de control con escáner de múltiples temporalidades en MetaQuotes Language 5 (MQL5), que muestra los indicadores Relative Strength Index (RSI), Estocástico, Commodity Channel Index (CCI), Average Directional Index (ADX) y Awesome Oscillator (AO) en múltiples temporalidades para identificar señales de trading para el símbolo actual del gráfico. En la parte 4, mejoramos este panel de control añadiendo un posicionamiento dinámico que permite arrastrar el panel por el gráfico y una función de alternancia para minimizar o maximizar la visualización, lo que mejora la usabilidad y la gestión de la pantalla. Trataremos los siguientes temas:

  1. Comprensión de la arquitectura de posicionamiento dinámico y alternancia
  2. Implementación en MQL5
  3. Pruebas retrospectivas (backtesting)
  4. Conclusión

Al final, tendrás un panel de control avanzado de MQL5 con una disposición flexible y funciones de alternancia, listo para su prueba y posterior personalización. ¡Empecemos!


Comprensión de la arquitectura de posicionamiento dinámico y alternancia

Estamos mejorando nuestro panel de control del escáner de múltiples temporalidades añadiéndole un posicionamiento dinámico, lo que permite arrastrarlo por el gráfico, y un botón para minimizarlo o maximizarlo, mejorando así su facilidad de uso. Estas funciones son esenciales para evitar que los gráficos resulten recargados y optimizar el espacio en pantalla con el fin de realizar un análisis de trading eficiente. Implementaremos la función de arrastrar y soltar con el ratón para reposicionar el panel de control y un botón para alternar entre las vistas compacta y completa, manteniendo así actualizaciones continuas de los indicadores para tomar mejores decisiones de trading. En resumen, esta es una representación visual de lo que pretendemos lograr.

ARQUITECTURA DE POSICIONAMIENTO Y ESTADOS DE ARRASTRE


Implementación en MQL5

Para implementar las mejoras en MQL5, definiremos el objeto de botón de alternancia adicional que utilizaremos más adelante para cambiar entre los estados maximizado y minimizado, ya que el paso del cursor y el arrastre se realizarán sobre el objeto de cabecera, que ya tenemos en la implementación.

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

//--- THE REST OF THE EXISTING OBJECTS

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

//--- THE REST OF THE EXISTING OBJECTS

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

Comenzamos la mejora de nuestro panel de control «Multi-Timeframe Scanner» actualizando las definiciones de los elementos de la interfaz de usuario (UI) para incluir un nuevo identificador para el botón de alternancia, lo que nos permite alcanzar nuestro objetivo de añadir la funcionalidad de minimizar y maximizar. Mantenemos las definiciones actuales para conservar la estructura básica del panel de control. El cambio principal es la incorporación del identificador «TOGGLE_BUTTON», definido como «BUTTON_TOGGLE», que nos permitirá crear un botón para alternar entre los estados minimizado y maximizado del panel de control.

Esta incorporación es fundamental, ya que permitirá activar la nueva función de alternancia, lo que nos dará la posibilidad de contraer el panel de control para ahorrar espacio en pantalla o ampliarlo para obtener una visibilidad completa, mejorando así la usabilidad sin alterar la lógica de visualización de los indicadores ya existente. A continuación, necesitaremos un mayor control sobre las variables globales, que utilizaremos para almacenar los estados del panel de control.

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

Para facilitar el seguimiento del estado del panel de control, añadimos algunas variables globales. Introducimos «panel_minimized» para realizar un seguimiento del estado minimizado, «panel_x» y «panel_y» para la posición del panel de control, y «panel_dragging», «panel_drag_x», «panel_drag_y», «panel_start_x» y «panel_start_y» para el arrastre. También añadimos «prev_mouse_state», «header_hovered», «toggle_hovered», «close_hovered», «last_mouse_x», «last_mouse_y», «prev_header_hovered», «prev_toggle_hovered» y «prev_close_hovered» para gestionar los eventos del ratón y los estados de resaltado al pasar el cursor. Esto permitirá crear un panel de control interactivo y arrastrable con función de alternancia.

A continuación, podemos pasar a añadir el botón de alternancia al panel de control. Para gestionar los estados de conmutación del panel, tendremos que dividir la lógica de creación en funciones independientes tanto para los paneles maximizados como para los minimizados. Empecemos con el panel maximizado.

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

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

Implementamos los estados del panel de control creando la función «create_full_dashboard», trasladando a ella la lógica de creación estática y actualizándola para que admita el posicionamiento dinámico y la funcionalidad de alternancia. El cambio principal consiste en integrar las variables «panel_x» y «panel_y» en el posicionamiento de todos los elementos del panel de control, lo que permite colocar el panel en cualquier parte del gráfico. Creamos el panel principal con «create_rectangle» utilizando «MAIN_PANEL», «panel_x» y «panel_y» para la posición, manteniendo el tamaño de 617 x 374. Del mismo modo, colocamos el panel de cabecera, el icono y el título utilizando «create_rectangle» y «create_label» para «HEADER_PANEL», «HEADER_PANEL_ICON» y «HEADER_PANEL_TEXT», ajustando sus coordenadas x e y en relación con «panel_x» y «panel_y».

Añadimos el nuevo «TOGGLE_BUTTON» con «create_label», situándolo en «panel_x - 570» y «panel_y + 14», y le asignamos un símbolo de minimización («r» en Webdings). El rectángulo del símbolo y la etiqueta («SYMBOL_RECTANGLE» y «SYMBOL_TEXT»), así como los rectángulos y las etiquetas del encabezado («HEADER_RECTANGLE» y «HEADER_TEXT»), utilizan «panel_x» y «panel_y» como desplazamientos en los ejes x e y, lo que garantiza que se muevan junto con el panel. Para cada intervalo de tiempo de «timeframes_array», creamos rectángulos y etiquetas de intervalo de tiempo («TIMEFRAME_RECTANGLE» y «TIMEFRAME_TEXT») y celdas de indicadores («RSI_RECTANGLE», «STOCH_RECTANGLE», etc.) cuyas posiciones se calculan en relación con «panel_x» y «panel_y», manteniendo el diseño, pero permitiendo reubicar el panel. Llamamos a la función ChartRedraw para actualizar la visualización. A diferencia de las coordenadas fijas de la versión anterior (por ejemplo, 632, 40), esta función utiliza coordenadas dinámicas, lo que permite arrastrar el panel de control, y añade un botón para minimizarlo o maximizarlo. Obtendremos algo parecido al ejemplo que se muestra a continuación.

ESTADO MAXIMIZADO

Así, podemos definir una función para crear el panel minimizado.

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

Para permitir el cambio a una vista compacta, definimos la función «create_minimized_dashboard». Creamos el panel de cabecera con «create_rectangle» para «HEADER_PANEL» en «panel_x» y «panel_y», añadimos «HEADER_PANEL_ICON» y «HEADER_PANEL_TEXT» con «create_label» para el icono y el título, incluimos «CLOSE_BUTTON», y añadimos «TOGGLE_BUTTON» con un símbolo de maximización («o» en Webdings) en «panel_x - 570» y «panel_y + 14». Llamamos a «ChartRedraw» para actualizar la visualización, lo que permite que el panel de control se pueda mover y minimizar. Como habrás podido observar, hemos elegido la fuente Wingdings tanto para el botón de maximizar como para el de minimizar, en aras de la coherencia. Puedes elegir los símbolos o la fuente que prefieras. En nuestro caso, los caracteres de los iconos son «r» y «o», respectivamente. Aquí tienes una vista general de todos ellos.

CARÁCTERES DE LA FUENTE WINGDINGS

Cuando se ejecuta el estado de panel minimizado, se obtiene el siguiente resultado.

ESTADO MINIMIZADO

Una vez que disponemos de las funciones, podemos elegir cuál utilizar en función del comando del usuario. Ahora bien, dado que hemos creado varios objetos, podemos centralizar la eliminación de todos ellos en una función, ya que tendremos que eliminarlos y volver a crearlos automáticamente.

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

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

Para tener un mayor control sobre cuándo eliminar nuestros objetos, creamos y actualizamos la función «delete_all_objects» para incluir la eliminación del nuevo «TOGGLE_BUTTON», lo que respalda la mejora de nuestra funcionalidad de alternancia. Añadimos la función ObjectDelete para «TOGGLE_BUTTON» a la lista de objetos que se van a eliminar, lo que garantiza que el botón de alternancia se elimine correctamente al cerrar el panel de control o al alternar su estado. Nos encargamos de eliminar todos los demás objetos, como «MAIN_PANEL», «HEADER_PANEL», «HEADER_PANEL_ICON», «HEADER_PANEL_TEXT», «CLOSE_BUTTON», así como todos los rectángulos y etiquetas de símbolos, marcos temporales, encabezados, indicadores y señales, mediante la función ObjectsDeleteAll. Este cambio garantiza que nuestro panel de control, que se puede mover y minimizar, elimine todos los componentes, incluido el nuevo botón de alternancia, manteniendo así un gráfico ordenado cuando el panel se oculta o se reinicia.

Para mejorar el posicionamiento dinámico, tendremos que crear una función que genere y actualice los elementos del panel de control en función de la posición del cursor. Esta es la lógica que seguimos para lograrlo.

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

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

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

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

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

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

Para facilitar el posicionamiento dinámico del panel de control, introducimos una nueva función denominada «update_panel_positions». Esta función ajusta las posiciones de todos los elementos del panel de control en función de las coordenadas actuales «panel_x» y «panel_y», lo que permite arrastrar el panel de control por el gráfico. Actualizamos el panel de cabecera, el icono, el título, el botón de cierre y el botón de alternancia («HEADER_PANEL», «HEADER_PANEL_ICON», «HEADER_PANEL_TEXT», «CLOSE_BUTTON», «TOGGLE_BUTTON») utilizando la función ObjectSetInteger con OBJPROP_XDISTANCE y «OBJPROP_YDISTANCE», estableciendo sus posiciones en relación con «panel_x» y «panel_y».

Si «panel_minimized» es falso, reposicionamos el panel principal («MAIN_PANEL»), el rectángulo y la etiqueta del símbolo («SYMBOL_RECTANGLE», «SYMBOL_TEXT»), los rectángulos y las etiquetas del encabezado («HEADER_RECTANGLE», «HEADER_TEXT»), así como los rectángulos de intervalo de tiempo, las etiquetas y las celdas de indicadores («TIMEFRAME_RECTANGLE», «TIMEFRAME_TEXT», «BUY_RECTANGLE», «RSI_RECTANGLE», etc.) utilizando los desplazamientos calculados a partir de «panel_x» y «panel_y». Llamamos a la función ChartRedraw para actualizar la visualización. Esta función garantizará que todos los elementos se desplacen al unísono cuando se arrastre el panel de control, una característica fundamental para nuestra mejora del panel de control arrastrable. A continuación, podemos probar el panel de control. Para ello, llamaremos a la función que crea el panel de control completo en el controlador de eventos OnInit y llamaremos a la función de destrucción en el controlador de eventos OnDeinit.

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

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

En este caso, simplemente llamamos a las funciones correspondientes durante la inicialización y la desinicialización del programa para poder comprobar las respuestas básicas antes de pasar a las siguientes partes. Siempre es mejor, desde el punto de vista de la programación, compilar el programa por etapas para asegurarse de que funciona. Al compilar el código, obtenemos el siguiente resultado.

GIF de INIT y DEINIT

En la visualización se puede observar que podemos inicializar y eliminar el panel correctamente. Ahora, pasemos a hacer que el panel de control responda a las interacciones del usuario. Primero tenemos que obtener la posición del cursor para determinar si se encuentra en el encabezado o en los botones, de modo que sepamos exactamente qué es lo que el usuario podría querer hacer. También rastrearemos el movimiento del cursor sobre los botones para poder conocer y visualizar los estados de paso del cursor y de clic cambiando el color, pero primero definamos una función para determinar la posición del cursor con respecto a los elementos del panel de control.

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

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

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

   return in_header || in_close || in_toggle;
}

En primer lugar, introducimos una nueva función «is_cursor_in_header_or_buttons» para permitir el posicionamiento dinámico y la funcionalidad de alternancia. La función comprobará si el cursor del ratón se encuentra sobre el panel de encabezado, el botón de cierre o el botón de alternancia, lo que permitirá arrastrar de forma interactiva y realizar acciones con los botones. Empezamos por obtener el ancho del gráfico utilizando ChartGetInteger con CHART_WIDTH_IN_PIXELS. Para el panel de cabecera, obtenemos «header_x», «header_y», «header_width» y «header_height» utilizando ObjectGetInteger con «OBJPROP_XDISTANCE», «OBJPROP_YDISTANCE», «OBJPROP_XSIZE» y OBJPROP_YSIZE para «HEADER_PANEL», calculando «header_left» y «header_right» en relación con el ancho del gráfico. Comprobamos si «mouse_x» y «mouse_y» se encuentran dentro de estos límites y, en caso afirmativo, establecemos «in_header» en «true».

Para el botón de cierre, obtenemos «close_x» y «close_y» para «CLOSE_BUTTON», definimos un área de 20 x 20 píxeles y calculamos «close_left» y «close_right», estableciendo «in_close» en «true» si el cursor se encuentra dentro de esta área. Del mismo modo, para el botón de alternancia, obtenemos «toggle_x» y «toggle_y» para «TOGGLE_BUTTON», definimos un área de 20x20 y establecemos «in_toggle» en «true» si el cursor se encuentra dentro de ella. Devolvemos «true» si el cursor se encuentra en cualquiera de estas áreas («in_header», «in_close» o «in_toggle»). Esta función será fundamental para detectar las interacciones del ratón con el encabezado y los botones arrastrables del panel de control, lo que permitirá disfrutar de nuestras funciones mejoradas de desplazamiento e interactividad. A continuación, podemos actualizar los estados al pasar el cursor visualizando los colores para facilitar su reconocimiento.

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

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

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

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

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

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

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

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

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

   update_button_hover_states(mouse_x, mouse_y);
}

Aquí presentamos dos nuevas funciones, «update_button_hover_states» y «update_header_hover_state», para añadir retroalimentación visual a las interacciones del usuario, mejorando así la usabilidad del panel de control. Empezamos con la función «update_button_hover_states», que toma como parámetros «mouse_x» y «mouse_y» para detectar cuando el cursor pasa por encima de los botones de cerrar y alternar. Para el «CLOSE_BUTTON», obtenemos «close_x» y «close_y» mediante ObjectGetInteger con «OBJPROP_XDISTANCE» y «OBJPROP_YDISTANCE», calculamos un área de 20x20 píxeles relativa al ancho del gráfico a partir de ChartGetInteger con «CHART_WIDTH_IN_PIXELS», y establecemos «is_close_hovered» si el cursor se encuentra dentro de esta área.

Si «is_close_hovered» difiere de «prev_close_hovered», actualizamos «CLOSE_BUTTON» con «ObjectSetInteger», estableciendo «OBJPROP_COLOR» en «clrWhite» y OBJPROP_BGCOLOR en «clrDodgerBlue» al pasar el cursor por encima, o en «clrYellow» y «clrNONE» cuando no sea así; actualizamos «prev_close_hovered» y llamamos a la función ChartRedraw. Del mismo modo, en el caso de «TOGGLE_BUTTON», recuperamos «toggle_x» y «toggle_y», comprobamos el área de 20x20 y actualizamos sus colores y «prev_toggle_hovered» si cambia «is_toggle_hovered», lo que garantiza una respuesta adecuada del botón.

A continuación, creamos la función «update_header_hover_state», que también toma como parámetros «mouse_x» y «mouse_y». Obtenemos «header_x», «header_y», «header_width» y «header_height» para «HEADER_PANEL» mediante «ObjectGetInteger», calculamos los límites del encabezado y excluimos las áreas de «CLOSE_BUTTON» y «TOGGLE_BUTTON» (20 × 20 píxeles cada una) para evitar solapamientos. Si «is_header_hovered» difiere de «prev_header_hovered» y «panel_dragging» es falso, actualizamos «OBJPROP_BGCOLOR» de «HEADER_PANEL» a «clrRed» cuando se pasa el cursor por encima, o a «C'60,60,60'» cuando no se pase el cursor por encima, actualizamos «prev_header_hovered» y llamamos a «ChartRedraw». A continuación, llamamos a «update_button_hover_states» para asegurarnos de que se actualicen los estados de los botones. Estas funciones proporcionarán indicaciones visuales para las acciones de arrastrar y las interacciones con los botones, lo que mejorará la interactividad del panel de control. A continuación, podemos utilizar las funciones del controlador de eventos OnChartEvent para llevar a cabo la implementación completa. Esta es la lógica que aplicamos.

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

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

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

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

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

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

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

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

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

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

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

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

En este punto, mejoramos nuestro programa actualizando el controlador de eventos OnChartEvent para que admita el posicionamiento dinámico y la funcionalidad de alternancia, lo que supone una mejora significativa con respecto a la versión anterior. Mantenemos el manejo de CHARTEVENT_OBJECT_CLICK para el «CLOSE_BUTTON», que registra el cierre con Print, reproduce un sonido con PlaySound, establece «panel_is_visible» en false, llama a la función «delete_all_objects» y vuelve a dibujar el gráfico.

La novedad es el tratamiento de los clics en el «TOGGLE_BUTTON», donde llamamos a la función «delete_all_objects», activamos o desactivamos «panel_minimized» y creamos, bien un panel minimizado con «create_minimized_dashboard» (registrando en el log «Minimizando el panel»), bien un panel completo con «create_full_dashboard» (registrando el mensaje «Maximizando el panel»). Restablecemos los estados de desplazamiento del cursor («prev_header_hovered», «prev_close_hovered», «prev_toggle_hovered») a «false», recuperamos los colores predeterminados para «HEADER_PANEL», «CLOSE_BUTTON» y «TOGGLE_BUTTON» mediante la función ObjectSetInteger, y volvemos a dibujar el gráfico.

Para el posicionamiento dinámico, añadimos el manejo de CHARTEVENT_MOUSE_MOVE cuando «panel_is_visible» es verdadero. Obtenemos «mouse_x», «mouse_y» y «mouse_state» a partir de los parámetros del evento; omitimos las actualizaciones redundantes si las coordenadas coinciden con «last_mouse_x» y «last_mouse_y» y «panel_dragging» es falso; y actualizamos estas coordenadas. Llamamos a «update_header_hover_state» para gestionar los efectos al pasar el cursor por encima. Si «prev_mouse_state» es 0 y «mouse_state» es 1, comprobamos si el cursor se encuentra sobre «HEADER_PANEL» (excluyendo las áreas «CLOSE_BUTTON» y «TOGGLE_BUTTON») mediante «ObjectGetInteger» y ChartGetInteger; a continuación, establecemos «panel_dragging» en «true», almacenamos las coordenadas en «panel_drag_x», «panel_drag_y», «panel_start_x» y «panel_start_y», establecemos el color de «HEADER_PANEL» en «clrMediumBlue» y desactivamos el desplazamiento del gráfico con «ChartSetInteger».

Mientras «panel_dragging» y «mouse_state» sean 1, calculamos el desplazamiento, actualizamos «panel_x» y «panel_y» dentro de los límites del gráfico, llamamos a «update_panel_positions» y volvemos a dibujar. Al soltar el botón del ratón, detenemos el arrastre, actualizamos los estados de paso del cursor, reactivamos el desplazamiento y volvemos a dibujar el gráfico. Actualizamos «prev_mouse_state». Estos cambios permitirán ahora arrastrar y alternar, a diferencia del manejo estático anterior que solo permitía hacer clic. Tras la compilación, obtenemos el siguiente resultado.

ARRASTRE, PASO DEL CURSOR Y ESTADOS DE VISIBILIDAD

En la visualización podemos ver que el panel de control está cobrando vida, pero tenemos que resolver los conflictos entre los controladores de eventos. Tenemos que dar prioridad a un controlador de eventos para que se le asigne un orden de ejecución preferente y así aumentar la productividad. Por ejemplo, cuando pasamos el cursor por encima o arrastramos un elemento, o incluso en modo minimizado, debemos dar prioridad a los eventos del gráfico. Cuando el panel está inactivo o en estado maximizado, debemos dar prioridad a las actualizaciones del panel de control. Lo conseguiremos modificando el evento de procesamiento de ticks, ya que es ahí donde actualizamos el panel de control.

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

Actualizamos el controlador de eventos OnTick para optimizar las actualizaciones del indicador. A diferencia de la versión anterior, que llamaba a la función «updateIndicators» basándose únicamente en «panel_is_visible», ahora añadimos condiciones para actualizar los indicadores solo cuando «panel_is_visible» es verdadero, «panel_minimized» sea falso y el cursor no se encuentre sobre el encabezado ni los botones, lo cual se comprueba mediante «is_cursor_in_header_or_buttons» con «last_mouse_x» y «last_mouse_y». Este cambio garantizará que las actualizaciones de los indicadores se suspendan cuando el panel esté minimizado o cuando el usuario interactúe con el encabezado, el botón de cierre o el botón de alternancia, lo que reducirá el procesamiento innecesario y mejorará el rendimiento durante las acciones de arrastre o alternancia. Tras la compilación, obtenemos el siguiente resultado.

GIF DE RENDIMIENTO MEJORADO

En la visualización se aprecia que el rendimiento del panel de control ha mejorado y que se han cumplido todos los objetivos. Ahora solo queda verificar el funcionamiento del proyecto, y eso se aborda en la sección anterior.


Pruebas retrospectivas (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).

BACKTEST DEL PANEL DE CONTROL


Conclusión

En conclusión, hemos mejorado nuestro panel de control del escáner de múltiples temporalidades en MQL5 añadiendo posicionamiento dinámico y la función de alternancia, partiendo de la parte 3 con una interfaz arrastrable, la opción de minimizar/maximizar y efectos interactivos al pasar el cursor, para ofrecer un mejor control al usuario. Hemos mostrado cómo implementar estas mejoras utilizando funciones como «create_minimized_dashboard» y «update_header_hover_state», lo que garantiza una integración perfecta con la cuadrícula de indicadores existente para obtener información en tiempo real sobre las operaciones. Puedes personalizar aún más este panel de control para adaptarlo a tus necesidades de negociación, lo que te permitirá monitorizar de forma más eficaz las señales del mercado en distintos intervalos de tiempo. 

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

linfo2
linfo2 | 17 jul 2025 en 17:38
Gracias, Allan. Es muy interesante, está bien documentado y aborda aspectos que no conocía. Te lo agradezco mucho.
Allan Munene Mutiiria
Allan Munene Mutiiria | 17 jul 2025 en 18:58
linfo2 #:
Gracias, Allan. Es genial, está muy bien documentado y trata aspectos que desconocía; te lo agradezco mucho
@linfo2, de nada. Muchas gracias.
Redes neuronales en el trading: descomposición en lugar de escalado (Final) Redes neuronales en el trading: descomposición en lugar de escalado (Final)
Le proponemos familiarizarse con el algoritmo para descomponer una serie temporal en capas semánticas y construir a partir de ellas un modelo eficiente. Mostramos paso a paso la arquitectura, la implementación práctica en MQL5/OpenCL y las pruebas reales sobre datos históricos de mercado.
Redes neuronales en el trading: descomposición en lugar de escalado — Construcción de módulos Redes neuronales en el trading: descomposición en lugar de escalado — Construcción de módulos
En este artículo continuamos la familiarización práctica con SSCNN: una solución arquitectónica de nueva generación capaz de trabajar con series temporales fragmentadas. En lugar de un escalado indiscriminado: modularidad inteligente, atención al detalle y normalización selectiva. Paso a paso, creamos bloques computacionales en el entorno MQL5 y sentamos las bases para un análisis predictivo fiable.
Implementación de módulos prácticos de otros lenguajes en MQL5 (Parte 02): Desarrollo de la biblioteca REQUESTS inspirada en requests de Python Implementación de módulos prácticos de otros lenguajes en MQL5 (Parte 02): Desarrollo de la biblioteca REQUESTS inspirada en requests de Python
En este artículo, implementamos un un módulo similar a requests, como el que ofrece Python para facilitar el envío y la recepción de solicitudes web en MetaTrader 5 utilizando MQL5.
Implementación de un circuito cuántico para Quantum Reservoir Computing (QRC) Implementación de un circuito cuántico para Quantum Reservoir Computing (QRC)
Hoy hablaremos de un enfoque revolucionario del aprendizaje automático en el trading mediante la computación cuántica. El artículo muestra la implementación práctica de un sistema QRC adaptativo con aprendizaje adicional continuo para predecir los movimientos del mercado en tiempo real.