Индикаторы с собственным подокном: размер и уровни

До сих пор мы ограничивались индикаторами, работающими в основном окне графика, то есть они имели директиву #property indicator_chart_window. Настало время изучить индикаторы, размещаемые в собственном подконе, под графиком котировок. Напомним, что их следует описывать с директивой #property indicator_separate_window.

Все, что мы изучили ранее, применимо и к индикатором в подокне, включая описание и привязку буферов, настройку типов и стилей рисования, применения как полной, так и сокращенной формы OnCalculate, на выбор. Однако они имеют и некоторые особенности, дополнительные настройки.

Поскольку подокно имеет собственную шкалу значений, MQL5 позволяет задать для него максимальную и минимальную величины (пользователи могут задавать подобные ограничения в диалоге настройки индикатора, на закладке Шкала). Программно это делается с помощью функции IndicatorSetDouble со следующим прототипом.

bool IndicatorSetDouble(ENUM_CUSTOMIND_PROPERTY_DOUBLE property, double value)

bool IndicatorSetDouble(ENUM_CUSTOMIND_PROPERTY_DOUBLE property, int modifier,
  double value)

Функция задает для индикатора значение свойства типа double. Две формы потребовались потому, что некоторые свойства могут существовать во множественном числе, в частности горизонтальные уровни (о них речь пойдет чуть ниже). Доступные свойства собраны в перечислении ENUM_CUSTOMIND_PROPERTY_DOUBLE.

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

Описание

INDICATOR_MINIMUM

Минимум по вертикальной оси

INDICATOR_MAXIMUM

Максимум по вертикальной оси

INDICATOR_LEVELVALUE

Значение горизонтального уровня (номер задается в параметре modifier)

Фиксированный диапазон шкалы применяется во многих осцилляторных индикаторах, например, WPR. Далее мы покажем с ним пример, охватывающий все функции (свойства) из данного раздела.

В случае успешного выполнения функция возвращает true, в противном случае false.

Кроме управления шкалой индикаторы в подокне могут иметь, как мы уже поняли, горизонтальные уровни. Чтобы задать их количество и атрибуты применяется другая функция IndicatorSetInteger. Пользователь может выполнить аналогичные действия в диалоге настройки индикатора на закладке Уровни.

bool IndicatorSetInteger(ENUM_CUSTOMIND_PROPERTY_INTEGER property, int value)

bool IndicatorSetInteger(ENUM_CUSTOMIND_PROPERTY_INTEGER property, int modifier,
  int value)

Функция также имеет две формы и позволяет задать для индикатора значение свойства типа int или эквивалентного ему (например, color или перечисление). Доступные свойства собраны в перечислении ENUM_CUSTOMIND_PROPERTY_INTEGER. Помимо свойств, связанных с уровнями, в нем присутствует свойство INDICATOR_DIGITS, являющее общим для индикаторов любых типов: его мы рассмотрим в следующем разделе.

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

Описание

INDICATOR_DIGITS

Точность отображения значений индикатора (знаки после десятичной точки)

INDICATOR_HEIGHT

Фиксированная высота собственного окна индикатора в пикселях (команда препроцессора #property indicator_height)

INDICATOR_LEVELS

Количество горизонтальных уровней в окне индикатора

INDICATOR_LEVELCOLOR

Цвет линии уровня (имеет тип color, параметр modifier задает номер уровня)

INDICATOR_LEVELSTYLE

Стиль линии уровня (имеет тип ENUM_LINE_STYLE, параметр modifier задает номер уровня)

INDICATOR_LEVELWIDTH

Толщина линии уровня (1-5) (параметр modifier задает номер уровня)

Уровни могут иметь текстовые метки. Для их назначения следует использовать функцию IndicatorSetString.

bool IndicatorSetString(ENUM_CUSTOMIND_PROPERTY_STRING property, string value)

bool IndicatorSetString(ENUM_CUSTOMIND_PROPERTY_STRING property, int modifier,
  string value)

Перечисление ENUM_CUSTOMIND_PROPERTY_STRING содержит список строковых свойств индикаторов. Здесь особо следует отметить другое свойство, не относящееся к уровням, — INDICATOR_SHORTNAME: оно также является общим для всех индикаторов и будет рассмотрено в следующем разделе.

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

Описание

INDICATOR_SHORTNAME

Публичный заголовок индикатора

INDICATOR_LEVELTEXT

Описание уровня (номер указывается в modifier)

Все упомянутые функции для числовых типов int и double дублируются специальными директивами (ниже приведена сводная таблица).

Директивы для
свойств уровней

Функции-аналоги

Тип
свойства

Описание

indicator_levelN

IndicatorSetDouble(
INDICATOR_LEVELVALUE,
N-1, value)

double

Значение для горизонтального уровня номер N на вертикальной оси

indicator_levelcolor

IndicatorSetInteger(
INDICATOR_LEVELCOLOR,
N-1, color)

color

Цвет горизонтальных уровней (разный цвет по номеру можно задать только с помощью функции)

indicator_levelwidth

IndicatorSetInteger(
INDICATOR_LEVELWIDTH,
N-1, width)

int

Толщина линий горизонтальных уровней в пикселях (разную толщину по номеру можно задать только с помощью функции)

indicator_levelstyle

IndicatorSetInteger(
INDICATOR_LEVELSTYLE,
N-1, style)

ENUM
_LINE
_STYLE

Стили линий горизонтальных уровней (разные стили по номеру можно задать только с помощью функции)

indicator_minimum

IndicatorSetDouble(
INDICATOR_MINIMUM, minimum)

double

Фиксированное минимальное значение, нижнее ограничение шкалы по вертикальной оси

indicator_maximum

IndicatorSetDouble(
INDICATOR_MAXIMUM, maximum)

double

Фиксированное максимальное значение, верхнее ограничение шкалы по вертикальной оси

Обратите внимание, что нумерация экземпляров свойств (модификаторов) при использовании директив #property начинается с 1 (единицы), в то время как функции используют нумерацию с 0 (нуля).

Внимательный читатель заметит, что для некоторых свойств не существует директив. К их числу относятся INDICATOR_LEVELTEXT, INDICATOR_SHORTNAME, INDICATOR_DIGITS. Предполагается, что данные свойства должны заполняться динамически из MQL-кода, в зависимости от входных переменных и графика, на котором размещен индикатор. INDICATOR_LEVELS задается опосредованно, за счет указания нескольких директив для уровней.  

Наконец, отличительной чертой индикаторов в подокне является то, что программа может "заморозить" вертикальный размер своего окна.

Директива для
размера подокна

Функция-аналог

Описание

indicator_height

IndicatorSetInteger(
INDICATOR_HEIGHT, height)

Фиксированная высота подокна индикатора в пикселях (пользователь не сможет изменить высоту)

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

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

В качестве примера работы с фиксированной шкалой и уровнями рассмотрим индикатор IndWPR.mq5. В нем воспользуемся стандартным алгоритмом WPR: на заданном количестве прошедших баров (период WPR) найдем максимумы H и минимумы L цены (то есть её размах). Затем вычислим отношение разницы текущей цены C и минимума L, C - L (или разницы -(H - C), со знаком минус) ко всему размаху, и приведем всё в диапазон от 0 до -100. Вот каноническая формула расчета WPR:

R% = (-(H – C) / (H – L)) * 100

В начале исходного кода добавим несколько директив. Помимо свойства расположения индикатора в собственном окне установим шкалу значений от 0 до -100.

#property indicator_separate_window
#property indicator_maximum    0.0
#property indicator_minimum    -100.0

Для хранения значений и отображения достаточного одного буфера и одной линейной диаграммы.

#property indicator_buffers    1
#property indicator_plots      1
#property indicator_type1      DRAW_LINE
#property indicator_color1     clrDodgerBlue

В индикаторе WPR принято выделять два уровня: -20 и -80, как границы областей перекупленности и перепроданности, соответственно. Создадим для них пару горизонтальных линий.

#property indicator_level1     -20.0
#property indicator_level2     -80.0
#property indicator_levelstyle STYLE_DOT
#property indicator_levelcolor clrSilver
#property indicator_levelwidth 1

Единственная входная переменная позволяет задать период расчета WPR.

input int WPRPeriod = 14// Period

Массив для буфера описывается на глобальном уровне и регистрируется в OnInit.

double WPRBuffer[];
   
void OnInit()
{
   // проверка на корректность ввода
   if(WPRPeriod < 1)
   {
      Alert(StringFormat("Incorrect Period value (%d). Should be 1 or larger",
         WPRPeriod));
   }
   
   // привязка массива как буфера
   SetIndexBuffer(0WPRBuffer);
}

Обработчик OnInit описан с типом void, что неявным образом подразумевает успешную инициализацию. Вместе с тем, если задан период меньше 1, это не позволит делать расчет, в связи с чем выдается предупреждение пользователю.

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

#define ON_CALCULATE_STD_FULL_PARAM_LIST \
const int rates_total,     \
const int prev_calculated, \
const datetime &time[],    \
const double &open[],      \
const double &high[],      \
const double &low[],       \
const double &close[],     \
const long &tick_volume[], \
const long &volume[],      \
const int &spread[]
   
#define ON_CALCULATE_STD_SHORT_PARAM_LIST \
const int rates_total,     \
const int prev_calculated, \
const int begin,           \
const double &data[]

Теперь мы можем применять в этом и других индикаторах сжатое определение OnCalculate (при условии, что нас устраивают предложенные названия параметров в макросах).

#include <MQL5Book/IndCommon.mqh>
 
int OnCalculate(ON_CALCULATE_STD_FULL_PARAM_LIST)
{
   if(rates_total < WPRPeriod || WPRPeriod < 1return 0;
   ...
   return rates_total;
}

В начале OnCalculate делается проверка на возможность расчета при текущих величинах WPRPeriod и rates_total. Если данных недостаточно или период слишком маленький, возвращаем 0, из-за чего окно индикатора останется пустым.

Далее заполняем первые несколько баров, для которых невозможно посчитать WPR заданного периода, пустым значением.

int OnCalculate(ON_CALCULATE_STD_FULL_PARAM_LIST)
{
   ...
   if(prev_calculated == 0)
   {
      ArrayFill(WPRBuffer0WPRPeriod - 1EMPTY_VALUE);
   }
   ...
}

Наконец, запускаем рабочий цикл с расчетом по формуле WPR и складываем результаты в буфер. Обратите внимание, что последний бар обновляется на каждом тике: это достигается за счет того, что цикл начинается с prev_calculated - 1.

int OnCalculate(ON_CALCULATE_STD_FULL_PARAM_LIST)
{
   ...
   for(int i = fmax(prev_calculated - 1WPRPeriod - 1);
      i < rates_total && !IsStopped(); i++)
   {
      double max_high = high[fmax(ArrayMaximum(highi - WPRPeriod + 1WPRPeriod), 0)];
      double min_low = low[fmax(ArrayMinimum(lowi - WPRPeriod + 1WPRPeriod), 0)];
      if(max_high != min_low)
      {
         WPRBuffer[i] = -(max_high - close[i]) * 100 / (max_high - min_low);
      }
      else
      {
         WPRBuffer[i] = WPRBuffer[i - 1];
      }
   }
   return rates_total;
}

Для поиска индексов максимальных high и минимальных low применяются функции ArrayMaximum и ArrayMinimum.

Посмотрим, как индикатор выглядит в собственном окне.

Индикатор WPR

Индикатор WPR

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