轻松快捷开发 MetaTrader 程序的函数库(第 二十五部分):处理交易服务器返回的错误·进阶篇
(2/3)· 发送订单后服务器甩回的 6 类回执,怎样用循环重发和参数纠正自动消化
◍ 交易库底层宏与错误枚举的落地定义
在 MT5 自建交易库时,先固定一批全局宏ID能省掉后续不少乱码式报错。下面这组常量把行情、事件、账户、品种四个集合的标识锁在 0x777A–0x777D,文件目录统一收进 DoEasy\ 与 DoEasy\Resource\,品种默认处理上限给到 1000 个,这些数字直接决定后续数组开多大。 错误分支用两个枚举管住:ENUM_TRADE_REQUEST_ERR_FLAGS 里 0 是无错,1 和 2 是致命/内部错要直接退出 EA,4 进列表错交给你自己处理,8 和 16 分别卡住挂单价格和限价单价格偏差。ENUM_ERROR_CODE_PROCESSING_METHOD 则只列了 OK 和 DISABLE 两态,对应“继续”和“给 EA 断交易”两种走向。 把宏原样贴进 mqh 头文件,MT5 里编译后若 COLLECTION_SYMBOLS_ID 取不到品种,先查 SYMBOLS_COMMON_TOTAL 是否小于你观测池数量——外汇与贵金属波动剧烈,这类底层上限设错会放大实盘风险。
class="macro">#define COLLECTION_MARKET_ID(0x777A) class=class="str">"cmt">// Market collection list ID class="macro">#define COLLECTION_EVENTS_ID(0x777B) class=class="str">"cmt">// Event collection list ID class="macro">#define COLLECTION_ACCOUNT_ID(0x777C) class=class="str">"cmt">// Account collection list ID class="macro">#define COLLECTION_SYMBOLS_ID(0x777D) class=class="str">"cmt">// Symbol collection list ID class=class="str">"cmt">//--- Data parameters for file operations class="macro">#define DIRECTORY("DoEasy\") class=class="str">"cmt">// Library directory for storing object folders class="macro">#define RESOURCE_DIR("DoEasy\\Resource\\") class=class="str">"cmt">// Library directory for storing resource folders class=class="str">"cmt">//--- Symbol parameters class="macro">#define CLR_DEFAULT(0xFF000000) class=class="str">"cmt">// Default class="type">class="kw">color class="macro">#define SYMBOLS_COMMON_TOTAL(class="num">1000) class=class="str">"cmt">// Total number of working symbols class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Flags indicating the trading request error handling methods | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_TRADE_REQUEST_ERR_FLAGS { TRADE_REQUEST_ERR_FLAG_NO_ERROR = class="num">0, class=class="str">"cmt">// No error TRADE_REQUEST_ERR_FLAG_FATAL_ERROR = class="num">1, class=class="str">"cmt">// Disable trading for an EA(critical error) - exit TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR = class="num">2, class=class="str">"cmt">// Library internal error - exit TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST = class="num">4, class=class="str">"cmt">// Error in the list - handle(ENUM_ERROR_CODE_PROCESSING_METHOD) TRADE_REQUEST_ERR_FLAG_PRICE_ERROR = class="num">8, class=class="str">"cmt">// Placement price error TRADE_REQUEST_ERR_FLAG_LIMIT_ERROR = class="num">16, class=class="str">"cmt">// Limit order price error }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| The methods of handling errors and server class="kw">return codes | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_ERROR_CODE_PROCESSING_METHOD { ERROR_CODE_PROCESSING_METHOD_OK, class=class="str">"cmt">// No errors ERROR_CODE_PROCESSING_METHOD_DISABLE, class=class="str">"cmt">// Disable trading for the EA };
「错误分支与报文的枚举定义细节」
在封装交易类库时,错误处理通常先用一个枚举区分「下一步怎么做」。下面这组分支覆盖了退出、改参重发、刷新重发、挂单、等待重发五种走向,实盘里最常被触发的是 CORRECT 与 REFRESH 两类。 ERROR_CODE_PROCESSING_METHOD_EXIT 表示直接终止交易方法;ERROR_CODE_PROCESSING_METHOD_CORRECT 要求修正下单参数后重发;ERROR_CODE_PROCESSING_METHOD_REFRESH 则是更新行情或账户数据后再试;PENDING 用于转挂单,WAIT 用于延时重试。 另一组 MSG_LIB_TEXT_* 常量专门描述终端与服务器的拒单原因。其中 MSG_LIB_TEXT_LIMIT_LESS_STOP_LEVEL 指出:限价单距离止损单的点数若小于品种 StopLevel 允许值,会被拒;MSG_LIB_TEXT_SL_LESS_FREEZE_LEVEL 与 TP 版同理,针对的是 FreezeLevel 冻结层。 外汇与贵金属杠杆高、点差跳变频繁,StopLevel 和 FreezeLevel 在重大数据发布时可能瞬间放大,EA 若在此时下单极易吃到这类报错。开 MT5 在品种规格里核对 StopLevel / FreezeLevel 数值,再比对日志里的 MSG_LIB_TEXT_* 就能定位是哪一类合规被击穿。
ERROR_CODE_PROCESSING_METHOD_EXIT, class=class="str">"cmt">// Exit the trading method ERROR_CODE_PROCESSING_METHOD_CORRECT, class=class="str">"cmt">// Correct trading request parameters and repeat ERROR_CODE_PROCESSING_METHOD_REFRESH, class=class="str">"cmt">// Update data and repeat ERROR_CODE_PROCESSING_METHOD_PENDING, class=class="str">"cmt">// Create a pending request ERROR_CODE_PROCESSING_METHOD_WAIT, class=class="str">"cmt">// Wait and repeat }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- CTrading MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED, class=class="str">"cmt">// Trade operations are not allowed in the terminal(the AutoTrading button is disabled) MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED, class=class="str">"cmt">// EA is not allowed to trade(F7 --> Common --> Allow Automated Trading) MSG_LIB_TEXT_ACCOUNT_NOT_TRADE_ENABLED, class=class="str">"cmt">// Trading is disabled for the current account MSG_LIB_TEXT_ACCOUNT_EA_NOT_TRADE_ENABLED, class=class="str">"cmt">// Trading on the trading server side is disabled for EAs on the current account MSG_LIB_TEXT_REQUEST_REJECTED_DUE, class=class="str">"cmt">// Request was rejected before sending to the server due to: MSG_LIB_TEXT_INVALID_REQUEST, class=class="str">"cmt">// Invalid request: MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR, class=class="str">"cmt">// Insufficient funds for performing a trade MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED, class=class="str">"cmt">// Exceeded maximum allowed aggregate volume of orders and positions in one direction MSG_LIB_TEXT_REQ_VOL_LESS_MIN_VOLUME, class=class="str">"cmt">// Request volume is less than the minimum acceptable one MSG_LIB_TEXT_REQ_VOL_MORE_MAX_VOLUME, class=class="str">"cmt">// Request volume exceeds the maximum acceptable one MSG_LIB_TEXT_CLOSE_BY_ORDERS_DISABLED, class=class="str">"cmt">// Close by is disabled MSG_LIB_TEXT_INVALID_VOLUME_STEP, class=class="str">"cmt">// Request volume is not a multiple of the minimum lot change step gradation MSG_LIB_TEXT_CLOSE_BY_SYMBOLS_UNEQUAL, class=class="str">"cmt">// Symbols of opposite positions are not equal MSG_LIB_TEXT_SL_LESS_STOP_LEVEL, class=class="str">"cmt">// StopLoss violates requirements for symbol&class="macro">#x27;s StopLevel MSG_LIB_TEXT_TP_LESS_STOP_LEVEL, class=class="str">"cmt">// TakeProfit violates requirements for symbol&class="macro">#x27;s StopLevel MSG_LIB_TEXT_PRICE_LESS_STOP_LEVEL, class=class="str">"cmt">// Order distance in points is less than a value allowed by symbol&class="macro">#x27;s StopLevel parameter MSG_LIB_TEXT_LIMIT_LESS_STOP_LEVEL, class=class="str">"cmt">// Limit order distance in points relative to a stop order is less than a value allowed by symbol&class="macro">#x27;s StopLevel parameter MSG_LIB_TEXT_SL_LESS_FREEZE_LEVEL, class=class="str">"cmt">// The distance from the price to StopLoss is less than a value allowed by symbol&class="macro">#x27;s FreezeLevel parameter MSG_LIB_TEXT_TP_LESS_FREEZE_LEVEL, class=class="str">"cmt">// The distance from the price to TakeProfit is less than a value allowed by symbol&class="macro">#x27;s FreezeLevel parameter
交易请求报错枚举与双语文案映射
在 MQL5 交易库里,挂单与改单失败的原因被集中定义成一组消息常量,方便 EA 在日志里直接吐出可读文本。上面这段代码列出的是部分关键枚举:FreezeLevel 距离不足、SL/TP 类型不是 int 或 double、交易被禁用等,都是实盘里最容易踩的坑。 注意 MSG_LIB_TEXT_LESS_FREEZE_LEVEL 和 MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ 这类被高亮的项——它们对应「价格距激活位小于 FreezeLevel」以及「创建挂单请求失败」,往往意味着你的下单逻辑在品种规则边界上撞墙。 下半段的双元素结构 {"俄文","英文"} 是库内部做的本地化映射,MT5 终端默认按英文输出。若你自建 EA 引用这套枚举,开策略测试器跑一遍 EURUSD 的即时单改单,就能在日志里看到具体哪条常量被触发。 外汇与贵金属杠杆高、滑点突变频繁,FreezeLevel 在重大数据发布时可能瞬间放大,任何距离校验失败都只是概率性提示,不等于行情方向判断。
MSG_LIB_TEXT_PR_LESS_FREEZE_LEVEL, class=class="str">"cmt">// The distance from the price to an order activation level is less than a value allowed by symbol&class="macro">#x27;s FreezeLevel parameter MSG_LIB_TEXT_UNSUPPORTED_SL_TYPE, class=class="str">"cmt">// Unsupported StopLoss parameter type(should be &class="macro">#x27;class="type">int&class="macro">#x27; or &class="macro">#x27;class="type">class="kw">double&class="macro">#x27;) MSG_LIB_TEXT_UNSUPPORTED_TP_TYPE, class=class="str">"cmt">// Unsupported TakeProfit parameter type(should be &class="macro">#x27;class="type">int&class="macro">#x27; or &class="macro">#x27;class="type">class="kw">double&class="macro">#x27;) MSG_LIB_TEXT_UNSUPPORTED_PR_TYPE, class=class="str">"cmt">// Unsupported price parameter type(should be &class="macro">#x27;class="type">int&class="macro">#x27; or &class="macro">#x27;class="type">class="kw">double&class="macro">#x27;) MSG_LIB_TEXT_UNSUPPORTED_PL_TYPE, class=class="str">"cmt">// Unsupported limit order price parameter type(should be &class="macro">#x27;class="type">int&class="macro">#x27; or &class="macro">#x27;class="type">class="kw">double&class="macro">#x27;) MSG_LIB_TEXT_UNSUPPORTED_PRICE_TYPE_IN_REQ, class=class="str">"cmt">// Unsupported price parameter type in a request MSG_LIB_TEXT_TRADING_DISABLE, class=class="str">"cmt">// Trading disabled for the EA until the reason is eliminated MSG_LIB_TEXT_TRADING_OPERATION_ABORTED, class=class="str">"cmt">// Trading operation is interrupted MSG_LIB_TEXT_CORRECTED_TRADE_REQUEST, class=class="str">"cmt">// Correcting trading request parameters MSG_LIB_TEXT_CREATE_PENDING_REQUEST, class=class="str">"cmt">// Creating a pending request MSG_LIB_TEXT_NOT_POSSIBILITY_CORRECT_LOT, class=class="str">"cmt">// Unable to correct a lot MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ, class=class="str">"cmt">// Failed to create a pending request MSG_LIB_TEXT_TRY_N, class=class="str">"cmt">// Trading attempt # }; {"Дистанция установки ордера в пунктах меньше разрешённой параметром StopLevel символа","Distance to place order in points less than allowed by symbol&class="macro">#x27;s StopLevel"}, {"Дистанция установки лимит-ордера относительно стоп-ордера меньше разрешённой параметром StopLevel символа","Distance to place limit order relative to stop order less than allowed by symbol&class="macro">#x27;s StopLevel"}, {"Дистанция от цены до StopLoss меньше разрешённой параметром FreezeLevel символа","Distance from price to StopLoss less than allowed by symbol&class="macro">#x27;s FreezeLevel"}, {"Дистанция от цены до TakeProfit меньше разрешённой параметром FreezeLevel символа","Distance from price to TakeProfit less than allowed by symbol&class="macro">#x27;s FreezeLevel"}, {"Дистанция от цены до цены срабатывания ордера меньше разрешённой параметром FreezeLevel символа","Distance from price to order triggering price less than allowed by symbol&class="macro">#x27;s FreezeLevel"}, {"Неподдерживаемый тип параметра StopLoss(необходимо class="type">int или class="type">class="kw">double)","Unsupported StopLoss parameter type(class="type">int or class="type">class="kw">double required)"}, {"Неподдерживаемый тип параметра TakeProfit(необходимо class="type">int или class="type">class="kw">double)","Unsupported TakeProfit parameter type(class="type">int or class="type">class="kw">double required)"}, {"Неподдерживаемый тип параметра цены (необходимо class="type">int или class="type">class="kw">double)","Unsupported price parameter type(class="type">int or class="type">class="kw">double required)"}, {"Неподдерживаемый тип параметра цены limit-ордера (необходимо class="type">int или class="type">class="kw">double)","Unsupported type of price parameter for limit order(class="type">int or class="type">class="kw">double required)"}, {"Неподдерживаемый тип параметра цены в запросе","Unsupported price parameter type in request"}, {"Торговля отключена для эксперта до устранения причины запрета","Trading for expert disabled till this ban eliminated"}, {"Торговая операция прервана","Trading operation aborted"}, {"Корректировка параметров торгового запроса ...","Correction of trade request parameters ..."}, {"Создание отложенного запроса","Create pending request"}, {"Нет возможности скорректировать лот","Unable to correct lot"}, {"Не удалось создать отложенный запрос","Failed to create pending request"},
◍ 下单函数的参数骨架
在 MT5 的自定义交易类里,把挂单和市价单统一收口到一个 SetOrder 方法,是减少重复代码的直接做法。下面这段声明给出了该方法暴露的全部入参,从订单类型到填充策略都在列。 bool SetOrder(const ENUM_ORDER_TYPE type, const double volume, const double price, const double sl=0, const double tp=0, const double price_stoplimit=0, const ulong magic=ULONG_MAX, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); sl 和 tp 默认 0,意味着不传就代表不带止损止盈,实盘里容易因漏设而裸奔,外汇和贵金属波动大,高风险下建议显式传值。type_filling 默认 WRONG_VALUE 是个明显信号:不同券商对 IOC 和 FOK 的支持不同,不主动指定可能在部分账户直接拒单。 把 magic 设为 ULONG_MAX 做默认值也有讲究,它和具体 EA 的魔数区隔开,便于在调试阶段一眼识别出“测试单”。开 MT5 新建个 CTradeObj 类,把这段声明贴进去编译,先验证参数默认值会不会触发你当前账户的报错。
class="type">bool SetOrder(class="kw">const ENUM_ORDER_TYPE type, class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">double price, class="kw">const class="type">class="kw">double sl=class="num">0, class="kw">const class="type">class="kw">double tp=class="num">0, class="kw">const class="type">class="kw">double price_stoplimit=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
「挂单请求结构体里那些容易填错的字段」
在封装交易类时,挂单函数入口往往会带一长串带默认值的参数:magic 默认 ULONG_MAX 表示沿用实例魔法码,comment 默认 NULL 则取类内注释,expiration 给 0 代表不限期。type_time 和 type_filling 传 WRONG_VALUE 时,引擎会回退到对象成员里的预设值,这一层兜底能避免重复传参。 代码里先拦掉市价单类型:ORDER_TYPE_BUY、ORDER_TYPE_SELL、ORDER_TYPE_CLOSE_BY(MQL4 下还含 BUY_STOP_LIMIT / SELL_STOP_LIMIT)若混进挂单接口,直接写 MSG_LIB_SYS_INVALID_ORDER_TYPE 并返回 false,不会走到服务器。 随后 ZeroMemory 清空 request 与 result,再逐字段赋值。注意 stoplimit 只针对 STOP_LIMIT 类挂单有效,普通 LIMIT 单填了也会被服务端忽略;type_filling 在外汇黄金这种高波动品种上若选错,可能触发部分成交,实盘前建议在 MT5 策略测试器里用不同填充策略跑一遍验证。 最后按 m_async_mode 分流:同步走 OrderSend,异步走 OrderSendAsync。异步模式下回码不在当场返回,得靠 OnTradeTransaction 抓结果,写 EA 时别漏了这部分回调。
class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { ::ResetLastError(); class=class="str">"cmt">//--- If an invalid order type has been passed, write the error code and description, send the message to the journal and class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27; if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL || type==ORDER_TYPE_CLOSE_BY class="macro">#ifdef __MQL4__ || type==ORDER_TYPE_BUY_STOP_LIMIT || type==ORDER_TYPE_SELL_STOP_LIMIT class="macro">#endif ) { this.m_result.retcode=MSG_LIB_SYS_INVALID_ORDER_TYPE; this.m_result.comment=CMessage::Text(this.m_result.retcode); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_INVALID_ORDER_TYPE),OrderTypeDescription(type)); class="kw">return class="kw">false; } class=class="str">"cmt">//--- Clear the structures ::ZeroMemory(this.m_request); ::ZeroMemory(this.m_result); class=class="str">"cmt">//--- Fill in the request structure this.m_request.action = TRADE_ACTION_PENDING; this.m_request.symbol = this.m_symbol; this.m_request.magic = (magic==ULONG_MAX ? this.m_magic : magic); this.m_request.volume = volume; this.m_request.type = type; this.m_request.stoplimit = price_stoplimit; this.m_request.price = price; this.m_request.sl = sl; this.m_request.tp = tp; this.m_request.expiration = expiration; this.m_request.type_time = (type_time>WRONG_VALUE ? type_time : this.m_type_time); this.m_request.type_filling = (type_filling>WRONG_VALUE ? type_filling : this.m_type_filling); this.m_request.comment = (comment==NULL ? this.m_comment : comment); class=class="str">"cmt">//--- Return the result of sending a request to the server class="macro">#ifdef __MQL5__ class="kw">return(!this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)); class="macro">#else ::ResetLastError();
下单回执与交易类骨架的衔接
发单动作结束后,立刻用 SymbolInfoTick 刷新本地 tick,避免拿到的 ask/bid 是几毫秒前的旧值。OrderSend 返回 ticket 不等于 WRONG_VALUE 时,先读 GetLastError 填 retcode,再尝试 OrderSelect 回捞真实开仓价与手数;若选单失败,就退回 m_request 里的预设值,保证结果结构永远有数可读。 成功分支里 m_result.price 和 volume 都走了「OrderSelect 成功才取真实值,否则用请求值」的三元判断,这套兜底能让回测与实盘在滑点场景下行为一致。外汇与贵金属点差跳变频繁,这种兜底可降低结果结构空窗导致的误判概率。 CTrading 类把账户、品种集、持仓集、历史集全部用指针挂进来,自己只持有一份 m_list_request 挂单队列和 m_list_errors 错误表。m_is_trade_disable 与 m_use_sound 两个布尔量分别管「禁交易开关」和「成交音效」,实盘里切策略时直接翻 m_is_trade_disable 比注释代码更安全。
class="type">int ticket=::OrderSend(m_request.symbol,m_request.type,m_request.volume,m_request.price,(class="type">int)m_request.deviation,m_request.sl,m_request.tp,m_request.comment,(class="type">int)m_request.magic,m_request.expiration,clrNONE); ::SymbolInfoTick(this.m_symbol,this.m_tick); if(ticket!=WRONG_VALUE) { this.m_result.retcode=::GetLastError(); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; this.m_result.order=ticket; this.m_result.price=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderOpenPrice() : this.m_request.price); this.m_result.volume=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderLots() : this.m_request.volume); this.m_result.comment=CMessage::Text(this.m_result.retcode); class="kw">return true; } else { this.m_result.retcode=::GetLastError(); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; this.m_result.comment=CMessage::Text(this.m_result.retcode); class="kw">return class="kw">false; } class="macro">#endif } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Trading class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CTrading { class="kw">private: CAccount *m_account; class=class="str">"cmt">// Pointer to the current account object CSymbolsCollection *m_symbols; class=class="str">"cmt">// Pointer to the symbol collection list CMarketCollection *m_market; class=class="str">"cmt">// Pointer to the list of the collection of market orders and positions CHistoryCollection *m_history; class=class="str">"cmt">// Pointer to the list of the collection of historical orders and deals CArrayObj m_list_request; class=class="str">"cmt">// List of pending requests CArrayInt m_list_errors; class=class="str">"cmt">// Error list class="type">bool m_is_trade_disable; class=class="str">"cmt">// Flag disabling trading class="type">bool m_use_sound; class=class="str">"cmt">// The flag of class="kw">using sounds of the object trading events