Пользовательские индикаторы и инфографика в CCanvas
Содержание
Введение
В предыдущей статье мы рассмотрели принципы построения графических индикаторов с использованием методов создания простых примитивов класса CCanvas. Но этим возможности использования библиотеки пользовательской графики не ограничиваются, поэтому я предлагаю рассмотреть новые виды индикаторов, с более сложной структурной реализацией. Вдобавок попробуем построить псевдообъемные типы индикаторов и динамично изменяющуюся инфографику.
Класс закругленного линейного индикатора CLineRounded
Для реализации графических объектов в этой статье я не стал заново создавать общую структуру базовых классов — используем уже готовую библиотеку CustomGUI, описанную в предыдущей статье. В частности, базовым классом будет CCanvasBase, а разработанные далее классы будут дополнять список в файле CustomGUI.mqh.
Для создания простого линейного индикатора определимся с его структурой и базовыми элементами. На рис.1 представлены элементы, которыми можно управлять при использовании этого типа индикатора. Отметим, что они не являются простыми элементами.
Рис.1. Базовая структура простого линейного индикатора.
Простым (базовым) элементом в данном случае является тот, который реализуется методами класса CCanvas. Например, в линейном индикаторе простым является только Значение, т.к. оно реализовано с помощью метода CCanvas::TextOut(). Три остальных по своей форме идентичны и состоят из трех базовых фигур — двух закрашенных кругов (реализация CCanvas::FillCircle()) и прямоугольника (CCanvas::FillRectangle()).
Создадим в папке CustomGUI/Indicators файл LineRounded.mqh, в ней — класс СLineRounded и сделаем для него базовым ранее созданный класс CCanvasBase.
//+------------------------------------------------------------------+ //| LineRounded.mqh | //| Copyright 2017, Alexander Fedosov | //| https://www.mql5.com/ru/users/alex2356 | //+------------------------------------------------------------------+ #include "..\CanvasBase.mqh" //+------------------------------------------------------------------+ //| Закругленный линейный индикатор | //+------------------------------------------------------------------+ class CLineRounded : public CCanvasBase
Также добавляем его в список в файле CustomGUI.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" #include "Indicators\LineGraph.mqh" #include "Indicators\LineRounded.mqh" //+------------------------------------------------------------------+
Полный список свойств и методов класса СLineRounded можно изучить в файле CustomGUI/Indicators/LineRounded.mqh. Мы же подробно остановимся на методах создания индикатора, установки и обновления его значения. Как было сказано выше, индикатор будет состоять из составных элементов. В листинге ниже отдельными блоками прокомментированы реализации этих элементов.
//+------------------------------------------------------------------+ //| Создает индикатор | //+------------------------------------------------------------------+ void CLineRounded::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(YSize()/2,0,XSize()-YSize()/2,YSize(),ColorToARGB(m_border_color,m_transparency)); m_canvas.FillCircle(YSize()/2,YSize()/2-1,YSize()/2,ColorToARGB(m_border_color,m_transparency)); m_canvas.FillCircle(XSize()-YSize()/2-1,YSize()/2,YSize()/2,ColorToARGB(m_border_color,m_transparency)); //--- Задний фон m_canvas.FillRectangle(YSize()/2,m_border,XSize()-YSize()/2,YSize()-m_border,ColorToARGB(m_bg_color,m_transparency)); m_canvas.FillCircle(YSize()/2,YSize()/2,YSize()/2-m_border,ColorToARGB(m_bg_color,m_transparency)); m_canvas.FillCircle(XSize()-YSize()/2,YSize()/2,YSize()/2-m_border,ColorToARGB(m_bg_color,m_transparency)); m_canvas.Update(); }
Как видно, Рамка и Задний фон реализованы с использованием двух методов. Не забудьте, что эти объекты являются слоями и отрисовываются, накладываясь друг на друга. Поэтому сначала рисуется рамка, а затем на ней — задний фон меньшего размера, шкала индикатора и численное значение.
Рассмотрим метод CLineRounded::NewValue().
//+------------------------------------------------------------------+ //| Устанавливает и обновляет значение индикатора | //+------------------------------------------------------------------+ void CLineRounded::NewValue(double value,double maxvalue,int digits=0) { int v; v=int((XSize()-YSize()/2)/maxvalue*value); //--- Задний фон m_canvas.FillRectangle(YSize()/2,m_border,XSize()-YSize()/2,YSize()-m_border,ColorToARGB(m_bg_color,m_transparency)); m_canvas.FillCircle(YSize()/2,YSize()/2,YSize()/2-m_border,ColorToARGB(m_bg_color,m_transparency)); m_canvas.FillCircle(XSize()-YSize()/2,YSize()/2,YSize()/2-m_border,ColorToARGB(m_bg_color,m_transparency)); //--- Шкала if(v>=YSize()/2 && v<=(XSize()-YSize()/2)) { m_canvas.FillRectangle(YSize()/2,m_border,v,YSize()-m_border,ColorToARGB(m_scale_color,m_transparency)); m_canvas.FillCircle(YSize()/2,YSize()/2,YSize()/2-m_border,ColorToARGB(m_scale_color,m_transparency)); m_canvas.FillCircle(v,YSize()/2,YSize()/2-m_border,ColorToARGB(m_scale_color,m_transparency)); } else if(v>(XSize()-YSize()/2)) { m_canvas.FillRectangle(YSize()/2,m_border,XSize()-YSize()/2,YSize()-m_border,ColorToARGB(m_scale_color,m_transparency)); m_canvas.FillCircle(YSize()/2,YSize()/2,YSize()/2-m_border,ColorToARGB(m_scale_color,m_transparency)); m_canvas.FillCircle(XSize()-YSize()/2,YSize()/2,YSize()/2-m_border,ColorToARGB(m_scale_color,m_transparency)); } else if(v>0 && v<YSize()/2) m_canvas.FillCircle(YSize()/2,YSize()/2,YSize()/2-m_border,ColorToARGB(m_scale_color,m_transparency)); //--- Численное значение m_canvas.FontSizeSet(m_font_size); m_canvas.TextOut(XSize()/2,YSize()/2,DoubleToString(value,digits),ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER); m_canvas.Update(); } //+------------------------------------------------------------------+Как видно, в начале метода снова присутствует блок реализации заднего фона. Для чего это нужно? Вспомним о послойной отрисовке элементов, с помощью последовательного применения соответствующих методов. То есть, в данном случае мы сначала отрисовываем фон шкалы, а на ней — сам индикатор, длина которого определяется максимальным значением параметра. Когда приходит новое значение индикатора, то заново перерисовывается сначала слой с задним фоном, а уже на нем — шкала с новой длиной.
Создание и передача значения в индикатор реализуется очень просто. В листинге ниже вы видите два индикатора с небольшими настройками и отличающиеся лишь тем, что у одного из них максимальное значение в два раза больше.
//+------------------------------------------------------------------+ //| 001.mq5 | //| Copyright 2017, Alexander Fedosov | //| https://www.mql5.com/ru/users/alex2356 | //+------------------------------------------------------------------+ #property version "1.00" #property indicator_plots 0 #property indicator_chart_window #include <CustomGUI\CustomGUI.mqh> //--- CLineRounded ind1,ind2; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- ind1.XSizeInd(350); ind1.YSizeInd(30); ind1.Create("line1",300,100); //--- ind2.XSizeInd(350); ind2.YSizeInd(30); ind2.ScaleColor(clrFireBrick); ind2.Create("line2",300,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[]) { //--- ind1.NewValue(50,100,2); ind2.NewValue(50,200,2); //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ind1.Delete(); ind2.Delete(); } //+------------------------------------------------------------------+
На рис.2 прекрасно видно, что при одинаковых значениях и разных масштабах длина шкалы индикатора имеет соответствующую величину. Значение 50 и максимальные (100 для первого и 200 для второго) выбраны так, чтобы даже визуально можно было оценить результат отображения.
Рис.2. Пример работы закругленного линейного индикатора.
Класс шестиугольного индикатора CHexagon
Для построения индикатора в форме правильного шестиугольника с помощью заданных классом CCanvas примитивов можно выбрать несколько способов. Это могут быть и построение полилинии, и последующее закрашивание области, и "сборка" из шести равносторонних треугольников, наподобие кусочков пиццы. Но наша цель —максимальная простота для понимания. Поэтому соберем правильный шестиугольник из трех примитивов — прямоугольника и двух равнобедренных треугольников.
Рис.3. Структура правильного шестиугольника.
Правильный шестиугольник будет вписан в квадратный холст. При этом нужно помнить свойства этой фигуры, а именно:
- Сторона правильного шестиугольника равна радиусу описанной около него окружности, или в нашем случае — половине стороны холста. Это потребуется при построении прямоугольника.
- Угол шестиугольника равен 120 градусов, поэтому углы при основаниях равнобедренных треугольников будут по 30 градусов. Эта информация нужна для определения высоты треугольника и, соответственно, для нахождения координат точек оснований треугольников при использовании метода CCanvas::FillTriangle().
Базовая структура самого индикатора достаточна проста и, помимо шестиугольника, включает 2 текстовых объекта — численное значение и описание (рис.4).
Рис.4. Базовая структура шестиугольного индикатора.
Создадим в папке CustomGUI/Indicators файл Hexagon.mqh, в нем — класс СHexagon и сделаем для него базовым ранее созданный класс CCanvasBase. Подключим его в общий список:
//+------------------------------------------------------------------+ //| 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" #include "Indicators\LineGraph.mqh" #include "Indicators\LineRounded.mqh" #include "Indicators\Hexagon.mqh" //+------------------------------------------------------------------+
С общим списком свойств и методов можно ознакомиться в файле, созданном выше. Остановимся лишь на методах, отвечающих за визуализацию индикатора.
//+------------------------------------------------------------------+ //| Создает индикатор | //+------------------------------------------------------------------+ void CHexagon::Create(string name,int x,int y) { int a,r; r=m_size; //--- Коррекция положения индикатора относительно координатных осей x=(x<r/2)?r/2:x; y=(y<r/2)?r/2:y; Name(name); X(x); Y(y); XSize(r); YSize(r); if(!CreateCanvas()) Print("Error. Can not create Canvas."); //--- Вычисление высоты треугольников a=int(YSize()/2*MathSin(30*M_PI/180)); //--- Шестиугольная форма m_canvas.FillTriangle(XSize()/2,0,0,a,XSize(),a,ColorToARGB(m_bg_color,m_transparency)); m_canvas.FillRectangle(0,a,XSize(),a+YSize()/2,ColorToARGB(m_bg_color,m_transparency)); m_canvas.FillTriangle(0,a+YSize()/2,XSize(),a+YSize()/2,XSize()/2,YSize(),ColorToARGB(m_bg_color,m_transparency)); //--- Описание и численные значения m_canvas.FontNameSet("Trebuchet MS"); m_canvas.FontSizeSet(m_value_font_size); m_canvas.TextOut(r/2,r/2,"-",ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER); m_canvas.FontSizeSet(m_label_font_size); m_canvas.TextOut(r/2,r/2+m_value_font_size,m_label_value,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER); //--- m_canvas.Update(); }
В методе Create() переменной a присваивается значение высоты равнобедренных треугольников, т.е. по сути, таким способом мы находим координаты точек треугольника по оси ординат(Y). Метод передачи и обновления численного значения отличен лишь тем, что в текстовый объект, отвечающий за отрисовку численного значения, передается значение аргумента value:
//+------------------------------------------------------------------+ //| Устанавливает и обновляет значение индикатора | //+------------------------------------------------------------------+ void CHexagon::NewValue(double value,int digits=2) { int a,r; r=m_size; //--- Вычисление высоты треугольников a=int(YSize()/2*MathSin(30*M_PI/180)); //--- Шестиугольная форма m_canvas.FillTriangle(XSize()/2,0,0,a,XSize(),a,ColorToARGB(m_bg_color,m_transparency)); m_canvas.FillRectangle(0,a,XSize(),a+YSize()/2,ColorToARGB(m_bg_color,m_transparency)); m_canvas.FillTriangle(0,a+YSize()/2,XSize(),a+YSize()/2,XSize()/2,YSize(),ColorToARGB(m_bg_color,m_transparency)); //--- Текст m_canvas.FontNameSet("Trebuchet MS"); m_canvas.FontSizeSet(m_value_font_size); m_canvas.TextOut(r/2,r/2,DoubleToString(value,digits),ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER); m_canvas.FontSizeSet(m_label_font_size); m_canvas.TextOut(r/2,r/2+m_value_font_size,m_label_value,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER); //--- m_canvas.Update(); } //+------------------------------------------------------------------+
Для примера реализуем небольшой набор из индикаторов шестиугольного типа:
//+------------------------------------------------------------------+ //| 002.mq5 | //| Copyright 2017, Alexander Fedosov | //| https://www.mql5.com/ru/users/alex2356 | //+------------------------------------------------------------------+ #property version "1.00" #property indicator_plots 0 #property indicator_chart_window #include <CustomGUI\CustomGUI.mqh> //--- CHexagon ind1,ind2,ind3,ind4,ind5,ind6,ind7,ind8,ind9,ind10,ind11,ind12,ind13,ind14; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- ind1.BgColor(clrWhite); ind1.Size(110); ind1.Create("hex1",300,200); ind2.BgColor(C'60,170,220'); ind2.Create("hex2",300,200); //--- ind3.BgColor(clrWhite); ind3.Size(110); ind3.Create("hex3",300,80); ind4.BgColor(C'230,80,25'); ind4.Create("hex4",300,80); //--- ind5.BgColor(clrWhite); ind5.Size(110); ind5.Create("hex5",300,320); ind6.BgColor(C'150,190,15'); ind6.Create("hex6",300,320); //--- ind7.BgColor(clrWhite); ind7.Size(110); ind7.Create("hex7",180,140); ind8.BgColor(C'10,115,185'); ind8.Create("hex8",180,140); //--- ind9.BgColor(clrWhite); ind9.Size(110); ind9.Create("hex9",420,140); ind10.BgColor(C'20,150,150'); ind10.Create("hex10",420,140); //--- ind11.BgColor(clrWhite); ind11.Size(110); ind11.Create("hex11",420,280); ind12.BgColor(C'225,0,80'); ind12.Create("hex12",420,280); //--- ind13.BgColor(clrWhite); ind13.Size(110); ind13.Create("hex13",180,280); ind14.BgColor(C'240,145,5'); ind14.Create("hex14",180,280); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ind1.Delete(); ind2.Delete(); ind3.Delete(); ind4.Delete(); ind5.Delete(); ind6.Delete(); ind7.Delete(); ind8.Delete(); ind9.Delete(); ind10.Delete(); ind11.Delete(); ind12.Delete(); ind13.Delete(); ind14.Delete(); } //+------------------------------------------------------------------+
Результат работы представлен на рис.5. Этот базовый шаблон очень просто настроить и изменить.
Рис.5. Пример реализации набора индикаторов с использованием класс CHexagon.
Класс лепесткового индикатора CPetal
В реализации индикатора в форме лепестка требуется 2-3 примитива из набора методов класса CCanvas. Это элемент закрашенный круг (метода FillCircle()) и, в зависимости от формы, 1-2 закрашенных треугольника (FillTriangle()). Структура и набор элементов представлены на рис.6.
Рис.6. Базовая структура лепесткового индикатора.
Как видно, здесь используется 2 треугольника, но мы включим в класс несколько видов форм с помощью перечисления enum. Рассмотрим эти виды более подробно:
enum ENUM_POSITION { TOPRIGHT=1, TOPLEFT=2, BOTTOMRIGHT=3, BOTTOMLEFT=4, BOTHRIGHT=5, BOTHLEFT=6 };
- TOPRIGHT — состоит из круга и треугольника, видимая вершина которого направлена в верхний правый угол.
- TOPLEFT — состоит из круга и треугольника, видимая вершина которого направлена в верхний левый угол.
- BOTTOMRIGHT — состоит из круга и треугольника, видимая вершина которого направлена в нижний правый угол.
- BOTTOMLEFT — состоит из круга и треугольника, видимая вершина которого направлена в нижний левый угол.
- BOTHRIGHT — состоит из круга и двух треугольников, верхний треугольник при этом находится в правом верхнем углу.
- BOTHLEFT— состоит из круга и двух треугольников, верхний треугольник при этом находится в левом верхнем углу.
Рис.7. Виды форм лепесткового индикатора.
Приступим к реализации. Создадим в папке CustomGUI/Indicators файл Petal.mqh, в нем — класс СPetal и сделаем для него базовым ранее созданный класс CCanvasBase. Также подключим его в общий список, который теперь будет выглядеть так:
//+------------------------------------------------------------------+ //| 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" #include "Indicators\LineGraph.mqh" #include "Indicators\LineRounded.mqh" #include "Indicators\Hexagon.mqh" #include "Indicators\Petal.mqh" //+------------------------------------------------------------------+
Не будем останавливаться на стандартных свойствах. Рассмотрим методы создания индикатора и обновления его значений. В методе Create() после построения закрашенного круга, в зависимости от рассмотренного ранее свойства "вид формы", перечислением ENUM_POSITION отрисовываются закрашенные треугольники в заданных позициях:
//+------------------------------------------------------------------+ //| Создает индикатор | //+------------------------------------------------------------------+ void CPetal::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."); //--- m_canvas.FillCircle(r,r,r,ColorToARGB(m_bg_color,m_transparency)); if(m_orientation==TOPRIGHT) m_canvas.FillTriangle(XSize()/2,0,XSize(),0,XSize(),YSize()/2,ColorToARGB(m_bg_color,m_transparency)); else if(m_orientation==TOPLEFT) m_canvas.FillTriangle(0,0,XSize()/2,0,0,YSize()/2,ColorToARGB(m_bg_color,m_transparency)); else if(m_orientation==BOTTOMRIGHT) m_canvas.FillTriangle(XSize(),YSize(),XSize(),YSize()/2,XSize()/2,YSize(),ColorToARGB(m_bg_color,m_transparency)); else if(m_orientation==BOTTOMLEFT) m_canvas.FillTriangle(0,YSize(),0,YSize()/2,XSize()/2,YSize(),ColorToARGB(m_bg_color,m_transparency)); else if(m_orientation==BOTHRIGHT) { m_canvas.FillTriangle(XSize()/2,0,XSize(),0,XSize(),YSize()/2,ColorToARGB(m_bg_color,m_transparency)); m_canvas.FillTriangle(0,YSize(),0,YSize()/2,XSize()/2,YSize(),ColorToARGB(m_bg_color,m_transparency)); } else if(m_orientation==BOTHLEFT) { m_canvas.FillTriangle(0,0,XSize()/2,0,0,YSize()/2,ColorToARGB(m_bg_color,m_transparency)); m_canvas.FillTriangle(XSize(),YSize(),XSize(),YSize()/2,XSize()/2,YSize(),ColorToARGB(m_bg_color,m_transparency)); } //--- Описание и численные значения m_canvas.FontNameSet("Trebuchet MS"); m_canvas.FontSizeSet(m_value_font_size); m_canvas.TextOut(r,r,"-",ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER); 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.Update(); }
В качестве примера приведем шаблон для создания индикатора в форме бабочки.
//+------------------------------------------------------------------+ //| 003.mq5 | //| Copyright 2017, Alexander Fedosov | //| https://www.mql5.com/ru/users/alex2356 | //+------------------------------------------------------------------+ #property version "1.00" #property indicator_plots 0 #property indicator_chart_window #include <CustomGUI\CustomGUI.mqh> //--- CPetal ind1,ind2,ind3,ind4; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { int b=2; //--- ind1.BgColor(C'230,80,80'); ind1.Orientation(BOTHLEFT); ind1.Create("petal1",200-b,100-b); ind2.BgColor(C'35,170,190'); ind2.Orientation(BOTHRIGHT); ind2.Create("petal2",300+b,100-b); ind3.BgColor(C'245,155,70'); ind3.Orientation(BOTHRIGHT); ind3.Create("petal3",200-b,200+b); ind4.BgColor(C'90,200,130'); ind4.Orientation(BOTHLEFT); ind4.Create("petal4",300+b,200+b); 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[]) { //--- //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ind1.Delete(); ind2.Delete(); ind3.Delete(); ind4.Delete(); } //+------------------------------------------------------------------+
Рис.8. Пример использования лепесткового индикатора.
Класс индикатора гистограмма CHistogram
При построении класса гистограмм за основу был взят класс CLIneGraph, а точнее — общая структура построения осей координат, делений оси и их значений. Поэтому описывать этот этап избыточно. Остановимся более подробно на видах гистограмм и самой реализации с помощью примитивов класса CCanvas. Прежде чем рассмотреть реализацию индикатора гистограммного типа, определимся с видами форм.
enum ENUM_TYPE_HISTOGRAM { SIMPLE=1, TRIANGLE=2, RECTANGLE=3 };
- SIMPLE — за основу простой формы было решено взять гистограмму в виде треугольника (рис.10), высота которого будет численным значением на графике.
- TRIANGLE — гистограмма в виде треугольника псевдообъемного типа (рис.11).
- RECTANGLE — классический вид гистограммы в виде столбцов (рис.12).
Рис.10. Вид гистограммы типа SIMPLE.
Рис.11. Вид гистограммы типа TRIANGLE.
Рис.12. Вид гистограммы типа RECTANGLE.
Создадим в папке CustomGUI/Indicators файл Histogram.mqh, в нем — класс СHistogram и сделаем для него базовым ранее созданный класс CCanvasBase. Также подключим его в общий список в файл CustomGUI.mqh. Большинство методов и свойств данного класса идентичны классу CLineGraph, в том числе и метод Create(). Поэтому рассмотрим лишь принципиальные отличия, а именно — метод SetArrayValue().
//+------------------------------------------------------------------+ //| Устанавливает массив входных данных | //+------------------------------------------------------------------+ void CHistogram::SetArrayValue(double &data[]) { int x,y,y1,y2; //--- Создание рамки графика и заднего фона 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)); ArrayResize(m_label_value,ArraySize(data)); //--- Построение гистограмм по массиву данных for(int i=0;i<ArraySize(data);i++) { if(data[i]>0) { x=int(m_x[i]+(m_x[i+1]-m_x[i])/2); y=int((YSize()-m_gap)-(YSize()-2*m_gap)/m_y_max*data[i]); y1=int((YSize()-m_gap)-(YSize()-2*m_gap)/m_y_max*data[i]*0.3); y2=int((YSize()-m_gap)-(YSize()-2*m_gap)/m_y_max*data[i]*0.1); y=(y<m_gap)?m_gap:y; if(m_type==SIMPLE) m_canvas.FillTriangle(m_x[i],YSize()-m_gap,m_x[i+1],YSize()-m_gap,x,y,ColorToARGB(m_graph_color1,m_transparency)); else if(m_type==TRIANGLE) { m_canvas.FillTriangle(m_x[i],YSize()-m_gap,x,y,x,y1,ColorToARGB(m_graph_color1,m_transparency)); m_canvas.FillTriangle(m_x[i],YSize()-m_gap,m_x[i+1],YSize()-m_gap,x,y1,ColorToARGB(m_graph_color2,m_transparency)); m_canvas.FillTriangle(x,y,x,y1,m_x[i+1],YSize()-m_gap,ColorToARGB(m_graph_color3,m_transparency)); } else if(m_type==RECTANGLE) { int x1,x2; x1=int(m_x[i]+(m_x[i+1]-m_x[i])/3); x2=int(m_x[i]+2*(m_x[i+1]-m_x[i])/3); m_canvas.FillRectangle(x1,y,x2,YSize()-m_gap,ColorToARGB(m_graph_color1,m_transparency)); } //--- Описание и численные значения m_canvas.FontNameSet("Trebuchet MS"); m_canvas.FontSizeSet(m_value_font_size); m_canvas.TextOut(x,y2,DoubleToString(data[i],2),ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER); m_canvas.TextOut(x,y-5,m_label_value[i],ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER); } } m_canvas.Update(); }
Существенное отличие состоит в том, что в блоке построения гистограмм по заданному массиву данных учитывается тип гистограммы ENUM_TYPE_HISTOGRAM, рассмотренный чуть выше. В качестве примера использования был реализован способ визуального отображения трех индикаторов RSI с различными периодами.
//+------------------------------------------------------------------+ //| 004.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> //+------------------------------------------------------------------+ //| Входные параметры индикатора | //+------------------------------------------------------------------+ input int RSIPeriod1=10; //Period of the indicator1 input int RSIPeriod2=14; //Period of the indicator2 input int RSIPeriod3=18; //Period of the indicator3 input ENUM_TYPE_HISTOGRAM Type=RECTANGLE; //Type Histogram //--- CHistogram ind; int InpInd_Handle1,InpInd_Handle2,InpInd_Handle3; double rsi1[],rsi2[],rsi3[],ex[3]; string descr[3]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //---- получение хендла индикатора InpInd_Handle1=iRSI(Symbol(),PERIOD_CURRENT,RSIPeriod1,PRICE_CLOSE); InpInd_Handle2=iRSI(Symbol(),PERIOD_CURRENT,RSIPeriod2,PRICE_CLOSE); InpInd_Handle3=iRSI(Symbol(),PERIOD_CURRENT,RSIPeriod3,PRICE_CLOSE); if(InpInd_Handle1==INVALID_HANDLE || InpInd_Handle2==INVALID_HANDLE || InpInd_Handle3==INVALID_HANDLE ) { Print("Failed to get indicator handle"); return(INIT_FAILED); } //--- descr[0]="RSI("+IntegerToString(RSIPeriod1)+")"; descr[1]="RSI("+IntegerToString(RSIPeriod2)+")"; descr[2]="RSI("+IntegerToString(RSIPeriod3)+")"; ind.NumGrid(10); ind.YMax(100); ind.Type(Type); ind.Create("rsi",350,250); ind.SetArrayLabel(descr); //--- ArraySetAsSeries(rsi1,true); ArraySetAsSeries(rsi2,true); ArraySetAsSeries(rsi3,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[]) { //--- if(CopyBuffer(InpInd_Handle1,0,0,1,rsi1)<=0 || CopyBuffer(InpInd_Handle2,0,0,1,rsi2)<=0 || CopyBuffer(InpInd_Handle3,0,0,1,rsi3)<=0 ) return(0); ex[0]=rsi1[0]; ex[1]=rsi2[0]; ex[2]=rsi3[0]; ind.SetArrayValue(ex); //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ind.Delete(); ChartRedraw(); } //+------------------------------------------------------------------+
Рис.13. Результат работы индикатора в виде гистограммы для трех периодов индикатора RSI.
Класс индикатора пирамида CPyramid
Мы уже рассмотрели классы и принципы построения сложных фигур с помощью примитивов. А в классе построения индикаторов гистограммного типа частично была затронута тема построения в двухмерном пространстве объектов, кажущихся объемными (рис.13) за счет цветовой подборки. Однако пирамида — не плоская фигура, поэтому при ее построении в заданной двухмерной системе координат мы используем ее изометрическую проекцию. Базовая структура не отличается большим числом элементов (рис.14), но тем не менее, способ построения проекции пирамиды и ее визуализация — основная часть реализации этого класса.
Рис.14. Базовая структура класса CPyramid.
Создадим в папке CustomGUI/Indicators файл Pyramid.mqh, в нем — класс СPyramid и сделаем для него базовым ранее созданный класс CCanvasBase. Также подключим его в общий список в файл CustomGUI.mqh. Теперь общий список подключенных классов будет таким:
//+------------------------------------------------------------------+ //| CustomGUI.mqh | //+------------------------------------------------------------------+ #include "Indicators\CircleSimple.mqh" #include "Indicators\CircleArc.mqh" #include "Indicators\CircleSection.mqh" #include "Indicators\LineGraph.mqh" #include "Indicators\LineRounded.mqh" #include "Indicators\Hexagon.mqh" #include "Indicators\Petal.mqh" #include "Indicators\Histogram.mqh" #include "Indicators\Pyramid.mqh" //+------------------------------------------------------------------+
С полным списком всех свойств и методов можно ознакомиться в самом классе, остановимся на ключевых. Прежде чем рассматривать основные методы Create() и NewValue() остановимся на тех приватных методах, которые в них используются.
//+------------------------------------------------------------------+ //| Записывает в массив коэффициенты уравнения прямой по двум точкам | //+------------------------------------------------------------------+ void CPyramid::Equation(double x1,double y1,double x2,double y2,double &arr[]) { ArrayResize(arr,2); arr[0]=(y1-y2)/(x1-x2); arr[1]=y2-arr[0]*x2; }
Метод Equation() служит для нахождения уравнения прямой по двум точкам, в частности — определяет коэффициенты k и b для общего уравнения y = kx + b из канонического уравнения прямой по двум точкам:
Этот метод нужен для определения уравнений прямых по заданным точкам и в дальнейшем — для нахождения точек пересечения ребер AB и AC пирамиды и прямых, параллельных сторонам основания пирамиды. Координаты точек, показанных на рис.15, необходимы для построения треугольников, подобных боковым граням пирамиды. В свою очередь, они являются секциями нашего индикатора.
Рис.15. Точки пересечения ребер пирамиды и прямых параллельных сторонам основания пирамиды.
Зная коэффициенты двух прямых, с помощью системы уравнений вычислим координаты точки пересечения. За это отвечает метод Cross():
//+---------------------------------------------------------------------------------+ //| Записывает в массив координаты точки пересечения двух прямых по их коэффициентам | //+---------------------------------------------------------------------------------+ void CPyramid::Cross(double k1,double b1,double k2,double b2,double &arr[]) { double x,y; ArrayResize(arr,2); y=(b1*k2-b2*k1)/(k2-k1); x=(y-b2)/k2; arr[0]=x; arr[1]=y; } //+------------------------------------------------------------------+
Теперь, имея представление о функциях, используемых при создании пирамиды, можно более подробно рассмотреть метод Create().
//+------------------------------------------------------------------+ //| Создает индикатор | //+------------------------------------------------------------------+ void CPyramid::Create(string name,int x,int y) { int size=m_size; //--- Коррекция положения индикатора относительно его размера x=(x<size/2)?size/2:x; y=(y<size/2)?size/2:y; Name(name); X(x); Y(y); XSize(size); YSize(size); if(!CreateCanvas()) Print("Error. Can not create Canvas."); //--- Описание и численные значения m_canvas.FontNameSet("Trebuchet MS"); m_canvas.FontSizeSet(m_font_size); m_canvas.FontAngleSet(200); //--- Нахождение уравнения прямой AB double x1,x2,y1,y2; x1=XSize()/2;y1=0; x2=0;y2=4*YSize()/5; Equation(x1,y1,x2,y2,m_line1); //--- Построение левых секций индикатора for(int i=5;i>0;i--) { //--- Определение ординат двух точек y1=i*YSize()/5; y2=(i-1)*YSize()/5; Equation(x1,y1,x2,y2,m_line2); //--- Определение координат точки пересечения ребра AB и прямой, параллельной стороне основания пирамиды Cross(m_line1[0],m_line1[1],m_line2[0],m_line2[1],m_cross); m_canvas.FillTriangle(int(x1),0,int(x1),int(y1),int(m_cross[0]),int(m_cross[1]),ColorToARGB(m_section_color[i-1],m_transparency)); //--- Построение делений шкалы m_canvas.LineAA(int(x1),int(y1),int(m_cross[0]),int(m_cross[1]),ColorToARGB(m_scale_color,m_transparency)); } //--- Нахождение уравнения прямой AC x1=XSize()/2;y1=0; x2=XSize();y2=4*YSize()/5; Equation(x1,y1,x2,y2,m_line1); //--- Построение правых секций индикатора for(int i=5;i>0;i--) { //--- Определение ординат двух точек y1=i*YSize()/5; y2=(i-1)*YSize()/5; Equation(x1,y1,x2,y2,m_line2); //--- Определение координат точки пересечение ребра AС и прямой, параллельной стороне основания пирамиды Cross(m_line1[0],m_line1[1],m_line2[0],m_line2[1],m_cross); m_canvas.FillTriangle(int(x1),0,int(x1),int(y1),int(m_cross[0]),int(m_cross[1]),ColorToARGB(m_section_color[i-1])); //--- Построение делений шкалы m_canvas.LineAA(int(x1),int(y1),int(m_cross[0]),int(m_cross[1]),ColorToARGB(m_scale_color,m_transparency)); //--- Построение значений шкалы m_canvas.TextOut(int(x1+2),int(y2+YSize()/6)," - ",ColorToARGB(m_label_color,m_transparency),TA_LEFT|TA_VCENTER); } m_canvas.LineVertical(XSize()/2,0,YSize(),ColorToARGB(m_scale_color,m_transparency)); m_canvas.Update(); }
Алгоритм построения пирамиды таков:
- Строим левую сторону пирамиды. Определяются координаты точек A и B, по ним находится уравнение прямой.
- Далее в цикле последовательно находим уравнения прямых, параллельных основаниям пирамиды, и находим точку их пересечения с ребром AB.
- По полученным точкам строим секции и деления шкалы.
- Строим правую сторону пирамиды аналогично.
- Помимо секций и делений шкал, на правой стороне добавляем значения шкалы.
- Вертикальное разделение двух секций.
В методе установки и обновления данных присутствует два аргумента: это текущее передаваемое значение и максимальное значение. Суть метода состоит в том, что исходя из максимально установленного значения, определяются пороговые значения, при прохождении которых каждая секция меняет цвет. При превышении порогового значения секция окрашивается в установленный для нее цвет, при понижении — в цвет, установленный неактивным.
//+------------------------------------------------------------------+ //| Устанавливает и обновляет значение индикатора | //+------------------------------------------------------------------+ void CPyramid::NewValue(double value,double maxvalue) { //--- double s; color col; //--- Нахождение уравнения прямой AB double x1,x2,y1,y2; x1=XSize()/2;y1=0; x2=0;y2=4*YSize()/5; Equation(x1,y1,x2,y2,m_line1); //--- Построение левых секций индикатора for(int i=5;i>0;i--) { //--- Нахождение значений деления шкалы s=maxvalue-(i-1)*maxvalue/5; //--- Определение цвета секции col=(value>=s)?m_section_color[i-1]:m_bg_color; //--- Определение ординат двух точек y1=i*YSize()/5; y2=(i-1)*YSize()/5; Equation(x1,y1,x2,y2,m_line2); //--- Определение координат точки пересечение ребра AB и прямой, параллельной стороне основания пирамиды Cross(m_line1[0],m_line1[1],m_line2[0],m_line2[1],m_cross); m_canvas.FillTriangle(int(x1),0,int(x1),int(y1),int(m_cross[0]),int(m_cross[1]),ColorToARGB(m_section_color[i-1],m_transparency)); m_canvas.FillTriangle(int(x1),0,int(x1),int(y1),int(m_cross[0]),int(m_cross[1]),ColorToARGB(col,m_transparency)); //--- Построение делений шкалы m_canvas.LineAA(int(x1),int(y1),int(m_cross[0]),int(m_cross[1]),ColorToARGB(clrWhite)); } //--- Нахождение уравнения прямой AC x1=XSize()/2;y1=0; x2=XSize();y2=4*YSize()/5; Equation(x1,y1,x2,y2,m_line1); //--- Построение правых секций индикатора for(int i=5;i>0;i--) { //--- Нахождение значений деления шкалы s=maxvalue-(i-1)*maxvalue/5; //--- Определение цвета секции col=(value>=s)?m_section_color[i-1]:m_bg_color; //--- Определение ординат двух точек y1=i*YSize()/5; y2=(i-1)*YSize()/5; Equation(x1,y1,x2,y2,m_line2); //--- Определение координат точки пересечение ребра AС и прямой, параллельной стороне основания пирамиды Cross(m_line1[0],m_line1[1],m_line2[0],m_line2[1],m_cross); m_canvas.FillTriangle(int(x1),0,int(x1),int(y1),int(m_cross[0]),int(m_cross[1]),ColorToARGB(m_section_color[i-1],m_transparency)); m_canvas.FillTriangle(int(x1),0,int(x1),int(y1),int(m_cross[0]),int(m_cross[1]),ColorToARGB(col,m_transparency)); //--- Построение делений шкалы m_canvas.LineAA(int(x1),int(y1),int(m_cross[0]),int(m_cross[1]),ColorToARGB(m_scale_color,m_transparency)); //--- Построение значений шкалы m_canvas.TextOut(int(x1+2),int(y2+YSize()/6),DoubleToString(s,0),ColorToARGB(m_label_color,m_transparency),TA_LEFT|TA_VCENTER); } m_canvas.LineVertical(XSize()/2,0,YSize(),ColorToARGB(m_scale_color,m_transparency)); m_canvas.Update(); }
Несмотря на более сложную реализацию отрисовки индикатора, создавать, настраивать и использовать его не сложнее предыдущих. В качестве небольшого примера используем его для отображения значений стандартного ADX. При этом, в зависимости от его местоположения, добавим круговой индикатор слева вверху для сравнения и наглядности отображения.
//+------------------------------------------------------------------+ //| 005.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> //+------------------------------------------------------------------+ //| Входные параметры индикатора | //+------------------------------------------------------------------+ input int period=10; //ADX Period input double maxval=50; //Max value //--- CPyramid ind1; CCircleSimple ind2; int InpInd_Handle; double adx[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //---- получение хэндла индикатора InpInd_Handle=iADX(Symbol(),PERIOD_CURRENT,period); if(InpInd_Handle==INVALID_HANDLE) { Print("Failed to get indicator handle"); return(INIT_FAILED); } ArraySetAsSeries(adx,true); //--- ind1.Size(250); ind1.Create("pyramid",200,0); //--- ind2.FontSize(ind1.FontSize()); ind2.LabelSize(ind1.FontSize()); ind2.Label("ADX"); ind2.Radius(30); ind2.Create("label",ind1.X()-ind1.Size()/3,ind1.Y()-ind1.Size()/4); 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,2,adx)<=0) return(0); ind1.NewValue(adx[0],maxval); ind2.NewValue(adx[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(); } //+------------------------------------------------------------------+
Как видно на рис. 16, круговой индикатор отображает значение выше третьего порогового значения 30, поэтому цветом окрашены три секции. Само собой, при текущем максимальном значении, которое в данном случае установлено в 50.
Рис.16. Пример использования индикатора в виде пирамиды.
Заключение
Как видно из рассмотренных в данной статье реализаций классов на основе CCanvas, возможности библиотеки в плане графики очень обширны. Виды и способы отображения такого типа индикаторов ограничены разве что воображением. При этом создание и организация таких индикаторов в виде библиотеки подключаемых классов не требует особых знаний или усилий.
В конце статьи приложен архив со всеми используемыми в статье файлами, рассортированными по папкам. Поэтому для корректной работы достаточно положить папку MQL5 из архива в корень терминала.
Программы, находящиеся в архиве и используемые в статье:
# |
Имя |
Тип |
Описание |
---|---|---|---|
1 |
CanvasBase.mqh | Библиотека | Базовый класс пользовательской графики |
2 |
CustomGUI.mqh | Библиотека | Файл списка включения всех классов библиотеки |
3 | CircleArc.mqh | Библиотека | Содержит класс кругового индикатора с дуговой индикацией CCircleArc |
4 | CircleSection.mqh | Библиотека | Содержит класс кругового индикатора с дуговой секционной индикацией CCircleSection |
5 | CircleSimple.mqh | Библиотека | Содержит класс простого кругового индикатора CCircleSimle |
6 | LineGraph.mqh | Библиотека | Содержит класс линейного графика CLineGraph |
7 | LineRounded.mqh | Библиотека | Содержит класс закругленного линейного индикатора CLineRounded |
8 | Hexagon.mqh | Библиотека | Содержит класс шестиугольного индикатора CHexagon |
9 | Petal.mqh | Библиотека | Содержит класс лепесткового индикатора CPetal |
10 | Histogram.mqh | Библиотека | Содержит класс индикатора гистограмма CHistogram |
11 | Pyramid.mqh | Библиотека | Содержит класс индикатора пирамида CPyramid |
12 | CLineRounded.mq5 | Индикатор | Пример реализации класса CLineRounded |
13 | CHexagon.mq5 | Индикатор | Пример реализации класса CHexagon |
14 | CPetal.mq5 | Индикатор | Пример реализации класса CPetal |
15 | CHistogram.mq5 | Индикатор | Пример реализации класса CHistogram |
16 | CPyramid.mq5 | Индикатор | Пример реализации класса CPyramid |
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
В 10-й раз тема отмечается непрочитанной, хотя ни правок сообщений, ни новых сообщений нет.
Кто-то шалит или форум ключит?
В 10-й раз тема отмечается непрочитанной, хотя ни правок сообщений, ни новых сообщений нет.
Кто-то шалит или форум ключит?
У меня подозрение, что авторы (автор?) правит свой пост.
Ещё вариант: кто-то начал писать пост и бросил, теперь этот пост висит в режиме редактирования, а форум каждую минуту (5, 10 минут?) отмечает, что в теме что-то происходит.
В 10-й раз тема отмечается непрочитанной, хотя ни правок сообщений, ни новых сообщений нет.
Кто-то шалит или форум ключит?
Это я, наверное, код обновлял и прикрепленые файлы. :)
Полдня и всю ночь?
Полдня и всю ночь?