Простой способ создания экземпляров индикаторов: iCustom

Для программного создания экземпляра индикатора MQL5 предоставляет две функции: iCustom и IndicatorCreate. Первая предполагает передачу списка параметров, который должен быть известен на момент компиляции программы. Вторая позволяет динамически формировать массив с параметрами вызываемого индикатора в процессе выполнения программы. Этот продвинутый режим будет рассмотрен в разделе Расширенный способ создания индикаторов: IndicatorCreate.

int iCustom(const string symbol, ENUM_TIMEFRAMES timeframe, const string pathname, ...)

Функция создает индикатор для указанного сочетания символа и таймфрейма. Значение NULL в параметре symbol может применяться для обозначения символа текущего графика, а значение 0 в параметре timeframe — для текущего периода.

В параметре pathname следует указать название индикатора (имя ex5-файла без расширения) и, опционально, путь. Подробнее про путь рассказано ниже.

Индикатор, на который ссылается pathname, должен быть откомпилирован.

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

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

Например, если индикатор принимает две входных переменных: период (input int WorkPeriod = 14) и тип цены (input ENUM_APPLIED_PRICE WorkPrice = PRICE_CLOSE), то можно вызвать для него iCustom разной степени конкретизации:

  • iCustom(_Symbol, _Period, 21, PRICE_TYPICAL) — задание значений для всего списка параметров;
  • iCustom(_Symbol, _Period, 21) — задание первого параметра, второй параметр опущен и получит значение PRICE_CLOSE;
  • iCustom(_Symbol, _Period) — оба параметры опущены и получат значения 14 и PRICE_CLOSE;

Опустить параметр в начале или в середине списка параметров нельзя.

Если создаваемый индикатор имеет краткую форму OnCalculate, то последним дополнительным параметром (сверх списка входных переменных, описанных внутри индикатора) можно передавать тип цены, по которой будет строиться индикатор. Это аналог выпадающего списка Применить к в диалоге свойств индикатора. Кроме того, в этом дополнительном параметре можно передать дескриптор другого, созданного предварительно индикатора (см. пример далее). В таком случае, вновь создаваемый индикатор будет рассчитываться по первому буферу индикатора с указанным дескриптором. Иными словами, программист может задать расчет одного индикатора от другого.

К сожалению, в MQL5 нет средств, чтобы программно узнать, реализован ли конкретный сторонний индикатор с использованием краткой или полной формы OnCalculate, то есть можно ли ему передавать дополнительный дескриптор при создании через iCustom. Также MQL5 не позволяет выбирать номер буфера, если индикатор, индентифицируемый дополнительным дескриптором, имеет несколько буферов.

Вернемся к параметру pathname.

Путем является строка, содержащая хотя бы одну обратную ('\') или прямую ('/') косую черту — это специальный символ, называемый также "слэшом" и используемый в файловой системе, как разделитель в иерархии папок и файлов. Вы можете применять как прямую, так и обратную черту, однако последняя требует "экранирования", то есть её следует писать дважды. Это связано с тем, что обратный "слэш" является управляющим символом, с помощью которого формируются многие служебные коды, такие как табуляция ('\t'), перенос на новую строку ('\n') и так далее (см. раздел Символьные типы).

Если путь начинается со слэша, он называется абсолютным и корневой папкой для него является каталог всех исходных текстов MQL5. Например, указание строки "/MyIndicator" в параметре pathname приведет к поиску файла MQL5/MyIndicator.ex5, а более длинный путь с каталогом "/Exercise/MyIndicator" сошлется на MQL5/Exercise/MyIndicator.ex5.

Если параметр pathname содержит один или более слэшей, но не начинается с него, то такой путь называется относительным, потому что он в этом случае рассматривается относительно одного из двух предопределенных мест размещения. Во-первых, файл индикатора ищется относительно той папки, где находится вызывающая MQL-программа, а если его там не удается найти, то во-вторых, поиск продолжается внутри общей папки индикаторов MQL5/Indicators.

В строке со слэшами тот фрагмент, что расположен правее самого правого слэша, трактуется как имя файла, а все предыдущие описывают иерархию папок. Например, путь "Folder/SubFolder/Filename" соответствует двум вложенным папкам: SubFolder внутри Folder, и внутри SubFolder — файл Filename.

Наиболее простой случай — когда в параметре pathname нет слэшей — задает только имя файла. Оно также рассматривается в контексте двух вышеупомянутых начальных точек поиска.

Например, советник MyExpert.ex5 находится в папке MQL5/Experts/Examples и содержит вызов iCustom(_Symbol, _Period, "MyIndicator"). Здесь относительный путь вырожден (пуст), и присутствует только имя файла. Таким образом, поиск индикатора начинается с папки MQL5/Experts/Examples/ и имени MyIndicator, что дает MQL5/Experts/Examples/MyIndicator.ex5. Если такой индикатор не найден в этом каталоге, поиск продолжится в корневой папке индикаторов, то есть по соединенным пути и имени MQL5/Indicators/MyIndicator.ex5.

Если индикатор не найден в обоих местах, функция вернет INVALID_HANDLE и установит код ошибки 4802 (ERR_INDICATOR_CANNOT_CREATE) в _LastError.

Более сложный случай, если pathname содержит не только имя, но и каталог, например, "TradeSignals/MyIndicator". Тогда указанный путь добавляется к папке вызывающей программы, в результате чего получается такая цель поиска: MQL5/Experts/Examples/TradeSignals/MyIndicator.ex5. Затем в случае неуспеха тот же путь добавляется к MQL5/Indicators, то есть ищется файл MQL5/Indicators/TradeSignals/MyIndicator.ex5. Если при этом в качестве разделителя решено использовать обратную косую черту, следует не забывать писать её дважды, например, iCustom(_Symbol, _Period, "TradeSignals\\MyIndicator").

Для освобождения памяти компьютера от неиспользуемого больше индикатора служит функция IndicatorRelease, которой передается описатель этого индикатора.

Особое внимание следует уделить тестированию программы, использующей индикаторы. Если параметр pathname в вызове iCustom задан константной строкой, то соответствующий необходимый индикатор выявляется компилятором автоматически и передается тестеру вместе с тестируемой программой. В противном случае, если параметр рассчитывается в выражении или получается извне (например, через input от пользователя), необходимо указать в исходном коде свойство #property tester_indicator:

#property tester_indicator "indicator_name.ex5"

Это означает, что в составе программ можно тестировать только заранее известные пользовательские индикаторы.

Рассмотрим пример нового индикатора UseWPR1.mq5, который будет внутри своего обработчика OnInit создавать дескриптор уже известного по прошлой главе индикатора IndWPR (не забудьте откомпилировать IndWPR, так как iCustom загружает ex5-файлы). Получаемый в UseWPR1 дескриптор пока никак не используется — изучим только саму возможность и проверим признак успеха. В связи с этим буфера в новом индикаторе не нужны.

#property indicator_separate_window
#property indicator_buffers 0
#property indicator_plots   0

Индикатор создаст пустое подокно, но ничего в нем не отобразит (пока) — это нормальное поведение.

Проверим несколько вариантов получения дескриптора, с разными значениями pathname:

  1. Абсолютный путь, начинающийся с косой черты, и потому включающий всю иерархию папок (начиная от MQL5) с примерами индикаторов 5-ой главы, то есть "/Indicators/MQL5Book/p5/IndWPR";
  2. Только имя "IndWPR" для поиска в той же папке, где находится и вызывающий индикатор UseWPR1.mq5 (оба индикатора поставляются в одной папке);
  3. Путь с иерархией папок примеров индикаторов относительно стандартного каталога MQL5/Indicators, то есть "MQL5Book/p5/IndWPR" (заметьте, что в начале нет косой черты);
  4. Только имя, как в пункте 2, но для несуществующего индикатора "IndWPR NonExistent";
  5. Абсолютный путь как в пункте 1, но с обратными косыми чертами без их экранирования, то есть "\Indicators\MQL5Book\p5\IndWPR";
  6. Полная копия пункта 2.

int OnInit()
{
   int handle1 = PRTF(iCustom(_Symbol_Period"/Indicators/MQL5Book/p5/IndWPR"));
   int handle2 = PRTF(iCustom(_Symbol_Period"IndWPR"));
   int handle3 = PRTF(iCustom(_Symbol_Period"MQL5Book/p5/IndWPR"));
   int handle4 = PRTF(iCustom(_Symbol_Period"IndWPR NonExistent"));
   int handle5 = PRTF(iCustom(_Symbol_Period"\Indicators\MQL5Book\p5\IndWPR"));
   int handle6 = PRTF(iCustom(_Symbol_Period"IndWPR"));
   return INIT_SUCCEEDED;
}

Поскольку переменные с дескрипторами не используются, они объявлены локальными. Особо поясним, что хотя локальные переменные handle и удаляются при выходе из OnInit, это не влияет на дескрипторы: те продолжают существовать, пока выполняется "родительский" индикатор UseWPR — мы просто теряем в своем коде значения этих дескрипторов, но это не страшно, потому что они здесь нигде не используются. В примерах реальных индикаторов, которые мы рассмотрим позднее, дескрипторы, разумеется, сохраняются (как правило, в глобальных переменных) и используются.

За утечку ресурсов также не стоит переживать: при удалении индикатора UseWPR с графика все созданные им дескрипторы будут автоматически очищены терминалом. Более детально принципы и необходимость явного освобождения дескрипторов будут описаны в разделе об удалении экземпляров индикаторов с помощью IndicatorRelease.

Вышеприведенный код OnInit генерирует следующие записи в журнале.

iCustom(_Symbol,_Period,/Indicators/MQL5Book/p5/IndWPR)=10 / ok
iCustom(_Symbol,_Period,IndWPR)=11 / ok
iCustom(_Symbol,_Period,MQL5Book/p5/IndWPR)=12 / ok
cannot load custom indicator 'IndWPR NonExistent' [4802]
iCustom(_Symbol,_Period,IndWPR NonExistent)=-1 / INDICATOR_CANNOT_CREATE(4802)
iCustom(_Symbol,_Period,\Indicators\MQL5Book\p5\IndWPR)=13 / ok
iCustom(_Symbol,_Period,IndWPR)=11 / ok

Как мы видим, осмысленные дескрипторы 10, 11, 12, 13 получены во всех случаях кроме 4-го, с несуществующим вызываемым индикатором. При этом значение дескриптора равно -1 (INVALID_HANDLE).

Также следует обратить внимание, что при компиляции строка с вариантом N5 генерирует несколько предупреждений "неизвестная управляющая последовательность" ("unrecognized character escape sequence"). Это следствие того, что мы незаэкранировали обратную косую черту. И нам еще повезло, что инструкция выполнилась успешно, потому что если бы название какой-либо папки или файла начиналось с одной из букв в поддерживаемых управляющих последовательностях, то интерпретация последовательности нарушила бы ожидаемое прочтение названия. Например, если бы мы имели в этой же папке индикатор с именем "test" и попытались создать его через путь "MQL5Book\p5\test", то получили бы INVALID_HANDLE и ошибку 4802. Ведь '\t' — это символ табуляции, и потому терминал искал бы "MQL5Book\p5<nbsp>est". Правильная запись должна была бы быть "MQL5Book\\p5\\test". Поэтому проще пользоваться прямой косой чертой.

Также важно отметить, что хотя все успешные варианты ссылаются на один и тот же индикатор MQL5/Indicators/MQL5Book/p5/IndWPR.ex5, и фактически пути 1, 2, 3 и 5 эквивалентны, терминал трактует их как разные строки, из-за чего мы получаем разные значения дескрипторов. И только вариант 6, который полностью дублирует вариант 2, возвращает идентичный дескриптор — 11.

Почему нумерация дескрипторов начинается с 10? Меньшие значения зарезервированы за системой. Как было сказано выше, для индикаторов с краткой формой OnCalculate последним параметром можно передавать тип цены или дескриптор другого индикатора, по буферу которого и будет рассчитываться вновь создаваемый экземпляр. Поскольку элементы перечисления ENUM_APPLIED_PRICE имеют свои значения-константы, они как раз и занимают область ниже 10. Подробнее об этом см. раздел Определение источника данных для индикатора.

В следующем примере UseWPR2.mq5 реализуем индикатор, который создаст экземпляр IndWPR и проверит прогресс его расчета, пользуясь дескриптором. Но для этого следует познакомиться с новой функцией BarsCalculated.