English Русский 中文 Deutsch 日本語
preview
Operar con el Calendario Económico MQL5 (Parte 5): Mejorar el panel de control con controles adaptables y botones de filtro

Operar con el Calendario Económico MQL5 (Parte 5): Mejorar el panel de control con controles adaptables y botones de filtro

MetaTrader 5Trading |
35 1
Allan Munene Mutiiria
Allan Munene Mutiiria

Introducción

En este artículo, nos basamos en el trabajo anterior de la Parte 4 de la serie MetaQuotes Language 5 (MQL5), donde añadimos actualizaciones en tiempo real al panel de control del Calendario económico de MQL5. Aquí, nuestro objetivo es hacer que el panel sea más interactivo añadiendo botones que nos permitan controlar directamente los filtros de pares de divisas, los niveles de importancia y los filtros de intervalos de tiempo, todo ello desde el propio panel, sin necesidad de cambiar la configuración en el código. También incluiremos un botón «Cancel» que borra los filtros seleccionados y elimina los componentes del panel de control, lo que nos da un control total sobre la visualización. Por último, mejoraremos la experiencia del usuario haciendo que los botones respondan a los clics, garantizando que funcionen correctamente y proporcionen una respuesta inmediata. Los temas que trataremos en este artículo incluyen:

  1. Creación de botones de filtro y control
  2. Automatización y adición de capacidad de respuesta a los botones
  3. Probando el panel de control mejorado
  4. Conclusión

Estas incorporaciones mejorarán significativamente la usabilidad de nuestro panel de control, haciéndolo más flexible y dinámico para que los usuarios interactúen con él en tiempo real. Con estos elementos interactivos, podemos filtrar y gestionar fácilmente los datos de noticias que se muestran sin necesidad de modificar el código subyacente cada vez. Comencemos por crear los botones de filtro e integrarlos en el diseño actual de nuestro panel de control.


Creación de botones de filtro y control

En esta sección, nos centraremos en crear los botones de filtro que nos permitirán controlar los diferentes aspectos de nuestro panel de control, como el filtro de pares de divisas, el filtro de nivel de importancia y el filtro de intervalo de tiempo, directamente desde el panel. Al añadir estos botones, nos resultará más fácil interactuar con el panel de control, sin necesidad de acceder al código o modificarlo cada vez que queramos cambiar la configuración de un filtro. El objetivo es diseñar una interfaz de usuario intuitiva que ofrezca flexibilidad y, al mismo tiempo, mantenga un diseño sencillo y limpio.

En primer lugar, definiremos las posiciones y propiedades de cada uno de los botones del filtro. Colocaremos estos botones dentro del panel del control, lo que nos permitirá alternar entre diferentes configuraciones para pares de divisas, niveles de importancia y filtros de tiempo. Por ejemplo, colocaremos los botones del filtro de divisa en la esquina superior derecha del panel de control, los botones de selección del filtro de divisa, importancia y tiempo en la sección del encabezado, y el botón de cancelar justo después de su definición. Cada botón corresponderá a una configuración de filtro específica, y podemos hacer clic en estos botones para aplicar nuestros filtros preferidos. Aquí hay una imagen que muestra el diseño inicial de los botones de filtro dentro del panel de control:

PLAN DE DISEÑO

Como se ve en la imagen, los botones de filtro están organizados dentro del panel de control para facilitar el acceso y la gestión. Cada botón está diseñado para realizar una función específica, ya sea habilitar los pares de divisas disponibles, establecer el nivel de importancia de los eventos o filtrar por tiempo. Los botones también se diseñarán de forma que se distingan visualmente entre sí, para que podamos diferenciar fácilmente los distintos grupos de control.

Para implementar esto, tendremos que definir constantes para los objetos y controles adicionales que vamos a incluir en el panel de control.

#define FILTER_LABEL "FILTER_LABEL"  //--- Define the label for the filter section on the dashboard

#define FILTER_CURR_BTN "FILTER_CURR_BTN"  //--- Define the button for filtering by currency pair
#define FILTER_IMP_BTN "FILTER_IMP_BTN"  //--- Define the button for filtering by event importance
#define FILTER_TIME_BTN "FILTER_TIME_BTN"  //--- Define the button for filtering by time range

#define CANCEL_BTN "CANCEL_BTN"  //--- Define the cancel button to reset or close the filters

#define CURRENCY_BTNS "CURRENCY_BTNS"  //--- Define the collection of currency buttons for user selection

Aquí definimos un conjunto de constantes string que representan los nombres de los distintos elementos del panel de control que añadiremos, principalmente botones y etiquetas, utilizando la palabra clave #define. Utilizaremos estas constantes para crear y gestionar componentes de la interfaz de usuario, como botones de filtro y un botón de cancelación. En primer lugar, definimos «FILTER_LABEL», que representa la etiqueta de la sección de filtros del panel de control.

A continuación, definimos tres botones: «FILTER_CURR_BTN» para filtrar por par de divisas, «FILTER_IMP_BTN» para filtrar por importancia del evento y «FILTER_TIME_BTN» para filtrar por el intervalo de tiempo del evento. También definimos un «CANCEL_BTN» que nos permite restablecer o cerrar los filtros activos y eliminar los componentes del panel de control y, por último, «CURRENCY_BTNS» representa una colección de botones de divisas que nos permiten seleccionar pares de divisas específicos. Estas definiciones nos ayudan a crear un panel dinámico e interactivo en el que podemos controlar los datos que se muestran directamente desde la interfaz. Para mejorar el dinamismo, eliminamos la matriz de divisas definidas fuera, en el entorno global, de la siguiente manera:

string curr_filter[] = {"AUD","CAD","CHF","EUR","GBP","JPY","NZD","USD"};
string curr_filter_selected[];

Aquí, mejoramos la flexibilidad y el dinamismo del filtro de divisas eliminando la lista predefinida de divisas de las funciones y colocándola en un entorno global. Ahora definimos una nueva matriz global «curr_filter» para almacenar la lista de posibles divisas, como «AUD», «CAD», «CHF», «EUR», «GBP», «JPY», «NZD» y «USD». Además, creamos una matriz vacía «curr_filter_selected», que almacenará dinámicamente las divisas seleccionadas por el usuario durante el tiempo de ejecución. Por último, creamos una variable que llevará un registro de la necesidad de realizar actualizaciones, ya que en esta ocasión no las necesitaremos una vez que eliminemos el panel de control mediante el botón «Cancel». Pan comido.

bool isDashboardUpdate = true;

Simplemente definimos una variable booleana «isDashboardUpdate» y la inicializamos como verdadera. Utilizaremos la variable para realizar un seguimiento de si es necesario actualizar el panel de control. Al establecerlo en verdadero, podemos indicar que se ha producido un cambio o una acción (como la selección de un filtro o el clic en un botón) que requiere que el panel se actualice con nuevos datos o ajustes. Del mismo modo, establecerlo en falso significará que no necesitamos llevar a cabo el proceso de actualización, lo que ayuda a gestionar el estado del panel de control de manera eficiente, garantizando que solo se actualice cuando sea necesario y evitando re-renderizaciones innecesarias.

Desde el entorno global, podemos ir a la sección de inicialización y asignar nuestros componentes de filtro adicionales al panel de control. Comenzaremos con los botones superiores, que son los activadores de filtro.

createLabel(FILTER_LABEL,370,55,"Filters:",clrYellow,16,"Impact"); //--- Create a label for the "Filters" section in the dashboard

//--- Define the text, color, and state for the Currency filter button
string filter_curr_text = enableCurrencyFilter ? ShortToString(0x2714)+"Currency" : ShortToString(0x274C)+"Currency"; //--- Set text based on filter state
color filter_curr_txt_color = enableCurrencyFilter ? clrLime : clrRed; //--- Set text color based on filter state
bool filter_curr_state = enableCurrencyFilter ? true : false; //--- Set button state (enabled/disabled)
createButton(FILTER_CURR_BTN,430,55,110,26,filter_curr_text,filter_curr_txt_color,12,clrBlack); //--- Create Currency filter button
ObjectSetInteger(0,FILTER_CURR_BTN,OBJPROP_STATE,filter_curr_state); //--- Set the state of the Currency button

Aquí, estamos añadiendo una etiqueta y un botón para el filtro de divisa en el panel de control. En primer lugar, utilizamos la función «createLabel» para colocar una etiqueta titulada «Filters:» en el gráfico, en las coordenadas (370 y 55), con color amarillo y un tamaño de fuente de 16. Esta etiqueta servirá como encabezado para la sección de filtros, indicando claramente al usuario dónde se encuentran las opciones de filtro.

A continuación, definimos y configuramos el botón para el filtro «Currency». Comprobamos el estado de la variable «enableCurrencyFilter» y, en función de su valor, establecemos dinámicamente el texto del botón utilizando la variable «filter_curr_text». Si el filtro de divisa está habilitado («enableCurrencyFilter» es verdadero), el botón mostrará una marca de verificación («0x2714») con el texto «Currency», lo que indica que el filtro está activo; si está deshabilitado, se mostrará una cruz («0x274C») en su lugar, lo que indica que el filtro está inactivo. Lo conseguimos utilizando un operador ternario, que funciona de manera similar al operador if, solo que es pequeño, sencillo y directo.

Para reflejar mejor el estado del filtro visualmente, configuramos el color del texto del botón utilizando la variable «filter_curr_txt_color». Si el filtro está activo, el texto aparecerá en verde; si está inactivo, aparecerá en rojo. También utilizamos la variable booleana «filter_curr_state» para controlar el estado real del botón, que determina si el botón está habilitado o deshabilitado.

A continuación, creamos el botón utilizando la función «createButton» y lo colocamos en (430, 55) en el gráfico con la etiqueta adecuada («filter_curr_text»), el color de texto («filter_curr_txt_color») y un fondo negro. Por último, utilizamos la función ObjectSetInteger para establecer el estado del botón (habilitado o deshabilitado) haciendo referencia a la variable «filter_curr_state». Esto garantiza que la apariencia y la funcionalidad del botón coincidan con la configuración actual del filtro. Tras la compilación, obtenemos el siguiente resultado.

FILTRO DE DIVISA Y ETIQUETA

Fue todo un éxito. Ahora podemos proceder a añadir los botones de filtro utilizando la misma lógica.

//--- Define the text, color, and state for the Importance filter button
string filter_imp_text = enableImportanceFilter ? ShortToString(0x2714)+"Importance" : ShortToString(0x274C)+"Importance"; //--- Set text based on filter state
color filter_imp_txt_color = enableImportanceFilter ? clrLime : clrRed; //--- Set text color based on filter state
bool filter_imp_state = enableImportanceFilter ? true : false; //--- Set button state (enabled/disabled)
createButton(FILTER_IMP_BTN,430+110,55,120,26,filter_imp_text,filter_imp_txt_color,12,clrBlack); //--- Create Importance filter button
ObjectSetInteger(0,FILTER_IMP_BTN,OBJPROP_STATE,filter_imp_state); //--- Set the state of the Importance button

//--- Define the text, color, and state for the Time filter button
string filter_time_text = enableTimeFilter ? ShortToString(0x2714)+"Time" : ShortToString(0x274C)+"Time"; //--- Set text based on filter state
color filter_time_txt_color = enableTimeFilter ? clrLime : clrRed; //--- Set text color based on filter state
bool filter_time_state = enableTimeFilter ? true : false; //--- Set button state (enabled/disabled)
createButton(FILTER_TIME_BTN,430+110+120,55,70,26,filter_time_text,filter_time_txt_color,12,clrBlack); //--- Create Time filter button
ObjectSetInteger(0,FILTER_TIME_BTN,OBJPROP_STATE,filter_time_state); //--- Set the state of the Time button

//--- Create a Cancel button to reset all filters
createButton(CANCEL_BTN,430+110+120+79,51,50,30,"X",clrWhite,17,clrRed,clrNONE); //--- Create the Cancel button with an "X"

//--- Redraw the chart to update the visual elements
ChartRedraw(0); //--- Redraw the chart to reflect all changes made above

Aquí, configuramos los botones para los filtros «Importance», «Time» y «Cancel» en el panel de control. Para el filtro «Importance», primero definimos el texto del botón con la variable «filter_imp_text». Según el valor de «enableImportanceFilter», si el filtro está activo, mostramos una marca de verificación («0x2714») junto al texto «Importance», lo que indica que el filtro está habilitado; si no lo está, mostramos una cruz («0x274C») con el mismo texto, lo que indica que el filtro está deshabilitado. También configuramos el color del texto del botón utilizando «filter_imp_txt_color», que es verde cuando está habilitado y rojo cuando está deshabilitado. El booleano «filter_imp_state» controla si el botón está habilitado o deshabilitado.

A continuación, utilizamos la función «createButton» para crear el botón de filtro «Importance» y lo colocamos en la posición (430+110, 55) con el texto, el color y el estado adecuados. A continuación, utilizamos ObjectSetInteger para establecer el estado del botón («OBJPROP_STATE») basándonos en «filter_imp_state», lo que garantiza que el botón refleje el estado correcto.

Seguimos un proceso similar para el botón del filtro «Time». Definimos su texto en «filter_time_text» y ajustamos el color con «filter_time_txt_color» en función del valor de «enableTimeFilter». El botón se crea en la posición (430+110+120, 55) y el estado se establece en consecuencia utilizando «filter_time_state».

Por último, creamos el botón «Cancel» utilizando la función «createButton», que restablecerá todos los filtros y eliminará el panel de control cuando se haga clic en él. Este botón se encuentra en la posición (430+110+120+79, 51) y tiene una «X» blanca sobre fondo rojo para indicar su función. Por último, llamamos a la función ChartRedraw para actualizar el gráfico y mostrar los botones y cambios recién creados. Al ejecutarlo, obtenemos el siguiente resultado.

BOTONES DE FILTRO COMPLETO

Fue todo un éxito. Ahora hemos añadido todos los botones de filtro al panel de control. Sin embargo, durante la creación de los textos, utilizamos una concatenación de caracteres extranjeros llamados caracteres Unicode. Vamos a mirarlos en profundidad.

ShortToString(0x2714);
ShortToString(0x274C);

Aquí utilizamos las funciones «ShortToString(0x2714)» y «ShortToString(0x274C)» para representar caracteres Unicode en MQL5, y los valores «0x2714» y «0x274C» hacen referencia a símbolos específicos del conjunto de caracteres Unicode.

  • «0x2714» es el punto de código Unicode para el símbolo «CHECK MARK» (marca de verificación). Se utiliza para indicar que algo está habilitado, completado o es correcto. En el contexto de los botones de filtro, lo utilizamos para mostrar que un filtro (como el filtro de divisa o importancia) está activo o habilitado.
  • «0x274C» es el punto de código Unicode para el símbolo «CROSS MARK» (marca cruzada). Se utiliza para representar algo desactivado, no realizado o incorrecto. Aquí lo utilizamos para indicar que un filtro está inactivo o desactivado.

También puede utilizar sus propios caracteres, siempre y cuando sean compatibles con el entorno de programación MQL5. El conjunto de caracteres es el siguiente:

EJEMPLO DE MARCA DE VERIFICACIÓN UNICODE

En el código, la función ShortToString convierte estos puntos de código Unicode en sus representaciones de caracteres correspondientes. Estos caracteres se añaden al texto de los botones de filtro para mostrar visualmente si un filtro está activo o no. A continuación, podemos crear los botones del filtro de divisa de forma dinámica.

int curr_size = 51;               //--- Button width
int button_height = 22;           //--- Button height
int spacing_x = 0;               //--- Horizontal spacing
int spacing_y = 3;               //--- Vertical spacing
int max_columns = 4;              //--- Number of buttons per row

for (int i = 0; i < ArraySize(curr_filter); i++){
   int row = i / max_columns;                              //--- Determine the row
   int col = i % max_columns;                             //--- Determine the column

   int x_pos = 575 + col * (curr_size + spacing_x);        //--- Calculate X position
   int y_pos = 83 + row * (button_height + spacing_y);    //--- Calculate Y position
   
   //--- Create button with dynamic positioning
   createButton(CURRENCY_BTNS+IntegerToString(i),x_pos,y_pos,curr_size,button_height,curr_filter[i],clrBlack);
}

if (enableCurrencyFilter == true){
   ArrayFree(curr_filter_selected);
   ArrayCopy(curr_filter_selected,curr_filter);
   Print("CURRENCY FILTER ENABLED");
   ArrayPrint(curr_filter_selected);
   
   for (int i = 0; i < ArraySize(curr_filter_selected); i++) {
      // Set the button to "clicked" (selected) state by default
      ObjectSetInteger(0, CURRENCY_BTNS + IntegerToString(i), OBJPROP_STATE, true);  // true means clicked
   }
}

Aquí, estamos creando dinámicamente botones de filtro de divisa y gestionando su diseño y estado en función de si el filtro está habilitado o no. Comenzamos definiendo los parámetros de disposición de los botones. Establecemos la variable entera (integer, int) «curr_size» en 51, lo que determina el ancho de cada botón, y «button_height» se establece en 22, lo que determina la altura del botón. «spacing_x» y «spacing_y» se establecen en 0 y 3 respectivamente para controlar el espaciado entre botones en las direcciones horizontal y vertical. También definimos «max_columns» como 4, lo que limita el número de botones por fila a cuatro.

A continuación, utilizamos el bucle for para recorrer la matriz «curr_filter», que contiene los códigos de los pares de divisas, como «AUD», «CAD», etc. Para cada iteración, calculamos la fila y la columna donde se debe colocar el botón. Calculamos las filas como «i / max_columns» para determinar el número de fila, y las columnas como «i % max_columns» para determinar la columna dentro de la fila. Utilizando estos valores, calculamos las posiciones X («x_pos») e Y («y_pos») de los botones en la pantalla. A continuación, llamamos a la función «createButton» para crear dinámicamente cada botón con su etiqueta establecida en la divisa correspondiente de la matriz «curr_filter» y el color establecido en negro.

Después de crear los botones, comprobamos si el filtro está habilitado evaluando el valor de «enableCurrencyFilter». Si el filtro está habilitado, borramos la matriz de divisas seleccionadas utilizando la función ArrayFree y copiamos el contenido de «curr_filter» en «curr_filter_selected» utilizando la función ArrayCopy. Esto copia efectivamente todas las divisas en la matriz seleccionada. A continuación, imprimimos «CURRENCY FILTER ENABLED» (FILTRO DE DIVISA ACTIVADO) y mostramos la matriz de filtros seleccionada utilizando la función ArrayPrint. Por último, recorremos las divisas seleccionadas en la matriz «curr_filter_selected» y establecemos el estado de cada botón correspondiente como seleccionado utilizando la función ObjectSetInteger al plantear los parámetros respectivos. Utilizamos la función IntegerToString para concatenar el índice de la selección y añadirlo a la macro «CURRENCY_BTNS», y establecemos el estado en verdadero, lo que marca visualmente el botón como pulsado.

Tras la compilación, obtenemos los siguientes resultados.

RESULTADO FINAL DE LOS BOTONES

En la imagen podemos ver que hemos añadido correctamente los botones de filtro al panel de control. Lo que tenemos que hacer ahora es actualizar la función responsable de destruir el panel de control para que también tenga en cuenta los componentes recién añadidos.

//+------------------------------------------------------------------+
//|      Function to destroy the Dashboard panel                     |
//+------------------------------------------------------------------+

void destroy_Dashboard(){
   
   //--- Delete the main rectangle that defines the dashboard background
   ObjectDelete(0,"MAIN_REC");
   
   //--- Delete the sub-rectangles that separate sections in the dashboard
   ObjectDelete(0,"SUB_REC1");
   ObjectDelete(0,"SUB_REC2");
   
   //--- Delete the header label that displays the title of the dashboard
   ObjectDelete(0,"HEADER_LABEL");
   
   //--- Delete the time and impact labels from the dashboard
   ObjectDelete(0,"TIME_LABEL");
   ObjectDelete(0,"IMPACT_LABEL");

   //--- Delete all calendar-related objects
   ObjectsDeleteAll(0,"ARRAY_CALENDAR");
   
   //--- Delete all news-related objects
   ObjectsDeleteAll(0,"ARRAY_NEWS");

   //--- Delete all data holder objects (for storing data within the dashboard)
   ObjectsDeleteAll(0,"DATA_HOLDERS");
   
   //--- Delete the impact label objects (impact-related elements in the dashboard)
   ObjectsDeleteAll(0,"IMPACT_LABEL");

   //--- Delete the filter label that identifies the filter section
   ObjectDelete(0,"FILTER_LABEL");
   
   //--- Delete the filter buttons for Currency, Importance, and Time
   ObjectDelete(0,"FILTER_CURR_BTN");
   ObjectDelete(0,"FILTER_IMP_BTN");
   ObjectDelete(0,"FILTER_TIME_BTN");
   
   //--- Delete the cancel button that resets or closes the filters
   ObjectDelete(0,"CANCEL_BTN");
   
   //--- Delete all currency filter buttons dynamically created
   ObjectsDeleteAll(0,"CURRENCY_BTNS");
   
   //--- Redraw the chart to reflect the removal of all dashboard components
   ChartRedraw(0);
   
}

También debemos actualizar la lógica del controlador de eventos OnTick para que solo ejecute las actualizaciones mientras el indicador de actualizaciones sea verdadero. Lo conseguimos mediante la siguiente lógica.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
//---

   if (isDashboardUpdate){
      update_dashboard_values(curr_filter_selected);
   }   
}

Aquí, estamos comprobando si la condición «isDashboardUpdate» es verdadera, lo que indica que el panel de control debe actualizarse con los datos más recientes. Si se cumple esta condición, llamamos a la función «update_dashboard_values» para actualizar los valores que se muestran en el panel de control utilizando los filtros de divisas seleccionados almacenados en la matriz «curr_filter_selected».

La matriz «curr_filter_selected» contiene las divisas que se han seleccionado para el filtrado y, al pasarla a la función «update_dashboard_values», nos aseguramos de que el panel de control refleje las selecciones de filtro más actuales. Si el indicador variable es falso, no se tendrán en cuenta las actualizaciones. Eso es todo lo que necesitamos tener en cuenta para crear los botones de filtro. Ahora solo tenemos que añadir capacidad de respuesta a los botones creados, lo cual se tratará en la siguiente sección.


Automatización y adición de capacidad de respuesta a los botones

Para añadir capacidad de respuesta al panel de control, tendremos que incluir un detector de eventos que registre los clics y actúe en función de ellos. Para ello, utilizaremos el controlador de eventos integrado OnChartEvent en MQL5.

//+------------------------------------------------------------------+
//|    OnChartEvent handler function                                 |
//+------------------------------------------------------------------+
void  OnChartEvent(
   const int       id,       // event ID  
   const long&     lparam,   // long type event parameter 
   const double&   dparam,   // double type event parameter 
   const string&   sparam    // string type event parameter 
){

//---

}

Esta es la función responsable de reconocer actividades en los gráficos, como cambios, clics, creaciones de objetos y muchas más. Sin embargo, solo nos interesan los clics en el gráfico, ya que solo queremos escuchar los clics en los botones. Comenzaremos por lo más sencillo hasta llegar a lo más complejo. El botón de cancelar es el más sencillo.

if (id == CHARTEVENT_OBJECT_CLICK){ //--- Check if the event is a click on an object
   
   if (sparam == CANCEL_BTN){ //--- If the Cancel button is clicked
      isDashboardUpdate = false; //--- Set dashboard update flag to false
      destroy_Dashboard(); //--- Call the function to destroy the dashboard
   }
}

Aquí, nos ocupamos del caso en el que el evento detectado es un clic en un objeto, detectado cuando el ID del evento es CHARTEVENT_OBJECT_CLICK. Si el evento es un clic, comprobamos si el objeto en el que se ha hecho clic es «CANCEL_BTN» evaluando si el parámetro de cadena «sparam» es «CANCEL_BTN». Si se cumple esta condición, significa que se ha pulsado el botón «Cancel» del panel de control. En respuesta, establecemos el indicador global «isDashboardUpdate» en falso, lo que desactiva de forma efectiva las actualizaciones posteriores del panel de control. A continuación, llamamos a la función «destroy_Dashboard» para eliminar todos los elementos gráficos asociados al panel de control del gráfico, borrándolo así. Esto garantiza que la interfaz se reinicie y se borre al hacer clic en el botón «Cancel». Aquí está su ilustración.

GIF DE CANCELAR

Fue todo un éxito. Ahora podemos aplicar la misma lógica para automatizar los demás botones. Ahora automatizaremos el botón del filtro de divisa. Utilizamos la siguiente lógica en un fragmento de código para lograrlo.

if (sparam == FILTER_CURR_BTN){ //--- If the Currency filter button is clicked
   bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); //--- Get the button state (clicked/unclicked)
   enableCurrencyFilter = btn_state; //--- Update the Currency filter flag
   Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableCurrencyFilter); //--- Log the state
   string filter_curr_text = enableCurrencyFilter ? ShortToString(0x2714)+"Currency" : ShortToString(0x274C)+"Currency"; //--- Set button text based on state
   color filter_curr_txt_color = enableCurrencyFilter ? clrLime : clrRed; //--- Set button text color based on state
   ObjectSetString(0,FILTER_CURR_BTN,OBJPROP_TEXT,filter_curr_text); //--- Update the button text
   ObjectSetInteger(0,FILTER_CURR_BTN,OBJPROP_COLOR,filter_curr_txt_color); //--- Update the button text color
   Print("Success. ¡Cambios actualizados! State: "+(string)enableCurrencyFilter); //--- Log success
   ChartRedraw(0); //--- Redraw the chart to reflect changes
}

Aquí gestionamos el comportamiento cuando se hace clic en «FILTER_CURR_BTN» (botón de filtro de divisas). Cuando se cumple esta condición, recuperamos el estado actual del botón (pulsado o no pulsado) utilizando la función ObjectGetInteger con la propiedad OBJPROP_STATE, y lo almacenamos en la variable «btn_state». A continuación, actualizamos el indicador «enableCurrencyFilter» con el valor de «btn_state» para reflejar si el filtro de divisa está activo.

A continuación, registramos el estado del botón y el valor actualizado del indicador para proporcionar información mediante la función Print. Según el estado del filtro de divisa, configuramos dinámicamente el texto del botón utilizando una marca de verificación o una cruz con la función ShortToString y actualizamos su color a verde para activo o rojo para inactivo. Estas actualizaciones se aplican al botón utilizando ObjectSetString para el texto y ObjectSetInteger para la propiedad de color.

Por último, registramos un mensaje de éxito para confirmar que los cambios se han aplicado y llamamos a la función ChartRedraw para actualizar el gráfico, asegurándonos de que los cambios visuales del botón se muestren inmediatamente. Esta interacción nos permite alternar el filtro de divisa de forma dinámica y reflejar los cambios en el panel de control. El mismo proceso se aplica a los demás botones de filtro.

if (sparam == FILTER_IMP_BTN){ //--- If the Importance filter button is clicked
   bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); //--- Get the button state
   enableImportanceFilter = btn_state; //--- Update the Importance filter flag
   Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableImportanceFilter); //--- Log the state
   string filter_imp_text = enableImportanceFilter ? ShortToString(0x2714)+"Importance" : ShortToString(0x274C)+"Importance"; //--- Set button text
   color filter_imp_txt_color = enableImportanceFilter ? clrLime : clrRed; //--- Set button text color
   ObjectSetString(0,FILTER_IMP_BTN,OBJPROP_TEXT,filter_imp_text); //--- Update the button text
   ObjectSetInteger(0,FILTER_IMP_BTN,OBJPROP_COLOR,filter_imp_txt_color); //--- Update the button text color
   Print("Success. ¡Cambios actualizados! State: "+(string)enableImportanceFilter); //--- Log success
   ChartRedraw(0); //--- Redraw the chart
}
if (sparam == FILTER_TIME_BTN){ //--- If the Time filter button is clicked
   bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); //--- Get the button state
   enableTimeFilter = btn_state; //--- Update the Time filter flag
   Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableTimeFilter); //--- Log the state
   string filter_time_text = enableTimeFilter ? ShortToString(0x2714)+"Time" : ShortToString(0x274C)+"Time"; //--- Set button text
   color filter_time_txt_color = enableTimeFilter ? clrLime : clrRed; //--- Set button text color
   ObjectSetString(0,FILTER_TIME_BTN,OBJPROP_TEXT,filter_time_text); //--- Update the button text
   ObjectSetInteger(0,FILTER_TIME_BTN,OBJPROP_COLOR,filter_time_txt_color); //--- Update the button text color
   Print("Success. ¡Cambios actualizados! State: "+(string)enableTimeFilter); //--- Log success
   ChartRedraw(0); //--- Redraw the chart
}

Aquí definimos el comportamiento para gestionar los clics en «FILTER_IMP_BTN» (botón de filtro de importancia) y «FILTER_TIME_BTN» (botón de filtro de tiempo), lo que permite alternar dinámicamente entre estos filtros. Para el «FILTER_IMP_BTN», cuando se hace clic en él, primero recuperamos su estado actual utilizando ObjectGetInteger con la propiedad OBJPROP_STATE y lo almacenamos en la variable «btn_state». A continuación, actualizamos el indicador «enableImportanceFilter» para reflejar si el filtro de importancia está activo. Mediante la función Print, registramos el estado del botón y el valor actualizado del indicador. Dependiendo del estado, establecemos el texto del botón como una marca de verificación o una cruz mediante la función ShortToString y actualizamos su color a verde para activo o rojo para inactivo. Estos cambios se aplican utilizando ObjectSetString para el texto y ObjectSetInteger para las funciones de propiedad de color. Por último, registramos un mensaje de éxito y llamamos a la función ChartRedraw para garantizar que las actualizaciones se apliquen visualmente.

Del mismo modo, para «FILTER_TIME_BTN», seguimos el mismo proceso. Recuperamos el estado del botón utilizando la función ObjectGetInteger y actualizamos el indicador «enableTimeFilter» en consecuencia. Registramos el estado y las actualizaciones de los indicadores para obtener comentarios. El texto y el color del botón se actualizan dinámicamente para reflejar el estado (activo/inactivo), utilizando las funciones ShortToString, ObjectSetString y ObjectSetInteger, respectivamente. Después de confirmar las actualizaciones con un registro, volvemos a dibujar el gráfico con la función ChartRedraw. Este proceso garantiza la capacidad de respuesta en tiempo real de los botones de filtro, lo que nos proporciona una experiencia fluida al alternar entre sus funciones. Aquí hay un resultado visual.

GIF DE BOTONES DE FILTRO

Fue todo un éxito. Ahora podemos proceder a automatizar también los botones de divisas.

if (StringFind(sparam,CURRENCY_BTNS) >= 0){ //--- If a Currency button is clicked
   string selected_curr = ObjectGetString(0,sparam,OBJPROP_TEXT); //--- Get the text of the clicked button
   Print("BTN NAME = ",sparam,", CURRENCY = ",selected_curr); //--- Log the button name and currency
   
   bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); //--- Get the button state
   
   if (btn_state == false){ //--- If the button is unselected
      Print("BUTTON IS IN UN-SELECTED MODE.");
      //--- Loop to find and remove the currency from the array
      for (int i = 0; i < ArraySize(curr_filter_selected); i++) {
         if (curr_filter_selected[i] == selected_curr) {
            //--- Shift elements to remove the selected currency
            for (int j = i; j < ArraySize(curr_filter_selected) - 1; j++) {
               curr_filter_selected[j] = curr_filter_selected[j + 1];
            }
            ArrayResize(curr_filter_selected, ArraySize(curr_filter_selected) - 1); //--- Resize the array
            Print("Removed from selected filters: ", selected_curr); //--- Log removal
            break;
         }
      }
   }
   else if (btn_state == true){ //--- If the button is selected
      Print("BUTTON IS IN SELECTED MODE. TAKE ACTION");
      //--- Check for duplicates
      bool already_selected = false;
      for (int j = 0; j < ArraySize(curr_filter_selected); j++) {
         if (curr_filter_selected[j] == selected_curr) {
            already_selected = true;
            break;
         }
      }

      //--- If not already selected, add to the array
      if (!already_selected) {
         ArrayResize(curr_filter_selected, ArraySize(curr_filter_selected) + 1); //--- Resize array
         curr_filter_selected[ArraySize(curr_filter_selected) - 1] = selected_curr; //--- Add the new currency
         Print("Added to selected filters: ", selected_curr); //--- Log addition
      }
      else {
         Print("Currency already selected: ", selected_curr); //--- Log already selected
      }
      
   }
   Print("SELECTED ARRAY SIZE = ",ArraySize(curr_filter_selected)); //--- Log the size of the selected array
   ArrayPrint(curr_filter_selected); //--- Print the selected array
   
   update_dashboard_values(curr_filter_selected); //--- Update the dashboard with the selected filters
   Print("SUCCESS. DASHBOARD UPDATED"); //--- Log success
   
   ChartRedraw(0); //--- Redraw the chart to reflect changes
}

Aquí, gestionamos los clics en los botones del filtro de divisas para administrar dinámicamente las divisas seleccionadas y actualizar el panel de control en consecuencia. Cuando se hace clic en un botón, primero determinamos si pertenece al grupo «CURRENCY_BTNS» utilizando la función StringFind. Si es cierto, recuperamos la etiqueta de texto del botón utilizando la función ObjectGetString con la propiedad «OBJPROP_TEXT» para identificar la divisa que representa. A continuación, comprobamos el estado del botón utilizando ObjectGetInteger con la propiedad «OBJPROP_STATE» para determinar si el botón está seleccionado (verdadero) o no seleccionado (falso).

Si el botón no está seleccionado, eliminamos la divisa correspondiente de la matriz «curr_filter_selected». Para lograrlo, recorremos la matriz para localizar la divisa coincidente, desplazamos todos los elementos posteriores hacia la izquierda para sobrescribirla y, a continuación, cambiamos el tamaño de la matriz utilizando la función ArrayResize para eliminar la última posición. Cada eliminación se registra para confirmar la acción. Por el contrario, si el botón está seleccionado, comprobamos si hay duplicados para evitar añadir la misma divisa varias veces. Si la divisa aún no está en la matriz, cambiamos el tamaño de la matriz con la función «ArrayResize», añadimos la nueva divisa a la última posición y registramos la adición. Si la divisa ya está seleccionada, se registra un mensaje para indicar que no es necesario realizar ninguna acción adicional.

Después de actualizar la matriz «curr_filter_selected», registramos su tamaño y contenido utilizando la función ArrayPrint para mayor visibilidad. A continuación, llamamos a la función «update_dashboard_values» para actualizar el panel con los filtros recién seleccionados. Para garantizar que todos los cambios se reflejen visualmente, concluimos llamando a la función ChartRedraw y actualizando la interfaz del gráfico en tiempo real.

Dado que ahora disponemos de diferentes filtros de divisas tras la interacción del usuario, debemos actualizar la función responsable de las actualizaciones con las últimas divisas seleccionadas por el usuario.

//+------------------------------------------------------------------+
//| Function to update dashboard values                              |
//+------------------------------------------------------------------+
void update_dashboard_values(string &curr_filter_array[]){

//---

      //--- Check if the event’s currency matches any in the filter array (if the filter is enabled)
      bool currencyMatch = false;
      if (enableCurrencyFilter) {
         for (int j = 0; j < ArraySize(curr_filter_array); j++) {
            if (country.currency == curr_filter_array[j]) {
               currencyMatch = true;
               break;
            }
         }
         
         //--- If no match found, skip to the next event
         if (!currencyMatch) {
            continue;
         }
      }

//---

}

Aquí actualizamos la función «update_dashboard_values» introduciendo una mejora crucial mediante el uso del parámetro «curr_filter_array», pasado por referencia utilizando el símbolo «&». Este diseño nos permite manipular y trabajar directamente con la matriz de filtros de divisas seleccionadas, lo que garantiza que el panel de control permanezca sincronizado con las preferencias del usuario.

Esto es lo que queremos decir con «Pasar por referencia (&)». El parámetro «curr_filter_array» se pasa por referencia a la función. Esto significa que la función accede a la matriz real en la memoria en lugar de a una copia. Los cambios en la matriz dentro de la función (si los hay) afectan directamente a la matriz original fuera de la función. Este enfoque mejora la eficiencia, especialmente en matrices más grandes, y mantiene la coherencia con las selecciones de filtro actuales del usuario. Más adelante, en lugar de utilizar la matriz inicial, la sustituimos por la última, que se pasa por referencia y contiene las preferencias del usuario. Hemos resaltado los cambios en amarillo para mayor claridad. Al compilar, obtenemos el siguiente resultado.

GID DE BOTONES DE DIVISA

En la visualización, podemos ver que el panel se actualiza cada vez que hacemos clic en cualquiera de los botones de divisas, lo cual es un éxito. Sin embargo, actualmente, existe una dependencia del botón de filtro de divisas con respecto a las divisas, ya que después de hacer clic en el filtro de una sola divisa, el panel de control no se actualiza. Para resolver esto, solo tenemos que llamar a la función de actualización en cada botón de filtro, como se muestra a continuación.

if (sparam == FILTER_CURR_BTN){ //--- If the Currency filter button is clicked
   bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); //--- Get the button state (clicked/unclicked)
   enableCurrencyFilter = btn_state; //--- Update the Currency filter flag
   Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableCurrencyFilter); //--- Log the state
   string filter_curr_text = enableCurrencyFilter ? ShortToString(0x2714)+"Currency" : ShortToString(0x274C)+"Currency"; //--- Set button text based on state
   color filter_curr_txt_color = enableCurrencyFilter ? clrLime : clrRed; //--- Set button text color based on state
   ObjectSetString(0,FILTER_CURR_BTN,OBJPROP_TEXT,filter_curr_text); //--- Update the button text
   ObjectSetInteger(0,FILTER_CURR_BTN,OBJPROP_COLOR,filter_curr_txt_color); //--- Update the button text color
   update_dashboard_values(curr_filter_selected);
   Print("Success. ¡Cambios actualizados! State: "+(string)enableCurrencyFilter); //--- Log success
   ChartRedraw(0); //--- Redraw the chart to reflect changes
}
if (sparam == FILTER_IMP_BTN){ //--- If the Importance filter button is clicked
   bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); //--- Get the button state
   enableImportanceFilter = btn_state; //--- Update the Importance filter flag
   Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableImportanceFilter); //--- Log the state
   string filter_imp_text = enableImportanceFilter ? ShortToString(0x2714)+"Importance" : ShortToString(0x274C)+"Importance"; //--- Set button text
   color filter_imp_txt_color = enableImportanceFilter ? clrLime : clrRed; //--- Set button text color
   ObjectSetString(0,FILTER_IMP_BTN,OBJPROP_TEXT,filter_imp_text); //--- Update the button text
   ObjectSetInteger(0,FILTER_IMP_BTN,OBJPROP_COLOR,filter_imp_txt_color); //--- Update the button text color
   update_dashboard_values(curr_filter_selected);
   Print("Success. ¡Cambios actualizados! State: "+(string)enableImportanceFilter); //--- Log success
   ChartRedraw(0); //--- Redraw the chart
}
if (sparam == FILTER_TIME_BTN){ //--- If the Time filter button is clicked
   bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); //--- Get the button state
   enableTimeFilter = btn_state; //--- Update the Time filter flag
   Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableTimeFilter); //--- Log the state
   string filter_time_text = enableTimeFilter ? ShortToString(0x2714)+"Time" : ShortToString(0x274C)+"Time"; //--- Set button text
   color filter_time_txt_color = enableTimeFilter ? clrLime : clrRed; //--- Set button text color
   ObjectSetString(0,FILTER_TIME_BTN,OBJPROP_TEXT,filter_time_text); //--- Update the button text
   ObjectSetInteger(0,FILTER_TIME_BTN,OBJPROP_COLOR,filter_time_txt_color); //--- Update the button text color
   update_dashboard_values(curr_filter_selected);
   Print("Success. ¡Cambios actualizados! State: "+(string)enableTimeFilter); //--- Log success
   ChartRedraw(0); //--- Redraw the chart
}

Aquí, solo nos aseguramos de llamar a la función de actualizaciones para mejorar la independencia de los eventos del panel de control. Hemos resaltado los cambios en color amarillo para mayor claridad. Por lo tanto, con estos cambios, los botones funcionan de forma independiente. Aquí tienes una visualización rápida.

GIF DE BOTONES INDEPENDIENTES

Hasta ahora, hemos logrado integrar los filtros de divisa. Siguiendo el mismo procedimiento, podemos continuar e integrar también los filtros de importancia. Esto será un poco complejo, ya que tendremos que trabajar con los botones existentes para añadir el efecto de filtro y necesitaremos dos matrices para los niveles de importancia: una principal y otra secundaria que utilizaremos para realizar las comparaciones. Primero llevaremos todas las matrices importantes al entorno global para poder acceder a ellas desde cualquier parte del código.

//--- Define labels for impact levels and size of impact display areas
string impact_labels[] = {"None","Low","Medium","High"};
string impact_filter_selected[];

// Define the levels of importance to filter (low, moderate, high)
ENUM_CALENDAR_EVENT_IMPORTANCE allowed_importance_levels[] = {CALENDAR_IMPORTANCE_NONE,CALENDAR_IMPORTANCE_LOW, CALENDAR_IMPORTANCE_MODERATE, CALENDAR_IMPORTANCE_HIGH};
ENUM_CALENDAR_EVENT_IMPORTANCE imp_filter_selected[];

Aquí definimos la matriz de cadenas «impact_labels», que contiene los diferentes niveles de impacto disponibles para que el usuario seleccione. Las etiquetas son «None», «Low», «Medium» y «High». Utilizamos esta matriz para presentar estas opciones al usuario con el fin de filtrar los eventos del calendario en función de su impacto percibido.

A continuación, introducimos la matriz «impact_filter_selected», que almacenará las etiquetas reales que el usuario seleccione de la matriz «impact_labels». Cada vez que el usuario interactúa con la interfaz y elige un nivel de impacto, añadimos la etiqueta correspondiente a esta matriz. Está en formato de cadena para que podamos interpretar fácilmente los respectivos niveles seleccionados, aparte de la lista de enumeración. Esto nos permite realizar un seguimiento dinámico de las preferencias de filtro del usuario.

A continuación, definimos la matriz «allowed_importance_levels», que contiene los valores enumerados del tipo ENUM_CALENDAR_EVENT_IMPORTANCE. Estos valores enumerados están asociados con los niveles de impacto: «CALENDAR_IMPORTANCE_NONE», «CALENDAR_IMPORTANCE_LOW», «CALENDAR_IMPORTANCE_MODERATE» y «CALENDAR_IMPORTANCE_HIGH». Ya los habíamos definido, solo que los trasladamos al entorno global. Estos valores se utilizarán para filtrar los eventos del calendario en función de su importancia.

También definimos la matriz «imp_filter_selected», donde almacenamos los niveles de importancia correspondientes a las etiquetas de impacto seleccionadas por el usuario. A medida que el usuario selecciona etiquetas de «impact_labels», emparejamos cada etiqueta con su nivel de importancia correspondiente de «allowed_importance_levels» y almacenamos el resultado en «imp_filter_selected». Esta matriz se utilizará para filtrar los eventos del calendario, asegurando que solo se muestren los eventos con los niveles de importancia seleccionados.

Ahora que pasamos a la función de inicialización, actualizaremos los botones para mayor claridad, ya que ahora los utilizamos no solo como guía sobre los resultados de los eventos, sino también para el proceso de filtrado. Por lo tanto, queremos mostrar su estado activo o inactivo cuando se hace clic en ellos.

if (enableImportanceFilter == true) { 
   ArrayFree(imp_filter_selected); //--- Clear the existing selections in the importance filter array
   ArrayCopy(imp_filter_selected, allowed_importance_levels); //--- Copy all allowed importance levels as default selections
   ArrayFree(impact_filter_selected);
   ArrayCopy(impact_filter_selected, impact_labels);
   Print("IMPORTANCE FILTER ENABLED"); //--- Log that importance filter is enabled
   ArrayPrint(imp_filter_selected); //--- Print the current selection of importance levels
   ArrayPrint(impact_filter_selected);
   
   // Loop through the importance levels and set their buttons to "selected" state
   for (int i = 0; i < ArraySize(imp_filter_selected); i++) {
      string btn_name = IMPACT_LABEL+IntegerToString(i); //--- Dynamically name the button for each importance level
      ObjectSetInteger(0, btn_name, OBJPROP_STATE, true); //--- Set the button state to "clicked" (selected)
      ObjectSetInteger(0, btn_name, OBJPROP_BORDER_COLOR, clrNONE); //--- Set the button state to "clicked" (selected)
   }
}

Aquí, primero comprobamos si la variable «enableImportanceFilter» está establecida en verdadero. Si es así, procedemos a configurar el sistema de filtro de importancia. Comenzamos borrando las selecciones existentes en la matriz «imp_filter_selected» utilizando la función ArrayFree. A continuación, copiamos todos los valores de la matriz «allowed_importance_levels» en «imp_filter_selected», estableciendo esencialmente todos los niveles de importancia como selecciones predeterminadas. Esto significa que, por defecto, todos los niveles de importancia están seleccionados inicialmente para el filtrado.

A continuación, borramos la matriz «impact_filter_selected» con la función ArrayFree, lo que garantiza que se eliminen todas las selecciones anteriores de la matriz de etiquetas de impacto. A continuación, copiamos todos los valores de la matriz «impact_labels» en «impact_filter_selected». Esto garantiza que las etiquetas que representan los niveles de impacto (como «None», «Low», «Medium» y «High») estén disponibles para el filtro. Después de configurar las matrices, imprimimos un mensaje de registro, «IMPORTANCE FILTER ENABLED» (FILTRO DE IMPORTANCIA ACTIVADO), para confirmar que el filtro de importancia ya está activo. También imprimimos el contenido de las matrices «imp_filter_selected» e «impact_filter_selected» para mostrar las selecciones actuales.

Por último, recorremos la matriz «imp_filter_selected», que contiene los niveles de importancia seleccionados actualmente, y establecemos dinámicamente los estados de los botones para cada nivel de importancia correspondiente. Para cada nivel de importancia, creamos un nombre de botón de forma dinámica utilizando «IMPACT_LABEL» y el índice del nivel de importancia actual mediante la función IntegerToString. A continuación, establecemos el estado del botón en «true» (seleccionado) utilizando la función ObjectSetInteger. Además, eliminamos cualquier color de borde estableciendo la propiedad OBJPROP_BORDER_COLOR en none para resaltar visualmente que el botón está seleccionado. Eso es todo lo que necesitamos para la inicialización. Ahora pasamos a la función de escucha de eventos, donde hacemos un seguimiento de los clics en el botón Importancia y actuamos en consecuencia. Aquí utilizamos una lógica similar a la que utilizamos con los botones de filtro de divisa.

if (StringFind(sparam, IMPACT_LABEL) >= 0) { //--- If an Importance button is clicked
   string selected_imp = ObjectGetString(0, sparam, OBJPROP_TEXT); //--- Get the importance level of the clicked button
   ENUM_CALENDAR_EVENT_IMPORTANCE selected_importance_lvl = get_importance_level(impact_labels,allowed_importance_levels,selected_imp);
   Print("BTN NAME = ", sparam, ", IMPORTANCE LEVEL = ", selected_imp,"(",selected_importance_lvl,")"); //--- Log the button name and importance level

   bool btn_state = ObjectGetInteger(0, sparam, OBJPROP_STATE); //--- Get the button state
   
   color color_border = btn_state ? clrNONE : clrBlack;
   
   if (btn_state == false) { //--- If the button is unselected
      Print("BUTTON IS IN UN-SELECTED MODE.");
      //--- Loop to find and remove the importance level from the array
      for (int i = 0; i < ArraySize(imp_filter_selected); i++) {
         if (impact_filter_selected[i] == selected_imp) {
            //--- Shift elements to remove the unselected importance level
            for (int j = i; j < ArraySize(imp_filter_selected) - 1; j++) {
               imp_filter_selected[j] = imp_filter_selected[j + 1];
               impact_filter_selected[j] = impact_filter_selected[j + 1];
            }
            ArrayResize(imp_filter_selected, ArraySize(imp_filter_selected) - 1); //--- Resize the array
            ArrayResize(impact_filter_selected, ArraySize(impact_filter_selected) - 1); //--- Resize the array
            Print("Removed from selected importance filters: ", selected_imp,"(",selected_importance_lvl,")"); //--- Log removal
            break;
         }
      }
   } 
   else if (btn_state == true) { //--- If the button is selected
      Print("BUTTON IS IN SELECTED MODE. TAKE ACTION");
      //--- Check for duplicates
      bool already_selected = false;
      for (int j = 0; j < ArraySize(imp_filter_selected); j++) {
         if (impact_filter_selected[j] == selected_imp) {
            already_selected = true;
            break;
         }
      }

      //--- If not already selected, add to the array
      if (!already_selected) {
         ArrayResize(imp_filter_selected, ArraySize(imp_filter_selected) + 1); //--- Resize the array
         imp_filter_selected[ArraySize(imp_filter_selected) - 1] = selected_importance_lvl; //--- Add the new importance level
         
         ArrayResize(impact_filter_selected, ArraySize(impact_filter_selected) + 1); //--- Resize the array
         impact_filter_selected[ArraySize(impact_filter_selected) - 1] = selected_imp; //--- Add the new importance level
         Print("Added to selected importance filters: ", selected_imp,"(",selected_importance_lvl,")"); //--- Log addition
      } 
      else {
         Print("Importance level already selected: ", selected_imp,"(",selected_importance_lvl,")"); //--- Log already selected
      }
   }
   Print("SELECTED ARRAY SIZE = ", ArraySize(imp_filter_selected)," >< ",ArraySize(impact_filter_selected)); //--- Log the size of the selected array
   ArrayPrint(imp_filter_selected); //--- Print the selected array
   ArrayPrint(impact_filter_selected);
   
   update_dashboard_values(curr_filter_selected,imp_filter_selected); //--- Update the dashboard with the selected filters
   
   ObjectSetInteger(0,sparam,OBJPROP_BORDER_COLOR,color_border);
   Print("SUCCESS. DASHBOARD UPDATED"); //--- Log success

   ChartRedraw(0); //--- Redraw the chart to reflect changes
}

Aquí, primero identificamos si se ha pulsado el botón del filtro de importancia utilizando la función StringFind. Esta función comprueba si el nombre del botón pulsado (representado por «sparam») contiene la cadena «IMPACT_LABEL». Si es así, procedemos a recuperar el nivel de importancia asociado al botón llamando a la función ObjectGetString y pasando la propiedad de texto, que obtiene el texto (como «Low», «Medium», etc.) del botón pulsado. A continuación, convertimos este texto en su valor de enumeración correspondiente (como CALENDAR_IMPORTANCE_LOW) pasando la etiqueta seleccionada a la función «get_importance_level(impact_labels, allowed_importance_levels, selected_imp)». Esta función toma la matriz de etiquetas, los valores de enumeración permitidos y la etiqueta de texto seleccionada para devolver el nivel de importancia adecuado como una enumeración. Más adelante trataremos la función personalizada.

A continuación, comprobamos el estado del botón utilizando la función ObjectGetInteger y pasando la propiedad state, que determina si el botón está en estado seleccionado (true) o no seleccionado (false). En función de este estado, añadimos o eliminamos el nivel de importancia seleccionado de las matrices de filtro. Si el botón no está seleccionado, iteramos a través de la matriz «imp_filter_selected» y eliminamos el nivel de importancia, redimensionando la matriz con la función ArrayResize. También hacemos lo mismo con la matriz «impact_filter_selected» para garantizar que ambas matrices permanezcan sincronizadas. Si se selecciona el botón, primero comprobamos si el nivel de importancia ya está en las matrices de filtro utilizando un bucle. Si no es así, lo añadimos a ambas matrices cambiando su tamaño y añadiendo el nuevo valor.

Una vez actualizadas las matrices, utilizamos la función ArrayPrint para registrar el contenido actual de las matrices de filtro con fines de depuración. A continuación, actualizamos el panel con las nuevas selecciones de filtro llamando a «update_dashboard_values(curr_filter_selected, imp_filter_selected)», lo que refleja los cambios en las matrices de filtro del panel. Hemos actualizado la función para que ahora acepte los dos parámetros de matriz según las preferencias del usuario. También lo veremos más adelante. Por último, se actualiza la apariencia del botón pulsado estableciendo el color del borde con el color calculado, donde el color se determina en función de si el botón está seleccionado o no. A continuación, volvemos a dibujar el gráfico utilizando la función ChartRedraw para reflejar visualmente los cambios.

Ahora echemos un vistazo a la función personalizada que se encarga de obtener la enumeración del nivel de importancia correspondiente.

//+------------------------------------------------------------------+
//| Function to get the importance level based on the selected label |
//+------------------------------------------------------------------+
ENUM_CALENDAR_EVENT_IMPORTANCE get_importance_level(string &impact_label[], ENUM_CALENDAR_EVENT_IMPORTANCE &importance_levels[], string selected_label) {
    // Loop through the impact_labels array to find the matching label
    for (int i = 0; i < ArraySize(impact_label); i++) {
        if (impact_label[i] == selected_label) {
            // Return the corresponding importance level
            return importance_levels[i];
        }
    }
    
    // If no match found, return CALENDAR_IMPORTANCE_NONE as the default
    return CALENDAR_IMPORTANCE_NONE;
}

Aquí definimos una función de enumeración «get_importance_level», que se utiliza para determinar el nivel de importancia en función de la etiqueta seleccionada. La función toma tres parámetros:

  • «impact_label»: Se trata de una matriz de cadenas (strings) que contiene las diferentes etiquetas para los niveles de impacto (como «None», «Low», «Medium» y «High»).
  • «importance_levels»: Esta matriz contiene los niveles de importancia correspondientes como valores enumerados (como CALENDAR_IMPORTANCE_NONE, CALENDAR_IMPORTANCE_LOW, etc.).
  • «selected_label»: Esta es la etiqueta, una cadena (string), que se pasa a la función y que representa el nivel de importancia seleccionado por el usuario (por ejemplo, «Medium»).

Dentro de la función, recorremos el array «impact_label» utilizando un bucle for. Para cada iteración, comprobamos si el elemento actual de la matriz coincide con «selected_label». Si se encuentra una coincidencia, devolvemos el nivel de importancia correspondiente de la matriz «importance_levels» en el mismo índice. Si no se encuentra ninguna coincidencia después de comprobar todas las etiquetas, la función devuelve por defecto CALENDAR_IMPORTANCE_NONE. Necesitamos esta función para ayudarnos a convertir una cadena que representa el nivel de impacto seleccionado (como «Medium») en su nivel de importancia correspondiente (como «CALENDAR_IMPORTANCE_MODERATE»).

El otro cambio que hicimos fue pasar los nuevos datos filtrados por importancia de la matriz a la función de actualización como referencia, de modo que los filtros surtieran efecto dinámicamente según las preferencias actualizadas del usuario. La declaración de la función ahora se asemeja a la que se muestra en el fragmento de código siguiente.

//+------------------------------------------------------------------+
//| Function to update dashboard values                              |
//+------------------------------------------------------------------+
void update_dashboard_values(string &curr_filter_array[], ENUM_CALENDAR_EVENT_IMPORTANCE &imp_filter_array[]){

//---      

}

Tras la actualización de la función, también debemos actualizar las funciones similares existentes para que incluyan la matriz de filtros de importancia. Por ejemplo, la llamada a la función del controlador de eventos «OnInit» será similar a la que se muestra a continuación.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
//---

   if (isDashboardUpdate){
      update_dashboard_values(curr_filter_selected,imp_filter_selected);
   }
   
}

Eso es todo lo que necesitamos para incorporar los filtros de divisa e importancia. Echemos un vistazo al hito actual para asegurarnos de que los filtros de importancia también funcionan como esperamos.

GIF DE FILTRO DE IMPORTANCIA

A partir de la visualización, podemos ver que hemos logrado nuestro objetivo de añadir la capacidad de respuesta del filtro de importancia. Por último, una cosa que podemos actualizar es el total de eventos que mostramos. Podemos tener el número de eventos filtrados, el total de eventos posibles que podemos mostrar en el gráfico y el total de eventos considerados. Esta es la lógica que podemos utilizar para lograrlo. En el entorno global, podemos definir algunas variables de seguimiento.

int totalEvents_Considered = 0;
int totalEvents_Filtered = 0;
int totalEvents_Displayable = 0;

Aquí definimos tres variables enteras (int): «totalEvents_Considered», «totalEvents_Filtered» y «totalEvents_Displayable». Estas variables servirán como contadores para realizar un seguimiento del estado de los eventos durante el procesamiento:

  • «totalEvents_Considered»: esta variable llevará un registro del número total de eventos que se tienen en cuenta inicialmente durante la fase de procesamiento. Representa el punto de partida, donde se tienen en cuenta todos los eventos antes de aplicar cualquier filtro.
  • «totalEvents_Filtered»: esta variable contará el número total de eventos que se excluyen o filtran en función de las condiciones aplicadas, como los filtros de divisa, importancia o tiempo. Indica cuántos eventos se eliminaron del conjunto de datos.
  • «totalEvents_Displayable»: esta variable realizará un seguimiento del número total de eventos que quedan después del filtrado y que pueden mostrarse en el panel de control. Representa el conjunto final de eventos que cumplen todos los criterios de filtrado y se pueden mostrar.

Al utilizar estos contadores, podemos supervisar y analizar el proceso de tramitación de eventos, garantizando que la lógica de filtrado funcione según lo previsto y proporcionando información sobre el flujo general de datos. A continuación, antes de que se realice cualquier acción de filtrado de datos, los establecemos por defecto en 0.

totalEvents_Displayable = 0;
totalEvents_Filtered = 0;
totalEvents_Considered = 0;

Antes del primer bucle for, sustituimos los valores anteriores por los valores más recientes para tener en cuenta todos los eventos. Aquí hay un ejemplo.

//--- Loop through each calendar value up to the maximum defined total
for (int i = 0; i < allValues; i++){

//---

}

En lugar de aplicar las condiciones de restricción desde el principio, se puede observar que seguimos teniendo en cuenta todos los eventos. Esto nos ayudará a mostrar los datos desbordados. Por lo tanto, dentro del bucle, tendremos la siguiente lógica de actualización.

   //--- Loop through each calendar value up to the maximum defined total
   for (int i = 0; i < allValues; i++){
   
      MqlCalendarEvent event; //--- Declare event structure
      CalendarEventById(values[i].event_id,event); //--- Retrieve event details by ID
   
      //--- Other declarations
      
      totalEvents_Considered++;
      
      //--- Check if the event’s currency matches any in the filter array (if the filter is enabled)
      bool currencyMatch = false;
      if (enableCurrencyFilter) {
         for (int j = 0; j < ArraySize(curr_filter); j++) {
            if (country.currency == curr_filter[j]) {
               currencyMatch = true;
               break;
            }
         }
         
         //--- If no match found, skip to the next event
         if (!currencyMatch) {
            continue;
         }
      }
      
      //--- Other filters

      //--- If we reach here, the filters passed
      totalEvents_Filtered++;
      
      //--- Restrict the number of displayable events to a maximum of 11
      if (totalEvents_Displayable >= 11) {
        continue; // Skip further processing if display limit is reached
      }
      
      //--- Increment total displayable events
      totalEvents_Displayable++;
      
      //--- Set alternating colors for data holders
      color holder_color = (totalEvents_Displayable % 2 == 0) ? C'213,227,207' : clrWhite;
      
      //--- Create rectangle label for the data holder
      createRecLabel(DATA_HOLDERS + string(totalEvents_Displayable), 62, startY - 1, 716, 26 + 1, holder_color, 1, clrNONE);

      //--- Initialize starting x-coordinate for each data entry
      int startX = 65;
      
      //--- Loop through calendar data columns
      for (int k=0; k<ArraySize(array_calendar); k++){
         
         //--- Prepare news data array with time, country, and other event details
         string news_data[ArraySize(array_calendar)];
         news_data[0] = TimeToString(values[i].time,TIME_DATE); //--- Event date
         news_data[1] = TimeToString(values[i].time,TIME_MINUTES); //--- Event time
         news_data[2] = country.currency; //--- Event country currency
      
         //--- Other fills and creations
      }
      
      ArrayResize(current_eventNames_data,ArraySize(current_eventNames_data)+1);
      current_eventNames_data[ArraySize(current_eventNames_data)-1] = event.name;
      
      //--- Increment y-coordinate for the next row of data
      startY += 25;
      
   }
   Print("CURRENT EVENT NAMES DATA SIZE = ",ArraySize(current_eventNames_data));
   //--- Other logs
      
   updateLabel(TIME_LABEL,"Server Time: "+TimeToString(TimeCurrent(),
              TIME_DATE|TIME_SECONDS)+"   |||   Total News: "+
              IntegerToString(totalEvents_Displayable)+"/"+IntegerToString(totalEvents_Filtered)+"/"+IntegerToString(totalEvents_Considered));
//---

Aquí, solo actualizamos los titulares de los números de eventos según corresponda. Una cosa crucial a tener en cuenta es el fragmento de código que hemos resaltado en azul claro. Su lógica funciona igual que los filtros, es decir, si se alcanza el límite total de visualización, se omite el procesamiento. Por último, actualizamos la etiqueta para que contenga los tres recuentos de eventos, lo que nos ayuda a conocer el recuento de desbordamiento de datos a largo plazo. A continuación, aplicamos la misma lógica a la función de actualizaciones para garantizar que también se sincronice en tiempo real. Al ejecutar el sistema, obtenemos el siguiente resultado.

RESULTADO DE LA VISUALIZACIÓN

En la imagen podemos ver que ahora tenemos 3 recuentos de noticias. El primer número, 11 en este caso, muestra el número total de noticias que se pueden mostrar, el segundo, 24, muestra el número total de eventos filtrados, solo que no podemos mostrarlos todos en el panel de control, y el tercero, 539, muestra el número total de noticias consideradas para su procesamiento. Con todo eso, para habilitar el filtro de tiempo, podemos tener los rangos de tiempo deseados en formatos de entrada para poder configurarlos cuando inicializamos el programa. Esta es la lógica para lograrlo.

sinput group "General Calendar Settings"
input ENUM_TIMEFRAMES start_time = PERIOD_H12;
input ENUM_TIMEFRAMES end_time = PERIOD_H12;
input ENUM_TIMEFRAMES range_time = PERIOD_H8;

Aquí definimos un grupo denominado «General Calendar Settings» (Configuración general del calendario) para proporcionar opciones configurables para gestionar las funciones relacionadas con el calendario. Utilizamos tres entradas del tipo ENUM_TIMEFRAMES para controlar los parámetros de tiempo dentro de los cuales se filtran o analizan los eventos del calendario. En primer lugar, definimos «start_time», que especifica el inicio del intervalo de tiempo para los eventos del calendario, con un valor predeterminado de 12 horas («PERIOD_H12»).

A continuación, presentamos «end_time», que marca el final de este intervalo de tiempo, también establecido en «PERIOD_H12» de forma predeterminada. Por último, utilizamos «range_time» para definir la duración o el intervalo de interés para el filtrado o los cálculos del calendario, con un valor predeterminado de 8 horas («PERIOD_H8»). De esta manera, nos aseguramos de que el programa funcione de manera flexible según los plazos definidos por el usuario, lo que nos permite adaptar los datos del calendario a intervalos específicos de interés. Estos ajustes permiten el filtrado dinámico y proporcionan al usuario control sobre el alcance temporal de los eventos mostrados o analizados.

Para efectuar los cambios, los añadimos a las entradas de control de sus respectivas funciones y lógicas de soporte de la siguiente manera.

//--- Define start and end time for calendar event retrieval
datetime startTime = TimeTradeServer() - PeriodSeconds(start_time);
datetime endTime = TimeTradeServer() + PeriodSeconds(end_time);

//--- Define time range for filtering news events based on daily period
datetime timeRange = PeriodSeconds(range_time);
datetime timeBefore = TimeTradeServer() - timeRange;
datetime timeAfter = TimeTradeServer() + timeRange;

Eso es todo. Tras la compilación, obtenemos el siguiente resultado.

RESULTADO DE ENTRADAS

En la imagen, podemos ver que ahora podemos acceder a los parámetros de entrada y elegir la configuración de tiempo en la lista desplegable habilitada. Hasta este momento, nuestro panel de control es totalmente funcional y receptivo. Solo tenemos que probarlo en diferentes condiciones y entornos para asegurarnos de que funciona correctamente sin fallos y, si se detecta alguno, mitigarlo. Eso se hace en la siguiente sección.


Probando el panel de control mejorado

En esta sección, realizamos pruebas en el panel de control mejorado que hemos desarrollado. El objetivo es garantizar que todos los filtros, el seguimiento de eventos y los mecanismos de visualización de datos funcionen según lo previsto. Hemos implementado una serie de filtros, entre los que se incluyen filtros por divisa, importancia y tiempo, que nos permiten ver y analizar los eventos del calendario de forma más eficaz.

El proceso de prueba consiste en simular las interacciones del usuario con el panel de control, habilitar y deshabilitar filtros, y garantizar que los eventos del calendario se actualicen dinámicamente en función de los criterios seleccionados. También verificamos la correcta visualización de los datos del evento, como el país, la divisa, el nivel de importancia y la hora del evento, dentro del intervalo de tiempo definido.

Mediante la ejecución de diversos escenarios de prueba, confirmamos la funcionalidad de cada característica, como filtrar eventos según su importancia o actualidad, limitar el número de eventos mostrados y garantizar que las etiquetas de datos se actualicen según lo previsto. El siguiente vídeo muestra estas pruebas en acción.



Conclusión

En conclusión, hemos desarrollado y probado con éxito el panel mejorado del Calendario Económico de MQL5, asegurando que los filtros, las visualizaciones de datos y los sistemas de seguimiento de eventos funcionen sin problemas. Este panel de control proporciona una interfaz intuitiva para realizar un seguimiento de los acontecimientos económicos, aplicando filtros basados en la divisa, la importancia y el tiempo, lo que nos ayuda a mantenernos informados y a tomar decisiones basadas en los datos del mercado.

De cara al futuro, en la próxima parte de esta serie ampliaremos esta base para integrar la generación de señales y la apertura de operaciones. Aprovechando los datos del panel de control mejorado, desarrollaremos un sistema capaz de generar automáticamente señales de trading basadas en acontecimientos económicos y condiciones del mercado, lo que permitirá estrategias de trading más eficientes y fundamentadas. Mantente atento.

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

Archivos adjuntos |
Petr Zharuk
Petr Zharuk | 25 jul 2025 en 13:39
Útil, gracias.
Pero por muchos artículos que he visto sobre interfaces, todo parece de 2005.
Creación de barras 3D basadas en el tiempo, el precio y el volumen Creación de barras 3D basadas en el tiempo, el precio y el volumen
Qué son los gráficos de precios multidimensionales en 3D y cómo se crean. Cómo las barras 3D predicen las inversiones de precios, y cómo Python y MetaTrader 5 permiten construir estas barras volumétricas en tiempo real.
Cambiamos a MQL5 Algo Forge (Parte 1): Creación del repositorio principal Cambiamos a MQL5 Algo Forge (Parte 1): Creación del repositorio principal
Mientras trabajan en proyectos en el MetaEditor, los desarrolladores se enfrentan a la necesidad de gestionar las versiones del código. A pesar de los planes de traslado a GIT y el lanzamiento de MQL5 Algo Forge, la integración aún no está completa. El presente artículo analizará posibles formas de mejorar la usabilidad de las herramientas actuales.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Reimaginando las estrategias clásicas en MQL5 (Parte 12): Estrategia de ruptura en EURUSD Reimaginando las estrategias clásicas en MQL5 (Parte 12): Estrategia de ruptura en EURUSD
Únase a nosotros hoy mismo y póngase a prueba para crear una estrategia de trading rentable en MQL5. Seleccionamos el par EURUSD e intentamos operar con rupturas de precios en el marco temporal horario. Nuestro sistema tenía dificultades para distinguir entre falsas rupturas y el inicio de tendencias reales. Hemos equipado nuestro sistema con filtros destinados a minimizar nuestras pérdidas y aumentar nuestras ganancias. Al final, logramos que nuestro sistema fuera rentable y menos propenso a falsas rupturas.