利用 CCanvas 类开发自定义指标·综合运用
(3/3)·把圆形、弧线、扇区与线性图统统封装进自己的指标库,告别平台 18 种绘图的限制
「用扇形弧线给圆形指标加刻度」
在基础圆形指标之外叠一层弧形显示,思路是用 Canvas 的 Pie() 画填充椭圆扇区,把数值映射成动态张角。fi3 固定为弧形起点,fi4 随传入指标值实时变化,这样盘面就能直接看出当前量在量程里的位置。 方位由 ENUM_ORIENTATION 控制:VERTICAL 时弧从 90 度起算,HORIZONTAL 时从 0 度起算,且一律逆时针计数。建对象有严格层序——先画圆形背景,再画扇区弧,随后文字底圆,最后数值与描述,顺序反了会遮层。 Create() 里给 fi3、fi4 赋初值要先把角度转弧度,示例里 b 变量默认 90 须乘 M_PI/180,否则扇区会画歪。NewValue() 同理,重算第二边界弧度后整体重绘。 实测用一个点差监控指标接这套类,点差越过阈值就改弧色,图例里能直观看到圆盘从绿转红。外汇与贵金属波动大、点差跳变频繁,这类图形仅作辅助,不构成方向判断。 属性只能在 Create() 前或 NewValue() 前改,两类方法一旦执行就按当时属性重绘,运行中再赋值不会生效。
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 里,用鼠标点不同品种自动切换配色,比手动改参数更利于复盘多周期。
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」这类标签。外汇与贵金属波动剧烈,这类自绘件只负责呈现,信号真假仍要靠历史回看验证,杠杆交易风险高。
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) 就能看到四分之三圆弧——外汇与贵金属图表叠加此类量表有刷新延迟风险,参数需实盘验证。
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 管阈值色;OnInit 中 arc.Create("spread_ind",100,100) 把控件挂在坐标(100,100)。外汇与贵金属点差受流动性影响跳动频繁,用这类可视化控件盯盘时须清楚其仅反映当时经纪商报价,高风险品种在数据行情中可能瞬时扩至量程外。
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,指标卸载或周期切换时清掉图形对象避免残留。外汇与贵金属杠杆高、点差跳变频繁,这段代码只做可视化预警,不构成任何方向判断。
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 元素。
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)三个参数,调用方传实时报价占比即可重绘指针或数值——外汇与贵金属波动剧烈,这类仪表盘只作概率参考,实盘仍属高风险。
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,能直观看到底环变窄、扇区显示更宽;外汇与贵金属图表加载此类自定义控件波动剧烈,属高风险操作,参数需自行回测验证。
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 看密集度变化。
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() 负责建资源贴图到品种图表,垂直/水平刻度由另外两个私有方法在构造或创建前注入。
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 线,太高则坐标轴文字发虚。外汇和贵金属波动快、跳空多,图表元素太抢眼可能干扰价格行为判断,调色时留点余量更稳。
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 里反复调用写方法。
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 设错可能让图形压缩失真,属高风险调试项。
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)) 实际被第二行覆盖,属冗余代码,复制时可删掉第一行省编译警告。外汇与贵金属图表自绘属高风险实验,参数错误可能画出空白面板。
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\ 路径下,否则编译会报找不到文件。外汇和贵金属杠杆高,这类独立面板只是观察辅助,信号反转概率不等于方向确定性。
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),意味着本次计算作废,避免拿空数组去画图。
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(); }
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 类更稳。 外汇和贵金属图表加载这类自绘指标波动剧烈、重绘频繁,实盘前务必在策略测试器用历史数据验证资源释放是否干净,避免图表卡顿或内存泄漏。
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);