利用 CCanvas 类开发自定义指标(基础篇)
「正文」
利用 CCanvas 类开发自定义指标 MetaTrader 5 — 指标 | 24 七月 2017, 10:08
- 002
19 Alexander Fedosov
◍ 先搭好图形对象的类骨架
在 MT5 里画自定义图形,第一步不是写绘图逻辑,而是把可复用的几何结构抽象成基类。原文把圆形、圆弧、圆截面和线性图各自拆成独立类,目的就是让后续指标只管「算点」,不管「怎么画」。 具体落了四个类:CCircleSimple 负责整圆,CCircleArc 管一段弧,CCircleSection 管圆被截出的部分,CLineGraph 则处理纯线性连接。这种分法在 MT5 自定义指标里很实用——你改一个圆的样式不会波及线图模块。 开 MT5 按 F4 进 MetaEditor,新建 Indicator 后别急着写 OnCalculate,先建这几个空类骨架,编译能过再填绘图代码,调试路径最短。外汇与贵金属波动大,图形类若耦合过重,实盘重载时容易卡顿,拆开写风险更低。
用 CCanvas 突破 18 种绘图限制
MT5 原生指标开发只能从 18 种图形绘图中选样式,手工和 EA 都能挂,但视觉表达很快触顶。 CCanvas 自定义图形库把画布控制权交给代码,能脱离标准绘图类型做任意像素级渲染,适合做非标准指标。 本文会拆这个函数库的能力边界,并给出一个可直接挂到图表上的自定义指标库样例,你打开 MT5 的 MQL5/Include 就能照抄验证。 外汇与贵金属杠杆高,这类自定义指标只解决“看不清”的问题,不替你过滤方向风险。
「画布基类的骨架怎么搭」
在 MT5 的数据文件夹下,路径 MQL5\Include\CustomGUI 里新建 CanvasBase.mqh,就能把自定义图形对象的公共底座收拢到一个类里。这个类管三样东西:画布名字、左上角坐标 (x,y)、以及宽高 (x_size,y_size),后续所有按钮、面板、刻度尺都从它继承。 构造阶段必须调用 CreateCanvas() 与 DeleteCanvas(),一个负责向图表申请图形资源,一个负责销毁。这两个方法内部引用的成员变量要在构造函数里先清零,否则第一次绘制可能拿到野指针坐标,画布出现在屏幕外的概率不低。 下面这段是基类头文件的核心声明,已去掉外部作者信息,只留结构。注意 m_canvas 是 protected,子类能直接拿来画,但坐标和名字被锁在 private,只能通过 X()/Y() 接口改。 别把坐标当摆设 基类里 X(int) 和 Y(int) 是写接口,X() 和 Y() 是读接口。实盘面板若要根据鼠标移动重绘,每次改完 m_x/m_y 都要重跑 CreateCanvas(),否则旧资源不释放,图表对象数会悄悄涨到平台上限(默认 2000 个以内)拖慢终端。
class="macro">#include <Canvas\Canvas.mqh> class CCanvasBase { class="kw">private: class=class="str">"cmt">//--- 画布名称 class="type">class="kw">string m_canvas_name; class=class="str">"cmt">//--- 画布坐标 class="type">int m_x; class="type">int m_y; class=class="str">"cmt">//--- 画布尺寸 class="type">int m_x_size; class="type">int m_y_size; class="kw">protected: CCanvas m_canvas; class=class="str">"cmt">//--- 创建对象的图形资源 class="type">bool CreateCanvas(class="type">void); class=class="str">"cmt">//--- 删除图形资源 class="type">bool DeleteCanvas(class="type">void); class="kw">public: CCanvasBase(class="type">void); ~CCanvasBase(class="type">void); class=class="str">"cmt">//--- 设置和获取坐标 class="type">void X(class="kw">const class="type">int x) { m_x=x; } class="type">void Y(class="kw">const class="type">int y) { m_y=y; } class="type">int X(class="type">void) { class="kw">return(m_x); } class="type">int Y(class="type">void) { class="kw">return(m_y); } class=class="str">"cmt">//--- 设置和获取尺寸
◍ 画布基类的尺寸与创建逻辑
在 MT5 里用 CCanvas 类做自定义图形面板,第一步往往是封装一个基类来管理画布的尺寸与名称。下面这段基类把画布默认宽高锁在 200×200 像素,左上角锚点,并在构造时清零坐标。 XSize() 和 YSize() 各有一对重载:带参版本写私有成员 m_x_size / m_y_size,无参版本读回当前值。Name() 则只负责把外部传入的字符串塞进 m_canvas_name,供后续 CreateBitmapLabel 调用。 CreateCanvas() 是真正落地的函数:先 ObjectDelete(0, m_canvas_name) 清掉同名的旧对象,避免重复创建报错;再调用 m_canvas.CreateBitmapLabel() 生成位图标签。注意这里用的色彩格式是 COLOR_FORMAT_ARGB_NORMALIZE,意味着像素 alpha 通道会被归一化到 0~1 区间。 创建成功后连续三句 ObjectSetInteger 把画布钉在左上角、中心锚点、且置于前景(OBJPROP_BACK=false)。外汇与贵金属图表叠加这类自定义层时波动跳价频繁,误设背景层可能导致遮挡报价,属于高风险操作,建议开 MT5 用这段直接验证锚点行为。
class="type">void XSize(class="kw">const class="type">int x_size) { m_x_size=x_size; } class="type">void YSize(class="kw">const class="type">int y_size) { m_y_size=y_size; } class="type">int XSize(class="type">void) { class="kw">return(m_x_size); } class="type">int YSize(class="type">void) { class="kw">return(m_y_size); } class=class="str">"cmt">//--- 在创建时设置指标名称 class="type">void Name(class="kw">const class="type">class="kw">string canvas_name) { m_canvas_name=canvas_name; } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 构造器 | class=class="str">"cmt">//+------------------------------------------------------------------+ CCanvasBase::CCanvasBase(class="type">void) : m_x(class="num">0), m_y(class="num">0), m_x_size(class="num">200), m_y_size(class="num">200) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 析构器 | class=class="str">"cmt">//+------------------------------------------------------------------+ CCanvasBase::~CCanvasBase(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 为一个对象创建图形资源 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CCanvasBase::CreateCanvas(class="type">void) { ObjectDelete(class="num">0,m_canvas_name); if(!m_canvas.CreateBitmapLabel(m_canvas_name,m_x,m_y,m_x_size,m_y_size,COLOR_FORMAT_ARGB_NORMALIZE)) class="kw">return(class="kw">false); ObjectSetInteger(class="num">0,m_canvas_name,OBJPROP_CORNER,CORNER_LEFT_UPPER); ObjectSetInteger(class="num">0,m_canvas_name,OBJPROP_ANCHOR,ANCHOR_CENTER); ObjectSetInteger(class="num">0,m_canvas_name,OBJPROP_BACK,class="kw">false); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+
清掉画布别留垃圾对象
在 MT5 里用 CCanvasBase 做自定义图形绘制时,程序退出或重绘前必须把画布对象从图表上摘掉,否则会留下看不见却占资源的 Object。 上面这段直接调 ObjectDelete(0, m_canvas_name) 删掉当前图表(图表 ID 为 0)下名为 m_canvas_name 的图形资源,成功返回 true,失败返回 false。 实盘或回测里若频繁新建画布却不删,MT5 终端的对象管理器里对象数可能几分钟涨到上百个,拖慢刷新;养成退出即 DeleteCanvas 的习惯能避坑。外汇与贵金属波动剧烈,这类资源管理不当会放大终端卡顿风险。
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">//+------------------------------------------------------------------+