利用 CCanvas 类开发自定义指标

Alexander Fedosov | 24 七月, 2017


内容

概述

自定义指标是现代 MetaTrader 5 交易的组成部分。它们均用于自动交易系统 (作为算法的一部分) 和手工交易。迄今, 在开发指标时, 可以设置 绘图样式并应用 18 种图形绘图。但平台具有更广泛的图形功能。CCanvas 自定义图形 库能够开发自定义的具有无限视效能力的非标准指标。本文为读者介绍函数库功能, 并提供其应用的示例。并且还开发了一个单独的自定义指标函数库。


开发自定义图形基类

为了开发一个基类, 我们需要编写一组方法来创建任意自定义图形对象的基础, 并包括一组通用的属性。为达此目的, <数据文件夹>\MQL5\Include 目录的 CustomGUI 文件夹里创建 CanvasBase.mqh 文件。这个文件将包含 CCanvasBase 基类, 用于所有未来类型的自定义图形。

//+------------------------------------------------------------------+
//|                                                  CCanvasBase.mqh |
//|                                 版权所有 2017, Alexander Fedosov |
//|                           https://www.mql5.com/zh/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;  }
  };
//+------------------------------------------------------------------+
//| 构造器                                                            |
//+------------------------------------------------------------------+
CCanvasBase::CCanvasBase(void) : m_x(0),
                                 m_y(0),
                                 m_x_size(200),
                                 m_y_size(200)
  {
  }
//+------------------------------------------------------------------+
//| 析构器                                                            |
//+------------------------------------------------------------------+
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 |
//|                                 版权所有 2017, Alexander Fedosov  |
//|                           https://www.mql5.com/zh/users/alex2356 |
//+------------------------------------------------------------------+
#include "..\CanvasBase.mqh"
//+------------------------------------------------------------------+
//| 带有数值和描述的圆形指标                                              |
//+------------------------------------------------------------------+
class CCircleSimple : public CCanvasBase

为了免于在 EA 和指标文件中手工输入所需的图形对象, 请在 CustomGUI 文件夹中创建 CustomGUI.mqh 文件。文件将包括 Indicators 文件夹下所有类的内容。因此, 我们只需要包含这个文件即可访问函数库中的所有类。现在, 让我们来看一看:

//+------------------------------------------------------------------+
//|                                                    CustomGUI.mqh |
//|                                 版权所有 2017, Alexander Fedosov  |
//|                           https://www.mql5.com/zh/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()。正如我们所见, 它只有三个参数。我发现它们是最重要的。提供额外的参数是多余的, 因为这令方法的实现变得复杂。因此, 所有其它属性都划分到单独的方法之中。接下来, 所有变量都会在类构造函数中初始化:

//+------------------------------------------------------------------+
//| 构造器                                                            |
//+------------------------------------------------------------------+
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("错误。未能创建指标。");
   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 |
//|                                   版权所有 2017, Alexander Fedosov |
//|                           https://www.mql5.com/zh/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "版权所有 2017, Alexander Fedosov"
#property link      "https://www.mql5.com/zh/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[];
//+------------------------------------------------------------------+
//| 自定义指标初始化函数                                                 |
//+------------------------------------------------------------------+
int OnInit()
  {

//---- 获取指标句柄
   InpInd_Handle=iADX(Symbol(),PERIOD_CURRENT,10);
   InpInd_Handle1=iRSI(Symbol(),PERIOD_CURRENT,10,PRICE_CLOSE);
   if(InpInd_Handle==INVALID_HANDLE)
     {
      Print("获取指标句柄失败");
      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]);
//--- 返回 prev_calculated 的数值以便下次调用
   return(rates_total);
  }

剩下的唯一需要做的就是编写逆初始化中删除图形资源的方法, 以便在删除指标时正确删除图形对象。

//+------------------------------------------------------------------+
//| 自定义指标的逆初始化函数                                              |
//+------------------------------------------------------------------+
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 |
//|                                   版权所有 2017, Alexander Fedosov |
//|                           https://www.mql5.com/zh/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("错误。未能创建指标。");
//---
   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();
  }
//+------------------------------------------------------------------+

检查并设置初始位置后, 第二条弧形边界的弧度需重新计算角度。此后, 所有指标元素都用新值重新绘制。作为应用示例, 我们开发了一种简单的点差指标, 当达到阈值时, 它会改变弧度指示的颜色。

//+------------------------------------------------------------------+
//|                                                                  |
//|                                  版权所有 2017, Alexander Fedosov |
//|                           https://www.mql5.com/zh/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "版权所有 2017, Alexander Fedosov"
#property link      "https://www.mql5.com/zh/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;
//+------------------------------------------------------------------+
//| 自定义指标初始化函数                                                 |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   arc.Orientation(HORIZONTAL);
   arc.LabelValue("Spread");
   arc.Create("spread_ind",100,100);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| 自定义指标迭代函数                                                  |
//+------------------------------------------------------------------+
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);
//--- 返回 prev_calculated 的数值以便下次调用
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| 自定义指标的逆初始化函数                                             |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   arc.Delete();
   ChartRedraw();
  }
//+------------------------------------------------------------------+

正如我们从列表中所见, 使用 CCircleArc 类来实现, 对于创建和配置是极其简单的。请记住, 只有在创建指标 (Create() 方法) 之前, 或使用 NewValue() 方法之前, 才应修改和配置属性。因为这些方法在指标元素重绘和改变属性时需要用到。点差指标示于图例. 4。

图例. 4. 简单的带有弧形指示的圆形指标


CCircleSection 指标类

不同于简单的弧形指示, 扇区图看起来好像具有间隔相等的标签。当创建这类指标的布局时, 我决定制作十个扇区并添加一个新的元素 – 内框。具有弧形扇区指示的基本结构如图例. 5 所示。


图例. 5. 带有弧形指示的圆形指标的基本结构

显示带有扇区的弧形指示与前一个类完全不同, 前者仅一次性使用 CCanvas 函数库的 Pie() 方法。在该方法中, 当数值变化时, 第二个椭圆扇区弧形的位置也会改变。在此, 该方法被使用了十次, 且弧形位置保持静止。简单地说, 指标具有 10 个填充的椭圆扇区, 在一个圆圈中彼此衔接。改变某些部分的颜色可作为视觉指示。

Indicators 文件夹里创建 CircleSection.mqh 文件, 并像以前所有的做法一样, 在 CustomUI.mqh 文件里包含它。现在, 它看起来如同以下:

//+------------------------------------------------------------------+
//|                                                    CustomGUI.mqh |
//|                                   版权所有 2017, Alexander Fedosov |
//|                           https://www.mql5.com/zh/users/alex2356 |
//+------------------------------------------------------------------+
#include "Indicators\CircleSimple.mqh"
#include "Indicators\CircleArc.mqh"
#include "Indicators\CircleSection.mqh"

由于指标类是按照构造和功能复杂度的顺序呈现, 所以我不会提供它们的共同属性和方法。代之, 我们将注意力集中在添加新功能, 以及不同的实现。所以, 在当前类中, 大多数方法与前一个相似, 以下是额外部分:

//+------------------------------------------------------------------+
//|                                                CircleRounded.mqh |
//|                                  版权所有 2017, Alexander Fedosov |
//|                           https://www.mql5.com/zh/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("错误。未能创建指标。");
//--- 指标扇区
   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 |
//|                                   版权所有 2017, Alexander Fedosov |
//|                           https://www.mql5.com/zh/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "版权所有 2017, Alexander Fedosov"
#property link      "https://www.mql5.com/zh/users/alex2356"
#property version   "1.00"
#property indicator_plots 0
#property indicator_chart_window

#include <CustomGUI\CustomGUI.mqh>
//---
CCircleSection ind;
//+------------------------------------------------------------------+
//| 自定义指标初始化函数                                                 |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ind.Radius(70);
   ind.LabelValue("回撤");
   ind.Create("回撤",150,150);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| 自定义指标迭代函数                                                   |
//+------------------------------------------------------------------+
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));
//--- 返回 prev_calculated 的数值以便下次调用
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| 自定义指标的逆初始化函数                                              |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ind.Delete();
   ChartRedraw();
  }
//+------------------------------------------------------------------+

不要忘记在 deinitialization 中使用 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 |
//|                                   版权所有 2017, Alexander Fedosov |
//|                           https://www.mql5.com/zh/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[];
   //--- Y 轴的最小和最大值
   double            m_y_min;
   double            m_y_max;
   //--- Y 轴刻度数量
   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;                             }
   //--- 设置和获取 Y 轴的最小和最大值
   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;                        }
   //--- 设置和获取 Y 轴刻度数量
   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("错误。未能创建指标。");
//--- 创建图形的框架和底图
   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() 图形显示方法时, 数据数组作为基础 (方法参数), 此处数组索引对应于 X 轴, 而其数值 — 对应于 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 |
//|                                  版权所有 2017, Alexander Fedosov |
//|                           https://www.mql5.com/zh/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "版权所有 2017, Alexander Fedosov"
#property link      "https://www.mql5.com/zh/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[];
//+------------------------------------------------------------------+
//| 自定义指标初始化函数                                                 |
//+------------------------------------------------------------------+
int OnInit()
  {
//---- 获取指标句柄
   InpInd_Handle=iRSI(Symbol(),PERIOD_CURRENT,10,PRICE_CLOSE);
   if(InpInd_Handle==INVALID_HANDLE)
     {
      Print("获取指标句柄失败");
      return(INIT_FAILED);
     }
//---
   ind.NumGrid(10);
   ind.YMax(100);
   ind.Create("rsi",350,250);

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| 自定义指标迭代函数                                                  |
//+------------------------------------------------------------------+
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);
//--- 返回 prev_calculated 的数值以便下次调用
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| 自定义指标的逆初始化函数                                             |
//+------------------------------------------------------------------+
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 类的实现