- Дескрипторы и счетчики владельцев индикаторов
- Простой способ создания экземпляров индикаторов: iCustom
- Проверка количества просчитанных баров: BarsCalculated
- Получение данных таймсерии из индикатора: CopyBuffer
- Поддержка множества символов и таймфреймов
- Обзор встроенных индикаторов
- Использование встроенных индикаторов
- Расширенный способ создания индикаторов: IndicatorCreate
- Гибкое создание индикаторов с помощью IndicatorCreate
- Обзор функций управления индикаторами на графике
- Комбинирование вывода в главное окно и вспомогательное
- Чтение данных из диаграмм, имеющих сдвиг
- Удаление экземпляров индикаторов: IndicatorRelease
- Получение настроек индикатора по его дескриптору
- Определение источника данных для индикатора
Получение данных таймсерии из индикатора: CopyBuffer
MQL-программа может читать данные из публичных буферов индикатора по его дескриптору. Напомним, что в пользовательских индикатрах такими буферами становятся массивы, указанные в исходном коде в вызовах функции SetIndexBuffer.
MQL5 API предоставляет для чтения буферов функцию CopyBuffer, имеющую 3 формы.
int CopyBuffer(int handle, int buffer, int offset, int count, double &array[])
int CopyBuffer(int handle, int buffer, datetime start, int count, double &array[])
int CopyBuffer(int handle, int buffer, datetime start, datetime stop, double &array[])
В параметре handle указывается дескриптор, полученный из вызова iCustom или других функций (см. далее разделы про IndicatorCreate и встроенные индикаторы). Параметр buffer задает индекс индикаторного буфера, из которого следует запросить данные. Нумерация ведется, начиная с 0.
Полученные элементы запрошенной таймсерии попадают в массив array, заданный по ссылке.
Три варианта функции различаются способом задания диапазона временных меток (start/stop) или номеров (offset) и количества (count) баров, для которых получаются данные. Принципы работы с этими параметрами полностью соответствуют тому, что мы изучали в Обзоре Copy-функций для получения массивов котировок. В частности, отсчет элементов копируемых данных в offset и count ведется от настоящего к прошлому, то есть стартовая позиция, равная 0, означает текущий бар. А в приемном массиве array элементы физически располагаются в порядке от прошлого к настоящему (однако эту адресацию можно поменять на логическом уровне на обратную с помощью ArraySetAsSeries).
В принципе CopyBuffer является аналогом функций для чтения встроенных таймсерий типа CopyOpen, CopyClose и других. Основное отличие заключается в том, что таймсерии с котировками формирует сам терминал, а таймсерии в индикаторных буферах рассчитывают пользовательские или встроенные индикаторы. Кроме того, конкретную пару символа и таймфрейма, которые определяют и идентифицируют таймсерию, в случае индикаторов мы задаем заранее — в функции создания дескриптора вроде iCustom, а в CopyBuffer эта информация передается опосредованно — через дескриптор handle.
При копировании заранее неизвестного количества данных в качестве массива-приемника желательно использовать динамический массив — тогда функция CopyBuffer распределит размер принимающего массива под размер копируемых данных. Если необходимо многократно копировать заранее известное количество данных, то лучше это делать в статически выделенный буфер (локальный с модификатором static или фиксированного размера в глобальном контексте), чтобы избежать повторных операций выделения памяти.
Если в качестве принимающего массива выступает индикаторный буфер (массив, предварительно зарегистрированный в системе функцией SetIndexBufer), то индексация в таймсерии и приемном буфере совпадают (при условии запроса для той же пары символ/таймфрейм). В этом случае легко реализовать частичное заполнение приемника (в частности, это используется для обновления последних баров, см. пример далее). Если же символ или таймфрейм запрашиваемой таймсерии не совпадают с символом и/или таймфреймом текущего графика, то функция вернет не больше элементов, чем минимальное количество баров в этих двух: источнике и приемнике.
Если в качестве аргумента array передан обычный массив (не буфер), то функция заполнит его, начиная с первых элементов — целиком (в случае динамического) или частично (в случае статического с превышением размера). Поэтому если необходимо произвести частичное копирование значений индикатора в произвольное место другого массив, то для этих целей необходимо использовать промежуточный массив, в который копируется требуемое количество элементов, а уже из него они переносятся в окончательное место назначения.
Функция возвращает количество скопированных элементов или -1 в случае ошибки, включая и временное отсутствие готовых данных.
Поскольку индикаторы, как правило, напрямую или опосредованно зависят от ценовых таймсерий, их расчет стартует не ранее, чем будут синхронизированы котировки. В связи с этим следует учитывать технические особенности организации и хранения таймсерий в терминале и быть готовым, что запрашиваемые данные появятся не сразу. В частности, мы можем получить 0 или количество, меньше запрошенного — все такие случаи следует обрабатывать согласно обстоятельствам, например, ждать построения или сообщать о проблеме пользователю.
Если запрашиваемые таймсерии еще не построены или их необходимо загрузить с сервера, то функция ведет себя по-разному в зависимости от типа MQL-программы, из которой она вызывается.
При запросе еще неготовых данных из индикатора, функция сразу же вернет -1, но при этом сам процесс загрузки и построения таймсерий будет инициирован.
При запросе данных из эксперта или скрипта, будет инициирована загрузка с сервера и/или начнется построение нужной таймсерии, если данные можно построить из локальной истории, но они еще не готовы. Функция вернет то количество данных, которые будут готовы к моменту истечения таймаута (45 секунд), отведенного на синхронное выполнение функции (вызывающий код при этом ожидает завершения работы функции).
Обратите внимание, что функция CopyBuffer может читать данные из буферов вне зависимости от режима их работы — INDICATOR_DATA, INDICATOR_COLOR_INDEX, INDICATOR_CALCULATIONS — два последних скрыты от пользователя.
Также важно отметить, что в вызываемом индикаторе может быть установлен сдвиг таймсерии с помощью свойства PLOT_SHIFT и он влияет на смещение считываемых данных с помощью CopyBuffer. Например, если линии индикатор сдвинуты в будущее на N баров то в параметрах CopyBuffer (первой формы) нужно задавать offset равным (- N), то есть с минусом, так как текущий бар таймсерии имеет индекс 0, а индексы будущих баров со сдвигом уменьшаются на единицу на каждом баре. В частности, такая ситуация возникает с индикатором Gator, потому что его нулевая диаграмма сдвинута вперед на значение параметр TeethShift, а первая диаграмма — на значение параметра LipsShift. На максимальное из них и нужно делать поправку. Пример будет представлен в разделе Чтение данных из диаграмм, имеющих сдвиг.
К сожалению, MQL5 не предоставляет программных средств узнать свойство PLOT_SHIFT у стороннего индикатора, поэтому при необходимости прийдется запрашивать эту информацию у пользователя через входную переменную.
Работать с CopyBuffer из кода экспертов нам придется в главе про эксперты, а пока ограничимся индикаторами.
Продолжим развивать пример со вспомогательным индикатором IndWPR. На этот раз в версии UseWPR3.mq5 предусмотрим индикаторный буфер и заполним его данными из IndWPR с помощью CopyBuffer. Для этого применим директивы с количеством буферов и настройками отрисовки.
#property indicator_separate_window
|
В глобальном контексте опишем входной параметр с периодом WPR, массив для буфера и переменную с дескриптором.
input int WPRPeriod = 14;
|
Обработчик OnInit практически не изменится: добавился только вызов SetIndexBuffer.
int OnInit()
|
В OnCalculate будем копировать данные без преобразований.
int OnCalculate(const int rates_total,
|
Если откомпилировать и запустить UseWPR3, мы получим фактически копию исходного WPR за исключением настройки уровней, точности отображения чисел и заголовка. В качестве проверки работоспособности самого механизма это сойдет, но обычно новые индикаторы, строящиеся на одном или нескольких вспомогательных индикаторах, предлагают некую собственную идею и преобразование данных. Поэтому разработаем другой индикатор, выдающий торговые сигналы на покупку и продажу (с позиции трейдинга не стоит рассматривать их как образец — это задача по программированию). Суть его композиции можно пояснить с помощью следующей картинки.
Индикаторы IndWPR, IndTripleEMA, IndFractals
Используем выход WPR из зон перекупленности и перепроданности как рекомендацию, соответственно, продавать и покупать. Чтобы сигналы не реагировали на случайные флуктуации, применим к WPR тройное скользящее среднее и будем проверять уже её значение на пересечение границ верхней и нижней зон.
В качестве фильтра к этим сигналам будем проверять, какой фрактал был последним перед этим моментом: фрактал-вершина означает разворот цены вниз и подтверждает продажу, а фрактал-впадина означает разворот вверх и потому подкрепляет покупку. По определению фракталы появляются с отставанием на количество баров, равное порядку фракталов.
Новый индикатор доступен в файле UseWPRFractals.mq5.
Нам потребуется три буфера: два сигнальных и еще один для фильтра. Последний мы могли бы оформить в режиме INDICATOR_CALCULATIONS. Вместо этого сделаем его стандартным INDICATOR_DATA, но со стилем DRAW_NONE — таким образом он не будет мешаться на графике, но его значения видны в Окне данных.
Сигналы будем отображать на основном графике (на ценах Close, по умолчанию), поэтому используем директиву indicator_chart_window. Это не мешает нам вызывать индикаторы типа WPR, которые предназначены для отдельного окна, поскольку все подчиненные индикаторы способны рассчитаться без визуализации. При необходимости мы можем вывести их на график, но поговорим об этом в главе про графики (см. ChartIndicatorAdd).
#property indicator_chart_window
|
Во входных переменных предусмотрим возможность указать период WPR, период усреднения (сглаживания) и порядок фракталов — это все параметры подчиненных индикаторов. Кроме того заведем переменную Offset с номером бара, на котором будут анализироваться сигналы. Значение 0 (по умолчанию) означает текущий бар и анализ в потиковом режиме (внимание: сигналы на последнем баре могу перерисовываться — некоторые трейдеры этого не любят). Если сделать Offset равным 1, будем анализировать уже сформированные бары, и такие сигналы не меняются.
input int PeriodWPR = 11;
|
Переменная Threshold определяет размер зон перекупленности и перепроданности как часть от ±1.0 (в каждую из сторон). Например, если следовать классическим настройкам WPR с уровнями -20 и -80 на шкале от 0 до -100, то Threshold должен быть равен 0.4.
Для индикаторных буферов заведены следующие массивы.
double UpBuffer[]; // верхний сигнал значит перекупленность, то есть продажа
|
Дескрипторы индикаторов сохраним в глобальных переменных.
int handleWPR, handleEMA3, handleFractals; |
Всю настройку выполним, как обычно, в OnInit. Поскольку функция CopyBuffer использует индексацию от настоящего к прошлому, мы для единообразия чтения данных взводим флаг "серийности" (ArraySetAsSeries) у всех массивов.
int OnInit()
|
В вызовах iCustom следует обратить внимание, как создается handleEMA3. Поскольку эта средняя должна считаться по WPR, мы передаем дескриптор handleWPR (полученный в предыдущем вызове iCustom) последним параметром, после фактических параметров индикатора IndTripleEMA. При этом мы обязаны указать полный список входных параметров IndTripleEMA (напомним, что там параметрами являются int InpPeriodEMA и BEGIN_POLICY InpHandleBegin — второй параметр мы использовали для изучения пропуска начальных баров и сейчас он нам в принципе не важен, но мы обязаны его передать, в данном случае просто как 0). Если бы мы опустили второй параметр в вызове, как несущественный в текущем контексте применения, то переданный следом дескриптор handleWPR был бы принят в вызванном индикаторе за InpHandleBegin. В результате IndTripleEMA применился бы к обычной цене Close.
Когда нам не нужно передавать дополнительный дескриптор, синтаксис вызова iCustom позволяет опускать произвольное количество последних параметров, при этом они получат значения по умолчанию из исходного кода.
В обработчике OnCalculate ожидаем готовности индикаторов WPR и фракталов, а затем рассчитываем сигналы на всей истории или последнем баре с помощью вспомогательной функции MarkSignals.
int OnCalculate(const int rates_total,
|
Интересующая нас в первую очередь работа с функцией CopyBuffer скрыта в MarkSignals. Значения сглаженного WPR будем читать в массив wpr[2], а фракталы — в массивы peaks[1] и hollows[1].
int MarkSignals(const int bar, const int offset, const double &data[])
|
Заполняем локальные массивы с помощью трех вызовов CopyBuffer. Обратите внимание, что нам не нужны напрямую показания IndWPR, потому что он участвует в расчетах IndTripleEMA. Мы читаем в массив wpr данные через дескриптор handleEMA3. Также важно, что во фрактальном индикаторе существует 2 буфера, и потому функция CopyBuffer вызывается два раза с разными индексами 0 и 1 для массивов peaks и hollows, соответственно. Фрактальные массивы читаются с отступом FractalOrder, потому что фрактал может сформироваться только на баре, у которого не только слева, но и справа имеется предопределенное количество баров.
if(CopyBuffer(handleEMA3, 0, bar + offset, 2, wpr) == 2
|
Далее берем с предыдущего бара буфера Filter прежнее направление фильтра (в начале истории там 0, но при появлении фрактала вверх или вниз мы пишем туда +1 или -1, это видно в исходном коде чуть ниже) и изменяем его соответствующим образом при обнаружении любого нового фрактала.
int filterdirection = (int)Filter[bar + 1];
|
Наконец, анализируем переход сглаженного WPR из верхней или нижней зоны в среднюю, с учетом ширины зон, заданной во Threshold.
// переводим 2 величины WPR в диапазон [-1,+1]
|
Ниже представлен скриншот получившегося индикатора на графике.
Сигнальный индикатор UseWPRFractals на основе WPR, EMA3 и фракталов