构建自动运行的 EA(第 08 部分):OnTradeTransaction(基础篇)
📘

构建自动运行的 EA(第 08 部分):OnTradeTransaction(基础篇)

第 1/2 篇

用 OnTradeTransaction 接住每一笔成交流水

在 MT5 的 EA 生命周期里,OnTradeTransaction 是专门用来响应交易事务事件的回调。只要账户里发生下单、成交、删单、改仓等动作,终端就会触发它,而不依赖你主动去轮询交易池。 它的原型是 void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result)。trans 描述发生了什么类型的动作,request 是这次动作的原始请求,result 是终端回给系统的执行结果。 实战中一个常见用法:在 trans.type 等于 TRADE_TRANSACTION_DEAL_ADD 时,抓取真实成交记录做统计。下面这段代码只做一件事——把每笔新成交的手数和符号打到日志,方便你开 MT5 后立刻验证事件是否真被捕获。 外汇与贵金属保证金交易杠杆高、滑点随机,任何基于成交事件的策略都只是概率倾向,不代表稳定收益。先把事件流看清,再谈后续逻辑。

MQL5 / C++
class="type">void OnTradeTransaction(const MqlTradeTransaction& trans,
                         const class="type">MqlTradeRequest& request,
                         const class="type">MqlTradeResult& result)
  {
   if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
     {
      Print("新成交: ", trans.symbol, " 手数: ", trans.volume);
     }
  }

◍ EA 与服务器的消息流向

做自动交易 EA 前,得先弄清它怎么跟交易服务器对话。图例 01 画了一条消息流:EA 通过 C_Orders 类调用 OrderSend 把市价单或挂单请求发到服务器,其余环节大多是单向的。 唯一出现双向箭头的地方,是 C_Orders 调用 OrderSend 时——这时它按结构接收服务器回包。除这一刻外,图上所有节点都是定向传递,系统其实很薄。 100% 自动化 EA 里,C_Mouse 类直接删掉,它只在半自动盯盘时有用。所有逻辑只写在 EA 与 C_Manager 之间,别往 EA 其他部分塞交易代码。 外汇和贵金属杠杆高、滑点突变频繁,搞不清消息流就改 EA,很容易在 OrderSend 回包处理上出隐蔽 bug。先把这条链路在 MT5 里单步跑通,再谈策略。

「用事件而非轮询让EA感知订单变化」

很多 MQL5 用户写 EA 时习惯自己写循环去扫订单簿,其实平台早已基于事件驱动把消息推过来了。MT5 不是进程式轮询工具:你不必持续搜信息,而是对交易服务器抛出的事件做响应,思路要从「主动查」换成「被动接」。 C_Manager 类里补了三个函数,让 EA 和订单系统状态保持同步。其中一个在通知单号等于挂单单号时清掉挂单号——正常 EA 不会删挂单,多是交易者手动干预删了,这时必须让 Manager 知道,否则后续补单逻辑会卡死。另一个函数在挂单变持仓或持仓被改时调用,Manager 会清掉原挂单单号并放行新挂单,但仅当没有严重错误时才继续。 事件入口别用 OnTick 或 OnTime。OnTick 每个新 tick 都触发太吵;OnTime 定时器则要反复扫清单,纯属给平台添堵。正解是 OnTradeTransaction:服务器订单系统一变动就推事件过来,不用你出门找数据。 MqlTradeTransaction 结构里我们重点看 type 字段,当前只处理三种:TRADE_TRANSACTION_REQUEST、TRADE_TRANSACTION_ORDER_DELETE、TRADE_TRANSACTION_POSITION。持仓被改时触发 POSITION 事件,EA 只把自管持仓交给 Manager,省去全量比对时间。ORDER_DELETE 在订单转持仓或平仓时都来,区别在 trans.order 与 trans.position 是否相等:相等即订单变持仓,不等则可能是加仓或平仓,分别发不同更新请求给 Manager。 REQUEST 枚举几乎必触发,要先按品种、服务器接受标志、魔术码三层过滤。之后 switch 检查的是 EA 发给服务器的动作类型(ENUM_TRADE_REQUEST_ACTIONS),不是服务器执行结果。当前自动 EA 只需盯 TRADE_ACTION_DEAL、TRADE_ACTION_SLTP、TRADE_ACTION_REMOVE 三种,其余对应手动类操作暂不重要。 外汇与贵金属杠杆高、滑点跳空频繁,靠事件处理虽能降 CPU 占用,但服务器推送顺序不保证,实盘前务必在 MT5 策略测试器用真实点差回放验证 trans 结构解析逻辑。

挂单转持仓与删除通知的边界处理

枚举器不止在市价单成交时被调用。当服务器触发 TRADE_ACTION_DEAL 且 trans.order 与 trans.position 相等时,也会走同一路径,此时可把挂单号直接接进持仓。但若对冲账户里另一笔持仓未平,代码会主动 SetUserError(ERR_Unknown),EA 可能就此停摆,这段保护逻辑藏在 UpdatePosition 里而非主流程。 修改止盈止损会触发另一类枚举,逻辑只是同步 SL/TP 数值,当前版本刻意写得极简,后续可扩展但够用。订单被删时(用户手动撤单或意外丢失)必须通知 C_Manager,否则 EA 会误以为挂单还在簿上,再也发不出下一笔。 下面这段展示了挂单号清空与转持仓的核心:若账户为对冲模式且已有持仓票号,则置用户错误;否则把挂单票号赋给持仓。注意 m_TicketPending 在赋值后立即归零,避免重复占用。 EraseTicketPendig 仅在传入票号等于当前挂单号时才将其置 0,是一种防误清的保护。若 _LastError 非成功,会回拉 UpdatePosition 做持仓刷新,最后跑一次 CheckToleranceLevel 校验容差。外汇与贵金属杠杆高,这类票号状态错乱在实盘可能直接中断策略,建议先在 MT5 策略测试器用历史 Tick 跑一遍确权。

MQL5 / C++
class="kw">inline class="type">void EraseTicketPendig(const class="type">class="kw">ulong ticket)
                        {
                                m_TicketPending = (ticket == m_TicketPending ? class="num">0 : m_TicketPending);
                        }
                class="type">void PedingToPosition(class="type">void)
                        {
                                ResetLastError();
                                if ((m_bAccountHedging) && (m_Position.Ticket > class="num">0)) SetUserError(ERR_Unknown);
                                        else m_Position.Ticket = (m_Position.Ticket == class="num">0 ? m_TicketPending : m_Position.Ticket);
                                m_TicketPending = class="num">0;
                                if (_LastError != ERR_SUCCESS) UpdatePosition(m_Position.Ticket);
                                CheckToleranceLevel();
                        }
                class="type">void UpdatePosition(const class="type">class="kw">ulong ticket)
                        {
                                class="type">int ret;

                                if ((ticket == class="num">0) || (ticket != m_Position.Ticket)) class="kw">return;
                                if (PositionSelectByTicket(m_Position.Ticket))
                                {
                                        ret = SetInfoPositions();
                                        m_StaticLeverage += (ret > class="num">0 ? ret : class="num">0);

常见问题

用 OnTradeTransaction 事件回调接住成交流水,服务器推送交易事务时 EA 自动触发,不必轮询账户持仓。
在 OnTradeTransaction 里看交易事务类型和订单状态,转持仓走 POSITION 分支,删除走 ORDER_DELETE 分支分别写逻辑。
小布内置了订单与成交流水的 AI 诊断,打开对应品种页就能直接看 EA 事件触发的订单变化,不用自己搭框架。
轮询有时间间隔,成交流水若在两次查询间完成就被跳过;改用事件驱动可让服务器每次变动都通知 EA。
服务器在订单成交、挂单改动时主动发交易事务给终端,EA 通过 OnTradeTransaction 接收并处理,是单向推送加本地响应。