傻瓜式MQL:如何设计和构建对象类(基础篇)
📘

傻瓜式MQL:如何设计和构建对象类(基础篇)

第 1/3 篇

把EA拆成对象类到底图什么

很多老手写MQL5还是一堆全局函数和数组,改一个逻辑要翻几百行。把行情、信号、订单各封成一个类,等于给EA建了几个独立车间,哪块出问题直接进那间屋查。 对象类的核心不是炫技,是隔离状态。比如一个CTrend类只管均线交叉判定,不碰持仓;COrder类只管发单和撤单,不关心指标怎么算。这样回测时你能单独替换信号模块,不用重写整段策略。 MQL5里类写在#include能复用的头文件中,一个项目积累五六个基础类后,新EA的开发时间往往能从两天压到半天。注意外汇和贵金属杠杆高,类再清晰也挡不住极端行情滑点,隔离只是为了排查快,不意味风险更低。

「从事件驱动看EA的类结构」

在 MT5 里写 EA 不强制用面向对象,纯过程式也能跑,但 OOP 的价值在于把行情、成交、计时器等外部刺激统一抽象成事件流,让对象类去截获和响应。这样你的策略模型更可能随市场状态自适应,而不是写死一串顺序逻辑。 OOP 落地的三根支柱是封装、继承、多态。封装等于黑盒:外部只管往类里塞事件,内部怎么算不暴露;继承允许从已有类派生新类并保留祖先的属性方法;多态则让派生类改写继承来的方法实现,virtual 修饰符就是改写入口,方法名保持不变。 下面这段骨架代码把 MT5 的五个标准事件入口和一個类定义摊开了。OnInit 返回 0 表示初始化成功,OnDeinit 带 reason 参数记录卸载原因,OnTick 每笔报价触发,OnTimer 按设定周期触发,OnChartEvent 接收图表交互的 id 与三类参数。 类 CNew 公有继承 CObject,private 区放 X、Y 和内部编辑函数,protected 的 on_event 作事件处理开关,public 的 virtual OnEvent 就是可被派生类覆盖的多态点。复制进 MT5 编辑器即可编译验证事件框架是否空载通过。 外汇与贵金属杠杆高,回测通过不代表实盘能扛住滑点和断连,事件框架只是工程地基。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| EA 初始化函数                                                |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">int OnInit()                                                          class=class="str">"cmt">// OnInit 事件处理
  {
   class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| EA 去初始化函数                                              |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void OnDeinit(const class="type">int reason)                                       class=class="str">"cmt">// OnDeInit 事件处理
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| EA 的 tick 函数                                              |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void OnTick()                                                         class=class="str">"cmt">// OnTick事件处理
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| EA 时间函数                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void OnTimer()                                                        class=class="str">"cmt">// OnTimer事件处理
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| EA 图表事件函数                                              |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">void OnChartEvent(const class="type">int id,                                      class=class="str">"cmt">// OnChartEvent 事件处理
                   const class="type">long &lparam,
                   const class="type">class="kw">double &dparam,
                   const class="type">class="kw">string &sparam)
  {
  }
class CNew:class="kw">public CObject
  {
class="kw">private:
   class="type">int               X,Y;
   class="type">void              EditXY();
class="kw">protected:
   class="type">bool              on_event;                                        class=class="str">"cmt">//事件处理标识
class="kw">public:
   class=class="str">"cmt">// 类的构造函数
   class="type">void              CNew();
   class=class="str">"cmt">// OnChart 事件处理方法
   class="kw">virtual class="type">void      OnEvent(const class="type">int id,

◍ 把图表事件接进自定义类

想在 MT5 自定义控件里响应鼠标或图表事件,得让类继承 CObject 并自己实现 OnEvent 虚函数。上面这段代码展示了一个 CNew 类重写事件入口的骨架,参数布局和系统回调完全一致。 逐行看:const long &lparam 承接事件相关的整型参数(如图表 ID 或坐标),const double &dparam 传浮点数据(如价格),const string &sparam 负责文本类信息(如对象名)。private 里的 X,Y 与 EditXY() 说明坐标状态由类内部维护,不在事件函数里裸写。 开 MT5 新建 EA 把这段塞进类定义,编译后给图表挂个 ObjectDelete 或鼠标点击,断点进 OnEvent 就能确认参数流向。外汇与贵金属杠杆高,这类交互逻辑只解决工程问题,不预示任何方向。

MQL5 / C++
const class="type">long &lparam,
const class="type">class="kw">double &dparam,
const class="type">class="kw">string &sparam);
};
class="kw">private:
  class="type">int        X,Y;
  class="type">void       EditXY();
class CNew: class="kw">public CObject
  class=class="str">"cmt">// OnChart 事件处理方法
  class="kw">virtual class="type">void      OnEvent(const class="type">int id,
                const class="type">long &lparam,
                const class="type">class="kw">double &dparam,
                const class="type">class="kw">string &sparam);

用 OOP 搭 MT5 视觉框架的四步落点

MQL5 的面向对象写法最实用的地方是可扩展性:老系统接新组件时,原有代码通常不用改。本文以在 MT5 里写一个 MasterWindows 类视觉设计器为例,把设计过程拆成四个阶段,读者可直接照此在 MetaEditor 里复刻。 第一阶段先用纸笔画草图,重点不是好看,而是定下用户接口与数据处理的组织方式。草图采用类似 Windows 窗口的行列结构,只放两种图形对象:OBJ_EDIT 和 OBJ_BUTTON。这一步常会耗掉超过一天,但能决定后面算法怎么写。 第二阶段设计基类,先落三个:CCell(单元)、CRow(由单元组成)、CWin(由行组成)。类间传数据除了普通变量,MQL5 还有 struct 结构。文中用的 WinCell 是简单结构(无动态数组),变量间可自由跨结构复制,后面会看到它省了多少事。基类都带事件截获与传递方法,缺了交互就断了;写基类方法越少越好,扩展留给派生类。 第三阶段开始运转项目。框架直接用 MQL5 向导生成的 EA 模板改,要求编译零错误、加载即跑。先链单元派生类(CCellText、CCellEdit、CCellButton 等),构造器里先禁事件处理,准备妥当再放开,避免干扰。测试时编辑框输入非“默认”值若变化即成功;按钮按一次后不弹起,只需改 CCell 基类,十几个派生按钮靠继承自动修正——这是 OOP 继承最直观的回报。 第四阶段把测好的模块级联成窗口。CMasterWindows 作为 CWin 的后代,只覆写少数方法即可。之前写的 WinCell 结构此时优势明确:一次结构复制就把所有成员带过去,代码简洁。到此类设计可转去视觉构建技术。 别在构造器外直接读别的类内部数据 对象交互靠事件和方法,跨类直读私有数据会让后期调试极难,错误常藏在莫名其妙的地方。 让小布替你跑这套 打开 MT5 新建 EA,粘入下方结构与原类骨架,先编译空框架确认无错,再逐阶段加 CCell 派生类,省得一次堆满报错无从查起。外汇与贵金属品种波动剧烈,任何界面工具只降人工成本,不预示行情方向,实盘前请充分回测。

MQL5 / C++
class="kw">struct WinCell
  {
   class="type">class="kw">color                TextColor;     class=class="str">"cmt">//文本颜色
   class="type">class="kw">color                BGColor;       class=class="str">"cmt">// 背景颜色
   class="type">class="kw">color                BGEditColor;   class=class="str">"cmt">// 编辑时的背景颜色
   ENUM_BASE_CORNER     Corner;        class=class="str">"cmt">// 标定角
   class="type">int                  H;             class=class="str">"cmt">// 元素高度
   class="type">int                  Corn;          class=class="str">"cmt">// 位移方向(class="num">1;-class="num">1)
  };
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| CCell 基类                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------
class CCell
  {
class="kw">private:
class="kw">protected:
   class="type">bool                on_event;       class=class="str">"cmt">// 事件处理标识
   ENUM_OBJECT         type;           class=class="str">"cmt">// 元素类型
class="kw">public:
   WinCell             Property;       class=class="str">"cmt">// 元素属性
   class="type">class="kw">string              name;           class=class="str">"cmt">// 元素名称
   class=class="str">"cmt">//+---------------------------------------------------------------+
   class=class="str">"cmt">// 类的构造函数
   class="type">void                CCell();

「界面对象的基类骨架怎么搭」

想在 MT5 里写一套自定义面板,第一步是把基类抽象清楚。下面这段把单元格(WinCell)和行(CRow)两个基类的方法签名列了出来,虚函数 Draw 和 OnEvent 是所有子类必须自己实现的入口。 Draw 接收四个参数:m_name 是绘图标识,m_xdelta / m_ydelta 是相对偏移像素,m_bsize 控制边框尺寸。OnEvent 则把图表事件拆成 id、lparam、dparam、sparam 四型参数,分别对应整型事件号、长整型、双精度与字符串载荷。 CRow 里有个 protected 的 on_event 布尔量,用来标记该行是否接管事件;public 的 name 和 Property(WinCell 类型)则供外部直接读写。开 MT5 新建一个 include 把这两段贴进去,就能以它们为父类去派生你自己的行情格子。 外汇与贵金属图表上挂这类面板属于高风险操作,事件响应逻辑写错可能卡死图表,建议先在模拟账户验证。

MQL5 / C++
class="kw">virtual    class=class="str">"cmt">// 绘图方法
class="type">void                Draw(class="type">class="kw">string m_name,
                      class="type">int m_xdelta,
                      class="type">int m_ydelta,
                      class="type">int m_bsize);
class="kw">virtual    class=class="str">"cmt">// 事件处理方法
class="type">void                OnEvent(const class="type">int id,
                          const class="type">long &lparam,
                          const class="type">class="kw">double &dparam,
                          const class="type">class="kw">string &sparam);
};
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| CRow 基类                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------
class CRow
  {
class="kw">protected:
  class="type">bool              on_event;      class=class="str">"cmt">// 事件处理标识
class="kw">public:
  class="type">class="kw">string            name;          class=class="str">"cmt">// 行名称
  WinCell           Property;      class=class="str">"cmt">// 行属性
  class=class="str">"cmt">//+---------------------------------------------------------------+
  class=class="str">"cmt">// 类的构造函数
  class="type">void              CRow();
  class="kw">virtual    class=class="str">"cmt">// 绘图方法
  class="type">void              Draw(class="type">class="kw">string m_name,
                      class="type">int m_xdelta,
                      class="type">int m_ydelta,
                      class="type">int m_bsize);
  class="kw">virtual    class=class="str">"cmt">// 事件处理方法
  class="type">void              OnEvent(const class="type">int id,
                          const class="type">long &lparam,
                          const class="type">class="kw">double &dparam,
                          const class="type">class="kw">string &sparam);
  };

◍ 把窗口类骨架直接搬进EA工程

下面这段 CWin 类把面板的坐标、尺寸、角落锚定和六类行对象一次性封装,省去每次手绘 OBJ_LABEL 的重复劳动。外汇与贵金属行情跳动快,这类轻量面板在高杠杆下只作辅助参考,实际下单仍须自行承担高风险。 类里 private 的 SetXY(int m_corner) 负责按锚角换算绝对坐标;protected 的 on_event 是事件开关,public 区则暴露 name、w_xpos、w_ypos、w_bsize、w_hsize 等可直接在外部赋值的字段。 STR1 到 STR6 分别对应 CRowType1~6 六种行样式,Draw() 接收二维 int 与 string 数组按 count 行渲染。把代码贴进 MT5 的 include 目录,新建 EA 时 #include 即可复用,改 w_corner 从 0 到 3 能验证四个锚角贴边效果。

MQL5 / C++
class CWin
  {
class="kw">private:
   class="type">void                SetXY(class="type">int m_corner); class=class="str">"cmt">//坐标
class="kw">protected:
   class="type">bool                on_event;   class=class="str">"cmt">// 事件处理标识
class="kw">public:
   class="type">class="kw">string              name;       class=class="str">"cmt">// 窗口名称
   class="type">int                 w_corner;   class=class="str">"cmt">// 窗口角
   class="type">int                 w_xdelta;   class=class="str">"cmt">// 垂直delta
   class="type">int                 w_ydelta;   class=class="str">"cmt">// 水平detla
   class="type">int                 w_xpos;     class=class="str">"cmt">// X 坐标
   class="type">int                 w_ypos;     class=class="str">"cmt">// Y 坐标
   class="type">int                 w_bsize;    class=class="str">"cmt">// 窗口宽度
   class="type">int                 w_hsize;    class=class="str">"cmt">// 窗口高度
   class="type">int                 w_h_corner; class=class="str">"cmt">// 隐藏模式的角落
   WinCell             Property;   class=class="str">"cmt">// 属性
   class=class="str">"cmt">//---
   CRowType1           STR1;       class=class="str">"cmt">// CRowType1
   CRowType2           STR2;       class=class="str">"cmt">// CRowType2
   CRowType3           STR3;       class=class="str">"cmt">// CRowType3
   CRowType4           STR4;       class=class="str">"cmt">// CRowType4
   CRowType5           STR5;       class=class="str">"cmt">// CRowType5
   CRowType6           STR6;       class=class="str">"cmt">// CRowType6
   class=class="str">"cmt">//+---------------------------------------------------------------+
   class=class="str">"cmt">// 类的构造函数
   class="type">void                CWin();
   class=class="str">"cmt">// 设置窗口属性
   class="type">void                SetWin(class="type">class="kw">string m_name,
                              class="type">int m_xdelta,
                              class="type">int m_ydelta,
                              class="type">int m_bsize,
                              class="type">int m_corner);
   class="kw">virtual     class=class="str">"cmt">// 绘制窗口的方法
   class="type">void                Draw(class="type">int &MMint[][class="num">3],
                            class="type">class="kw">string &MMstr[][class="num">3],
                            class="type">int count);
   class="kw">virtual     class=class="str">"cmt">// OnEventTick句柄
   class="type">void                OnEventTick();
   class="kw">virtual     class=class="str">"cmt">// OnChart 事件处理方法

常见问题

按职责把EA拆成事件、界面、策略等独立对象类,改哪块只动对应类,降低耦合。先把图表事件接进自定义类,再逐步抽基类。
在自定义类里重写图表事件处理方法,把OnChartEvent转派给你的对象实例。这样点击、拖拽都能映射成类内部调用。
小布可以读取你的EA工程结构,标出耦合过高的类并给出拆分示意。把重复劳动交给小布,你专注决策。
先搭一个界面对象基类骨架,管坐标、显隐和销毁,再派生按钮、窗口类。四步落点:定义基类、继承窗口类、挂事件、入工程。
注意命名空间和全局对象生命周期,避免和原EA的OnInit/OnDeinit重复注册。先独立编译窗口类再include进主文件。