内容
- 概述
- 开发自定义图形基类
- CCircleSimple 指标类
- CCircleArc 指标类
- CCircleSection 指标类
- CLineGraph 线性图形类
概述
自定义指标是现代 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() 方法, 因为它们的实现未能提供。请记住, 创建的对象层叠在一起, 因此创建它们的顺序如下:
- 弧形指标背景。作为填充的圆形。
- 弧形指标。作为填充的椭圆扇区。
- 文本背景。作为填充的圆形。
- 指标数值及其描述。
按照下面显示的既定顺序实施:
//+------------------------------------------------------------------+ //| 创建指标 | //+------------------------------------------------------------------+ 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 指标类 |
6 | LineGraph.mqh | 函数库 | 包含 CLineGraph 线性图形类 |
7 | 1.mq5 | 指标 | 示例 CCirleSimple 类的实现 |
8 | 2.mq5 | 指标 | 示例 CCirleArc 类的实现 |
9 | 3.mq5 | 指标 | 示例 CCirleSection 类的实现 |
10 | 4.mq5 | 指标 | 示例 CLineGraph 类的实现 |