MetaTrader 5 и экономический календарь MQL5: как превратить новости в воспроизводимую торговую систему
Введение
Главные проблемы современного новостного трейдера — разрозненность инструментов и отсутствие алгоритмизации торговой системы. Попробуйте разорваться между интернет-браузером (просматривая новостные сайты) и торговым терминалом, совершая сделки.
Рабочий процесс новостного трейдера выглядит так: Быстро открыть календарь новостей в интернет браузере и просмотреть события — не изменились ли → Опять же быстро оценить ближайшие события и принять решение — что и как торговать → Перейти в терминал MetaTrader 5 — либо поставить отложенные ордера, либо сидеть у терминала и ждать времени выхода новости, чтобы принять решение. При таком сценарии у трейдера часто происходит разрыв контекста, что приводит к задержкам в реакции на новости, а это ведет к убыткам.
Вы хотите, чтобы торговля на новостях работала как инженерная задача — с чёткими правилами, повторяемыми результатами и возможностью автоматического тестирования? Цель статьи — показать рабочую архитектуру новостного слоя для MetaTrader 5: единый источник данных, корректная работа с API календаря, механизм фильтрации и кэширования, экспорт исторических событий в ресурс для тестера и автоматическое переключение между Live и Tester — чтобы один и тот же код давал детерминированные результаты и в реальном времени, и на истории.
Рис.1: Главная проблема ручного трейдера — разрозненность инструментов
Ручная торговля на новостях устарела
Во-первых, новостная стратегия, зависящая от внешних факторов, не поддается тестированию. А ведь половина успеха в трейдинге связана с проверкой работоспособности стратегии на истории — тестированием. Невозможность тестирования стратегии приводит к субъективности принимаемых решений. Стратегия останется гипотезой, а для проверки ее вы затратите массу времени — месяцы и даже годы.
Во-вторых, ручную торговлю очень сложно масштабировать. Попытайтесь масштабировать бизнес, который нестабилен и нерегулярно приносит прибыль. Расширение его может только усилить проблемы. Если нет чёткой структуры, систематизации и оптимизации, то о масштабировании думать бесполезно.
В-третьих, задержки в реакции на новости это «бич» ручной торговли. Представьте, что вы трейдер FOREX и торгуете на новости о занятости в несельскохозяйственном секторе (по-английски: Non Farm Payrolls — NFP) — знаменитые «нонфармы». Данные по NFP выходят, как правило, раз в месяц. Вас отвлекли, и вы пропустили NFP, и потеряли возможность заработать. Знакомая ситуация для многих трейдеров.
Вывод: автоматизация — единственный путь к воспроизводимой новостной торговле.

Рис. 2: Робот всегда быстрей
Встроенный Экономический календарь в MetaTrader 5: единый источник информации
Для алгоритмизации новостной торговли, а значит и тестирования на истории используйте уникальный продукт от нашей компании — встроенный в MetaTrader 5 экономический календарь с доступом к новостям через MQL5-API. Именно это превратит вашу новостную торговлю из импровизации в проверяемый алгоритмический процесс. Доступны как текущие события — в реальном времени, так и история событий — для оффлайн-тестирования.
Таблица 1:
Что делает календарь, встроенный в MetaTrader 5, уникальным:
| Параметр | |
|---|---|
| Скорость доступа | <100 мс — после загрузки новостей в терминал |
| Интеграция | Нативная — на уровне ядра терминала + MQL-API |
| Тестирование | Полностью поддерживается — после загрузки новостей в терминал (доступ в тестере через файлы, ресурсы или через SQLite) |
| Надёжность | Высокая |
В MetaTrader 5 есть превосходный тестер стратегий — быстрый, мульти-инструментальный, с возможностью использовать для тестирования и оптимизации компьютеры в локальной и глобальной сетях. Попробуйте «прогнать» свою новостную торговую систему на истории — возможно ваш подход к торговле изменится в лучшую сторону.
Функции экономического календаря в MetaTrader 5: обзор MQL5-API
Переход от ручного анализа к алгоритмическому начинается с понимания архитектуры данных. В MQL5 экономический календарь — это не просто таблица, а структурированная база данных, доступная через нативный API. Разберем, как правильно запрашивать события, в чем разница между Event и Value, и почему синхронизация времени — это необходимое условие успеха стратегии.
Ядро календаря MQL5 состоит из нескольких ключевых функций, каждая из которых решает свою задачу. Важно понимать, что универсальной функции, отдающей все данные Экономического календаря сразу, не существует — требуется комбинированный подход.
- CalendarValueHistory — основной инструмент для первичной загрузки. Позволяет получить массив значений событий за заданный временной интервал. Это «тяжелая артиллерия», используемая при инициализации советника для заполнения кэша историческими данными или данными на неделю вперед.
- CalendarValueLast — основная функция для роботов, работающих в реальном времени. Возвращает только изменившиеся или новые значения с момента последнего запроса (через механизм change_id). Это позволяет экономить трафик и ресурсы сервера, не запрашивая весь массив данных на каждом тике.
- CalendarEventByCountry — получение описаний всех событий по стране. Возвращает список описаний событий для конкретной страны, заданной кодом по «ISO 3166-1 alpha-2». Необходима для построения фильтров (например, показать только события для «US» (США), «RU» (Россия), «CA» (Канада) и т.д.).
- CalendarEventByCurrency — получение описаний всех событий по валюте. Возвращает список описаний событий для конкретной валюты, заданной кодом («USD», «EUR» и т.д.).
- CalendarCountryById — получение свойств страны по 'id'.
- CalendarEventById — получение свойств события по 'id'.
- CalendarValueById — получение конкретного значения по 'id'.
Структуры данных: что возвращает календарный MQL5-API
Все API-функции экономического календаря возвращают либо массив структур, либо переменную с отдельной структурой. Перечислим структуры и их поля полностью.
Описания событий — используется в функциях CalendarEventById, CalendarEventByCountry и CalendarEventByCurrency:
struct MqlCalendarEvent { ulong id; // идентификатор события ENUM_CALENDAR_EVENT_TYPE type; // тип события из перечисления ENUM_CALENDAR_EVENT_TYPE ENUM_CALENDAR_EVENT_SECTOR sector; // сектор, к которому относится событие ENUM_CALENDAR_EVENT_FREQUENCY frequency; // частота (периодичность) события ENUM_CALENDAR_EVENT_TIMEMODE time_mode; // режим времени события ulong country_id; // идентификатор страны ENUM_CALENDAR_EVENT_UNIT unit; // единица измерения значения экономического индикатора ENUM_CALENDAR_EVENT_IMPORTANCE importance; // важность события ENUM_CALENDAR_EVENT_MULTIPLIER multiplier; // множитель значения экономического индикатора uint digits; // количество знаков после запятой string source_url; // URL источника, где публикуется событие string event_code; // код события string name; // текстовое имя события на языке терминала (в текущей кодировке терминала) };
Описания стран — используется в функциях CalendarCountryById и CalendarCountries:
struct MqlCalendarCountry { ulong id; // идентификатор страны по стандарту ISO 3166-1 string name; // текстовое имя страны (в текущей кодировке терминала) string code; // кодовое имя страны ISO 3166-1 alpha-2 string currency; // код валюты страны string currency_symbol; // символ/знак валюты страны string url_name; // имя страны, используемое в URL на сайте mql5.com };
Значения событий — используется в функциях CalendarValueById, CalendarValueHistoryByEvent, CalendarValueHistory, CalendarValueLastByEvent и CalendarValueLast.
struct MqlCalendarValue { ulong id; // ID значения ulong event_id; // ID события datetime time; // время и дата события datetime period; // отчетный период события int revision; // ревизия публикуемого индикатора по отношению к отчетному периоду long actual_value; // актуальное значение в миллионных долях или LONG_MIN, если значение не задано long prev_value; // предыдущее значение в миллионных долях или LONG_MIN, если значение не задано long revised_prev_value; // пересмотренное предыдущее значение в миллионных долях или LONG_MIN, если значение не задано long forecast_value; // прогнозное значение в миллионных долях или LONG_MIN, если значение не задано ENUM_CALENDAR_EVENT_IMPACT impact_type; // потенциальное влияние на курс валюты //--- функции для проверки значений bool HasActualValue(void) const; // возвращет true, если значение в поле actual_value задано bool HasPreviousValue(void) const; // возвращет true, если значение в поле prev_value задано bool HasRevisedValue(void) const; // возвращет true, если значение в поле revised_prev_value задано bool HasForecastValue(void) const; // возвращет true, если значение в поле forecast_value задано //--- функции для получение значений double GetActualValue(void) const; // возвращает actual_value или nan, если значение не задано double GetPreviousValue(void) const; // возвращает prev_value или nan, если значение не задано double GetRevisedValue(void) const; // возвращает revised_prev_value или nan, если значение не задано double GetForecastValue(void) const; // возвращает forecast_value или nan, если значение не задано };
Важно:
Обратите внимание, что структура MqlCalendarValue предоставляет методы для проверки и получения значений из полей actual_value, forecast_value, prev_value и revised_prev_value. Перечисленные поля могут не иметь значений — например, поле actual_value может быть не заполнено, поскольку новость еще не вышла. Наилучший способ получения значений — делать проверку и получать значения методами самой структуры.
Структуры связаны между собой следующими отношениями:

Рис. 3: Отношения календарных структур
Структура MqlCalendarCountry связана с MqlCalendarEvent посредством идентификатора страны. Форма связи «один ко многим» (1..*).
Структура MqlCalendarEvent связана с MqlCalendarValue посредством идентификатора события. Форма связи «один ко многим» (1..*).
Время релиза новости и серверное время
Все функции для работы с Экономическим календарем используют время торгового сервера TimeTradeServer(). Это означает, что время в структуре MqlCalendarValue и входные параметры с временем в функциях CalendarValueHistoryByEvent() и CalendarValueHistory() задаются в часовом поясе торгового сервера, а не в локальном времени пользователя.
Нет необходимости в конвертации: время события MqlCalendarValue::period можно напрямую сравнивать со временем, получаемым при вызове функций TimeCurrent() или TimeTradeServer(). В тестере функция TimeTradeServer() возвращает модельное время, идентичное времени в исторических данных. Логика работы с временными окнами («за 30 минут до новости») работает одинаково в реальном времени и на истории. Если брокер учитывает переход на летнее/зимнее время — календарь автоматически подстраивается под этот переход.
Практический пример — получение списка событий на сегодня (на текущие сутки):
//+------------------------------------------------------------------+ //| Получение значений календаря на текущие сутки | //+------------------------------------------------------------------+ void GetTodayUSD_Events() { //--- определяем границы периода в серверном времени datetime server_now = TimeTradeServer(); datetime day_start = server_now - (server_now % 86400); datetime day_end = day_start + 86400; MqlCalendarValue values[]; MqlCalendarEvent event; MqlCalendarCountry country; //--- запрашиваем значения только для USD if(CalendarValueHistory(values, day_start, day_end, NULL, "USD")) { Print(" Получено событий для USD: ", ArraySize(values)); //--- проходим по массиву значений for(int i = 0; i < ArraySize(values); i++) { //--- получим описание события if(CalendarEventById(values[i].event_id, event)) { //--- получим описание страны if(CalendarCountryById(event.country_id, country)) { Print("✅ Событие #", i); Print("ID события: ", values[i].event_id); Print("Название события: ", event.name); Print("Сектор: ", event.sector); Print("Источник: ", event.source_url); Print("Название страны: ", country.name); Print("URL страны: ", country.url_name); Print("Время: ", TimeToString(values[i].time, TIME_DATE | TIME_SECONDS)); Print("Влияние: ", values[i].impact_type); // ПРОВЕРКА И ВЫВОД ЗНАЧЕНИЙ if(values[i].HasActualValue()) Print("Факт: ", values[i].GetActualValue()); if(values[i].HasRevisedValue()) Print("Пересмотрено: ", values[i].GetRevisedValue()); if(values[i].HasForecastValue()) Print("Прогноз: ", values[i].GetForecastValue()); if(values[i].HasPreviousValue()) Print("Предыдущее: ", values[i].GetPreviousValue()); } } } } else { int error = GetLastError(); if(error == 0) { Print("❌ CalendarValueHistory: No Events"); } else { Print("❌ Ошибка CalendarValueHistory: ", error); } } } //+------------------------------------------------------------------+
Функция выводит список событий для валюты «USD» на текущий день и содержимое основных полей структур, получаемых через MQL-API календаря. Полный код скрипта содержится в файле «GetTodayEvents-S.mq5», приложенном к статье.
Результат работы данной функции смотрим во вкладке «Инструменты\Эксперты» терминала MetaTrader 5. Видим, что получено два события для валюты «USD». На момент запроса эти события еще не произошли (новости не вышли). Поэтому поле MqlCalendarValue::actual_value не содержит значения — функция HasActualValue() возвращает false.
Для данных видов событий отсутствует (не содержит значения) поле MqlCalendarValue::forecast_value, которое проверяем функцией HasForecastValue(). Для других событий могут отсутствовать все четыре поля (prev_value, actual_value, forecast_value, revised_prev_value).
Получено событий для USD: 2
✅ Событие #0
ID события: 840220005
Название события: Аукцион по размещению 3-месячных казначейских векселей
Сектор: 1
Источник: https://home.treasury.gov/
Название страны: Соединенные Штаты
URL страны: united-states
Время: 2026.04.20 18:30:00
Влияние: 0
Предыдущее: 3.62
✅ Событие #1
ID события: 840220006
Название события: Аукцион по размещению 6-месячных казначейских векселей
Сектор: 1
Источник: https://home.treasury.gov/
Название страны: Соединенные Штаты
URL страны: united-states
Время: 2026.04.20 18:30:00
Влияние: 0
Предыдущее: 3.61
Пояснения по приведенному коду:
Сначала получаем массив значений по всем событиям на заданном диапазоне времени с фильтром по валюте «USD». В цикле проходим по массиву значений и для каждого значения запрашиваем по идентификатору события MqlCalendarValue::event_id описание события функцией CalendarEventById(). Затем запрашиваем описание страны по идентификатору MqlCalendarEvent::country_id функцией CalendarCountryById().
Если функция CalendarValueHistory() возвращает false, но GetLastError() возвращает ноль (отсутствие ошибки) — это говорит о том, что событий по запросу с данными настройками нет.
Обработка ошибок и лимиты при работе с календарем
Работа с удаленными данными всегда сопряжена с рисками разрывов соединения или ограничений доступа. Функции календаря возвращают false или 0 при неудаче. Чтобы понять причину, необходимо использовать GetLastError(). В документации выделена отдельная группа ошибок для календарного модуля.
Коды ошибок:
- ERR_CALENDAR_TIMEOUT (код 5200) — время ожидания ответа от сервера истекло. Проблема сети или сервер перегружен. Решение: Повторить запрос через паузу 5...10 секунд.
- ERR_CALENDAR_NO_DATA (код 5201) — данные календаря еще не загружены. Календарь инициализируется асинхронно. Решение: Подождать 1...2 секунды и повторить.
- ERR_CALENDAR_INVALID_DATE (код 5202) — некорректный диапазон дат. Ошибка в коде (например, дата начала больше даты конца). Решение: Исправить логику, повторный запрос бесполезен.
- ERR_CALENDAR_INVALID_COUNTRY (код 5203) — неизвестный код страны/валюты. Ошибка в параметрах запроса. Решение: Проверить код (например, "US" вместо "USA").
- ERR_CALENDAR_TOO_MANY_REQUESTS (код 5204) — превышен лимит запросов. Критическая ошибка. Решение: Увеличить интервал между запросами.
Ограничение частоты запросов:
Серверы, к которым подключается терминал MetaTrader 5, защищают инфраструктуру от перегрузок. Если советник будет вызывать CalendarValueHistory() на каждом тике или в цикле без задержек, сервер вернёт ошибку 5204 и временно заблокирует доступ к календарю для вашего терминала. Лучшая практика: загружайте данные один раз при старте в OnInit() на необходимый период.
Для обновления в реальном времени используйте CalendarValueLast() с сохранением значения возвращаемой переменной change_id — это позволит получать только изменения, а не весь массив данных. Обновляйте данные по таймеру в OnTimer() с интервалом не чаще 5–10 минут.
Разберем на примерах решение проблем, которые могут возникнуть при загрузке календаря:
При запуске терминала («холодном» старте) и советника календарь не готов мгновенно. Данные подгружаются с сервера в фоне. Если вызовете функцию CalendarValueHistory() в самой первой строке OnInit(), вы, с большой вероятностью, получите ошибку 5201 — нет данных. Необходимо реализовать механизм опроса готовности с экспоненциальным или фиксированным тайм аутом.
Практический пример — функция загрузки данных календаря с обработкой ошибок:
//+------------------------------------------------------------------+ //| Функция загрузки календаря | //+------------------------------------------------------------------+ bool LoadCalendar(MqlCalendarValue& values[], const datetime from, const datetime to, const string country_code = NULL, const string currency = NULL, const int max_retries = 5) { int retry_count = 0; while(retry_count < max_retries) { ResetLastError(); //--- пытаемся загрузить if(CalendarValueHistory(values, from, to, country_code, currency)) return true; // Успех int error = GetLastError(); //--- если ошибка "Нет данных" (5201) или "Таймаут" (5200) — ждем и повторяем if(error == 5201 || error == 5200) { retry_count++; Sleep(1000); // пауза 1 секунда перед повтором continue; } //--- если ошибка критическая (например, неверная дата) — прерываем загрузку сразу Print("❌ Critical Calendar Error: ", error); return false; } Print("❌ Failed to load calendar after ", max_retries, " attempts."); return false; } //+------------------------------------------------------------------+
Пояснения по приведенному коду:
Учитываем возможность возникновения ошибок «Нет данных» (5201) и «Таймаут» (5200). Это ошибки устранимые. Обрабатываем их делая 1...5 секундную паузу и повторно запрашивая данные. При возникновении неустранимых ошибок, прерываем загрузку сразу. Неустранимые ошибки это — неверная дата, не правильный код валюты или страны и т.п. Полный код скрипта содержится в файле «GetTodayEvents-S.mq5», приложенном к статье.
Чтобы избежать лимитов и обеспечить максимальную скорость реакции, в OnTimer() следует использовать механизм change_id. При старте загружаем полную историю событий и запоминаем последний change_id. В таймере запрашиваем только новые данные функцией CalendarValueLast(). Сервер вернёт только то, что изменилось (или false, если изменений нет), не тратя ресурсы на передачу старых данных.
Практический пример — функция обновления данных календаря с обработкой ошибок:
//+------------------------------------------------------------------+ //| Таймер: инкрементальное обновление | //+------------------------------------------------------------------+ void OnTimer() { if(!is_initialized) return; MqlCalendarValue updates[]; ResetLastError(); //--- API автоматически обновляет last_change_id по ссылке if(!CalendarValueLast(last_change_id, updates, (InpCountryCode == "") ? NULL : InpCountryCode, (InpCurrency == "") ? NULL : InpCurrency)) { int err = GetLastError(); // 0 = SUCCESS/NO_NEW_DATA, 5402 = ERR_CALENDAR_NO_CHANGES if(err != 0 && err != 5402) Print("🔴️ CalendarValueLast error: ", err); return; } int cnt = ArraySize(updates); if(cnt == 0) return; Print("🟢 Received ", cnt, " updates. New change_id: ", last_change_id); if(InpPrintChanges) ArrayPrint(updates); SyncCache(updates); } //+------------------------------------------------------------------+ //| Синхронизация кэша | //+------------------------------------------------------------------+ void SyncCache(const MqlCalendarValue &updates[]) { int upd_cnt = ArraySize(updates); int cache_sz = ArraySize(calendar_cache); for(int u = 0; u < upd_cnt; u++) { bool found = false; for(int c = 0; c < cache_sz; c++) { if(calendar_cache[c].id == updates[u].id) { calendar_cache[c] = updates[u]; found = true; break; } } if(!found) { ArrayResize(calendar_cache, cache_sz + 1); calendar_cache[cache_sz] = updates[u]; cache_sz++; total_events++; } } } //+------------------------------------------------------------------+
Пояснения по приведенному коду:
Инкрементальное обновление событий происходит через обработчик событий от таймера OnTimer(). Вызывается функция календарного API CalendarValueLast() — в случае появления новых событий, они обновляют в загруженном ранее массиве структур MqlCalendarValue соответствующие элементы и выводится информация в журнал терминала. Если такого события в загруженном массиве не находится, то оно добавляется в массив.
Полный код скрипта содержится в файле «CalendarEventMonitor-EA.mq5», приложенном к статье.
Фильтрация событий: от общего к частному
Зачем фильтровать:
В экономическом календаре публикуется 60−90 событий ежедневно. Это как слушать непрерывный шум из сотен второстепенных индикаторов, праздников и речей чиновников, которые не оказывают немедленного влияния на рынок. Торговать по каждому из них — прямой путь к «переторговке» и сливу депозита. Задача новостного алготрейдера — оставить 3−5 событий, которые действительно двигают рынок.
Фильтрация в алгоритмической торговле на новостях эквивалентна повышению соотношения «сигнал/шум» до уровня, пригодного для принятия автоматических решений. В календарном MQL5-API этот процесс реализуется как многоуровневая система сито, через которое проходят массивы структур MqlCalendarEvent и MqlCalendarValue.
Критерии фильтрации:
Рынок реагирует не на сам факт выхода новости, а на отклонение факта от прогноза и уровень макроэкономического влияния. События с низким влиянием (MqlCalendarValue::impact_type < 3) часто игнорируются торговыми алгоритмами и маркет-мейкерами.
Без фильтрации вы получаете:
- Ложные срабатывания стратегии на второстепенной статистике;
- Торговлю в периоды расширенного спреда без волатильности;
- Перегрузку логики советника ненужными проверками.
Цель фильтрации: сократить поток данных на 90%, оставив только события с высоким влиянием (ENUM_CALENDAR_EVENT_IMPORTANCE::CALENDAR_IMPORTANCE_HIGH), для выбранной трейдером целевой валюты, в заданном временном окне.
Разберем, как построить надежный фильтр, который будет работать одинаково стабильно и в режиме реального времени, и в режиме тестирования.
Многоуровневая система фильтрации:
«Сырой» массив событий, загруженный из терминала, подобен необработанной руде: в нём содержатся тысячи записей — от малозначимых статистических отчётов до праздничных дней, которые не несут для алгоритмической стратегии никакой пользы.
Чтобы превратить этот поток данных в чистый сигнал, необходима многоуровневая система фильтрации. Отбор событий выступает в роли «сита», через которое проходят исходные данные, оставляя на выходе только те события, которые соответствуют заданным критериям: валюте, важности, коду и временному окну.
Эффективный фильтр должен иметь иерархическую структуру: начиная от первоначальной, грубой отсечки по географии и завершая тонкой настройкой по временному окну.
Важная особенность новостного MQL5-API — структура MqlCalendarValue не содержит полей currency_id и country_id. Она хранит только event_id, время, значения индикаторов и тип влияния. Как это влияет на фильтрацию?
Валютная фильтрация выполняется на уровне API-запроса. Когда вы вызываете CalendarValueHistory(..., NULL, "USD"), терминал сам отбрасывает всё, кроме доллара. Массив, который вы получаете, уже отфильтрован по валюте. Поэтому в реальном времени не нужно проверять currency_id — достаточно фильтровать по времени, важности и наличию/значению прогноза.
Уровень 1: Валютный фильтр
Первый барьер — география события. Торгуя парой «EURUSD», робот должен реагировать только на события, влияющие на Еврозону («EUR») и США («USD»). Фильтрация сводится к запросу событий с кодами валюты торгуемого инструмента. Для кросс-курсов (например, «GBPJPY») необходимо запрашивать события по обеим валютам («GBP» и «JPY») и доллару США («USD»).
Уровень 2: Важность события
Все новости имеют различную важность по воздействию на экономику — фактически, на цену. Индекс потребительских цен (CPI) может сдвинуть рынок на 100 пунктов, в то время как индекс доверия потребителей ZEW — лишь на 10.
MQL5-API использует перечисление ENUM_CALENDAR_EVENT_IMPORTANCE для задания степени важности:
enum ENUM_CALENDAR_EVENT_IMPORTANCE { CALENDAR_IMPORTANCE_NONE, // степень важности не задана CALENDAR_IMPORTANCE_LOW, // низкая важность CALENDAR_IMPORTANCE_MODERATE, // средняя важность CALENDAR_IMPORTANCE_HIGH // высокая важность };
Новостные роботы должны игнорировать всё ниже HIGH, чтобы избежать ложных срабатываний на новостях с низкой ликвидностью.
Уровень 3: Код события
Даже среди событий высокой важности есть исключения. Например, выступление главы ЕЦБ или МВФ может быть помечено как важное, но алгоритмически его труднее обрабатывать, чем публикацию конкретных цифр. Поэтому нужна фильтрация по коду события: поле MqlCalendarEvent::event_code содержит уникальный идентификатор (например, "NONFARM", "CPI", "GDP"). Отбираем только события с цифрами экономических показателей, а не опросы и речи.
Только пройдя первые три уровня проверки, событие копируется в результирующий массив и используется в торговле.

Рис.4: Многоуровневая фильтрация — ключ к успеху в новостной торговле
После первых трех уровней фильтрации отслеживается время выхода события на предмет попадания во временное окно — это происходит уже во время работы торгового эксперта.
Уровень 4: Временное окно
Финальный этап — проверка актуальности. Новость, которая выйдет через месяц, нам сейчас не нужна. Новость, которая была вчера, уже отработана рынком. Поэтому, событие считается активным, если TimeCurrent() попадает в интервал: заданное время до выхода новости и заданное время после выхода. Обычно используют настройки: 15–30 минут до выхода — для закрытия позиций и 60 минут после — для анализа волатильности и направления движения цены.
Бинарные ресурсы MQL5: перенос событий в оффлайн-тест для эффективного кэширования
Проблема тестирования в новостной алгоритмической торговле — тестер стратегий не имеет доступа к интернету. Такое решение ускоряет тестирование и защищает от недетерминированных факторов, но создаёт проблему для тестирования новостных стратегий.
Если робот в тестере вызовет функцию CalendarValueHistory(), он получит ошибку и пустой массив. Чтобы протестировать стратегию, нужно «зашить» исторические данные новостей внутрь исполняемого файла «.ex5». Для этого используем вариант с бинарными ресурсами MQL5. Почему именно этот вариант? Это самый быстрый способ доступа к структурированным данным — скорость доступа ограничевается только производительностью файловой системы компьютера.
Разберем пошагово, как превратить массив MqlCalendarValue[] в компактный бинарный файл и как встроить его в код так, чтобы тестер читал данные из оперативной памяти со скоростью молнии. Первым шагом является создание скрипта-экспортёра, который загрузит актуальные данные из календаря и сохранит их в бинарный файл.
Практический пример — скрипт экспорта событий в бинарный файл:
Полный код скрипта находится в файле «ExportCalendarForTester.mq5», приложенном к статье.
Шаг 1: Сборка и фильтрация массива
Не будем хранить в ресурсе все события мира. На этапе экспорта в бинарный файл применим те же фильтры, что и в советнике: валюта, важность, тип события. Входные параметры скрипта задают список валют, временной интервал загрузки событий, коды событий и минимальную важность.
Список кодов событий (параметр InpEventCodes) по умолчанию пустой — это значит, что загружаются все типы событий. Можно сузить этот фильтр. Например, только «нонфармы» — тогда список кодов будет такой "NONFARM".
Флаг InpUseCommonDir задает куда сохранять бинарный файл: true — сохранять в общую папку для всех всех клиентских терминалов "\Terminal\Common\Files".
//--- входные параметры input string InpCurrencies = "USD"; input datetime InpDateFrom = D'2025.01.01'; input datetime InpDateTo = 0; input string InpEventCodes = ""; input ENUM_CALENDAR_EVENT_IMPORTANCE InpMinImportance = CALENDAR_IMPORTANCE_HIGH; input string InpOutputFile = "calendar_test_res.bin"; input bool InpUseCommonDir = true;
Часть скрипта, выполняющая загрузку и фильтрацию событий с заданными во входных параметрах значениями приведена ниже:
Print("🔄 Экспорт календаря:"); Print("----------------------------------"); Print("Параметры фильтрации:"); Print(" 🟨 Интервал = ", InpDateFrom, " — ", InpDateTo); Print(" 🟨 Currencies = ", InpCurrencies, "\n 🟨 Event Codes = ", InpEventCodes, "\n 🟨 Min Importance = ", EnumToString(InpMinImportance)); Print("----------------------------------"); //--- инициализация фильтра по валютам ArrayResize(currencies, 0); if(InpCurrencies == "") return; StringSplit(InpCurrencies, ',', currencies); currencies_size = ArraySize(currencies); for(int i = 0; i < currencies_size; i++) StringToUpper(currencies[i]); //--- инициализация фильтра по кодам событий ArrayResize(event_codes, 0); if(InpEventCodes != "") StringSplit(InpEventCodes, ',', event_codes); event_codes_Size = ArraySize(event_codes); //--- загрузка значений календаря с ФИЛЬТРОМ ПО ВАЛЮТЕ (если он задан) if(currencies_size > 0) { ArrayResize(values_size, currencies_size); ArrayFill(values_size, 0, currencies_size, 0); for(int i = 0; i < currencies_size; i++) { if(LoadCalendar(raw_values, InpDateFrom, InpDateTo, "", currencies[i])) { raw_values_size = ArraySize(raw_values); //--- загрузка событий с ФИЛЬТРОМ ПО ВАЖНОСТИ И КОДУ СОБЫТИЯ for(int k = 0; k < raw_values_size; k++) { //--- получим описание события if(CalendarEventById(raw_values[k].event_id, event)) { //--- берем только индикаторы if(event.type != CALENDAR_TYPE_INDICATOR) continue; // дальше фильтровать нечего //--- проверка по ВАЖНОСТИ if(event.importance < InpMinImportance) continue; // дальше фильтровать нечего //--- проверка по КОДУ СОБЫТИЯ (если он задан) if(event_codes_Size > 0) { bool code_allowed = false; for(int c = 0; c < event_codes_Size; c++) { StringToUpper(event.event_code); if(StringFind(event.event_code, event_codes[c]) >= 0) { code_allowed = true; break; } } if(code_allowed == false) continue; } //--- пополним массив отфильтрованных событий int event_index = ArraySize(values); ArrayResize(values, event_index + 1); values[event_index] = raw_values[k]; values_size[i]++; } } Print("✅ Получено значений ПО ВАЛЮТЕ \"", currencies[i], "\": ", raw_values_size, " → Из них отфильтровано: ", values_size[i]); } else { int error = GetLastError(); if(error == 0) Print("⚠ LoadCalendar Info: No Events for ", currencies[i]); else Print("❌ LoadCalendar Error: ", error, " for ", currencies[i]); return; // дальше фильтровать нечего } } }
Пояснения по приведенному коду:
- В массиве values_size[] хранятся число отфильтрованных событий для каждого заданного во входных параметрах кода валюты.
- Значению ноль для типа MQL5 datetime соответствует дата 1970.01.01 00:00:00.
- Дополнительная фильтрация происходит по типу события. Фильтруются только события типа ENUM_CALENDAR_EVENT_TYPE::CALENDAR_TYPE_INDICATOR — это экономические события (не речи, а цыфры). Все остальные новости отсекаются. Это делается проверкой в самом начале фильтрации:
//--- берем только индикаторы if(event.type != CALENDAR_TYPE_INDICATOR) continue; // Дальше фильтровать нечего
Запустим скрипт с разными наборами параметров фильтрации и посмотрим насколько быстро работает скрипт загрузки/фильтрации событий. Понятно, что скорость зависит от мощности компьютера, скорости связи с серверами данных через интернет-соединение. Тем не менее, проведем стресс-тестирование. Результаты представлены ниже.
Все возможные события по доллару США — за всю доступную историю, высокой важности:
13:59:43.724 🔄 Экспорт календаря:
13:59:43.724 ----------------------------------
13:59:43.724 Параметры фильтрации:
13:59:43.724 🟨 Интервал = 1970.01.01 00:00:00 — 1970.01.01 00:00:00
13:59:43.724 🟨 Currencies = USD
13:59:43.724 🟨 Event Codes =
13:59:43.724 🟨 Min Importance = CALENDAR_IMPORTANCE_HIGH
13:59:43.724 ----------------------------------
13:59:45.511 ✅ Получено значений ПО ВАЛЮТЕ "USD": 53346 → Из них отфильтровано: 9009
13:59:45.511 ----------------------------------
13:59:45.513 ✅ Saved: USD_calendar_test_res.bin Size: 1153152 bytes (9009 events)
Время получения массива отфильтрованных значений (структур MqlCalendarValue): 1.80 сек (0.20 мсек / на одну фильтрацию).
Все возможные события по доллару США, евро, японской йене — за всю доступную историю, высокой важности:
14:03:08.724 🔄 Экспорт календаря:
14:03:08.724 ----------------------------------
14:03:08.724 Параметры фильтрации:
14:03:08.724 🟨 Интервал = 1970.01.01 00:00:00 — 1970.01.01 00:00:00
14:03:08.724 🟨 Currencies = USD,EUR,JPY
14:03:08.724 🟨 Event Codes =
14:03:08.724 🟨 Min Importance = CALENDAR_IMPORTANCE_HIGH
14:03:08.724 ----------------------------------
14:03:10.488 ✅ Получено значений ПО ВАЛЮТЕ "USD": 53346 → Из них отфильтровано: 9009
14:03:10.947 ✅ Получено значений ПО ВАЛЮТЕ "EUR": 45139 → Из них отфильтровано: 1102
14:03:11.404 ✅ Получено значений ПО ВАЛЮТЕ "JPY": 18907 → Из них отфильтровано: 937
14:03:11.404 ----------------------------------
14:03:11.406 ✅ Saved: USD_calendar_test_res.bin Size: 1153152 bytes (9009 events)
14:03:11.407 ✅ Saved: EUR_calendar_test_res.bin Size: 141056 bytes (1102 events)
14:03:11.408 ✅ Saved: JPY_calendar_test_res.bin Size: 119936 bytes (937 events)
Время получения массива отфильтрованных значений (структур MqlCalendarValue): 2.68 сек (0.24 мсек / на одну фильтрацию).
События NFP («нонфармы») по доллару США — за всю доступную историю, высокой важности:
14:07:22.203 ----------------------------------
14:07:22.203 Параметры фильтрации:
14:07:22.203 🟨 Интервал = 1970.01.01 00:00:00 — 1970.01.01 00:00:00
14:07:22.203 🟨 Currencies = USD
14:07:22.203 🟨 Event Codes = NONFARM
14:07:22.203 🟨 Min Importance = CALENDAR_IMPORTANCE_HIGH
14:07:22.203 ----------------------------------
14:07:22.290 ✅ Получено значений ПО ВАЛЮТЕ "USD": 53346 → Из них отфильтровано: 473
14:07:22.290 ----------------------------------
14:07:22.291 ✅ Saved: USD_calendar_test_res.bin Size: 60544 bytes (473 events)
Время получения массива отфильтрованных значений (структур MqlCalendarValue): 0.09 сек (0.18 мсек / на одну фильтрацию).
Замечания:
- Не обязательно задавать код события в фильтре полностью — достаточно задать уникальную часть строки с кодом. Например, для «нонфармов» полный код: «NONFARM-PAYROLLS» — достаточно задать «NONFARM».
- При работе торговой системы в реальном времени в терминале MetaTrader 5 вряд ли понадобится загрузка всех событий за всё время. Обычно загружают текущие события за сутки и на неделю вперед. Поэтому временем загрузки можно пренебречь в алгоритме новостного эксперта.
Шаг 2: Экспорт массива событий в бинарный файл
Функция SaveToBinary сохраняет отфильтрованные события в бинарный файл. Число файлов равно числу заданных во входных параметрах кодов валюты — каждая валюта экспортируется в свой бинарный файл. Причем имя файла начинается с префикса кода валюты. Например, «USD_calendar_test_res.bin». Так мы получаем взаимно однозначное соответствие между кодом валюты и списком событий для него.
//+------------------------------------------------------------------+ //| Сохранение массива в бинарный файл | //+------------------------------------------------------------------+ void SaveToBinary(MqlCalendarValue &values[], const int &vls_size[], const string filename, const string ¤cies[]) { if(ArraySize(values) == 0) { Print("⚠️ Nothing to save"); return; } Print("----------------------------------"); int offset = 0; for(int i = 0; i < ArraySize(vls_size); i++) { int file_handle = FileOpen(currencies[i] + "_" + filename, FILE_WRITE | FILE_BIN | (InpUseCommonDir ? FILE_COMMON : 0)); if(file_handle == INVALID_HANDLE) { Print("❌ FileSave failed: ", GetLastError()); return; } FileWriteArray(file_handle, values, offset, vls_size[i]); FileFlush(file_handle); FileClose(file_handle); Print("✅ Saved: ", currencies[i] + "_" + filename, " Size: ", vls_size[i] * sizeof(MqlCalendarValue), " bytes", " (", vls_size[i], " events)"); offset += vls_size[i]; } }
После успешной отработки скрипта экспорта событий в бинарные файлы во вклвдке «Эксперты» MetaTrader 5 появятся сообщения вида:
14:28:42.077 ----------------------------------
14:28:42.078 ✅ Saved: USD_calendar_test_res.bin Size: 58240 bytes (455 events)
14:28:42.079 ✅ Saved: EUR_calendar_test_res.bin Size: 7680 bytes (60 events)
Шаг 3: Компиляция ресурса в советник
После запуска скрипта «ExportCalendarForTester.mq5» в папке «MQL5/Files/» появятся файлы «USD_calendar_test_res.bin» и «EUR_calendar_test_res.bin». Теперь их нужно «вшить» в советник. В начале файла советника (после #property директив) добавьте одну (если используется файл для одной валюты) или несколько строчек вида:
// Встраиваем бинарный файл как статический ресурс на этапе компиляции #resource "\\Files\\USD_calendar_test_res.bin" as MqlCalendarValue USD_res_calendar_data[] #resource "\\Files\\EUR_calendar_test_res.bin" as MqlCalendarValue EUR_res_calendar_data[]
Важно:
- Путь в #resource указывается относительно папки «MQL5/Files/». Префикс «\\Files\\» является обязательным.
- При компиляции советника во вкладке «Ошибки» MetaEditor должны появиться сообщения вида:
- 'USD_calendar_test_res.bin' as 'const MqlCalendarValue USD_res_calendar_data[455]'
- 'EUR_calendar_test_res.bin' as 'const MqlCalendarValue EUR_res_calendar_data[60]'
При старте советника в массивах USD_res_calendar_data[] и EUR_res_calendar_data[] будут находиться структуры сохраненных на этапе экспорта событий.
Интеграция в советник: автоматическое переключение режимов
Задача — сделать так, чтобы один и тот же код советника работал и в реальном времени, и в тестере, автоматически выбирая источник данных. Функция MQLInfoInteger(MQL_TESTER) возвращает true, если советник запущен в тестере стратегий или оптимизаторе.
//+------------------------------------------------------------------+ //| Инициализация: выбор источника данных | //+------------------------------------------------------------------+ int OnInit() { Print("🔄 Инициализация новостного модуля:"); //--- инициализация фильтра по валютам ArrayResize(currencies, 0); if(InpCurrencies == "") return INIT_FAILED; StringSplit(InpCurrencies, ',', currencies); currencies_size = ArraySize(currencies); for(int i = 0; i < currencies_size; i++) StringToUpper(currencies[i]); //--- инициализация фильтра по кодам событий ArrayResize(event_codes, 0); if(InpEventCodes != "") StringSplit(InpEventCodes, ',', event_codes); event_codes_Size = ArraySize(event_codes); //--- определяем среду исполнения bool is_tester = MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_OPTIMIZATION) || MQLInfoInteger(MQL_VISUAL_MODE); if(is_tester) { //--- РЕЖИМ ТЕСТЕРА: Загрузка из ресурса if(!LoadFromResource()) { Print("❌ ERROR: Failed to load calendar from resource"); return INIT_FAILED; } is_live_mode = false; Print("⚠️ Режим: TESTER (данные из ресурса)"); } else { //--- РЕЖИМ LIVE: Загрузка из API if(!LoadFromCalendarAPI()) { int error = GetLastError(); Print("❌ ERROR: Failed to load calendar from API - ", error); return INIT_FAILED; } is_live_mode = true; Print("⚠️ Режим: LIVE (данные из API)"); } return INIT_SUCCEEDED; }
Пояснения по приведенному коду:
Основная проверка делается в обработчике события инициализации эксперта OnInit():
//--- определяем среду исполнения bool is_tester = MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_OPTIMIZATION) || MQLInfoInteger(MQL_VISUAL_MODE); if(is_tester) { //--- РЕЖИМ ТЕСТЕРА: Загрузка из ресурса if(!LoadFromResource()) { ... } } else { //--- РЕЖИМ LIVE: Загрузка из API if(!LoadFromCalendarAPI()) { ... } }
Пояснения по приведенному коду:
Определяем в каком именно режиме находится выполняющаяся mql5-программа — в данном случае, наш эксперт. Нас интересуют флаги:
- MQL_TESTER — признак работы запущенной программы в тестере;
- MQL_OPTIMIZATION — признак работы запущенной программы в процессе оптимизации;
- MQL_VISUAL_MODE — признак работы запущенной программы в визуальном режиме тестирования.
14:42:18.894 🔄 Инициализация новостного модуля:
14:42:18.905 ✅ Events from Server Loaded: 455 events for: USD
14:42:18.914 ✅ Events from Server Loaded: 60 events for: EUR
14:42:18.914 ⚠️ Режим: LIVE (данные из API)
Валидация цепочки преобразований данных: События → Бинарный файл → Компиляция в ресурс → Получение событий из ресурса
Осталось проверить получение событий в тестере и убедиться, что они полностью совпадают со списком, полученным в реальном времени. Это подтвердит возможность тестирования и оптимизации новостных торговых алгоритмов в автоматическом режиме.
Для этого добавим вывод полученных из сервера/загруженных из ресурса событий в журнал терминала/тестера стратегий. Полный текстовый файл «ImportTesterLog.txt» с результатами прогона в тестере приложен к статье. Ниже приведен фрагмент лог-файла — начало и конец массива событий по каждому коду валюты.
Результат одиночного прогона в тестере стратегий, в визуальном режиме:
16:48:22.527 InpCurrencies=USD,EUR
16:48:22.527 InpDateFrom=1735689600
16:48:22.527 InpDateTo=1767225600
16:48:22.527 InpEventCodes=
16:48:22.527 InpMinImportance=3
16:48:22.546 🔄 Инициализация новостного модуля:
16:48:22.546 ✅ Events from Resource Loaded: 455 events for: USD
16:48:22.546 ✅ Событие #0
16:48:22.546 ID: 230215
16:48:22.546 ID события: 840140001
16:48:22.546 Время: 2025.01.02 16:30:00
16:48:22.546 Влияние: CALENDAR_IMPACT_POSITIVE
16:48:22.546 Ревизия: 0
16:48:22.546 Факт: 211.0
16:48:22.546 Пересмотрено: 220.0
16:48:22.546 Прогноз: 219.0
16:48:22.546 Предыдущее: 219.0
16:48:22.546 ✅ Событие #1
16:48:22.546 ID: 230641
16:48:22.546 ID события: 840500001
16:48:22.546 Время: 2025.01.02 17:45:00
16:48:22.546 Влияние: CALENDAR_IMPACT_NEGATIVE
16:48:22.546 Ревизия: 3
16:48:22.546 Факт: 49.4
16:48:22.546 Прогноз: 50.5
16:48:22.546 Предыдущее: 49.7
...
16:48:22.553 ✅ Событие #453
16:48:22.553 ID: 274419
16:48:22.553 ID события: 840510001
16:48:22.553 Время: 2025.12.30 17:45:00
16:48:22.553 Влияние: CALENDAR_IMPACT_POSITIVE
16:48:22.553 Ревизия: 0
16:48:22.553 Факт: 43.5
16:48:22.553 Прогноз: 42.4
16:48:22.553 Предыдущее: 36.3
16:48:22.553 ✅ Событие #454
16:48:22.553 ID: 274484
16:48:22.553 ID события: 840140001
16:48:22.553 Время: 2025.12.31 16:30:00
16:48:22.553 Влияние: CALENDAR_IMPACT_POSITIVE
16:48:22.553 Ревизия: 0
16:48:22.553 Факт: 199.0
16:48:22.553 Пересмотрено: 215.0
16:48:22.553 Прогноз: 227.0
16:48:22.553 Предыдущее: 214.0
...
16:48:22.553 ✅ Events from Resource Loaded: 60 events for: EUR
16:48:22.553 ✅ Событие #0
16:48:22.553 ID: 231322
16:48:22.553 ID события: 999030013
16:48:22.553 Время: 2025.01.07 13:00:00
16:48:22.553 Влияние: CALENDAR_IMPACT_NA
16:48:22.553 Ревизия: 1
16:48:22.553 Факт: 2.4
16:48:22.553 Прогноз: 2.4
16:48:22.553 Предыдущее: 2.2
16:48:22.553 ✅ Событие #1
16:48:22.553 ID: 187384
16:48:22.553 ID события: 999040007
16:48:22.553 Время: 2025.01.08 13:00:00
16:48:22.553 Влияние: CALENDAR_IMPACT_POSITIVE
16:48:22.553 Ревизия: 0
16:48:22.553 Факт: 21.0
16:48:22.553 Пересмотрено: 17.8
16:48:22.553 Прогноз: 15.7
16:48:22.553 Предыдущее: 17.7
...
16:48:22.555 ✅ Событие #58
16:48:22.555 ID: 204746
16:48:22.555 ID события: 999010006
16:48:22.555 Время: 2025.12.18 16:15:00
16:48:22.555 Влияние: CALENDAR_IMPACT_NA
16:48:22.555 Ревизия: 0
16:48:22.555 Факт: 2.0
16:48:22.555 Предыдущее: 2.0
16:48:22.555 ✅ Событие #59
16:48:22.555 ID: 204762
16:48:22.555 ID события: 999010007
16:48:22.555 Время: 2025.12.18 16:15:00
16:48:22.555 Влияние: CALENDAR_IMPACT_NA
16:48:22.555 Ревизия: 0
16:48:22.555 Факт: 2.15
16:48:22.555 Предыдущее: 2.15
16:48:22.555 ⚠️ Режим: TESTER (данные из ресурса)
Видно, что все массивы событий загрузились из ресурса примерно за 10 мс, что не удивительно, — ресурс «вшит» в код советника и загружается вместе с ним при старте тестирования.
Такой же прогон (старт) эксперта в Live-режиме в терминале MetaTrader 5 дает аналогичный лог-файл со списком загруженных событий. Полный текстовый файл «LoadLiveLog.txt» с результатами Live-загрузки приложен к статье. Ниже приведен фрагмент лог-файла — начало и конец массива событий по каждому коду валюты.
Результат Live-загрузки событий из сервера:
17:27:25.250 🔄 Инициализация новостного модуля:
17:27:25.258 ✅ Получено значений ПО ВАЛЮТЕ "USD": 3589 → Из них отфильтровано: 455
17:27:25.258 ✅ Events from Server Loaded: 455 events for: USD
17:27:25.258 ✅ Событие #0
17:27:25.258 ID: 230215
17:27:25.258 ID события: 840140001
17:27:25.258 Время: 2025.01.02 16:30:00
17:27:25.258 Влияние: CALENDAR_IMPACT_POSITIVE
17:27:25.258 Ревизия: 0
17:27:25.258 Факт: 211.0
17:27:25.258 Пересмотрено: 220.0
17:27:25.258 Прогноз: 219.0
17:27:25.258 Предыдущее: 219.0
17:27:25.258 ✅ Событие #1
17:27:25.258 ID: 230641
17:27:25.258 ID события: 840500001
17:27:25.258 Время: 2025.01.02 17:45:00
17:27:25.258 Влияние: CALENDAR_IMPACT_NEGATIVE
17:27:25.258 Ревизия: 3
17:27:25.258 Факт: 49.4
17:27:25.258 Прогноз: 50.5
17:27:25.258 Предыдущее: 49.7
...
17:27:25.288 ✅ Событие #453
17:27:25.288 ID: 274419
17:27:25.288 ID события: 840510001
17:27:25.288 Время: 2025.12.30 17:45:00
17:27:25.288 Влияние: CALENDAR_IMPACT_POSITIVE
17:27:25.288 Ревизия: 0
17:27:25.288 Факт: 43.5
17:27:25.288 Прогноз: 42.4
17:27:25.288 Предыдущее: 36.3
17:27:25.288 ✅ Событие #454
17:27:25.288 ID: 274484
17:27:25.288 ID события: 840140001
17:27:25.288 Время: 2025.12.31 16:30:00
17:27:25.288 Влияние: CALENDAR_IMPACT_POSITIVE
17:27:25.288 Ревизия: 0
17:27:25.288 Факт: 199.0
17:27:25.288 Пересмотрено: 215.0
17:27:25.288 Прогноз: 227.0
17:27:25.288 Предыдущее: 214.0
17:27:25.301 ✅ Получено значений ПО ВАЛЮТЕ "EUR": 3116 → Из них отфильтровано: 0
17:27:25.301 ✅ Events from Server Loaded: 60 events for: EUR
17:27:25.301 ✅ Событие #0
17:27:25.301 ID: 231322
17:27:25.301 ID события: 999030013
17:27:25.301 Время: 2025.01.07 13:00:00
17:27:25.301 Влияние: CALENDAR_IMPACT_NA
17:27:25.301 Ревизия: 1
17:27:25.301 Факт: 2.4
17:27:25.301 Прогноз: 2.4
17:27:25.301 Предыдущее: 2.2
17:27:25.301 ✅ Событие #1
17:27:25.301 ID: 187384
17:27:25.301 ID события: 999040007
17:27:25.301 Время: 2025.01.08 13:00:00
17:27:25.301 Влияние: CALENDAR_IMPACT_POSITIVE
17:27:25.301 Ревизия: 0
17:27:25.301 Факт: 21.0
17:27:25.301 Пересмотрено:17.8
17:27:25.301 Прогноз: 15.7
17:27:25.301 Предыдущее: 17.7
...
17:27:25.303 ✅ Событие #58
17:27:25.303 ID: 204746
17:27:25.303 ID события: 999010006
17:27:25.303 Время: 2025.12.18 16:15:00
17:27:25.303 Влияние: CALENDAR_IMPACT_NA
17:27:25.303 Ревизия: 0
17:27:25.303 Факт: 2.0
17:27:25.303 Предыдущее: 2.0
17:27:25.303 ✅ Событие #59
17:27:25.303 ID: 204762
17:27:25.303 ID события: 999010007
17:27:25.303 Время: 2025.12.18 16:15:00
17:27:25.303 Влияние: CALENDAR_IMPACT_NA
17:27:25.303 Ревизия: 0
17:27:25.303 Факт: 2.15
17:27:25.303 Предыдущее: 2.15
17:27:25.303 ⚠️ Режим: LIVE (данные из API)
Полная загрузка с фильтрацией и выводом в лог занимает около 50 мс — тоже достаточно быстро, чтобы не обращать внимания на время загрузки/фильтрации событий. Сравнивая два лог-файла, видим, что список событий совпадает полностью и по количеству событий и по значению полей. Валидация цепочки преобразования данных пройдена успешно.
Разбор типичных ошибок и ложных ожиданий
Разберём шесть наиболее распространённых ошибок, которые совершают разработчики при интеграции экономического календаря MQL5. Каждая из них проверена на реальном опыте и может стоить кому-то потерянного депозита.
1.«Календарь предсказывает движение»
Ложное ожидание:
Если выходит новость с важностью HIGH и факт сильно отличается от прогноза — цена гарантированно пойдёт в сторону отклонения. Достаточно купить при actual > forecast для валюты базы.
Реальность:
Экономический календарь — это источник данных, а не генератор торговых сигналов. Он сообщает что произошло, но не как рынок отреагирует. Почему цена может пойти против «очевидной» логики?
- Рынок заложил ожидания в цену за дни до публикации. На самом выходе новости происходит «продажа на факте».
- Сильные данные по инфляции в период ужесточения политики ЦБ могут укрепить валюту, но в период смягчения — вызвать распродажу из-за страха перед перегревом.
- Новость может быть хорошей, но если предыдущее значение пересмотрено в худшую сторону, суммарный сигнал становится неоднозначным.
- Одновременный выход данных по нескольким валютам создаёт перекрёстное влияние, которое невозможно интерпретировать линейно.
Правильный подход — используйте календарь как фильтр волатильности, а не как триггер входа:
//--- вместо if(actual > forecast) OrderSend(...); //--- используйте: if(IsHighImpactNewsComingSoon(30)) { //--- уменьшаем размер позиции или временно приостанавливаем торговлю ReduceRiskExposure(); }
2.«Все HIGH-события одинаково важны»
Ложное ожидание:
Поле importance == CALENDAR_IMPORTANCE_HIGH означает, что событие гарантированно сдвинет рынок на 50+ пунктов. Можно торговать все такие новости одинаковым алгоритмом.
Реальность:
Конкретные значения в перечислении ENUM_CALENDAR_EVENT_IMPORTANCE — это субъективная оценка редакторов календаря, а не количественная метрика рыночного воздействия. Например, возьмём два события с меткой HIGH, но разной рыночной значимостью.
Первое событие — «Non-Farm Payrolls» (США) дает отклик по волатильности 80–150 пунктов, поскольку это ключевой индикатор занятости, влияет на политику ФРС. Второе событие — «Индекс деловой активности в строительстве» (Еврозона) дает отклик 10–30 пунктов, поскольку это узкосекторный индикатор, вторичен для ЕЦБ.
Правильный подход − дополните фильтрацию по важности списком приоритетных кодов событий:
//--- список событий, на которые действительно стоит реагировать bool IsGoodEvent(const string event_code) { static const string tier1_codes[] = { "NONFARM", "CPI", "GDP", "RATE", "FOMC", "ECB_RATE", "RETAIL_SALES", "UNEMPLOYMENT", "PMI_MANUFACTURING" }; for(int i = 0; i < ArraySize(tier1_codes); i++) if(StringFind(event_code, tier1_codes[i]) != -1) return true; return false; } //--- использование в фильтре if(event.importance == CALENDAR_IMPORTANCE_HIGH && IsGoodEvent(event.event_code)) { //--- обрабатываем только действительно значимые новости }
Рекомендация:
Ведите собственный рейтинг событий на основе исторического анализа волатильности — это надёжнее любой предустановленной метки.
3.«Использовали TimeLocal() вместо TimeTradeServer()»
Ложное ожидание:
Время события в структуре MqlCalendarValue::time указано в моём локальном часовом поясе (или в UTC), поэтому могу сравнивать его с TimeLocal() или TimeGMT().
Реальность:
Все функции экономического календаря возвращают время в часовом поясе торгового сервера (TimeTradeServer()). Это архитектурное решение, которое устраняет необходимость ручной конвертации, но требует дисциплины от азработчика.
Последствия этой ошибки серьёзны — если ваш сервер в часовом поясе EET (UTC+2), а вы используете TimeLocal() (например, MSK, UTC+3), вы будете проверять события со сдвигом в 1 час. Робот может пропустить новость или, наоборот, среагировать на неё постфактум.
Правильный подход — всегда используйте TimeTradeServer() для всех сравнений времени:
//--- НЕПРАВИЛЬНО — риск рассинхронизации datetime now = TimeLocal(); if(event.time - now < 1800) { ... } //--- ПРАВИЛЬНО — гарантированная синхронизация datetime now = TimeTradeServer(); if(event.time - now < 1800) { ... } //--- в тестере TimeTradeServer() возвращает модельное время, поэтому логика работает идентично live
Рекомендация:
Добавьте в журнал отладки вывод TimeToString(TimeTradeServer(), TIME_MINUTES) и сравнивайте с временем события — они должны совпадать без конвертации.
4.«Забыли, что поля событий могут быть пустыми»
Ложное ожидание:
Поля actual_value, forecast_value и другие всегда содержат корректные числовые значения. Можно смело делить их на 1 000 000 и сравнивать.
Реальность:
Согласно документации, числовые поля структуры MqlCalendarValue хранят значения, умноженные на миллион или константу LONG_MIN, если значение не задано. Что происходит при игнорировании проверки:
//--- ошибка double actual = values[i].actual_value / 1000000.0; // если actual_value == LONG_MIN, результат: -9223372036.854776 if(actual > forecast) // сравнение с "мусорным" числом вызовет ложный сигнал OpenBuy();
Правильный подход — используйте встроенные методы структуры MqlCalendarValue для безопасного получения значений:
//--- правильный подход - встроенные методы if(values[i].HasActualValue() && values[i].HasForecastValue()) { double actual = values[i].GetActualValue(); double forecast = values[i].GetForecastValue(); if(!MathIsNaN(actual) && !MathIsNaN(forecast)) { double deviation = actual - forecast; // ... логика анализа } } //+------------------------------------------------------------------+
Важно:
Функции GetActualValue(), GetForecastValue() и др. возвращают NaN при отсутствии данных. Всегда проверяйте результат через MathIsValidNumber() или MathIsNaN() перед использованием в расчётах.
5.«Вызов функций календарного API в OnTick()»
Ложное ожидание:
Чтобы всегда иметь актуальные данные, буду вызывать CalendarValueHistory() на каждом тике. Это гарантирует, что робот не пропустит свежую новость».
Реальность:
Функции календаря работают через удалённый сервер, к которому подключается MetaTrade 5, и имеют строгие лимиты частоты запросов. Вызов в OnTick() (который может срабатывать десятки раз в секунду) приведёт к проблемам:
- Ошибке 5204 (ERR_CALENDAR_TOO_MANY_REQUESTS) — временная блокировка доступа к календарю.
- Задержкам исполнения — сетевой запрос в торговом цикле увеличивает проскальзывание.
- Избыточному трафику — загрузка одних и тех же данных многократно.
//--- глобальные переменные MqlCalendarValue calendar_cache[]; bool cache_initialized = false; long last_change_id = 0; //+------------------------------------------------------------------+ int OnInit() { //--- первоначальная загрузка истории (один раз при старте) datetime from = TimeCurrent() - 7 * 86400; datetime to = TimeCurrent() + 30 * 86400; if(CalendarValueHistory(calendar_cache, from, to, "USD")) { cache_initialized = true; } //--- установка таймера для периодического обновления (не чаще 5-10 минут) EventSetTimer(300); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnTimer() { if(!cache_initialized) return; MqlCalendarValue updates[]; //--- запрашиваем только изменения с момента последнего обновления if(CalendarValueLast(last_change_id, updates, "USD") > 0) { if(ArraySize(updates) > 0) { //--- слияние новых данных с кэшем MergeUpdates(calendar_cache, updates); last_change_id = updates[ArraySize(updates) - 1].change_id; } } } //+------------------------------------------------------------------+ void OnTick() { //--- работа только с локальным кэшем — без сетевых задержек if(cache_initialized) ProcessNewsSignals(calendar_cache); }
Рекомендация:
OnTick() должен работать только с локальными данными. Сетевые запросы — только в OnInit(), OnTimer() или по событию пользователя.
6.«Забыли перекомпилировать эксперт для тестирования на новых событиях»
Ложное ожидание:
Я обновил файл ресурса «calendar_test_res.bin» в папке Files, теперь тестер автоматически увидит новые события. Перекомпиляция советника не нужна.
Реальность:
Директива #resource встраивает данные в исполняемый файл «.ex5» на этапе компиляции. Изменения во внешнем файле «.bin» не подхватываются динамически — советник продолжает использовать ту версию ресурса, которая была скомпилирована в момент сборки.
Симптомы ошибки:
- В журнал тестера не попадают события, добавленные в ресурс после последней компиляции.
- Стратегия «не видит» важные новости, хотя в файле они есть.
- Результаты тестирования расходятся с ожиданиями, хотя код логически верен.
Правильный подход — всегда перекомпилируйте советник после обновления ресурса:
//--- в начале файла советника #resource "\\Files\\USD_calendar_test_res.bin" as MqlCalendarValue USD_res_calendar_data[] //--- после обновления USD_calendar_test_res.bin: // 1. Сохраните изменения в файле ресурса. // 2. Нажмите F7 в MetaEditor (или Compile в меню). // 3. Убедитесь, что в журнале компиляции нет ошибок. // 4. Запустите тестирование заново.
Рекомендация:
Добавьте в журнал компиляции вывод версии ресурса (например, хэш или дату последнего обновления), чтобы визуально контролировать актуальность данных в «.ex5».
Замечание:
Ошибки, разобранные в этом разделе, возникают не из-за незнания синтаксиса MQL5, а из-за упрощённого представления о природе экономических данных (индикаторов). Тот, кто научится избегать этих шести ошибок, получит не просто работающий код, а устойчивую торговую систему, способную адаптироваться к изменениям рынка и требованиям экономических регуляторов. Именно такой подход отличает любителя от профессионала в алгоритмической торговле.
Заключение
Экономический календарь в терминале MetaTrader 5 — это мощный инструмент. Но как любой инструмент, он требует понимания контекста, дисциплины в использовании и учета ограничений платформы.
В статье показана конкретная схема реализации и использования новостного API для советника в MetaTrader 5. Статья даёт набор практических элементов, которые в совокупности превращают ручную новостную торговлю в воспроизводимый модуль:
- понимание API календаря (Event vs Value, структуры MqlCalendarEvent/MqlCalendarValue, серверное время);
- безопасные методы загрузки и обновления (CalendarValueHistory для первичной подгрузки, CalendarValueLast + change_id для инкрементальных обновлений);
- обработка типовых ошибок и соблюдение лимитов запросов;
- многоуровневая фильтрация по валюте, важности, коду события и временным окнам, чтобы снизить шум и оставить 3–5 значимых событий;
- механизм экспорта отфильтрованных данных в бинарный ресурс и автоматическое переключение источника данных «реальное время» ↔ «тестер стратегий», обеспечивающее идентичность поведения в реальном времени и в бэктесте.
Критерии готовности модуля: успешно загружаются события для заданного периода; инкрементальные обновления работают через change_id без превышения лимитов; в тестере EA читает ресурс и выдаёт те же решения, что и в Live при идентичных входных данных.
Рекомендуемые следующие шаги: реализовать экспортёр, зашить .bin в #resource, проверить OnInit/OnTimer логику обновления и прогнать контролируемые бэктесты (например, сценарий «не торговать за 30 минут / возобновить через 60 минут»).
Это позволит вам перейти от гипотез к проверяемой, масштабируемой новостной торговой системе. Разработка новостных советников — это не просто программирование, это построение моста между макроэкономической теорией и рыночной практикой.
Рекомендуемые материалы для углублённого изучения функций API календаря в MetaTrader 5 и их использования в новостной торговле:
- Документация — Функции Экономического Календаря;
- Статья «Трейдинг с экономическим календарем MQL5 — Часть 1: Освоение функций экономического календаря MQL5»;
- Статья «Трейдинг с экономическим календарем MQL5 — Часть 7: Подготовка к тестированию стратегий с анализом новостей»;
- Статья «Трейдинг с экономическим календарем MQL5 — Часть 8: Оптимизируем тестирование новостных стратегий с помощью фильтров и логов»;
- Статья «Рецепты MQL5 – Экономический календарь».
Список файлов, приложенных к статье:
| Название файла | Описание |
|---|---|
| CalendarEventMonitor-EA.mq5 | Файл, содержащий код тестового скрипта для проверки функции обновления данных календаря |
| ExportCalendarForTester-S.mq5 | Файл, содержащий код тестового скрипта для проверки экспорта событий с заданными фильтрами в бинарный файл |
| GetTodayEvents-S.mq5 | Файл, содержащий код тестового скрипта для проверки получения событий за текущие сутки |
| ImportCalendarValidation-EA.mq5 | Файл, содержащий код тестового эксперта для проверки получения новостей в тестере стратегий из ресурса |
| ImportTesterLog.txt | Файл, содержащий результаты одиночного прогона эксперта «ImportCalendarValidation-EA.mq5» в тестере в визуальном режиме |
| LoadLiveLog.txt | Файл, содержащий результаты Live-загрузки событий в эксперте «ImportCalendarValidation-EA.mq5» |
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Греки опционов по Блэку — Шоулзу: Гамма и Дельта
Разработка торговой стратегии на основе псевдокорреляции Пирсона
Бимодальный Market Profile с дельтой и памятью в MQL5
Нейросети в трейдинге: Оценка риска по несогласованности представлений (ReGEN-TAD)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
