例解 MQL5 中的面向对象编程:处理警告和错误代码·进阶篇
📘

例解 MQL5 中的面向对象编程:处理警告和错误代码·进阶篇

第 2/3 篇

EA 报错码与静默日志的初始装配

在 MT5 的 EA 错误映射表里,索引 22 到 25 覆盖的是交易通道类故障:10027 代表终端禁用了自动交易,10028 是请求被锁在处理队列,10029 表示订单或持仓被冻结,10030 则是订单填充类型不被允许。碰到这类码,EA 多半连单都下不出去,外汇和贵金属品种在高波动时更容易触发,属于典型的高风险环境信号。 从索引 26 开始是内部运行时错误,4001 到 4014 共 14 条,包含内存不足(4004/4007/4008)、动态数组损坏(4006/4007)、指针类型错误(4012/4013)以及系统函数调用受限(4014)。其中 4011 明确标注请求数组尺寸超过 2 GB,这种边界在回测大周期 tick 数据时可能撞上。 代码末尾那一组 Set 调用把提示、打印、声音、写盘标志全置为 false,只保留写路径指向 LogErrors.txt 和播放 alert.wav。等于说这套初始化只干一件事:把错误码落盘,不吵人。开 MT5 把这段塞进 OnInit 前面,跑一遍就能在终端目录下翻到错误日志。

MQL5 / C++
Errors[class="num">22].code = class="num">10027;Errors[class="num">22].desc = "Autotrading disabled by client terminal";
Errors[class="num">23].code = class="num">10028;Errors[class="num">23].desc = "Request locked for processing";
Errors[class="num">24].code = class="num">10029;Errors[class="num">24].desc = "Order or position frozen";
Errors[class="num">25].code = class="num">10030;Errors[class="num">25].desc = "Invalid order filling type";
class=class="str">"cmt">// 常见错误
Errors[class="num">26].code = class="num">4001;Errors[class="num">26].desc = "Unexpected internal error";
Errors[class="num">27].code = class="num">4002;Errors[class="num">27].desc = "Wrong parameter in the inner call of the client terminal function";
Errors[class="num">28].code = class="num">4003;Errors[class="num">28].desc = "Wrong parameter when calling the system function";
Errors[class="num">29].code = class="num">4004;Errors[class="num">29].desc = "Not enough memory to perform the system function";
Errors[class="num">30].code = class="num">4005;Errors[class="num">30].desc = "The structure contains objects of strings and/or dynamic arrays and/or structure of such objects and/or classes";
Errors[class="num">31].code = class="num">4006;Errors[class="num">31].desc = "Array of a wrong type, wrong size, or a damaged object of a dynamic array";
Errors[class="num">32].code = class="num">4007;Errors[class="num">32].desc = "Not enough memory for the relocation of an array, or an attempt to change the size of a class="kw">static array";
Errors[class="num">33].code = class="num">4008;Errors[class="num">33].desc = "Not enough memory for the relocation of class="type">class="kw">string";
Errors[class="num">34].code = class="num">4009;Errors[class="num">34].desc = "Not initialized class="type">class="kw">string";
Errors[class="num">35].code = class="num">4010;Errors[class="num">35].desc = "Invalid date and/or time";
Errors[class="num">36].code = class="num">4011;Errors[class="num">36].desc = "Requested array size exceeds class="num">2 GB";
Errors[class="num">37].code = class="num">4012;Errors[class="num">37].desc = "Wrong pointer";
Errors[class="num">38].code = class="num">4013;Errors[class="num">38].desc = "Wrong type of pointer";
Errors[class="num">39].code = class="num">4014;Errors[class="num">39].desc = "System function is not allowed to call";
}
SetAlert(false);
SetPrint(false);
SetSound(false);
SetWriteFlag(false);
SetPlaySoundFile("alert.wav");
SetWritePath("LogErrors.txt");

「错误控制的标记与文件开关怎么挂」

在 MT5 自定义类里,标记设置方法本质上只是把外部传入的参数写进类内部属性,没有复杂逻辑。标记位只用布尔值,非 0 即 1,因此构造函数之后直接赋值即可,不需要做范围校验。 文件名和路径则不同,赋值前必须检查字符串合法性,否则日志写不进去或声音播不出来时很难排查。下面这段就是 ControlErrors 类的六个 setter 实现,分别对应弹窗、打印、播音、写文件开关、写路径与声音文件名的设定。 调用时通常在初始化阶段一次性设好,例如 SetWritePath(_Symbol+"\\logs\") 把品种日志隔离存放。外汇与贵金属波动剧烈,这类错误捕获开关建议默认全开,便于复盘滑点与时延异常。

MQL5 / C++
class="type">void ControlErrors::SetAlert(class="type">bool value)
{
  _AlertInfo = value;
}
class="type">void ControlErrors::SetPrint(class="type">bool value)
{
  _PrintInfo = value;
}
class="type">void ControlErrors::SetSound(class="type">bool value)
{
  _PlaySound = value;
}
class="type">void ControlErrors::SetWriteFlag(class="type">bool flag)
{
  _WriteFile = flag;
}
class="type">void ControlErrors::SetWritePath(class="type">class="kw">string path)
{
  _DataPath = path;
}
class="type">void ControlErrors::SetPlaySoundFile(class="type">class="kw">string file)
{
  _PlaySoundFile = file;
}
type Class_name::Function_Name(parameters_description)
{
  class=class="str">"cmt">// 函数体
}

◍ 把错误码缓存下来而不是反复查

在 MT5 的 EA 或脚本里直接调 GetLastError() 有个坑:每次调用都会清除内部错误状态,连续取说明时容易拿到 0。ControlErrors 类用 mGetLastError() 把这次错误码和对应描述一次性存进 _Error,后面要展示或写日志直接读 _Error 即可,不必再碰 GetLastError()。 mResetLastError() 则只是原样包了一层 ResetLastError(),用于在下一段逻辑前清空错误标记。两者配合,可以在一次 tick 内稳定拿到「代码 + 中文/英文描述」而不被系统清掉。 下面这段是类的两个方法本体,注意 _Error.desc 来自 mGetDesc(_Error.code) 的查表,不是系统直接给的字符串: int ControlErrors::mGetLastError(void) { _Error.code = GetLastError(); _Error.desc = mGetDesc(_Error.code); return _Error.code; } void ControlErrors::mResetLastError(void) { ResetLastError(); } 实盘或回测里若发现报错后日志空白,优先检查是不是在 GetLastError() 和取描述之间插了别的函数调用;用上面的缓存写法能规避这类竞态。外汇与贵金属波动大、滑点频繁,错误码监控只是风控一环,不代表信号可靠。

MQL5 / C++
class="type">int ControlErrors::mGetLastError(class="type">void)
{
  _Error.code = GetLastError();
  _Error.desc = mGetDesc(_Error.code);
  class="kw">return _Error.code;
}
class="type">void ControlErrors::mResetLastError(class="type">void)
{
  ResetLastError();
}

复位与读取错误状态的两个入口

在 MT5 的错误处理封装里,最后一条错误消息不会自动消失,得靠两个方法主动干预。mGetError() 只做一件事:把内部 _Error 结构里的 code 字段原样返回,调用方据此判断上一次失败类型。 mResetError() 负责清场,把 _Error.code 置 0、_Error.desc 置空串,相当于告诉后续逻辑“暂无未处理错误”。若不清零,旧错误码可能误导下一次判断。 下面这段是类的两个方法实现,直接可粘进 MT5 的 ControlErrors 类里编译验证: int ControlErrors::mGetError(void) { return _Error.code; } void ControlErrors::mResetError(void) { _Error.code = 0; _Error.desc = ""; } 逐行拆解:第 1 行定义返回 int 的 mGetError,无参;第 3 行直接 return _Error.code,无额外加工。第 5 行 mResetError 无返回值;第 7 行把错误码归零;第 8 行把描述清空,两者共同完成状态复位。外汇与贵金属 EA 调用后务必确认返回值,此类品种波动剧烈、滑点风险高,错误状态误判可能放大亏损概率。

MQL5 / C++
class="type">int ControlErrors::mGetError(class="type">void)
{
  class="kw">return _Error.code;
}
class="type">void ControlErrors::mResetError(class="type">void)
{
  _Error.code = class="num">0;
  _Error.desc = "";
}

「区分自定义与系统错误代码边界」

在 MT5 的错误处理链里,先要判断刚抛出的错误到底是平台预定义的,还是你自己在 EA 里定义的。系统常量 ERR_USER_ERROR_FIRST 的值是 65536,所有自定义错误码都从这个数起步,这是个硬分界。 mGetTypeError() 干的事很直接:调 mGetError() 拿最近一次错误码,跟 65536 比大小。小于它返回 0,说明是预定义错误;大于等于它返回 1,就是自定义错误;理论上两个分支没覆盖的情况返回 -1。 下面这段代码就是该方法的完整实现,逐行看逻辑比读说明更快: int ControlErrors::mGetTypeError(void) 判断函数无入参,返回 int 类型错误类别 if (mGetError() < ERR_USER_ERROR_FIRST) 若最新错误码小于 65536 return 0; 归为预定义错误 else if (mGetError() >= ERR_USER_ERROR_FIRST) 否则错误码达到自定义起点 return 1; 归为自定义错误 return -1; 兜底返回,正常流程走不到这 开 MT5 新建个 ControlErrors 类把这段贴进去,故意用 65535 和 65536 各触发一次,就能确认你的错误分流是否生效。外汇与贵金属杠杆高,错误分类不准可能导致止损逻辑误判,实盘前务必在策略测试器跑通。

MQL5 / C++
class="type">int ControlErrors::mGetTypeError(class="type">void)
{
   if (mGetError() < ERR_USER_ERROR_FIRST)
   {
      class="kw">return class="num">0;
   }
   else if (mGetError() >= ERR_USER_ERROR_FIRST)
   {
      class="kw">return class="num">1;
   }
   class="kw">return -class="num">1;
}

◍ 给自己的 EA 留一手自定义报错

MQL5 允许你在类里挂一个 _UserError 属性,专门存自己拍脑袋定义的错误码和说明,系统错误和你的业务异常从此分两条线看。 mSetUserError() 干了两件事:先调 SetUserError(value),把内部变量 _LastError 推到 ERR_USER_ERROR_FIRST + value 这个区间(用户错误码从 65536 起算,即 ERR_USER_ERROR_FIRST 的值),再把 value 和 desc 写进 _UserError 结构体。这样后面查错时,既能用标准 _LastError 判断,也能直接读你自己的可读说明。 mResetUserError() 只清 _UserError 的 code 和 desc,不会碰 _LastError。要复位系统错误位,得单独调 mResetLastError()。在外汇/贵金属 EA 里滥用系统错误位容易掩盖真实故障,自定义错误隔离后能降低误判概率,但杠杆交易本身高风险,错误兜底不等于策略安全。 下面这段是类方法原样,逐行拆给你看怎么落: void ControlErrors::mSetUserError(ushort value, string desc = "") —— 定义方法,value 是用户错误码,desc 可选说明 { SetUserError(value); —— 把 _LastError 设为 ERR_USER_ERROR_FIRST + value _UserError.code = value; —— 记下原始码 _UserError.desc = desc; —— 存说明文本 } void ControlErrors::mResetUserError(void) —— 复位方法,无参 { _UserError.code = 0; —— 清码 _UserError.desc = ""; —— 清说明 } 开 MT5 建个 ControlErrors 类,把这两段贴进去,随便抛个 1 号自定义错,再用 Print(_UserError.desc) 验证隔离效果。

MQL5 / C++
class="type">void ControlErrors::mSetUserError(class="type">class="kw">ushort value, class="type">class="kw">string desc = "")
{
  SetUserError(value);
  _UserError.code = value;
  _UserError.desc = desc;
}
class="type">void ControlErrors::mResetUserError(class="type">void)
{
  _UserError.code = class="num">0;
  _UserError.desc = "";
}

常见问题

先看日志里有没有错误码输出,若日志被静默就检查文件开关是否关掉;把错误码缓存下来比反复查系统更稳。
在自定义错误代码段单独划区间,别和系统码重叠;复位与读取用两个独立入口,避免状态互相覆盖。
小布可以替你跑这套错误诊断,打开对应品种页就能看到缓存的错误码和开关状态,不用自己翻文件。
可能你复位和读取用了同一个入口;按规范拆成两个函数,复位完再单独调读取入口才准。
大概率是错误控制的标记没挂到文件开关上;检查初始装配里静默日志的开关变量有没有正确传参。