English 中文 Español Deutsch 日本語
preview
Трейдинг с экономическим календарем MQL5 (Часть 5): Добавление в панель адаптивных элементов управления и кнопок сортировки

Трейдинг с экономическим календарем MQL5 (Часть 5): Добавление в панель адаптивных элементов управления и кнопок сортировки

MetaTrader 5Трейдинг |
304 1
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

Мы будем опираться на на предыдущую статью серии о языке MetaQuotes Language 5 (MQL5), в которой мы добавили обновления в реальном времени к панели экономического календаря MQL5. Здесь мы сосредоточились на том, чтобы сделать панель управления более интерактивной, добавив кнопки, которые позволяют нам напрямую управлять фильтрами валютных пар, уровнями важности и фильтрами временного диапазона, и все это с самой панели — без необходимости изменять настройки в коде. Мы также добавим кнопку Cancel (отмена), которая очищает выбранные фильтры и удаляет компоненты панели, предоставляя нам полный контроль над отображением. Наконец, мы улучшим пользовательский опыт, сделав кнопки чувствительными к нажатиям, гарантируя их бесперебойную работу и мгновенную обратную связь. В этой статье мы рассмотрим следующие темы:

  1. Создание кнопок и элементов управления фильтрами
  2. Автоматизация и добавление адаптивности к кнопкам
  3. Тестирование расширенной панели
  4. Заключение

Эти дополнения значительно улучшат удобство использования нашей панели управления, сделав ее более гибкой и динамичной для взаимодействия пользователей в реальном времени. Благодаря этим интерактивным элементам мы можем легко фильтровать и управлять отображаемыми новостными данными, не внося каждый раз изменений в базовый код. Начнем с создания кнопок фильтров и их интеграции в существующий макет панели инструментов.


Создание кнопок и элементов управления фильтрами

В этом разделе мы сосредоточимся на создании кнопок фильтров, которые позволят нам управлять различными аспектами нашей панели, такими как фильтры валютных пар, уровня важности и временного диапазона, непосредственно с панели. Добавив эти кнопки, мы упростим взаимодействие с панелью управления, избавившись от необходимости каждый раз открывать код или изменять его, когда нам нужно изменить настройки фильтра. Цель — разработать интуитивно понятный пользовательский интерфейс, обеспечивающий гибкость и при этом сохраняющий простоту и ясность макета.

Сначала мы определим положение и свойства каждой из кнопок фильтра. Мы разместим эти кнопки на панели инструментов, что позволит нам переключаться между различными настройками валютных пар, уровнями важности и временными фильтрами. Например, мы разместим кнопки фильтра валют в правом верхнем углу панели инструментов, кнопки выбора фильтра валюты, важности и времени — в разделе заголовка, а кнопку отмены — сразу после их определения. Каждая кнопка будет соответствовать определенной настройке фильтра, и мы можем нажать на эти кнопки, чтобы применить нужные нам фильтры. Вот изображение, показывающее первоначальную компоновку кнопок фильтров на панели:

Схема макета

Как видно на изображении, кнопки фильтров организованы на панели управления для удобства доступа и управления. Каждая кнопка предназначена для выполнения определенной функции: включения доступных валютных пар, установки уровня важности событий или фильтрации по времени. Кнопки также будут визуально отличаться друг от друга, чтобы мы могли легко различать разные группы управления.

Для реализации нам потребуется определить константы для дополнительных объектов и элементов управления, которые мы собираемся включить в панель управления.

#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

Здесь мы определяем набор строковых констант, которые представляют имена различных элементов панели, которые мы будем добавлять, в первую очередь кнопки и метки, используя ключевое слово #define. Мы будем использовать эти константы для создания и управления компонентами пользовательского интерфейса, такими как кнопки фильтров и кнопка отмены. Сначала мы определяем FILTER_LABEL, который представляет собой метку для раздела фильтра на панели.

Далее определяем три кнопки: FILTER_CURR_BTN для сортировки по валютной паре, FILTER_IMP_BTN для сортировки по важности события и FILTER_TIME_BTN для сортировки по временному диапазону события. Мы также определяем CANCEL_BTN, чтобы иметь возможность сбросить или закрыть активные фильтры и удалить компоненты панели управления, и, наконец, CURRENCY_BTNS представляет собой набор кнопок валют, позволяющих нам выбирать конкретные валютные пары. Эти определения помогают нам создать динамическую и интерактивную панель управления, где мы можем управлять данными, отображаемыми непосредственно из интерфейса. Для повышения динамизма мы убираем массив определенных валют наружу, в глобальную область следующим образом:

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

Здесь мы повышаем гибкость и динамизм фильтра валют, удаляя предопределенный список валют из функций и помещая его в глобальную область. Теперь мы определяем новый глобальный массив curr_filter для хранения списка возможных валют, таких как AUD, CAD, CHF, EUR, GBP, JPY, NZD и USD. Кроме того, мы создаем пустой массив curr_filter_selected, который будет динамически хранить выбранные пользователем валюты во время выполнения. Наконец, мы создаем переменную, которая будет отслеживать необходимость выполнения обновлений, поскольку на этот раз обновления нам не понадобятся, как только мы избавимся от панели управления с помощью кнопки отмены. Пока всё довольно просто.

bool isDashboardUpdate = true;

Мы просто определяем булеву переменную isDashboardUpdate и инициализируем ее в значение true. Мы будем использовать эту переменную для отслеживания необходимости обновления панели. Установив значение true, мы можем отметить, что произошло изменение или действие (например, выбор фильтра или нажатие кнопки), требующее обновления панели новыми данными или настройками. Аналогично, если задать значение false, то нам не придется выполнять процесс обновления, что поможет эффективно управлять состоянием панели, гарантируя, что она обновляется только при необходимости, и избегая ненужных повторных отображений.

Из глобальной области мы можем перейти в раздел инициализации и сопоставить наши дополнительные компоненты фильтра с панелью управления. Начнем с самых верхних кнопок, которые являются кнопками включения фильтров.

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

Здесь мы добавляем метку и кнопку для фильтра валют на панель инструментов. Сначала мы используем функцию createLabel, чтобы поместить на график метку с заголовком Filters: в точке с координатами (370 и 55) желтого цвета и размером шрифта 16. Эта метка будет служить заголовком раздела фильтра, четко указывая пользователю, где находятся параметры фильтра.

Далее определяем и настраиваем кнопку для фильтра Currency. Мы проверяем состояние переменной enableCurrencyFilter и на основе ее значения динамически задаем текст кнопки с помощью переменной filter_curr_text. Если фильтр валют включен (значение enableCurrencyFilter равно true), на кнопке будет отображаться галочка (значение 0x2714) с текстом Currency, указывающая на то, что фильтр активен; если фильтр отключен, вместо этого будет отображаться крестик (значение 0x274C), сигнализирующий о том, что фильтр неактивен. Мы достигаем этого с помощью тернарного оператора, который работает аналогично оператору if, при этом он меньше и проще.

Для дальнейшего визуального отображения состояния фильтра мы задаем цвет текста кнопки с помощью переменной filter_curr_txt_color. Если фильтр активен, текст будет отображаться зеленым цветом; если неактивен, - красным. Мы также используем логическую переменную filter_curr_state для управления фактическим состоянием кнопки, которое определяет, включена кнопка или выключена.

Затем мы создаем саму кнопку с помощью функции createButton, помещая ее в точку (430, 55) на графике с соответствующей меткой (filter_curr_text), цветом текста (filter_curr_txt_color) и черным фоном. Наконец, мы используем функцию ObjectSetInteger для установки состояния кнопки (включена или выключена) путем ссылки на переменную filter_curr_state. Это гарантирует, что внешний вид и функциональность кнопки соответствуют текущим настройкам фильтра. После компиляции мы получаем следующий результат.

Фильтр и метка валюты

Всё работает как надо. Теперь мы можем перейти к добавлению кнопок фильтров, используя ту же логику.

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

Здесь мы настраиваем кнопки для фильтров Importance (важность), Time (время) и Cancel (отмена) на панели инструментов. Для фильтра Importance мы сначала определяем текст кнопки с помощью переменной filter_imp_text. В зависимости от значения enableImportanceFilter, если фильтр активен, мы отображаем галочку (0x2714) рядом с текстом Importance, указывая на то, что фильтр включен; если нет, мы отображаем крестик (0x274C) с тем же текстом, указывая на то, что фильтр отключен. Мы также устанавливаем цвет текста кнопки с помощью filter_imp_txt_color, который имеет зеленый цвет при включении и красный при выключении. Логическое значение filter_imp_state управляет тем, включена или выключена кнопка.

Затем мы используем функцию createButton для создания кнопки фильтра Importance и размещаем ее в позиции (430+110, 55) с соответствующим текстом, цветом и состоянием. Затем мы используем ObjectSetInteger, чтобы установить состояние кнопки (OBJPROP_STATE) на основе filter_imp_state, гарантируя, что кнопка отражает правильный статус.

Аналогичный действия проделываем для кнопки фильтра Time. Мы определяем его текст в filter_time_text и настраиваем цвет с помощью filter_time_txt_color на основе значения enableTimeFilter. Кнопка создается в позиции (430+110+120, 55), а состояние задается соответствующим образом с помощью filter_time_state.

Наконец, мы создаем кнопку Cancel с помощью функции createButton, которая при нажатии сбросит все фильтры и удалит панель управления. Эта кнопка расположена в позиции (430+110+120+79, 51) с белым знаком «X» на красном фоне, указывающим на ее назначение. Наконец, мы вызываем функцию ChartRedraw для обновления графика и вновь созданных кнопок и изменений. После запуска получаем следующий результат.

Кнопки фильтра

Всё работает как надо. Теперь мы добавили все кнопки фильтров на панель управления. Однако при создании текстов мы использовали конкатенацию символов Unicode. Давайте рассмотрим их подробнее.

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

Здесь мы используем функции ShortToString(0x2714) и ShortToString(0x274C) для представления символов Unicode в MQL5, а значения 0x2714 и 0x274C относятся к определенным символам в наборе символов Unicode.

  • 0x2714 — кодовая точка Unicode для символа CHECK MARK (галочка). Он используется для указания того, что что-то включено, завершено или правильно. В контексте кнопок фильтров мы используем его, чтобы показать, что фильтр активен.
  • 0x274C - кодовая точка Unicode для символа CROSS MARK (крестик). Он используется для обозначения чего-то невыполненного, несделанного или неправильного. Здесь мы используем его, чтобы показать, что фильтр неактивен или отключен.

Вы также можете использовать свои символы, при условии, что вы предоставите символы, совместимые со средой кодирования MQL5. Набор символов следующий:

Пример галочки Unicode

В коде функция ShortToString преобразует эти кодовые точки Unicode в соответствующие им представления символов. Затем эти символы добавляются к тексту кнопок фильтров, чтобы визуально показать, активен фильтр или нет. Далее мы можем динамически создавать кнопки фильтра валют.

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

Здесь мы динамически создаем кнопки фильтра валют и управляем их макетом и состоянием в зависимости от того, включен ли фильтр или нет. Начнем с определения параметров макета кнопки. Мы устанавливаем целочисленную переменную curr_size на 51 для определения ширины каждой кнопки, а button_height (высота кнопки) устанавливается на 22. spacing_x и spacing_y устанавливаются на 0 и 3 соответственно для управления расстоянием между кнопками в горизонтальном и вертикальном направлениях. Мы также определяем max_columns как 4, ограничивая количество кнопок в строке четырьмя.

Далее мы используем цикл for для циклического прохождения массива curr_filter, содержащего коды валютных пар, например AUD, CAD и т.д. Для каждой итерации мы вычисляем строку и столбец, где должна быть размещена кнопка. Мы вычисляем строки как i / max_columns для определения номера строки, а столбцы - как i % max_columns для определения столбца в строке. Используя эти значения, мы вычисляем положение кнопок на экране по осям X (x_pos) и Y (y_pos). Затем мы вызываем функцию createButton для динамического создания каждой кнопки с меткой, соответствующей валюте из массива curr_filter, и окрашенной в черный цвет.

После создания кнопок мы проверяем, включен ли фильтр, оценивая значение enableCurrencyFilter. Если фильтр включен, очищаем массив выбранных валют с помощью функции ArrayFree и копируем содержимое curr_filter в curr_filter_selected, используя функцию ArrayCopy. Это фактически копирует все валюты в выбранный массив. Затем выводим CURRENCY FILTER ENABLED и отображаем выбранный массив фильтров с помощью функции ArrayPrint. Наконец, перебираем выбранные валюты в массиве curr_filter_selected и устанавливаем состояние каждой соответствующей кнопки на selected (выбрано) с помощью функции ObjectSetInteger, задавая соответствующие параметры. Мы используем функцию IntegerToString для объединения индекса выделения и добавления его в макрос CURRENCY_BTNS, а также установки состояния в значение true, что визуально отмечает нажатие кнопки.

После компиляции мы получаем следующее.

Кнопки (финальный результат)

На изображении видно, что мы успешно добавили кнопки фильтров на панель. Теперь нам нужно обновить функцию, отвечающую за удаление панели, чтобы она также учитывала вновь добавленные компоненты.

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

Нам также необходимо обновить логику обработчика событий OnTick для обновления только до тех пор, пока флаг обновлений имеет значение true. Сделаем это с помощью следующей логики.

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

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

Здесь мы проверяем, является ли условие isDashboardUpdate истинным, что указывает на то, что панель мониторинга должна быть обновлена последними данными. Если это условие выполняется, мы вызываем функцию update_dashboard_values для обновления значений, отображаемых на панели мониторинга, с использованием выбранных фильтров валют, хранящихся в массиве curr_filter_selected.

Массив curr_filter_selected содержит валюты, выбранные для сортировки, и, передавая его в функцию update_dashboard_values, мы гарантируем, что панель отражает самые последние выборы фильтров. Если флаг переменной равен false, обновления рассматриваться не будут. Это все, что нам нужно учесть для создания кнопок фильтров. Теперь нам осталось только добавить адаптивности созданным кнопкам.


Автоматизация и добавление адаптивности к кнопкам

Чтобы сделать панель управления более отзывчивой, нам потребуется включить прослушиватель событий, который будет отслеживать клики и предпринимать действия на их основе. Для этого мы используем обработчик событий OnChartEvent в 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 
){

//---

}

Эта функция отвечает за распознавание действий на графике, таких как изменения, клики, создание объектов и многое другое. Однако нас интересуют только щелчки по графику, поскольку мы хотим прослушивать только клики по кнопкам. Пойдем от простого к сложному. Кнопка отмены самая простая.

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

Здесь мы обрабатываем сценарий, когда обнаруженное событие — это клик по объекту, обнаруженный, когда идентификатор события равен CHARTEVENT_OBJECT_CLICK. Если событием является клик, проверяем, является ли нажатый объект CANCEL_BTN, оценивая, является ли строковый параметр sparam CANCEL_BTN. Если это условие выполнено, это означает, что была нажата кнопка Cancel на панели инструментов. В ответ мы устанавливаем глобальный флаг isDashboardUpdate в false, фактически отключая дальнейшие обновления панели мониторинга. Затем мы вызываем функцию destroy_Dashboard, чтобы удалить с графика все графические элементы, связанные с панелью управления, тем самым очищая ее. Это обеспечивает сброс и очистку интерфейса при нажатии кнопки Cancel. Ниже приведена соответствующая иллюстрация.

Отмена (gif)

Всё работает как надо. Теперь мы можем применить ту же логику для автоматизации других кнопок. Автоматизируем кнопку сортировки валют. Для достижения этой цели мы используем следующую логику во фрагменте кода.

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. Изменения обновлены! State: "+(string)enableCurrencyFilter); //--- Log success
   ChartRedraw(0); //--- Redraw the chart to reflect changes
}

Здесь мы обрабатываем поведение при нажатии кнопки FILTER_CURR_BTN (кнопка фильтра валют). Когда это условие выполняется, мы получаем текущее состояние кнопки (нажата или не нажата) с помощью функции ObjectGetInteger со свойством OBJPROP_STATE и сохраняем его в переменной btn_state. Затем мы обновляем флаг enableCurrencyFilter значением btn_state, чтобы отразить, активен ли фильтр валюты.

Затем мы регистрируем состояние кнопки и обновленное значение флага, чтобы предоставить обратную связь с помощью функции Print. В зависимости от состояния фильтра Currency, мы динамически устанавливаем текст кнопки с помощью галочки или крестика с помощью функции ShortToString и обновляем ее цвет на зеленый для активного состояния или красный для неактивного. Эти обновления применяются к кнопке с помощью ObjectSetString для текста и ObjectSetInteger для свойства цвета.

Наконец, мы регистрируем сообщение об успешном завершении, чтобы подтвердить, что изменения были применены, и вызываем функцию ChartRedraw для обновления графика, гарантирующая немедленное отображение изменений кнопки. Это взаимодействие позволяет нам динамически переключать фильтр валюты и отображать изменения на панели. Тот же процесс применяется к остальным кнопкам фильтров.

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. Изменения обновлены! 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. Изменения обновлены! State: "+(string)enableTimeFilter); //--- Log success
   ChartRedraw(0); //--- Redraw the chart
}

Здесь мы определяем поведение обработки нажатий на кнопки FILTER_IMP_BTN (кнопка фильтра важности) и FILTER_TIME_BTN (кнопка фильтра времени), позволяя динамически переключать эти фильтры. Для FILTER_IMP_BTN при нажатии мы сначала получаем его текущее состояние с помощью ObjectGetInteger со свойством OBJPROP_STATE и сохраняем его в переменной btn_state. Затем мы обновляем флаг enableImportanceFilter, чтобы отразить, активен ли фильтр важности. Используя функцию Print, мы регистрируем состояние кнопки и обновленное значение флага. В зависимости от состояния мы устанавливаем текст кнопки в виде галочки или крестика с помощью функции ShortToString и обновляем ее цвет на зеленый для активного состояния или красный для неактивного. Эти изменения применяются с помощью ObjectSetString для текста и ObjectSetInteger - для функций свойств цвета. Наконец, мы регистрируем сообщение об успешном завершении и вызываем функцию ChartRedraw, обеспечивающую визуальное применение обновлений.

Аналогично для FILTER_TIME_BTN мы следуем тому же процессу. Мы получаем состояние кнопки с помощью функции ObjectGetInteger и соответствующим образом обновляем флаг enableTimeFilter. Регистрируем состояние и отмечаем обновления для обратной связи. Текст и цвет кнопки динамически обновляются в соответствии с состоянием (активно/неактивно) с помощью функций ShortToString, ObjectSetString и ObjectSetInteger соответственно. После подтверждения обновлений с помощью журнала мы перерисовываем график с помощью функции ChartRedraw. Этот процесс обеспечивает реагирование кнопок фильтров в реальном времени, предоставляя нам возможность бесперебойного переключения их функций. Вот наглядный результат.

Кнопки фильтров (gif)

Всё работает как надо. Теперь мы можем также автоматизировать кнопки валют.

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
}

Здесь мы обрабатываем нажатия на кнопки фильтров валют, чтобы динамически управлять выбранными валютами и соответствующим образом обновлять панель. При нажатии кнопки мы сначала определяем, принадлежит ли она группе CURRENCY_BTNS, используя функцию StringFind. Если значение равно true, мы извлекаем текстовую метку кнопки с помощью функции ObjectGetString со свойством OBJPROP_TEXT для определения валюты. Затем мы проверяем состояние кнопки, используя ObjectGetInteger со свойством OBJPROP_STATE, чтобы определить, выбрана ли кнопка (true) или нет (false).

Если кнопка находится в невыбранном состоянии, мы удаляем соответствующую валюту из массива curr_filter_selected. Чтобы добиться этого, мы проходим по массиву, чтобы найти соответствующую валюту, сдвигаем все последующие элементы влево, чтобы перезаписать ее, а затем изменяем размер массива с помощью функции ArrayResize для удаления последней позиции. Каждое удаление регистрируется для подтверждения действия. И наоборот, если кнопка находится в выбранном состоянии, мы проверяем наличие дубликатов, чтобы предотвратить добавление одной и той же валюты несколько раз. Если валюты еще нет в массиве, мы изменяем размер массива с помощью функции ArrayResize, добавляем новую валюту в последнюю позицию и регистрируем добавление. Если валюта уже выбрана, регистрируется сообщение, указывающее на то, что никаких дальнейших действий не требуется.

После обновления массива curr_filter_selected мы регистрируем его размер и содержимое с помощью функции ArrayPrint для наглядности. Затем мы вызываем функцию update_dashboard_values, чтобы обновить панель мониторинга с использованием вновь выбранных фильтров. Чтобы все изменения были визуально отражены, мы завершаем вызовом функции ChartRedraw для обновления интерфейса графика в реальном времени.

Поскольку теперь после взаимодействия с пользователем у нас есть различные фильтры валют, нам необходимо обновить функцию, отвечающую за обновления, используя последние выбранные пользователем валюты.

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

//---

}

Здесь мы обновляем функцию update_dashboard_values, внося существенное улучшение за счет использования параметра curr_filter_array, передаваемого по ссылке с помощью символа &. Такая конструкция позволяет нам напрямую работать с выбранным массивом фильтров валют, гарантируя синхронизацию панели с предпочтениями пользователя.

Вот что мы подразумеваем под "передачей по ссылке (&)". Параметр curr_filter_array передается по ссылке на функцию. Это означает, что функция обращается к реальному массиву в памяти, а не к его копии. Изменения массива внутри функции (если таковые имеются) напрямую влияют на исходный массив вне функции. Такой подход повышает эффективность, особенно для больших массивов, и обеспечивает согласованность с текущим выбором фильтров пользователя. Позже, вместо использования исходного массива, мы заменяем его последним, переданным по ссылке, содержащим пользовательские настройки. Для наглядности мы выделили изменения желтым цветом. При компиляции получаем следующий результат.

Кнопки валют (gif)

Из визуализации мы видим, что панель обновляется каждый раз, когда мы нажимаем на любую из кнопок валют, а значит, всё работает как надо. Однако в настоящее время существует зависимость кнопки фильтра от валют, поскольку после нажатия на один лишь фильтр валюты панель управления не обновляется. Чтобы решить эту проблему, нам просто нужно вызвать функцию обновления для каждой кнопки фильтра, как показано ниже.

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. Изменения обновлены! 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. Изменения обновлены! 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. Изменения обновлены! State: "+(string)enableTimeFilter); //--- Log success
   ChartRedraw(0); //--- Redraw the chart
}

Здесь мы просто вызываем функцию обновления, чтобы повысить независимость событий панели управления. Для наглядности мы выделили изменения желтым цветом. Таким образом, благодаря этим изменениям кнопки становятся независимыми друг от друга. Вот краткая визуализация.

Кнопки работают независимо друг от друга (gif)

На данный момент нам удалось интегрировать фильтры валют. Используя ту же процедуру, мы можем также интегрировать фильтры важности. Это будет немного сложнее, поскольку мы будем иметь дело с существующими кнопками, чтобы добавить эффект фильтра, и нам потребуется два массива для уровней важности: основной и побочный строковый, который мы будем использовать для выполнения сравнений. Сначала мы перенесем все массивы важности в глобальную область видимости, чтобы иметь к ним доступ из любой точки кода.

//--- 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[];

Здесь мы определяем строковый массив impact_labels, который содержит различные уровни воздействия, доступные для выбора пользователем. Доступные метки - None (нет), Low (низкий), Medium (средний) и High (высокий). Мы использовали этот массив, чтобы предоставить пользователю возможность сортировать события календаря на основе их предполагаемого влияния.

Затем мы вводим массив impact_filter_selected, в котором будут храниться фактические метки, которые пользователь выбирает из массива impact_labels. Всякий раз, когда пользователь взаимодействует с интерфейсом и выбирает уровень воздействия, мы добавляем соответствующую метку в этот массив. Он представлен в строковом формате, что позволяет нам легко интерпретировать соответствующие выбранные уровни не только из списка перечисления. Это позволяет нам динамически отслеживать предпочтения фильтра пользователя.

Далее определяем массив allowed_importance_levels, который содержит перечисленные значения типа ENUM_CALENDAR_EVENT_IMPORTANCE. Эти перечисленные значения связаны с уровнями воздействия: CALENDAR_IMPORTANCE_NONE, CALENDAR_IMPORTANCE_LOW, CALENDAR_IMPORTANCE_MODERATE и CALENDAR_IMPORTANCE_HIGH. Мы их уже определили, просто перенесли их в глобальную область действия. Эти значения будут использоваться для сортировки событий календаря по степени их важности.

Мы также определяем массив imp_filter_selected, в котором сохраняем уровни важности, соответствующие выбранным пользователем меткам воздействия. Когда пользователь выбирает метки из impact_labels, мы сопоставляем каждую метку с соответствующим ей уровнем важности из allowed_importance_levels и сохраняем результат в imp_filter_selected. Этот массив затем будет использоваться для сортировки событий календаря, гарантируя, что будут отображаться только события с выбранными уровнями важности.

Переходя к функции инициализации, мы обновим кнопки для большей ясности, поскольку теперь мы используем их не только для руководства по результатам событий, но и для процесса сортировки. Таким образом, мы хотим показать их активное или неактивное состояние при щелчке по ним.

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

Здесь мы сначала проверяем, установлена ли переменная enableImportanceFilter в значение true. Если да, то переходим к настройке системы фильтров важности. Начнем с очистки существующих выборок в массиве imp_filter_selected, используя функцию ArrayFree. Затем мы копируем все значения из массива allowed_importance_levels в imp_filter_selected, по сути устанавливая все уровни важности в качестве значений по умолчанию. Это означает, что по умолчанию для сортировки изначально выбраны все уровни важности.

Затем мы очищаем массив impact_filter_selected с помощью функции ArrayFree, что гарантирует удаление любых предыдущих выборок в массиве меток воздействия. После этого мы копируем все значения из массива impact_labels в impact_filter_selected. Это гарантирует, что метки, представляющие уровни воздействия (например, None, Low, Medium и High), будут доступны для фильтра. После настройки массивов мы выводим сообщение "IMPORTANCE FILTER ENABLED" (фильтр важности включен). Мы также выводим содержимое массивов imp_filter_selected и impact_filter_selected для отображения текущих выборов.

Наконец, мы проходим по массиву imp_filter_selected, который содержит текущие выбранные уровни важности, и динамически устанавливаем состояния кнопок для каждого соответствующего уровня важности. Для каждого уровня важности мы динамически создаем имя кнопки с помощью IMPACT_LABEL и индекс текущего уровня важности с помощью функции IntegerToString. Затем мы устанавливаем состояние кнопки на true (выбрано) с помощью функции ObjectSetInteger. Кроме того, мы удаляем любой цвет границы, устанавливая свойство OBJPROP_BORDER_COLOR на none, чтобы визуально подчеркнуть, что кнопка выбрана. Это все, что нам нужно для инициализации. Теперь мы переходим к функции прослушивания событий, где мы отслеживаем нажатия кнопки Importance и действуем соответствующим образом. Здесь мы используем ту же логику, что и с кнопками фильтра валют.

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
}

Здесь мы сначала определяем, была ли нажата кнопка фильтра важности, используя функцию StringFind. Эта функция проверяет, содержит ли имя нажатой кнопки (представленное sparam) строку IMPACT_LABEL. Если содержит, продолжаем извлекать уровень важности, связанные с кнопкой, вызывая функцию ObjectGetString и передавая свойство text, получающее текст (например, Low, Medium и т.д.) нажатой кнопки. Затем мы преобразуем этот текст в соответствующее ему значение перечисления (например, CALENDAR_IMPORTANCE_LOW) путем передачи выбранной метки в функцию get_importance_level(impact_labels, allowed_importance_levels, selected_imp). Эта функция принимает массив меток, допустимые значения перечисления и выбранную текстовую метку, чтобы вернуть соответствующий уровень важности в виде перечисления. Мы рассмотрим пользовательскую функцию позже.

Далее мы проверяем состояние кнопки с помощью функции ObjectGetInteger и передачи свойства состояния, которое определяет, находится ли кнопка в выбранном (true) или невыбранном состоянии (false). На основе этого состояния мы либо добавляем, либо удаляем выбранный уровень важности из массивов фильтров. Если кнопка не выбрана, проходим по массиву imp_filter_selected и удаляем уровень важности, изменяя размер массива с помощью функции ArrayResize. То же самое мы делаем и для массива impact_filter_selected, чтобы гарантировать синхронизацию обоих массивов. Если кнопка выбрана, мы сначала проверяем, находится ли уровень важности уже в массивах фильтров, используя цикл. Если это не так, мы добавляем его в оба массива, изменяя их размер и добавляя новое значение.

После обновления массивов мы используем функцию ArrayPrint для регистрации текущего содержимого массивов фильтров для отладки. Затем мы обновляем панель новыми выборками фильтров, вызывая update_dashboard_values(curr_filter_selected, imp_filter_selected), что отражает изменения в массивах фильтров на панели. Мы обновили функцию так, чтобы теперь она принимала два параметра массива в соответствии с предпочтениями пользователя. Рассмотрим это позже. Наконец, внешний вид нажатой кнопки обновляется путем установки цвета ее границы на основе вычисленного цвета, причем цвет определяется на основе того, нажата кнопка или нет. Затем мы перерисовываем график с помощью функции ChartRedraw для визуального отображения изменений.

Теперь давайте рассмотрим пользовательскую функцию, которая отвечает за получение соответствующего перечисления уровней важности.

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

Здесь мы определяем функцию перечисления get_importance_level, которая используется для определения уровня важности на основе выбранной метки. Функция принимает три параметра:

  • impact_label - массив строк, содержащий различные метки уровней важности (None, Low, Medium и High).
  • importance_levels - массив содержит соответствующие уровни важности в виде значений перечисления (например, CALENDAR_IMPORTANCE_NONE, CALENDAR_IMPORTANCE_LOW и т.д.).
  • selected_label - метка (строка), переданная функции, которая представляет уровень важности, выбранный пользователем (например, Medium).

Внутри функции мы перебираем массив impact_label, используя цикл for. Для каждой итерации мы проверяем, соответствует ли текущий элемент в массиве selected_label. Если совпадение найдено, возвращаем соответствующий уровень важности из массива importance_levels по тому же индексу. Если после проверки всех меток совпадений не обнаружено, функция по умолчанию возвращает CALENDAR_IMPORTANCE_NONE. Эта функция нужна нам для преобразования строки, представляющей выбранный уровень важности (например, Medium), в соответствующий ему уровень важности (например, CALENDAR_IMPORTANCE_MODERATE).

Другие внесенные нами изменения заключались в передаче новых данных массива, отсортированных по важности, в функцию обновления в качестве ссылки, чтобы фильтры вступали в силу динамически в соответствии с обновленными настройками пользователя. Объявление функции теперь похоже на показанное во фрагменте кода ниже.

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

//---      

}

После обновления функции нам также необходимо обновить существующие аналогичные функции, чтобы они содержали массив фильтров важности. Например, вызов функции обработчика событий OnInit будет выглядеть так, как показано ниже.

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

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

Это все, что нам нужно для включения фильтров валюты и важности. Давайте посмотрим на текущие результаты, чтобы убедиться, что фильтры важности работают так, как мы ожидаем.

Фильтр важности (gif)

Из визуализации видно, что мы достигли своей цели — сделали фильтр важности отзывчивым. Наконец, мы можем обновить общее количество отображаемых нами событий. Мы можем получить количество отсортированных событий, общее количество возможных событий, которые можно отобразить на графике, и общее количество рассмотренных событий. Вот логика, которую мы можем для этого использовать. В глобальной области действия мы можем определить некоторые переменные отслеживания.

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

Здесь мы определяем три целочисленные переменные: totalEvents_Considered, totalEvents_Filtered и totalEvents_Displayable. Эти переменные будут служить счетчиками для отслеживания статуса событий во время обработки:

  • totalEvents_Considered - переменная будет отслеживать общее количество событий, которые изначально рассматриваются на этапе обработки. Он представляет собой отправную точку, где все события учитываются до применения какой-либо сортировки.
  • totalEvents_Filtered - эта переменная подсчитывает общее количество событий, которые исключены или отсортированы на основе примененных условий, таких как фильтры валюты, важности или времени. Она показывает, сколько событий было удалено из набора данных.
  • totalEvents_Displayable - эта переменная будет отслеживать общее количество событий, оставшихся после сортировки и подлежащих отображению на панели управления. Она представляет собой окончательный набор событий, которые соответствуют всем критериям сортировки и доступны для отображения.

Используя эти счетчики, мы можем отслеживать и анализировать конвейер обработки событий, гарантируя, что логика сортировки работает ожидаемым образом, и предоставляя информацию об общем потоке данных. Затем, перед выполнением каких-либо действий по сортировке данных, мы устанавливаем их по умолчанию на 0.

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

Перед первым циклом мы заменяем более ранние значения последними значениями, чтобы учесть все события. Вот пример.

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

//---

}

Как видите, вместо того, чтобы применять условия ограничения заранее, мы по-прежнему рассматриваем все события. Это поможет нам увидеть данные о переполнении. Таким образом, внутри цикла мы будем иметь следующую логику обновления.

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

Здесь мы просто соответствующим образом обновляем держатели номеров событий. Важно отметить фрагмент кода, который мы выделили светло-голубым цветом. Его логика работает так же, как и у фильтров: если достигнут общий лимит отображения, мы пропускаем обработку. Наконец, мы обновляем метку так, чтобы она содержала все 3 счетчика событий, что помогает нам узнать счетчик переполнения данных в долгосрочной перспективе. Затем мы применяем ту же логику к функции обновлений, чтобы гарантировать ее синхронизацию в реальном времени. После запуска системы мы получили следующий результат.

Отображение результата

На изображении мы видим, что теперь у нас 3 счетчика новостей. Первое число, в данном случае 11, показывает общее количество отображаемых новостей, второе, 24, показывает общее количество отфильтрованных событий (только мы не можем отобразить их все на панели управления), а третье, 539, показывает общее количество новостей, рассматриваемых для обработки. При всем этом, чтобы включить фильтр времени, мы можем иметь желаемые временные диапазоны во входных форматах, чтобы иметь возможность задать их при инициализации программы. Вот логика для достижения этой цели.

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;

Здесь мы определяем группу под названием General Calendar Settings ("Общие настройки календаря"), чтобы предоставить настраиваемые параметры для управления функциональными возможностями, связанными с календарем. Мы используем три входа типа ENUM_TIMEFRAMES для управления временными параметрами, в рамках которых сортируются или анализируются события календаря. Сначала мы определяем start_time, которое указывает начало временного диапазона для событий календаря, по умолчанию 12 часов (PERIOD_H12).

Далее мы вводим end_time, который отмечает конец этого временного диапазона, также установленный по умолчанию на PERIOD_H12. Наконец, мы используем range_time для определения продолжительности или интервала, представляющего интерес для сортировки или расчетов календаря, со значением по умолчанию 8 часов (PERIOD_H8). Благодаря этому мы обеспечиваем гибкую работу программы на основе определяемых пользователем временных рамок, что позволяет нам адаптировать календарные данные к конкретным интересующим интервалам. Эти настройки позволяют осуществлять динамическую сортировку и предоставляют пользователю контроль над временными рамками отображаемых или анализируемых событий.

Чтобы внести изменения, мы добавляем их к управляющим входам соответствующих функций и логик держателя следующим образом.

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

И это все. После компиляции мы получаем следующий результат.

Результат

На изображении видно, что теперь мы можем получить доступ к входным параметрам и выбрать настройки времени из выпадающего списка. На данный момент наша панель полностью функциональна и отзывчива. Нам нужно протестировать ее в разных условиях и средах, чтобы убедиться, что она работает отлично и без сбоев, и если таковые обнаружатся, устранить их. Это будет сделано в следующем разделе.


Тестирование расширенной панели

Протестируем усовершенствованную панель. Цель — убедиться в том, что все фильтры, механизмы отслеживания событий и отображения данных работают так, как задумано. Мы реализовали ряд фильтров, включая фильтры по валюте, важности и времени, которые позволяют нам более эффективно просматривать и анализировать события календаря.

Процесс тестирования включает в себя моделирование взаимодействия пользователя с панелью управления, включение и отключение фильтров, а также обеспечение динамического обновления событий календаря на основе выбранных критериев. Мы также проверяем правильность отображения данных о событиях, таких как страна, валюта, уровень важности и время события, в пределах определенного временного диапазона.

Запуская различные тестовые сценарии, мы подтверждаем работоспособность каждой функции, например, сортировку событий по важности или валюте, ограничение количества отображаемых событий и обеспечение ожидаемого обновления меток данных. Видео ниже демонстрирует тесты в действии.



Заключение

Мы успешно разработали и протестировали усовершенствованную панель экономического календаря MQL5, гарантирующую бесперебойную работу фильтров, отображение данных и систем отслеживания событий. Панель предоставляет интуитивно понятный интерфейс для отслеживания экономических событий, применяя фильтры на основе валюты, важности и времени, что помогает нам оставаться в курсе событий и принимать решения на основе рыночных данных.

В следующей части этой серии мы, опираясь на эту основу, интегрируем генерацию сигналов и торговые операции. Используя данные улучшенной панели, мы разработаем систему, которая сможет автоматически генерировать торговые сигналы на основе экономических событий и рыночных условий, что позволит разрабатывать более эффективные и обоснованные торговые стратегии. Оставайтесь с нами!

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/16404

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
Petr Zharuk
Petr Zharuk | 25 июл. 2025 в 13:39
Полезно, спасибо.
Но сколько статей не видел касательно интерфейсов, все это выглядит так будто сейчас 2005 год. 
Преодоление ограничений машинного обучения (Часть 1): Нехватка совместимых метрик Преодоление ограничений машинного обучения (Часть 1): Нехватка совместимых метрик
В настоящей статье показано, что часть проблем, с которыми мы сталкиваемся, коренится в слепом следовании «лучшим практикам». Предоставляя читателю простые, основанные на реальном рынке доказательства, мы объясним ему, почему мы должны воздержаться от такого поведения и вместо этого принять передовой опыт, основанный на конкретных областях, если наше сообщество хочет получить хоть какой-то шанс на восстановление скрытого потенциала ИИ.
Алгоритм конкурентного обучения — Competitive Learning Algorithm (CLA) Алгоритм конкурентного обучения — Competitive Learning Algorithm (CLA)
В статье представлен алгоритм конкурентного обучения (Competitive Learning Algorithm, CLA) — новый метаэвристический метод оптимизации, основанный на моделировании образовательного процесса. Алгоритм организует популяцию решений в виде классов со студентами и учителями, где агенты обучаются через три механизма: следование за лучшим в классе, использование личного опыта и обмен знаниями между классами.
Торговый инструментарий MQL5 (Часть 4): Разработка EX5-библиотеки для управления историей Торговый инструментарий MQL5 (Часть 4): Разработка EX5-библиотеки для управления историей
Узнайте, как извлекать, обрабатывать, классифицировать, сортировать, анализировать и управлять закрытыми позициями, ордерами и историями сделок с помощью MQL5, создав обширную EX5-библиотеку управления историей с помощью подробного пошагового подхода.
Создание динамических графических интерфейсов на MQL5 через бикубическую интерполяцию Создание динамических графических интерфейсов на MQL5 через бикубическую интерполяцию
В настоящей статье мы исследуем динамические графические интерфейсы MQL5, использующие бикубическую интерполяцию для высококачественного масштабирования изображений на торговых графиках. Мы подробно описываем гибкие варианты позиционирования, позволяющие выполнять динамическое центрирование или угловую привязку с настраиваемыми смещениями.