Визуализация пропусков данных (пустых элементов)

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

Для установки "пустого" значения, которое не выводится ни на графике, ни в Окне данных, применяется функция PlotIndexSetDouble.

bool PlotIndexSetDouble(int index, ENUM_PLOT_PROPERTY_DOUBLE property, double value)

Функция предназначена для установки у диаграммы под номером index свойств типа double. Набор таких свойств сведен в перечисление ENUM_PLOT_PROPERTY_DOUBLE, но на текущий момент в нем только один элемент: PLOT_EMPTY_VALUE. Он и задает "пустое" значение. Само значение передается в последнем параметре value.

В качестве подходящего примера индикатора с "редкими" значениями мы рассмотрим довольно известный детектор фракталов. Суть его работы в том, чтобы пометить на графике высокие цены (High), которые выше N соседних баров, и низкие цена (Low), которые ниже N соседних баров, в обе стороны. Файл индикатора называется IndFractals.mq5.

В индикаторе будет два буфера и два графических построения типа DRAW_ARROW.

#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   2
   
// настройки отрисовки
#property indicator_type1   DRAW_ARROW
#property indicator_type2   DRAW_ARROW
#property indicator_color1  clrBlue
#property indicator_color2  clrRed
#property indicator_label1  "Fractal Up"
#property indicator_label2  "Fractal Down"
   
// индикаторные буфера
double UpBuffer[];
double DownBuffer[];

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

input int FractalOrder = 3;

Для символов-стрелок предусмотрим отступ в 10 пикселей от экстремумов для лучший видимости.

const int ArrowShift = 10;

В функции OnInit выполним традиционные действия по объявлению массивов буферами и их привязке к визуализируемым диаграммам.

int OnInit()
{
   // привязка буферов
   SetIndexBuffer(0UpBufferINDICATOR_DATA);
   SetIndexBuffer(1DownBufferINDICATOR_DATA);
   
   // коды символов-стрелок вверх и вниз
   PlotIndexSetInteger(0PLOT_ARROW217);
   PlotIndexSetInteger(1PLOT_ARROW218);
   
   // отступ для стрелок
   PlotIndexSetInteger(0PLOT_ARROW_SHIFT, -ArrowShift);
   PlotIndexSetInteger(1PLOT_ARROW_SHIFT, +ArrowShift);
   
   // установка "пустого" значения (можно опустить, т.к. EMPTY_VALUE по умолчанию)
   PlotIndexSetDouble(0PLOT_EMPTY_VALUEEMPTY_VALUE);
   PlotIndexSetDouble(1PLOT_EMPTY_VALUEEMPTY_VALUE);
   
   return FractalOrder > 0 ? INIT_SUCCEEDED : INIT_PARAMETERS_INCORRECT;
}

Обратите внимание, что по умолчанию "пустым" значением является специальная константа EMPTY_VALUE, поэтому вышеприведенные вызовы PlotIndexSetDouble необязательны.

В обработчике OnCalculate в момент первого вызова инициализируем оба массива значением EMPTY_VALUE, и далее присваиваем его новым элементам по мере формирования баров. Заполнение необходимо, потому что распределенная под буфера память может содержать произвольные данные (мусор).

int OnCalculate(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[])
{
   if(prev_calculated == 0)
   {
      // на старте заполняем массивы целиком
      ArrayInitialize(UpBufferEMPTY_VALUE);
      ArrayInitialize(DownBufferEMPTY_VALUE);
   }
   else
   {
      // на новых барах также чистим элементы
      for(int i = prev_calculatedi < rates_total; ++i)
      {
         UpBuffer[i] = EMPTY_VALUE;
         DownBuffer[i] = EMPTY_VALUE;
      }
   }
   ...

В основном цикле по барам сравниваем цены high и low с этими же типами цен на соседних барах и оставляем метки там, где обнаруживается экстремум среди FractalOrder баров в каждую из сторон.

   // просматриваем все или новые бары, имеющие в окружении FractalOrder баров
   for(int i = fmax(prev_calculated - FractalOrder - 1FractalOrder);
       i < rates_total - FractalOrder; ++i)
   {
      // проверяем, что верхняя цена выше соседних баров
      UpBuffer[i] = high[i];
      for(int j = 1j <= FractalOrder; ++j)
      {
         if(high[i] <= high[i + j] || high[i] <= high[i - j])
         {
            UpBuffer[i] = EMPTY_VALUE;
            break;
         }
      }
      
      // проверяем, что нижняя цена ниже соседних баров
      DownBuffer[i] = low[i];
      for(int j = 1j <= FractalOrder; ++j)
      {
         if(low[i] >= low[i + j] || low[i] >= low[i - j])
         {
            DownBuffer[i] = EMPTY_VALUE;
            break;
         }
      }
   }
   
   return rates_total;
}

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

Индикатор фракталов

Индикатор фракталов

Теперь заменим тип отрисовки с DRAW_ARROW на DRAW_ZIGZAG и сравним эффект "пустых" значений для обоих вариантов. Очевидно, что в результате должен получиться зигзаг на фракталах. Модифицированная версия индикатора прилагается в файле IndFractalsZigZag.mq5.

Одно из главных изменений касается количества диаграмм: она теперь одна, так как DRAW_ZIGZAG "потребляет" оба буфера.

#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   1
   
// настройки отрисовки
#property indicator_type1   DRAW_ZIGZAG
#property indicator_color1  clrMediumOrchid
#property indicator_width1  2
#property indicator_label1  "ZigZag Up;ZigZag Down"
...

Из OnInit уходят все вызовы функций, связанных с настройкой стрелок.

int OnInit()
{
   SetIndexBuffer(0UpBufferINDICATOR_DATA);
   SetIndexBuffer(1DownBufferINDICATOR_DATA);
   
   PlotIndexSetDouble(0PLOT_EMPTY_VALUEEMPTY_VALUE);
   
   return FractalOrder > 0 ? INIT_SUCCEEDED : INIT_PARAMETERS_INCORRECT;
}

В остальном исходный код без изменений.

На следующем изображении приведен график, на который зигзаг наложен в дополнение к фракталам: тем самым можно наглядно сравнить их результаты. Оба индикатора работают совершенно независимо, но из-за одинакового алгоритма найденные экстремумы совпадают.

Индикатор Зиг-Заг по фракталам

Индикатор Зиг-Заг по фракталам

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

Также следует отметить, что для отрисовки DRAW_ZIGZAG (так же, как и DRAW_SECTION), видимые отрезки соединяют "непустые" элементы и потому, строго говоря, какой-то фрагмент отрезка рисуется на каждом баре, включая и те, что имеют значение EMPTY_VALUE (или другое назначенное вместо него). Однако в Окне данных четко видно, что "пустые" элементы действительно пусты: для них значения не выводятся.