Опорные события индикаторов и советников: OnInit и OnDeinit

В интерактивных MQL-программах — индикаторах и экспертах — среда генерирует два события для подготовки к запуску (OnInit) и к остановке (OnDeinit). В скриптах и сервисах таких событий нет, потому что они не принимают асинхронных событий: после передачи управления их единственному обработчику события OnStart и вплоть до завершения работы, контекст выполнения потока скрипта/сервиса находится в коде MQL-программы. В отличие от этого, для индикаторов и экспертов нормальный ход работы предполагает, что среда будет многократно вызывать их специфические функции обработки событий (мы рассмотрим их в разделах, посвященных индикаторам и советникам), и каждый раз, предприняв необходимые действия, программы будут возвращать управление терминалу для холостого ожидания новых событий.

int OnInit()

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

Функция должна вернуть одно из значений перечисления ENUM_INIT_RETCODE.

Идентификатор

Описание

INIT_SUCCEEDED

Инициализация прошла успешно, выполнение программы можно продолжать; соответствует значению 0

INIT_FAILED

Неудачная инициализация, продолжать выполнение нельзя из-за неустранимых ошибок (например, не удалось создать файл или вспомогательный индикатор); значение 1

INIT_PARAMETERS_INCORRECT

Некорректный набор входных параметров, выполнение программы невозможно

INIT_AGENT_NOT_SUITABLE

Специфический код для работы в тестере: по каким-то причинам данный агент не подходит для проведения тестирования (например, недостаточно оперативной памяти, нет поддержки OpenCL и т.д.)

Если OnInit вернет любой ненулевой код возврата, это означает неудачную инициализацию и следом генерируется событие Deinit с кодом причины деинициализации REASON_INITFAILED (см. далее).

Функция OnInit может быть описана с типом результата void: в этом случае инициализация всегда считается успешной.

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

void OnDeinit(const int reason)

Функция OnDeinit (если она определена) вызывается при деинициализации эксперта или индикатора. Функция необязательна.

Параметр reason содержит код причины деинициализации. Возможные значения приведены в следующей таблице.

Константа

Значение

Описание

REASON_PROGRAM

0

Эксперт прекратил свою работу, вызвав функцию ExpertRemove

REASON_REMOVE

1

Программа удалена с графика

REASON_RECOMPILE

2

Программа перекомпилирована

REASON_CHARTCHANGE

3

Изменен символ или период графика

REASON_CHARTCLOSE

4

График закрыт

REASON_PARAMETERS

5

Изменены входные параметры

REASON_ACCOUNT

6

Активирован другой счет либо произошло переподключение к торговому серверу вследствие изменения настроек счета

REASON_TEMPLATE

7

Применен другой шаблон графика

REASON_INITFAILED

8

Обработчик OnInit вернул ненулевое значение

REASON_CLOSE

9

Терминал был закрыт

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

В файле AllInOne.mqh описан класс Finalizer, позволяющий "перехватить" код деинициализации в деструкторе через вызов UninitializeReason. То же самое значение мы должны получить и в обработчике OnDeinit.

class Finalizer
{
   static const Finalizer f;
public:
   ~Finalizer()
   {
      PRTF(EnumToString((ENUM_DEINIT_REASON)UninitializeReason()));
   }
};
 
static const Finalizer Finalizer::f;

Для удобства перевода кодов в строковое представление (названия причин) посредством EnumToString, в файле Uninit.mqh описано перечисление ENUM_DEINIT_REASON с константами из вышеприведенной таблицы. В журнале будут выводиться записи вида:

OnDeinit DEINIT_REASON_REMOVE
EnumToString((ENUM_DEINIT_REASON)UninitializeReason())=DEINIT_REASON_REMOVE / ok

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

При тестировании индикаторов в тестере разработчики MetaTrader 5 решили не генерировать событие OnDeinit. Их идея состоит в том, что индикатор может создавать некоторые графические объекты, которые он обычно удаляет в обработчике OnDeinit, однако пользователю желательно их видеть после завершения теста. На самом деле автор MQL-программы может при желании и сам обеспечить аналогичное поведение, оставляя объекты при положительной проверке режима MQLInfoInteger(MQL_TESTER). Это тем более странно, поскольку обработчик OnDeinit вызывается после теста эксперта, а эксперт может точно также удалять объекты в OnDeinit. Сейчас же исключительно для индикаторов получается, что обеспечить штатное поведение самого обработчика OnDeinit в тестере нельзя. Более того, не выполняется и прочая финализация, например, не вызываются деструкторы глобальных объектов.

Таким образом, если вам требуется выполнить после тестового прогона подсчет статистики, сохранение файла или иное действие, изначально предусмотренное для OnDeinit индикатора, придется отказаться от теста самого индикатора и перенести алгоритмы в эксперт.