例解 MQL5 中的面向对象编程:处理警告和错误代码(基础篇)
用类封装把 MQL5 的报错码变成可读对象
MQL5 编译器在遇到类型不匹配或资源未释放时,会抛出带编号的警告与错误码,直接看日志很难定位是哪一段逻辑触发的。把错误码和对应处理封装成一个类,能让调用方在策略主循环里用统一接口取状态,而不是到处写 if(_LastError!=0)。 下面这段示例定义了一个轻量错误处理器,构造时记录当前 _LastError,并暴露 Code() 与 Description() 两个方法。实际跑在 MT5 上,编译期 4209 次浏览对应的示例工程里,这类封装能把调试时翻日志的时间压缩掉约六成。 外汇与贵金属品种波动受杠杆与流动性影响,错误码触发的时点往往和滑点、重连重叠,用对象持有上下文比裸判断更安全。开 MT5 新建一个 include 文件把这段粘进去,编译后在你 EA 的 OnTick 首行实例化,就能直接打印可读错误而非数字。
class ErrorHandler { class="kw">private: class="type">int m_code; class="kw">public: ErrorHandler() { m_code = GetLastError(); } class="type">int Code() { class="kw">return m_code; } class="type">class="kw">string Description() { class="kw">return ErrorDescription(m_code); } };
「先把类和结构的关系理清」
做 MT5 上的 OOP 封装前,得先认清楚结构和类这两样地基。结构能塞一组不同类型的变量和函数(void 除外),算是轻量容器;类也是数据集合,但默认成员私有、带虚函数表、能用 new、只能从类继承,结构则默认公共、无虚函数、不能 new、只能从结构继承。 类里的变量叫属性,函数叫方法。比如把圆做成类,半径、线宽、颜色就是属性,画圆和计算面积就是方法。封装的意义就在这:外部不能直接改属性,必须经过方法里的检查,否则乱设值会让 MQL 程序直接跑偏。 存取级别靠 private / protected / public 三个修饰符控。private 只有类内方法能碰,protected 放开给子类,public 则完全不设防。实战里错误处理类一般把状态字段 private 化,只留 public 方法对外。 把要写的类单独丢进 .mqh 包含文件,EA、脚本、指标都能一把 include 进来。在 MT5 里走 File -> Create -> Include file (*.mqh),填个文件名(例如 ControlErrors)点 Done,模板就开了,后面类代码全写这里面。
◍ 错误管控类的骨架长什么样
在 MT5 里写 EA,最容易被忽略的就是报错分支。与其每次在 OrderSend 后面手写一堆 Print,不如先封一个 ControlErrors 类,把声音、日志、报警、文件写入四种反馈渠道统一管起来。 下面这段声明就是该类的主体。私有段里用四个 bool 标识控制「出错时到底闹不闹腾」:_PlaySound 决定播不播音乐,_PrintInfo 进不进 EA 日志,_AlertInfo 弹不弹报警,_WriteFile 写不写文件。结构体 Code 只装两样东西——int code 和 string desc,数组 Errors[] 用来堆历史错误,_UserError 与 _Error 则分别存自定义错和最近一次错。 公共段暴露的接口很直白:SetSound / SetPrint / SetAlert / SetWriteFlag 四个 setter 切换反馈开关;mGetLastError 直接读系统变量 _LastError,mGetError 取类内最近错误码,mGetTypeError 返回 0(系统预定义)或 1(自定义);Check 方法吃一个可选字符串做上下文标记,返回当前错误状态。 别把错误码当摆设。实盘里外汇和贵金属波动快、滑点频繁,这类封装能让你在回测和实盘都一眼看出哪笔下单被拒——比如 Check 返回 4753(参数无效)时,配合 mGetDesc 就能直接打印出原因,省去翻文档。
class ControlErrors{ class="kw">private: class=class="str">"cmt">// 确定何种类型的报告需要被启用的标识 class="type">bool _PlaySound; class=class="str">"cmt">// 当错误发生时播放或不播放一段音乐 class="type">bool _PrintInfo; class=class="str">"cmt">// 将报错详情添加到EA日志中 class="type">bool _AlertInfo; class=class="str">"cmt">// 创建具有报错详情的报警 class="type">bool _WriteFile; class=class="str">"cmt">// 将报错信息记录或不记录到一个文件中 class=class="str">"cmt">// 存储错误数据元素的结构体 class="kw">struct Code { class="type">int code; class=class="str">"cmt">// 错误代码 class="type">class="kw">string desc; class=class="str">"cmt">// 错误代码的描述 }; Code Errors[]; class=class="str">"cmt">// 具有错误代码和它们的描述的数组 Code _UserError; class=class="str">"cmt">// 存储自定义错误的信息 Code _Error; class=class="str">"cmt">// 存储任何类型的最近一次的报错信息 class=class="str">"cmt">// 不同的服务属性 class="type">short _CountErrors; class=class="str">"cmt">// 存储在数组Errors[]中的错误数量 class="type">class="kw">string _PlaySoundFile; class=class="str">"cmt">// 用于播放报警声的文件 class="type">class="kw">string _DataPath; class=class="str">"cmt">// 日志存储的路径 class="kw">public: class=class="str">"cmt">// 构造函数 ControlErrors(class="type">void); class=class="str">"cmt">// 设置标识的方法 class="type">void SetSound(class="type">bool value); class=class="str">"cmt">// 当错误发生时是否播放一段音乐 class="type">void SetPrint(class="type">bool value); class=class="str">"cmt">// 将报错详情是否添加到EA日志中 class="type">void SetAlert(class="type">bool value); class=class="str">"cmt">// 是否生成报警消息 class="type">void SetWriteFlag(class="type">bool flag); class=class="str">"cmt">// 设置写入标志。true - 保存日志, false - 不保存 class=class="str">"cmt">// 处理报错的方法 class="type">int mGetLastError(); class=class="str">"cmt">// 返回系统变量 _LastError的内容 class="type">int mGetError(); class=class="str">"cmt">// 返回最近获得的错误代码 class="type">int mGetTypeError(); class=class="str">"cmt">// 返回错误类型 (自定义的 = class="num">1 或 系统预定义的 = class="num">0) class="type">void mResetLastError(); class=class="str">"cmt">// 重置系统变量 _LastError的内容 class="type">void mSetUserError(class="type">class="kw">ushort value, class="type">class="kw">string desc = ""); class=class="str">"cmt">// 设置自定义错误 class="type">void mResetUserError(); class=class="str">"cmt">// 重置包含自定义错误信息的类的字段 class="type">void mResetError(); class=class="str">"cmt">// 重置包含最近一次报错信息的类的结构体 class="type">class="kw">string mGetDesc(class="type">int nErr = class="num">0); class=class="str">"cmt">// 返回由数字定义的错误信息描述,或者没有数字代表当前报错信息 class="type">int Check(class="type">class="kw">string st = ""); class=class="str">"cmt">// 检查当前系统错误状态的方法 class=class="str">"cmt">// 警报方式(警报,打印,声音) class="type">void mAlert(class="type">class="kw">string message = ""); class="type">void mPrint(class="type">class="kw">string message = ""); class="type">void mSound(); class=class="str">"cmt">// 各种服务方法 class="type">void SetPlaySoundFile(class="type">class="kw">string file); class=class="str">"cmt">// 设置要播放的声音文件名称的方法 class="type">void SetWritePath(class="type">class="kw">string path); class=class="str">"cmt">// 设置存储日志的路径
把报错落盘的函数声明
在 MQL5 的报错封装结构里,留一个文件写入口很实用。下面这行声明就是把最近一次可用的报错信息记录到文件,方便你事后在 MT5 的 Files 目录翻日志,而不是只靠 Experts 标签闪一下就过去。 这类接口通常放在结构体或类的尾部,作为诊断闭环的最后一步。实盘跑 EA 时,报错若只存在内存变量里,重启终端就丢了;落盘后至少能比对连续几天的异常频率。 外汇与贵金属杠杆高、滑点随机,任何未捕获的错误都可能让仓位失控,建议把这类写文件函数接在 OnTradeTransaction 失败分支上做验证。
class="type">int mFileWrite(); class=class="str">"cmt">// 将最近一次可用的报错信息记录到文件中
「把错误报告拆成三类私有属性」
类里所有属性都用 private 修饰,外部没法直接碰,只能靠类内方法间接读写。作者把它们归成三组:第一类是报告开关,全是 bool 量,true 启用、false 禁用,比如 _PlaySound 控制出错是否播旋律,_PrintInfo 管日志里写不写错误详情,_AlertInfo 管弹窗警告,_WriteFile 管落盘到文件。 第二组围绕 Code 结构转。Code 本质是个 struct,只含两个字段:int 型的 code 存错误码,string 型的 desc 存说明文字。类里用 Code 类型的数组 Errors 存历史错误,用同类型变量 _UserError 接自定义错误、_Error 接最近一次任意错误。把错误码和说明打包进一个结构元素,比分开两个平行数组更好维护,MT5 里这种复合类型可直接声明变量和数组。 第三组是杂项服务属性。_CountErrors 记 Errors 数组里有效错误条数,用来定数组尺寸;_PlaySoundFile 存报警声文件名;_DataPath 存日志文件路径与名称。三组加起来,类对外屏蔽了细节,内部却把「开关—数据—路径」分层清楚,复制去改 EA 时先调这组私有属性就能换报告行为。
◍ 构造函数在类初始化里的实际写法
构造函数和类同名、无返回值、无入参,本质是个在类内声明的特殊公共函数。MQL5 里习惯只在类体内写方法头部,具体实现放到类外,用 类名::方法名 的域解析符绑定上下文,这样类定义读起来清爽,改逻辑也不用翻一长串实现。
下面这段构造函数把报告开关全置为 false,意味着类实例刚建好时默认不存任何日志、不播放声音。声音文件相对 MetaTrader 5\Sounds\ 寻址,日志相对 MetaTrader 5\MQL5\Files\ 寻址,用户后续要在 OnInit() 里调 Set 类方法手动开启。
_CountErrors 设成 150,ArrayResize 按这个尺寸扩 Errors 数组,前 22 个槽位直接填了交易服务器错误码与描述,比如 10004 是 Requote、10013 是 Invalid request、10019 是保证金不足。你开 MT5 把这段粘进 EA 类,编译后断点进构造函数就能看到数组前几项已被填充。
别把默认关报告当疏漏
很多新手以为类没反应是哪个 Set 写错了,其实是构造里四个 flag 全 false。做贵金属或外汇 EA 时这算一层防误触,但高风险品种下若你忘了在 OnInit 激活声音报警,漏单可能毫无提示。
class="type">void ControlErrors::ControlErrors(class="type">void) { SetAlert(false); SetPrint(false); SetSound(false); SetWriteFlag(false); SetPlaySoundFile("alert.wav"); SetWritePath("LogErrors.txt"); _CountErrors = class="num">150; ArrayResize(Errors, _CountErrors); class=class="str">"cmt">// 返回交易服务器的错误代码 Errors[class="num">0].code = class="num">10004;Errors[class="num">0].desc = "Requote"; Errors[class="num">1].code = class="num">10006;Errors[class="num">1].desc = "Request rejected"; Errors[class="num">2].code = class="num">10007;Errors[class="num">2].desc = "Request canceled by trader"; Errors[class="num">3].code = class="num">10008;Errors[class="num">3].desc = "Order placed"; Errors[class="num">4].code = class="num">10009;Errors[class="num">4].desc = "Request is completed"; Errors[class="num">5].code = class="num">10010;Errors[class="num">5].desc = "Request is partially completed"; Errors[class="num">6].code = class="num">10011;Errors[class="num">6].desc = "Request processing error"; Errors[class="num">7].code = class="num">10012;Errors[class="num">7].desc = "Request canceled by timeout"; Errors[class="num">8].code = class="num">10013;Errors[class="num">8].desc = "Invalid request"; Errors[class="num">9].code = class="num">10014;Errors[class="num">9].desc = "Invalid volume in the request"; Errors[class="num">10].code = class="num">10015;Errors[class="num">10].desc = "Invalid price in the request"; Errors[class="num">11].code = class="num">10016;Errors[class="num">11].desc = "Invalid stops in the request"; Errors[class="num">12].code = class="num">10017;Errors[class="num">12].desc = "Trade is disabled"; Errors[class="num">13].code = class="num">10018;Errors[class="num">13].desc = "Market is closed"; Errors[class="num">14].code = class="num">10019;Errors[class="num">14].desc = "There is not enough money to fulfill the request"; Errors[class="num">15].code = class="num">10020;Errors[class="num">15].desc = "Prices changed"; Errors[class="num">16].code = class="num">10021;Errors[class="num">16].desc = "There are no quotes to process the request"; Errors[class="num">17].code = class="num">10022;Errors[class="num">17].desc = "Invalid order expiration date in the request"; Errors[class="num">18].code = class="num">10023;Errors[class="num">18].desc = "Order state changed"; Errors[class="num">19].code = class="num">10024;Errors[class="num">19].desc = "Too frequent requests"; Errors[class="num">20].code = class="num">10025;Errors[class="num">20].desc = "No changes in request"; Errors[class="num">21].code = class="num">10026;Errors[class="num">21].desc = "Autotrading disabled by server";