轻松快捷开发 MetaTrader 程序的函数库 (第 二十八部分) :延后交易请求之平仓、删除和修改·进阶篇

轻松快捷开发 MetaTrader 程序的函数库 (第 二十八部分) :延后交易请求之平仓、删除和修改·进阶篇

(2/3)· 当服务器报错不再 Sleep 阻塞,如何用延后请求干净地撤单改价并避开重复下单

案例拆解 第 2/3 篇
很多 EA 在收到服务器错误后习惯用 Sleep() 硬等,结果主逻辑被卡死,重复发单还查不出来。延后请求若没在魔幻数字里写死唯一 ID,平仓改单很容易叠出多笔同源请求。这一篇把撤单改价的封装拆给你看。

订单枚举与报错处理的底层定义

在 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 文件确认你客户端版本是否支持。

MQL5 / C++
  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 分支一旦误触发,都可能让你错过关键行情,建议在策略测试器里专门跑一遍错误注入。

MQL5 / C++
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——部分平台对对冲账户的实现可能倾向留空,这会直接影响后续按仓位联动平仓的逻辑。外汇与贵金属杠杆高,验证属性遗漏可能放大滑点风险。

MQL5 / C++
  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,能让你在批量扫描历史成交时快速区分「被止损打掉」还是「止盈离场」,不必再解析成交注释。

MQL5 / C++
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() 返回值,就能确认当前账户到底吃的哪种成交策略。

MQL5 / C++
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 代表未设,别误当市价。

MQL5 / C++
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
把重复劳动交给小布
这些延后请求的状态追踪和同源识别,小布盯盘的 AIGC 已内置,打开对应品种页即可看到请求生命周期,你只管判断该不该撤。

常见问题

在 Defines.mqh 的整数型属性总数从 24 扩到 26,并追加可排序字段,旧索引不变则兼容,仅末端扩展了枚举,不会重排已有顺序。
可以,小布会读取魔幻数字里的唯一 ID 归属,把同源开仓、平仓、改单聚成一条链路,省去你手动比对请求历史。
因为收到服务器错误需等待时已全部默认切到延后请求,不再需要显式开关,删掉能减少分支并避免误配成阻塞式延迟。
每类动作沿用独立唯一 ID 写入魔幻数字,查找时按排序标准检索已有延后请求,确认无同源未结请求才新建,从而规避重复操作。