
Трейдинг с экономическим календарем MQL5 (Часть 3): Добавление сортировки по валюте, важности и времени
Введение
В этой статье я опираюсь на предыдущую работу об экономическом календаре на языке MetaQuotes Language 5 (MQL5), в которой мы разработали панель новостей для отображения экономических событий. Теперь мы улучшим эту панель управления, внедрив специальные фильтры по валюте, важности и времени, что позволит трейдерам сосредоточиться только на новостях, наиболее соответствующих их стратегиям. Эти фильтры обеспечат целенаправленное представление событий, влияющих на рынок, помогая оптимизировать процесс принятия решений и повысить эффективность торговли. Мы рассмотрим следующие темы:
Благодаря этим дополнениям наша панель управления станет мощным инструментом мониторинга и сортировки экономических новостей в среде MQL5, адаптированной к потребностям трейдеров в своевременной и актуальной информации.
Типы фильтров в экономических календарях
Чтобы усовершенствовать функциональность нашей панели мониторинга, мы должны понимать назначение и преимущества каждого типа фильтра: валюта, важность и время. Фильтр валют позволяет нам просматривать экономические события, которые непосредственно влияют на валюты, которыми мы торгуем, что упрощает выявление соответствующих событий, которые могут повлиять на наши открытые позиции. Этот фильтр помогает оптимизировать панель управления, уменьшая информационную перегрузку и концентрируясь только на валютах из нашего торгового портфеля. В торговом терминале MetaTrader 5 мы можем получить доступ к новостям и отсортировать их по валюте, наведя курсор на вкладку "Календарь", щелкнув правой кнопкой мыши внутри нее и выбрав предпочтительную валюту или страну.
Фильтр важности классифицирует события на основе их ожидаемого влияния, которое обычно определяется как низкое, среднее или высокое. События с большим влиянием, такие как заявления центральных банков или данные по безработице, могут привести к волатильности рынка. Сортируя новости по важности, мы можем быстро оценить, какие события могут оказать наиболее существенное влияние на наши торговые решения, повышая оперативность. Чтобы отсортировать новости по уровню влияния, вы можете снова щелкнуть правой кнопкой мыши по вкладке "Календарь" и выбрать новости по приоритету.
Наконец, временной фильтр позволяет нам указать временные рамки для соответствующих экономических событий, что особенно полезно для тех, кто торгует в течение определенных сессий или готовится к предстоящим новостям. С помощью этого фильтра мы можем видеть события, происходящие в течение определенного периода, например, следующего часа, дня или недели, предоставляя временную шкалу, которая соответствует нашим торговым стратегиям и временным предпочтениям. Вместе эти фильтры создают настраиваемый интерфейс, который адаптирует данные экономических новостей к индивидуальным потребностям торговли, формируя основу отзывчивой и эффективной панели MQL5.
Реализация фильтров в MQL5
Чтобы реализовать фильтры в MQL5, первым шагом является определение булевых переменных в глобальной области видимости. Эти переменные будут определять, включены или выключены фильтры по валюте, важности и времени. Определяя их глобально, мы гарантируем, что к фильтрам можно будет получить доступ и изменять их по всему коду, что обеспечит гибкость в работе панели новостей. Этот шаг заложит основу для реализации логики сортировки и позволит нам адаптировать функциональность панели управления в соответствии с нашими торговыми потребностями. Чтобы добиться этого, мы используем следующую логику.
//--- Define flags to enable/disable filters bool enableCurrencyFilter = true; // Set to 'true' to enable currency filter, 'false' to disable bool enableImportanceFilter = true; // Set to 'true' to enable importance filter, 'false' to disable bool enableTimeFilter = true; // Set to 'true' to enable time filter, 'false' to disable
Здесь мы определяем три булевые переменные - enableCurrencyFilter, enableImportanceFilter и enableTimeFilter, которые мы будем использовать для включения/выключения фильтров валюты, важности и времени. Каждой переменной по умолчанию присвоено значение true, что означает, что все фильтры будут активны. Изменяя эти значения на false, мы можем отключить любые фильтры, которые нам не нужны.
Отсюда, в логике инициализации при подсчете допустимых новостей, мы начнем с фильтра валют. Сначала нам необходимо определить коды валют, к которым мы хотим применить фильтр. Определим их следующим образом.
string curr_filter[] = {"AUD","CAD","CHF","EUR","GBP","JPY","NZD","USD"}; int news_filter_count = 0;
Здесь мы определяем "строковый" массив curr_filter, содержащий список валютных пар — AUD, CAD, CHF, EUR, GBP, JPY, NZD и USD, — которые мы хотим использовать для сортировки новостей на основе определенных валют. Этот массив поможет нам сузить круг новостей, отображаемых на панели управления, сосредоточившись только на тех, которые имеют отношение к выбранным валютам. Мы также определяем переменную news_filter_count для отслеживания количества отфильтрованных новостных событий, соответствующих выбранным нами критериям, гарантируя, что мы отображаем только самую релевантную информацию. Переходим к логике фильтра.
//--- 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; } }
Здесь мы проверяем, соответствует ли валюта события какой-либо валюте в массиве curr_filter, но только если включен фильтр валют, на что указывает флаг enableCurrencyFilter. Если фильтр включен, мы проходим по массиву curr_filter, используя цикл for. Для каждой итерации мы сравниваем валюту события с валютами в фильтре.
Если совпадение найдено, мы устанавливаем флаг currencyMatch в значение true и прерываем (break) цикл. Если совпадений не найдено (то есть currencyMatch остается равным false), мы используем оператор continue для пропуска текущего события и перехода к следующему, гарантируя, что обрабатываются только соответствующие события. Затем мы используем ту же логику для сортировки событий по степени важности.
//--- Check importance level if importance filter is enabled bool importanceMatch = false; if (enableImportanceFilter) { for (int k = 0; k < ArraySize(allowed_importance_levels); k++) { if (event.importance == allowed_importance_levels[k]) { importanceMatch = true; break; } } //--- If importance does not match the filter criteria, skip the event if (!importanceMatch) { continue; } }
Здесь мы проверяем уровень важности события по предопределенному массиву allowed_importance_levels, но только если включен фильтр важности, на что указывает флаг enableImportanceFilter. Если фильтр включен, мы проходим массив allowed_importance_levels с помощью цикла for, сравнивая важность события с уровнями в массиве.
Если совпадение найдено, мы устанавливаем флаг importanceMatch в true и прерываем (break) цикл. Если совпадений не найдено (то есть importanceMatch остается равным false), мы используем оператор continue для пропуска текущего события, гарантируя, что будут обработаны только события с требуемым уровнем важности. Массив для определения уровней важности используется следующим образом:
// Define the levels of importance to filter (low, moderate, high) ENUM_CALENDAR_EVENT_IMPORTANCE allowed_importance_levels[] = {CALENDAR_IMPORTANCE_LOW, CALENDAR_IMPORTANCE_MODERATE, CALENDAR_IMPORTANCE_HIGH};
Здесь мы добавили все уровни важности, что означает, что технически мы разрешаем все новости на основе приоритета, но вы можете выбрать те, которые лучше всего подходят для ваших торговых решений. Далее нам необходимо определить диапазоны временных фильтров.
//--- Define time range for filtering news events based on daily period datetime timeRange = PeriodSeconds(PERIOD_D1); datetime timeBefore = TimeTradeServer() - timeRange; datetime timeAfter = TimeTradeServer() + timeRange;
Определяем временной диапазон для сортировки новостей на основе дневного периода. Мы используем функцию PeriodSeconds с константой PERIOD_D1, чтобы определить количество секунд в сутках, которое мы затем присвоим datetime-переменной timeRange. Переменные timeBefore и timeAfter используются для расчета временного диапазона вокруг текущего времени сервера, полученного с помощью функции TimeTradeServer, вычитая и прибавляя timeRange соответственно. Это гарантирует, что для обработки будут рассматриваться только события, попадающие в указанный временной диапазон (в течение одного дня до или после текущего времени сервера). Обязательно внесите изменения в соответствии с вашими потребностями. Вооружившись этой логикой, мы можем применить временной фильтр.
//--- Apply time filter and set timeMatch flag (if the filter is enabled) bool timeMatch = false; if (enableTimeFilter) { datetime eventTime = values[i].time; if (eventTime <= TimeTradeServer() && eventTime >= timeBefore) { timeMatch = true; //--- Event is already released } else if (eventTime >= TimeTradeServer() && eventTime <= timeAfter) { timeMatch = true; //--- Event is yet to be released } //--- Skip if the event doesn't match the time filter if (!timeMatch) { continue; } }
Здесь мы применяем фильтр времени, проверяя, попадает ли время события в указанный временной диапазон, и используем флаг timeMatch, чтобы отслеживать, соответствует ли событие критериям. Если enableTimeFilter имеет значение true, мы сначала извлекаем время события из переменной values[i].time. Затем мы проверяем, находится ли время события в прошлом (между текущим временем сервера и timeBefore) или в будущем (между текущим временем сервера и timeAfter). Если время события попадает в любой из диапазонов, флаг timeMatch устанавливается в значение true, что указывает на то, что событие соответствует фильтру времени. Если совпадений не найдено, пропускаем событие, используя оператор continue.
На этом с фильтрами все. На этом этапе мы прошли все проверки и у нас есть какие-то новостные события. Обновляем счетчик новостных фильтров на единицу.
//--- If we reach here, the currency matches the filter news_filter_count++; //--- Increment the count of filtered events
Теперь для создания разделов данных мы используем данные о количестве новостных фильтров, поскольку на этот раз мы не рассматриваем все выбранные события. Это гарантирует, что мы создаем на панели управления ровно столько держателей данных, сколько необходимо для нас.
//--- Set alternating colors for each data row holder color holder_color = (news_filter_count % 2 == 0) ? C'213,227,207' : clrWhite; //--- Create rectangle label for each data row holder createRecLabel(DATA_HOLDERS+string(news_filter_count),62,startY-1,716,26+1,holder_color,1,clrNONE);
Здесь мы устанавливаем чередующиеся цвета для каждого держателя строки данных, чтобы улучшить визуальное различие между строками. holder_color определяется с помощью тернарного оператора, в котором, если news_filter_count имеет четное значение (то есть "news_filter_count % 2 == 0"), используется светло-зеленый цвет (C'213,227,207'), а если нечетное, цвет устанавливается на белый. Это гарантирует, что каждая строка имеет чередующийся цвет, что облегчает чтение данных.
Затем мы создаем прямоугольную метку для каждого держателя строки данных, используя функцию createRecLabel, которая размещает цветной прямоугольник в указанных координатах. Метка однозначно идентифицируется путем объединения DATA_HOLDERS со строковым представлением news_filter_count, чтобы гарантировать, что каждая строка имеет уникальное имя, а размеры прямоугольника задаются в соответствии с содержимым. Граница прямоугольника имеет толщину 1, при этом мы устанавливаем цвет заливки на чередующийся holder_color, а цвет границы - на clrNONE (без цвета).
Однако обратите внимание, что мы добавили 1 пиксель к смещению держателей по оси Y (выделено желтым цветом), чтобы избавиться от границ. Вот результат сравнения.
До добавления 1 пикселя:
После добавления 1 пикселя:
Всё работает как надо. Следующее, что нам нужно сделать, — это обновить новости, отображаемые на панели управления после применения фильтров.
updateLabel(TIME_LABEL,"Server Time: "+TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS)+" ||| Total News: "+ IntegerToString(news_filter_count)+"/"+IntegerToString(allValues));
Здесь мы используем функцию updateLabel для обновления метки, которая отображает текущее время сервера и общее количество отсортированных новостных событий. Мы обновляем метку, идентифицированную как TIME_LABEL, новой строкой, которая объединяет текущее время сервера и количество новостей. Чтобы получить текущее время сервера, мы используем функцию TimeCurrent и форматируем ее с помощью функции TimeToString с флагами TIME_DATE | TIME_SECONDS.
Затем мы отображаем общее количество отсортированных новостей, хранящееся в news_filter_count, а также общее количество доступных новостей, представленное в allValues. Обновляя эту метку, мы предоставляем информацию в реальном времени как о времени сервера, так и о состоянии новостного фильтра, что помогает нам быть в курсе текущих рыночных новостей, которые имеют для нас значение.
Фрагмент кода пользовательской функции, которую мы используем для обновления метки, выглядит следующим образом.
//+------------------------------------------------------------------+ //| Function to create text label | //+------------------------------------------------------------------+ bool updateLabel(string objName,string txt) { // Reset any previous errors ResetLastError(); if (!ObjectSetString(0,objName,OBJPROP_TEXT,txt)) { Print(__FUNCTION__, ": failed to update the label! Error code = ", _LastError); return (false); } ObjectSetString(0, objName, OBJPROP_TEXT, txt); // Text displayed on the label // Redraw the chart to display the label ChartRedraw(0); return (true); // Label creation successful }
Здесь мы определяем функцию updateLabel, которую используем для обновления существующей метки на графике. Функция принимает два параметра: objName (имя объекта метки) и txt (текст, который будет отображаться на метке). Начнем со сброса всех предыдущих ошибок с помощью функции ResetLastError, чтобы получить "чистый лист". Далее пытаемся обновить текст метки с помощью предоставленной строки txt с использованием функции ObjectSetString. Если обновление не удалось, выводим сообщение об ошибке с помощью функции Print вместе с кодом ошибки, полученным из _LastError, и вернем false.
Если обновление метки прошло успешно, вызываем функцию ChartRedraw для обновления графика и отображения обновленной метки, и, наконец, возвращаем true, чтобы указать, что операция прошла успешно. Это функция, которая позволяет нам динамически обновлять содержимое меток на графике, предоставляя гибкий метод отображения такой информации, как время сервера или количество новостей. После запуска программы мы получаем следующее.
Благодаря внедрению этой функции мы теперь уверены, что учитываем только актуальные для нас новости и игнорируем все остальные. Мы также отображаем общее количество прошедших новостей из всех выбранных, показывая как доступные, так и рассмотренные новости. Полный фрагмент кода инициализации, отвечающий за применение фильтров, приведен ниже.
string curr_filter[] = {"AUD","CAD","CHF","EUR","GBP","JPY","NZD","USD"}; int news_filter_count = 0; // Define the levels of importance to filter (low, moderate, high) ENUM_CALENDAR_EVENT_IMPORTANCE allowed_importance_levels[] = {CALENDAR_IMPORTANCE_LOW, CALENDAR_IMPORTANCE_MODERATE, CALENDAR_IMPORTANCE_HIGH}; //--- Loop through each calendar value up to the maximum defined total for (int i = 0; i < valuesTotal; i++){ MqlCalendarEvent event; //--- Declare event structure CalendarEventById(values[i].event_id,event); //--- Retrieve event details by ID MqlCalendarCountry country; //--- Declare country structure CalendarCountryById(event.country_id,country); //--- Retrieve country details by event's country ID MqlCalendarValue value; //--- Declare calendar value structure CalendarValueById(values[i].id,value); //--- Retrieve actual, forecast, and previous values //--- 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; } } //--- Check importance level if importance filter is enabled bool importanceMatch = false; if (enableImportanceFilter) { for (int k = 0; k < ArraySize(allowed_importance_levels); k++) { if (event.importance == allowed_importance_levels[k]) { importanceMatch = true; break; } } //--- If importance does not match the filter criteria, skip the event if (!importanceMatch) { continue; } } //--- Apply time filter and set timeMatch flag (if the filter is enabled) bool timeMatch = false; if (enableTimeFilter) { datetime eventTime = values[i].time; if (eventTime <= TimeTradeServer() && eventTime >= timeBefore) { timeMatch = true; //--- Event is already released } else if (eventTime >= TimeTradeServer() && eventTime <= timeAfter) { timeMatch = true; //--- Event is yet to be released } //--- Skip if the event doesn't match the time filter if (!timeMatch) { continue; } } //--- If we reach here, the currency matches the filter news_filter_count++; //--- Increment the count of filtered events //--- Set alternating colors for each data row holder color holder_color = (news_filter_count % 2 == 0) ? C'213,227,207' : clrWhite; //--- Create rectangle label for each data row holder createRecLabel(DATA_HOLDERS+string(news_filter_count),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++){ //--- Print event details for debugging //Print("Name = ",event.name,", IMP = ",EnumToString(event.importance),", COUNTRY = ",country.name,", TIME = ",values[i].time); //--- Skip event if currency does not match the selected country code // if (StringFind(_Symbol,country.currency) < 0) continue; //--- 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 //--- Determine importance color based on event impact color importance_color = clrBlack; if (event.importance == CALENDAR_IMPORTANCE_LOW){importance_color=clrYellow;} else if (event.importance == CALENDAR_IMPORTANCE_MODERATE){importance_color=clrOrange;} else if (event.importance == CALENDAR_IMPORTANCE_HIGH){importance_color=clrRed;} //--- Set importance symbol for the event news_data[3] = ShortToString(0x25CF); //--- Set event name in the data array news_data[4] = event.name; //--- Populate actual, forecast, and previous values in the news data array news_data[5] = DoubleToString(value.GetActualValue(),3); news_data[6] = DoubleToString(value.GetForecastValue(),3); news_data[7] = DoubleToString(value.GetPreviousValue(),3); //--- Create label for each news data item if (k == 3){ createLabel(ARRAY_NEWS+IntegerToString(i)+" "+array_calendar[k],startX,startY-(22-12),news_data[k],importance_color,22,"Calibri"); } else { createLabel(ARRAY_NEWS+IntegerToString(i)+" "+array_calendar[k],startX,startY,news_data[k],clrBlack,12,"Calibri"); } //--- Increment x-coordinate for the next column startX += buttons[k]+3; } //--- Increment y-coordinate for the next row of data startY += 25; //Print(startY); //--- Print current y-coordinate for debugging } //Print("Final News = ",news_filter_count); updateLabel(TIME_LABEL,"Server Time: "+TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS)+" ||| Total News: "+ IntegerToString(news_filter_count)+"/"+IntegerToString(allValues));
Всё работает как надо. В конечном итоге мы хотим избавиться от панели управления при удалении программы из графика. Чтобы сделать это более профессионально, мы можем определить функцию, в которую добавим всю управляющую логику.
//+------------------------------------------------------------------+ //| Function to destroy the Dashboard panel | //+------------------------------------------------------------------+ void destroy_Dashboard(){ //--- Delete main rectangle panel ObjectDelete(0, MAIN_REC); //--- Delete first sub-rectangle in the dashboard ObjectDelete(0, SUB_REC1); //--- Delete second sub-rectangle in the dashboard ObjectDelete(0, SUB_REC2); //--- Delete header label text ObjectDelete(0, HEADER_LABEL); //--- Delete server time label text ObjectDelete(0, TIME_LABEL); //--- Delete label for impact/importance ObjectDelete(0, IMPACT_LABEL); //--- Delete all objects related to the calendar array ObjectsDeleteAll(0, ARRAY_CALENDAR); //--- Delete all objects related to the news array ObjectsDeleteAll(0, ARRAY_NEWS); //--- Delete all data holder objects created in the dashboard ObjectsDeleteAll(0, DATA_HOLDERS); //--- Delete all impact label objects ObjectsDeleteAll(0, IMPACT_LABEL); //--- Redraw the chart to update any visual changes ChartRedraw(0); }
Здесь мы определяем пользовательскую функцию destroy_Dashboard, которую будем использовать для полного удаления всех элементов, созданных для нашей панели на графике, возвращая его в исходное состояние. Это подразумевает удаление каждого объекта, метки и держателя, используемых на панели управления. Сначала мы удаляем прямоугольник основной панели, вызывая функцию ObjectDelete на MAIN_REC, которая представляет собой основной контейнер нашей панели управления. Затем мы приступаем к удалению остальных прямоугольников, таких как SUB_REC1 и SUB_REC2, которые мы использовали для организации различных разделов панели управления.
После этого мы удаляем метки, отображающие такую информацию, как заголовок панели мониторинга (HEADER_LABEL), время сервера (TIME_LABEL) и уровень важности (IMPACT_LABEL). Каждая из этих меток удаляется, чтобы гарантировать удаление любой текстовой информации, отображаемой на графике. Далее удаляем все объекты в ARRAY_CALENDAR и ARRAY_NEWS, в которых хранится информация о календаре и новостных данных соответственно. Мы выполняем это действие с помощью функции ObjectsDeleteAll, которая позволяет нам очистить любые динамически созданные объекты, связанные с этими массивами.
Затем мы удаляем все объекты, связанные с DATA_HOLDERS, которые представляют собой отдельные строки или контейнеры, отображающие точки данных на панели управления, после чего выполняем еще один вызов для удаления экземпляров IMPACT_LABEL, чтобы гарантировать отсутствие визуальных элементов.
Наконец, мы вызываем функцию ChartRedraw, которая обновляет график и очищает все остатки панели управления, предоставляя чистый холст для дальнейшего рисования или сброса панели управления. Эта функция по сути демонтирует весь дисплей панели управления, подготавливая график для свежих обновлений или других визуальных элементов по мере необходимости после удаления программы. Наконец, мы просто вызываем функцию в обработчике событий OnDeinit для удаления панели.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ //--- destroy_Dashboard(); }
После вызова пользовательской функции в обработчике событий OnDeinit удаляем панель с графика. Это все, что нужно для добавления фильтров на нашу панель управления.
Заключение
Мы улучшили нашу панель экономического календаря MQL5, добавив основные возможности сортировки для просмотра только самых релевантных новостей с учетом валюты, важности и времени. Эти фильтры обеспечивают более рационализированный интерфейс, позволяя нам сосредоточиться на экономических событиях, которые соответствуют нашей конкретной торговой стратегии и целям.
Фильтры делают панель более мощным и эффективным инструментом для принятия обоснованных решений. В следующей части мы расширим эту основу, добавив оперативные обновления в логику панели календаря, что позволит ей постоянно получать последние экономические новости непосредственно в панели MQL5.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/16380
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.





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