- Дескрипторы и счетчики владельцев индикаторов
- Простой способ создания экземпляров индикаторов: iCustom
- Проверка количества просчитанных баров: BarsCalculated
- Получение данных таймсерии из индикатора: CopyBuffer
- Поддержка множества символов и таймфреймов
- Обзор встроенных индикаторов
- Использование встроенных индикаторов
- Расширенный способ создания индикаторов: IndicatorCreate
- Гибкое создание индикаторов с помощью IndicatorCreate
- Обзор функций управления индикаторами на графике
- Комбинирование вывода в главное окно и вспомогательное
- Чтение данных из диаграмм, имеющих сдвиг
- Удаление экземпляров индикаторов: IndicatorRelease
- Получение настроек индикатора по его дескриптору
- Определение источника данных для индикатора
Гибкое создание индикаторов с помощью IndicatorCreate
После знакомства с новым способом создания индикаторов обратимся к задаче, более приближенной к реальности. IndicatorCreate обычно применяют в тех случаях, когда вызываемый индикатор заранее не известен. Такая необходимость, например, возникает при написании универсальных экспертов, способных торговать по произвольным сигналам, настраиваемым пользователем. И даже названия индикаторов могут задаваться пользователем.
Мы пока не готовы к разработке экспертов, а потому изучим эту технологию на примере индикатора-обертки UseDemoAll.mq5, способного отобразить данные любого другого индикатора.
С точки зрения пользователя это должно выглядеть так. После наложения UseDemoAll на график, в диалоге свойств появляется список для выбора одного из встроенных индикаторов или пользовательского, причем в последнем случае дополнительно нужно будет указать его имя в поле ввода. В другом строковом параметре можно ввести список параметров, через запятую. Типы параметров будут определяться автоматически, исходя из их написания. Например, число с десятичной точкой (10.0) будет трактоваться как double, число без точки (15) — как целое, а нечто заключенное в кавычки ("текст") — как строка.
Это далеко не все, но основные настройки UseDemoAll. С остальными мы будем разбираться по мере возникновения конструктивных проблем.
За основу решения возьмем перечисление ENUM_INDICATOR: в нем уже есть элементы для всех типов индикаторов, включая пользовательские (IND_CUSTOM). Правда в чистом виде оно не подходит по нескольким причинам. Во-первых, из него невозможно получить метаданные о конкретном индикаторе, такие как количество и типы аргументов, количество буферов и в какое окно индикатор выводится (в главное или в собственное). А эта информация важна для правильного создания и визуализации индикатора. Во-вторых, если мы опишем входную переменную типа ENUM_INDICATOR, чтобы пользователь мог выбирать интересующий его индикатор, в диалоге свойств это будет представлено выпадающим списком, где варианты содержат лишь имя элемента. А было бы желательно обеспечить в этом списке подсказки для пользователя (как минимум, про параметры). Поэтому опишем свое собственное перечисление IndicatorType. Напомним, что MQL5 позволяет для каждого элемента указать справа комментарий, который показывается в интерфейсе.
В каждом элементе перечисления IndicatorType будем кодировать особым образом не только соответствующий идентификатор (ID) из ENUM_INDICATOR, но и количество параметров (P), количество буферов (B) и номер рабочего окна (W). Для этих целей разработаны следующие макросы.
#define MAKE_IND(P,B,W,ID) (int)((W << 24) | ((B & 0xFF) << 16) | ((P & 0xFF) << 8) | (ID & 0xFF))
|
Макрос MAKE_IND принимает в качестве параметров все вышеозвученные характеристики и упаковывает их в разные байты одного 4-х байтового целого числа, формируя таким образом уникальный код для элемента нового перечисления. Остальные 4 макроса позволяют выполнять обратную операцию — по коду вычислить все характеристики индикатора.
Само перечисление IndicatorType не станем приводить здесь полностью, а покажем лишь фрагмент. С полным исходным кодом можно ознакомиться в файле AutoIndicator.mqh.
enum IndicatorType
|
В комментариях, которые станут элементами выпадающего списка, видимого пользователю, указаны прототипы с именованными параметрами, количество буферов в квадратных скобках и пометки звездочкой тех индикаторов, которые выводятся в собственное окно. Сами идентификаторы также сделаны информативными, потому что именно они преобразуются в текст функцией EnumToString, которая используется для вывода сообщений в журнал.
Список параметров особенно важен, так как пользователь должен будет ввести соответствующие значения, разделенные запятыми, в зарезервированную для этого входную переменную. Мы могли бы "подсказать" и типы параметров, но для простоты решено оставить только имена со смыслом, из которого можно заключить и тип. Например, period, fast, slow — это целые с периодом (количеством баров), method — метод усреднения ENUM_MA_METHOD, price — тип цены ENUM_APPLIED_PRICE, volume — тип объема ENUM_APPLIED_VOLUME.
Для удобства пользователя (чтобы не вспоминать значения элементов перечислений) в программе будут поддержаны названия всех перечислений. В частности, идентификатор sma обозначит MODE_SMA, ema — MODE_EMA и так далее. Цена close превратится в PRICE_CLOSE, open — в PRICE_OPEN, и прочие типы цен — аналогично, по последнему слову (после подчеркивания) в идентификаторе элемента перечисления. Например, для списка параметров индикатора iMA (iMA_period_shift_method_price) можно написать такую строку: 11,0,sma,close. Идентификаторы не надо брать в кавычки. Однако ничто не мешает, при необходимости, передать строку с таким же текстом, например, список — 1.5,"close" — содержит вещественное число 1.5 и строку "close".
Тип индикатора, а также строки со списком параметров и, опционально, именем (если индикатор пользовательский) — это основные данные для конструктора класса AutoIndicator.
class AutoIndicator
|
Здесь и далее опущены некоторые фрагменты, связанные с проверками входных данных на корректность. Полный исходный код прилагается к книге.
Процесс анализа строки с параметрами поручается методу parseParameters. В нем реализована описанная выше схема с распознаванием типов значений и их передача в объект MqlParamBuilder, с которым мы познакомились в предыдущем примере.
int parseParameters(const string &list)
|
Вспомогательная функция lookUpLiterals обеспечивает конвертацию идентификаторов в константы стандартных перечислений.
int lookUpLiterals(const string &s)
|
После того как параметры распознаны и сохранены во внутреннем массиве объекта MqlParamBuilder, вызывается метод create. Его задача, скопировать параметры в локальный массив, дополнить его именем пользовательского индикатора (в случае такового), и вызвать функцию IndicatorCreate.
int create()
|
Метод возвращает полученный дескриптор.
Особый интерес вызывает то, каким образом в самое начало массива вставляется дополнительный строковый параметр с названием пользовательского индикатора. Сначала массиву назначается порядок индексации "как в таймсериях" (см. ArraySetAsSeries), в результате чего индекс последнего (физически, по расположению в памяти) элемента становится равным 0, и подсчет элементов идет справа налево. Затем массив увеличивается в размере и в добавленный элемент записывается имя индикатора. За счет обратной индексации это добавление происходит не справа от существующих элементов, а слева. В завершение мы возвращаем массиву привычный порядок индексации, и под индексом 0 оказывается бывший только что последним новый элемент со строкой.
Дополнительно класс AutoIndicator умеет формировать сокращенное имя встроенного индикатора из названия элемента перечисления.
...
|
Теперь все готово, чтобы заняться непосредственно исходным кодом UseDemoAll.mq5. Но начнем со слегка упрощенной версии UseDemoAllSimple.mq5.
Прежде всего, определимся с количеством индикаторных буферов. Поскольку максимальное количество буферов среди встроенных индикаторов равно пяти (у Ichimoku), примем его как ограничитель. Регистрацию этого количества массивов в качестве буферов поручим уже известному нам классу BufferArray (см. раздел Мультивалютные и мультитаймфреймовые индикаторы, пример IndUnityPercent).
#define BUF_NUM 5
|
Важно напомнить, что индикатор может быть спроектирован либо для отображения в главном окне, либо в собственном. Совместить два режима MQL5 не позволяет. Однако нам заранее не известно, какой индикатор выберет пользователь, и потому требуется изобрести некий "обходной маневр". Пока разместим свой индикатор в главном окне, а с проблемой отдельного окна разберемся позже.
Чисто технически нет никаких препятствий для копированиях данных из буферов индикаторов со свойством indicator_separate_window в свои буфера, отображаемые в главном окне. Однако следует иметь в виду, что диапазон величин подобных индикаторов часто не совпадает с масштабом цен, и потому увидеть их на графике вряд ли получится (линии будут находиться где-то далеко за пределами видимой области, вверху или внизу), хотя значения по-прежнему будут выводиться в Окно данных.
С помощью входных переменных обеспечим выбор типа индикатора, названия пользовательского индикатора и списка параметров. Также добавим переменные для типа отрисовки и ширины линий. Поскольку буфера будут подключаться к работе динамически, в зависимости от количества буферов исходного индикатора, мы не описываем стили буферов статически с помощью директив и будем делать это в OnInit вызовами встроенных Plot-функций.
input IndicatorType IndicatorSelector = iMA_period_shift_method_price; // Built-in Indicator Selector
|
Для хранения дескриптора индикатора опишем глобальную переменную.
int Handle; |
В обработчике OnInit задействуем представленный ранее класс AutoIndicator для парсинга входных данных, подготовки массива MqlParam и получения на его основе дескритора.
#include <MQL5Book/AutoIndicator.mqh>
|
Для настройки диаграмм опишем набор цветов и получим краткое имя индикатора из объекта AutoIndicator. Также вычислим количество используемых буферов n встроенного индикатора с помощью макроса IND_BUFFERS, а для любого пользовательского индикатора (который неизвестен заранее), за неимением лучшего решения, включим все буфера. Далее в процессе копирования данных лишние вызовы функции CopyBuffer просто вернут ошибку, и такие массивы можно будет заполнить пустыми значениями.
...
|
В цикле настроим свойства диаграмм, с учетом ограничителя n: буфера сверх него скрываются.
for(int i = 0; i < BUF_NUM; ++i)
|
В верхнем левом углу графика, в комментарии будет выводиться название индикатора с параметрами.
В обработчике OnCalculate, по готовности данных у дескриптора, читаем их в свои массивы.
int OnCalculate(ON_CALCULATE_STD_SHORT_PARAM_LIST)
|
Вышеописанная реализация является упрощенной и соответствует исходному файлу UseDemoAllSimple.mq5. Её расширением мы займемся далее, а пока проверим поведение текущей версии. На следующем изображении показаны 2 копии индикатора: синяя линия — с настройками по умолчанию (iMA_period_shift_method_price, параметры "11,0,sma,close"), а красная — iRSI_period_price с параметрами "11,close".
Два экземпляра индикатора UseDemoAllSimple с показаниями iMA и iRSI
Для демонстрации специально выбран график USDRUB, потому что значения котировок здесь более или менее совпадают с диапазоном индикатора RSI (который должен был бы выводиться в отдельном окне). На большинстве графиков других символов мы бы не заметили RSI. Если для вас важен только программный доступ к величинам, то в этом нет ничего страшного, однако при наличии требований по визуализации это проблема, которую следует решить.
Итак, следует каким-то образом обеспечить отдельное отображение индикаторов, предназначенных для подокна. В принципе, в среде MQL-разработчиков довольно часто встречается требование выводить графику одновременно в главное окно и в подокно. Мы представим один из вариантов решения, но для этого нужно сначала познакомиться с некоторыми новыми функциями.