第6章 进阶实战:从能跑走向可靠
为什么“能跑”还不够
教学示例通常只保留最顺利的路径:有 Tick、点差正常、止损合法、订单池里只有自己的单。真实账户里,这些条件随时会变。一个 EA 要长期运行,至少要回答四个问题:没有行情时谁在巡检?订单池里哪些单属于我?下单失败后怎么知道原因?参数是回测出来的,还是拍脑袋定的?
这四个问题对应三种能力:事件组织(OnTick 与 OnTimer 分工)、状态管理(订单池、错误码、面板对象)、验证方法(指标缓冲区、回测与样本外检验)。它们不改变策略本身,却决定策略能不能被信任。
第六章地图
- 6.1 OnTimer 定时器:让 EA 在没有 Tick 时也能做心跳、移动止损和状态巡检。
- 6.2 订单池遍历:用 OrderSelect 逐条读取,按品种和魔术号过滤,倒序平仓不漏单。
- 6.3 错误处理:ResetLastError、GetLastError、止损距离与重试边界。
- 6.4 自定义指标:缓冲区、prev_calculated、OnCalculate 与 iCustom 的 shift。
- 6.5 图表对象与面板:ObjectCreate、OnChartEvent、OnDeinit 清理。
- 6.6 回测与参数优化:样本内、样本外、前推滚动与过拟合。
一个可扩展的 EA 骨架
先把事件入口固定下来:OnInit 只做注册和初始化,OnTick 只处理行情,OnTimer 只做轻量巡检,OnDeinit 负责释放。下面的骨架可以直接编译,后续每一节往对应入口里填逻辑。
这段骨架有意留空策略主体。先让四个入口都能打印日志、都能安全返回,再逐步加功能,比一开始堆满逻辑更容易定位问题。
事件顺序:一张图记住
EA 加载时先调用 OnInit,然后终端在每次价格变化时调用 OnTick,在定时器到点时调用 OnTimer,两者互不阻塞。EA 被移除、图表关闭或参数改变时调用 OnDeinit。参数改变会触发一次完整的“卸载—重载”,所以不要在 OnInit 里假设全局变量一定还是旧值。
需要特别注意的是:OnTimer 不是“每隔 N 秒一定执行一次”的硬实时保证。终端繁忙、图表切换、网络抖动都会让触发时间漂移。它适合做巡检和节流,不适合做毫秒级抢单。
调试顺序
- 先用 Print 确认四个入口都被调用,且 reason 参数符合预期。
- 再确认订单池过滤条件:只操作 Symbol() 与 MagicNumber 都匹配的单。
- 然后模拟失败:把止损设到最小止损以内,观察 GetLastError 是否按预期返回 130。
- 最后才看回测收益。事件与错误路径没跑通之前,收益曲线没有参考价值。
先建立可观测性
工程版 EA 和教学版 EA 最大的差别,不是策略更复杂,而是“出错时能看见”。下单时把 ticket、品种、方向、手数、价格、止损、止盈、魔术号写进订单注释或日志;失败时把函数名、attempt、错误码、当前点差一起打印。这样复盘时不需要猜,直接能在日志里还原当时的上下文。
日志也要有节制。每个 Tick 都 Print 会把日志文件迅速撑大,还会拖慢回测。推荐只在“状态发生变化”时打印:新 K 线、下单成功、下单失败、订单被修改、定时器发现异常。用 PrintFormat 统一格式,例如 `PrintFormat("BUY ticket=%d lots=%.2f price=%.5f err=%d", ticket, Lots, price, err);`,比字符串拼接更容易读。
把策略拆成三层
第一层是信号:什么时候想买、想卖。第二层是风控:手数多大、止损多远、同时最多几单、当天最大亏损多少。第三层是执行:怎么下单、失败怎么处理、订单怎么维护。教学示例常把三层揉在一个 OnTick 里,改一处就牵动全身。工程版应该把三层拆成独立函数,OnTick 只负责“取数据—调信号—交给风控—调执行”。
拆层之后,回测也更好做。你可以单独测试信号层(固定手数和止损),再测试风控层(固定信号),最后组合。出现问题时,能快速判断是信号失效、风控过严,还是执行滑点太大。
上线前的十分钟检查
- 把 EA 挂到模拟账户,确认 OnInit、OnTick、OnTimer、OnDeinit 四个入口都有日志。
- 手动平掉一张单,确认订单池遍历不会把别的 EA 的单一起处理。
- 把止损设到最小止损以内,确认错误码是 130,且日志里有 attempt 与 err。
- 切换一次周期,确认 OnDeinit 清理了定时器和面板对象,没有重复日志。
- 用策略测试器跑最近三个月,确认最大回撤、交易次数、盈亏比都在可接受范围。
版本与兼容性
MQL4 与 MQL5 的语法相似,但交易函数、指标缓冲区和事件模型差异很大。本课程以 MT4 build 600+ 的 MQL4 为准。使用 `#property strict` 可以让编译器按更严格的规则检查类型和返回值,减少隐式转换带来的 bug。老代码里常见的 `extern` 在新代码中建议改成 `input`,后者会出现在策略测试器的参数列表里,方便优化。
不同经纪商的 StopLevel、FreezeLevel、最小手数、最大手数、佣金模型都不同。不要把这些值写死在代码里,用 MarketInfo 读取;也不要在多个经纪商之间直接复制参数,先用模拟账户跑一遍最小手数验证。兼容性问题的根源往往不是语法,而是交易规则。
常见错误
- 把 EventSetTimer 写在 OnTick 里:每来一个 Tick 就重设定时器,定时器永远等不到下一次触发。只在 OnInit 里注册一次。
- OnDeinit 忘记 EventKillTimer:切换周期或参数后,旧定时器可能残留,日志出现重复输出。
- 在 OnInit 里下单:此时报价、指标和订单环境都还没稳定,应该等第一个 Tick。
- 把策略主体同时写在 OnTick 和 OnTimer:两个入口都会下单,仓位翻倍。
小结
第六章的关键词是“边界”:事件边界、订单边界、错误边界、数据边界。每一节都会给你一个可以单独编译的小例子,再说明它在真实账户里最容易坏在哪里。学完这一章,你应该能把一个教学 EA 改造成能长期观察、能解释失败原因、能复盘的工程版本。
练习题
-
第6章 进阶实战:从能跑走向可靠 选择题1.EA 中注册定时器的正确位置通常是? (2分) [选择]A. OnInitB. OnTickC. OnTimerD. OnDeinit2.OnDeinit 中释放定时器应调用哪个函数? (2分) [选择]A. EventSetTimerB. EventKillTimerC. SleepD. RefreshRates3.OnTimer 的触发间隔是硬实时保证,误差不会超过 1 毫秒。 (2分) [判断]
-
第6章 进阶实战:从能跑走向可靠 编程实战(AI 批改)1.【实战】写出带定时器与安全退出的 EA 骨架 (10分) [MQL4自动批改]写一个 EA 骨架:OnInit 中注册 1 秒定时器并返回 INIT_SUCCEEDED;OnDeinit 中关闭定时器;OnTick 中先判断 IsTradeAllowed,再用 Time[0] 与静态变量 lastBar 保证每根 K 线只执行一次;OnTimer 中判断 IsConnected 后只打印心跳。代码必须出现 EventSetTimer、EventKillTimer、OnTick、OnTimer 四个关键字。
《MQL4 教程与 MT4 EA 编程》课程目录
系统讲解 MQL4 编程:从变量、循环、函数到 OrderSend 下单、iMA 指标调用与 EA 回测。36 节中文教程,覆盖 MT4 编程入门、外汇 EA 编写与外汇量化策略三类实战,每节配可运行代码和 AI 自动批改练习
- 第1章 基本语法
- 1.1 第一个EA
- 1.2 如何学习
- 1.3 变量
- 1.4 语句
- 1.5 运算符
- 1.6 常量与枚举
- 第2章 条件
- 2.1 if语句
- 2.2 关系运算符
- 2.3 逻辑运算符
- 2.4 复合条件
- 2.5 switch语句
- 第3章 循环
- 3.1 for循环
- 3.2 while循环
- 3.3 break和continue
- 3.4 嵌套循环
- 第4章 函数
- 4.1 函数的定义
- 4.2 参数和返回值
- 4.3 变量的作用域
- 4.4 常用内置函数
- 4.5 日期和时间函数
- 4.6 交易函数
- 4.7 账户和市场信息函数
- 4.8 图表和对象函数
- 4.9 文件操作函数
- 4.10 数组函数
- 4.11 技术指标函数
- 第5章 如何写EA
- 5.1 EA的基本结构
- 5.2 常用内置变量
- 5.3 下单函数
- 5.4 技术指标
- 5.5 实战:均线交叉EA
- 第6章 进阶实战:从能跑走向可靠
- 6.1 OnTimer 定时器与事件处理
- 6.2 订单池遍历与 OrderSelect
- 6.3 错误处理与 GetLastError
- 6.4 自定义指标与指标缓冲区
- 6.5 图表对象与交易面板
- 6.6 回测与参数优化