Статистические распределения в виде гистограмм без индикаторных буферов и массивов

Sergey Pavlov | 1 ноября, 2016

Введение

Гистограмма — инструмент, позволяющий зрительно оценить распределение статистических данных, сгруппированных по частоте их попадания в определённый (заранее заданный) интервал.

Построение гистограмм и использование их в анализе статистических данных — достаточно изученная тема, которой посвящено множество статей [1, 2, 3, 4, 5, 6, 7] и по которой создано изрядное количество примеров в CodeBase [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]. Однако используемые алгоритмы базируются на применении индикаторных буферов или массивов. В данной статье рассмотрена возможность построения статистических распределений различных характеристик рынка без сложных расчётов, сортировок, выборок и т.п. Для этого воспользуемся "графической" памятью — тем разделом, где хранятся свойства графических объектов. Дело в том, что при построении пользовательских гистограмм так или иначе используются графические объекты, так почему бы не задействовать их "скрытые" возможности на полную мощь и не воспользоваться имеющимся богатым функционалом?

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

Принцип построения гистограмм

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

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

Рис. 1. Вертикальная гистограмма распределения цен Bid и Ask. 

Рис. 1. Вертикальная гистограмма распределения цен Bid и Ask.

Рассмотрим конкретную задачу:

Теперь усложним условие: нельзя использовать индикаторные буферы, массивы или структуры.

Как решить эту проблему? 

Во-первых, нужно придумать, где сохранять накопленные частоты для каждого столбца гистограммы. Даже на рис. 1. видно, что таких столбцов может быть сколько угодно, и первое что приходит в голову — это использовать массив. Причём динамический, т.к. заранее неизвестен возможный диапазон цен (количество столбцов) на выбранном временном участке ценового графика. Но по условию задачи это запрещено.

Во-вторых, нужно решить задачу поиска и сортировки: где и как искать данные для пересчёта и перерисовки гистограммы.

Оказывается, что разработчиками языка MQL5 уже давно создан необходимый для решения поставленной задачи функционал, причём достаточно мощный. Да, уважаемый читатель, предлагается использовать "скрытые" (неочевидные) возможности группы функций графических объектов. У каждого объекта есть свойства — это такие переменные, которые создаются вместе с объектом и служат для хранения множества различных параметров. Некоторые свойства можно использовать не совсем по назначению, и при этом функциональность не будет потеряна. Назовём такие свойства "графической" памятью. Другими словами, если нам нужно сохранить какую-то переменную и при необходимости получить её значение, то создаём графический объект и присваиваем определённому свойству значение переменной.

Свойства графических объектов — это своего рода аналог глобальных переменных терминала, только лучше. Глобальные переменные существуют в клиентском терминале 4 недели от времени последнего обращения к ним, а потом автоматически уничтожаются. Графическая память существует до тех пор, пока графический объект не будет удалён. Представляете, какие возможности нам это открывает? 

Какие свойства графических объектов можно задействовать в графической памяти

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

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

У любого графического объекта есть координаты: цена OBJPROP_PRICE и время OBJPROP_TIME. Их также можно использовать в графической памяти.

Вернёмся к нашей задаче. Частоты будем хранить в свойстве OBJPROP_TEXT, а значения цен Bid и Ask — в свойстве OBJPROP_NAME. Код функции, которая создаёт объекты и накапливает частоты, приведён ниже:

void DrawHistogram(bool draw,     // слева или справа рисуем гистограмму
                   string h_name, // уникальный префикс для имени объекта
                   double price,  // цена (исследуемый параметр)
                   datetime time, // привязка гистограммы к текущему бару
                   int span,      // разрядность исследуемого параметра
                   int swin=0)    // окно гистограммы
  {
   double y=NormalizeDouble(price,span);
   string pfx=DoubleToString(y,span);
// если draw=true, то рисуем гистограмму справа
   if(draw)
     {
      string name="+ "+h_name+pfx;                   // имя объекта: префикс+цена
      ObjectCreate(0,name,OBJ_TREND,swin,time,y);    // создаём объект
      ObjectSetInteger(0,name,OBJPROP_COLOR,color_R_active); // задаём цвет объекту
      ObjSet;                                                // макрос для сокращения кода
      if(StringFind(ObjectGetString(0,name,OBJPROP_TEXT),"*",0)<0)
        {// если полученая цена попала в выборку впервые
         ObjectSetString(0,name,OBJPROP_TEXT,"*1");          // частота для данной цены равна 1
         ObjectSetInteger(0,name,OBJPROP_TIME,1,time+hsize); // определили координату времени
        }
      else
        {// если полученая цена попала в выборку уже не в первый раз
         string str=ObjectGetString(0,name,OBJPROP_TEXT);    // получаем значение свойства
         string strint=StringSubstr(str,1);                  // выделяем подстроку
         long n=StringToInteger(strint);                     // получили частоту для дальнейших расчётов
         n++;                                                // увеличили значение на 1
         ObjectSetString(0,name,OBJPROP_TEXT,"*"+(string)n); // записали в свойство новое значение
         ObjectSetInteger(0,name,OBJPROP_TIME,1,time+hsize*n);//определили координату времени
        }
     }
// если draw=false, то рисуем гистограмму слева
   if(!draw)
     {
      string name="- "+h_name+pfx;
      ObjectCreate(0,name,OBJ_TREND,swin,time,y);
      ObjectSetInteger(0,name,OBJPROP_COLOR,color_L_active);
      ObjSet;
      if(StringFind(ObjectGetString(0,name,OBJPROP_TEXT),"*",0)<0)
        {
         ObjectSetString(0,name,OBJPROP_TEXT,"*1");
         ObjectSetInteger(0,name,OBJPROP_TIME,1,time-hsize);
        }
      else
        {
         string str=ObjectGetString(0,name,OBJPROP_TEXT);
         string strint=StringSubstr(str,1);
         long n=StringToInteger(strint);
         n++;
         ObjectSetString(0,name,OBJPROP_TEXT,"*"+(string)n);
         ObjectSetInteger(0,name,OBJPROP_TIME,1,time-hsize*n);
        }
     }
   ChartRedraw();
  }

Функция состоит из двух аналогичных блоков: отдельно для Bid и Ask. Поэтому комментарии есть только в первом блоке.

У внимательного читателя наверняка возник резонный вопрос: а зачем дублировать цену в имени объекта, если она и так доступна в свойстве OBJPROP_PRICE? В чём смысл этой работы?

На этом моменте остановимся поподробнее. При поступлении новой цены необходимо определить, в какой столбец она попадёт, и там, соответственно, увеличить значение частоты. Если бы использовалась координата цены непосредственно из своего свойства, то пришлось бы перебрать все графические объекты, запросить значение этого свойства, сравнить с поступившей ценой и только после этого записать новое значение частоты в нужный столбец. А сравнивать действительные числа типа double — это та ещё "засада". В общем, очень неэффективный алгоритм. Другое дело, когда при поступлении нового значения цены мы сразу записываем изменившуюся частоту туда, куда надо. Каким образом? Дело в том, что при создании нового графического объекта с таким же именем, с которым уже есть существующий, новый объект не создаётся и поля свойств не обнуляются. Другими словами, мы создаём объекты, не задумываясь о том, что они будут дублироваться. Не нужно делать дополнительных проверок, потому что копии и клоны создаваться не будут. Об этом позаботится функционал терминала и графических объектов. Остаётся только лишь определить, впервые эта цена попала в выборку или нет. Для этого в рассматриваемой функции DrawHistogram() используется префикс "звёздочка" (*) в свойстве объекта OBJPROP_TEXT. Если "звёздочки" нет, то такая цена поступила впервые. Действительно, когда мы создаем объект впустую, это поле пусто. При последующих обращениях там будет храниться значение частоты с заданным префиксом.

Следующим шагом мы делаем смещение гистограмм вправо. При появлении нового бара график сдвигается влево, а надо, чтобы гистограмма всегда отображалась на текущем баре, т.е. сдвигалась в противоположную сторону. Рассмотрим, как это сделать:

//--- Смещение диаграмм на новый бар
   if(time[0]>prevTimeBar) // определяем появление нового бара
     {
      prevTimeBar=time[0];
      // делаем перебор всех графических объектов
      for(int obj=ObjectsTotal(0,-1,-1)-1;obj>=0;obj--)
        {
         string obj_name=ObjectName(0,obj,-1,-1);               // получаем имя найденного объекта
         if(obj_name[0]==R)                                     // ищем префикс элемента гистограммы
           {                                                    // если нашли элемент гистограммы
            ObjectSetInteger(0,obj_name,OBJPROP_TIME,           // устанавливаем новое значение координаты
                             0,time[0]);                        // для "0" точки привязки
            string str=ObjectGetString(0,obj_name,OBJPROP_TEXT);// читаем переменную из свойства объекта
            string strint=StringSubstr(str,1);                  // выделяем подстроку из полученной переменной
            long n=StringToInteger(strint);                     // преобразуем строку в переменную long
            ObjectSetInteger(0,obj_name,OBJPROP_TIME,           // рассчитываем новое значение координаты
                             1,time[0]+hsize*n);                // для "1" точки привязки
            ObjectSetInteger(0,obj_name,OBJPROP_COLOR,
                             color_R_passive);                  // изменяем цвет сдвинутого элемента гистограммы
           }
         if(obj_name[0]==L)
           {
            ObjectSetInteger(0,obj_name,OBJPROP_TIME,0,time[0]);
            string str=ObjectGetString(0,obj_name,OBJPROP_TEXT);
            string strint=StringSubstr(str,1);
            long n=StringToInteger(strint);
            ObjectSetInteger(0,obj_name,OBJPROP_TIME,1,time[0]-hsize*n);
            ObjectSetInteger(0,obj_name,OBJPROP_COLOR,color_L_passive);
           }
        }
      ChartRedraw();
     }

Вот, собственно, и всё. Гистограмма цен Bid и Ask строится на графике, и программа не использует ни одного массива или индикаторного буфера. Полностью код этого решения находится в приложении к статье.


На видео показано готовое решение аналогичной задачи с использованием графической памяти. Сам код можно найти в CodeBase: индикатор "Histogram bid and ask prices".

Примеры построения гистограмм в главном окне

Технология программирования функционала с использованием графической памяти рассмотрена, теперь давайте построим несколько гистограмм для стандартных индикаторов. Принцип построения аналогичен рассмотренному, только вместо цен Bid и Ask будем использовать значения индикаторов на текущем баре.

     1. Индикатор iMA. Возьмём два индикатора с разными периодами усреднения и построим гистограммы. Вот как выглядит код:

input int               period_MA1=20;       // Averaging period of iMA1 
input int               period_MA2=14;       // Averaging period of iMA2 
//---- indicator buffers
double      MA1[];
double      MA2[];
//---- handles for indicators
int         iMA1_handle;
int         iMA2_handle;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   ObjectsDeleteAll(0,-1,-1);
   ChartRedraw();
   iMA1_handle=iMA(_Symbol,_Period,period_MA1,0,MODE_SMA,PRICE_CLOSE);
   iMA2_handle=iMA(_Symbol,_Period,period_MA2,0,MODE_SMA,PRICE_CLOSE);
   ArraySetAsSeries(MA1,true);
   ArraySetAsSeries(MA2,true);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
   ArraySetAsSeries(time,true);
   
   CopyBuffer(iMA1_handle,0,0,1,MA1);
   CopyBuffer(iMA2_handle,0,0,1,MA2);

   DrawHistogram(true,"iMA("+(string)period_MA1+")=",MA1[0],time[0],_Digits);
   DrawHistogram(false,"iMA("+(string)period_MA2+")=",MA2[0],time[0],_Digits);

//--- Смещение диаграмм на новый бар
   if(time[0]>prevTimeBar) // определяем появление нового бара
     {
      prevTimeBar=time[0];
      // делаем перебор всех графических объектов
      for(int obj=ObjectsTotal(0,-1,-1)-1;obj>=0;obj--)
        {
         string obj_name=ObjectName(0,obj,-1,-1);               // получаем имя найденного объекта
         if(obj_name[0]==R)                                     // ищем префикс элемента гистограммы
           {                                                    // если нашли элемент гистограммы
            ObjectSetInteger(0,obj_name,OBJPROP_TIME,           // устанавливаем новое значение координаты
                             0,time[0]);                        // для "0" точки привязки
            string str=ObjectGetString(0,obj_name,OBJPROP_TEXT);// читаем переменную из свойства объекта
            string strint=StringSubstr(str,1);                  // выделяем подстроку из полученной переменной
            long n=StringToInteger(strint);                     // преобразуем строку в переменную long
            ObjectSetInteger(0,obj_name,OBJPROP_TIME,           // рассчитываем новое значение координаты
                             1,time[0]+hsize*n);                // для "1" точки привязки
            ObjectSetInteger(0,obj_name,OBJPROP_COLOR,
                             color_R_passive);                  // изменяем цвет сдвинутого элемента гистограммы
           }
         if(obj_name[0]==L)
           {
            ObjectSetInteger(0,obj_name,OBJPROP_TIME,0,time[0]);
            string str=ObjectGetString(0,obj_name,OBJPROP_TEXT);
            string strint=StringSubstr(str,1);
            long n=StringToInteger(strint);
            ObjectSetInteger(0,obj_name,OBJPROP_TIME,1,time[0]-hsize*n);
            ObjectSetInteger(0,obj_name,OBJPROP_COLOR,color_L_passive);
           }
        }
      ChartRedraw();
     }
   return(rates_total);
  }

Рис. 2. Гистограмма 2-х индикаторов iMA.

Рис. 2. Гистограмма 2-х индикаторов iMA. 

     2. Индикатор iBands. Для этого индикатора построим гистограммы для верхней (UPPER_BAND) и нижней (LOWER_BAND) границ. Сокращённый код показан ниже.

input int   period_Bands=14;           // Averaging period of iBands 
//---- indicator buffers
double      UPPER[];
double      LOWER[];
//---- handles for indicators
int         iBands_handle;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   ObjectsDeleteAll(0,-1,-1);
   ChartRedraw();
   iBands_handle=iBands(_Symbol,_Period,period_Bands,0,5.00,PRICE_CLOSE);
   ArraySetAsSeries(UPPER,true);
   ArraySetAsSeries(LOWER,true);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
//---
   ArraySetAsSeries(time,true);
   
   CopyBuffer(iBands_handle,1,0,1,UPPER);
   CopyBuffer(iBands_handle,2,0,1,LOWER);

   DrawHistogram(true,"iBands(UPPER)=",UPPER[0],time[0],_Digits);
   DrawHistogram(false,"iBands(LOWER)=",LOWER[0],time[0],_Digits);

//--- Смещение диаграммы на новый бар
   if(time[0]>prevTimeBar)
     {
     ...
     }
   return(rates_total);
  }

Рис. 3. Гистограмма полос Боллинджера индикатора iBands.

Рис. 3. Гистограмма полос Боллинджера индикатора iBands. 

Любопытные гистограммы получатся для индикатора Боллинджера, если использовать все 3 индикаторных буфера (0 - BASE_LINE, 1 - UPPER_BAND, 2 - LOWER_BAND). Гистограмму верхней и нижней полос расположим справа от текущего бара, а гистограмму базовой линии — слева от нулевого бара. Результат показан на следующем рисунке:

Рис. 4. Гистограмма полос Боллинджера на 3 индикаторных буферах.

Рис. 4. Гистограмма полос Боллинджера на 3 индикаторных буферах.  

Код для этого варианта гистограмм приведён ниже. Обратите внимание, что справа отображаются фактически две гистограммы: для верхней и нижней границ индикатора. При этом визуально они воспринимаются как одна единая диаграмма.

input int   period_Bands=20;       // Averaging period of iBands 
//---- indicator buffers
double      BASE[];
double      UPPER[];
double      LOWER[];
//---- handles for indicators
int         iBands_handle;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   ObjectsDeleteAll(0,-1,-1);
   ChartRedraw();
   iBands_handle=iBands(_Symbol,_Period,period_Bands,0,10.00,PRICE_CLOSE);
   ArraySetAsSeries(BASE,true);
   ArraySetAsSeries(UPPER,true);
   ArraySetAsSeries(LOWER,true);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
//---
   ArraySetAsSeries(time,true);
   
   CopyBuffer(iBands_handle,0,0,1,BASE);
   CopyBuffer(iBands_handle,1,0,1,UPPER);
   CopyBuffer(iBands_handle,2,0,1,LOWER);

   DrawHistogram(true,"iBands(UPPER)=",UPPER[0],time[0],_Digits);
   DrawHistogram(true,"iBands(LOWER)=",LOWER[0],time[0],_Digits);
   DrawHistogram(false,"iBands(LOWER)=",BASE[0],time[0],_Digits);

//--- Смещение диаграммы на новый бар
   if(time[0]>prevTimeBar)
     {
     ...
     }
   return(rates_total);
  }

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

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

А теперь посмотрим, как изменить интервал распределения, в котором будет несколько значений варианты, используя всего одну переменную и мощь функционала графических объектов. В рассмотренной выше функции DrawHistogram() есть входной параметр span — разрядность исследуемого параметра. Так вот, варьируя разрядность, мы можем изменять интервал для расчёта частот гистограммы. Поскольку в имя графического объкта влючена цена, то отбор происходит автоматически на этапе создания и корректировки свойств объектов. Посмотрите, как легко решаются задачи сортировки и группировки с использованием графической памяти без программирования.

Рис. 5. Гистограмма цен Bid и Ask с увеличенным интервалом столбиков.

Рис. 5. Гистограмма цен Bid и Ask с увеличенным интервалом столбиков. 

Итак, построить гистограммы в главном окне графика оказалось очень просто. Теперь разберёмся с тем, как сделать подобные диаграммы в дополнительных окнах графика.

Гистограммы в дополнительных окнах

Принцип создания статистических распределений в дополнительных окнах такой же, как и в главном. Разница в том, что значения исследуемых величин могут иметь отрицательные величины и произвольную разрядность. Так, в главном окне ценовая шкала имеет заранее известную разрядность, которую можно получить с помощью переменной _Digits. В дополнительных окнах переменные могут принимать как положительные, так и отрицательные значения. Кроме того, количество значащих цифр после запятой может быть каким угодно. Поэтому эту особенность нужно учесть при задании входных параметров функции DrawHistogram() для построения гистограмм, а также указать номер дополнительного окна. По умолчанию, в функции используется главное окно, которое равно нулю.

Примеры построения гистограмм в дополнительных окнах

Лучше всего информация усваивается на примерах. Ими и займемся. Расмотрим несколько примеров построения гистограмм в дополнительных окнах для стандартных технических индикаторов.

     1. Индикатор iChaikin.

Рис. 6. Гистограмма индикатора Chaikin Oscillator.

Рис. 6. Гистограмма индикатора Chaikin Oscillator.

Сокращённый код для этого индикатора:

input int   period_fast=3;       // Averaging period fast of iChaikin 
input int   period_slow=10;      // Averaging period of slow iChaikin 
//---- indicator buffers
double      CHO[];
//---- handles for indicators
int         iChaikin_handle;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   ObjectsDeleteAll(0,-1,-1);
   ChartRedraw();
   iChaikin_handle=iChaikin(_Symbol,_Period,period_fast,period_slow,MODE_SMA,VOLUME_TICK);
   ArraySetAsSeries(CHO,true);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
//---
   ArraySetAsSeries(time,true);
   
   CopyBuffer(iChaikin_handle,0,0,2,CHO);

   DrawHistogram(true,"iChaikin=",CHO[0],time[0],0,1);
   
//--- Смещение диаграммы на новый бар
   if(time[0]>prevTimeBar)
     {
     ...
     }
   return(rates_total);
  }

     2. Индикатор iCCI.

Рис. 7. Гистограмма индикатора Commodity Channel Index.

Рис. 7. Гистограмма индикатора Commodity Channel Index.

Сокращённый код:

input int   period_CCI=14;             // Averaging period of iCCI 
//---- indicator buffers
double      CCI[];
//---- handles for indicators
int         iCCI_handle;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   ObjectsDeleteAll(0,-1,-1);
   ChartRedraw();
   iCCI_handle=iCCI(_Symbol,_Period,period_CCI,PRICE_CLOSE);
   ArraySetAsSeries(CCI,true);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
//---
   ArraySetAsSeries(time,true);
   
   CopyBuffer(iCCI_handle,0,0,2,CCI);

   DrawHistogram(true,"iCCI=",CCI[0],time[0],0,1);
   
//--- Смещение диаграммы на новый бар
   if(time[0]>prevTimeBar)
     {
     ...
     }
   return(rates_total);
  }

     3. Индикаторы: iEnvelopesiATR, iMACD. Комбинация гистограмм для трёх индикаторов, каждый из которых будет отображаться в своём окне.

Рис. 8. Гисограммы 3-х индикаторов: iEnvelopes, iATR and iMACD.

Рис. 8. Гистограммы 3-х индикаторов: iEnvelopes, iATR and iMACD. 

Сокращённый код, реализующий набор гистограмм, изображенный на рис. 8.

input int   period_Envelopes=14;       // Averaging period of iEnvelopes 
input int   period_ATR=14;             // Averaging period of iATR 
input int   period_fast=12;            // Averaging period fast of iMACD 
input int   period_slow=26;            // Averaging period of slow iMACD 
//---- indicator buffers
double      UPPER[];
double      LOWER[];
double      ATR[];
double      MAIN[];
double      SIGNAL[];
//---- handles for indicators
int         iEnvelopes_handle;
int         iATR_handle;
int         iMACD_handle;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   ObjectsDeleteAll(0,-1,-1);
   ChartRedraw();
   iEnvelopes_handle=iEnvelopes(_Symbol,_Period,period_Envelopes,0,MODE_SMA,PRICE_CLOSE,0.1);
   iATR_handle=iATR(_Symbol,_Period,period_ATR);
   iMACD_handle=iMACD(_Symbol,_Period,period_fast,period_slow,9,PRICE_CLOSE);
   ArraySetAsSeries(UPPER,true);
   ArraySetAsSeries(LOWER,true);
   ArraySetAsSeries(ATR,true);
   ArraySetAsSeries(MAIN,true);
   ArraySetAsSeries(SIGNAL,true);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
//---
   ArraySetAsSeries(time,true);
   
   CopyBuffer(iEnvelopes_handle,0,0,1,UPPER);
   CopyBuffer(iEnvelopes_handle,1,0,1,LOWER);
   CopyBuffer(iATR_handle,0,0,1,ATR);
   CopyBuffer(iMACD_handle,0,0,1,MAIN);
   CopyBuffer(iMACD_handle,1,0,1,SIGNAL);

   DrawHistogram(true,"iEnvelopes.UPPER=",UPPER[0],time[0],_Digits);
   DrawHistogram(false,"iEnvelopes.LOWER=",LOWER[0],time[0],_Digits);
   DrawHistogram(true,"iATR=",ATR[0],time[0],_Digits+1,1);
   DrawHistogram(true,"iMACD.SIGNAL=",SIGNAL[0],time[0],_Digits+1,2);
   DrawHistogram(false,"iMACD.MAIN=",MAIN[0],time[0],_Digits+1,2);

//--- Смещение диаграммы на новый бар
   if(time[0]>prevTimeBar)
     {
     ...
     }
   return(rates_total);
  }

Итак, рассмотрев технологию построения различных гистограмм статистических распределений характеристик рынка, можно утверждать, что:

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

Численные характеристики статистических распределений

В первой части статьи был показан способ построения гистограмм для начинающих программистов. Приведённые примеры легко проецируются на большой класс задач, связанных с исследованием статистических распределений и их визуализацией. Но для более глубокого анализа гистограмм необходимы наиболее универсальные и прогрессивные методы программирования, а именно — ООП. Итак, рассмотримкакие параметры статистических распределений будут небезынтересны читателям.

В первую очередь, нас интересуют численные характеристики вариационных рядов:

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

Класс CHistogram

Функционал данного класса позволяет отображать на графике гистограммы и основные характеристики статистических распределений. Рассмотрим основные методы.

Метод: конструктор класса CHistogram.

Инициализирует экземпляр класса. 

void CHistogram(
   string name,                     // уникальный префикс имени
   int    hsize,                    // масштаб диаграммы
   int    width,                    // толщина линий столбцов гистограммы
   color  active,                   // цвет активных линий
   color  passive,                  // цвет пассивных линий
   bool   Left_Right=true,          // left=false or right=true
   bool   relative_frequency=false, // относительная или абсолютная гистограмма
   int    sub_win=0                 // индекс окна построения гистограммы
   );

Параметры:

 name

    [in] Уникальный префикс имени для всех столбцов гистограммы. 

 hsize

    [in] Масштаб отображения гистограммы.

 width

    [in] Толщина линий столбцов гистограммы.

 active

    [in] Цвет столбцов гистограммы, обновлённых на текущем баре.

 passive

    [in] Цвет столбцов гистограммы, которые на текущем баре не обновлялись.

 Left_Right=true

    [in] Направление отображения гистограммы. false — гистограмма расположена слева от текущего бара, true — справа.

 relative_frequency=false

    [in] Метод учёта значений частот. false — абсолютные значения частот, true — относительные значения частот.

 sub_win=0

    [in] Индекс окна для построения гистограммы. 0 — главное окно графика. 

Возвращаемое значение:

 Нет возвращаемого значения. В случае успеха создаётся экземпляр класса с заданными параметрами.

 

Метод: отображения гистограммы DrawHistogram.

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

void DrawHistogram(
   double price,  // значение варианты
   datetime time  // время текущего бара
   );

Параметры:

 price

    [in] Значение варианты исследуемой характеристики рынка.

 time

    [in] Время текущего бара. На этом баре будет ось гистограммы.  

Возвращаемое значение:

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

 

Метод: расчёт характеристик гистограммы HistogramCharacteristics. 

Возвращает рассчитанные характеристики вариационного ряда в переменной типа sVseries.

sVseries HistogramCharacteristics();

Параметры:

 Нет входных параметров.

Возвращаемое значение:

 В случае успеха возвращает значение переменной типа sVseries.

 

Структура для получения текущих значений характеристик гистограммы (sVseries).

Структура для хранения последних значений характеристик статистического распределения. Предназначена для получения наиболее востребованной информации о вариационном ряде. 

struct sVseries
  {
   long     N;    // общее число наблюдений
   double   Na;   // среднее значение частот
   double   Vmax; // максимальное значение варианты
   double   Vmin; // минимальное значение варианты
   double   A;    // амплитуда ряда
   double   Mean; // взвешенная средняя арифметическая
   double   D;    // дисперсия
   double   SD;   // среднеквадратическое отклонение
  };

Переменная типа sVseries позволяет за один вызов функции HistogramCharacteristics() получить значения всех основных характеристик вариационного ряда, отображённого в виде гистограммы.

 

Метод: визуализация значения средней DrawMean. 

Отображает значение взвешенной средней арифметической вариационного ряда на графике. 

void DrawMean(
   double coord,     // значение взвешенной средней арифметической
   datetime time,    // время текущего бара
   bool marker=false,// отображать маркер или нет
   bool save=false   // сохранять значение в истории или нет
   );

Параметры:

 coord

    [in] Значение взвешенной средней арифметической.

 time

    [in] Время текущего бара. На этом баре будет фиксироваться значение взвешенной средней арифметической.

 marker=false

    [in] Отображать маркер на графике или нет. false — маркер не отображается, true — маркер отображается на графике.

 save=false

    [in]  Сохранять значение взвешенной средней арифметической в истории. false — не отображать, true — отображать значение на графике.

Возвращаемое значение:

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

 

Метод: визуализация среднеквадратического отклонения DrawSD.

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

void DrawSD(
   sVseries &coord,        // переменная типа sVseries
   datetime time,          // время текущего бара
   double deviation=1.0,   // отклонение
   color clr=clrYellow     // цвет отображения
   );

Параметры:

 coord

    [in] Значение переменной типа sVseries.

 time

    [in] Время текущего бара.

 deviation=1.0

    [in] Коэффициент, на который будет увеличено значение среднеквадратического отклонения.

 clr=clrYellow

    [in] Цвет прямоугольника визуализирующего среднеквадратического отклонения.

Возвращаемое значение

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

  

Пример построения гистограмм с использованием класса CHistogram

В качестве примера построим гистограммы статистических распределений цен Bid и Ask, а также двух индикаторов ATR и MACD (рис. 9). В отличие от предыдущих примеров, с помощью рассмотренного класса можно создавать относительные диаграммы. Для цен, кроме самой гистограммы, создадим графическое отображение взвешенной средней арифметической и среднеквадратического отклонения. При этом среднюю будем сохранять с помощью индикаторных буферов. На рисунке среднеквадратические отклонения изображены в виде прямоугольников, ширина которых соответствует средней частоте, а высота равна двум отклонениям, увеличенным для наглядности в несколько раз. 

 Рис. 9. Гистограммы 2-х индикаторов: iATR, iMACD и цен Bid и Ask.

Рис. 9. Гистограммы 2-х индикаторов: iATR, iMACD и цен Bid и Ask.


Код этого примера находится в приложении к статье. Использование индикаторных буферов в данной программе вызвано желанием продемонстрировать связь рассмотренного класса и функционала индикатора.

Заключение