傻瓜式MQL:如何设计和构建对象类(基础篇)
把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 编辑器即可编译验证事件框架是否空载通过。 外汇与贵金属杠杆高,回测通过不代表实盘能扛住滑点和断连,事件框架只是工程地基。
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 就能确认参数流向。外汇与贵金属杠杆高,这类交互逻辑只解决工程问题,不预示任何方向。
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 派生类,省得一次堆满报错无从查起。外汇与贵金属品种波动剧烈,任何界面工具只降人工成本,不预示行情方向,实盘前请充分回测。
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 把这两段贴进去,就能以它们为父类去派生你自己的行情格子。 外汇与贵金属图表上挂这类面板属于高风险操作,事件响应逻辑写错可能卡死图表,建议先在模拟账户验证。
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 能验证四个锚角贴边效果。
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 事件处理方法