从基础到中级:变量 (III):·进阶篇
📘

从基础到中级:变量 (III):·进阶篇

第 2/2 篇

用脚本看清错误码的生命周期

MT5 里错误码不是一次性提示,而是会被后续调用覆盖的状态变量。写个最简单的脚本,连续改几次就能看清楚它的流转规律。 下面这段 MQL5 脚本在 OnStart 里做了四次打印:先读默认错误码,再手动设用户错误 2,清回 0,最后 ResetLastError 再读。跑完你会在专家日志里看到 0、2、0、0 的变化序列。 [CODE] //+------------------------------------------------------------------+ #property copyright "Daniel Jose" //+------------------------------------------------------------------+ void OnStart(void) { Print(GetLastError()); SetUserError(2); Print(GetLastError()); SetUserError(0); Print(GetLastError()); ResetLastError(); Print(GetLastError()); } //+------------------------------------------------------------------+ [/CODE] 逐行拆解:第 6 行 Print(GetLastError()) 打印初始错误码,未出错时为 0;第 7 行 SetUserError(2) 把错误码强制写成用户自定义错误 2;第 8 行再打印就显示 2。第 9 行 SetUserError(0) 把错误码归零,第 10 行打印为 0;第 11 行 ResetLastError() 清空错误状态,第 12 行打印仍为 0。 外汇和贵金属交易里调用订单函数后务必立刻读 GetLastError,否则下一次系统调用可能覆盖掉真实故障码,排查会偏方向。开 MT5 新建脚本贴入上面代码,跑一遍比看文档直观。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart(class="type">void)
{
   Print(GetLastError());
   SetUserError(class="num">2);
   Print(GetLastError());
   SetUserError(class="num">0);
   Print(GetLastError());
   ResetLastError();
   Print(GetLastError());
}
class=class="str">"cmt">//+------------------------------------------------------------------+

「INIT_SUCCEEDED 背后藏着 0 而不是 true」

MT5 指标模板里几乎必有的一行是 return INIT_SUCCEEDED;,乍看像在返回一个成功状态,实际它只是别名。文档里 INIT_SUCCEEDED 对应的底层值是 0,而布尔逻辑中 0 即 false。 若把 OnInit 末尾改成 return true;,指标会在初始化后立即终止,终端打印一条非预期消息。因为系统判定返回的非零值不等于预期的 0,视为初始化未通过。 想让指标持续运行,可写 return 0;return false;,并在 OnDeinit 里 Print(reason) 观察卸载原因;这与用别名效果完全一致。 别把别名当万能标签 极端写法是在 OnInit 返回前用 GetLastError 做校验:若中途 _LastError 非零就返回该值。这种做法很少见,因为错误可能来自价格交互而非代码本身,_LastError 会莫名其妙非零,反而让调试更乱。外汇与贵金属 EA 开发高风险,这类隐性错误可能让策略在实盘偶发失效。 想反推 SetUserError 写了什么,用 _LastError - ERR_USER_ERROR_FIRST 即可拿到自定义错误号。下面代码跑完会在终端给出三个值,第二、第三个才是校正后的用户错误值。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
{
   class="kw">return INIT_SUCCEEDED;
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">int begin, const class="type">class="kw">double &price[])
{
   class="kw">return rates_total;
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
{
   class="kw">return true;
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">int begin, const class="type">class="kw">double &price[])
{
   class="kw">return rates_total;
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
{
   Print(reason);
};
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 事件函数骨架与自定义错误读取

这段 MT5 脚本把三类最基础的生命周期函数都摆了出来:指标用的 OnInit / OnCalculate / OnDeinit,以及脚本用的 OnStart。注意前两处 OnCalculate 都直接 return rates_total,意味着指标不实际计算缓冲区,只是满足编译器对自定义指标结构的最低要求。 OnInit 里返回的是 GetLastError() 而非 ERR_SUCCESS,这和外挂 EA 常见的初始化成功返回 0 不同——若初始化阶段无错误,GetLastError 返回 0,函数也就顺利结束;一旦之前有报错,就会把错误码透出来。 OnStart 这一段最能说明自定义错误的玩法:先打印系统错误,再用 SetUserError(2) 写入用户错误,紧接着两次 GetLastError 分别拿到 65538(即 ERR_USER_ERROR_FIRST + 2)和减去基准值后的 2。ERR_USER_ERROR_FIRST 在 MQL5 里固定为 65536,所以用户错误码区间从 65536 起算。 开 MT5 新建脚本粘入下方代码,跑一次就能在专家日志看到 0、65538、2 三行输出,比看文档直观。外汇与贵金属品种叠加这类脚本测试时波动随机,仅作机制验证,实盘高风险。

MQL5 / C++
class="macro">#class="kw">property copyright "Daniel Jose"
class="type">int OnInit()
{
   class="kw">return GetLastError();
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">int begin, const class="type">class="kw">double &price[])
{
   class="kw">return rates_total;
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
{
    Print(reason);
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class="type">void OnStart(class="type">void)
{
    Print(GetLastError());
    SetUserError(class="num">2);
    Print(GetLastError());
    Print(GetLastError() - ERR_USER_ERROR_FIRST);
}

画得少,看得清

在 SetUserError 里塞负值不会如预期那样映射到 MQL5 定义的错误区间,根子在它接收的数据类型只认非负用户错误码(4000+ 段),负参被静默吞掉,调试时容易误以为自定义异常生效了。 如果真要标记特殊分支,老老实实走 4000 以上的空闲码,别拿负数碰运气。附件 ZIP 里那几段示例(2.11 KB)能直接拖进 MT5 跑,对照本文现象自己验证一遍比看十遍顺。 外汇和贵金属杠杆高、滑点狠,任何自定义错误封装都只是辅助定位,不能替你挡实盘风险。代码跑通了,下一步该想的是怎么让小布这类 AIGC 工具替你盯这些异常分支。

常见问题

用一段脚本把 OnInit 的返回值直接打印到日志,重点看 INIT_SUCCEEDED 实际是 0 而不是 true,对照文档就能定位问题。
可以,在事件函数里用自定义变量接住错误码,再在 OnDeinit 或脚本里输出,就能分离系统错误和你自己抛的错。
小布能直接读取品种页里的运行诊断,把初始化状态和错误码用中文列出来,你打开对应页面就能看到,不用手跑脚本。
只保留关键价位和一两条件线,用最少对象表达逻辑,回看历史时干扰少、信号更清楚。
在脚本里给自定义错误单独前缀并打印触发时间,和平台错误码分开输出,就能看清各自从产生到清除的链路。