内容
- 概述
- CLineRounded 类
- CHexagon 类
- CPetal 类
- CHistogram 类
- CPyramid 类
- 结论
概述
在我们 之前的 文章中, 我们已研究了利用 CCanvas 类开发简单图元的方法来构建图形指标的原理。然而, 自定义图形库具有更广泛的功能, 因此我建议您查阅拥有更复杂结构实现的新型指标。此外, 我们将绘制伪 3D 指标类型和动态图。
CLineRounded 类
我不打算在本文中重新创建基类的一般结构来实现图形对象。代之, 我们将使用现成的在 之前文章 中描述过的 CustomGUI 函数库。特别地, CCanvasBase 类将会作为基类。新开发的类将在 CustomGUI.mqh 文件中列出。
为了开发一个简单的线性指标, 我们应定义其结构和基本元素。图例. 1 示意使用此类型指标时您可以管理的元素。请注意, 它们不是简单的元素。
图例. 1. 简单线性指标的基本结构
由 CCanvas 类方法实现的元素可认为是简单的 (基本的)。例如, 在线性指标中只有 Value 可认为是简单的, 因为它使用 CCanvas::TextOut() 方法。其余的三个元素与此类似, 主要由三个基本图形组成 — 两个实心圆 (CCanvas::FillCircle() 实现) 和一个矩形 (CCanvas::FillRectangle())。
在 CustomGUI/Indicators 文件夹里创建 LineRounded.mqh 文件, 在新生成的文件里创建 СLineRounded 类, 并为其指派以前创建的 CCanvasBase 类作为基类。
//+------------------------------------------------------------------+ //| LineRounded.mqh | //| 版权所有 2017, Alexander Fedosov | //| https://www.mql5.com/zh/users/alex2356 | //+------------------------------------------------------------------+ #include "..\CanvasBase.mqh" //+------------------------------------------------------------------+ //| 圆形线性指标 | //+------------------------------------------------------------------+ class CLineRounded : public CCanvasBase
还有, 将其 添加 到 CustomGUI.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" #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("错误。不能创建画布。"); //--- 边框 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 | //| 版权所有 2017, Alexander Fedosov | //| https://www.mql5.com/zh/users/alex2356 | //+------------------------------------------------------------------+ #property version "1.00" #property indicator_plots 0 #property indicator_chart_window #include <CustomGUI\CustomGUI.mqh> //--- CLineRounded ind1,ind2; //+------------------------------------------------------------------+ //| 自定义指标初始化函数 | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| 自定义指标迭代函数 | //+------------------------------------------------------------------+ 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); //--- 返回 prev_calculated 的数值以便下次调用 return(rates_total); } //+------------------------------------------------------------------+ //| 自定义指标逆初函数 | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ind1.Delete(); ind2.Delete(); } //+------------------------------------------------------------------+
图例. 2 清晰地表明, 在具有相似值和不同尺度的情况下, 指标刻度长度具有适当的值。选择数值 50 和最大值 (第一个为 100, 第二个为 200), 以便直观地评估显示结果。
图例. 2. 圆形线性指标示例
CHexagon 类
我们可以运用许多方法利用 CCanvas 类设置的基元来绘制正六边形指标。它们包括构筑多边形, 然后填充该区域, 并 "装配" 六个等边三角形, 有如切片的匹萨饼。然而, 我们的目标是最大简化。所以, 我们将使用三个基元构造一个正六边形 — 一个矩形和两个等腰三角形。
图例. 3. 正六边形结构
正六角形插入到方形画布中。记住图形的属性:
- 正六边形的一边等于围绕它的圆形半径 (在我们的例子中, 这是画布边长的一半)。绘制矩形时, 此功能是必需的。
- 六边形角度为 120 度, 因此等腰三角形的底角为 30 度。当利用 CCanvas::FillTriangle() 方法时, 此数据对于定义三角形高度和查找三角形基点的坐标是必需的。
指标本身的基本结构非常简单, 除了六边形外, 还包括两个文本对象 - 数值和描述 (图例. 4)。
图例. 4. 六角形指标的基本结构
在 CustomGUI/Indicators 文件夹里创建 Hexagon.mqh 文件, 在新生成的文件里创建 СHexagon 类, 并为其指派以前创建的 CCanvasBase 类作为基类。将其包含在清单里:
//+------------------------------------------------------------------+ //| CustomGUI.mqh | //| 版权所有 2017, Alexander Fedosov | //| https://www.mql5.com/zh/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("错误。不能创建画布。"); //--- 计算三角形高度 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 | //| 版权所有 2017, Alexander Fedosov | //| https://www.mql5.com/zh/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; //+------------------------------------------------------------------+ //| 自定义指标初始化函数 | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| 自定义指标逆初函数 | //+------------------------------------------------------------------+ 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 类
利用 CCanvas 类方法中的 2-3 个基元实现花瓣状指标。依据形状, 这些是填充圆 (FillCircle() 方法) 以及 1-2 个填充三角形 (FillTriangle())。元素的结构和集合如图例. 6 所示。
图例. 6. 花瓣状指标的基本结构
正如我们所见, 这里使用了两个三角形, 但我们将会用 "枚举" 把多个表单类型添加到类中。我们来更详细地研究这些类型:
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 | //| 版权所有 2017, Alexander Fedosov | //| https://www.mql5.com/zh/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() 方法当中, 在构造填充圆之后, 根据先前研究的 "form type" 属性, 参考 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("错误。不能创建画布。"); //--- 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 | //| 版权所有 2017, Alexander Fedosov | //| https://www.mql5.com/zh/users/alex2356 | //+------------------------------------------------------------------+ #property version "1.00" #property indicator_plots 0 #property indicator_chart_window #include <CustomGUI\CustomGUI.mqh> //--- CPetal ind1,ind2,ind3,ind4; //+------------------------------------------------------------------+ //| 自定义指标初始化函数 | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| 自定义指标迭代函数 | //+------------------------------------------------------------------+ 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[]) { //--- //--- 返回 prev_calculated 的数值以便下次调用 return(rates_total); } //+------------------------------------------------------------------+ //| 自定义指标逆初函数 | //+------------------------------------------------------------------+ 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. 简单类型的直方图
图例. 11. 三角类型直方图
图例. 12. 矩形类型直方图
在 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 | //| 版权所有 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 int RSIPeriod1=10; //指标1 的周期 input int RSIPeriod2=14; //指标2 的周期 input int RSIPeriod3=18; //指标3 的周期 input ENUM_TYPE_HISTOGRAM Type=RECTANGLE; //直方图类型 //--- CHistogram ind; int InpInd_Handle1,InpInd_Handle2,InpInd_Handle3; double rsi1[],rsi2[],rsi3[],ex[3]; string descr[3]; //+------------------------------------------------------------------+ //| 自定义指标初始化函数 | //+------------------------------------------------------------------+ 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("获取指标句柄失败"); 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); } //+------------------------------------------------------------------+ //| 自定义指标迭代函数 | //+------------------------------------------------------------------+ 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); //--- 返回 prev_calculated 的数值以便下次调用 return(rates_total); } //+------------------------------------------------------------------+ //| 自定义指标逆初函数 | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ind.Delete(); ChartRedraw(); } //+------------------------------------------------------------------+
图例. 13. 以三个周期的 RSI 直方图形式显示的指标结果
CPyramid 类
我们已研究了使用基元构造复杂形状的类和原理。在描述构造直方图类型指标的类时, 我曾提到通过颜色选择绘制伪 3D 对象 (图例. 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() 方法用于查找两点直线方程。特别地, 它可从常规的两点直线方程来定义通用方程式 y = kx + b 的 k 和 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("错误。不能创建画布。"); //--- 描述和数值 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); //--- 定义 AC 边线与平行于金字塔底边直线之间的交叉点的坐标 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); //--- 定义 AC 边线与平行于金字塔底边直线之间的交叉点的坐标 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 | //| 版权所有 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 int period=10; //ADX 周期 input double maxval=50; //最大值 //--- CPyramid ind1; CCircleSimple ind2; int InpInd_Handle; double adx[]; //+------------------------------------------------------------------+ //| 自定义指标初始化函数 | //+------------------------------------------------------------------+ int OnInit() { //---- 获取指标句柄 InpInd_Handle=iADX(Symbol(),PERIOD_CURRENT,period); if(InpInd_Handle==INVALID_HANDLE) { Print("获取指标句柄失败"); 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); } //+------------------------------------------------------------------+ //| 自定义指标迭代函数 | //+------------------------------------------------------------------+ 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]); //--- 返回 prev_calculated 的数值以便下次调用 return(rates_total); } //+------------------------------------------------------------------+ //| 自定义指标逆初函数 | //+------------------------------------------------------------------+ 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 类的实现示例 |