Создание пользовательских индикаторов с использованием класса CCanvas

Alexander Fedosov | 17 мая, 2017

Содержание

Введение

Неотъемлемая часть современного трейдинга в терминале MetaTrader 5 — пользовательские индикаторы. Используются они и в автоматических торговых системах (как часть алгоритмов), и при ручной торговле. К настоящему моменту при создании индикатора можно было задавать стили рисования и использовать 18 типов графического построения. Но этим графические возможности терминала не ограничиваются. Для создания собственных, нестандартных индикаторов с бесконечными возможностями визуализации создана библиотека Пользовательской графики CCanvas. Цель этой статьи — познакомить пользователей с возможностями этой библиотеки и представить примеры ее использования, а также разработать отдельную библиотеку пользовательских индикаторов различных типов.


Построение базового класса пользовательской графики

В качестве базового класса необходимо написать набор методов, которые создают основу любого объекта пользовательской графики, и при этом включают в себя общий набор свойств. Для этого создадим в <каталог данных>\MQL5\Include папку CustomGUI, а в ней — файл CanvasBase.mqh. Этот файл будет содержать базовый класс CCanvasBase для всех будущих типов пользовательской графики.

//+------------------------------------------------------------------+
//|                                                  CCanvasBase.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#include <Canvas\Canvas.mqh>
//+------------------------------------------------------------------+
//| Базовый класс для создания пользовательской графики              |
//+------------------------------------------------------------------+
class CCanvasBase
  {
private:
   //--- Имя холста
   string            m_canvas_name;
   //--- Координаты холста
   int               m_x;
   int               m_y;
   //--- Размер холста
   int               m_x_size;
   int               m_y_size;
protected:
   CCanvas           m_canvas;
   //--- Создает графический ресурс для объекта
   bool              CreateCanvas(void);
   //--- Удаляет графический ресурс
   bool              DeleteCanvas(void);
public:
                     CCanvasBase(void);
                    ~CCanvasBase(void);
   //--- Устанавливает и возвращает координаты
   void              X(const int x)                         { m_x=x;                      }
   void              Y(const int y)                         { m_y=y;                      }
   int               X(void)                                { return(m_x);                }
   int               Y(void)                                { return(m_y);                }
   //--- Устанавливает и возвращает размеры
   void              XSize(const int x_size)                { m_x_size=x_size;            }
   void              YSize(const int y_size)                { m_y_size=y_size;            }
   int               XSize(void)                            { return(m_x_size);           }
   int               YSize(void)                            { return(m_y_size);           }
   //--- Устанавливает имя индикатора при создании
   void              Name(const string canvas_name) { m_canvas_name=canvas_name;  }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCanvasBase::CCanvasBase(void) : m_x(0),
                                 m_y(0),
                                 m_x_size(200),
                                 m_y_size(200)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CCanvasBase::~CCanvasBase(void)
  {
  }
//+------------------------------------------------------------------+
//| Создает графический ресурс для объекта                           |
//+------------------------------------------------------------------+
bool CCanvasBase::CreateCanvas(void)
  {
   ObjectDelete(0,m_canvas_name);
   if(!m_canvas.CreateBitmapLabel(m_canvas_name,m_x,m_y,m_x_size,m_y_size,COLOR_FORMAT_ARGB_NORMALIZE))
      return(false);
   ObjectSetInteger(0,m_canvas_name,OBJPROP_CORNER,CORNER_LEFT_UPPER);
   ObjectSetInteger(0,m_canvas_name,OBJPROP_ANCHOR,ANCHOR_CENTER);
   ObjectSetInteger(0,m_canvas_name,OBJPROP_BACK,false);
   return(true);
  }
//+------------------------------------------------------------------+
//| Удаляет графический ресурс                                       |
//+------------------------------------------------------------------+
bool CCanvasBase::DeleteCanvas()
  {
   return(ObjectDelete(0,m_canvas_name)?true:false);
  }
//+------------------------------------------------------------------+

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


Класс простого круглого индикатора CCircleSimple

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

  • Рамка. Своего рода очерченная окантовка.
  • Фон. Пространство, в котором будут находиться текстовые элементы.
  • Значение. Текстовый элемент, который отображать числовое значение индикатора.
  • Описание. Текстовый элемент для описания индикатора (его названия, периода или другой отличительной информации).


Рис.1 Базовая структура простого круглого индикатора

В ранее созданной папке CustomGUI создадим еще одну папку Indicator. В ней будут находиться все классы будущих индикаторов. Создадим первый из них с именем CircleSimple.mqh. Вначале подключим уже созданный ранее файл CanvaseBase.mqh с базовым классом для всех видов графических объектов и сделаем класс CCanvaseBase базовым для текущего, чтобы все методы этого класса стали доступны текущему CCircleSimple.

//+------------------------------------------------------------------+
//|                                                 CircleSimple.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#include "..\CanvasBase.mqh"
//+------------------------------------------------------------------+
//| Круглый индикатор с численным значением и описанием              |
//+------------------------------------------------------------------+
class CCircleSimple : public CCanvasBase

Чтобы при использовании графических объектов в файлах экспертов и индикаторов не прописывать их вручную каждый раз, создадим в папке CustomGUI файл CustomGUI.mqh в котором будут собраны все включения классов из папки Indicators. Таким образом, нам нужно будет подключать только этот файл, чтобы получить доступ ко всем классам библиотеки. Теперь подключим текущий:

//+------------------------------------------------------------------+
//|                                                    CustomGUI.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#include "Indicators\CircleSimple.mqh"

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

//+------------------------------------------------------------------+
//| Круглый индикатор с численным значением и описанием              |
//+------------------------------------------------------------------+
class CCircleSimple : public CCanvasBase
  {
private:
   //--- Цвет фона 
   color             m_bg_color;
   //--- Цвет рамки
   color             m_border_color;
   //--- Цвет текста значения
   color             m_font_color;
   //--- Цвет текста надписи
   color             m_label_color;
   //--- Прозрачность
   uchar             m_transparency;
   //--- Толщина рамки
   int               m_border;
   //--- Размер индикатора
   int               m_radius;
   //--- Размер шрифта значения
   int               m_font_size;
   //--- Размер шрифта надписи
   int               m_label_font_size;
   //---
   int               m_digits;
   //--- Надпись
   string            m_label;
public:
                     CCircleSimple(void);
                    ~CCircleSimple(void);
   //--- Устанавливает и возвращает цвет фона
   color             Color(void)                      { return(m_bg_color);            }
   void              Color(const color clr)           { m_bg_color=clr;                }
   //--- Устанавливает и возвращает размер
   int               Radius(void)                     { return(m_radius);              }
   void              Radius(const int r)              { m_radius=r;                    }
   //--- Устанавливает и возвращает размер шрифта значения
   int               FontSize(void)                   { return(m_font_size);           }
   void              FontSize(const int fontsize)     { m_font_size=fontsize;          }
   //--- Устанавливает и возвращает размер шрифта надписи
   int               LabelSize(void)                  { return(m_label_font_size);    }
   void              LabelSize(const int fontsize)    { m_label_font_size=fontsize;   }
   //--- Устанавливает и возвращает цвет шрифта значения
   color             FontColor(void)                  { return(m_font_color);          }
   void              FontColor(const color fontcolor) { m_font_color=fontcolor;        }
   //--- Устанавливает и возвращает цвет шрифта надписи
   color             LabelColor(void)                 { return(m_label_color);         }
   void              LabelColor(const color fontcolor){ m_label_color=fontcolor;       }
   //--- Устанавливает цвет и толщину рамки
   void              BorderColor(const color clr)     { m_border_color=clr;            }
   void              BorderSize(const int border)     { m_border=border;               }
   //--- Устанавливает и возвращает прозрачность
   uchar             Alpha(void)                      { return(m_transparency);        }
   void              Alpha(const uchar alpha)         { m_transparency=alpha;          }
   //--- Устанавливает и возвращает значение надписи
   string            Label(void)                      { return(m_label);               }
   void              Label(const string label)        { m_label=label;                 }
   //--- Создает индикатор
   void              Create(string name,int x,int y);
   //--- Удаляет индикатор
   void              Delete(string name);
   //--- Устанавливает и обновляет значение индикатора
   void              NewValue(int value);
   void              NewValue(double value);
  };

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

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCircleSimple::CCircleSimple(void) : m_bg_color(clrAliceBlue),
                                     m_border_color(clrRoyalBlue),
                                     m_font_color(clrBlack),
                                     m_label_color(clrBlack),
                                     m_transparency(255),
                                     m_border(5),
                                     m_radius(40),
                                     m_font_size(17),
                                     m_label_font_size(20),
                                     m_digits(2),
                                     m_label("label")
  {
  }

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

//+------------------------------------------------------------------+
//| Создает индикатор                                                |
//+------------------------------------------------------------------+
void CCircleSimple::Create(string name,int x,int y)
  {
   int r=m_radius;
//--- Коррекция положения индикатора относительно радиуса
   x=(x<r)?r:x;
   y=(y<r)?r:y;
   Name(name);
   X(x);
   Y(y);
   XSize(2*r+1);
   YSize(2*r+1);
   if(!CreateCanvas())
      Print("Error. Can not create Canvas.");
   if(m_border>0)
      m_canvas.FillCircle(r,r,r,ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillCircle(r,r,r-m_border,ColorToARGB(m_bg_color,m_transparency));
//---
   m_canvas.FontSizeSet(m_font_size);
   m_canvas.TextOut(r,r,"0",ColorToARGB(m_font_color,m_transparency),TA_CENTER|TA_VCENTER);
//---
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_label_font_size,m_label,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }

Не будем вдаваться в подробности реализации метода. Остановимся лишь на основных моментах:

  • Вначале корректируем положение графического ресурса относительно фактического размера индикатора, чтобы не вышло так, что часть его будет за пределами основного графика.
  • Затем используется настройка и работа с методами названия, размеров, координат. Создается основа из базового класса CCanvasBase.
  • При реализации рамки был использован принцип наложения, описанный выше. А именно — созданы два круга: первый (закрашенный) и второй, радиус которого меньше радиуса первого на значение толщины рамки.
  • Поверх этих объектов были созданы элементы числового значения и описания.

Далее рассмотрим метод NewValue(), который реализует отображение обновления численного значения индикатора в реальном времени:

//+------------------------------------------------------------------+
//| Устанавливает и обновляет значение индикатора                    |
//+------------------------------------------------------------------+
void CCircleSimple::NewValue(int value)
  {
   int r=m_radius;
   m_canvas.FillCircle(r,r,r-m_border,ColorToARGB(m_bg_color,m_transparency));
//---
   m_canvas.FontSizeSet(m_font_size);
   m_canvas.TextOut(r,r,IntegerToString(value),ColorToARGB(m_font_color,m_transparency),TA_CENTER|TA_VCENTER);
//---
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_label_font_size,m_label,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }

Чтобы численное значение индикатора обновлялось корректно, нужно заново перерисовывать три объекта (фон и текстовые элементы) в том же порядке, что и при создании. Поэтому в методе NewValue() перерисовывается фон, а затем текстовые элементы значения и описания.

Итак, настало время проверить реализацию кругового индикатора в терминале MetaTrader 5. Для этого создадим пустой индикатор, подключим к нему файл CustomGUI.mqh и создадим два экземпляра класса CCircleSimple:

//+------------------------------------------------------------------+
//|                                              CustomIndicator.mq5 |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Alexander Fedosov"
#property link      "https://www.mql5.com/ru/users/alex2356"
#property version   "1.00"
#property indicator_plots 0
#property indicator_chart_window

#include <CustomGUI\CustomGUI.mqh>
//---
CCircleSimple ind1,ind2;

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

//---
int InpInd_Handle,InpInd_Handle1;
double adx[],rsi[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {

//---- получение хендла индикатора
   InpInd_Handle=iADX(Symbol(),PERIOD_CURRENT,10);
   InpInd_Handle1=iRSI(Symbol(),PERIOD_CURRENT,10,PRICE_CLOSE);
   if(InpInd_Handle==INVALID_HANDLE)
     {
      Print("Failed to get indicator handle");
      return(INIT_FAILED);
     }
   ArraySetAsSeries(adx,true);
   ArraySetAsSeries(rsi,true);

Определим некоторые свойства круговых индикаторов с целью демонстрации и создадим их:

//---
   ind1.Radius(60);
   ind1.Label("ADX");
   ind1.Color(clrWhiteSmoke);
   ind1.LabelColor(clrBlueViolet);
   ind1.Create("adx",100,100);
//---
   ind2.Radius(55);
   ind2.BorderSize(8);
   ind2.FontSize(22);
   ind2.LabelColor(clrCrimson);
   ind2.BorderColor(clrFireBrick);
   ind2.Label("RSI");
   ind2.Create("rsi",250,100);

В расчетной части индикатора остается только подставить текущие численные значения стандартных индикаторов в созданные:

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(CopyBuffer(InpInd_Handle,0,0,2,adx)<=0 || 
      CopyBuffer(InpInd_Handle1,0,0,2,rsi)<=0
      )
      return(0);
   ind1.NewValue(adx[0]);
   ind2.NewValue(rsi[0]);
//--- return value of prev_calculated for next call
   return(rates_total);
  }

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

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ind1.Delete();
   ind2.Delete();
   ChartRedraw();
  }
//+------------------------------------------------------------------+

Результат работы представлен на рис.2. Как видно, индикаторы заметно отличаются друг от друга по многим параметрам.

Рис.2. Пример работы круговых индикаторов.

Класс кругового индикатора с дуговой индикацией CCircleArc

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


Рис.3 Базовая структура кругового индикатора с дуговой индикацией.

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

void  Pie( 
   int         x,       // координата X центра эллипса 
   int         y,       // координата Y центра эллипса 
   int         rx,      // радиус эллипса по координате X 
   int         ry,      // радиус эллипса по координате Y 
   int         fi3,     // угол луча из центра эллипса, задающий первую границу дуги 
   int         fi4,     // угол луча из центра эллипса, задающий вторую границу дуги 
   const uint  clr,     // цвет линии 
   const uint  fill_clr // цвет заливки 
   );

Здесь значение fi3, задающее первую границу дуги, будет служить началом нашей дуговой шкалы, а значение fi4 будет динамически изменяться в зависимости от числового значения, передаваемого в наш индикатор. Создадим в папке Indicators файл CircleArc.mqh и сразу же в файле CustomGUI.mqh добавим следующую строчку:

#include "Indicators\CircleArc.mqh"

Тем самым добавляем будущий класс к общему списку включений всех индикаторов. Определимся с набором общих свойств и методов. По сути они не отличаются от методов предыдущего класса, но стоит остановиться на перечислении ENUM_ORIENTATION. Оно имеет два значения — VERTICAL и HORIZONTAL — и определяет положение первой границы дуги дуговой шкалы. При вертикальном значении дуга начинается с 90 градусов, а при горизонтальном — с нуля. Индикация отсчитывается против часовой стрелки.

//+------------------------------------------------------------------+
//|                                                    CircleArc.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#include "..\CanvasBase.mqh"
//+------------------------------------------------------------------+
//| Круглый индикатор с численным значением и дуговой индикацией     |
//+------------------------------------------------------------------+
class CCircleArc : public CCanvasBase
  {
private:
   //--- Цвет фона индикатора
   color             m_bg_color;
   //--- Цвет дуги индикатора
   color             m_arc_color;
   //--- Цвет области индикации 
   color             m_area_color;
   //--- Цвет описания индикатора 
   color             m_label_color;
   //--- Цвет значения индикатора
   color             m_value_color;
   //--- Прозрачность индикатора
   uchar             m_transparency;
   //--- Размер индикатора
   int               m_radius;
   //--- Толщина шкалы индикатора 
   int               m_scale_width;
   //--- Размер шрифта описания
   int               m_label_font_size;
   //--- Размер шрифта значения
   int               m_value_font_size;
   //--- Описание индикатора
   string            m_label_value;
   //--- Тип ориентации шкалы индикатора
   ENUM_ORIENTATION  m_orientation;
public:
                     CCircleArc(void);
                    ~CCircleArc(void);
   //--- Устанавливает и возвращает цвет фона индикатора
   color             BgColor(void)                                   { return(m_bg_color);            }
   void              BgColor(const color clr)                        { m_bg_color=clr;                }
   //--- Устанавливает и возвращает цвет дуги индикацтора
   color             ArcColor(void)                                  { return(m_arc_color);           }
   void              ArcColor(const color clr)                       { m_arc_color=clr;               }
   //--- Устанавливает и возвращает цвет области индикации
   color             AreaColor(void)                                 { return(m_area_color);          }
   void              AreaColor(const color clr)                      { m_area_color=clr;              }
   //--- Устанавливает и возвращает цвет описания индикатора 
   color             LabelColor(void)                                { return(m_label_color);         }
   void              LabelColor(const color clr)                     { m_label_color=clr;             }
   //--- Устанавливает и возвращает цвет значения индикатора
   color             ValueColor(void)                                { return(m_value_color);         }
   void              ValueColor(const color clr)                     { m_value_color=clr;             }
   //--- Устанавливает и возвращает прозрачность индикатора 
   uchar             Alpha(void)                                     { return(m_transparency);        }
   void              Alpha(const uchar trn)                          { m_transparency=trn;            }
   //--- Устанавливает и возвращает размер индикатора
   int               Radius(void)                                    { return(m_radius);              }
   void              Radius(const int r)                             { m_radius=r;                    }
   //--- Устанавливает и возвращает толщину шкалы индикатора
   int               Width(void)                                     { return(m_scale_width);         }
   void              Width(const int w)                              { m_scale_width=w;               }
   //--- Устанавливает и возвращает размер шрифта описания
   int               LabelSize(void)                                 { return(m_label_font_size);     }
   void              LabelSize(const int sz)                         { m_label_font_size=sz;          }
   //--- Устанавливает и возвращает размер шрифта значения
   int               ValueSize(void)                                 { return(m_value_font_size);     }
   void              ValueSize(const int sz)                         { m_value_font_size=sz;          }
   //--- Устанавливает и возвращает описание индикатора
   string            LabelValue(void)                                { return(m_label_value);         }
   void              LabelValue(const string str)                    { m_label_value=str;             }
   //--- Устанавливает и возвращает тип ориентации шкалы
   void              Orientation(const ENUM_ORIENTATION orietation)  { m_orientation=orietation;      }
   ENUM_ORIENTATION  Orientation(void)                               { return(m_orientation);         }
   //--- Создает индикатор
   void              Create(string name,int x,int y);
   //--- Удаляет индикатор
   void              Delete(void);
   //--- Устанавливает и обновляет значение индикатора
   void              NewValue(double value,double maxvalue,int digits);
  };

Более подробно остановимся на методах, реализация которых не представлена: Create() и NewValue(). Помним, что при создании объектов они накладываются друг на друга слоями, поэтому порядок создания их будет следующим:

  1. Фон дугового индикатора. В виде закрашенного круга.
  2. Дуговой индикатор. В виде закрашенного сектора эллипса.
  3. Фон отображения текстовой информации. В виде закрашенного круга.
  4. Численное значение индикатора и его описание.

Ниже в установленном порядке представлена реализация:

//+------------------------------------------------------------------+
//| Создает индикатор                                                |
//+------------------------------------------------------------------+
void CCircleArc::Create(string name,int x,int y)
  {
   int r=m_radius;
   double a,b;
//--- Установка начального положения индикатора
   a=(m_orientation==VERTICAL)?M_PI_2:0;
   b=(m_orientation==VERTICAL)?M_PI_2:0;
   b+=90*M_PI/180;
//--- Коррекция положения индикатор относительно радиуса
   x=(x<r)?r:x;
   y=(y<r)?r:y;
   Name(name);
   X(x);
   Y(y);
   XSize(2*r+1);
   YSize(2*r+1);
   if(!CreateCanvas())
      Print("Error. Can not create Canvas.");
//---
   m_canvas.FillCircle(r,r,r,ColorToARGB(m_bg_color,m_transparency));
   m_canvas.Pie(r,r,XSize()/2,YSize()/2,a,b,ColorToARGB(m_arc_color,m_transparency),ColorToARGB(m_arc_color,m_transparency));
   m_canvas.FillCircle(r,r,r-m_scale_width,ColorToARGB(m_area_color,m_transparency));
//---
   m_canvas.FontSizeSet(m_value_font_size);
   m_canvas.TextOut(r,r,"0",ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER);
//---
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_label_font_size,m_label_value,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }

Здесь стоит обратить внимание на то, что задавая начальные значения первой и второй границы дуги, (то есть начало индикатора и его текущее значение) мы учитываем его начальную ориентацию и то, что углы для метода Pie() должны задаваться в радианах. В качестве примера, по умолчанию текущее значение, соответствующее переменной b установлено в 90, при этом оно переводится в радианы, иначе отображение будет некорректно. По этим же принципам реализуется метод NewValue():

//+------------------------------------------------------------------+
//| Устанавливает и обновляет значение индикатора                    |
//+------------------------------------------------------------------+
void CCircleArc::NewValue(double value,double maxvalue,int digits=2)
  {
   int r=m_radius;
   double a,b,result;
//--- Проверки на выход за пределы диапазона
   value=(value>maxvalue)?maxvalue:value;
   value=(value<0)?0:value;
//--- Установка начального положения индикатора
   a=(m_orientation==VERTICAL)?M_PI_2:0;
   b=(m_orientation==VERTICAL)?M_PI_2:0;
//---
   result=value*(360.0/maxvalue);
   b+=result*M_PI/180;
   if(b>=2*M_PI)
      b=2*M_PI-0.02;
//---
   m_canvas.FillCircle(r,r,r,ColorToARGB(m_bg_color,m_transparency));
   m_canvas.Pie(r,r,XSize()/2,YSize()/2,a,b,ColorToARGB(m_arc_color,m_transparency),ColorToARGB(m_arc_color,m_transparency));
   m_canvas.FillCircle(r,r,r-m_scale_width,ColorToARGB(m_area_color,m_transparency));
//---
   m_canvas.FontSizeSet(m_value_font_size);
   m_canvas.TextOut(r,r,DoubleToString(value,digits),ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER);
//---
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_label_font_size,m_label_value,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//|                                                                  |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Alexander Fedosov"
#property link      "https://www.mql5.com/ru/users/alex2356"
#property version   "1.00"
#property indicator_plots 0
#property indicator_chart_window

#include <CustomGUI\CustomGUI.mqh>
//+------------------------------------------------------------------+
//|  Входные параметры индикатора                                    |
//+------------------------------------------------------------------+
input double         maxspr=12;                 // Максимальное значение
input int            stepval=8;                 // Пороговое значение
input color          stepcolor=clrCrimson;      // Цвет при пороговом значении
//---
CCircleArc arc;
double spr;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   arc.Orientation(HORIZONTAL);
   arc.LabelValue("Spread");
   arc.Create("spread_ind",100,100);
//---
   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[])
  {
   spr=double(SymbolInfoInteger(Symbol(),SYMBOL_SPREAD));
   if(spr>=stepval)
      arc.ArcColor(stepcolor);
   else
      arc.ArcColor(clrForestGreen);
//---
   arc.NewValue(spr,maxspr,0);
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   arc.Delete();
   ChartRedraw();
  }
//+------------------------------------------------------------------+

Как видно из листинга, реализация с помощью класса CCircleArc достаточно проста в создании и настройке. Стоит заметить: изменять и настраивать какие-либо свойства нужно строго перед созданием индикатора(метод Create()), либо перед использованием метода NewValue(), так как именно в них происходит перерисовка элементов индикатора, а соответственно, и применение измененных свойств. На рис.4 показан пример работы индикатора спреда.

Рис.4. Пример работы кругового индикатора с дуговой индикацией.


Класс кругового индикатора с дуговой секционной индикацией CCircleSection

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


Рис.5 Базовая структура кругового индикатора с дуговой секционной индикацией.

Метод отображения дуговой индикации с секциями в корне отличается от предыдущего класса. Разница в том, что в предыдущем классе был единоразово использован метод Pie() библиотеки CCanvas, в котором при изменении значения менялось значение и положение второй дуги сектора эллипса. Здесь же этот метод применяется десять раз, и положения их статичны. То есть, проще говоря, в данном индикаторе присутствуют 10 закрашенных секторов эллипса, расположенных по кругу друг за другом. Визуальной индикацией будет служить изменение цвета определенных секций.

Создадим в папке Indicators файл CircleSection.mqh, и сразу же, как и все предыдущие, подключим его в файле CustomUI.mqh. Теперь его список будет выглядеть следующим образом:

//+------------------------------------------------------------------+
//|                                                    CustomGUI.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#include "Indicators\CircleSimple.mqh"
#include "Indicators\CircleArc.mqh"
#include "Indicators\CircleSection.mqh"

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

//+------------------------------------------------------------------+
//|                                                CircleRounded.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#include "..\CanvasBase.mqh"
//+--------------------------------------------------------------------------+
//| Круглый индикатор с численным значением и круговой секционной индикацией |
//+--------------------------------------------------------------------------+
class CCircleSection : public CCanvasBase
  {
private:
   //--- Цвет активного/неактивного деления шкалы
   color             m_scale_color_on;
   color             m_scale_color_off;
public:
   //--- Устанавливает цвет активного/неактивного деления шкалы
   void              ScaleColorOn(const color clr)                   { m_scale_color_on=clr;          }
   void              ScaleColorOff(const color clr)                  { m_scale_color_off=clr;         }
   //--- Создает индикатор
   void              Create(string name,int x,int y);
   //--- Устанавливает и обновляет значение индикатора
   void              NewValue(double value,double maxvalue,int digits);
  };

Методы Create() и NewValue() оставлены по причине отличия их реализации в сравнении с предыдущими. Как видно из листинга ниже, после коррекции положения относительно радиуса идет блок создания десяти секций с помощью цикла. При этом учитывается начало отсчета: горизонтальное — с нуля градусов, вертикальное — с 90.

//+------------------------------------------------------------------+
//| Создает индикатор                                                |
//+------------------------------------------------------------------+
void CCircleSection::Create(string name,int x,int y)
  {
   int r=m_radius;
   double a,b;
//--- Коррекция положения индикатора относительно радиуса
   x=(x<r)?r:x;
   y=(y<r)?r:y;
   Name(name);
   X(x);
   Y(y);
   XSize(2*r+1);
   YSize(2*r+1);
   if(!CreateCanvas())
      Print("Error. Can not create Canvas.");
//--- Секции индикатора
   for(int i=0;i<10;i++)
     {
      if(m_orientation==HORIZONTAL)
        {
         a=36*i*M_PI/180;
         b=36*(i+1)*M_PI/180;
         if(a>2*M_PI)
            a-=2*M_PI;
         if(b>2*M_PI)
            b-=2*M_PI;
        }
      else
        {
         a=M_PI_2+36*i*M_PI/180;
         b=M_PI_2+36*(i+1)*M_PI/180;
         if(a>2*M_PI)
            a-=2*M_PI;
         if(b>2*M_PI)
            b-=2*M_PI;
        }
      m_canvas.Pie(r,r,XSize()/2,YSize()/2,a,b,ColorToARGB(m_bg_color,m_transparency),ColorToARGB(m_scale_color_off,m_transparency));
     }
//--- Рамка и фон
   m_canvas.FillCircle(r,r,XSize()/2-m_scale_width,ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillCircle(r,r,XSize()/2-m_scale_width-m_border_size,ColorToARGB(m_area_color,m_transparency));
//--- Описание
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_value_font_size,m_label_value,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
//--- Численное значение
   m_canvas.FontSizeSet(m_value_font_size);
   m_canvas.TextOut(r,r,"0",ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }

Как мы уже говорили, помимо изменения численного значения, визуальная индикация представлена в виде изменения цвета секций. При этом изменение должно быть последовательным и отталкиваться от текущего и максимально указанного значения. Так, например, при численном значении 20 и максимальном 100 цвет поменяют две секции, но при максимальном равном 200 лишь одна. Рассмотрим более подробно, как это сделано в методе NewValue():

//+------------------------------------------------------------------+
//| Устанавливает и обновляет значение индикатора                    |
//+------------------------------------------------------------------+
void CCircleSection::NewValue(double value,double maxvalue=100,int digits=2)
  {
//---
   int r=m_radius;
   double a,b;
   color clr;
//---
   for(int i=0;i<10;i++)
     {
      if(m_orientation==HORIZONTAL)
        {
         a=36*i*M_PI/180;
         b=36*(i+1)*M_PI/180;
         if(a>2*M_PI)
            a-=2*M_PI;
         if(b>2*M_PI)
            b-=2*M_PI;
        }
      else
        {
         a=M_PI_2+36*i*M_PI/180;
         b=M_PI_2+36*(i+1)*M_PI/180;
         if(a>2*M_PI)
            a-=2*M_PI;
         if(b>2*M_PI)
            b-=2*M_PI;
        }
      clr=(maxvalue/10*(i+1)<=value)?m_scale_color_on:m_scale_color_off;
      m_canvas.Pie(r,r,XSize()/2,YSize()/2,a,b,ColorToARGB(m_bg_color,m_transparency),ColorToARGB(clr,m_transparency));
     }
//---
   m_canvas.FillCircle(r,r,XSize()/2-m_scale_width,ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillCircle(r,r,XSize()/2-m_scale_width-m_border_size,ColorToARGB(m_area_color,m_transparency));
//---
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_value_font_size,m_label_value,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
//---
   m_canvas.FontSizeSet(m_value_font_size);
   m_canvas.TextOut(r,r,DoubleToString(value,digits),ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//|                                              CustomIndicator.mq5 |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Alexander Fedosov"
#property link      "https://www.mql5.com/ru/users/alex2356"
#property version   "1.00"
#property indicator_plots 0
#property indicator_chart_window

#include <CustomGUI\CustomGUI.mqh>
//---
CCircleSection ind;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ind.Radius(70);
   ind.LabelValue("Drawdown");
   ind.Create("drawdown",150,150);
//---
   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[])
  {
//---
   ind.NewValue(AccountInfoDouble(ACCOUNT_EQUITY),AccountInfoDouble(ACCOUNT_BALANCE));
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ind.Delete();
   ChartRedraw();
  }
//+------------------------------------------------------------------+

Не забываем также в деинициализации использовать метод Delete() для корректного удаления индикатора с графика.


Класс линейного графика CLineGraph

Для создания линейного графика посредством пользовательской графики, нужно сперва определиться с элементами самого графика, а именно:

  • Задний фон графика. У него будет два свойства: это размер, который будет также размером самого объекта, и цвет.
  • Рамка графика. Имеет только свойство цвет. Состоит из двух закрашенных прямоугольников, при этом координаты последнего (который также выполняет роль фона графика) смещены на единицу, что дает эффект рамки.
  • Фон графика. Имеет только свойство цвет. 
  • Оси графика. Имеет только свойство цвет. Реализовано тем же самым способом, что и рамка графика — наложением двух прямоугольников, где верхний имеет смещение по координатам на единицу.
  • Деление оси. Имеет только свойство цвет. Набор из линий, реализованный с помощью методов LineHorizontal() для оси Y и LineVertical() для оси Х.
  • Значение оси. Имеет свойство цвет и размер шрифта. Набор из текстовых объектов с помощью метода TextOut().
  • Сетка. Имеет только свойство цвет. Для реализации сетки был выбран метод LineAA() т.к. в нем можно установить стиль линии.
  • График. Имеет только свойство цвет. Состоит из элементов линия и закрашенный круг — метод FillCircle()

В описании элементов указаны только настраиваемые свойства. На рис.6 представлена структура элементов графика. 


Рис.6. Базовая структура линейного графика.

Для начала создадим в папке Indicators файл LineGraph.mqh и подключим его в файле CustomGUI.mqh:

//+------------------------------------------------------------------+
#include "Indicators\CircleSimple.mqh"
#include "Indicators\CircleArc.mqh"
#include "Indicators\CircleSection.mqh"
#include "Indicators\LineGraph.mqh"
//+------------------------------------------------------------------+

Далее в файле LineGraph.mqh создадим класс CLineGraph и сделаем, как и в предыдущих, базовым для него CCanvasBase. Определим все свойства и методы, описанные выше в базовой структуре линейного графика. 

//+------------------------------------------------------------------+
//|                                                    LineGraph.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#include "..\CanvasBase.mqh"
//+------------------------------------------------------------------+
//| Линейный график                                                  |
//+------------------------------------------------------------------+
class CLineGraph : public CCanvasBase
  {
private:
   //--- Цвет заднего фона графика
   color             m_bg_color;
   //--- Цвет фона графика
   color             m_bg_graph_color;
   //--- Цвет рамки графика
   color             m_border_color;
   //--- Цвет значений осей графика
   color             m_axis_color;
   //--- Цвет сетки графика
   color             m_grid_color;
   //--- Цвет делений на осях графика
   color             m_scale_color;
   //--- Цвет линий графика
   color             m_graph_color;
   //--- Размер графика
   int               m_x_size;
   int               m_y_size;
   //--- Отступ графика от края заднего фона
   int               m_gap;
   //--- Размер шрифта значений на осях графика
   int               m_font_size;
   //--- Массив значений оси ординат(y)
   int               m_x[];
   //--- Минимальное и максимальное значение оси ординат
   double            m_y_min;
   double            m_y_max;
   //--- Количество делений оси ординат
   int               m_num_grid;
   //--- Прозрачность графика
   uchar             m_transparency;
public:
                     CLineGraph(void);
                    ~CLineGraph(void);
   //--- Устанавливает и возвращает цвет заднего фона графика
   color             BgColor(void)                                   { return(m_bg_color);                  }
   void              BgColor(const color clr)                        { m_bg_color=clr;                      }
   //--- Устанавливает и возвращает цвет фона графика
   color             BgGraphColor(void)                              { return(m_bg_graph_color);            }
   void              BgGraphColor(const color clr)                   { m_bg_graph_color=clr;                }
   //--- Устанавливает и возвращает цвет рамки графика
   color             BorderColor(void)                               { return(m_border_color);              }
   void              BorderColor(const color clr)                    { m_border_color=clr;                  }
   //--- Устанавливает и возвращает цвет значений осей графика
   color             AxisColor(void)                                 { return(m_axis_color);                }
   void              AxisColor(const color clr)                      { m_axis_color=clr;                    }
   //--- Устанавливает и возвращает цвет сетки графика
   color             GridColor(void)                                 { return(m_grid_color);                }
   void              GridColor(const color clr)                      { m_grid_color=clr;                    }
   //--- Устанавливает и возвращает цвет делений на осях графика
   color             ScaleColor(void)                                { return(m_scale_color);               }
   void              ScaleColor(const color clr)                     { m_scale_color=clr;                   }
   //--- Устанавливает и возвращает цвет линий графика
   color             GraphColor(void)                                { return(m_graph_color);               }
   void              GraphColor(const color clr)                     { m_graph_color=clr;                   }
   //--- Устанавливает и возвращает размеры графика
   int               XGraphSize(void)                                { return(m_x_size);                    }
   void              XGraphSize(const int x_size)                    { m_x_size=x_size;                     }
   int               YGraphSize(void)                                { return(m_y_size);                    }
   void              YGraphSize(const int y_size)                    { m_y_size=y_size;                     }
   //--- Устанавливает и возвращает размер шрифта значений на осях графика
   int               FontSize(void)                                  { return(m_font_size);                 }
   void              FontSize(const int fontzise)                    { m_font_size=fontzise;                }
   //--- Устанавливает и возвращает отступ графика от края заднего фона
   int               Gap(void)                                       { return(m_gap);                       }
   void              Gap(const int g)                                { m_gap=g;                             }
   //--- Устанавливает и возвращает минимальное и максимальное значение оси ординат
   double            YMin(void)                                      { return(m_y_min);                     }
   void              YMin(const double ymin)                         { m_y_min=ymin;                        }
   double            YMax(void)                                      { return(m_y_max);                     }
   void              YMax(const double ymax)                         { m_y_max=ymax;                        }
   //--- Устанавливает и возвращает количество делений оси ординат
   int               NumGrid(void)                                   { return(m_num_grid);                  }
   void              NumGrid(const int num)                          { m_num_grid=num;                      }
   //--- Создает график
   void              Create(string name,int x,int y);
   //--- Удаляет график 
   void              Delete(void);
   //--- Устанавливает массив входных данных
   void              SetArrayValue(double &data[]);
private:
   //---
   void              VerticalScale(double min,double max,int num_grid);
   void              HorizontalScale(int num_grid);
  };

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

//+------------------------------------------------------------------+
//| Создает график                                                   |
//+------------------------------------------------------------------+
void CLineGraph::Create(string name,int x,int y)
  {
//--- Коррекция положения индикатора относительно
   x=(x<m_x_size/2)?m_x_size/2:x;
   y=(y<m_y_size/2)?m_y_size/2:y;
   Name(name);
   X(x);
   Y(y);
   XSize(m_x_size);
   YSize(m_y_size);
   if(!CreateCanvas())
      Print("Error. Can not create Canvas.");
//--- Создание рамки графика и заднего фона
   m_canvas.FillRectangle(0,0,XSize(),YSize(),ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillRectangle(1,1,XSize()-2,YSize()-2,ColorToARGB(m_bg_color,m_transparency));
//--- Создание осей и фона графика
   m_canvas.FillRectangle(m_gap-1,m_gap-1,XSize()-m_gap+1,YSize()-m_gap+1,ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillRectangle(m_gap,m_gap,XSize()-m_gap,YSize()-m_gap,ColorToARGB(m_bg_graph_color,m_transparency));
//--- Создание делений оси и их значений
   VerticalScale(m_y_min,m_y_max,m_num_grid);
   HorizontalScale(5);
   m_canvas.Update();
  }

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

//+------------------------------------------------------------------+
//| Устанавливает массив входных данных                              |
//+------------------------------------------------------------------+
void CLineGraph::SetArrayValue(double &data[])
  {
   int y0,y1;
//--- Создание рамки графика и заднего фона
   m_canvas.FillRectangle(0,0,XSize(),YSize(),ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillRectangle(1,1,XSize()-2,YSize()-2,ColorToARGB(m_bg_color,m_transparency));
//--- Создание осей и фона графика
   m_canvas.FillRectangle(m_gap-1,m_gap-1,XSize()-m_gap+1,YSize()-m_gap+1,ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillRectangle(m_gap,m_gap,XSize()-m_gap,YSize()-m_gap,ColorToARGB(m_bg_graph_color,m_transparency));
//--- Если максимальное значение в массиве данных превышает верхний предел оси Y, то масштабируем ось.
   if(data[ArrayMaximum(data)]>m_y_max)
      m_y_max=data[ArrayMaximum(data)];
//--- Создание делений оси и их значений
   VerticalScale(m_y_min,m_y_max,m_num_grid);
   HorizontalScale(ArraySize(data));
//--- Построение графика по массиву данных
   for(int i=0;i<ArraySize(data)-1;i++)
     {
      y0=int((YSize()-2*m_gap)*(1-data[i]/m_y_max));
      y0=int((YSize()-m_gap)-(YSize()-2*m_gap)/m_y_max*data[i]);
      y0=(y0<m_gap)?m_gap:y0;
      y1=int((YSize()-m_gap)-(YSize()-2*m_gap)/m_y_max*data[i+1]);
      y1=(y1<m_gap)?m_gap:y1;
      m_canvas.LineAA(m_x[i+1],y0,m_x[i+2],y1,ColorToARGB(m_graph_color,m_transparency),STYLE_SOLID);
      m_canvas.FillCircle(m_x[i+1],y0,2,ColorToARGB(m_graph_color,m_transparency));
      m_canvas.FillCircle(m_x[i+2],y1,2,ColorToARGB(m_graph_color,m_transparency));
     }
   m_canvas.Update();
  }

В качестве примера использования данного класса было решено построить график по значениям выбранного осциллятора и сравнить его с оригинальным отображением. Для этого был взят обычный RSI. Реализацию построения по его значениям из буфера с помощью класса ClineGraph можно увидеть в листинге ниже.

//+------------------------------------------------------------------+
//|                                                            4.mq5 |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Alexander Fedosov"
#property link      "https://www.mql5.com/ru/users/alex2356"
#property version   "1.00"
#property indicator_plots 0
#property indicator_chart_window

#include <CustomGUI\CustomGUI.mqh>
//---
CLineGraph ind;
int InpInd_Handle;
double rsi[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---- получение хэндла индикатора
   InpInd_Handle=iRSI(Symbol(),PERIOD_CURRENT,10,PRICE_CLOSE);
   if(InpInd_Handle==INVALID_HANDLE)
     {
      Print("Failed to get indicator handle");
      return(INIT_FAILED);
     }
//---
   ind.NumGrid(10);
   ind.YMax(100);
   ind.Create("rsi",350,250);

//---
   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[])
  {
//---
   if(CopyBuffer(InpInd_Handle,0,0,10,rsi)<=0)
      return(0);
   ind.SetArrayValue(rsi);
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ind.Delete();
   ChartRedraw();
  }
//+------------------------------------------------------------------+

Теперь установим оригинальный индикатор с теми же параметрами и сравним полученные результаты на рис.7:

Рис.7. Сравнение реализации построения со помощью класса CLineGraph и оригинального RSI.

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


Заключение

В статье рассмотрены примеры поэтапного создания пользовательских индикаторов произвольной формы с помощью библиотеки пользовательской графики CСanvas. Описаны примеры разной сложности: от простого круглого индикатора, состоящего всего из четырех элементов, до кругового с секционной индикацией. Кроме этого, рассмотрена возможность реализации уже известных видов индикаторов новым способом. При этом возможности не ограничены жестко заданным набором свойств. Данные примеры реализации в статье были структурированы в виде классов, т.е., по своей сути являются дополнением или расширением уже готового класса CCanvas. 

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

Программы, используемые в статье:

#
 Имя
Тип
Описание
1
CanvasBase.mqh Библиотека  Базовый класс пользовательской графики
2
CustomGUI.mqh Библиотека  Файл списка включения всех классов библиотеки 
3 CircleArc.mqh Библиотека  Содержит класс кругового индикатора с дуговой индикацией CCircleArc
4 CircleSection.mqh Библиотека  Содержит класс кругового индикатора с дуговой секционной индикацией CCircleSection
5 CircleSimple.mqh Библиотека  Содержит класс простого кругового индикатора CCircleSimle
LineGraph.mqh Библиотека  Содержит класс линейного графика CLineGraph
7 1.mq5 Индикатор  Пример реализации класса CCirleSimple
8 2.mq5 Индикатор  Пример реализации класса CCirleArc
9 3.mq5 Индикатор  Пример реализации класса CCirleSection
 10 4.mq5 Индикатор  Пример реализации класса CLineGraph