交易事务. 请求和响应结构、描述和记录·进阶篇
📡

交易事务. 请求和响应结构、描述和记录·进阶篇

(2/3)· 从 MqlTradeRequest 到 MqlTradeTransaction,理清每字段在实战 EA 里的真实去向

偏理论进阶 第 2/3 篇

不少交易者把 OrderSend() 当黑盒,填错 action 对应的字段就怪服务器拒单。其实 MQL5 的下单链路是一组明确结构在对话,读不懂请求和响应谁承载什么,调试 EA 只能靠瞎试。

交易返回码 10020–10031 的逐行含义

在 MT5 的 CTrade / 订单校验逻辑里,返回码 10020 到 10031 属于「请求被终端或服务器拒绝但未成交」的区间,和 10009(已成交)完全两回事。下面这段 switch 分支把每个宏映射成可读字符串,开 MT5 按 F4 搜 TRADE_RETCODE_PRICE_CHANGED 就能定位到标准库定义。 注意 ext_descr 这个布尔参数:为真时拼接英文括号说明,为假时只留裸码。实盘里建议日志统一传 true,否则排查「10024」这种码连上下文都没有。 外汇与贵金属杠杆高,这类拒绝码频繁出现往往意味着报价断裂或服务器限流,可能触发滑点放大,需结合连通性判断而非盲目重试。

MQL5 / C++
   case TRADE_RETCODE_PRICE_CHANGED         :  class="kw">return "class="num">10020 PRICE_CHANGED"+(ext_descr ? " (Prices changed)" : "");
   class=class="str">"cmt">//--- There are no quotes to process the request
   case TRADE_RETCODE_PRICE_OFF              :  class="kw">return "class="num">10021 PRICE_OFF"+(ext_descr ? " (There are no quotes to process the request)" : "");
   class=class="str">"cmt">//--- Invalid order expiration date in the request
   case TRADE_RETCODE_INVALID_EXPIRATION     :  class="kw">return "class="num">10022 INVALID_EXPIRATION"+(ext_descr ? " (Invalid order expiration date in the request)" : "");
   class=class="str">"cmt">//--- Order state changed
   case TRADE_RETCODE_ORDER_CHANGED          :  class="kw">return "class="num">10023 ORDER_CHANGED"+(ext_descr ? " (Order state changed)" : "");
   class=class="str">"cmt">//--- Too frequent requests
   case TRADE_RETCODE_TOO_MANY_REQUESTS      :  class="kw">return "class="num">10024 TOO_MANY_REQUESTS"+(ext_descr ? " (Too frequent requests)" : "");
   class=class="str">"cmt">//--- No changes in request
   case TRADE_RETCODE_NO_CHANGES             :  class="kw">return "class="num">10025 NO_CHANGES"+(ext_descr ? " (No changes in request)" : "");
   class=class="str">"cmt">//--- Autotrading disabled by server
   case TRADE_RETCODE_SERVER_DISABLES_AT     :  class="kw">return "class="num">10026 SERVER_DISABLES_AT"+(ext_descr ? " (Autotrading disabled by server)" : "");
   class=class="str">"cmt">//--- Autotrading disabled by client terminal
   case TRADE_RETCODE_CLIENT_DISABLES_AT     :  class="kw">return "class="num">10027 CLIENT_DISABLES_AT"+(ext_descr ? " (Autotrading disabled by client terminal)" : "");
   class=class="str">"cmt">//--- Request locked for processing
   case TRADE_RETCODE_LOCKED                 :  class="kw">return "class="num">10028 LOCKED"+(ext_descr ? " (Request locked for processing)" : "");
   class=class="str">"cmt">//--- Order or position frozen
   case TRADE_RETCODE_FROZEN                 :  class="kw">return "class="num">10029 FROZEN"+(ext_descr ? " (Order or position frozen)" : "");
   class=class="str">"cmt">//--- Invalid order filling type
   case TRADE_RETCODE_INVALID_FILL           :  class="kw">return "class="num">10030 INVALID_FILL"+(ext_descr ? " (Invalid order filling type)" : "");
   class=class="str">"cmt">//--- No connection with the trade server
   case TRADE_RETCODE_CONNECTION             :  class="kw">return "class="num">10031 CONNECTION"+(ext_descr ? " (No connection with the trade server)" : "");

「交易返回码 10032–10043 的逐条映射」

在 EA 的报错解析函数里,10032 到 10043 这一段返回码专门描述账户与品种层面的下单限制。 live 账户限制、挂单数量上限、持仓量上限都落在这个区间,调试时若看到这些数字,先怀疑账户类型或品种规则,而不是策略逻辑。 下面这段 switch-case 把每个宏映射成可读字符串,ext_descr 为 true 时追加英文说明。直接拷进你的 Trade 辅助类就能用: case TRADE_RETCODE_ONLY_REAL : return "10032 ONLY_REAL"+(ext_descr ? " (Operation is allowed only for live accounts)" : ""); //--- Number of pending orders reached the limit case TRADE_RETCODE_LIMIT_ORDERS : return "10033 LIMIT_ORDERS"+(ext_descr ? " (The number of pending orders has reached the limit)" : ""); //--- Volume of orders and positions for the symbol reached the limit case TRADE_RETCODE_LIMIT_VOLUME : return "10034 LIMIT_VOLUME"+(ext_descr ? " (The volume of orders and positions for the symbol has reached the limit)" : ""); //--- Incorrect or prohibited order type case TRADE_RETCODE_INVALID_ORDER : return "10035 INVALID_ORDER"+(ext_descr ? " (Incorrect or prohibited order type)" : ""); //--- Position with specified POSITION_IDENTIFIER already closed case TRADE_RETCODE_POSITION_CLOSED : return "10036 POSITION_CLOSED"+(ext_descr ? " (Position with the specified POSITION_IDENTIFIER has already been closed)" : ""); //--- Close volume exceeds the current position volume case TRADE_RETCODE_INVALID_CLOSE_VOLUME: return "10038 INVALID_CLOSE_VOLUME"+(ext_descr ? " (A close volume exceeds the current position volume)" : ""); //--- Close order already exists for specified position case TRADE_RETCODE_CLOSE_ORDER_EXIST : return "10039 CLOSE_ORDER_EXIST"+(ext_descr ? " (A close order already exists for a specified position)" : ""); //--- Number of positions reached the limit case TRADE_RETCODE_LIMIT_POSITIONS : return "10040 LIMIT_POSITIONS"+(ext_descr ? " (The number of positions has reached the limit)" : ""); //--- Pending order activation request is rejected, order is canceled case TRADE_RETCODE_REJECT_CANCEL : return "10041 REJECT_CANCEL"+(ext_descr ? " (The pending order activation request is rejected, the order is canceled)" : ""); //--- Request rejected, only long positions are allowed on symbol case TRADE_RETCODE_LONG_ONLY : return "10042 LONG_ONLY"+(ext_descr ? " (Only long positions are allowed)" : ""); //--- Request rejected, only short positions are allowed on symbol case TRADE_RETCODE_SHORT_ONLY : return "10043 SHORT_ONLY"+(ext_descr ? " (Only short positions are allowed)" : ""); 逐行看:10033 与 10040 分别对应挂单数和持仓数触顶,经纪商对黄金属类品种常把挂单上限压到 100 以内,回测不报错实盘却频发 10033 时要核对账户条款。 10038 提醒平仓位大于持仓余量,网格类策略在部分平仓后若还按原仓位发单,就会撞这块墙。外汇与贵金属杠杆高,这类限制随时随经纪商规则变动,上 MT5 用 Print(retcode) 抓真实值最稳。

MQL5 / C++
case TRADE_RETCODE_ONLY_REAL           : class="kw">return "class="num">10032 ONLY_REAL"+(ext_descr ? " (Operation is allowed only for live accounts)" : "");
class=class="str">"cmt">//--- Number of pending orders reached the limit
case TRADE_RETCODE_LIMIT_ORDERS        : class="kw">return "class="num">10033 LIMIT_ORDERS"+(ext_descr ? " (The number of pending orders has reached the limit)" : "");
class=class="str">"cmt">//--- Volume of orders and positions for the symbol reached the limit
case TRADE_RETCODE_LIMIT_VOLUME        : class="kw">return "class="num">10034 LIMIT_VOLUME"+(ext_descr ? " (The volume of orders and positions for the symbol has reached the limit)" : "");
class=class="str">"cmt">//--- Incorrect or prohibited order type
case TRADE_RETCODE_INVALID_ORDER       : class="kw">return "class="num">10035 INVALID_ORDER"+(ext_descr ? " (Incorrect or prohibited order type)" : "");
class=class="str">"cmt">//--- Position with specified POSITION_IDENTIFIER already closed
case TRADE_RETCODE_POSITION_CLOSED     : class="kw">return "class="num">10036 POSITION_CLOSED"+(ext_descr ? " (Position with the specified POSITION_IDENTIFIER has already been closed)" : "");
class=class="str">"cmt">//--- Close volume exceeds the current position volume
case TRADE_RETCODE_INVALID_CLOSE_VOLUME: class="kw">return "class="num">10038 INVALID_CLOSE_VOLUME"+(ext_descr ? " (A close volume exceeds the current position volume)" : "");
class=class="str">"cmt">//--- Close order already exists for specified position
case TRADE_RETCODE_CLOSE_ORDER_EXIST   : class="kw">return "class="num">10039 CLOSE_ORDER_EXIST"+(ext_descr ? " (A close order already exists for a specified position)" : "");
class=class="str">"cmt">//--- Number of positions reached the limit
case TRADE_RETCODE_LIMIT_POSITIONS     : class="kw">return "class="num">10040 LIMIT_POSITIONS"+(ext_descr ? " (The number of positions has reached the limit)" : "");
class=class="str">"cmt">//--- Pending order activation request is rejected, order is canceled
case TRADE_RETCODE_REJECT_CANCEL       : class="kw">return "class="num">10041 REJECT_CANCEL"+(ext_descr ? " (The pending order activation request is rejected, the order is canceled)" : "");
class=class="str">"cmt">//--- Request rejected, only class="type">long positions are allowed on symbol
case TRADE_RETCODE_LONG_ONLY           : class="kw">return "class="num">10042 LONG_ONLY"+(ext_descr ? " (Only class="type">long positions are allowed)" : "");
class=class="str">"cmt">//--- Request rejected, only class="type">short positions are allowed on symbol
case TRADE_RETCODE_SHORT_ONLY          : class="kw">return "class="num">10043 SHORT_ONLY"+(ext_descr ? " (Only class="type">short positions are allowed)" : "");

◍ 成交回码与订单类型的可读化拆解

MT5 交易类函数返回的 retcode 里,10044 到 10046 这组常被新手忽略:10044 代表账户只允许平仓、10045 是 FIFO 规则下只能按先进先出平仓、10046 说明该账户禁止同品种反向锁仓。若用默认描述,日志里只蹦出数字,排查 EA 报错时极费时间。 下面这段 switch 分支把上述回码转成带含义的字符串,ext_descr 为 true 时还会补英文括号说明。注意 10034 LIMIT_VOLUME 表示品种订单和持仓数已达上限,这是 broker 端限制而非代码 bug。 OrderTypeDescription() 则负责把枚举名读成人话:从 ENUM_ORDER_TYPE 的字符串里截掉前 11 个字符,全转小写后首字母大写,再循环把下划线替换成空格并大写后一字母。比如 ORDER_TYPE_BUY_LIMIT 会变成 'Buy Limit'。 在 MT5 里新建脚本粘入这两个函数,打印 OrderTypeDescription(ORDER_TYPE_SELL_STOP,true),能直接看到格式化结果,省去查文档的功夫。外汇与贵金属杠杆高,回码处理不当可能让 EA 在锁仓禁令下反复发单被拒。

MQL5 / C++
   case TRADE_RETCODE_CLOSE_ONLY           :  class="kw">return "class="num">10044 CLOSE_ONLY"+(ext_descr ? " (Only position closing is allowed)" : "");
   class=class="str">"cmt">//--- Request rejected, position closing for trading account is allowed only by FIFO rule
   case TRADE_RETCODE_FIFO_CLOSE           :  class="kw">return "class="num">10045 FIFO_CLOSE"+(ext_descr ? " (Position closing is allowed only by FIFO rule)" : "");
   class=class="str">"cmt">//--- Request rejected, opposite positions on a single symbol are disabled for trading account
   case TRADE_RETCODE_HEDGE_PROHIBITED     :  class="kw">return "class="num">10046 HEDGE_PROHIBITED"+(ext_descr ? " (Opposite positions on a single symbol are disabled)" : "");
   class=class="str">"cmt">//--- Unknown class="kw">return code 
   class="kw">default                                 :  class="kw">return "Undefined("+(class="type">class="kw">string)retcode+")";
   }
}
class="num">10034 LIMIT_VOLUME
class="num">10034 LIMIT_VOLUME(交易品种的订单和仓位数量已达到极限)
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the order type description                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string OrderTypeDescription(const ENUM_ORDER_TYPE type,const class="type">bool ext_descr=false)
  {
class=class="str">"cmt">//--- "Cut out" the order type from the class="type">class="kw">string obtained from enum
   class="type">class="kw">string res=StringSubstr(EnumToString(type),class="num">11);
class=class="str">"cmt">//--- Convert all received characters to lowercase and
   if(res.Lower())
   {
   class=class="str">"cmt">//--- replace the first letter from small to capital
   res.SetChar(class="num">0,class="type">class="kw">ushort(res.GetChar(class="num">0)-0x20));
   class="type">int total=(class="type">int)res.Length();  class=class="str">"cmt">// Text length
   class="type">int index=class="num">0;                  class=class="str">"cmt">// index to start searching for "_" in a text
   class=class="str">"cmt">//--- Search for underscores in a loop through all characters
   for(class="type">int i=class="num">0;i<total;i++)
     {
      class="type">int pos=StringFind(res,"_",index);
      class=class="str">"cmt">//--- If an underscore is found,
      if(pos>class="num">0)
        {
        class=class="str">"cmt">//--- replace it with space and convert the next letter to uppercase 
        res.SetChar(pos,&class="macro">#x27; &class="macro">#x27;);
        res.SetChar(pos+class="num">1,class="type">class="kw">ushort(res.GetChar(pos+class="num">1)-0x20));
        class=class="str">"cmt">//--- Set a new index for starting the search for "_"
        index=pos;
        }
     }
   }
  class="type">class="kw">string descr="";
  class="kw">switch(type)
   {

订单类型到可读文本的映射分支

在 MT5 的 EA 或脚本里,把枚举值转成人类能读的文字,最常见做法就是 switch 对 ENUM_ORDER_TYPE 逐个 case。下面这段把市价单、六种挂单、以及 CLOSE_BY 都覆盖了,default 直接 break 不补描述,避免未知类型炸日志。 市价买卖用 ORDER_TYPE_BUY / ORDER_TYPE_SELL,挂单里 BUY_LIMIT 和 SELL_LIMIT 是限价,BUY_STOP / SELL_STOP 是止损,BUY_STOP_LIMIT / SELL_STOP_LIMIT 则是触价后转限价。CLOSE_BY 对应的是用反向仓位平仓的特殊指令,外汇和贵金属品种上都可能出现,杠杆环境下误操作风险偏高。 函数末尾用 res+(!ext_descr ? "" : descr) 拼接:不传扩展描述时只返回短名,传了才把括号里的长说明带上。样例里 'Sell Limit (Sell Limit pending order)' 就是 ext_descr=true 时的输出,开 MT5 跑一遍改个 EURUSD 挂单就能验证。

MQL5 / C++
   case ORDER_TYPE_BUY              :  descr=" (Market Buy order)";                break;
   case ORDER_TYPE_SELL             :  descr=" (Market Sell order)";               break;
   case ORDER_TYPE_BUY_LIMIT        :  descr=" (Buy Limit pending order)";         break;
   case ORDER_TYPE_SELL_LIMIT       :  descr=" (Sell Limit pending order)";        break;
   case ORDER_TYPE_BUY_STOP         :  descr=" (Buy Stop pending order)";          break;
   case ORDER_TYPE_SELL_STOP        :  descr=" (Sell Stop pending order)";         break;
   case ORDER_TYPE_BUY_STOP_LIMIT   :  descr=" (Upon reaching the order price, a pending Buy Limit order is placed at the StopLimit price)"; break;
   case ORDER_TYPE_SELL_STOP_LIMIT  :  descr=" (Upon reaching the order price, a pending Sell Limit order is placed at the StopLimit price)";break;
   case ORDER_TYPE_CLOSE_BY         :  descr=" (Order to close a position by an opposite one)"; break;
   class="kw">default: break;
    }
  class="kw">return res+(!ext_descr ? "" : descr);
  class=class="str">"cmt">/* Sample output:
     Sell Limit(Sell Limit pending order)
   */
}

「把成交填充与事务类型翻成可读字符串」

在 MT5 的 EA 或脚本里,枚举值 ORDER_FILLING_RETURN、TRADE_TRANSACTION_ORDER_ADD 直接打印出来是机器串,人工排查订单日志时极不友好。下面这段函数把 ENUM_ORDER_TYPE_FILLING 和 ENUM_TRADE_TRANSACTION_TYPE 裁掉前缀,转成首字母大写的可读词,还能按需拼上解释。 OrderTypeFillingDescription 里用 StringSubstr(EnumToString(type_filling),14) 切掉前 14 个字符(即 'ORDER_FILLING_' 长度),对 RETURN 类型先整体转小写再把首字符减 0x20 升成大写。switch 中 FOK 对应 'Fill or Kill'、IOC 对应 'Immediate or Cancel'、BOC 是被动挂单不可立即成交、RETURN 是部分成交后余量不撤继续挂。 TradeTransactionTypeDescription 同理,裁掉前 18 个字符后用 StringReplace 把下划线换成空格,例如 ORDER_ADD 会变成 'Order Add'。两个函数都靠 ext_descr 参数控制是否追加括号里的长描述,默认只返短名。 外汇与贵金属杠杆交易高风险,这类字符串函数仅用于日志与界面展示,不改变任何下单逻辑与成交概率。复制进 MT5 的 include 文件,调用时传 true 就能在回测日志里看到 'Type filling: Return (In case of partial filling...)' 这样的输出,便于核对部分成交行为。

MQL5 / C++
class="type">class="kw">string OrderTypeFillingDescription(const ENUM_ORDER_TYPE_FILLING type_filling,const class="type">bool ext_descr=false)
  {
   class="type">class="kw">string res=StringSubstr(EnumToString(type_filling),class="num">14);
class=class="str">"cmt">//--- Convert all obtained symbols to lower case and replace the first letter from small to capital(for ORDER_FILLING_RETURN)
   if(type_filling==ORDER_FILLING_RETURN && res.Lower())
      res.SetChar(class="num">0,class="type">class="kw">ushort(res.GetChar(class="num">0)-0x20));
   class="type">class="kw">string descr="";
   class="kw">switch(type_filling)
     {
      case ORDER_FILLING_FOK      :  descr=" (Fill or Kill. An order can be executed in the specified volume only)";                        break;
      case ORDER_FILLING_IOC      :  descr=" (Immediate or Cancel. A deal with the volume maximally available in the market within that indicated in the order)"; break;
      case ORDER_FILLING_BOC      :  descr=" (Passive(Book or Cancel). The order can only be placed in the Depth of Market and cannot be immediately executed)"; break;
      case ORDER_FILLING_RETURN   :  descr=" (In case of partial filling, an order with remaining volume is not canceled but processed further)";                break;
      class="kw">default: break;
     }
   class="kw">return res+(!ext_descr ? "" : descr);
   class=class="str">"cmt">/* Sample output:
      Type filling: Return(In case of partial filling, an order with remaining volume is not canceled but processed further)
    */
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return a description of the trade transaction type                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string TradeTransactionTypeDescription(const ENUM_TRADE_TRANSACTION_TYPE transaction,const class="type">bool ext_descr=false)
  {
class=class="str">"cmt">//--- "Cut out" the transaction type from the class="type">class="kw">string obtained from enum
   class="type">class="kw">string res=StringSubstr(EnumToString(transaction),class="num">18);
class=class="str">"cmt">//--- Convert all obtained symbols to lower case and replace the first letter from small to capital
   if(res.Lower())
      res.SetChar(class="num">0,class="type">class="kw">ushort(res.GetChar(class="num">0)-0x20));
class=class="str">"cmt">//--- Replace all underscore characters with space in the resulting line
   StringReplace(res,"_"," ");
   class="type">class="kw">string descr="";
   class="kw">switch(transaction)
     {
      case TRADE_TRANSACTION_ORDER_ADD          :  descr=" (Adding a new open order)";                                                                                    break;

◍ 把交易事务类型翻译成可读描述

在 OnTradeTransaction 回调里拿到交易事务类型后,直接套一层 switch 就能把枚举转成人类能读的句子。下面这段分支覆盖了从挂单增删改到成交历史、持仓变动以及服务器回执的全部 11 种情形,缺一个都可能让日志里出现看不懂的空白。 实际跑起来时,TRADE_TRANSACTION_ORDER_ADD 这类会频繁触发,而 HISTORY_DELETE 在普通日内交易里可能几天才碰到一次。把 descr 拼到返回串里,样本输出形如「Order add (Adding a new open order)」,开 MT5 用专家日志看一眼就能确认你的映射没漏。 外汇与贵金属杠杆高,这类事务监听只负责把动作讲清楚,不等于信号;任何基于成交事件的自动动作都先上模拟盘验。

MQL5 / C++
      case TRADE_TRANSACTION_ORDER_UPDATE      : descr=" (Updating an open order)";                                                                                          break;
      case TRADE_TRANSACTION_ORDER_DELETE      : descr=" (Removing an order from the list of the open ones)";                                                              break;
      case TRADE_TRANSACTION_DEAL_ADD          : descr=" (Adding a deal to the history)";                                                                                   break;
      case TRADE_TRANSACTION_DEAL_UPDATE       : descr=" (Updating a deal in the history)";                                                                                break;
      case TRADE_TRANSACTION_DEAL_DELETE       : descr=" (Deleting a deal from the history)";                                                                              break;
      case TRADE_TRANSACTION_HISTORY_ADD       : descr=" (Adding an order to the history as a result of execution or cancellation)";                                       break;
      case TRADE_TRANSACTION_HISTORY_UPDATE    : descr=" (Changing an order located in the orders history)";                                                              break;
      case TRADE_TRANSACTION_HISTORY_DELETE    : descr=" (Deleting an order from the orders history)";                                                                     break;
      case TRADE_TRANSACTION_POSITION          : descr=" (Changing a position not related to a deal execution)";                                                          break;
      case TRADE_TRANSACTION_REQUEST           : descr=" (The trade request has been processed by a server and processing result has been received)";  break;
      class="kw">default: break;
   }
   class="kw">return res+(!ext_descr ? "" : descr);
   class=class="str">"cmt">/* Sample output:
      Order add(Adding a new open order)
   */
   }
把请求响应对账交给小布
小布盯盘的 AIGC 已内置交易事务结构对照,打开对应品种页即可看到请求字段与服务器回执的映射提示,你只需专注策略逻辑而非翻文档。

常见问题

action 决定其余字段如何被服务器解析,填错类型可能直接返回无效请求代码,订单不会进入市场,需在 MqlTradeCheckResult 里提前拦截。
可以,小布盯盘内置了请求与响应结构的对照视图,能提示当前品种页下常见字段遗漏,减少 EA 回测外的手工核对成本。
前者是 OrderSend 后的直接回执含成交价与代码,后者是服务器推送的事务描述结构,用于事件处理中追踪订单状态流转。
由 type_time 配合 expiration 字段设定,限价与止损类挂单若不设有效期类型,默认行为依经纪商规则可能倾向即时取消。
服务器在成交后给出的余额、净值与保证金水平才是真实账户状态,本地估算可能因点差或延迟偏离,外汇贵金属高风险下应以回执为准。