- Дескрипторы и счетчики владельцев индикаторов
- Простой способ создания экземпляров индикаторов: iCustom
- Проверка количества просчитанных баров: BarsCalculated
- Получение данных таймсерии из индикатора: CopyBuffer
- Поддержка множества символов и таймфреймов
- Обзор встроенных индикаторов
- Использование встроенных индикаторов
- Расширенный способ создания индикаторов: IndicatorCreate
- Гибкое создание индикаторов с помощью IndicatorCreate
- Обзор функций управления индикаторами на графике
- Комбинирование вывода в главное окно и вспомогательное
- Чтение данных из диаграмм, имеющих сдвиг
- Удаление экземпляров индикаторов: IndicatorRelease
- Получение настроек индикатора по его дескриптору
- Определение источника данных для индикатора
Расширенный способ создания индикаторов: IndicatorCreate
Создание индикатора с помощью функции iCustom или одной из тех, что составляют набор встроенных индикаторов, требует знания перечня параметров на стадии кодирования. Однако на практике часто возникает необходимость в написании достаточно гибких программ, чтобы в них можно было заменить один индикатор на другой.
Например, при оптимизации эксперта в тестере имеет смысл подбирать не только период скользящего среднего, но и алгоритм его расчета. Разумеется, если строить алгоритм на единственном индикаторе iMA, можно предусмотреть задание в настройках его методов ENUM_MA_METHOD. Но кто-то, вероятно, хотел бы расширить выбор за счет переключения между двойной экспоненциальной, тройной экспоненциальной и фрактальной скользящей средней. На первый взгляд, для этого можно было бы использовать switch с вызовом, соответственно, iDEMA, iTEMA и iFrAMA. Однако, как быть с включением в этот список пользовательских индикаторов?
Хотя название индикатора достаточно просто заменить в вызове iCustom, список параметров может существенно отличаться. В общем случае в эксперте может потребоваться генерация сигналов на сочетании любых, не известных заранее индикаторов, а не только скользящих средних.
Для подобных случаев в MQL5 существует универсальный метод создания произвольного технического индикатора с помощью функции IndicatorCreate.
int IndicatorCreate(const string symbol, ENUM_TIMEFRAMES timeframe, ENUM_INDICATOR indicator, int count = 0, const MqlParam ¶meters[] = NULL)
Функция создает экземпляр индикатора для указанной пары символа и таймфрейма. Тип индикатора задается с помощью параметра indicator. Его тип — перечисление ENUM_INDICATOR (см. далее) содержит идентификаторы для всех встроенных индикаторов, а также вариант для iCustom. Количество параметров индикатора и их описания передаются, соответственно, в аргументе count и массиве структур MqlParam (см. ниже).
Каждый элемент этого массива описывает соответствующий входной параметр создаваемого индикатора, поэтому содержимое и порядок следования элементов должны отвечать прототипу функции встроенного индикатора или, в случае пользовательского индикатора, описаниям входных переменных в его исходном коде.
Нарушение этого правила может привести к ошибке на стадии выполнения программы (см. пример далее) и невозможности создать дескриптор. А в худшем случае переданные параметры будут интерпретированы неправильно, и индикатор поведет себя не так, как ожидалось, однако из-за отсутствия ошибок заметить это нелегко. Исключением является передача пустого массива или не передача его вовсе (поскольку аргументы count и parameters являются опциональными): в этом случае индикатор будет создан с настройками по умолчанию. Также для пользовательских индикаторов можно опускать произвольное количество параметров с конца списка.
Структура MqlParam специально разработана для передачи входных параметров при создании индикатора с помощью IndicatorCreate или для получения информации о параметрах стороннего индикатора (выполняющегося на графике) с помощью IndicatorParameters.
struct MqlParam
|
Фактическое значение параметра должно быть задано в одном из полей integer_value, double_value, string_value, в соответствии со значением первого поля type. В свою очередь для описания поля type используется перечисление ENUM_DATATYPE, содержащее идентификаторы для всех встроенных типов MQL5.
Идентификатор |
Тип данных |
---|---|
TYPE_BOOL |
bool |
TYPE_CHAR |
char |
TYPE_UCHAR |
uchar |
TYPE_SHORT |
short |
TYPE_USHORT |
ushort |
TYPE_COLOR |
color |
TYPE_INT |
int |
TYPE_UINT |
uint |
TYPE_DATETIME |
datetime |
TYPE_LONG |
long |
TYPE_ULONG |
ulong |
TYPE_FLOAT |
float |
TYPE_DOUBLE |
double |
TYPE_STRING |
string |
Если какой-либо параметр индикатора имеет тип перечисления, для его описания в поле type необходимо применять значение TYPE_INT.
Перечисление ENUM_INDICATOR, используемое в третьем параметре IndicatorCreate для указания типа индикатора, содержит следующие константы.
Идентификатор |
Индикатор |
---|---|
IND_AC |
Accelerator Oscillator |
IND_AD |
Accumulation/Distribution |
IND_ADX |
Average Directional Index |
IND_ADXW |
ADX by Welles Wilder |
IND_ALLIGATOR |
Alligator |
IND_AMA |
Adaptive Moving Average |
IND_AO |
Awesome Oscillator |
IND_ATR |
Average True Range |
IND_BANDS |
Bollinger Bands® |
IND_BEARS |
Bears Power |
IND_BULLS |
Bulls Power |
IND_BWMFI |
Market Facilitation Index |
IND_CCI |
Commodity Channel Index |
IND_CHAIKIN |
Chaikin Oscillator |
IND_CUSTOM |
Custom indicator |
IND_DEMA |
Double Exponential Moving Average |
IND_DEMARKER |
DeMarker |
IND_ENVELOPES |
Envelopes |
IND_FORCE |
Force Index |
IND_FRACTALS |
Fractals |
IND_FRAMA |
Fractal Adaptive Moving Average |
IND_GATOR |
Gator Oscillator |
IND_ICHIMOKU |
Ichimoku Kinko Hyo |
IND_MA |
Moving Average |
IND_MACD |
MACD |
IND_MFI |
Money Flow Index |
IND_MOMENTUM |
Momentum |
IND_OBV |
On Balance Volume |
IND_OSMA |
OsMA |
IND_RSI |
Relative Strength Index |
IND_RVI |
Relative Vigor Index |
IND_SAR |
Parabolic SAR |
IND_STDDEV |
Standard Deviation |
IND_STOCHASTIC |
Stochastic Oscillator |
IND_TEMA |
Triple Exponential Moving Average |
IND_TRIX |
Triple Exponential Moving Averages Oscillator |
IND_VIDYA |
Variable Index Dynamic Average |
IND_VOLUMES |
Volumes |
IND_WPR |
Williams Percent Range |
Важно отметить, что если в качестве типа индикатора передается значение IND_CUSTOM, то первый элемент массива параметров должен иметь поле type со значением TYPE_STRING, а поле string_value должно содержать имя (путь) пользовательского индикатора.
В случае успеха функция IndicatorCreate возвращает дескриптор созданного индикатора, а в случае неудачи — INVALID_HANDLE. Код ошибки будет находиться в _LastError.
Напомним, что для тестирования MQL-программ, создающих пользовательские индикаторы, имена которых не известны на стадии компиляции (что имеет место и при использовании IndicatorCreate), необходимо явным образом обеспечивать их привязку с помощью директивы:
#property tester_indicator "indicator_name.ex5" |
Это дает возможность тестеру отправить требуемые вспомогательные индикаторы на агенты тестирования, но ограничивает процесс только заранее известными индикаторами.
Рассмотрим несколько примеров. Начнем с простого применения IndicatorCreate в качестве альтернативы уже известных функций, а затем для демонстрации гибкости нового подхода создадим универсальный индикатор-обертку для визуализации произвольных встроенных или пользовательских индикаторов.
Первый пример — UseEnvelopesParams1.mq5 — создает встроенную копию индикатора Envelopes. Для этого описываем два буфера и две диаграммы, массивы под них и входные параметры, повторяющие параметры iEnvelopes.
#property indicator_chart_window
|
Обработчик OnInit мог бы выглядеть следующим образом, если использовать функцию iEnvelopes.
int OnInit()
|
Привязка буферов останется прежней, но для создания дескриптора мы сейчас пойдем другим путем. Опишем массив MqlParam, заполним его и вызовем функцию IndicatorCreate.
int OnInit()
|
Получив дескриптор, используем его в OnCalculate для заполнения двух своих буферов.
int OnCalculate(const int rates_total,
|
Проверим, как созданный индикатор UseEnvelopesParams1 выглядит на графике.
Индикатор UseEnvelopesParams1
Выше был представлен стандартный, но не очень изящный способ заполнения свойств. Поскольку вызов IndicatorCreate может потребоваться во многих проектах, имеет смысл упростить процедуру для вызывающего кода. Для этой цели разработаем класс MqlParamBuilder (см. файл MqlParamBuilder.mqh). Его задачей будет принимать с помощью некоторых методов значения параметров, определять их тип и добавлять в массив соответствующие элементы — корректно заполненные структуры.
К сожалению, MQL5 не поддерживает в полной мере концепцию, которая есть во многих других языках программирования и называется "информацией о типах времени исполнения" (Run-Time Type Information, RTTI). С помощью неё программы могут запрашивать у среды исполнения описательные мета-данные о своих составных частях — переменных, структурах, классах, функциях и т.д. К немногочисленным встроенным возможностям MQL5, которые можно отнести к разряду RTTI, являются операторы typename и offsetof. Поскольку typename возвращает название типа в виде строки, построим свой автодетектор типов на строках (см. файл RTTI.mqh).
template<typename T>
|
Шаблонная функция rtti получает с помощью typename строку с именем типа-параметра шаблона и сравнивает её с элементами массива, содержащего все встроенные типы из перечисления ENUM_DATATYPE. Порядок перечисления имен в массиве соответствует значению элемента перечисления, поэтому при обнаружении совпадающей строки достаточно привести индекс к типу (ENUM_DATATYPE) и вернуть вызывающему коду. Например, вызов rtti(1.0) или rtti<double>() даст значение TYPE_DOUBLE.
Имея этот инструмент, мы можем вернуться к разработке MqlParamBuilder. Опишем в классе свой массив структур MqlParam и переменную n, которая будет содержать индекс последнего, заполняемого элемента.
class MqlParamBuilder
|
Публичный метод для добавления очередного значения в список параметров сделаем шаблонным. Более того, реализуем его в виде перегрузки оператора '<<', который возвращает указатель на сам объект "строителя". Это позволит записывать в массив несколько значений одной строкой, например, так: builder << WorkPeriod << PriceType << SmoothingMode.
Именно в этом методе мы увеличиваем размер массива, получаем рабочий индекс n для заполнения и сразу же обнуляем эту n-ую структуру.
...
|
Там, где стоит многоточие, последует основная рабочая часть, то есть заполнение полей структуры. Можно было бы предположить, что тип параметра мы напрямую определим с помощью самодельного rtti. Но следует обратить внимание на один нюанс. Если мы напишем инструкцию array[n].type = rtti(v), она неправильно сработает для перечислений. Каждое перечисление является самостоятельным типом со своим названием, несмотря на то, что хранится по образцу целых чисел. Для перечислений функция rtti вернет 0, в связи с чем, нужно явным образом заменить его на TYPE_INT.
...
|
Осталось положить само значение v в одно из трех полей структуры: integer_value типа long (обратите внимание, long — это длинное целое, отсюда и название поля), double_value типа double или string_value типа string. При этом количество встроенных типов гораздо больше, поэтому подразумевается, что все целочисленные типы (включая int, short, char, color, datetime, перечисления) должны попадать в поле integer_value, значения float — в поле double_value, и лишь для поля string_value существует однозначное толкование: это всегда string.
Для выполнения данной задачи реализуем несколько перегруженных методов assign: три с конкретными типами float, double, string, и один шаблонный для всего остального.
class MqlParamBuilder
|
На этом процесс заполнения структур заканчивается, и остается вопрос передачи сформированного массива в вызывающий код. Это действие поручено публичному методу с перегрузкой оператора '>>', который имеет единственный аргумент: ссылку на приемный массив MqlParam.
// экспортируем внутренний массив наружу
|
Теперь все готово, чтобы заняться исходным кодом модифицированного индикатора UseEnvelopesParams2.mq5. Изменения по сравнению с первой версией коснуться только заполнения массива MqlParam в обработчике OnInit. В нем мы описываем объект "строителя", отсылаем в него через '<<' все параметры и возвращаем через '>>' готовый массив. Все в одной строке.
int OnInit()
|
Для контроля выводим массив в журнал (выше показан результат для значений по умолчанию).
Если массив заполнен не полностью, вызов IndicatorCreate завершится ошибкой. Например, если передать только 3 параметра из 5 требуемых Envelopes, получим ошибку 4002 и недействительный дескриптор.
Handle = PRTF(IndicatorCreate(_Symbol, _Period, IND_ENVELOPES, 3, params));
|
Однако более длинный массив, чем в спецификации индикатора, не считается ошибкой: лишние значения просто не принимаются во внимание.
Обратите внимание, что когда типы значений отличаются от ожидаемых типов параметров, система выполняет их неявное приведение, и это не вызывает явных ошибок, хотя созданный индикатор может работать не так, как ожидалось. Например, если вместо Deviation отправить в индикатор строку, она будет интерпретирована как число 0, в результате чего "конверт" схлопнется: обе линии совместятся на средней, относительно которой и делается отступ на размер Deviation (в процентах). Похожим образом, передав вещественное число с дробной частью в том параметре, где ожидается целое число, мы спровоцируем его округление.
Но мы, разумеется, оставим правильный вариант вызова IndicatorCreate и получим рабочий индикатор, также как и в первой версии.
...
|
Внешне новый индикатор ничем не отличается от предыдущего.