- Основные понятия календаря
- Получение списка и описаний доступных стран
- Запрос видов событий по странам и валютам
- Получение описания вида события по идентификатору
- Получение записей о событиях по странам или валютам
- Получение записей о событиях конкретного вида
- Чтение записи о событии по идентификатору
- Отслеживание изменений событий по стране или валюте
- Отслеживание изменений событий по типу
- Фильтрация событий по множеству условий
- Перенос базы календаря в тестер
- Торговля по календарю
Фильтрация событий по множеству условий
Как мы знаем из предыдущих разделов этой главы, MQL5 API позволяет запрашивать события календаря по нескольким условиям:
- странам (CalendarValueHistory, CalendarValueLast);
- валютам (CalendarValueHistory, CalendarValueLast);
- идентификаторам видов событий (CalendarValueHistoryByEvent, CalendarValueLastByEvent);
- временному диапазону (CalendarValueHistory, CalendarValueHistoryByEvent);
- изменениям с момента предыдущего опроса календаря (CalendarValueLast, CalendarValueLastByEvent);
- конкретную новость по идентификатору (CalendarValueById).
Это можно обобщить в виде следующей таблицы функций (из всех CalendarValue-функций здесь отсутствует только CalendarValueById для получения одного конкретного значения).
Условия |
Временной диапазон |
Последние изменения |
---|---|---|
Страны |
CalendarValueHistory |
CalendarValueLast |
Валюты |
CalendarValueHistory |
CalendarValueLast |
События |
CalendarValueHistoryByEvent |
CalendarValueLastByEvent |
Подобный инструментарий покрывает основные, но далеко не все востребованные сценарии анализа календаря. Поэтому на практике часто требуется реализовать в MQL5 собственные механизмы фильтрации, включающие, в частности, запросы событий по:
- нескольким странам;
- нескольким валютам;
- нескольким видам событий;
- значениями произвольных свойств событий (важность, сектор экономики, отчетный период, тип, наличие прогноза, оценочное влияние на курс, подстрока в названии события, и т.д.).
Для решения данных задач создан класс CalendarFilter (CalendarFilter.mqh).
В силу специфики встроенных функций API, часть новостных атрибутов сделана более приоритетной, чем остальные. Сюда относятся страна, валюта и диапазон дат. Их можно указать в конструкторе класса, и тогда соответствующее свойство нельзя затем динамически менять в условиях фильтрации.
Это вызвано тем, что класс фильтра будет впоследствии расширяться возможностями кэширования новостей для их чтения из тестера, и начальные условия конструктора фактически определяют контекст кэширования, в пределах которого и возможна последующая фильтрации. Например, если мы при создании объекта укажем код страны "EU", то очевидно, бессмысленно запрашивать через него новости по США или Бразилии. Аналогично с диапазоном дат: его указание в конструкторе сделает невозможным получение новостей вне диапазона.
Вместе с тем, никто не мешает создать объект без начальных условий (т.к. все параметры конструктора являются опциональными), и тогда он будет способен кэшировать и фильтровать новости по всей базе календаря (по состоянию на момент сохранения).
Кроме того, поскольку страны и валюты имеют сейчас почти однозначное отображение (за исключением Европейского союза и EUR), их передача в конструктор осуществляется через единственный параметр context: если в нем указать строку длиной 2 символа — подразумевается код страны (или объединения стран), а если длина равна 3-м символам — подразумевается код валюты. Для кодов "EU" и "EUR", еврозона является подмножеством "EU" (в рамках стран с официальными договорами). В особых случаях, когда интерес представляют страны Евросоюза вне зоны евро, их также можно описать контекстом "EU". При необходимости, более узкие условия на новости по валютам этих стран (BGN, HUF, DKK, ISK, PLN, RON, HRK, CZK, SEK) можно добавить в фильтр динамически, с помощью методов, которые мы представим позднее. Однако ввиду экзотичности, нет гарантий, что такие новости попадут в календарь.
Приступим к изучению класса.
class CalendarFilter
|
Под страны и валюты выделены два массива country и currency. Если они не заполнены из context-а во время создания объекта, то затем MQL-программа получит возможность добавить условия на несколько стран или валют, чтобы осуществить по ним комбинированный запрос новостей.
Для хранения условий на все прочие атрибуты новостей в объекте CalendarFilter описан массив selectors, с размером 3 по второму измерению. Можно сказать, что это некая таблица, в которой каждая строка имеет 3 колонки.
long selectors[][3]; // [0] - свойство, [1] - значение, [2] - условие |
По 0-му индексу будут располагаться идентификаторы свойств новостей. Поскольку атрибуты разнесены по трем таблицам базы (MqlCalendarCountry, MqlCalendarEvent, MqlCalendarValue) они описаны с помощью элементов обобщенного перечисления ENUM_CALENDAR_PROPERTY (CalendarDefines.mqh).
enum ENUM_CALENDAR_PROPERTY
|
По индексу 1 будут храниться значения, для сравнения с ними в условиях отбора новостных записей. Например, если потребуется установить фильтр по сектору экономики, то в selectors[i][0] запишем CALENDAR_PROPERTY_EVENT_SECTOR, а в selectors[i][1] — одно из значений стандартного перечисления ENUM_CALENDAR_EVENT_SECTOR.
Наконец, последняя колонка (под 2-м индексом) зарезервирована для операции сравнения значения селектора со значением атрибута в новости: все поддерживаемые операции сведены в перечисление IS. Напомним его.
enum IS
|
Похожий подход нам уже встречался в TradeFilter.mqh. Таким образом, мы сможем компоновать условия не только на равенство значений, но и на неравенство или больше/меньше. Например, легко представить фильтр на поле CALENDAR_PROPERTY_EVENT_IMPORTANCE, которое должно быть больше (GREATER), чем CALENDAR_IMPORTANCE_LOW (это — элемент стандартного перечисления ENUM_CALENDAR_EVENT_IMPORTANCE), что означает выборку новостей средней и высокой важности.
Следующим перечислением, определенным специально для календаря, является ENUM_CALENDAR_SCOPE. Поскольку фильтрация календаря часто связана с отрезками времени, здесь перечислены наиболее востребованные из них.
#define DAY_LONG (60 * 60 * 24)
|
Все перечисления вынесены в отдельный заголовочный файл CalendarDefines.mqh.
Но вернемся к классу CalendarFilter. Тип массива selectors в нем — long, что подходит для хранения значений почти всех задействованных типов: перечислений, даты и времени, идентификаторов, целых чисел и даже значений экономический показателей, потому что они хранятся в календаре в виде long-чисел (в миллионных долях от вещественных величин). Однако что делать со строковыми свойствами?
Данная проблема решена за счет массива строк stringCache, в который будут добавляться все строки, упомянутые в условиях фильтров.
class CalendarFilter
|
Тогда вместо значения строки в selectors[i][1] легко сохранить индекс элемента в массиве stringCache. Сейчас мы покажем это в деталях.
Для заполнения массива selectors условиями фильтров предусмотрено несколько let-методов, в частности, для перечислений:
class CalendarFilter
|
Для фактических значений показателей:
// здесь обрабатываются поля:
|
И для строк:
// здесь условия на все строковые свойства (с сокращениями)
|
В перегрузке метода для строк обратите внимание, что строки длиной 2 и 3 символа (если они без шаблонной звездочки '*', которая является заменой для произвольной последовательности знаков) попадают в массивы стран и символов соответственно, а все остальные строки — трактуются как фрагменты названия или источника новостей, и оба эти поля задействуют stringCache и selectors.
Особым образом в классе поддержана и фильтрация по виду (идентификатору) событий.
protected:
|
Таким образом, в число приоритетных фильтров (которые обрабатываются вне массива selectors), входят не только страны, валюты, диапазон дат, но и идентификаторы видов событий. Такое конструктивное решение вызвано тем, что именно эти параметры могут передаваться в те или иные API-функции календаря как входные. Все остальные атрибуты новостей мы получаем как выходные значения полей в массивах структур (MqlCalendarValue, MqlCalendarEvent, MqlCalendarCountry). Именно по ним мы будет выполнять дополнительную фильтрацию, согласно правилами в массиве selectors.
Все let-методы возвращают указатель на объект, что позволяет нанизывать их вызовы в цепочку. Например, так:
CalendarFilter f;
|
Условия по странам и валютам можно, в принципе, комбинировать, но следует иметь в виду, что несколько значений можно задать только либо для стран, либо для валют, но не для того и другого. Один из этих двух аспектов контекста (любой из двух) в текущей реализации поддерживает только одно или ни одного значения (т.е. отсутствие фильтра по нему). Например, при выбранной валюте "EUR" можно сузить контекст поиска новостей только по Германии и Франции (коды стран "DE" и "FR") — в результате будут отброшены новости ЕЦБ и евростата, а также, в частности, по Италии и Испании. Однако указание "EUR" в данном случае является избыточным, так как в Германии и Франции нет других валют.
Поскольку класс использует встроенные функции, в которых параметры country и currency применяются к новостям с помощью операции логического И, проверяйте непротиворечивость условий фильтрации.
После того как вызывающий код настроит условия фильтрации, следует на их основе произвести выборку новостей. Этим занимается публичный метод select (приводится с упрощениями).
public:
|
В зависимости от того, какие из массивов с приоритетными атрибутами заполнены, метод вызывает разные функции API для опроса календаря:
- если заполнен массив ids, в цикле для всех идентификаторов вызывается CalendarValueHistoryByEvent;
- если заполнен массив country, и он больше, чем массив валют, выполняется цикл по странам с вызовом CalendarValueHistory;
- если заполнен массив currency, и он больше или равен размеру массива стран, выполняется цикл по валютам с вызовом CalendarValueHistory;
Каждый вызов функции заполняет временный массив структур MqlCalendarValue temp[], который последовательно аккумулируется в массиве-параметре result. После записи в него всех подходящих новостей по основным условиям (даты, страны, валюты, идентификаторы), если они есть, в дело вступает вспомогательный метод filter, который прореживает массив на основе условий в selectors. В завершении метода select производится сортировка новостей в хронологическом порядке, который может быть нарушен из-за объединения результатов множественных запросов "календарных" функций. Для сортировки используется макрос SORT_STRUCT, рассмотренный в разделе Сравнение, сортировка и поиск в массивах.
Метод filter вызывает для каждого элемента массива новостей рабочий метод match, возвращающий логический признак того, подходит ли новость под условия фильтра. Если нет, элемент удаляется из массива.
protected:
|
Наконец, метод match анализирует наш массив selectors и сравнивает его с полями переданной структуры MqlCalendarValue. Здесь код приводится с сокращениями.
bool match(const MqlCalendarValue &v)
|
Методы equal и greater перенесены почти без изменений из наших предыдущих наработок с классами-фильтрами.
На этом задача фильтрации, в целом, решена, то есть MQL-программа может использовать объект CalendarFilter следующим образом:
CalendarFilter f;
|
Но на самом деле метод select умеет еще кое-что важное, что мы оставили для факультатива.
Во-первых, в получаемом списке новостей желательно тем или иным образом вставить разделитель (delimiter) между прошлым и будущим, чтобы за него мог зацепиться глаз. В принципе, эта возможность является крайне важной для календарей, но по каким-то причинам отсутствует в пользовательском интерфейсе MetaTrader 5 и на сайте mql5.com. Наша реализация умеет вставлять между прошлым и будущим пустую структуру, которую лишь остается наглядно отобразить (чем мы займемся чуть ниже).
Во-вторых, размер результирующего массива может оказаться довольно большим (особенно на первых этапах подбора настроек), и потому метод select дополнительно предоставляет возможность ограничить размер массива (limit). Это делается за счет удаления элементов наиболее далеко отстоящих от текущего времени.
Таким образом, полный прототип метода выглядит так:
bool select(MqlCalendarValue &result[],
|
По умолчанию разделитель не вставляется, и массив не усекается.
Парой абзацев выше была вскользь упомянута дополнительная подзадача фильтрации — визуализация полученного массива. В классе CalendarFilter имеется специальный метод format, который превращает передаваемый массив структур MqlCalendarValue &data[] в массив удобочитаемых строк string &result[]. С телом метода можно ознакомиться в прилагаемом файле CalendarFilter.mqh.
bool format(const MqlCalendarValue &data[],
|
Какие именно поля из MqlCalendarValue мы хотим отобразить, задается в массиве props. Напомним, что в перечислении ENUM_CALENDAR_PROPERTY имеются поля из всех трех зависимых структур календаря, так что MQL-программа может автоматически выводить не только экономические показатели из конкретной записи о событии, но и его название, характеристики, код страны или валюты — это все берет на себя метод format.
Каждая строка в выходном массиве result содержит текстовое представление значения одного из полей (число, описание, элемент перечисления). Размер массива result равен произведению количества структур на входе (в data) на количество отображаемых полей (в props). Опциональный параметр header позволяет добавить в начало выходного массива ряд с названиями полей (колонок). Параметр padding управляет генерацией дополнительных пробелов в тексте, чтобы таблицу было удобно выводить моноширинным шрифтом (например, в журнал).
В завершение описания класса CalendarFilter упомянем еще один важный публичный метод — update.
bool update(MqlCalendarValue &result[]); |
Он по своей структуре почти полностью повторяет select, но вместо вызовов функций CalendarValueHistoryByEvent и CalendarValueHistory использует CalendarValueLastByEvent и CalendarValueLast. Назначение метода очевидно: он запрашивает календарь на наличие свежих изменений, соответствующих условиям фильтрации. Но для своей работы он требует идентификатор изменений. И такое поле действительно определено в классе: в первый раз оно заполняется внутри метода select.
class CalendarFilter
|
Кое-какие нюансы класса CalendarFilter все еще "остались за кадром", но к некоторым из них мы обратимся в следующих разделах.
Давайте проверим фильтр в деле: сначала в простом скрипте CalendarFilterPrint.mq5, а затем в более практичном индикаторе CalendarMonitor.mq5.
Во входных параметрах скрипта можно задать контекст (код страны или валюту), временной диапазон, строку для полнотекстного поиска по названиям событий и ограничить размер результирующей таблицы с новостями.
input string Context; // Context (страна - 2 знака, currency - 3 знака, пусто - без фильтра)
|
С учетом параметров создается глобальный объект фильтра.
CalendarFilter f(Context, TimeCurrent() - Scope, TimeCurrent() + Scope); |
Затем в OnStart настраивается пара дополнительных постоянных условий (средняя и высокая важность событий), наличие прогноза (поле не равно LONG_MIN), а также в объект передается поисковая строка.
void OnStart()
|
Далее вызывается метод select и полученный массив структур MqlCalendarValue форматируется в таблицу с 9-ю колонками методом format.
MqlCalendarValue records[];
|
Ячейки таблицы "склеиваются" в ряды и выводятся в журнал.
С настройками по умолчанию (то есть, по всем странам и валютам, с фрагментом "farm" в названии событий средней и высокой важности) можно получить примерно такое расписание.
Selecting calendar records... country[i]= / ok calendarValueHistory(temp,from,to,country[i],c)=2372 / ok Filtering 2372 records Got 9 records TIME | CUR⁞ | NAME | IMPORTAN⁞ | ACTU⁞ | FORE⁞ | PREV⁞ | IMPACT | SECT⁞ 2022.06.02 15:15 | USD | ADP Nonfarm Employment Change | HIGH | +128 | -225 | +202 | POSITIVE | JOBS 2022.06.02 15:30 | USD | Nonfarm Productivity q/q | MODERATE | -7.3 | -7.5 | -7.5 | POSITIVE | JOBS 2022.06.03 15:30 | USD | Nonfarm Payrolls | HIGH | +390 | -19 | +436 | POSITIVE | JOBS 2022.06.03 15:30 | USD | Private Nonfarm Payrolls | MODERATE | +333 | +8 | +405 | POSITIVE | JOBS 2022.06.09 08:30 | EUR | Nonfarm Payrolls q/q | MODERATE | +0.3 | +0.3 | +0.3 | NA | JOBS — | — | — | — | — | — | — | — | — 2022.07.07 15:15 | USD | ADP Nonfarm Employment Change | HIGH | +nan | -263 | +128 | NA | JOBS 2022.07.08 15:30 | USD | Nonfarm Payrolls | HIGH | +nan | -229 | +390 | NA | JOBS 2022.07.08 15:30 | USD | Private Nonfarm Payrolls | MODERATE | +nan | +51 | +333 | NA | JOBS
|
Теперь займемся индикатором CalendarMonitor.mq5. Его назначение: отображать пользователю на графике текущую подборку событий в соответствии с заданными фильтрами. Для визуализации таблицы будет использоваться уже знакомый нам класс табло (Tableau.mqh, см. раздел Расчет залога для будущего ордера). Буферов и диаграмм индикатор не имеет.
Входные параметры позволяют задать диапазон временного окна (Scope), а также глобальный контекст для объекта CalendarFilter — либо кодом валюты или страны в строке Context (по умолчанию пусто, т.е. без ограничений), либо с помощью логического флага UseChartCurrencies. Он по умолчанию включен, и именно им рекомендуется пользоваться, чтобы автоматически получать новости тех валют, которые составляют рабочий инструмент графика.
input string Context; // Context (country - 2 chars, currency - 3 chars, empty - all)
|
Дополнительные фильтры можно наложить на тип события, сектор и важность.
input ENUM_CALENDAR_EVENT_TYPE_EXT Type = TYPE_ANY;
|
Важность задает нижнюю границу отбора, а не точное соответствие. Таким образом, установленное по умолчанию значение IMPORTANCE_MODERATE захватит не только умеренную, но и высокую важность.
Внимательные читатель обратит внимание, что здесь использованы неизвестные перечисления: ENUM_CALENDAR_EVENT_TYPE_EXT, ENUM_CALENDAR_EVENT_SECTOR_EXT, ENUM_CALENDAR_EVENT_IMPORTANCE_EXT. Они находятся в уже упоминавшемся файле CalendarDefines.mqh и почти один в один совпадают с аналогичными встроенными перечислениями. Единственное отличие заключается в том, что в них добавлен элемент, означающий "любое" значение. Необходимость в описании подобных перечислений возникла для упрощения ввода условий: сейчас фильтр для каждого поля настраивается с помощью выпадающего списка, где можно выбрать как одно из значений, так и отключить фильтр. Если бы не добавленный элемент перечисления, пришлось бы вводить в интерфейс для каждого поля логический флаг "включено/выключено".
Кроме того входные параметры позволяют запрашивать события по наличию в них актуальных, прогнозных и предыдущих показателей, а также с поиском строки текста (Text).
input string Text;
|
Объекты CalendarFilter и Tableau описаны на глобальном уровне.
CalendarFilter f(Context);
|
Обратите внимание, что фильтр создается единожды, а таблица представлена автоуказателем и будет пересоздаваться динамически в зависимости от размера полученных данных.
Настройки фильтра делаются в OnInit последовательными вызовами let-методов согласно входным параметрам.
int OnInit()
|
В конце запускается секундный таймер. Вся работа выполняется в OnTimer.
void OnTimer()
|
Если запустить индикатор на графике EURUSD с настройками по умолчанию можем получить следующую картину.
Отфильтрованный и отформатированный набор новостей на графике