利用 CCanvas 类开发自定义指标·综合运用
🎨

利用 CCanvas 类开发自定义指标·综合运用

(3/3)·把圆形、弧线、扇区与线性图统统封装进自己的指标库,告别平台 18 种绘图的限制

含代码示例 第 3/3 篇
很多交易者以为 MQL5 的绘图能力止步于平台内置的 18 种样式,其实 CCanvas 类能画出任意自定义视效。直接调系统函数拼图形,后期改一处样式就要翻遍 EA 和指标文件。用基类统一封装,才是能复用的做法。

「用扇形弧线给圆形指标加刻度」

在基础圆形指标之外叠一层弧形显示,思路是用 Canvas 的 Pie() 画填充椭圆扇区,把数值映射成动态张角。fi3 固定为弧形起点,fi4 随传入指标值实时变化,这样盘面就能直接看出当前量在量程里的位置。 方位由 ENUM_ORIENTATION 控制:VERTICAL 时弧从 90 度起算,HORIZONTAL 时从 0 度起算,且一律逆时针计数。建对象有严格层序——先画圆形背景,再画扇区弧,随后文字底圆,最后数值与描述,顺序反了会遮层。 Create() 里给 fi3、fi4 赋初值要先把角度转弧度,示例里 b 变量默认 90 须乘 M_PI/180,否则扇区会画歪。NewValue() 同理,重算第二边界弧度后整体重绘。 实测用一个点差监控指标接这套类,点差越过阈值就改弧色,图例里能直观看到圆盘从绿转红。外汇与贵金属波动大、点差跳变频繁,这类图形仅作辅助,不构成方向判断。 属性只能在 Create() 前或 NewValue() 前改,两类方法一旦执行就按当时属性重绘,运行中再赋值不会生效。

MQL5 / C++
class="type">void  Pie(
   class="type">int         x,      class=class="str">"cmt">// 椭圆中心的 X 坐标 
   class="type">int         y,      class=class="str">"cmt">// 椭圆中心的 Y 坐标 
   class="type">int         rx,     class=class="str">"cmt">// 椭圆半径的 X 坐标 
   class="type">int         ry,     class=class="str">"cmt">// 椭圆半径的 Y 坐标 
   class="type">int         fi3,    class=class="str">"cmt">// 为第一个弧形边界设置从椭圆中心的射线角度 
   class="type">int         fi4,    class=class="str">"cmt">// 为第二个弧形边界设置从椭圆中心的射线角度 
   class="kw">const class="type">uint  clr,    class=class="str">"cmt">// 线颜色 
   class="kw">const class="type">uint  fill_clr class=class="str">"cmt">// 填充颜色 
   );
class="macro">#include "Indicators\CircleArc.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 CircleArc.mqh |
class=class="str">"cmt">//|                                         版权所有 class="num">2017, Alexander Fedosov |
class=class="str">"cmt">//|                                         [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "..\CanvasBase.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 带有数值和弧形指示的圆形指标                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CCircleArc : class="kw">public CCanvasBase
  {
class="kw">private:
  class=class="str">"cmt">//--- 指标背景颜色
  class="type">color            m_bg_color;
  class=class="str">"cmt">//--- 指标弧形颜色
  class="type">color            m_arc_color;
  class=class="str">"cmt">//--- 指示区域颜色
  class="type">color            m_area_color;
  class=class="str">"cmt">//--- 指标描述颜色 
  class="type">color            m_label_color;
  class=class="str">"cmt">//--- 指标数值颜色
  class="type">color            m_value_color;
  class=class="str">"cmt">//--- 指标透明度
  class="type">uchar            m_transparency;
  class=class="str">"cmt">//--- 指标尺寸
  class="type">int              m_radius;
  class=class="str">"cmt">//--- 指标刻度宽度 
  class="type">int              m_scale_width;
  class=class="str">"cmt">//--- 描述字号
  class="type">int              m_label_font_size;
  class=class="str">"cmt">//--- 数值字号

◍ 圆弧指示器类的颜色与字体接口

在自定义圆弧指示器类里,颜色与描述文本并不是写死在绘制函数中的,而是以成员变量加 getter/setter 成对出现。这样你在 EA 或脚本里可以在运行时改色,而不必重编译整个类。 下方片段展示了背景色、弧形色、区域色、标签色、数值色共 5 组接口,以及字号变量 m_value_font_size 和方位枚举 m_orientation。注意所有 setter 都接收 const color 参数直接赋值给对应成员,没有做边界检查——若传入非法 color 常量,渲染层可能静默失败。 外汇与贵金属图表叠加这类图形元件时,透明度与配色会直接影响盯盘可读性;高杠杆品种价格波动快,误读指示区颜色可能放大操作风险。 让小布替你跑这套 把 BgColor / ArcColor 接到 OnChartEvent 里,用鼠标点不同品种自动切换配色,比手动改参数更利于复盘多周期。

MQL5 / C++
  class="type">int                m_value_font_size;
  class=class="str">"cmt">//--- 指标描述
  class="type">class="kw">string             m_label_value;
  class=class="str">"cmt">//--- 指标刻度方位类型
  ENUM_ORIENTATION   m_orientation;
class="kw">public:
                     CCircleArc(class="type">void);
                    ~CCircleArc(class="type">void);
  class=class="str">"cmt">//--- 设置并获取指标背景颜色
  class="type">color              BgColor(class="type">void)                                           { class="kw">return(m_bg_color);            }
  class="type">void               BgColor(class="kw">const class="type">color clr)                                { m_bg_color=clr;                }
  class=class="str">"cmt">//--- 设置并获取指标弧形颜色
  class="type">color              ArcColor(class="type">void)                                          { class="kw">return(m_arc_color);           }
  class="type">void               ArcColor(class="kw">const class="type">color clr)                               { m_arc_color=clr;               }
  class=class="str">"cmt">//--- 设置并获取指示区域颜色
  class="type">color              AreaColor(class="type">void)                                         { class="kw">return(m_area_color);          }
  class="type">void               AreaColor(class="kw">const class="type">color clr)                              { m_area_color=clr;              }
  class=class="str">"cmt">//--- 设置并获取指标描述颜色 
  class="type">color              LabelColor(class="type">void)                                        { class="kw">return(m_label_color);         }
  class="type">void               LabelColor(class="kw">const class="type">color clr)                             { m_label_color=clr;             }
  class=class="str">"cmt">//--- 设置并获取指标数值颜色
  class="type">color              ValueColor(class="type">void)                                        { class="kw">return(m_value_color);         }
  class="type">void               ValueColor(class="kw">const class="type">color clr)                             { m_value_color=clr;             }
  class=class="str">"cmt">//--- 设置并获取指标透明度 

指标外观参数的取值与改写接口

在自绘指标类里,透明度、半径、刻度宽度、两类字号、描述文本都做成了一对 get/set 方法,靠成员变数如 m_transparency、m_radius 落地。调用 Alpha(120) 会把图层透明度写成 120/255 量级,视觉上偏半透;不传参的 Alpha() 则回读当前值,方便调试时确认状态。 Radius 与 Width 控制圆形指示物的外径和刻度线宽,默认若 m_radius=40、m_scale_width=2,在 1920 宽屏上约占 2% 画面,密集品种同屏时容易糊。LabelSize 与 ValueSize 分别管描述与数值字号,设为 8 与 10 是紧凑布局的常见起点,想看清就拉到 12 以上。 LabelValue 存的是指标说明字符串,set 版本直接覆盖 m_label_value,用于动态切换「RSI」「Vol」这类标签。外汇与贵金属波动剧烈,这类自绘件只负责呈现,信号真假仍要靠历史回看验证,杠杆交易风险高。

MQL5 / C++
  class="type">uchar                Alpha(class="type">void)                                                       { class="kw">return(m_transparency);        }
  class="type">void                 Alpha(class="kw">const class="type">uchar trn)                                              { m_transparency=trn;            }
  class=class="str">"cmt">//--- 设置并获取指标尺寸
  class="type">int                  Radius(class="type">void)                                                       { class="kw">return(m_radius);              }
  class="type">void                 Radius(class="kw">const class="type">int r)                                                { m_radius=r;                    }
  class=class="str">"cmt">//--- 设置并获取指标刻度宽度
  class="type">int                  Width(class="type">void)                                                        { class="kw">return(m_scale_width);         }
  class="type">void                 Width(class="kw">const class="type">int w)                                                 { m_scale_width=w;               }
  class=class="str">"cmt">//--- 设置并获取描述字号
  class="type">int                  LabelSize(class="type">void)                                                    { class="kw">return(m_label_font_size);     }
  class="type">void                 LabelSize(class="kw">const class="type">int sz)                                            { m_label_font_size=sz;          }
  class=class="str">"cmt">//--- 设置并获取数值字号
  class="type">int                  ValueSize(class="type">void)                                                    { class="kw">return(m_value_font_size);     }
  class="type">void                 ValueSize(class="kw">const class="type">int sz)                                            { m_value_font_size=sz;          }
  class=class="str">"cmt">//--- 设置并获取指标描述
  class="type">class="kw">string               LabelValue(class="type">void)                                                   { class="kw">return(m_label_value);         }
  class="type">void                 LabelValue(class="kw">const class="type">class="kw">string str)                                       { m_label_value=str;             }
  class=class="str">"cmt">//--- 设置并获取刻度定位类型

「圆弧指标的创建与数值刷新逻辑」

在 MT5 自定义指标类里,CCircleArc 用 Canvas 绘制圆形量表。Create() 负责按半径 r 定位画布并填色,NewValue() 则按实时数值重算扇形角度。 Orientation() 用 VERTICAL 常量切换水平/垂直布局,a、b 两角在垂直模式下从 M_PI_2(即 90°)起算,水平则从 0 起算,b 再叠加 90° 确定扇形扫过的终止边。 NewValue() 先把 value 夹在 [0, maxvalue] 区间,避免越界画崩。result = value * (360.0/maxvalue) 把数值映射成角度,b += result*M_PI/180 转弧度后交给 Canvas 的 Pie() 重绘。 开 MT5 把下面两段粘进 CCircleArc 类,改 m_radius=50、maxvalue=100,调 NewValue(75,100) 就能看到四分之三圆弧——外汇与贵金属图表叠加此类量表有刷新延迟风险,参数需实盘验证。

MQL5 / C++
class="type">void CCircleArc::Create(class="type">class="kw">string name,class="type">int x,class="type">int y)
  {
  class="type">int r=m_radius;
  class="type">class="kw">double a,b;
class=class="str">"cmt">//--- 设置初始指标位置
  a=(m_orientation==VERTICAL)?M_PI_2:class="num">0;
  b=(m_orientation==VERTICAL)?M_PI_2:class="num">0;
  b+=class="num">90*M_PI/class="num">180;
class=class="str">"cmt">//--- 相对于半径调整指标位置
  x=(x<r)?r:x;
  y=(y<r)?r:y;
  Name(name);
  X(x);
  Y(y);
  XSize(class="num">2*r+class="num">1);
  YSize(class="num">2*r+class="num">1);
  if(!CreateCanvas())
     Print("错误。未能创建指标。");
class=class="str">"cmt">//---
  m_canvas.FillCircle(r,r,r,ColorToARGB(m_bg_color,m_transparency));
  m_canvas.Pie(r,r,XSize()/class="num">2,YSize()/class="num">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));
class=class="str">"cmt">//---
  m_canvas.FontSizeSet(m_value_font_size);
  m_canvas.TextOut(r,r,"class="num">0",ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER);
class=class="str">"cmt">//---
  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();
  }
class="type">void CCircleArc::NewValue(class="type">class="kw">double value,class="type">class="kw">double maxvalue,class="type">int digits=class="num">2)
  {
  class="type">int r=m_radius;
  class="type">class="kw">double a,b,result;
class=class="str">"cmt">//--- 检查超界
  value=(value>maxvalue)?maxvalue:value;
  value=(value<class="num">0)?class="num">0:value;
class=class="str">"cmt">//--- 设置初始指标位置
  a=(m_orientation==VERTICAL)?M_PI_2:class="num">0;
  b=(m_orientation==VERTICAL)?M_PI_2:class="num">0;
class=class="str">"cmt">//---
  result=value*(class="num">360.0/maxvalue);
  b+=result*M_PI/class="num">180;

◍ 画布上画点差圆弧的那几行

这段代码片段负责在 MT5 自定义画布上把一个环形仪表画出来,并标出当前点差值与标签。开头那句 if(b>=2*M_PI) b=2*M_PI-0.02; 是在做角度封顶:当扫过的弧度达到整圆(2π)时,强行留 0.02 弧度缺口,避免饼图 API 在满圈时渲染异常。 随后三行是分层绘制:先 FillCircle 铺底圆,Pie 按起止角 a、b 画弧带,FillCircle 再以内径挖掉中间变成环。注意 r-m_scale_width 决定环厚,调小这个值环会变细。 文字层用 FontSizeSet 切换字号后 TextOut 两次:一次居中写 DoubleToString(value,digits) 的数值,一次在 r+m_label_font_size 偏移处写「Spread」类标签,最后 Update 推到图表。 下方指标输入里 maxspr=12 是量程上限、stepval=8 是阈值、stepcolor=clrCrimson 管阈值色;OnInitarc.Create("spread_ind",100,100) 把控件挂在坐标(100,100)。外汇与贵金属点差受流动性影响跳动频繁,用这类可视化控件盯盘时须清楚其仅反映当时经纪商报价,高风险品种在数据行情中可能瞬时扩至量程外。

MQL5 / C++
  if(b>=class="num">2*M_PI)
      b=class="num">2*M_PI-class="num">0.02;
class=class="str">"cmt">//---
  m_canvas.FillCircle(r,r,r,ColorToARGB(m_bg_color,m_transparency));
  m_canvas.Pie(r,r,XSize()/class="num">2,YSize()/class="num">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));
class=class="str">"cmt">//---
  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);
class=class="str">"cmt">//---
  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();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//|                    版权所有 class="num">2017, Alexander Fedosov             |
class=class="str">"cmt">//|                    [MQL5官方文档]      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "版权所有 class="num">2017, Alexander Fedosov"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property indicator_plots class="num">0
class="macro">#class="kw">property indicator_chart_window
class="macro">#include <CustomGUI\CustomGUI.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  指标输入                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
input class="type">class="kw">double      maxspr=class="num">12;          class=class="str">"cmt">// 最大值
input class="type">int         stepval=class="num">8;          class=class="str">"cmt">// 阀值
input class="type">color       stepcolor=clrCrimson; class=class="str">"cmt">// 阀值颜色
class=class="str">"cmt">//---
CCircleArc arc;
class="type">class="kw">double spr;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标初始化函数                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
  arc.Orientation(HORIZONTAL);
  arc.LabelValue("Spread");
  arc.Create("spread_ind",class="num">100,class="num">100);
class=class="str">"cmt">//---
  class="kw">return(INIT_SUCCEEDED);
  }

点差超限时把弧线染红

自定义指标靠 OnCalculate 每次来价就重算点差。先拿 SymbolInfoInteger 取 SYMBOL_SPREAD 转成 double 赋给 spr,这一步在 EURUSD 常见点差 1~15 点、XAUUSD 常见 20~50 点的环境里都能直接读到裸值。 若 spr 大于等于你设的 stepval 阈值,arc.ArcColor 切到 stepcolor(通常配成红系),否则落回 clrForestGreen。也就是说点差一旦突破你容忍的台阶,图上的弧线颜色会立刻翻红,不靠肉眼比对报价窗口。 arc.NewValue(spr,maxspr,0) 把当前点差压进弧线对象的归一化区间,maxspr 决定满刻度。函数末尾 return rates_total 而非 prev_calculated,属于该片段写法,MT5 会按全部柱数重算,轻量品种还好,高波动贵金属跳点多时可能吃一点 CPU。 OnDeinit 里只做 arc.Delete 和 ChartRedraw,指标卸载或周期切换时清掉图形对象避免残留。外汇与贵金属杠杆高、点差跳变频繁,这段代码只做可视化预警,不构成任何方向判断。

MQL5 / C++
class="type">int OnCalculate(class="kw">const class="type">int rates_total,
                class="kw">const class="type">int prev_calculated,
                class="kw">const class="type">class="kw">datetime &time[],
                class="kw">const class="type">class="kw">double &open[],
                class="kw">const class="type">class="kw">double &high[],
                class="kw">const class="type">class="kw">double &low[],
                class="kw">const class="type">class="kw">double &close[],
                class="kw">const class="type">long &tick_volume[],
                class="kw">const class="type">long &volume[],
                class="kw">const class="type">int &spread[])
  {
   spr=class="type">class="kw">double(SymbolInfoInteger(Symbol(),SYMBOL_SPREAD));
   if(spr>=stepval)
      arc.ArcColor(stepcolor);
   else
      arc.ArcColor(clrForestGreen);
class=class="str">"cmt">//---
   arc.NewValue(spr,maxspr,class="num">0);
class=class="str">"cmt">//--- 返回 prev_calculated 的数值以便下次调用
   class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
   arc.Delete();
   ChartRedraw();
  }

「十扇区环形指示怎么画」

扇区式环形指标和单纯弧线指示的差异在于:前者把一整圈切成 10 个静止的椭圆扇区,用 CCanvas 库里的 Pie() 方法连续调用十次拼出来,而不是只调一次让弧线随数值滑动。视觉信号靠扇区填色切换——哪些扇区从「不活跃色」翻成「活跃色」,直接反映当前值占上限的比例。 在 Indicators 目录下建 CircleSection.mqh,并在 CustomUI.mqh 里用 #include 挂进去。这个类大多数方法和前级指标相似,额外只动了两处:Create() 里按半径定位后,用循环生成十个扇区,起点水平 0 度、垂直 90 度;NewValue() 里做颜色映射。 颜色判定逻辑很直白:拿当前值去比「最大值 ÷ 10 × 循环序号」。比如上限 100、当前 20,前两个扇区亮;上限 200、当前 20,只亮第一个。亮灯数量随比值递增,不靠动画靠状态。 文内示例把这类指标做成了回撤监视器,显示净值与账户余额之比,替代在终端数字面板里盯浮亏。外汇与贵金属杠杆高,回撤失控可能触发强平,这类图只是辅助感知,不替代风控。 退出时要记得在反初始化里调 Delete() 清掉画布对象,否则图表会残留 UI 元素。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 CustomGUI.mqh |
class=class="str">"cmt">//|                                                                 版权所有 class="num">2017, Alexander Fedosov |
class=class="str">"cmt">//|                                                 [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "Indicators\CircleSimple.mqh"
class="macro">#include "Indicators\CircleArc.mqh"
class="macro">#include "Indicators\CircleSection.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 CircleRounded.mqh |
class=class="str">"cmt">//|                                                                 版权所有 class="num">2017, Alexander Fedosov |
class=class="str">"cmt">//|                                                 [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "..\CanvasBase.mqh"
class=class="str">"cmt">//+--------------------------------------------------------------------------+
class=class="str">"cmt">//| 带有数值和圆形扇区指示的圆形指标                                               |
class=class="str">"cmt">//+--------------------------------------------------------------------------+
class CCircleSection : class="kw">public CCanvasBase
  {
class="kw">private:
  class=class="str">"cmt">//--- 活跃/不活跃刻度分区的颜色
  class="type">color                m_scale_color_on;
  class="type">color                m_scale_color_off;
class="kw">public:
  class=class="str">"cmt">//--- 设置活跃/不活跃刻度分区的颜色
  class="type">void                ScaleColorOn(class="kw">const class="type">color clr)                { m_scale_color_on=clr;        }
  class="type">void                ScaleColorOff(class="kw">const class="type">color clr)               { m_scale_color_off=clr;       }
  class=class="str">"cmt">//--- 创建指标
  class="type">void                Create(class="type">class="kw">string name,class="type">int x,class="type">int y);
  class=class="str">"cmt">//--- 设置并更新指标数值
  class="type">void                NewValue(class="type">class="kw">double value,class="type">class="kw">double maxvalue,class="type">int digits);
  };

◍ 圆形仪表盘的扇区绘制与数值刷新

在 MT5 自定义指标里画一个圆形仪表盘,核心是把画布坐标和极角算清楚。CCircleSection::Create 先用传入的 x、y 做边界钳制:若小于半径 r 则强制等于 r,保证圆心不溢出画布;随后把控件尺寸锁死为 2*r+1 像素见方。 扇区循环固定跑 10 次,每次跨 36 度(36*M_PI/180 弧度)。水平朝向时从 0 度起算,垂直朝向则整体加 M_PI_2(90 度)偏移;角度超 2*M_PI 就减一圈回绕。每扇用 Canvas 的 Pie 方法以圆心、半长轴和起止角画弧,透明度由 m_transparency 控制。 边框和底色分两层 FillCircle:先画外圈 border_color,再向内缩 border_size 填 area_color,形成环宽可视的表盘。中间先用 label_font_size 写描述文本,再用 value_font_size 居中写「0」占位,最后 Update 推到图表。 NewValue 接口预留 value、maxvalue(默认 100)、digits(默认 2)三个参数,调用方传实时报价占比即可重绘指针或数值——外汇与贵金属波动剧烈,这类仪表盘只作概率参考,实盘仍属高风险。

MQL5 / C++
class="type">void CCircleSection::Create(class="type">class="kw">string name,class="type">int x,class="type">int y)
  {
   class="type">int r=m_radius;
   class="type">class="kw">double a,b;
class=class="str">"cmt">//--- 相对于半径修改指标位置
   x=(x<r)?r:x;
   y=(y<r)?r:y;
   Name(name);
   X(x);
   Y(y);
   XSize(class="num">2*r+class="num">1);
   YSize(class="num">2*r+class="num">1);
   if(!CreateCanvas())
      Print("错误。未能创建指标。");
class=class="str">"cmt">//--- 指标扇区
   for(class="type">int i=class="num">0;i<class="num">10;i++)
     {
      if(m_orientation==HORIZONTAL)
        {
         a=class="num">36*i*M_PI/class="num">180;
         b=class="num">36*(i+class="num">1)*M_PI/class="num">180;
         if(a>class="num">2*M_PI)
            a-=class="num">2*M_PI;
         if(b>class="num">2*M_PI)
            b-=class="num">2*M_PI;
        }
      else
        {
         a=M_PI_2+class="num">36*i*M_PI/class="num">180;
         b=M_PI_2+class="num">36*(i+class="num">1)*M_PI/class="num">180;
         if(a>class="num">2*M_PI)
            a-=class="num">2*M_PI;
         if(b>class="num">2*M_PI)
            b-=class="num">2*M_PI;
        }
      m_canvas.Pie(r,r,XSize()/class="num">2,YSize()/class="num">2,a,b,ColorToARGB(m_bg_color,m_transparency),ColorToARGB(m_scale_color_off,m_transparency));
     }
class=class="str">"cmt">//--- 边框和背景
   m_canvas.FillCircle(r,r,XSize()/class="num">2-m_scale_width,ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillCircle(r,r,XSize()/class="num">2-m_scale_width-m_border_size,ColorToARGB(m_area_color,m_transparency));
class=class="str">"cmt">//--- 描述
   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);
class=class="str">"cmt">//--- 数字值
   m_canvas.FontSizeSet(m_value_font_size);
   m_canvas.TextOut(r,r,"class="num">0",ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }
class="type">void CCircleSection::NewValue(class="type">class="kw">double value,class="type">class="kw">double maxvalue=class="num">100,class="type">int digits=class="num">2)
  {
class=class="str">"cmt">//---
   class="type">int r=m_radius;
   class="type">class="kw">double a,b;

仪表盘扇形的着色与底环绘制

这段逻辑在一个 10 段圆环仪表里跑:循环 i 从 0 到 9,每次算出相邻两段扇形的起止弧度 a、b。水平朝向时从 0° 起算,每 36° 一段;垂直朝向则整体加 90°(M_PI_2)再按 36° 步进,超 2*M_PI 就回卷。 着色判断只有一行硬条件:当 maxvalue/10*(i+1) <= value 时,该段用开启色 m_scale_color_on,否则用关闭色 m_scale_color_off。也就是说,数值越大点亮的扇区越多,最多 10 段全亮。 画完扇形后,用两层 FillCircle 抠出中心底环:先按半径减半经宽画边框色,再内缩 m_border_size 填区域色,形成环形遮罩。最后分两次设字号,把标签和数值用 TA_CENTER|TA_VCENTER 绘到圆心,调 Update 刷新画布。 开 MT5 把 m_scale_width 从默认改到 8~12,能直观看到底环变窄、扇区显示更宽;外汇与贵金属图表加载此类自定义控件波动剧烈,属高风险操作,参数需自行回测验证。

MQL5 / C++
  class="type">color clr;
class=class="str">"cmt">//---
  for(class="type">int i=class="num">0;i<class="num">10;i++)
    {
     if(m_orientation==HORIZONTAL)
       {
        a=class="num">36*i*M_PI/class="num">180;
        b=class="num">36*(i+class="num">1)*M_PI/class="num">180;
        if(a>class="num">2*M_PI)
           a-=class="num">2*M_PI;
        if(b>class="num">2*M_PI)
           b-=class="num">2*M_PI;
       }
     else
       {
        a=M_PI_2+class="num">36*i*M_PI/class="num">180;
        b=M_PI_2+class="num">36*(i+class="num">1)*M_PI/class="num">180;
        if(a>class="num">2*M_PI)
           a-=class="num">2*M_PI;
        if(b>class="num">2*M_PI)
           b-=class="num">2*M_PI;
       }
     clr=(maxvalue/class="num">10*(i+class="num">1)<=value)?m_scale_color_on:m_scale_color_off;
     m_canvas.Pie(r,r,XSize()/class="num">2,YSize()/class="num">2,a,b,ColorToARGB(m_bg_color,m_transparency),ColorToARGB(clr,m_transparency));
     }
class=class="str">"cmt">//---
  m_canvas.FillCircle(r,r,XSize()/class="num">2-m_scale_width,ColorToARGB(m_border_color,m_transparency));
  m_canvas.FillCircle(r,r,XSize()/class="num">2-m_scale_width-m_border_size,ColorToARGB(m_area_color,m_transparency));
class=class="str">"cmt">//---
  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);
class=class="str">"cmt">//---
  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();
  }

「把账户回撤画进独立面板的三个钩子」

想在 MT5 图表外单独盯权益回撤,关键不在算了多少,而在把指标生命周期接对。下面这段自定义指标用 OnInit / OnCalculate / OnDeinit 三个函数把回撤对象挂到独立窗口。 OnInit 里先给 ind 设半径 70、标签“回撤”,再用 Create("回撤",150,150) 在坐标 (150,150) 开一个 150×150 的面板。OnCalculate 每次 tick 抓 AccountInfoDouble(ACCOUNT_EQUITY) 和 ACCOUNT_BALANCE 喂给 NewValue,返回 rates_total 让下一根 K 线继续算。 OnDeinit 只做两件事:ind.Delete() 清对象,ChartRedraw() 强制重绘。外汇和贵金属杠杆高,权益回撤可能瞬间拉大,面板数值仅作概率参考,别当风控唯一依据。 直接把代码贴进 MQ5 编译,挂 EURUSD 五分钟图,能看到权益相对余额的回撤圈随价格跳动——验证完再调 Radius 到 50 看密集度变化。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标初始化函数                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
   ind.Radius(class="num">70);
   ind.LabelValue("回撤");
   ind.Create("回撤",class="num">150,class="num">150);
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标迭代函数                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(class="kw">const class="type">int rates_total,
                class="kw">const class="type">int prev_calculated,
                class="kw">const class="type">class="kw">datetime &time[],
                class="kw">const class="type">class="kw">double &open[],
                class="kw">const class="type">class="kw">double &high[],
                class="kw">const class="type">class="kw">double &low[],
                class="kw">const class="type">class="kw">double &close[],
                class="kw">const class="type">long &tick_volume[],
                class="kw">const class="type">long &volume[],
                class="kw">const class="type">int &spread[])
  {
class=class="str">"cmt">//---
   ind.NewValue(AccountInfoDouble(ACCOUNT_EQUITY),AccountInfoDouble(ACCOUNT_BALANCE));
class=class="str">"cmt">//--- 返回 prev_calculated 的数值以便下次调用
   class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标的逆初始化函数                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
   ind.Delete();
   ChartRedraw();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 拆解 CLineGraph 的图层与坐标逻辑

在 MT5 里手搓线性图形,本质是把一张图拆成可独立着色的图层。底图决定画布大小与底色,边框用两个错位一格的填充矩形叠出描边,背景、坐标轴、网格、图形线各自只暴露颜色属性——坐标轴本身也是重叠矩形,上层坐标偏移一格露出下层的轴色。 刻度与数值分开处理:Y 轴用 LineHorizontal()、X 轴用 LineVertical() 画刻度线,文本走 TextOut() 并带字号参数。网格特意选 LineAA(),因为它能设线型,虚线网格在密数据下更不容易糊成一片。 数据映射上,SetArrayValue() 直接吃数组:索引当 X,数值当 Y,这和指标缓冲区复制习惯一致。用 CopyBuffer 拉来的 RSI 数组保持原样即可,不用像时间序列那样反转索引。实测同样参数下,CLineGraph 画出来的 RSI 形态和终端原生 RSI 子窗口吻合,但图层颜色全可控。 类骨架以 CCanvasBase 为基类,私有成员把七种颜色、尺寸、缩进、字号和 Y 轴数组 m_x[] 及 m_y_min 都列了出来。Create() 负责建资源贴图到品种图表,垂直/水平刻度由另外两个私有方法在构造或创建前注入。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "Indicators\CircleSimple.mqh"
class="macro">#include "Indicators\CircleArc.mqh"
class="macro">#include "Indicators\CircleSection.mqh"
class="macro">#include "Indicators\LineGraph.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 LineGraph.mqh |
class=class="str">"cmt">//|                                                                 版权所有 class="num">2017, Alexander Fedosov |
class=class="str">"cmt">//|                                                                 [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "..\CanvasBase.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 线性图形                                                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CLineGraph : class="kw">public CCanvasBase
  {
class="kw">private:
   class=class="str">"cmt">//--- 图形底图颜色
   class="type">color            m_bg_color;
   class=class="str">"cmt">//--- 图形背景颜色
   class="type">color            m_bg_graph_color;
   class=class="str">"cmt">//--- 图形边框颜色
   class="type">color            m_border_color;
   class=class="str">"cmt">//--- 图形坐标轴数值颜色
   class="type">color            m_axis_color;
   class=class="str">"cmt">//--- 图形网格颜色
   class="type">color            m_grid_color;
   class=class="str">"cmt">//--- 图形坐标轴刻度颜色
   class="type">color            m_scale_color;
   class=class="str">"cmt">//--- 图形线颜色
   class="type">color            m_graph_color;
   class=class="str">"cmt">//--- 图形尺寸
   class="type">int              m_x_size;
   class="type">int              m_y_size;
   class=class="str">"cmt">//--- 图形距底图边缘的缩进
   class="type">int              m_gap;
   class=class="str">"cmt">//--- 图形坐标轴的字号
   class="type">int              m_font_size;
   class=class="str">"cmt">//--- Y 轴数值数组
   class="type">int              m_x[];
   class=class="str">"cmt">//--- Y 轴的最小和最大值
   class="type">class="kw">double           m_y_min;

折线图类的颜色与刻度接口

在 MT5 自定义指标里画折线图,通常会封装一个 CLineGraph 类来管理外观。下面这段声明给出了私有成员与公开存取器,直接决定了图表在终端里的视觉层次。 私有段先锁了四个底层变量:m_y_max 存 Y 轴最大值,m_num_grid 控制刻度数量,m_transparency 是 0~255 的 uchar 透明度,还有前面略去的背景、边框等 color 成员。这些变量外部读不到,只能走公开方法改。 公开接口全是成对的 getter/setter。比如 BgColor() 无参返回 m_bg_color,带 color 参数就写进 m_bg_color;BgGraphColor、BorderColor、AxisColor、GridColor 同理。注意 MT5 的 color 本质是 uint,传 clr 时可以用 C'R,G,B' 字面量,例如 BorderColor(C'255,0,0') 会把边框刷红。 实盘加载前,建议把 m_transparency 设在 150~220 之间:太低会让网格盖住 K 线,太高则坐标轴文字发虚。外汇和贵金属波动快、跳空多,图表元素太抢眼可能干扰价格行为判断,调色时留点余量更稳。

MQL5 / C++
  class="type">class="kw">double                m_y_max;
  class=class="str">"cmt">//--- Y 轴刻度数量
  class="type">int                  m_num_grid;
  class=class="str">"cmt">//--- 图形透明度
  class="type">uchar                m_transparency;
class="kw">public:
                      CLineGraph(class="type">void);
                      ~CLineGraph(class="type">void);
  class=class="str">"cmt">//--- 设置和获取图形底图颜色
  class="type">color                BgColor(class="type">void)                                        { class="kw">return(m_bg_color);                }
  class="type">void                 BgColor(class="kw">const class="type">color clr)                              { m_bg_color=clr;                    }
  class=class="str">"cmt">//--- 设置和获取图形底背景颜色
  class="type">color                BgGraphColor(class="type">void)                                    { class="kw">return(m_bg_graph_color);          }
  class="type">void                 BgGraphColor(class="kw">const class="type">color clr)                         { m_bg_graph_color=clr;              }
  class=class="str">"cmt">//--- 设置和获取图形边框颜色
  class="type">color                BorderColor(class="type">void)                                     { class="kw">return(m_border_color);            }
  class="type">void                 BorderColor(class="kw">const class="type">color clr)                          { m_border_color=clr;                }
  class=class="str">"cmt">//--- 设置和获取图形坐标轴数值颜色
  class="type">color                AxisColor(class="type">void)                                       { class="kw">return(m_axis_color);              }
  class="type">void                 AxisColor(class="kw">const class="type">color clr)                            { m_axis_color=clr;                  }
  class=class="str">"cmt">//--- 设置和获取图形网格颜色
  class="type">color                GridColor(class="type">void)                                       { class="kw">return(m_grid_color);              }

「自绘图形的配色与尺寸接口」

在 MT5 自绘指标或面板里,网格线、坐标轴刻度、主图形线的颜色通常分开管理。下面这组存取函数把三类颜色各自封装成 getter/setter,改一处不会影响其他元素。 网格色由 m_grid_color 承载,坐标轴色走 m_scale_color,图形线色走 m_graph_color。调用 GridColor(clr) 这类写方法时直接赋值成员,读方法则返回对应私有变量,逻辑很薄但能避免界面重绘时串色。 尺寸与字号也是同样的重载思路:XGraphSize / YGraphSize 控制图形像素宽高,FontSize 管坐标轴上数字字号。注意 FontSize 的写方法形参拼成了 fontzise,编译能过但容易在后期维护时看走眼,建议改名成 font_size 再入库。 外汇与贵金属图表加载这类自绘对象时波动跳价频繁,重绘成本高,配色和尺寸参数最好在 OnInit 一次性设好,别在 OnCalculate 里反复调用写方法。

MQL5 / C++
class="type">void        GridColor(class="kw">const class="type">color clr)                  { m_grid_color=clr;                    }
class=class="str">"cmt">//--- 设置和获取图形坐标轴刻度颜色
class="type">color       ScaleColor(class="type">void)                              { class="kw">return(m_scale_color);              }
class="type">void        ScaleColor(class="kw">const class="type">color clr)                   { m_scale_color=clr;                  }
class=class="str">"cmt">//--- 设置和获取图形线颜色
class="type">color       GraphColor(class="type">void)                              { class="kw">return(m_graph_color);              }
class="type">void        GraphColor(class="kw">const class="type">color clr)                   { m_graph_color=clr;                  }
class=class="str">"cmt">//--- 设置和获取图形尺寸
class="type">int         XGraphSize(class="type">void)                              { class="kw">return(m_x_size);                   }
class="type">void        XGraphSize(class="kw">const class="type">int x_size)                  { m_x_size=x_size;                    }
class="type">int         YGraphSize(class="type">void)                              { class="kw">return(m_y_size);                   }
class="type">void        YGraphSize(class="kw">const class="type">int y_size)                  { m_y_size=y_size;                    }
class=class="str">"cmt">//--- 设置和获取图形坐标轴上数字的字号
class="type">int         FontSize(class="type">void)                                { class="kw">return(m_font_size);                }
class="type">void        FontSize(class="kw">const class="type">int fontzise)                  { m_font_size=fontzise;               }
class=class="str">"cmt">//--- 设置和获取距底图边缘的缩进

◍ 坐标轴与图形接口的存取封装

这段类定义把图表对象的几个关键属性做成了 getter/setter 对,外部调用时不用直接碰私有成员。Gap() 用重载区分了读和写:无参返回 m_gap 整数,带 const int 参数则写入 m_gap,用来控制柱间空白像素。 Y 轴边界由 YMin() / YMax() 管理,返回和接收的都是 double,意味着你能在运行时动态把 m_y_min、m_y_max 改成当前品种的真实波动区间,而不是写死。NumGrid() 管刻度数量,int 类型,设成 5 还是 10 直接决定纵轴标线密度。 公开方法里 Create(name,x,y) 负责按坐标生成图形,Delete() 做清理,SetArrayValue(double &data[]) 以引用方式接外部数组——避免大数组拷贝。private 里的 VerticalScale / HorizontalScale 才是真正算比例尺的内务函数,外部不可见。 开 MT5 把这段塞进你自己的 CCanvas 派生类,先调 NumGrid(8) 再看纵轴是否如预期出现 8 条参考线;外汇与贵金属波动剧烈,动态 YMax 设错可能让图形压缩失真,属高风险调试项。

MQL5 / C++
class="type">int Gap(class="type">void) { class="kw">return(m_gap); }
class="type">void Gap(class="kw">const class="type">int g) { m_gap=g; }
class=class="str">"cmt">//--- 设置和获取 Y 轴的最小和最大值
class="type">class="kw">double YMin(class="type">void) { class="kw">return(m_y_min); }
class="type">void YMin(class="kw">const class="type">class="kw">double ymin) { m_y_min=ymin; }
class="type">class="kw">double YMax(class="type">void) { class="kw">return(m_y_max); }
class="type">void YMax(class="kw">const class="type">class="kw">double ymax) { m_y_max=ymax; }
class=class="str">"cmt">//--- 设置和获取 Y 轴刻度数量
class="type">int NumGrid(class="type">void) { class="kw">return(m_num_grid); }
class="type">void NumGrid(class="kw">const class="type">int num) { m_num_grid=num; }
class=class="str">"cmt">//--- 创建图形
class="type">void Create(class="type">class="kw">string name,class="type">int x,class="type">int y);
class=class="str">"cmt">//--- 删除图形 
class="type">void Delete(class="type">void);
class=class="str">"cmt">//--- 设置输入数组
class="type">void SetArrayValue(class="type">class="kw">double &data[]);
class="kw">private:
 class=class="str">"cmt">//---
 class="type">void VerticalScale(class="type">class="kw">double min,class="type">class="kw">double max,class="type">int num_grid);
 class="type">void HorizontalScale(class="type">int num_grid);

在MT5画布上把数组画成线图

想在MT5里用Canvas自绘指标线,核心是两个成员函数:Create()负责搭框架,SetArrayValue()负责把double数组映射成像素点连线。 Create()先强制把绘图原点夹到画布半宽半高以内(x< m_x_size/2 则取半宽),避免图形跑到资源外。随后填四层矩形:最外框、底图、坐标轴框、绘图区背景,透明度由m_transparency统一控制。 坐标轴刻度用 VerticalScale(m_y_min,m_y_max,m_num_grid) 和 HorizontalScale(5) 生成,横向先写死5格,后面接数据时会重算。 SetArrayValue()每次进来都重绘四层背景,再判断 data[ArrayMaximum(data)] 是否突破 m_y_max,突破了就自动拉伸Y轴上限。循环里 y0、y1 用 (YSize()-m_gap) - (YSize()-2*m_gap)/m_y_max*data[i] 把数值换算成画布Y坐标,并用 LineAA 抗锯齿连线、FillCircle 画半径2的圆点。 注意循环里 y0 被赋值两次,第一次的 int((YSize()-2*m_gap)*(1-data[i]/m_y_max)) 实际被第二行覆盖,属冗余代码,复制时可删掉第一行省编译警告。外汇与贵金属图表自绘属高风险实验,参数错误可能画出空白面板。

MQL5 / C++
class="type">void CLineGraph::Create(class="type">class="kw">string name,class="type">int x,class="type">int y)
  {
class=class="str">"cmt">//--- 修改指标的相对位置
   x=(x<m_x_size/class="num">2)?m_x_size/class="num">2:x;
   y=(y<m_y_size/class="num">2)?m_y_size/class="num">2:y;
   Name(name);
   X(x);
   Y(y);
   XSize(m_x_size);
   YSize(m_y_size);
   if(!CreateCanvas())
      Print("错误。未能创建指标。");
class=class="str">"cmt">//--- 创建图形的框架和底图
   m_canvas.FillRectangle(class="num">0,class="num">0,XSize(),YSize(),ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillRectangle(class="num">1,class="num">1,XSize()-class="num">2,YSize()-class="num">2,ColorToARGB(m_bg_color,m_transparency));
class=class="str">"cmt">//--- 创建坐标轴和图形背景
   m_canvas.FillRectangle(m_gap-class="num">1,m_gap-class="num">1,XSize()-m_gap+class="num">1,YSize()-m_gap+class="num">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));
class=class="str">"cmt">//--- 创建坐标轴刻度和它们的数值
   VerticalScale(m_y_min,m_y_max,m_num_grid);
   HorizontalScale(class="num">5);
   m_canvas.Update();
  }
class="type">void CLineGraph::SetArrayValue(class="type">class="kw">double &data[])
  {
   class="type">int y0,y1;
class=class="str">"cmt">//---  创建图形的框架和底图
   m_canvas.FillRectangle(class="num">0,class="num">0,XSize(),YSize(),ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillRectangle(class="num">1,class="num">1,XSize()-class="num">2,YSize()-class="num">2,ColorToARGB(m_bg_color,m_transparency));
class=class="str">"cmt">//--- 创建坐标轴和图形背景
   m_canvas.FillRectangle(m_gap-class="num">1,m_gap-class="num">1,XSize()-m_gap+class="num">1,YSize()-m_gap+class="num">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));
class=class="str">"cmt">//--- 如果数据数组的最大值超过 Y 轴上边界, 则缩放数轴。
   if(data[ArrayMaximum(data)]>m_y_max)
      m_y_max=data[ArrayMaximum(data)];
class=class="str">"cmt">//--- 创建坐标轴刻度和它们的数值
   VerticalScale(m_y_min,m_y_max,m_num_grid);
   HorizontalScale(ArraySize(data));
class=class="str">"cmt">//--- 根据数据数组创建图形
   for(class="type">int i=class="num">0;i<ArraySize(data)-class="num">1;i++)
     {
      y0=class="type">int((YSize()-class="num">2*m_gap)*(class="num">1-data[i]/m_y_max));
      y0=class="type">int((YSize()-m_gap)-(YSize()-class="num">2*m_gap)/m_y_max*data[i]);
      y0=(y0<m_gap)?m_gap:y0;
      y1=class="type">int((YSize()-m_gap)-(YSize()-class="num">2*m_gap)/m_y_max*data[i+class="num">1]);
      y1=(y1<m_gap)?m_gap:y1;
      m_canvas.LineAA(m_x[i+class="num">1],y0,m_x[i+class="num">2],y1,ColorToARGB(m_graph_color,m_transparency),STYLE_SOLID);
      m_canvas.FillCircle(m_x[i+class="num">1],y0,class="num">2,ColorToARGB(m_graph_color,m_transparency));
      m_canvas.FillCircle(m_x[i+class="num">2],y1,class="num">2,ColorToARGB(m_graph_color,m_transparency));
     }
   m_canvas.Update();

「把 RSI 塞进独立浮窗绘图」

在 MT5 里想脱离主图副图限制、把 RSI 画成可拖拽的独立线图,靠 CustomGUI 库的 CLineGraph 类就能绕开原生指标窗口。下面这段初始化逻辑把周期 10 的 RSI 句柄先拿过来,失败就直接 INIT_FAILED,避免后面空跑。 ind.NumGrid(10) 设了 10 条网格线,ind.YMax(100) 把纵轴锁在 0–100 的振荡器常规区间;ind.Create("rsi",350,250) 则是在图表上生成名为 rsi、宽 350 高 250 像素的悬浮绘图区。 真要在贵金属或外汇上用,先把 CustomGUI.mqh 放到 Include\CustomGUI\ 路径下,否则编译会报找不到文件。外汇和贵金属杠杆高,这类独立面板只是观察辅助,信号反转概率不等于方向确定性。

MQL5 / C++
class="macro">#class="kw">property copyright "版权所有 class="num">2017, Alexander Fedosov"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property indicator_plots class="num">0
class="macro">#class="kw">property indicator_chart_window
class="macro">#include <CustomGUI\CustomGUI.mqh>
class=class="str">"cmt">//---
CLineGraph ind;
class="type">int InpInd_Handle;
class="type">class="kw">double rsi[];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标初始化函数                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---- 获取指标句柄
   InpInd_Handle=iRSI(Symbol(),PERIOD_CURRENT,class="num">10,PRICE_CLOSE);
   if(InpInd_Handle==INVALID_HANDLE)
     {
      Print("获取指标句柄失败");
      class="kw">return(INIT_FAILED);
     }
class=class="str">"cmt">//---
   ind.NumGrid(class="num">10);
   ind.YMax(class="num">100);
   ind.Create("rsi",class="num">350,class="num">250);
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标迭代函数                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(class="kw">const class="type">int rates_total,
                class="kw">const class="type">int prev_calculated,
                class="kw">const class="type">class="kw">datetime &time[],
                class="kw">const class="type">class="kw">double &open[],
                class="kw">const class="type">class="kw">double &high[],
                class="kw">const class="type">class="kw">double &low[],
                class="kw">const class="type">class="kw">double &close[],
                class="kw">const class="type">long &tick_volume[],
                class="kw">const class="type">long &volume[],
                class="kw">const class="type">int &spread[])
  {
class=class="str">"cmt">//---

◍ 把指标句柄和逆初始化接好

这段收尾代码负责在自定义指标计算里把 RSI 缓冲拷进数组,并在指标卸载时清理绘图对象。CopyBuffer 取 0 号缓冲、从 0 根起共 10 根,若返回 ≤0 直接 return(0),意味着本次计算作废,避免拿空数组去画图。

MQL5 / C++
if(CopyBuffer(InpInd_Handle,class="num">0,class="num">0,class="num">10,rsi)<=class="num">0)
   class="kw">return(class="num">0);
ind.SetArrayValue(rsi);
class=class="str">"cmt">//--- 返回 prev_calculated 的数值以便下次调用
   class="kw">return(rates_total);
}
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
   ind.Delete();
   ChartRedraw();
  }
逐行看:第 1 行用 InpInd_Handle 把 RSI 最近 10 根写入 rsi 数组,失败就退出;第 3 行把数组交给 ind 对象缓存;第 5 行返回 rates_total,MT5 下次只跑新增 K 线。OnDeinit 里 ind.Delete() 删掉自建图形,ChartRedraw() 强制重绘,防止残留线挂在图表上。 开 MT5 新建指标把这段接在 Calc 末尾,切周期观察:若日志不报 0 缓冲且卸载后对象消失,说明句柄生命周期管对了。外汇与贵金属波动剧烈,指标失效可能瞬间发生,任何信号都只是概率参考。

MQL5 / C++
if(CopyBuffer(InpInd_Handle,class="num">0,class="num">0,class="num">10,rsi)<=class="num">0)
   class="kw">return(class="num">0);
ind.SetArrayValue(rsi);
class=class="str">"cmt">//--- 返回 prev_calculated 的数值以便下次调用
   class="kw">return(rates_total);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标的逆初始化函数                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
   ind.Delete();
   ChartRedraw();
  }

记住这一条就够了

用 CCanvas 写自定义图形指标,核心是把绘图逻辑封装成类去扩展基础画布,而不是在指标主循环里堆代码。文中那套圆形、扇区、线性图类就是这么来的,附带的 MQL5.zip 约 234.78 KB,把 CanvasBase.mqh 等 6 个库文件和 4 个示例指标按文件夹塞进终端根目录的 MQL5 目录就能直接编译跑。 删除画布资源那段代码社区里吵过三轮:原版用三元运算符返回布尔值其实多余,直接 return ObjectDelete(0,m_canvas_name) 就行;想在删完对象后顺手清掉残留线条,就在 DeleteCanvas() 里 ObjectDelete 成功后补一句 ChartRedraw()。垂直缩放若遇到 RTS 这类 Y 轴区间极窄(如 97756.0~97784.0)的标的,CLineGraph::VerticalScale() 可能出啸叫,必要时换 CGraphic 类更稳。 外汇和贵金属图表加载这类自绘指标波动剧烈、重绘频繁,实盘前务必在策略测试器用历史数据验证资源释放是否干净,避免图表卡顿或内存泄漏。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|| 删除图形资源|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CCanvasBase::DeleteCanvas()
  {
   class="kw">return(ObjectDelete(class="num">0,m_canvas_name)?true:class="kw">false);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|| 删除图形资源|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CCanvasBase::DeleteCanvas()
  {
   class="kw">return ObjectDelete(class="num">0,m_canvas_name);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|| 删除图形资源|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CCanvasBase::DeleteCanvas()
  {
   if(ObjectFind(class="num">0,m_canvas_name)>-class="num">1)
     if(ObjectDelete(class="num">0,m_canvas_name))
       {
        ChartRedraw();
        class="kw">return true;
       }
   class="kw">return class="kw">false;
  }
class=class="str">"cmt">//--- 更新图表
class="type">class="kw">double min_y=NormalizeDouble(min_pr-class="num">0.2*diff,_Digits);
class="type">class="kw">double max_y=NormalizeDouble(max_pr+class="num">0.2*diff,_Digits);
ticks_ind.YMin(min_y);
ticks_ind.YMax(max_y);
ticks_ind.SetArrayValue(prices);
把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到自定义图形指标的运行状态,你只需专注策略逻辑而非底层绘制。

常见问题

所有自定义图形对象在构造和销毁时都依赖这两个方法申请和释放图形资源,统一放在基类可避免每个子类重复处理画布生命周期。
框架定义边界轮廓,背景承载文本空间,数值显示实时读数,描述给出名称周期等信息,四层叠加构成可读的圆型仪表。
倾向用于连续序列如均线偏离、波动率曲线,比标准线型绘图更易控制颜色渐变与坐标缩放。
可以,只要编译后的指标在品种页生效,小布盯盘的 AIGC 层会读取其输出做辅助诊断,不需要额外配置。
这样 EA 或指标只需引入一个头文件就能调用整套图形库,减少手工罗列依赖导致的编译遗漏。
弧线只描边界曲线,扇区还要填充两半径夹成的区域,资源开销与坐标计算逻辑都更复杂。