例解 MQL5 中的面向对象编程:处理警告和错误代码·综合运用
「错误代码怎么变回人话」
ControlErrors 类里有个 mGetDesc() 方法,作用是把数字错误码翻成可读说明。它带一个 int 型参数 nErr,默认填 0:调用时若给了具体码就查那个码,不给就查上次 mGetError() 记下的错。 方法开头用 ErrorNumber 承接错误码、ReturnDesc 存说明文本,两行赋值都用了 ?: 三元运算——等价于精简版 if-else。先判 mGetError() 是否大于 0,是就把返回值塞给 ErrorNumber;再判 nErr 是否大于 0,是就覆盖成 nErr。这样无论外部传参还是内部残留错码都能接住。 拿到码后分两路:小于 ERR_USER_ERROR_FIRST(即系统错)就跑 for 循环在 Errors 数组里比对 code 字段;大于该常量的自定义错,直接读 _UserError.desc。都没匹配上就回 Unknown error code: 加数字。下面逐行拆一下核心片段。 ErrorNumber = (mGetError()>0)?mGetError():ErrorNumber; —— 若内部错误码大于0,取它;否则保留初值0。 ErrorNumber = (nErr>0)?nErr:ErrorNumber; —— 若传入参数大于0,以参数为准;否则沿用上一步结果。 在 MT5 里接这段逻辑时,自定义错阈值 ERR_USER_ERROR_FIRST 是平台常量,不用自己定;但 _UserError.desc 为空时会回退成 "Cusrom error"(原文拼写如此),真要上生产建议顺手改掉这个拼写。
class="type">class="kw">string ControlErrors::mGetDesc(class="type">int nErr=class="num">0) { class="type">int ErrorNumber = class="num">0; class="type">class="kw">string ReturnDesc = ""; ErrorNumber = (mGetError()>class="num">0)?mGetError():ErrorNumber; ErrorNumber = (nErr>class="num">0)?nErr:ErrorNumber; if ((ErrorNumber > class="num">0) && (ErrorNumber < ERR_USER_ERROR_FIRST)) { for (class="type">int i = class="num">0;i<_CountErrors;i++) { if (Errors[i].code == ErrorNumber) { ReturnDesc = Errors[i].desc; break; } } } else if (ErrorNumber > ERR_USER_ERROR_FIRST) { ReturnDesc = (_UserError.desc=="")?"Cusrom error":_UserError.desc; } if (ReturnDesc == ""){class="kw">return "Unknown error code: "+(class="type">class="kw">string)ErrorNumber;} class="kw">return ReturnDesc; } ErrorNumber = (mGetError()>class="num">0)?mGetError():ErrorNumber; ErrorNumber = (nErr>class="num">0)?nErr:ErrorNumber;
◍ 错误通知的三个出口
在自写 EA 或指标里,把报错和信号提示拆成 mAlert()、mPrint()、mSound() 三个方法,逻辑最干净。三者开头都先查允许标记(_AlertInfo / _PrintInfo / _PlaySound),标记没开就直接返回,不浪费终端资源。 mAlert() 和 mPrint() 都带一个 string message 参数,默认空串。若 message 为空且内部 mGetError() 返回码大于 0,就弹标准错误框或写日志;若传了自定义 message,则优先输出你写的那句。这样同一处报错既能走系统描述也能走业务话术。 mSound() 不带参数,标记一过就直接 PlaySound(_PlaySoundFile)。实盘里建议把声音文件换成短促低频 wav,避免连续报错时吵到无法判断。外汇与贵金属波动剧烈,这类提示只作辅助,不代表任何方向确定性。
class="type">void ControlErrors::mAlert(class="type">class="kw">string message="") { if (_AlertInfo == true) { if (message == "") { if (mGetError() > class="num">0) { Alert("Error №",mGetError()," - ",mGetDesc()); } } else { Alert(message); } } } class="type">void ControlErrors::mPrint(class="type">class="kw">string message="") { if (_PrintInfo == true) { if (message == "") { if (mGetError() > class="num">0) { Print("Error №",mGetError()," - ",mGetDesc()); } } else { Print(message); } } } class="type">void ControlErrors::mSound(class="type">void) { if (_PlaySound == true) { PlaySound(_PlaySoundFile); } }
Check() 怎么把错误检查串成一条流水线
ControlErrors 类的 Check() 不干脏活,只负责按固定顺序把内部方法排好队:先取错误号,写日志,弹告警,打终端,响声音,最后清场。它接收一个 string 参数 st,这个字符串会原样透传给 mAlert() 和 mPrint(),让你在报告里带上自定义上下文,比如"EURUSD 市价单提交后"。 从代码看,函数开头 errNum 初始化为 0,随后 mGetLastError() 覆盖它为真实错误码;整条链跑完,mResetError()、mResetLastError()、mResetUserError() 三连清,确保下一根 K 线不会读到旧错。返回值是本次捕获的 errNum,调用方可以据此做分支——非 0 就跳过后续下单逻辑。 实盘里建议把 Check() 放在每个 OrderSend / PositionOpen 之后立刻跑。MT5 的 GetLastError() 是全局态,拖到几行后才查就可能被别的操作冲掉,这个类的顺序调用恰好堵住了这个坑。外汇与贵金属杠杆高,错误静默跳过可能直接变成穿仓,自动化脚本里这类显式检查不是可选项。
class="type">int ControlErrors::Check(class="type">class="kw">string st="") { class="type">int errNum = class="num">0; errNum = mGetLastError(); mFileWrite(); mAlert(st); mPrint(st); mSound(); mResetError(); mResetLastError(); mResetUserError(); class="kw">return errNum; }
「把报错和指标值落盘到文本日志」
在 MT5 里做价格行为复盘,最怕策略跑了一夜却拿不到当时的上下文。mFileWrite() 就是个轻量落盘口子:开关 _WriteFile 打开、_DataPath 路径合法,就能把任意字符串追加进本地 ANSI 文本。
| 函数入口只收一个 message 参数,不传就自动拼「时间 - Error №错误码 描述」。四个标记 FILE_READ | FILE_WRITE | FILE_TXT | FILE_ANSI 一起用,意味着打开非空 txt 并单字节写入,中文环境不会乱码。 |
|---|
打开后先判 handle != INVALID_HANDLE,失败直接 return 0;成功才走 FileSize 取大小、FileSeek 偏移到末尾、FileWrite 写一行、FileClose 收尾。FileWrite 最多吞 63 个参数,但日志场景一行 text 足够。 下面这段是原生实现,逐行拆完你就能直接抄进 EA 的错误处理类。handle 和 _return 初始化为 0;time 取 TimeCurrent();text 用三元判断决定写自定义消息还是自动错误串。FileOpen 拿不到句柄就跳过写盘,避免阻塞主逻辑。
class="type">int ControlErrors::mFileWrite(class="type">class="kw">string message = "") { class="type">int handle = class="num">0, _return = class="num">0; class="type">class="kw">datetime time = TimeCurrent(); class="type">class="kw">string text = (message != "")?message:time+" - Error №"+mGetError()+" "+mGetDesc(); if (_WriteFile == true) { handle = FileOpen(_DataPath,FILE_READ|FILE_WRITE|FILE_TXT|FILE_ANSI); if (handle != INVALID_HANDLE) { class="type">class="kw">ulong size = FileSize(handle); FileSeek(handle,size,SEEK_SET); _return = FileWrite(handle,text); FileClose(handle); } } class="kw">return _return; }
◍ 把工具请下神坛
把前面封装好的错误处理类接进 EA,第一步是在源码顶部用 #include 把 ControlErrors.mqh 挂进来,否则编译器根本不认这个类名。挂好之后,创建对象跟声明一个普通变量没两样,只是类型位置换成了类名 ControlErrors。 OnInit() 里决定你想开哪些报错通道:默认对象一建出来所有标记都是 false,也就是全禁。上面那段代码只把弹窗、打印、写文件打开,声音关掉,还顺手指定了播报用的 news.wav。若你某些条件下才想存日志,完全可以在别的地方动态改标记,不必在初始化时一次定死。 真正的捕捉放在 mControl.Check():它认出错误码、按已开的标记分别写报告,再把内部错误变量复位。Check() 不合用的时候,类里的单个方法也能拼出你自己的处理流。这类封装说到底只是少写几行重复代码的手段,外汇和贵金属杠杆高、滑点狠,真遇上运行时崩溃它救不了你,该做的防御还得自己在逻辑里写。
class="macro">#include <ControlErrors.mqh> ControlErrors mControl; class="type">int OnInit() { class=class="str">"cmt">//--- mControl.SetAlert(true); mControl.SetPrint(true); mControl.SetSound(false); mControl.SetWriteFlag(true); mControl.SetPlaySoundFile("news.wav"); class=class="str">"cmt">//--- class="kw">return(class="num">0); } mControl.Check();