Назначение массива в качестве буфера: SetIndexBuffer

Роль индикаторных буферов могут выполнять любые динамические массивы типа double со временем существования от запуска программы и до её остановки. Самый распространенный способ определения такого массива — на глобальном уровне. Но в некоторых случаях удобнее делать массивы членами классов, после чего создавать глобальные объекты с массивами. Мы рассмотрим примеры такого подхода, когда реализуем мультивалютный индикатор (см. пример IndUnityPercent.mq5 в разделе Мультивалютные и мультитаймфреймовые индикаторы) и индикатор дельты объемов (см. IndDeltaVolume.mq5 в разделе Ожидание данных и управление видимостью).

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

double buffer[];

Чтобы зарегистрировать его как буфер, в терминале существует специальная функция SetIndexBuffer. Как правило, она вызывается в обработчике OnInit, как и многие другие функции для настройки индикатора, которые мы рассмотрим позднее.

bool SetIndexBuffer(int index, double buffer[],
  ENUM_INDEXBUFFER_TYPE mode = INDICATOR_DATA)

Функция связывает указанный по индексу (index) индикаторный буфер с динамическим массивом buffer. Значение index должно лежать в пределах от 0 до N - 1, где N — количество буферов, определенное директивой #property indicator_buffers.

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

Направление индексации после связывания массива с индикаторным буфером по умолчанию устанавливается как в обычных массивах, но при необходимости его можно изменить с помощью функцию ArraySetAsSeries.

Функция SetIndexBuffer возвращает признак успешного выполнения (true) или ошибки (false).

Опциональный параметр mode указывает системе, каким образом будет использоваться буфер. Возможные значения собраны в перечислении ENUM_INDEXBUFFER_TYPE.

Идентификатор

Описание

INDICATOR_DATA

данные для отрисовки

INDICATOR_COLOR_INDEX

цвета отрисовки

INDICATOR_CALCULATIONS

внутренние результаты промежуточных вычислений

По умолчанию индикаторный буфер предназначен для отрисовки данных (INDICATOR_DATA). Это значение привносит еще один эффект помимо отображения массива на графике: значение каждого буфера для бара под курсором мыши показываются в Окне данных. Правда, с помощью некоторых настроек индикатора это поведение можно изменить (см. свойство PLOT_SHOW_DATA в разделе Настройка графических построений). Большинство примеров в данной Главе относится к режиму INDICATOR_DATA.

Если для расчета индикатора требуется хранить промежуточные результаты для каждого бара, для них можно выделить вспомогательный неотображаемый буфер (INDICATOR_CALCULATIONS). Это более удобно, чем использование для тех же целей обычного массива, поскольку тогда программист должен самостоятельно управлять его размером. В данной Главе будет представлено два примера с INDICATOR_CALCULATIONS: IndTripleEMA.mq5 (см. раздел Пропуск отрисовки на начальных барах) и IndSubChartSimple.mq5 (см. раздел Мультивалютные и мультитаймфреймовые индикаторы).

Некоторые построения позволяют задавать для каждого бара цвет отображения. Для хранения информации о цвете используются цветовые буфера (INDICATOR_COLOR_INDEX). Цвет представлен целочисленным типом color, но все индикаторные буфера должны иметь тип double, и в них в данном случае хранится номер цвета из особой палитры, заданной разработчиком (см. раздел Поэлементное раскрашивание диаграмм и пример индикатора IndColorWPR.mq5 в нем).

Значения цветовых и вспомогательных буферов не отображаются в Окне данных, а также их нельзя получить с помощью функции CopyBuffer, которую мы изучим позднее в Главе про Использование встроенных и пользовательских индикаторов из MQL5.

Индикаторный буфер не инициализируется никакими значениями. Если какие-то его элементы не рассчитываются по тем или иным причинам (например, в настройках индикатора есть ограничение на максимальное количество баров или само графическое построение подразумевает редкие значащие элементы, между которыми должны быть пропуски, как между вершинами ZigZag-а), то их следует явным образом заполнить специальным "пустым" значением. "Пустое" значение не отображается на графике и не выводится в Окне данных. По умолчанию для него существует константа EMPTY_VALUE (DBL_MAX), но при необходимости его можно заменить на любое другое, например, на "обычный" 0. Это делается с помощью функции PlotIndexSetDouble.

С учетом новых знаний о функции SetIndexBuffer дополним наш очередной пример IndReplica1.mq5, который мы начали в предыдущем разделе. В частности, нам потребуется обработчик OnInit.

#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots 1
 
#include <MQL5Book/PRTF.mqh>
 
double buffer[]; // глобальный динамический массив
 
int OnInit()
{
   // регистрируем массив в качестве индикаторного буфера
   PRTF(SetIndexBuffer(0buffer)); // true / ok
   // здесь сделан намеренно второй некорректный вызов, чтобы показать ошибку
   PRTF(SetIndexBuffer(1buffer)); // false / BUFFERS_WRONG_INDEX(4602)
   // проверяем размер: по-прежнему 0
   PRTF(ArraySize(buffer)); // 0
   return INIT_SUCCEEDED;
}

Количество буферов определено директивой равным 1, поэтому назначение массива для единственного буфера использует индекс 0 (первый параметр SetIndexBuffer). Второй вызов функции является ошибочным и сделан только для демонстрации проблемы: поскольку индекс 1 подразумевает наличие двух объявленных буферов, он генерирует ошибку BUFFERS_WRONG_INDEX (4602).

В самом начале функции OnCalculate выведем размер массива еще раз. В этом месте он уже будет распределен под количество баров.

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &data[])
{
   // после старта проверяем, что платформа автоматически управляет размером массива
   if(prev_calculated == 0)
   {
      PRTF(ArraySize(buffer)); // 10189 - актуальное количество баров
   }
   ...

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

   ...
   // на каждом новом баре или множесте баров (включая первый расчет)
   if(prev_calculated != rates_total)
   {
      // заполняем все новые бары
      ArrayCopy(bufferdataprev_calculatedprev_calculated);
   }
   else // тики на текущем баре
   {
      // обновляем последний бар
      buffer[rates_total - 1] = data[rates_total - 1];
   }
   
   // сообщаем количество обработанных баров самим себе в будущем
   return rates_total;
}

Теперь индикатор компилируется без предупреждений. Мы можем запустить на графике, и при настройках по умолчанию он должен дублировать в буфере значения цены закрытия баров. Это происходит благодаря краткой форме OnCalculate, мы рассматривали этот аспект в разделе Главное событие индикаторов: OnCalculate.

Однако мы обнаружим странную вещь: в Окне данных наш буфер действительно выводится и содержит правильные значения, однако на графике линии нет. Это следствие того, что за отображение отвечают графические построения, а не буфера. В текущей версии индикатора мы настроили только буфер. В следующем разделе мы создадим новую версию IndReplica2.mq5 и дополним необходимыми инструкциями.

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

О том, как управлять индикаторами из MQL5-кода, будет рассказано в следующей главе.