首页 / 交易学院 / 小布知识库 / MT5 交易事件模型:一次请求为何触发多个 OnTrade
MT5 交易事件模型:一次请求为何触发多个 OnTrade

MT5 交易事件模型:一次请求为何触发多个 OnTrade

从 OrderSend 到成交回包,理清异步交易事件的来龙去脉

EA 开发实战 难度 · 进阶 2026-07-15 10 分钟阅读
#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 事件(每笔成交、每次数量变化、订单移入历史各算一个)。

⚠ 不要假设一一对应
不能依赖“一个请求一个事件”的假设。OnTrade 可能因一次操作被连串调用——每个订单状态、持仓、历史的变化都会单独生成事件。

五、消息是独立到达的

来自服务器的消息各自独立到达:订单单证可能先到,而 Trade 事件、活动列表更新稍后才到;生至出现“成交已执行、订单已进历史”的情况也属正常,因为服务器处理远快于网络传输。

六、可靠的事件处理算法:对比状态

唯一能确定“账户到底变了什么”的方法,是比较前后快照。做法:全局声明 orders/positions/deals/history_orders 计数器;OnInit 里用 HistorySelect 初始化;在 OnTick/OnTrade 中重新 HistorySelect 并比较前后数量与状态,差异即变化;用 CheckStartDateInTradeHistory 滚动历史窗口。

✦ 对比优于推断
由于消息异步且可能乱序,靠“这次 OnTrade 一定意味着 X”来推断并不可靠;维护计数器快照、比较差异才是最稳的做法。

七、实战:用状态对比法写稳健的 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;
}
✦ 对账而非解析
把 OnTrade 写成「重新读真实状态并 diff」,比穷举每种事件类型稳得多。事件模型再复杂,真相永远在持仓与历史里。

八、为什么不能假设事件顺序

新手常写「收到成交事件就认为单已全成」,这是错的。一股大单可能被拆成多笔 Deal(部分成交),或市价单在跳空时以远偏离价格成交;限价单可能等很久才成甚至不成。若在 OnTrade 里假设「一次事件等于一个确定结果」,遇到拆分就会状态错乱。

更隐蔽的是「一次请求触发多条事件」:发单、部分成交、改仓、平仓可能在同一毫秒连发,你的 OnTrade 会被连续调用多次。任何非幂等的写法(比如计数加一、开关布尔)都会在这一连串调用里出错。回到状态对账法,才能无视顺序与次数。

九、把交易事件接到日志与风控

OnTrade 适合做「成交后的副作用」:写成交日志(时间、品种、方向、成交量、成交价)、累加当日笔数、触发手机通知、更新连亏计数供 OnTick 风控使用。这些都只读状态、更新标量、发通知,绝不在此下单或做重计算。

把日志落盘到 Files 目录下,配合 OnTester 的回测日志,你能在事后精确复盘「哪一笔是在什么行情下成交的」,这是手工交易永远做不到的透明度,也是系统化最大的红利之一。

✦ 副作用清单
OnTrade 能做的:记账、通知、更新标量状态。OnTrade 不能做的:下单、重计算、耗时循环。守住这条,事件回调就不会卡死主线程。

十、小结

常见问题

不能。OnTrade 只被 EA 调用;指标/脚本里即使写了同名函数也不会触发。
一次操作(如部分成交)会改变订单状态、持仓、交易历史等多处,每处变化生成一个独立事件,逐个推送,于是 OnTrade 被连串调用。
不代表。true 只表示请求通过客户端初步检查、被服务器接受进入队列;是否真成交要看 MqlTradeResult 的字段。
维护订单/持仓/成交的计数器快照,在 OnTrade/OnTick 里重新 HistorySelect 并比较前后数量与状态,差异即变化。