Конструирование MQL-программ различных типов

Тип программы является фундаментальным свойством в MQL5. В отличие от C++ или других общецелевых языков программирования, где любую программу можно развивать в произвольных направлениях, например, добавляя графический интерфейс или подкачку данных с сервера по сети, в MQL5 существует разделение программ на группы по их предназначению. Так, технический анализ с визуализацией временных рядов "поручен" индикаторам, но они не способны торговать. В свою очередь, экспертам доступны функции торгового API, но в них отсутствуют индикаторные буфера (массивы для построения линий).

Поэтому при решении конкретной прикладной задачи разработчик должен, как правило, разложить её на части, функционал каждой из которых укладывается в специализацию отдельного типа. Разумеется, в простых случаях бывает достаточно единственной MQL-программы, но иногда поиск оптимального технического решения неочевиден. Например, для построения "графика" ренко, стоит ли реализовать его как индикатор, как пользовательский символ, генерируемый сервисом, или может быть выполнять расчет "кирпичей" непосредственно в торговом эксперте (если имеется торговая стратегия на ренко)? Все варианты осуществимы.

Тип MQL-программы характеризуется несколькими факторами.

Во-первых, под каждый тип программ выделена собственная папка в рабочем каталоге MQL5. Во введении в первую Часть мы уже упоминали этот факт и перечисляли папки. Так для индикаторов, экспертов, скриптов и сервисов предназначены, соответственно, папки Indicators, Experts, Scripts, и Services. Для библиотек в папке MQL5 зарезервирована подпапка Libraries. В каждой из них можно организовать дерево вложенных папок произвольной конфигурации.

Напомним, что бинарный файл (готовая программа с расширением ex5) — результат компиляции mq5-файла — генерируется в том же каталоге, где лежит исходный mq5-файл. Однако следует упомянуть о существовании в MetaEditor проектов (файлов с расширением mqproj) — их мы разберем в главе Проекты. При разработке проекта готовый продукт создается в каталоге рядом с проектом. При создании программы из Мастера MQL5 в редакторе MetaEditor (команда Файл -> Создать) исходный файл по умолчанию размещается в папке, соответствующей типу программы. Если вы случайно скопируете программу не в тот каталог, ничего страшного не произойдет: она не превратится, например, из эксперта в индикатор или обратно. Её можно перенести в нужное место непосредственно в редакторе в окне Навигатор или во внешнем файловом менеджере. В Навигаторе каждый тип программы отображается отличительным значком.

Размещение внутри каталога MQL5 во вложенной папке, выделенной под конкретный тип программ, не является фактором, определяющим тип конкретной MQL-программы. Тип определяется на основе содержимого исполняемого файла, которое, в свою очередь, формируется компилятором из директив-свойств и инструкций в исходном коде.
 
Иерархия папок по типам программ введена для удобства. Рекомендуется её придерживаться за исключением случаев, когда речь идет о группе связанных проектов (с программами разных типов), которые логичнее хранить в отдельном каталоге.

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

Например, мы уже видели, что в скриптах и сервисах работа запускается в функции OnStart, и поскольку она там единственная, её можно назвать главной "точкой входа", через которую терминал передает управления прикладном коду. В других типах программ ситуация несколько сложнее. В целом отметим, что тип программы характеризуется неким набором обработчиков, часть из которых может быть обязательной, а часть опциональной (но вместе с тем, недопустимой для других типов программ). В частности, в индикаторе требуется функция OnCalculate (без неё индикатор не скомпилируется, компилятор выдаст ошибку). Однако в эксперте эта функция не используется.

В-третьих, некоторые типы программ требуют особых директив #property. В разделе Общие свойства программ мы уже познакомились с директивами, которые могут использоваться во всех типах программ. Однако есть и другие, специализированные директивы. Например, в примерах с сервисами нам встречалась директива #property service, которая, собственно, и делает программу сервисом. Без неё даже размещение программы в папке Services не позволит запускать её в фоне.

Аналогичным образом определяющую роль в создании библиотек играет директива #property library. Все подобные директивы-свойства будут рассмотрены в разделах для соответствующих типов программ.

Сочетание директив и обработчиков событий учитывается в становлении типа MQL-программы следующим образом (обход сверху вниз до первого совпадения):

  • Индикатор — наличие обработчика OnCalculate;
  • Библиотека — наличие #property library;
  • Скрипт — наличие обработчика OnStart и отсутствие #property service;
  • Сервис — наличие обработчика OnStart и #property service;
  • Эксперт — наличие любого другого обработчика.

Пример того, какой эффект данные свойства оказывают на компилятор, будет приведен в разделе Обзор функций обработки событий.

Для всех вышеперечисленных пунктов следует учитывать еще один момент. Тип программы определяет главный компилируемый модуль — файл с расширением mq5, в который могут быть подключены с помощью директивы #include другие исходные тексты из других каталогов. Все включенные таким образом функции принимаются к рассмотрению наравне с теми, что присутствуют непосредственно в основном mq5-файле.
 
С другой стороны, директивы #property имеют эффект только при размещении в основном, компилируемом mq5-файле. Если директивы встретятся в файлах, включенных в программу с помощью #include, они будут проигнорированы.

Главный mq5-файл не обязан буквально содержать функции-обработчики событий. Вполне допустимо поместить часть или целиком весь алгоритм в некоторых заголовочных mqh-файлах, а затем включить их в одну или несколько программ. Например, мы можем реализовать обработчик OnStart с набором полезных действий в mqh-файле и с помощью #include использовать его внутри двух отдельных программ: скрипта и сервиса.

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

Включаемые файлы хоть и принято называть заголовочными и давать им расширение mqh, чисто технически это не обязательно. Вполне допустимо (хотя и не рекомендуется) включить в один mq5-файл другой mq5-файл или, например, txt-файл. В них может быть какой-то унаследованный код или, скажем, инициализация неких массивов константами. Для нас здесь важно, что включение другого mq5-файла не делает его главным.
 
При этом нужно следить, чтобы в программу попали только свойственные её типу функции обработки событий и среди них не было двойников (как известно, функции идентифицируются по совокупности имени и списку параметров: перегрузка функции разрешена только с различным набором параметров). Обычно это достигают при помощи различных директив препроцессора. Например, определив макрос #define OnStart OnStartPrevious перед включением стороннего mq5-файла скрипта в какую-то свою программу, мы фактически превратим описанную в нем функцию OnStart в OnStartPrevious, и сможем вызывать её как обычную из своих собственных обработчиков событий.
 
Однако такой подход имеет смысл применять лишь в исключительных случаях, когда исходный код включаемого mq5-файла по тем или иным причинам нельзя подвергнуть модификации, в частности, структурировать его с выделением интересующих алгоритмов в функции или классы в отдельных заголовочных файлах.

По принципу взаимодействия с пользователем MQL-программы можно разделить на интерактивные и утилитарные.

Интерактивные — индикаторы и эксперты — могут обрабатывать события, возникающие в программной среде в ответ на действия пользователя, такие как нажатия кнопок на клавиатуре, перемещение мыши, изменения размера окна, а также многие прочие события, например, связанные с получением котировочных данных или таймером.

Утилитарные — сервисы и скрипты — руководствуются только входными переменными, заданными в момент запуска, и не реагируют на события в системе.

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