6.1 OnTimer 定时器与事件处理
OnTimer 适合做什么
把 OnTimer 当成一个轻量巡检器,而不是第二个策略入口。适合放在这里的任务有三类:
- 状态巡检:连接是否正常、交易是否允许、点差是否过大、订单是否还在。
- 时间驱动动作:收盘前平仓、持仓超时、每日固定时间重置统计。
- 界面刷新:更新面板上的余额、浮盈、状态文字,不需要每个 Tick 都重绘。
不适合放在 OnTimer 里的任务:长时间循环、Sleep、复杂的指标重算、每次触发都下单。定时器是单线程事件循环的一部分,它卡住,OnTick 也会被拖延。
注册与释放
EventSetTimer(seconds) 在 OnInit 里调用一次即可。它注册的是“终端级”定时器,不是每个图表各一份;同一个 EA 重复调用会重置计时。EA 被移除时必须在 OnDeinit 里调用 EventKillTimer(),否则切换周期、修改参数后可能留下旧定时器,日志出现重复输出。
还有一个容易忽略的点:修改输入参数会让 EA 走一次 OnDeinit → OnInit。如果你在 OnInit 里创建了图表对象或打开了文件,也要在 OnDeinit 里对应清理。
OnTick 与 OnTimer 的分工
- OnTick:读取价格、判断信号、开仓平仓。它由行情驱动,可能一秒几十次,也可能几分钟一次。
- OnTimer:检查状态、维护订单、刷新界面。它由时间驱动,适合“至少每 N 秒看一次”的逻辑。
- 共享状态:两者都能读写全局变量和订单池。不要在 OnTimer 里假设 OnTick 刚刚执行过,也不要在 OnTick 里假设定时器已经初始化完成。
完整示例:定时移动止损
下面的 EA 在新 K 线时留给策略主体,在 OnTimer 里对多单做移动止损。它先 RefreshRates() 取最新 Bid,再倒序遍历订单池,只处理本 EA、本品种的 BUY 单。新止损必须高于旧止损且低于当前 Bid,否则 OrderModify 会报 130 或 1。
注意 OrderModify 的第二个参数是“开仓价”,不是当前价。很多初学者把 Bid 填进去,结果修改失败。移动止损只改 SL/TP,开仓价保持 OrderOpenPrice()。
定时器不是硬实时
EventSetTimer(1) 表示“大约每秒一次”,不是“精确到毫秒”。终端处理大量图表、历史数据下载、指标重算时,回调会排队。因此:不要用 OnTimer 做毫秒级抢价;不要因为两次回调间隔 1.8 秒就认为定时器坏了;对时间敏感的逻辑用 TimeCurrent() 判断实际时间差,而不是数回调次数。
常见错误
- 在 OnTick 里 EventSetTimer:每个 Tick 都重置计时,定时器可能永远不触发。只在 OnInit 注册。
- 忘记 EventKillTimer:切换参数后旧定时器残留,日志翻倍。OnDeinit 必须成对释放。
- 在 OnTimer 里下单却不判断 IsTradeAllowed:终端未登录、交易被禁用时仍然发请求,错误码刷屏。
- 在 OnTimer 里 Sleep:会阻塞整个 EA 的事件循环,OnTick 也被拖住。需要等待时用状态变量记录下一次尝试时间。
- 把定时器当第二个策略入口:OnTick 开多、OnTimer 也开多,仓位翻倍。策略信号只保留一个入口。
小结
OnTimer 的价值是“没有行情时仍然能工作”。把它限制在巡检、维护、刷新三类任务上,遵守“OnInit 注册、OnDeinit 释放、回调里不阻塞”的规则,你的 EA 就不会在周末变成一具只会等待 Tick 的空壳。
时间驱动逻辑的三种写法
第一种是“时间戳门闩”:在 OnTick 里比较 TimeCurrent() 与上次执行时间,超过 N 秒才执行。它不需要定时器,但前提是必须有 Tick;周末没有行情时,门闩永远不会被推开。第二种是 OnTimer,完全由时间驱动,适合巡检和超时处理。第三种是两者结合:OnTimer 只设置一个 `needCheck` 标志,真正的重活仍在 OnTick 里做,避免定时器回调阻塞行情处理。
选择哪种写法,取决于“没有 Tick 时这件事是否必须发生”。移动止损、面板刷新、持仓超时必须在没有 Tick 时也发生,用 OnTimer;新 K 线信号、指标计算依赖行情,用 OnTick。不要为了“统一”把所有逻辑都塞进定时器,那样会失去 Tick 驱动的及时性。
一个超时平仓的例子
假设策略要求持仓最多 4 小时。可以在下单时把 `OrderOpenTime()` 记下来,在 OnTimer 里遍历订单,比较 `TimeCurrent() - OrderOpenTime() > 4*3600`,满足就平仓。不要在 OnTick 里用 `Sleep(4*3600)`,那会把整个 EA 卡死;也不要用循环等待,事件驱动模型里不应该有长阻塞。
超时平仓还要处理“平仓失败”的情况。如果 OrderClose 返回 false,记录错误码,下一轮定时器再试;不要因为一次失败就把订单从“待处理”列表里删掉。用订单池本身作为状态源,比维护一份容易失真的内存列表更可靠。
在策略测试器里的表现
策略测试器支持 OnTimer,但触发节奏受建模方式影响。Every tick 模式下比较接近真实;Control points 模式下,定时器可能在两个控制点之间被跳过或合并。因此,依赖精确定时的策略(例如每秒检查一次点差)在回测里可能被低估或高估。上线前至少用 Every tick 跑一段短周期数据,确认定时器逻辑没有明显的时序偏差。
测试器里 `IsTesting()` 为 true,`IsVisualMode()` 只在可视化模式下为 true。可以在 OnInit 里根据这两个标志调整日志级别:回测少打印,可视化模式多打印。不要根据 `IsDemo()` 判断是否可以交易,模拟账户和真实账户的差异不在这个标志上。
调试定时器
最常见的问题是“定时器没触发”。排查顺序:确认 EventSetTimer 在 OnInit 里只调用一次;确认返回值没有报错(EventSetTimer 返回 bool);在 OnTimer 第一行打印 `TimeCurrent()`,看是否真的有回调;确认 EA 没有被暂停、图表没有离线。若日志里每秒出现两次,检查是否在 OnTick 里又调用了一次 EventSetTimer,或者挂了两个相同 EA。
多图表与多定时器
同一个 EA 挂在三个图表上,就有三份独立的全局变量和三个 OnTimer 回调。它们共享同一个账户订单池,所以订单过滤必须带图表标识。定时器本身是终端级的,但每个图表上的 EA 实例都会注册自己的定时器;三个实例就是三个定时器,不会互相覆盖。若发现日志数量是预期的三倍,先数图表,不要怀疑终端。
多图表场景下,建议给每个图表分配不同的 MagicNumber,并在面板上显示当前图表的魔术号。这样下单、平仓、统计都不会串。若必须共享同一个魔术号,至少加上品种和注释前缀,并在平仓前二次确认订单号。
时间函数怎么选
TimeCurrent() 是服务器时间,TimeLocal() 是本地时间,TimeGMT() 是 GMT。交易逻辑一律用服务器时间,因为开盘、收盘、隔夜利息都按服务器时间计算。判断“是否新 K 线”用 Time[0],它返回当前 K 线开盘时间,比 TimeCurrent() 更稳定。计算持仓时长用 TimeCurrent() - OrderOpenTime(),两者都是服务器时间,单位是秒。
不要用 `TimeDay(TimeCurrent())` 判断“今天”而不考虑服务器时区;也不要在 OnInit 里用 TimeCurrent() 做一次性初始化,因为策略测试器里时间会随回测推进。需要“每天重置一次”的逻辑,用静态变量记录上一次的日期,在 OnTick 或 OnTimer 里比较。
交互演示:OnTick 与 OnTimer 的事件时间线
黑色节点是事件到达顺序,蓝色是当前回调。单步走一遍:哪些 Tick 被新 K 线过滤,哪些动作由定时器完成。
提示:动画默认暂停。点「播放」连续演示,点「单步」自己控制节奏;桌面端也可以用空格播放、方向键单步、R 重置。
练习题
-
6.1 OnTimer 定时器与事件处理 选择题1.EventSetTimer 通常应放在哪个函数中? (2分) [选择]A. OnInitB. OnTickC. OnTimerD. OnCalculate2.下列哪项最适合放在 OnTimer 中? (2分) [选择]A. 每次 Tick 重新计算全部历史指标B. 检查连接、刷新面板、维护移动止损C. Sleep 等待行情D. 递归遍历全部历史订单3.OnTimer 的触发间隔是硬实时保证,误差不会超过 1 毫秒。 (2分) [判断]
-
6.1 OnTimer 定时器与事件处理 编程实战(AI 批改)1.【实战】用 OnTimer 写移动止损 (10分) [MQL4自动批改]写一个 EA:OnInit 注册 1 秒定时器;OnDeinit 关闭;OnTimer 中先判断 IsConnected 与 IsTradeAllowed,再 RefreshRates,倒序遍历订单池,只处理本品种、本魔术号的 BUY 单;当 Bid-TrailPoints*Point 高于旧止损且低于 Bid 时调用 OrderModify。必须出现 EventSetTimer、OnTimer、OrderModify、GetLastError。
《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 回测与参数优化