Herramientas de trading de MQL5 (Parte 4): Mejorando el panel del escáner multitemporal con posicionamiento dinámico y funciones de alternancia
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:
- Comprensión de la arquitectura de posicionamiento dinámico y alternancia
- Implementación en MQL5
- Pruebas retrospectivas (backtesting)
- 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.

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.

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.

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

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.

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.

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.

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).

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
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Redes neuronales en el trading: descomposición en lugar de escalado (Final)
Redes neuronales en el trading: descomposición en lugar de escalado — Construcción de módulos
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 un circuito cuántico para Quantum Reservoir Computing (QRC)
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
Gracias, Allan. Es genial, está muy bien documentado y trata aspectos que desconocía; te lo agradezco mucho