查找错误和记录(基础篇)
终端日志里藏着的报错线索
MT5 的「专家」标签不会替你解释逻辑错误,它只把执行期的异常原样吐出来。很多策略在回测里跑得顺,一上实盘就沉默,往往不是行情问题,而是 OnInit 或 OnTick 里某行句柄没拿到。 打开 MT5 终端窗口切到「专家」页,时间戳精确到秒,例如一条记录写着「9 十二月 2013, 13:04」并伴随错误码,那就是当时 EA 初始化的现场。老手习惯把这类日志当第一现场,而不是等账户浮亏才反查。 注意外汇与贵金属品种波动剧烈,同一段代码在低点差时段能过、在新闻秒能崩,日志里的报错概率随流动性变化而变,别把一次通过当成永远安全。
「三种查错路子先掰开看」
在 MT5 里写 EA、脚本或指标,查错逃不开三条路:看最终结果、逐步调试、写日志。前两条能定位问题但各有短板,日志法才是日常最省时间的手段。 拿一段只有一行明显错的代码说事:声明了 10 个元素的数组,循环却只写到 i<9,最后 Alert 第 9 号元素。编译跑完弹窗显示 0,而预期是 9,说明程序没按想的走。这种「看结果」法最快,但完全不知道错在哪一行。 逐步调试就精准了。在 MetaEditor 里给 for 循环打断点,盯着 Watch 里的 i:走到 i=8 就跳出循环,漏掉了给 intArray[9] 赋值,所以读出来是 0。把条件改成 i<10 或 i<=9,再跑一次得到 9。缺点是得手动加观察变量、一步步点,遇到真盘 EA 的异步错误根本没法复现。 日志法是在关键步骤打印。给上面的脚本每轮循环加一句 Alert(i),跑完日志打出「0 1 2 3 4 5 6 7 8 0」,最后那个 0 立刻暴露第 9 位没赋值。不用单步、不用碰运气,还能存文件事后比对,代价是代码变长、优化时稍拖速度。 别把正态当圣经 调试能看清运行时细节,但真要在演示或真实账户上抓 EA 的偶发错误,日志才是能一直开着不打断执行的办法。
class="type">void OnStart() { class=class="str">"cmt">//--- class="type">int intArray[class="num">10]; for(class="type">int i=class="num">0;i<class="num">9;i++) { intArray[i]=i; } Alert(intArray[class="num">9]); } for(class="type">int i=class="num">0;i<class="num">9;i++) class="type">void OnStart() { class=class="str">"cmt">//--- class="type">int intArray[class="num">10]; for(class="type">int i=class="num">0;i<class="num">9;i++) { intArray[i]=i; Alert(i); } Alert(intArray[class="num">9]); }
◍ 什么时候该让 EA 自己写日志
在 MT5 里跑 EA 或脚本,不是所有情况都值得开日志,但有几类场景不开就会抓瞎。 程序出现错误行为时,日志是唯一能还原现场的证据链——比如下单返回无效句柄、数组越界,没有 Print 你只能盲猜。 做优化时记录运行耗时很关键。同一套逻辑在 1000 根 K 线和 10000 根 K 线上跑,时间复杂度差异可能差出数倍,日志能帮你定位哪段循环拖了后腿。 运行时把建仓、平仓、挂单撤销等动作打印出来,等于给策略装了个黑匣子。实盘贵金属波动大、滑点频发,这类通知能让你在爆仓前发现逻辑漏单。 学 MQL5 阶段,用 Print 把数组内容、结构体字段打出来,比单步调试更快建立直觉。 参加 EA 锦标赛前,先本地跑几天日志,确认没有隐蔽的报错或超时,否则线上一轮就被淘汰。外汇与贵金属杠杆高风险,日志只辅助排查,不预示收益。
MT5里把运行状态打出来的四种手段
在MQL5里把程序跑到的状态留痕,常用也就四招:Comment() 把字吐在图表左上角,Alert() 弹窗带声音,Print() 写进专家日志窗口,FileWrite() 则是落到自定义文件。前三种零配置就能用,后一种适合要长期归档的场合。 Comment() 最适合看“这一瞬”的变量值。比如循环里写 Comment("变量 i: ",i),左上角就跟着刷。但它有硬伤:输出字符数有限,看不到历史,只能盯当前帧,而且刷得太勤会拖慢主图。做EA状态牌(如"Open 2 deal")还行,拿来做完整回放不可能。 Alert() 会弹独立窗口并响一声,所有调用同时被记进 Terminal_dir\MQL5\Logs\date.txt。听着省事,但脚本/指标/EA全写同一个日志,没法分组;在策略测试器里根本不生效;若每个tick都调一次,终端可能卡死好几秒。实盘超短线或改止损时,这一条能要命——你以为弹了提示,其实终端冻了。 Print() 最干净:消息进Experts标签页,不弹窗不响铃,不影响图表渲染。下面这段代码把三种函数的写法并排列出,你可以直接贴进脚本比对体感。 从实测节奏看,Print() 在10次循环里零延迟返回,Alert() 带Sleep(1000)时整个脚本至少多耗10秒,Comment() 每帧重绘对CPU有隐性占用。普通监控用Print(),要肉眼警报再叠Alert(),别反过来。
class="type">void OnStart() { class=class="str">"cmt">//--- class="type">int intArray[class="num">10]; for(class="type">int i=class="num">0;i<class="num">10;i++) { intArray[i]=i; Comment("变量 i: ",i); Sleep(class="num">5000); } Alert(intArray[class="num">9]); } class="type">void OnStart() { class=class="str">"cmt">//--- Alert("启动脚本"); class="type">int intArray[class="num">10]; for(class="type">int i=class="num">0;i<class="num">10;i++) { intArray[i]=i; Alert("变量 i:", I); Sleep(class="num">1000); } Alert(intArray[class="num">9]); Alert("停止脚本"); } class="type">void OnStart() { class=class="str">"cmt">//--- Print("启动脚本"); class="type">int intArray[class="num">10]; for(class="type">int i=class="num">0;i<class="num">10;i++) { intArray[i]=i; Print("变量 i: ",i); } Print(intArray[class="num">9]); Print("停止脚本"); }
「把日志直接写进文件更可控」
前几种 MQL5 日志方案(Alert、Print 类)会把脚本、EA、指标的所有输出混进同一个 'Terminal_dir\MQL5\Logs' 下的 data.txt,查看不便且无法分组。若程序规模变大,这种交叉日志会拖慢排查效率。 最后一种思路是自己用 FileOpen 把消息写进 'Terminal_dir\MQL5\Files' 下的独立文件。跑完去那个目录用记事本开 log.txt,里面只有你主动写入的内容,没有多余系统提示。优点很明显:快、只写想要的、不同程序可落不同文件;缺点是没新消息弹窗、不能按类别标色、每次得手动进文件夹开文件。 下面这段代码是最简写法,在 MT5 里建个脚本挂上就能验证: void OnStart() { //--- 打开日志文件
| int fileHandle=FileOpen("log.txt",FILE_WRITE | FILE_TXT | FILE_SHARE_READ | FILE_UNICODE); |
|---|
FileWrite(fileHandle,"启动脚本"); int intArray[10]; for(int i=0;i<10;i++) { intArray[i]=i; FileWrite(fileHandle,"变量 i: ",i); // Sleep(1000); } FileWrite(fileHandle,intArray[9]); FileWrite(FileHandle,"停止脚本"); FileClose(fileHandle); // 关闭日志文件 } 逐行拆一下:FileOpen 以写+文本+共享读+Unicode 方式开 log.txt,拿到的句柄给后续写操作;循环里把 0~9 依次写进文件,同时塞进数组;最后写数组第 9 位和结束标记,FileClose 收尾。注意原代码里有一行 FileWrite(FileHandle,...) 大小写混用,MT5 编译器认大小写,实测会报 'FileHandle' 未声明,得改成 fileHandle 才能跑通。 哪种方式合适用法取决于程序复杂度。只想盯一个数值,Print 类更顺手;若是几百行以上的 EA,自己写文件能决定记录时机与格式,灵活性最高。外汇与贵金属杠杆高、滑点突发行情多,任何日志方案都只是辅助排查,别拿历史输出反推下一步必赢。
class="type">void OnStart() { class=class="str">"cmt">//--- 打开日志文件 class="type">int fileHandle=FileOpen("log.txt",FILE_WRITE|FILE_TXT|FILE_SHARE_READ|FILE_UNICODE); FileWrite(fileHandle,"启动脚本"); class="type">int intArray[class="num">10]; for(class="type">int i=class="num">0;i<class="num">10;i++) { intArray[i]=i; FileWrite(fileHandle,"变量 i: ",i); class=class="str">"cmt">// Sleep(class="num">1000); } FileWrite(fileHandle,intArray[class="num">9]); FileWrite(FileHandle,"停止脚本"); FileClose(fileHandle); class=class="str">"cmt">// 关闭日志文件 }