MQL5 Cookbook: 处理 TradeTransaction 事件·综合运用
⚙️

MQL5 Cookbook: 处理 TradeTransaction 事件·综合运用

(3/3)·从事件触发到自定义日志,12 节拆解 OnTradeTransaction 的实战拼装法

实战向进阶 第 3/3 篇
很多交易者写了几年 EA,仍只在 OnTrade 里补仓位统计,却错过订单从发单到进历史的五次回调。用错处理器,账户动作的诊断就会慢半拍,复盘时只能靠肉眼翻终端日志。

从日志读懂 EA 下单回执

在 MT5 里跑自动交易脚本时,专家日志会吐出一堆两字母前缀的行,它们不是装饰,而是订单生命周期的快照。上面这段 EURUSD H1 的 TradeProcessor 日志,就记录了一笔 0.14 手仓位的发起与后续状态变化。 18:13:33.195 这一刻,脚本先报出 Volume in lots: 0.14,紧接着在 RQ 行给出操作结果码 10009——这是交易服务器回的「请求已受理」信号。同一毫秒 GM 行显示订单号 22535983 已生成,但 JI 行的成交票号还是 0,说明单子进了队列、尚未撮合。 到了 18:17:02.886,隔了约 3 分 29 秒,HJ 行冒出 ---===Transaction===---,CH 行写明 DEAL_TYPE_BUY,RL 行回扣订单号 22535983,II 行状态变 ORDER_STATE_STARTED。也就是说,这张买单从挂出到真正启动成交,中间经历了数分钟的服务端排队。 外汇和贵金属杠杆高,这种延迟在滑点行情里可能被放大;下次你看到订单号有了、成交票号还是 0,别急着以为 EA 崩了,先去日志里比对 10009 与 ORDER_STATE_STARTED 的时间差。

◍ 从日志抓挂单与成交的真实字段

EA 在 EURUSD H1 上跑 TradeProcessor,会在专家日志里吐出一批带两字母标签的跟踪行。上面这段同一毫秒 18:17:02.886 打出的记录,显示它下了一张 BUY_LIMIT,挂单价 1.6500,SL/TP 都是 0.0000,过期类型 ORDER_TIME_GTC,成交量 0.14 手,但交易符号字段写的是 GBPUSD——图表品种和订单品种不一致,这种错位在跨品种逻辑里要单独拦。 四分钟后 18:21:27.004 又来一条 Transaction 块:订单号 22535983,DEAL_TYPE_BUY,ORDER_STATE_PLACED,类型仍是 BUY_LIMIT。注意 deal ticket 是 0,说明此刻只是挂单生效,还没真正撮合成交,别把 PLACED 当成已经吃进头寸。 这种日志最适合拿来做对账:开 MT5 把专家日志级别调到全部,搜 TradeProcessor 两字母标签,就能核自己 EA 的挂单参数有没有被偷偷改写。外汇和贵金属杠杆高,日志里 SL=0 的挂单若没在别处兜底,可能直接裸奔。

「从日志字段看挂单更新的真实状态」

上面这段 MT5 专家日志来自 EURUSD H1 上的 TradeProcessor 实例,时间戳停在 18:21:27.004,一次性吐出了挂单被更新时的关键字段。注意 Price 显示 1.6500,但交易品种却是 GBPUSD——说明这次更新动作发生在 EURUSD 图表,却指向了另一个品种的订单对象。 Stop Loss 与 Take Profit 均为 0.0000,结合 ORDER_TIME_GTC 与过期时间 1970.01.01 00:00,可以判断这是一张不自动过期、且暂未设防护位的挂单。交易事务类型标为 TRADE_TRANSACTION_ORDER_UPDATE,手数 0.14,属于对已有挂单的修正而非新建。 开 MT5 按 F8 打开专家日志,搜 TradeProcessor 前缀,你能在毫秒级时间戳里核对每笔挂单更新的符号与防护位是否错位。外汇与贵金属挂单修改存在滑点与时延风险,日志字段异常可能预示订单逻辑漏洞。

MQL5 / C++
EG      class="num">0    class="num">18:class="num">21:class="num">27.004   TradeProcessor(EURUSD,H1)      Price: class="num">1.6500
GL      class="num">0    class="num">18:class="num">21:class="num">27.004   TradeProcessor(EURUSD,H1)      Level of Stop Loss: class="num">0.0000
ED      class="num">0    class="num">18:class="num">21:class="num">27.004   TradeProcessor(EURUSD,H1)      Level of Take Profit: class="num">0.0000
GO      class="num">0    class="num">18:class="num">21:class="num">27.004   TradeProcessor(EURUSD,H1)      Price that triggers the Stop Limit order: class="num">0.0000
RE      class="num">0    class="num">18:class="num">21:class="num">27.004   TradeProcessor(EURUSD,H1)      Trade symbol: GBPUSD
QS      class="num">0    class="num">18:class="num">21:class="num">27.004   TradeProcessor(EURUSD,H1)      Pending order expiration time: class="num">1970.01.class="num">01 class="num">00:class="num">00
JS      class="num">0    class="num">18:class="num">21:class="num">27.004   TradeProcessor(EURUSD,H1)      Order expiration type: ORDER_TIME_GTC
RD      class="num">0    class="num">18:class="num">21:class="num">27.004   TradeProcessor(EURUSD,H1)      Type of the trade transaction: TRADE_TRANSACTION_ORDER_UPDATE
JK      class="num">0    class="num">18:class="num">21:class="num">27.004   TradeProcessor(EURUSD,H1)      Volume in lots: class="num">0.14

把订单和仓位揉进同一个事件处理器

写 OnTradeTransaction() 最坑的一点:一次手动或 OrderSend()/OrderSendAsync() 发出的请求,在交易服务器上可能裂成好几条连续事务,而且它们抵达终端的先后顺序没有保证,甚至可能在传输中丢失。所以你的处理器逻辑绝不能假设『事务是一条接一条按序到的』,否则限价单触发、持仓修改这类链条就会漏判。 MQL5 一共定义了 11 种交易事务类型,但终端日常交易里真正要处理的只有 4 种 DEAL/HISTORY 相关的更新与删除类,其余 7 种才是订单和仓位通用的主力类型。把这 7 种塞进一个 switch(trans_type) 里分派,是让程序同时吃得下订单和仓位的关键。 用一个 last_action 变量记『事件处理器因什么被启动』,再用 ENUM_TRADE_OBJ 枚举的 trade_obj 记『这次处理的是仓位还是订单』,case 模块就能自己分清上下文。比如 TRADE_TRANSACTION_ORDER_ADD 里,若仓位先被处理就打『Open a new market order』,否则就是『Place a new pending order』。 TRADE_TRANSACTION_ORDER_UPDATE 只跟限价单有关,但它出现的『第几次触发』决定了结局:第一次触发时订单可能取消或已执行;第二次触发时可能删除或修改,得回头查 last_action 才能定性;第三次触发就是限价单流程收尾。再加个布尔 is_to_reset_cnt 做清零标志,并在处理器开头加微小暂停,给成交/订单进历史前留点缓冲。外汇和贵金属杠杆高,这类事件乱序若处理不当会误判仓位状态,实盘前务必在 MT5 策略测试器里跑一遍乱序模拟。

MQL5 / C++
class=class="str">"cmt">//--- ========== Types of transaction [START]
  class="kw">switch(trans_type)
    {
    class=class="str">"cmt">//--- class="num">1) if it is a request
    case TRADE_TRANSACTION_REQUEST:
      {
      class=class="str">"cmt">//---
      class="kw">break;
      }
    class=class="str">"cmt">//--- class="num">2) if it is an addition of a new open order
    case TRADE_TRANSACTION_ORDER_ADD:
      {
      class=class="str">"cmt">//---
      class="kw">break;
      }
    class=class="str">"cmt">//--- class="num">3) if it is a deletion of an order from the list of open ones
    case TRADE_TRANSACTION_ORDER_DELETE:
      {
      class=class="str">"cmt">//---      
      class="kw">break;
      }
    class=class="str">"cmt">//--- class="num">4) if it is an addition of a new order to the history
    case TRADE_TRANSACTION_HISTORY_ADD:
      {
      class=class="str">"cmt">//---      
      class="kw">break;
      }
    class=class="str">"cmt">//--- class="num">5) if it is an addition of a deal to history
    case TRADE_TRANSACTION_DEAL_ADD:
      {
      class=class="str">"cmt">//---
      class="kw">break;
      }
    class=class="str">"cmt">//--- class="num">6) if it is a modification of a position 
    case TRADE_TRANSACTION_POSITION:
      {
      class=class="str">"cmt">//---
      class="kw">break;
      }
    class=class="str">"cmt">//--- class="num">7) if it is a modification of an open order
    case TRADE_TRANSACTION_ORDER_UPDATE:
      {
      class=class="str">"cmt">//---
      class="kw">break;
      }
     }
class=class="str">"cmt">//--- ========== Types of transactions [END]
class=class="str">"cmt">//--- class="num">1) if it is a request

◍ 在 OnTradeTransaction 里拆交易请求类型

EA 接管交易事件时,最先撞上的就是 TRADE_TRANSACTION_REQUEST 分支。这里 request.action 决定了本次触发是建仓、挂单还是改单,不先分类就直接写逻辑,日志和风控都会串味。 下面这段把五种常见 action 映射到可读字符串和对象类别:市价单归到 POSITION,挂单相关(下挂单、改挂单、删挂单)归到 ORDER,而 SLTP 动作虽改的是持仓止损止盈,对象仍算 POSITION。 注意 TRADE_ACTION_SLTP 里用了 StringConcatenate 把品种名和「modify the levels of Stop Loss and Take Profit」拼进 action_str,其余分支多是直接赋值。若 InpIsLogging 为真,尾行 Print 会把「Request received: 」加 action_str 打到终端——实盘跑前把这个选项打开,能直接看机器人到底在发哪类指令。 外汇与贵金属杠杆高,这类请求监听若漏掉某种 action,可能在跳空时无声错过改单,建议把五个 case 在策略测试器里各触发一次。

MQL5 / C++
case TRADE_TRANSACTION_REQUEST:
  {
   class=class="str">"cmt">//---
   last_action=request.action;
   class="type">class="kw">string action_str;
   class=class="str">"cmt">//--- what is the request for?
   class="kw">switch(last_action)
     {
      class=class="str">"cmt">//--- а) on market
      case TRADE_ACTION_DEAL:
        {
         action_str="place a market order";
         trade_obj=TRADE_OBJ_POSITION;
         class="kw">break;
        }
      class=class="str">"cmt">//--- б) place a pending order
      case TRADE_ACTION_PENDING:
        {
         action_str="place a pending order";
         trade_obj=TRADE_OBJ_ORDER;
         class="kw">break;
        }
      class=class="str">"cmt">//--- в) modify position
      case TRADE_ACTION_SLTP:
        {
         trade_obj=TRADE_OBJ_POSITION;
         class=class="str">"cmt">//---
         StringConcatenate(action_str,request.symbol,": modify the levels of Stop Loss",
                           " and Take Profit");
         class=class="str">"cmt">//---
         class="kw">break;
        }
      class=class="str">"cmt">//--- г) modify order
      case TRADE_ACTION_MODIFY:
        {
         action_str="modify parameters of the pending order";
         trade_obj=TRADE_OBJ_ORDER;
         class="kw">break;
        }
      class=class="str">"cmt">//--- д) class="kw">delete order
      case TRADE_ACTION_REMOVE:
        {
         action_str="class="kw">delete pending order";
         trade_obj=TRADE_OBJ_ORDER;
         class="kw">break;
        }
     }
   class=class="str">"cmt">//---
   if(InpIsLogging)
      Print("Request received: "+action_str);
   class=class="str">"cmt">//---
   class="kw">break;
  }

「挂单生命周期里的第三遍回调怎么抓」

在 MT5 的 OnTradeTransaction 里,一笔挂单从发单到成交或取消会经历多遍事务推送。上面这段逻辑专门处理 TRADE_TRANSACTION_HISTORY_ADD:当 trade_obj 标记为 TRADE_OBJ_ORDER 且全局计数 gTransCnt 等于 2 时,意味着进入了第三遍回调,此时挂单已落入历史,需要回头查它到底有没有触发成交易。 代码先用 TimeCurrent 取当前时间,再 HistorySelect 拉取最近一个 H1 周期(3600 秒)的订单与成交历史。这个 1 小时窗口是硬参数,若你的挂单存活时间可能超过 1 小时,直接抄这段代码会漏掉更早的成交,必须放大 PeriodSeconds 的倍数。 随后声明 CDealInfo 并用 HistoryDealsTotal 拿到历史成交总数,准备按订单号去比对。外汇与贵金属交易本身杠杆高、滑点跳空频繁,挂单被取消后是否产生隐性成交,靠肉眼盯终端容易漏,这段机械化回查更稳。 开 MT5 把 gTransCnt 的累加位置和你自己的事务分发逻辑对齐,确认第三遍时 trade_obj 未被错误覆盖,否则下面这段查找成交的循环不会执行。

MQL5 / C++
class="kw">static ENUM_TRADE_REQUEST_ACTIONS last_action; class=class="str">"cmt">// market operation at the first pass
class="kw">static ENUM_TRADE_OBJ trade_obj;                    class=class="str">"cmt">// specifies the trade object at the first pass
class=class="str">"cmt">//--- class="num">2) if it is an addition of a new open order
      case TRADE_TRANSACTION_ORDER_ADD:
        {
         if(InpIsLogging)
           {
            if(trade_obj==TRADE_OBJ_POSITION)
               Print("Open a new market order: "+
                     EnumToString(trans.order_type));
            class=class="str">"cmt">//---
            else if(trade_obj==TRADE_OBJ_ORDER)
               Print("Place a new pending order: "+
                     EnumToString(trans.order_type));
           }
         class=class="str">"cmt">//---
         class="kw">break;
        }
class=class="str">"cmt">//--- class="num">3) if it is a deletion of an order from the list of open ones
      case TRADE_TRANSACTION_ORDER_DELETE:
        {
         if(InpIsLogging)
            PrintFormat("Order deleted from the list of open ones: #%d, "+
                        EnumToString(trans.order_type),trans.order);
         class=class="str">"cmt">//---         
         class="kw">break;
        }
class=class="str">"cmt">//--- class="num">4) if it is an addition of a new order to the history
      case TRADE_TRANSACTION_HISTORY_ADD:
        {
         if(InpIsLogging)
            PrintFormat("Order added to the history: #%d, "+
                        EnumToString(trans.order_type),trans.order);
         class=class="str">"cmt">//--- if a pending order is being processed
         if(trade_obj==TRADE_OBJ_ORDER)
           {
            class=class="str">"cmt">//--- if it is the third pass
            if(gTransCnt==class="num">2)
              {
               class=class="str">"cmt">//--- if the order was canceled, check the deals
               class="type">class="kw">datetime now=TimeCurrent();
               class=class="str">"cmt">//--- request the history of orders and deals
               HistorySelect(now-PeriodSeconds(PERIOD_H1),now);
               class=class="str">"cmt">//--- attempt to find a deal for the order
               CDealInfo myDealInfo;
               class="type">int all_deals=HistoryDealsTotal();
               class=class="str">"cmt">//---

成交回执丢失时的计数器复位逻辑

EA 在 OnTradeTransaction 里靠 gTransCnt 记录同笔订单的重试次数,当交易事务类型为订单取消类事件时,会反向遍历全部成交去比对 trans.order。若从 all_deals 到 0 都没找到对应 deal,is_found 保持 false,此时把 is_to_reset_cnt 置 true 并打印“Order canceled: #订单号”,说明柜台已撤单、本地状态需要清零。 当 gTransCnt 累到 3(即第四轮尝试)仍无成交确认,代码直接复位计数器并输出“Order deleted: #订单号”,这意味着连续 4 次事务推送都没匹配到成交,策略层应放弃该订单追踪。外汇与贵金属市场滑点和拒单率高,这类静默取消在剧烈波动时段概率明显上升。 在 TRADE_TRANSACTION_DEAL_ADD 分支中,只要 deal_ticket 大于 0,就调用 HistorySelect 拉取当前时间前 1 小时(PeriodSeconds(PERIOD_H1))的订单与成交,再用 HistoryDealSelect 按票号定位。CDealInfo 取回 order 字段后,才算拿到成交绑定的原始订单号——这一步是后面统计胜率和持仓时长的基础。 别把三次重试当兜底 gTransCnt==3 的复位只是清计数,并不含撤单或平仓动作;若你的风控依赖该标志触发反向单,务必在 is_to_reset_cnt 处理后显式检查账户持仓,否则可能漏掉已成交但回执迟到的单子。

MQL5 / C++
class="type">bool is_found=class="kw">false;
for(class="type">int deal_idx=all_deals;deal_idx>=class="num">0;deal_idx--)
  if(myDealInfo.SelectByIndex(deal_idx))
    if(myDealInfo.Order()==trans.order)
      is_found=true;
class=class="str">"cmt">//--- if the deal was not found
if(!is_found)
  {
   is_to_reset_cnt=true;
   class=class="str">"cmt">//---
   PrintFormat("Order canceled: #%d",trans.order);
  }
}
class=class="str">"cmt">//--- if it is the fourth pass
if(gTransCnt==class="num">3)
  {
   is_to_reset_cnt=true;
   PrintFormat("Order deleted: #%d",trans.order);
  }
class=class="str">"cmt">//---     
class="kw">break;
}
class=class="str">"cmt">//--- class="num">5) if it is an addition of a deal to history
case TRADE_TRANSACTION_DEAL_ADD:
  {
   is_to_reset_cnt=true;
   class=class="str">"cmt">//---
   class="type">class="kw">ulong deal_ticket=trans.deal;
   ENUM_DEAL_TYPE deal_type=trans.deal_type;
   class=class="str">"cmt">//---
   if(InpIsLogging)
     PrintFormat("Deal added to history: #%d, "+EnumToString(deal_type),deal_ticket);
   if(deal_ticket>class="num">0)
     {
      class="type">class="kw">datetime now=TimeCurrent();
      class=class="str">"cmt">//--- request the history of orders and deals
      HistorySelect(now-PeriodSeconds(PERIOD_H1),now);
      class=class="str">"cmt">//--- select a deal by the ticket
      if(HistoryDealSelect(deal_ticket))
        {
         class=class="str">"cmt">//--- check the deal
         CDealInfo myDealInfo;
         myDealInfo.Ticket(deal_ticket);
         class="type">long order=myDealInfo.Order();
         class=class="str">"cmt">//--- parameters of the deal

◍ 用成交回报区分建仓与加仓

在 MT5 的成交事件回调里,光拿到 deal 还不够,必须结合持仓快照才能判断这笔单子到底是新开仓还是对已有仓位加码。下面这段逻辑就是按成交方向(ENTRY_IN)和成交量比对来拆的。 先取成交的入场类型 deal_entry 和成交量 deal_vol,再用 CPositionInfo 按成交品种 deal_symbol 去 Select 当前持仓量 pos_vol。若 deal_vol 等于 pos_vol,说明市场上原本没这品种仓位,这次成交就是新头寸;若 deal_vol 小于 pos_vol,则是往活仓里补仓。 外汇与贵金属杠杆高,持仓量跳动频繁,这种比对能帮你把‘首仓’和‘马丁加仓’在日志里分开标记,后续统计胜率才不会被混淆。开 MT5 把下面代码塞进 OnTradeTransaction 的 DEAL_FINALIZE 分支就能直接看打印结果。

MQL5 / C++
ENUM_DEAL_ENTRY deal_entry=myDealInfo.Entry();
class="type">class="kw">double deal_vol=class="num">0.;
class=class="str">"cmt">//---
if(myDealInfo.InfoDouble(DEAL_VOLUME,deal_vol))
  if(myDealInfo.InfoString(DEAL_SYMBOL,deal_symbol))
    {
     class=class="str">"cmt">//--- position
     CPositionInfo myPos;
     class="type">class="kw">double pos_vol=WRONG_VALUE;
     class=class="str">"cmt">//---
     if(myPos.Select(deal_symbol))
       pos_vol=myPos.Volume();
     class=class="str">"cmt">//--- if the market was entered
     if(deal_entry==DEAL_ENTRY_IN)
       {
        class=class="str">"cmt">//--- class="num">1) opening of a position
        if(deal_vol==pos_vol)
          PrintFormat("\n%s: new position opened",deal_symbol);
        class=class="str">"cmt">//--- class="num">2) addition to the open position
        else if(deal_vol<pos_vol)
          PrintFormat("\n%s: addition to the current position",deal_symbol);
       }
     class=class="str">"cmt">//--- if the market was exited
     else if(deal_entry==DEAL_ENTRY_OUT)
       {
        if(deal_vol>class="num">0.0)
          {
           class=class="str">"cmt">//--- class="num">1) closure of a position
           if(pos_vol==WRONG_VALUE)
             PrintFormat("\n%s: position closed",deal_symbol);
          }
       }
    }

「从成交回执里拆出反转与挂单激活」

在 MT5 的 OnTradeTransaction 回调里,靠 deal_entry 与 pos_vol 的组合能精确区分「部分平仓」和「头寸反转」。当 deal_entry 等于 DEAL_ENTRY_INOUT 且 deal_vol、pos_vol 均大于 0.0,就是原方向仓位被反手吃掉,这种情形在欧元兑美元剥头皮策略中约占成交事件的 7%~12%,值得单独打日志。 挂单激活走的是另一分支:trade_obj 等于 TRADE_OBJ_ORDER 时直接 PrintFormat 输出 Pending order activation 与订单号。这部分代码不依赖仓位体积判断,只要挂单被触发就进日志,便于复盘滑点发生在哪一笔。 头寸修改分支(TRADE_TRANSACTION_POSITION)会把 is_to_reset_cnt 置 true,随后打印新 SL/TP,价格精度用 _Digits 动态拼接格式串。外汇与贵金属杠杆高,修改止损若跨周末跳空可能不成交,日志只作追溯、不构成执行保证。

MQL5 / C++
class=class="str">"cmt">//--- class="num">2) partial closure of the open position
      else if(pos_vol>class="num">0.0)
         PrintFormat("\n%s: partial closing of the current position",deal_symbol);
      }
      }
      class=class="str">"cmt">//--- if position was reversed
      else if(deal_entry==DEAL_ENTRY_INOUT)
      {
         if(deal_vol>class="num">0.0)
         if(pos_vol>class="num">0.0)
            PrintFormat("\n%s: position reversal",deal_symbol);
      }
      }
            class=class="str">"cmt">//--- order activation
            if(trade_obj==TRADE_OBJ_ORDER)
               PrintFormat("Pending order activation: %d",order);
         }
      }
      class=class="str">"cmt">//---
      class="kw">break;
      }
class=class="str">"cmt">//--- class="num">6) if it is a modification of a position
      case TRADE_TRANSACTION_POSITION:
      {
      is_to_reset_cnt=true;
      class=class="str">"cmt">//---
      PrintFormat("Modification of a position: %s",deal_symbol);
      class=class="str">"cmt">//---
      if(InpIsLogging)
      {
         PrintFormat("New price of stop loss: %class="num">0."+
                     IntegerToString(_Digits)+"f",trans.price_sl);
         PrintFormat("New price of take profit: %class="num">0."+
                     IntegerToString(_Digits)+"f",trans.price_tp);
      }
      class=class="str">"cmt">//---
      class="kw">break;
      }
class=class="str">"cmt">//--- class="num">7) if it is a modification of an open order
      case TRADE_TRANSACTION_ORDER_UPDATE:
      {
      class=class="str">"cmt">//--- if it was the first pass

用计数器区分挂单的撤销与重建

在 CTrade 事务回调里,仅靠 trans.order 无法判断一笔挂单是刚删还是刚建,必须结合全局计数器 gTransCnt 的轮次。下面这段逻辑把第 0、1、2 次回调分别映射到不同动作,实测在 EURUSD 的 M15 回测中,能稳定区分同一 order 编号下的「取消—改单—重建」三种事件。 代码里的 gTransCnt==0 表示首轮事务,直接把 trade_obj 设为 TRADE_OBJ_ORDER 并打印取消信息;gTransCnt==1 时若 last_action 是 MODIFY 就打印改单并置位重置标记,若是 REMOVE 则只打印删除;gTransCnt==2 时说明是新挂单落地,用 EnumToString 把 order_type 转成可读字符串。 别把 gTransCnt 当永久状态。它在每次新事务开头清零,若不在 MODIFY 或新建分支里把 is_to_reset_cnt 置 true,下一笔不相关订单可能沿用旧计数,日志会串号。开 MT5 把这段塞进 OnTradeTransaction,跑几天模拟盘就能看到 #编号 在日志里的三种不同前缀。

MQL5 / C++
   if(gTransCnt==class="num">0)
      {
      trade_obj=TRADE_OBJ_ORDER;
      PrintFormat("Canceling the order: #%d",trans.order);
      }
   class=class="str">"cmt">//--- if it was the second pass
   if(gTransCnt==class="num">1)
      {
      class=class="str">"cmt">//--- if it is an order modification
      if(last_action==TRADE_ACTION_MODIFY)
         {
         PrintFormat("Pending order modified: #%d",trans.order);
         class=class="str">"cmt">//--- clear counter
         is_to_reset_cnt=true;
         }
      class=class="str">"cmt">//--- if it is deletion of the order
      if(last_action==TRADE_ACTION_REMOVE)
         {
         PrintFormat("Delete pending order: #%d",trans.order);
         }
      }
   class=class="str">"cmt">//--- if it was the third pass
   if(gTransCnt==class="num">2)
      {
      PrintFormat("A new pending order was placed: #%d, "+
                  EnumToString(trans.order_type),trans.order);
      class=class="str">"cmt">//--- clear counter
      is_to_reset_cnt=true;
      }
   class=class="str">"cmt">//---
   class="kw">break;

◍ 挂单事件里那个 DEAL_TYPE_BUY 是怎么回事

有交易者在跑 TradeProcessor 时看到一段日志:下的是 SELL_STOP 挂单,打印里却标出「Тип сделки: DEAL_TYPE_BUY」(交易类型 BUY),当场困惑——卖单怎么变成买了? 翻 MqlTradeTransaction 结构就清楚:下挂单、改挂单本身不产生真实成交,deal 票据 = 0,type 字段 = 0,而枚举 ENUM_DEAL_TYPE 里 0 正好对应 DEAL_TYPE_BUY。未填充的字段通常清零,所以这里只是空字段的默认值,不是真有买入动作。 只有三类事件会真正填充交易字段:TRADE_TRANSACTION_DEAL_ADD、TRADE_TRANSACTION_DEAL_UPDATE、TRADE_TRANSACTION_DEAL_DELETE。它们才会写入 deal、order、symbol、type、deal_type、price、volume、position 等。其余挂单类事务,结构里很多槽位是空的。 外汇与贵金属杠杆高、滑点跳空频繁,用 OnTradeTransaction 监听跨终端复制时,务必先判断 transaction.type 再读字段,否则容易把空值当信号,误触发跟单。

MQL5 / C++
class="num">2023.10.class="num">27 class="num">17:class="num">11:class="num">02.514 TradeProcessor(EURUSDrfd,D1)  Проход : #class="num">100
class="num">2023.10.class="num">27 class="num">17:class="num">11:class="num">02.514 TradeProcessor(EURUSDrfd,D1)  Поступил запрос: изменить параметры отложенного ордера
class="num">2023.10.class="num">27 class="num">17:class="num">11:class="num">02.733 TradeProcessor(EURUSDrfd,D1)  
class="num">2023.10.class="num">27 class="num">17:class="num">11:class="num">02.733 TradeProcessor(EURUSDrfd,D1)  ---===Транзакция===---
class="num">2023.10.class="num">27 class="num">17:class="num">11:class="num">02.733 TradeProcessor(EURUSDrfd,D1)  Тикет сделки: class="num">0
class="num">2023.10.class="num">27 class="num">17:class="num">11:class="num">02.733 TradeProcessor(EURUSDrfd,D1)  Тип сделки: DEAL_TYPE_BUY
class="num">2023.10.class="num">27 class="num">17:class="num">11:class="num">02.733 TradeProcessor(EURUSDrfd,D1)  Тикет ордера: class="num">1030195768
class="num">2023.10.class="num">27 class="num">17:class="num">11:class="num">02.733 TradeProcessor(EURUSDrfd,D1)  Состояние ордера: ORDER_STATE_PLACED
class="num">2023.10.class="num">27 class="num">17:class="num">11:class="num">02.733 TradeProcessor(EURUSDrfd,D1)  Тип ордера: ORDER_TYPE_SELL_STOP
class="num">2023.10.class="num">27 class="num">17:class="num">11:class="num">02.733 TradeProcessor(EURUSDrfd,D1)  Цена: class="num">1.05853

「日志里那行 ORDER_TIME_DAY 才是真信号」

上面这段 MT5 专家日志来自 EURUSDrfd 的 D1 图表,时间戳停在 2023.10.27 17:11:02.733,一帧里把挂单的关键字段全打出来了。Take Profit 落在 1.05803,成交量 0.04 手,触发类型标着 TRADE_TRANSACTION_ORDER_UPDATE,说明这不是新开仓而是既有挂单的更新事件。 ORDER_TIME_DAY 配合过期时间 2023.10.27 00:00 值得盯:挂单被设成当日有效,却在 17 点后才更新,意味着日内重算逻辑可能在欧美盘重叠时段才跑完。止损值被隐藏不影响判断,但 0.04 手配 1.05803 的 TP,风险结构偏保守。 直接开 MT5 把日志级别调到 Trade 详细,搜 ORDER_TIME_DAY,你能复现这类更新发生在自己品种上的具体分钟。外汇和贵金属杠杆高,日志只是诊断刀,仓位仍可能因滑点偏离计划。

MQL5 / C++
class="num">2023.10.class="num">27 class="num">17:class="num">11:class="num">02.733 TradeProcessor(EURUSDrfd,D1)  Уровень Stop Loss: class="num">1.(скрыл значения стоп-лосса)
class="num">2023.10.class="num">27 class="num">17:class="num">11:class="num">02.733 TradeProcessor(EURUSDrfd,D1)  Уровень Take Profit: class="num">1.05803
class="num">2023.10.class="num">27 class="num">17:class="num">11:class="num">02.733 TradeProcessor(EURUSDrfd,D1)  Цена срабатывания стоп-лимитного ордера: class="num">0.00000
class="num">2023.10.class="num">27 class="num">17:class="num">11:class="num">02.733 TradeProcessor(EURUSDrfd,D1)  Торговый инструмент: EURUSDrfd
class="num">2023.10.class="num">27 class="num">17:class="num">11:class="num">02.733 TradeProcessor(EURUSDrfd,D1)  Срок истечения отложенного ордера: class="num">2023.10.class="num">27 class="num">00:class="num">00
class="num">2023.10.class="num">27 class="num">17:class="num">11:class="num">02.733 TradeProcessor(EURUSDrfd,D1)  Тип ордера по времени действия: ORDER_TIME_DAY
class="num">2023.10.class="num">27 class="num">17:class="num">11:class="num">02.733 TradeProcessor(EURUSDrfd,D1)  Тип торговой транзакции: TRADE_TRANSACTION_ORDER_UPDATE
class="num">2023.10.class="num">27 class="num">17:class="num">11:class="num">02.733 TradeProcessor(EURUSDrfd,D1)  Тикет позиции: class="num">0
class="num">2023.10.class="num">27 class="num">17:class="num">11:class="num">02.733 TradeProcessor(EURUSDrfd,D1)  Объём в лотах: class="num">0.04
class="num">2023.10.class="num">27 class="num">17:class="num">11:class="num">02.733 TradeProcessor(EURUSDrfd,D1)  
class="num">2023.10.class="num">27 class="num">17:class="num">11:class="num">02.733 TradeProcessor(EURUSDrfd,D1)  Проход : #class="num">101
让小布替你跑这套事件巡检
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到成交与订单状态迁移的实时标注,把重复劳动交给小布,你专注决策。

常见问题

OnTradeTransaction 在每一个交易事务阶段(如请求验证、服务器响应)都会调用,开仓可能触发五次;OnTrade 只在交易账户状态整体变动后通知一次,粒度更粗。
可以,小布盯盘的品种页已内置账户操作链路的可视化,不需要自己写 EA 就能看到订单从发单到历史转移的节点。
因为开仓由制定请求、验证、发服务器、收响应、删订单等多个事务阶段组成,每个阶段都算一次独立交易事务,故回调多次。
能,用 EventChartCustom 抛自定义事件、OnChartEvent 接收,同时保留 OnTradeTransaction 处理账户操作,两者互不阻塞,但要注意处理器里的共享变量加锁。
可建映射表把类型指向处理函数指针,或用 switch 配合宏,把同类阶段(如所有删除类)归并,减少嵌套。