交易事务. 请求和响应结构、描述和记录(基础篇)
📘

交易事务. 请求和响应结构、描述和记录(基础篇)

第 1/3 篇

「交易请求的收发结构怎么看」

在 MT5 里做自动化交易,绕不开「请求—响应」这一对结构。每次下单、改单、撤单,EA 都要先组装一个请求包发给交易服务器,服务器回一个响应包,里面带着执行结果和ticket。 请求结构主要包含动作类型、品种、手数、价格、偏差、订单类型、止损止盈等字段;响应结构则回吐重填价格、成交量和错误代码。理解这两层,是写稳交易类 EA 的前提。 以 2024 年 2 月 22 日一篇社区示例为例,该文获 1409 次查看、82 条讨论,说明基础事务结构仍是高频痛点。外汇与贵金属杠杆高,请求字段填错可能瞬间错单,实盘前务必在策略测试器跑通。 打开 MT5 按 F4 进 MetaEditor,搜 MqlTradeRequestMqlTradeResult 两个结构体,对照官方字段逐条读一遍,比看十篇泛文都管用。

◍ CTrade 类封装了哪些交易接口

MQL5 标准库里的 CTrade 类把下单、挂单、平仓等动作收敛成一组直白方法,省去手工填 MqlTradeRequest 的繁琐。打开仓位只需调 OpenBuy / OpenSell,平仓有 ClosePosition 与 ClosePositionsAll,挂单则细分到 SetBuyStop、SetBuyLimit、SetSellStopLimit 等七种。 每笔请求底层都依赖 MqlTradeRequest 结构,字段覆盖交易量、价格、止损获利位、订单类型、有效期与备注等 17 项。发送前可用 MqlTradeCheckResult 做预检,返回响应代码、余额、净值、保证金水平等数据,能在实盘前暴露多数参数错误。 成交后由 MqlTradeResult 回写运行代码、订单号、成交价、买卖报价及经纪商备注;异步事务则走 MqlTradeTransaction,携带事务类型、订单状态与仓位编号。外汇与贵金属杠杆高,任何接口调用失败都可能漏单,建议在策略初始化时先跑一次 OpenBuy 微型手数验证通道可用性。

从填结构到抓回执:MT5 下单链路怎么跑通

MT5 里所有下单、改单、平仓动作都收敛到 OrderSend() 一个入口,第一个参数永远是 MqlTradeRequest 结构。这个结构的 action 字段决定你要干的事:市价成交、挂单、改 SLTP、改挂单参数、删挂单、反向仓平仓,分别对应 TRADE_ACTION_DEAL / PENDING / SLTP / MODIFY / REMOVE / CLOSE_BY 六种枚举。填错 action 类型,后面字段填了也白填——每种操作只认自己那几个必填项。 动手前先用 OrderCheck() 探一遍:把填好的结构和 MqlTradeCheckResult 变量传进去,资金不足或参数非法它会返回 false;返回 true 只代表结构基础校验过,不保证服务器真给你成交。真要发单再调 OrderSend(),第二个参数接 MqlTradeResult,回执写进这个结构里,但函数返回成功仅意味『服务器收了请求』,不是『单子已成交』。 异步场景更值得盯。OrderSendAsync() 发出去后,终端给每次请求打一个 request_id,服务器回的消息进 OnTradeTransaction() 处理函数,里面三个参数里只有后两个(请求描述、执行结果)在 TRADE_TRANSACTION_REQUEST 类型时才有效。靠 request_id 把『我发的单』和『回来的果』对上号,就能在回调里完整追踪挂单进场、成交、拒绝的全过程。 一套标准下单流程就五步:填 MqlTradeRequest → OrderCheck() 验证 → OrderSend() 发单 → 必要时读 MqlTradeResult → OnTradeTransaction() 里落日志分析。下面这段是 MqlTradeRequest 的原生结构定义,逐行看清楚每个字段的语义,后面写通知型 EA 打印全链路事件就靠它打底。

MQL5 / C++
class="kw">struct class="type">MqlTradeRequest
  {
   ENUM_TRADE_REQUEST_ACTIONS      action;            class=class="str">"cmt">// Type of a performed action
   class="type">ulong                            magic;            class=class="str">"cmt">// EA stamp(magic number ID)
   class="type">ulong                            order;            class=class="str">"cmt">// Order ticket
   class="type">class="kw">string                           symbol;           class=class="str">"cmt">// Symbol name
   class="type">class="kw">double                           volume;           class=class="str">"cmt">// Requested volume of a deal in lots
   class="type">class="kw">double                           price;            class=class="str">"cmt">// Price 
   class="type">class="kw">double                           stoplimit;        class=class="str">"cmt">// StopLimit order level
   class="type">class="kw">double                           sl;               class=class="str">"cmt">// Stop Loss order level
   class="type">class="kw">double                           tp;               class=class="str">"cmt">// Take Profit order level
   class="type">ulong                            deviation;        class=class="str">"cmt">// Maximum acceptable deviation from the requested price
   ENUM_ORDER_TYPE                  type;             class=class="str">"cmt">// Order type
   ENUM_ORDER_TYPE_FILLING          type_filling;     class=class="str">"cmt">// Order filling type
   ENUM_ORDER_TYPE_TIME             type_time;        class=class="str">"cmt">// Order lifetime type

「挂单结构与成交前校验的字段映射」

在 MT5 的订单发送流程里,先填 MqlTradeRequest 再拿 MqlTradeCheckResult 做预检,是绕不开的两步。前者描述“我想下什么单”,后者回吐“经纪商觉得这单下去账户会变成什么样”。 MqlTradeRequest 末尾几个字段很容易被忽略:expiration 只在 ORDER_TIME_SPECIFIED 类型挂单时生效,comment 是随单备注,position 与 position_by 分别挂的是持仓单号和对冲反向单号。改错这几个值,订单可能直接被服务器拒掉。 MqlTradeCheckResult 的价值在于“先算后下”。retcode 非 0 就别发单;balance、equity 是成交后的账面与净值,profit 是浮动盈亏,margin 到 margin_level 四项是保证金占用与水位。外汇与贵金属杠杆高,预检里 margin_level 掉到 100% 附近时,再下新单可能触发强平,务必在脚本里拦一道。 把下面结构直接贴进 MT5 的 MQ5 文件,就能在策略里调用校验,不用等报错才发现问题。

MQL5 / C++
  class="type">class="kw">datetime                expiration;     class=class="str">"cmt">// Order expiration time(for ORDER_TIME_SPECIFIED type orders)
  class="type">class="kw">string                  comment;         class=class="str">"cmt">// Order comment
  class="type">ulong                   position;        class=class="str">"cmt">// Position ticket
  class="type">ulong                   position_by;     class=class="str">"cmt">// Opposite position ticket
  };
class="kw">struct MqlTradeCheckResult
  {
  class="type">uint      retcode;                       class=class="str">"cmt">// Response code
  class="type">class="kw">double    balance;                       class=class="str">"cmt">// Balance after performing a deal
  class="type">class="kw">double    equity;                        class=class="str">"cmt">// Equity after performing a deal
  class="type">class="kw">double    profit;                        class=class="str">"cmt">// Floating profit
  class="type">class="kw">double    margin;                        class=class="str">"cmt">// Margin requirements
  class="type">class="kw">double    margin_free;                   class=class="str">"cmt">// Free margin
  class="type">class="kw">double    margin_level;                  class=class="str">"cmt">// Margin level
  class="type">class="kw">string    comment;                       class=class="str">"cmt">// Comment on the response code(error description)
  };

◍ 键盘状态与服务器回报的底层读取函数

用快捷键控EA时,先得知道Ctrl和Shift是否被按住。MQL5里不用自己挂钩子,直接问终端:TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL)返回值小于0就代表Ctrl处于按下态,Shift同理换TERMINAL_KEYSTATE_SHIFT。这两个布尔函数返回快、无副作用,适合在OnChartEvent里每次按键都跑一遍。 交易发单后,服务器会给一个数字回报码。RetcodeDescription()把常用码翻成可读串:比如0是"OK (0)",10004是REQUOTE,10006是REJECT,10007是CANCEL。第二个参数ext_descr设为true时追加括号里的英文长描述,方便在日志里排查拒单原因。 订单类型、成交填充模式、订单有效期这些枚举,最好也各写一个小函数做描述映射。特别是SYMBOL_FILLING_MODE,用SymbolInfoInteger(SYMBOL_FILLING_MODE)拿到的是标志组合,ORDER_FILLING_RETURN除市场执行模式外常开,发单前不核对就可能报参数错。外汇和贵金属杠杆高,回报码解读错一笔就可能放大亏损,验证函数逻辑后再上实盘。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Return the state of the Ctrl key                                                |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">bool</span> IsCtrlKeyPressed(<span class="keyword">class="type">void</span>)
  {
   <span class="keyword">class="kw">return</span>(::<span class="functions">TerminalInfoInteger</span>(<span class="macro">TERMINAL_KEYSTATE_CONTROL</span>)&lt;<span class="number">class="num">0</span>);
  }
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Return the state of the Shift key                                |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">bool</span> IsShiftKeyPressed(<span class="keyword">class="type">void</span>)
  {
   <span class="keyword">class="kw">return</span>(::<span class="functions">TerminalInfoInteger</span>(<span class="macro">TERMINAL_KEYSTATE_SHIFT</span>)&lt;<span class="number">class="num">0</span>);
  }
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Return a description of the trade server class="kw">return code             |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">class="kw">string</span> RetcodeDescription(<span class="keyword">const</span> <span class="keyword">class="type">uint</span> retcode,<span class="keyword">class="type">bool</span> ext_descr=<span class="macro">false</span>)
  {
   <span class="keyword">class="kw">switch</span>(retcode)
     {
      <span class="comment">class=class="str">"cmt">//--- Done</span>
      <span class="keyword">case</span> <span class="number">class="num">0</span>                                       :   <span class="keyword">class="kw">return</span> <span class="class="type">class="kw">string">"OK(class="num">0)"</span>;
      <span class="comment">class=class="str">"cmt">//--- Requote</span>
      <span class="keyword">case</span> <span class="macro">TRADE_RETCODE_REQUOTE</span>                   :   <span class="keyword">class="kw">return</span> <span class="class="type">class="kw">string">"class="num">10004 REQUOTE"</span>+(ext_descr ? <span class="class="type">class="kw">string">" (Requote)"</span> : <span class="class="type">class="kw">string">""</span>);
      <span class="comment">class=class="str">"cmt">//--- Request rejected</span>
      <span class="keyword">case</span> <span class="macro">TRADE_RETCODE_REJECT</span>                   :   <span class="keyword">class="kw">return</span> <span class="class="type">class="kw">string">"class="num">10006 REJECT"</span>+(ext_descr ? <span class="class="type">class="kw">string">" (Request rejected)"</span> : <span class="class="type">class="kw">string">""</span>);
      <span class="comment">class=class="str">"cmt">//--- Request canceled by trader</span>
      <span class="keyword">case</span> <span class="macro">TRADE_RETCODE_CANCEL</span>                   :   <span class="keyword">class="kw">return</span> <span class="class="type">class="kw">string">"class="num">10007 CANCEL"</span>+(ext_descr ? <span class="class="type">class="kw">string">" (Request canceled by trader)"</span> : <span class="class="type">class="kw">string">""</span>);
      <span class="comment">class=class="str">"cmt">//--- Order placed</span>

成交回执码的后半段映射

上面这段 switch 分支接着把 10008 到 10019 这一批交易回执码翻译成可读字符串,做 EA 日志或报警时直接用人话而不是冷冰冰的数字。10009 DONE 代表整单请求完全成交,10010 DONE_PARTIAL 则提示只成交了一部分,黄金剥头皮策略里若碰上流动性薄的时段,部分成交概率会明显抬升。

  • 到 10013 属于典型失败类:ERROR 是服务端处理出错,TIMEOUT 是超时撤单,INVALID 则是请求本身结构有问题;这三类在 MT5 真实账户回测和实盘里都可能出现,需要分别计数。

10014~10017 基本是参数层面被拒:手数、价格、止损止盈位置不合规,或者品种处于禁交易状态。10018 MARKET_CLOSED 在非农间隙或平台维护时常见,10019 NO_MONEY 则说明保证金不够——外汇和贵金属杠杆高,这条最容易在重仓时触发。 把 ext_descr 参数设为 true,返回串会带上括号里的英文说明,调试阶段建议开着;上线后关掉能省一点日志体积。

MQL5 / C++
   case TRADE_RETCODE_PLACED                :  class="kw">return "class="num">10008 PLACED"+(ext_descr ? " (Order placed)" : "");
   class=class="str">"cmt">//--- Request completed
   case TRADE_RETCODE_DONE                  :  class="kw">return "class="num">10009 DONE"+(ext_descr ? " (Request completed)" : "");
   class=class="str">"cmt">//--- Request completed partially
   case TRADE_RETCODE_DONE_PARTIAL          :  class="kw">return "class="num">10010 DONE_PARTIAL"+(ext_descr ? " (Only part of the request was completed)" : "");
   class=class="str">"cmt">//--- Request processing error
   case TRADE_RETCODE_ERROR                 :  class="kw">return "class="num">10011 ERROR"+(ext_descr ? " (Request processing error)" : "");
   class=class="str">"cmt">//--- Request canceled by timeout
   case TRADE_RETCODE_TIMEOUT               :  class="kw">return "class="num">10012 TIMEOUT"+(ext_descr ? " (Request canceled by timeout)" : "");
   class=class="str">"cmt">//--- Invalid request
   case TRADE_RETCODE_INVALID               :  class="kw">return "class="num">10013 INVALID"+(ext_descr ? " (Invalid request)" : "");
   class=class="str">"cmt">//--- Invalid volume in the request
   case TRADE_RETCODE_INVALID_VOLUME        :  class="kw">return "class="num">10014 INVALID_VOLUME"+(ext_descr ? " (Invalid volume in the request)" : "");
   class=class="str">"cmt">//--- Invalid price in the request
   case TRADE_RETCODE_INVALID_PRICE         :  class="kw">return "class="num">10015 INVALID_PRICE"+(ext_descr ? " (Invalid price in the request)" : "");
   class=class="str">"cmt">//--- Invalid stops in the request
   case TRADE_RETCODE_INVALID_STOPS         :  class="kw">return "class="num">10016 INVALID_STOPS"+(ext_descr ? " (Invalid stops in the request)" : "");
   class=class="str">"cmt">//--- Trading disabled
   case TRADE_RETCODE_TRADE_DISABLED        :  class="kw">return "class="num">10017 TRADE_DISABLED"+(ext_descr ? " (Trade is disabled)" : "");
   class=class="str">"cmt">//--- Market is closed
   case TRADE_RETCODE_MARKET_CLOSED         :  class="kw">return "class="num">10018 MARKET_CLOSED"+(ext_descr ? " (Market is closed)" : "");
   class=class="str">"cmt">//--- There is not enough money to complete the request
   case TRADE_RETCODE_NO_MONEY              :  class="kw">return "class="num">10019 NO_MONEY"+(ext_descr ? " (There is not enough money to complete the request)" : "");
   class=class="str">"cmt">//--- Prices changed

常见问题

先看请求结构里填什么、响应结构回什么:请求填品种、方向、手数、类型,响应拿回执码和成交号。搞清这两块字段映射,下单链路就通了一半。
CTrade 封装了市价单、挂单、改单、删单等常用接口,普通策略直接调方法即可,不用手填底层结构;只有做特殊校验才需碰原始字段。
小布可替你跑下单链路诊断,自动比对挂单结构与成交前校验字段,并标记回执码异常,你只看结论不用翻底层函数。
把挂单结构里的价格、过期时间映射到成交前校验字段,本地先做范围判断;服务器回报的非零回执码就是被拒原因。
回执码后半段是服务器对请求的具体处理结果映射,需对照表查;键盘状态读取用于底层感知终端交互锁,普通策略很少直接用。