交易事务. 请求和响应结构、描述和记录·进阶篇
(2/3)· 从 MqlTradeRequest 到 MqlTradeTransaction,理清每字段在实战 EA 里的真实去向
不少交易者把 OrderSend() 当黑盒,填错 action 对应的字段就怪服务器拒单。其实 MQL5 的下单链路是一组明确结构在对话,读不懂请求和响应谁承载什么,调试 EA 只能靠瞎试。
交易返回码 10020–10031 的逐行含义
在 MT5 的 CTrade / 订单校验逻辑里,返回码 10020 到 10031 属于「请求被终端或服务器拒绝但未成交」的区间,和 10009(已成交)完全两回事。下面这段 switch 分支把每个宏映射成可读字符串,开 MT5 按 F4 搜 TRADE_RETCODE_PRICE_CHANGED 就能定位到标准库定义。 注意 ext_descr 这个布尔参数:为真时拼接英文括号说明,为假时只留裸码。实盘里建议日志统一传 true,否则排查「10024」这种码连上下文都没有。 外汇与贵金属杠杆高,这类拒绝码频繁出现往往意味着报价断裂或服务器限流,可能触发滑点放大,需结合连通性判断而非盲目重试。
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) 抓真实值最稳。
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 在锁仓禁令下反复发单被拒。
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 挂单就能验证。
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...)' 这样的输出,便于核对部分成交行为。
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 用专家日志看一眼就能确认你的映射没漏。 外汇与贵金属杠杆高,这类事务监听只负责把动作讲清楚,不等于信号;任何基于成交事件的自动动作都先上模拟盘验。
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) */ }