MT5 交易事件模型:一次请求为何触发多个 OnTrade
从 OrderSend 到成交回包,理清异步交易事件的来龙去脉
理解交易事件模型,是写出健壮 EA 的前提。MT5 的异步特性意味着:你发的“一个请求”和客户端收到的“事件”并不是一一对应的。本文顺着请求到成交的完整链路,把这件事讲透。
一、请求如何从客户端到服务器
手动交易按 F9 打开请求窗口,程序用 OrderSend() 发送。发往服务器前,客户端先做强正确性检查(如买几百万手、负价),错误请求根本不会出门——这是为保护服务器不被程序 bug 刷爆。可用 OrderCheck() 预检,结果存入 MqlTradeCheckResult。
二、服务器的初步检查
请求到达后,服务器校验资金是否充足、价格/止损止盈是否正确、数量(最小/最大/步长/持仓限额)、品种状态与交易时段、账户限制等。不通过即拒绝,回包写入 MqlTradeResult(通过 OrderSend 第二参数取回)。通过检查的请求进入等待队列,寿命 3 分钟,超时删除。
三、OnTrade:交易事件处理函数
MQL5 的事件模型会在订单/持仓/成交/历史变化时调用预定义的 OnTrade()。注意:OnTrade 仅供 EA 使用,你在指标或脚本里写同名函数也不会被触发。
四、关键真相:一个请求 → 多个事件
例:一个买 10 手 EURUSD 的挂单,被 1+4+5 手三笔反向成交吃掉。作为结果,客户端会收到 7 个 Trade 事件(每笔成交、每次数量变化、订单移入历史各算一个)。
五、消息是独立到达的
来自服务器的消息各自独立到达:订单单证可能先到,而 Trade 事件、活动列表更新稍后才到;生至出现“成交已执行、订单已进历史”的情况也属正常,因为服务器处理远快于网络传输。
六、可靠的事件处理算法:对比状态
唯一能确定“账户到底变了什么”的方法,是比较前后快照。做法:全局声明 orders/positions/deals/history_orders 计数器;OnInit 里用 HistorySelect 初始化;在 OnTick/OnTrade 中重新 HistorySelect 并比较前后数量与状态,差异即变化;用 CheckStartDateInTradeHistory 滚动历史窗口。
七、实战:用状态对比法写稳健的 OnTrade
最稳的事件处理不是去解析事件类型,而是「对比前后状态」。在 OnTrade 里重新 PositionSelect、HistorySelect,读当前真实持仓与最近成交,和 EA 自己记的上一次状态做 diff,据此更新。这样无论终端一次发一个还是一串事件,EA 最终状态都收敛到真实值。
具体做法:在全局维护 lastTicket、hasPos 等标量;OnTrade 里先 HistorySelect 最近几秒,取最新 Deal 的 ticket 与方向,再 PositionSelect 看当前是否在场,更新状态机。事件来多少次都只做同一件事——重新对账,天然幂等。
bool g_hasPos = false;
void OnTrade()
{
bool now = PositionSelect(_Symbol);
if(now && !g_hasPos)
{
double op = PositionGetDouble(POSITION_PRICE_OPEN);
Print("新持仓 opened @ ", op);
}
g_hasPos = now;
}
八、为什么不能假设事件顺序
新手常写「收到成交事件就认为单已全成」,这是错的。一股大单可能被拆成多笔 Deal(部分成交),或市价单在跳空时以远偏离价格成交;限价单可能等很久才成甚至不成。若在 OnTrade 里假设「一次事件等于一个确定结果」,遇到拆分就会状态错乱。
更隐蔽的是「一次请求触发多条事件」:发单、部分成交、改仓、平仓可能在同一毫秒连发,你的 OnTrade 会被连续调用多次。任何非幂等的写法(比如计数加一、开关布尔)都会在这一连串调用里出错。回到状态对账法,才能无视顺序与次数。
九、把交易事件接到日志与风控
OnTrade 适合做「成交后的副作用」:写成交日志(时间、品种、方向、成交量、成交价)、累加当日笔数、触发手机通知、更新连亏计数供 OnTick 风控使用。这些都只读状态、更新标量、发通知,绝不在此下单或做重计算。
把日志落盘到 Files 目录下,配合 OnTester 的回测日志,你能在事后精确复盘「哪一笔是在什么行情下成交的」,这是手工交易永远做不到的透明度,也是系统化最大的红利之一。
十、小结
- 发单前客户端先强校验保护服务器
- OrderSend 返回 true≠成交,看 MqlTradeResult
- OnTrade 仅 EA 可用
- 一个操作可触发多个 Trade 事件
- 服务器消息独立到达、可能乱序
- 最稳做法:维护计数器快照并比较差异