我盯着MT4图表,一头雾水。刚挂上去的EA在EURUSD上跑得好好的,一切到GBPUSD就不交易了。没报错,没警告,就只是沉默。重启MT4,重新挂EA,又活了。但只要一切品种,又死了。
那时候我才意识到,我压根不了解MT4到底是怎么加载和运行程序的。我知道怎么把EA拖到图表上,但后台发生了什么,我一无所知。于是我翻了一遍MQL4文档,发现了一些完全改变了我调试方式的东西。
三种程序类型——行为各不相同
先理清楚我们面对的是什么。MT4有三种MQL4可执行程序:
每种类型的加载和卸载规则都不一样。大部分混乱就来自这里。
步骤1:EA到底是怎么加载的——不只是拖放
把EA拖到图表上,后台其实做了这么一串事:
OnInit()事件处理器,就调用它。关键点来了: 每个MQL4程序至少得有一个事件处理器函数。如果没有,程序加载了也不会执行。我见过有人花好几个小时调试一个编译通过了但图表上啥都不干的EA,结果只是忘了写
OnTick()或OnStart()。EA在以下情况也会被加载:
终端启动时,如果EA在上次关闭前已经挂在图表上。
加载包含这个EA的模板时。
切换包含这个EA的配置文件时。
一个容易忽略的点: 重新连接账户时——哪怕连的是同一个账户。如果你断开再连上账户,EA会重新加载。
步骤2:品种/周期切换——让我顿悟的那个瞬间
这就是我在EURUSD和GBPUSD之间切换时遇到的问题。根据MQL4文档,当你在挂有EA的图表上切换品种或周期时,EA不会被卸载再重新加载。
等等,什么?我看了两遍才确认。
官方文档的原话是:切换品种或周期时,终端会在旧品种/周期上调用
OnDeinit(),然后再在新品种/周期上调用OnInit()。全局变量和静态变量不会重置,它们的值会保留下来。我的独家解读: 这是功能设计,不是bug,但也是陷阱。我见过很多EA就因为假设了品种相关的数据(比如点值或小数位数)不会在运行中变化而翻车。如果你的EA在
OnInit()里初始化了品种相关的东西——比如把止损距离按点数存成固定值——从EURUSD切到GBPUSD时这个值就失效了,因为点值变了。我现在用的方法: 品种相关的值永远在
OnTick()或OnTimer()里重新计算,不要只在OnInit()里算。或者更稳妥一点,把品种名存成一个全局变量,每个报价来了都检查一下有没有变。步骤3:EA卸载时发生了什么
EA会在以下情况被卸载:
从图表上移除。
在同一图表上挂新的EA(旧的会被卸掉)。
关闭终端。
加载的模板覆盖了EA。
关闭挂了EA的图表。
切换配置文件(如果EA挂在被切换的图表上)。
ExpertRemove()函数时。EA被卸载时,终端会做两件事:
OnDeinit(),带一个原因代码。步骤4:指标的加载——共享线程的问题
指标的加载规则和EA基本一样,但有一个关键区别:所有指标共享图形界面线程。
这意味着如果你有一个指标卡在死循环里,卡住的不只是那个指标,而是整个MT4终端。我早年踩过这个坑,在
OnCalculate()里不小心写了个while(1)循环,MT4直接卡死,只能从任务管理器强行杀进程。这也是为什么指标开多了会变慢。 它们都在抢同一个线程的资源。10个图表上跑10个指标,就是10个程序抢一个线程。
参考来源:MQL4官方文档,“程序运行”。
步骤5:脚本——最简单,但有个坑
脚本是MQL4程序里最简单的类型。挂上去就执行
OnStart(),然后自动卸载。坑在哪里: 脚本和EA一样跑在独立的线程里。但脚本的信息在终端会话之间是不保存的。重启MT4之后,之前挂过的脚本不会自动重启。
也就是说,如果你用脚本做关键任务,每次重启终端都得手动重新挂。
步骤6:重新编译和输入参数——文档没细说的行为
还有一件事让我意外过。如果正在图表上运行的EA被重新编译了,旧版本会被移除,新版本会自动加载。
但这里有个重点: 如果源代码里的输入参数没变,之前设置的值会保留。但如果参数的数量、顺序、名称或类型变了,就会用新代码里的默认值。
我被这个坑过。给一个运行中的EA加了一个新的输入参数,重新编译之后新参数默认值是
false,但我以为它会默认true。EA的交易行为变了,我过了一个小时看日志才发现。我现在的工作流: 改完参数之后,即使文档说会自动重载,我也会手动把EA重启一遍。
步骤7:事件队列——为什么你的EA可能会漏掉报价
每个MQL4程序都有自己的事件队列。终端把事件发到图表上,程序按顺序一个一个处理。
重点: NewTick事件是合并的。如果队列里已经有一个NewTick事件在排队或正在处理,新的NewTick事件不会被加进队列。Timer和ChartEvent也一样。
这意味着如果你的
OnTick()处理时间太长,你会漏掉报价。终端不会排队等你——直接丢弃。我见过有人在OnTick()里做大量计算甚至调Sleep(),EA卡住,漏掉几个报价,然后又恢复正常。我的建议: 不要在
OnTick()里做重活。用OnTimer()做周期性计算,或者把工作分到不阻塞主线程的函数里去。参考来源:MQL4官方文档,“程序运行——事件队列”。
步骤8:DLL导入——老生常谈但总出问题
如果你的EA用到外部DLL,加载行为会不一样。DLL必须放在
terminal_dir\MQL4\Libraries文件夹里。EA加载的时候会尝试导入DLL函数。如果DLL不存在或者函数签名不匹配,EA初始化会失败。一个我没在文档里看到明确说明的点: 有些DLL依赖系统DLL(比如Visual C++运行库)。如果那些依赖缺失,导入会静默失败——你只会在“EA”标签里看到“DLL调用不被允许”或“错误126”。这不是MT4的问题,是Windows依赖环境的问题。
MQL4官方文档只讲了导入语法,系统级别的依赖是微软那边的事。参考来源:微软官方文档,“Visual C++ Redistributable”。
步骤9:自动交易按钮——总开关
还有一个经常把人搞晕的东西:MT4窗口顶部的“自动交易”按钮。这是所有EA的全局开关。
就算你的EA加载成功了,
OnInit()也跑完了,如果自动交易按钮是关的,EA下不了单。EA照样可以分析价格跑计算,但OrderSend()这类交易函数都用不了。参考来源:MetaQuotes官方帮助中心,“自动交易设置”章节。
最后说几句
折腾了这么一圈我明白了。EA不是一段傻跑的代码。它有生命周期——加载、初始化、运行、反初始化、卸载——每个阶段都有规则影响EA的行为。
如果你的EA在EURUSD上能跑,切到GBPUSD就不行了,别一上来就怀疑代码。先搞清楚切换品种时发生了什么。记住
OnInit()只跑一次,品种相关的值可能会过期。还要记住事件队列会丢掉处理太慢时的报价。搞懂MT4管理程序的底层机制之后,调试简单多了。很多“bug”其实根本不是bug——只是我不了解平台的工作原理而已。
参考来源:
本文首发于FXEAR.com,原创内容,未经授权禁止转载。