轻松快捷开发 MetaTrader 程序的函数库 (第 二十八部分) :延后交易请求之平仓、删除和修改·进阶篇
(2/3)· 当服务器报错不再 Sleep 阻塞,如何用延后请求干净地撤单改价并避开重复下单
订单枚举与报错处理的底层定义
在 MT5 的 EA 框架里,挂单和持仓的批量处理往往依赖一组排序枚举。下面这段枚举把订单可按数值属性排序的维度列得很全:止损价、止盈价、浮动盈利、佣金、库存费、总成交量、未成交残余量,以及把利润+佣金+库存费合并计算的 FULL 利润,还有 StopLimit 触发后的挂单价。 字符串维度的排序从 FIRST_ORD_STR_PROP 基准开始,覆盖品种名、注释、扩展注释,以及外部交易系统的订单 ID。实盘里若想按库存费从大到小挑单做对冲,直接引用 SORT_BY_ORDER_SWAP 即可,不用自己写比较函数。 报错行为由 ENUM_ERROR_HANDLING_BEHAVIOR 控制,有三种取向:BREAK 直接中止本次交易尝试;CORRECT 自动修正非法参数后重试;PENDING_REQUEST 则是把请求转成后台待处理队列。外汇与贵金属杠杆高,自动修正参数可能放大单边暴露,用 CORRECT 前应在策略测试器跑过极端点差环境。 注意原文中 ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST 带有高亮标记,说明该枚举值在官方示例里属于较新加入或需重点留意的成员,开 MT5 对照 include 文件确认你客户端版本是否支持。
SORT_BY_ORDER_SL, class=class="str">"cmt">// Sort by StopLoss price SORT_BY_ORDER_TP, class=class="str">"cmt">// Sort by TakeProfit price SORT_BY_ORDER_PROFIT, class=class="str">"cmt">// Sort by profit SORT_BY_ORDER_COMMISSION, class=class="str">"cmt">// Sort by commission SORT_BY_ORDER_SWAP, class=class="str">"cmt">// Sort by swap SORT_BY_ORDER_VOLUME, class=class="str">"cmt">// Sort by volume SORT_BY_ORDER_VOLUME_CURRENT, class=class="str">"cmt">// Sort by unexecuted volume SORT_BY_ORDER_PROFIT_FULL, class=class="str">"cmt">// Sort by profit+commission+swap SORT_BY_ORDER_PRICE_STOP_LIMIT, class=class="str">"cmt">// Sort by Limit order when StopLimit order is activated class=class="str">"cmt">//--- Sort by class="type">class="kw">string properties SORT_BY_ORDER_SYMBOL = FIRST_ORD_STR_PROP, class=class="str">"cmt">// Sort by symbol SORT_BY_ORDER_COMMENT, class=class="str">"cmt">// Sort by comment SORT_BY_ORDER_COMMENT_EXT, class=class="str">"cmt">// Sort by custom comment SORT_BY_ORDER_EXT_ID class=class="str">"cmt">// Sort by order ID in an external trading system }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA behavior when handling errors | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_ERROR_HANDLING_BEHAVIOR { ERROR_HANDLING_BEHAVIOR_BREAK, class=class="str">"cmt">// Abort trading attempt ERROR_HANDLING_BEHAVIOR_CORRECT, class=class="str">"cmt">// Correct invalid parameters ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST, class=class="str">"cmt">// Create a pending request };
「EA 报错处置的枚举分支怎么列」
写 MT5 自动交易系统,第一步得把“出错之后怎么办”先固化成枚举,不然 OnTradeTransaction 里一堆 if 会迅速失控。下面这段把错误处理行为分成终止尝试与纠正参数两类,属于最底层的决策开关。 enum ENUM_ERROR_HANDLING_BEHAVIOR { ERROR_HANDLING_BEHAVIOR_BREAK, // 中止本次交易尝试 ERROR_HANDLING_BEHAVIOR_CORRECT, // 修正非法参数 }; 再往上一层,是对服务器返回码的统一处置方法枚举。注意其中 ERROR_CODE_PROCESSING_METHOD_PENDING 这一项在第二段代码里被拿掉了,说明作者中途把“挂单式重试”从基础方法里剔除,只保留 OK / DISABLE / EXIT / CORRECT / REFRESH / WAIT 六种。实盘里若遇报价延迟,REFRESH 后重复提交往往比直接 EXIT 更实用。 enum ENUM_ERROR_CODE_PROCESSING_METHOD { ERROR_CODE_PROCESSING_METHOD_OK, // 无错误 ERROR_CODE_PROCESSING_METHOD_DISABLE, // 禁用 EA 交易 ERROR_CODE_PROCESSING_METHOD_EXIT, // 退出交易方法 ERROR_CODE_PROCESSING_METHOD_CORRECT, // 修正请求参数并重试 ERROR_CODE_PROCESSING_METHOD_REFRESH, // 刷新数据并重试 ERROR_CODE_PROCESSING_METHOD_WAIT, // 等待后重试 }; 挂单请求若需要排序,可用 ENUM_SORT_PEND_REQ_MODE,目前只暴露了按 ID(值 0)与按类型两种。外汇与贵金属杠杆高,枚举里任何 DISABLE 分支一旦误触发,都可能让你错过关键行情,建议在策略测试器里专门跑一遍错误注入。
enum ENUM_ERROR_HANDLING_BEHAVIOR { ERROR_HANDLING_BEHAVIOR_BREAK, class=class="str">"cmt">// Abort trading attempt ERROR_HANDLING_BEHAVIOR_CORRECT, class=class="str">"cmt">// Correct invalid parameters }; 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 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 }; 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 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_WAIT, class=class="str">"cmt">// Wait and repeat }; enum ENUM_SORT_PEND_REQ_MODE { SORT_BY_PEND_REQ_ID = class="num">0, class=class="str">"cmt">// Sort by ID SORT_BY_PEND_REQ_TYPE, class=class="str">"cmt">// Sort by type };
◍ 订单对象的属性快捷读取接口
在 MT5 的自定义订单类里,通常会封装一组只读方法,把底层 GetProperty 调用暴露成直观的函数名。上面这组代码就列出了其中 12 个常用接口:从 Ticket() 到 PositionByID(),覆盖了订单号、父单号、衍生单号、魔术码、触发原因、持仓 ID 与反向持仓 ID 等字段。 每个方法都声明为 const,意味着不修改对象内部状态,只返回对应属性值。例如 TicketFrom() 取的是 ORDER_PROP_TICKET_FROM,代表该订单由哪一张母单派生;PositionByID() 对应 ORDER_PROP_POSITION_BY_ID,用于反查对冲或关联仓位。 开 MT5 新建 EA 时,可以直接把这段声明粘进你的 COrder 派生类。实盘跑之前,建议在 OnTradeTransaction 里逐个 Print 这些返回值,确认你的经纪商在挂单成交后填的 PositionByID 不是 0——部分平台对对冲账户的实现可能倾向留空,这会直接影响后续按仓位联动平仓的逻辑。外汇与贵金属杠杆高,验证属性遗漏可能放大滑点风险。
SORT_BY_PEND_REQ_TICKET, class=class="str">"cmt">// Sort by ticket }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Methods of a simplified access to the order object properties | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Return(class="num">1) ticket, (class="num">2) parent order ticket, (class="num">3) derived order ticket, (class="num">4) magic number, (class="num">5) order reason, class=class="str">"cmt">//--- (class="num">6) position ID, (class="num">7) opposite position ID, (class="num">8) first group ID, (class="num">9) second group ID, class=class="str">"cmt">//--- (class="num">10) pending request ID, (class="num">11) magic number ID, (class="num">12) type, (class="num">13) flag of closing by StopLoss, class=class="str">"cmt">//--- (class="num">14) flag of closing by TakeProfit(class="num">15) open time, (class="num">16) close time, class=class="str">"cmt">//--- (class="num">17) order expiration date, (class="num">18) state, (class="num">19) status, (class="num">20) type by direction, (class="num">21) execution type by remainder, (class="num">22) order lifetime class="type">long Ticket(class="type">void) const { class="kw">return this.GetProperty(ORDER_PROP_TICKET); } class="type">long TicketFrom(class="type">void) const { class="kw">return this.GetProperty(ORDER_PROP_TICKET_FROM); } class="type">long TicketTo(class="type">void) const { class="kw">return this.GetProperty(ORDER_PROP_TICKET_TO); } class="type">long Magic(class="type">void) const { class="kw">return this.GetProperty(ORDER_PROP_MAGIC); } class="type">long Reason(class="type">void) const { class="kw">return this.GetProperty(ORDER_PROP_REASON); } class="type">long PositionID(class="type">void) const { class="kw">return this.GetProperty(ORDER_PROP_POSITION_ID); } class="type">long PositionByID(class="type">void) const { class="kw">return this.GetProperty(ORDER_PROP_POSITION_BY_ID); }
订单属性读取的封装方法
在 MT5 的自定义订单类里,把底层 GetProperty 调用包成只读成员函数,是避免主逻辑里到处散落枚举常量的常见做法。下面这组方法覆盖了魔数、分组 ID、挂单请求号、订单类型以及被 SL/TP 平仓的标志,全部声明为 const,意味着调用时不修改对象状态。 TimeOpen 与 TimeClose 返回的是 UTC 秒级时间戳(long 型),在回测里你能直接用两者之差算出持仓时长;例如一笔 XAUUSD 订单 TimeClose - TimeOpen = 3661,就代表持有了约 1 小时 1 分钟。外汇与贵金属杠杆高,用这种精确时长做持仓分布统计时,需意识到样本受滑点和点差扰动,结论只具概率意义。 IsCloseByStopLoss / IsCloseByTakeProfit 把属性强转为 bool,能让你在批量扫描历史成交时快速区分「被止损打掉」还是「止盈离场」,不必再解析成交注释。
class="type">long MagicID(class="type">void) const { class="kw">return this.GetProperty(ORDER_PROP_MAGIC_ID); } class="type">long GroupID1(class="type">void) const { class="kw">return this.GetProperty(ORDER_PROP_GROUP_ID1); } class="type">long GroupID2(class="type">void) const { class="kw">return this.GetProperty(ORDER_PROP_GROUP_ID2); } class="type">long PendReqID(class="type">void) const { class="kw">return this.GetProperty(ORDER_PROP_PEND_REQ_ID); } class="type">long TypeOrder(class="type">void) const { class="kw">return this.GetProperty(ORDER_PROP_TYPE); } class="type">bool IsCloseByStopLoss(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(ORDER_PROP_CLOSE_BY_SL); } class="type">bool IsCloseByTakeProfit(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(ORDER_PROP_CLOSE_BY_TP); } class="type">long TimeOpen(class="type">void) const { class="kw">return this.GetProperty(ORDER_PROP_TIME_OPEN); } class="type">long TimeClose(class="type">void) const { class="kw">return this.GetProperty(ORDER_PROP_TIME_CLOSE); }
「订单对象的属性读取与构造落地」
在 MT5 的自定义订单类里,过期时间、状态、方向这些字段并不靠公开成员变量暴露,而是统一走 GetProperty 接口按枚举索引取。下面这段声明能直接看到,TimeExpiration 返回 datetime,State 与 Status 分别映射 ENUM_ORDER_STATE 和 ENUM_ORDER_STATUS,TypeByDirection 取出买卖方向枚举。 被高亮的 TypeFilling 与 TypeTime 是容易被忽略的两项:前者决定市价单是立刻成交还是返回错(ORDER_FILLING_RETURN 在流动性不足时直接拒单),后者标记订单是当日有效还是挂到指定日期。做贵金属跨平台搬砖时,经纪商若只允许 ORDER_TIME_DAY 而代码写死 ORDER_TIME_SPECIFIED,开仓会概率性失败。 构造器 COrder::COrder 接收 ticket 和 status,把魔术码、票号、过期时间、成交方式、时间类型一次性灌进 m_long_prop 数组。注意 ORDER_PROP_MAGIC 取自 OrderMagicNumber(),意味着同 tick 内若改了 EA 的魔术码,构造出的对象会带旧值——这是回测和实盘不一致的高发点。 开 MT5 按 F4 把下面代码塞进你的 order wrapper 类,编译后打印 TypeFilling() 返回值,就能确认当前账户到底吃的哪种成交策略。
class="type">class="kw">datetime TimeExpiration(class="type">void) const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(ORDER_PROP_TIME_EXP); } ENUM_ORDER_STATE State(class="type">void) const { class="kw">return (ENUM_ORDER_STATE)this.GetProperty(ORDER_PROP_STATE); } ENUM_ORDER_STATUS Status(class="type">void) const { class="kw">return (ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS); } ENUM_ORDER_TYPE TypeByDirection(class="type">void) const { class="kw">return (ENUM_ORDER_TYPE)this.GetProperty(ORDER_PROP_DIRECTION); } ENUM_ORDER_TYPE_FILLING TypeFilling(class="type">void) const { class="kw">return (ENUM_ORDER_TYPE_FILLING)this.GetProperty(ORDER_PROP_TYPE_FILLING); } ENUM_ORDER_TYPE_TIME TypeTime(class="type">void) const { class="kw">return (ENUM_ORDER_TYPE_TIME)this.GetProperty(ORDER_PROP_TYPE_TIME); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Closed parametric constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ COrder::COrder(ENUM_ORDER_STATUS order_status,const class="type">class="kw">ulong ticket) { class=class="str">"cmt">//--- Save integer properties this.m_ticket=ticket; this.m_long_prop[ORDER_PROP_STATUS] = order_status; this.m_long_prop[ORDER_PROP_MAGIC] = this.OrderMagicNumber(); this.m_long_prop[ORDER_PROP_TICKET] = this.OrderTicket(); this.m_long_prop[ORDER_PROP_TIME_EXP] = this.OrderExpiration(); this.m_long_prop[ORDER_PROP_TYPE_FILLING] = this.OrderTypeFilling(); this.m_long_prop[ORDER_PROP_TYPE_TIME] = this.OrderTypeTime();
◍ 把订单属性塞进结构体的写法
在封装订单对象时,把 MQL5 的订单方法返回值一次性灌进 long / double 两类数组,是最省事的内部存储方案。下面这段把 11 个整型字段和 10 个浮点字段分别落位,开 MT5 新建 EA 把代码贴进类方法即可验证。 long 数组覆盖了订单类型、状态、方向、关联持仓 ID、开平时间(毫秒级)等。注意 ORDER_PROP_TIME_UPDATE 取的是 PositionTimeUpdateMSC 而非订单自身更新时间,回测时这两者的差值可能达到数百毫秒。 double 数组则保存开平仓价、盈亏、佣金、库存费、成交量、SL/TP 等。ORDER_PROP_VOLUME_CURRENT 与 ORDER_PROP_VOLUME 在部分成交时不同,前者是当前未平量,后者是原始挂单量。外汇与贵金属杠杆高,SL/TP 字段返回 0 代表未设,别误当市价。
this.m_long_prop[ORDER_PROP_TYPE] = this.OrderType(); this.m_long_prop[ORDER_PROP_STATE] = this.OrderState(); this.m_long_prop[ORDER_PROP_DIRECTION] = this.OrderTypeByDirection(); this.m_long_prop[ORDER_PROP_POSITION_ID] = this.OrderPositionID(); this.m_long_prop[ORDER_PROP_REASON] = this.OrderReason(); this.m_long_prop[ORDER_PROP_DEAL_ORDER_TICKET] = this.DealOrderTicket(); this.m_long_prop[ORDER_PROP_DEAL_ENTRY] = this.DealEntry(); this.m_long_prop[ORDER_PROP_POSITION_BY_ID] = this.OrderPositionByID(); this.m_long_prop[ORDER_PROP_TIME_OPEN] = this.OrderOpenTimeMSC(); this.m_long_prop[ORDER_PROP_TIME_CLOSE] = this.OrderCloseTimeMSC(); this.m_long_prop[ORDER_PROP_TIME_UPDATE] = this.PositionTimeUpdateMSC(); class=class="str">"cmt">//--- Save real properties this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_OPEN)] = this.OrderOpenPrice(); this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_CLOSE)] = this.OrderClosePrice(); this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT)] = this.OrderProfit(); this.m_double_prop[this.IndexProp(ORDER_PROP_COMMISSION)] = this.OrderCommission(); this.m_double_prop[this.IndexProp(ORDER_PROP_SWAP)] = this.OrderSwap(); this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME)] = this.OrderVolume(); this.m_double_prop[this.IndexProp(ORDER_PROP_SL)] = this.OrderStopLoss(); this.m_double_prop[this.IndexProp(ORDER_PROP_TP)] = this.OrderTakeProfit(); this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME_CURRENT)] = this.OrderVolumeCurrent(); this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_STOP_LIMIT)] = this.OrderPriceStopLimit(); class=class="str">"cmt">//--- Save class="type">class="kw">string properties