例解 MQL5 中的面向对象编程:处理警告和错误代码·综合运用
📘

例解 MQL5 中的面向对象编程:处理警告和错误代码·综合运用

第 3/3 篇

「错误代码怎么变回人话」

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"(原文拼写如此),真要上生产建议顺手改掉这个拼写。

MQL5 / C++
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,避免连续报错时吵到无法判断。外汇与贵金属波动剧烈,这类提示只作辅助,不代表任何方向确定性。

MQL5 / C++
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() 是全局态,拖到几行后才查就可能被别的操作冲掉,这个类的顺序调用恰好堵住了这个坑。外汇与贵金属杠杆高,错误静默跳过可能直接变成穿仓,自动化脚本里这类显式检查不是可选项。

MQL5 / C++
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_READFILE_WRITEFILE_TXTFILE_ANSI 一起用,意味着打开非空 txt 并单字节写入,中文环境不会乱码。

打开后先判 handle != INVALID_HANDLE,失败直接 return 0;成功才走 FileSize 取大小、FileSeek 偏移到末尾、FileWrite 写一行、FileClose 收尾。FileWrite 最多吞 63 个参数,但日志场景一行 text 足够。 下面这段是原生实现,逐行拆完你就能直接抄进 EA 的错误处理类。handle 和 _return 初始化为 0;time 取 TimeCurrent();text 用三元判断决定写自定义消息还是自动错误串。FileOpen 拿不到句柄就跳过写盘,避免阻塞主逻辑。

MQL5 / C++
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() 不合用的时候,类里的单个方法也能拼出你自己的处理流。这类封装说到底只是少写几行重复代码的手段,外汇和贵金属杠杆高、滑点狠,真遇上运行时崩溃它救不了你,该做的防御还得自己在逻辑里写。

MQL5 / C++
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();

常见问题

用ErrorDescription()把错误码转成字符串,或在自定义映射表里按码查中文短语,打印前先转译再输出。
通常三个出口:Print()落终端日志、Alert()或MessageBox弹窗、SendNotification()推手机,按严重程度选其一或组合。
小布可接管日志采集与摘要,把报错码和对应指标快照聚合成每日诊断,你只需打开品种页查看。
在Check()里统一调GetLastError并return布尔,主流程每步用if(!Check())return,错误即中断并留痕。
在错误分支用FileOpen/FileWrite把时间、错误码、指标值追加到csv,注意用FILE_ANSI和FILE_WRITE|FILE_APPEND。