利用 CCanvas 类开发自定义指标(基础篇)
📘

利用 CCanvas 类开发自定义指标(基础篇)

第 1/3 篇

「正文」

利用 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 个以内)拖慢终端。

MQL5 / C++
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 用这段直接验证锚点行为。

MQL5 / C++
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 的习惯能避坑。外汇与贵金属波动剧烈,这类资源管理不当会放大终端卡顿风险。

MQL5 / C++
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">//+------------------------------------------------------------------+

常见问题

用CCanvas类在独立画布上自由绘制,不依赖平台内置图形对象,可画任意形状与组合。
先定义画布宽度和高度,再调用Create绑定图表与子窗口,尺寸错会导致绘制失败。
可以,小布能扫描品种页的AI诊断,提示未清理的画布与游离对象,你按提示删掉即可。
多在周期切换时没重清画布,需在OnDeinit里释放、OnInit重建,避免旧像素残留。
ResourceCreate建资源句柄,CanvasCreate绑定位图并映射图表,顺序错则画不出来。