
Трейдинг с экономическим календарем MQL5 (Часть 5): Добавление в панель адаптивных элементов управления и кнопок сортировки
Введение
Мы будем опираться на на предыдущую статью серии о языке MetaQuotes Language 5 (MQL5), в которой мы добавили обновления в реальном времени к панели экономического календаря MQL5. Здесь мы сосредоточились на том, чтобы сделать панель управления более интерактивной, добавив кнопки, которые позволяют нам напрямую управлять фильтрами валютных пар, уровнями важности и фильтрами временного диапазона, и все это с самой панели — без необходимости изменять настройки в коде. Мы также добавим кнопку Cancel (отмена), которая очищает выбранные фильтры и удаляет компоненты панели, предоставляя нам полный контроль над отображением. Наконец, мы улучшим пользовательский опыт, сделав кнопки чувствительными к нажатиям, гарантируя их бесперебойную работу и мгновенную обратную связь. В этой статье мы рассмотрим следующие темы:
- Создание кнопок и элементов управления фильтрами
- Автоматизация и добавление адаптивности к кнопкам
- Тестирование расширенной панели
- Заключение
Эти дополнения значительно улучшат удобство использования нашей панели управления, сделав ее более гибкой и динамичной для взаимодействия пользователей в реальном времени. Благодаря этим интерактивным элементам мы можем легко фильтровать и управлять отображаемыми новостными данными, не внося каждый раз изменений в базовый код. Начнем с создания кнопок фильтров и их интеграции в существующий макет панели инструментов.
Создание кнопок и элементов управления фильтрами
В этом разделе мы сосредоточимся на создании кнопок фильтров, которые позволят нам управлять различными аспектами нашей панели, такими как фильтры валютных пар, уровня важности и временного диапазона, непосредственно с панели. Добавив эти кнопки, мы упростим взаимодействие с панелью управления, избавившись от необходимости каждый раз открывать код или изменять его, когда нам нужно изменить настройки фильтра. Цель — разработать интуитивно понятный пользовательский интерфейс, обеспечивающий гибкость и при этом сохраняющий простоту и ясность макета.
Сначала мы определим положение и свойства каждой из кнопок фильтра. Мы разместим эти кнопки на панели инструментов, что позволит нам переключаться между различными настройками валютных пар, уровнями важности и временными фильтрами. Например, мы разместим кнопки фильтра валют в правом верхнем углу панели инструментов, кнопки выбора фильтра валюты, важности и времени — в разделе заголовка, а кнопку отмены — сразу после их определения. Каждая кнопка будет соответствовать определенной настройке фильтра, и мы можем нажать на эти кнопки, чтобы применить нужные нам фильтры. Вот изображение, показывающее первоначальную компоновку кнопок фильтров на панели:
Как видно на изображении, кнопки фильтров организованы на панели управления для удобства доступа и управления. Каждая кнопка предназначена для выполнения определенной функции: включения доступных валютных пар, установки уровня важности событий или фильтрации по времени. Кнопки также будут визуально отличаться друг от друга, чтобы мы могли легко различать разные группы управления.
Для реализации нам потребуется определить константы для дополнительных объектов и элементов управления, которые мы собираемся включить в панель управления.
#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. Набор символов следующий:
В коде функция 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. Ниже приведена соответствующая иллюстрация.
Всё работает как надо. Теперь мы можем применить ту же логику для автоматизации других кнопок. Автоматизируем кнопку сортировки валют. Для достижения этой цели мы используем следующую логику во фрагменте кода.
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. Этот процесс обеспечивает реагирование кнопок фильтров в реальном времени, предоставляя нам возможность бесперебойного переключения их функций. Вот наглядный результат.
Всё работает как надо. Теперь мы можем также автоматизировать кнопки валют.
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 передается по ссылке на функцию. Это означает, что функция обращается к реальному массиву в памяти, а не к его копии. Изменения массива внутри функции (если таковые имеются) напрямую влияют на исходный массив вне функции. Такой подход повышает эффективность, особенно для больших массивов, и обеспечивает согласованность с текущим выбором фильтров пользователя. Позже, вместо использования исходного массива, мы заменяем его последним, переданным по ссылке, содержащим пользовательские настройки. Для наглядности мы выделили изменения желтым цветом. При компиляции получаем следующий результат.
Из визуализации мы видим, что панель обновляется каждый раз, когда мы нажимаем на любую из кнопок валют, а значит, всё работает как надо. Однако в настоящее время существует зависимость кнопки фильтра от валют, поскольку после нажатия на один лишь фильтр валюты панель управления не обновляется. Чтобы решить эту проблему, нам просто нужно вызвать функцию обновления для каждой кнопки фильтра, как показано ниже.
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 }
Здесь мы просто вызываем функцию обновления, чтобы повысить независимость событий панели управления. Для наглядности мы выделили изменения желтым цветом. Таким образом, благодаря этим изменениям кнопки становятся независимыми друг от друга. Вот краткая визуализация.
На данный момент нам удалось интегрировать фильтры валют. Используя ту же процедуру, мы можем также интегрировать фильтры важности. Это будет немного сложнее, поскольку мы будем иметь дело с существующими кнопками, чтобы добавить эффект фильтра, и нам потребуется два массива для уровней важности: основной и побочный строковый, который мы будем использовать для выполнения сравнений. Сначала мы перенесем все массивы важности в глобальную область видимости, чтобы иметь к ним доступ из любой точки кода.
//--- 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); } }
Это все, что нам нужно для включения фильтров валюты и важности. Давайте посмотрим на текущие результаты, чтобы убедиться, что фильтры важности работают так, как мы ожидаем.
Из визуализации видно, что мы достигли своей цели — сделали фильтр важности отзывчивым. Наконец, мы можем обновить общее количество отображаемых нами событий. Мы можем получить количество отсортированных событий, общее количество возможных событий, которые можно отобразить на графике, и общее количество рассмотренных событий. Вот логика, которую мы можем для этого использовать. В глобальной области действия мы можем определить некоторые переменные отслеживания.
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
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Но сколько статей не видел касательно интерфейсов, все это выглядит так будто сейчас 2005 год.