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

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

第 1/3 篇

◍ 把平仓和修改也塞进延后队列

延后交易请求的核心思路,是把本该立即发往服务器的下单、平仓、删除挂单和修改挂单指令,先写进一个本地请求池,由统一调度在下一根 tick 或指定时刻再真正执行。这样做在策略回测与实盘切换时,能避免同根 K 线内多次发单造成的上下文竞态。 原文示例给出的是第 28 部分的库结构,其中平仓、删除与修改三类请求被归到同一延后处理分支。实际在 MT5 里打开这类库,你会看到请求对象用结构体缓存,包含订单号、请求类型和新价位三个字段。 需要提醒的是,外汇与贵金属属于高杠杆高风险品种,延后执行可能让平仓指令错过预设止损价位,实盘前务必在策略测试器用 2020 年 3 月之后的行情复算一遍。

延后请求下的平仓与改单验证

这是围绕“延后请求”概念的第三篇文章,本小节聚焦用具体方法完成概念测试:平仓、删除挂单、以及修改持仓止价或挂单参数。 原文提到对抽象订单类做了小幅扩展,新增了两个属性的返回值——订单填充类型与到期类型,同时基准跨平台交易对象的交易方法代码也略作优化。这些改动不逐条展开,只挑其中一个修改方法作为单独示例呈现。 测试环节的目标很直接:在 MT5 里跑通延后请求下的平仓与改单逻辑,确认非阻塞调用不会丢单或错位。外汇与贵金属品种波动快,这类机制实盘前务必用策略测试器先验证,存在滑点和断连的高风险。

「给抽象订单补上执行类型与时间属性」

要在 MT5 里把订单的成交方式(filling type)和存续时间(time type)纳进抽象订单对象,得先在 Datas.mqh 的文本消息索引里加 MSG_ORD_TYPE_FILLING 与 MSG_ORD_TYPE_TIME,并配上对应的中俄英三语描述串。这俩属性不是装饰,后续按它们排序延后请求时直接派得上用场。 Defines.mqh 里整数型属性枚举要动刀:原总数 24,加了两个新属性后必须改成 26,否则 COrder 里属性数组越界会编译不过。同时把 EA 错误处理枚举里“手动建延后请求”的选项删掉——现在服务器报错后默认就切到延后请求,不再用 Sleep() 卡主线程。 Order.mqh 的 COrder 类改动集中在三处:简化访问方法里返回新属性的两个 getter、闭合构造函数里用 OrderTypeFilling() 和 OrderTypeTime() 填数组、以及属性说明方法里补两条描述。下面这段是 Datas.mqh 与 Defines.mqh 里实际落地的代码片段,高亮行就是新增部分。 平仓删改时魔幻 ID 不够用,因为那是按 ticket 操作的。延后请求结构里自带 ticket,靠它就能反查请求挂在哪笔仓单上,成功处理后及时删对象,避免重复发单。CEventsCollection 加个 GetObject() 返回自身指针,交易类就能实时读事件集合,不必自己重造轮子。 开仓方法向服务器发单的模块也顺手优化了:旧代码在 MQL4 下重置错误码的位置有逻辑坑(发请求前就清了,价格函数报错会覆盖掉服务器返回码),新写法把验证和发单拆干净,其余类方法同理微调。

MQL5 / C++
&nbsp;&nbsp; MSG_ORD_TIME_EXP,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Expiration date</span>
<span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">&nbsp;&nbsp; MSG_ORD_TYPE_FILLING,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Type of execution by remainder</span>
&nbsp;&nbsp; MSG_ORD_TYPE_TIME,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Order lifetime</span></span>
&nbsp;&nbsp; MSG_ORD_TYPE,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Type</span>
&nbsp;&nbsp; MSG_LIB_TEXT_PEND_REQUEST_CREATED,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Pending request created</span>
&nbsp;&nbsp; MSG_LIB_TEXT_PEND_REQUEST_DELETED,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Pending request is removed due to its expiration</span>
<span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">&nbsp;&nbsp; MSG_LIB_TEXT_PEND_REQUEST_EXECUTED,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Pending request is removed due to its execution</span></span>
&nbsp;&nbsp; {"Дата экспирации","Date of expiration"},
&nbsp;&nbsp; <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">242, class="num">153);">{"Тип исполнения по остатку","Order filling type"}</span>,
&nbsp;&nbsp; <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">242, class="num">153);">{"Время жизни ордера","Order lifetime"}</span>,
&nbsp;&nbsp; {"Тип","Type"},
&nbsp;&nbsp; {"Создан отложенный запрос","Pending request created"},
&nbsp;&nbsp; {"Отложенный запрос удалён в связи с окончанием времени его действия","Pending request deleted due to expiration"},
&nbsp;&nbsp; <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">242, class="num">153);">{"Отложенный запрос удалён в связи с его исполнением","Pending request deleted due to execution"}</span>,
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Order, deal, position integer properties&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;       |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">enum</span> ENUM_ORDER_PROP_INTEGER
&nbsp;&nbsp;{
&nbsp;&nbsp; ORDER_PROP_TICKET = <span class="number">class="num">0</span>,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Order ticket</span>
&nbsp;&nbsp; ORDER_PROP_MAGIC,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Real order magic number</span>

◍ 订单属性枚举里容易被忽略的时间与执行字段

在 MT5 的订单属性枚举(ENUM_ORDER_PROPERTY)中,有一批字段直接决定你回测或实盘里能读到什么维度的数据。开仓时间 ORDER_PROP_TIME_OPEN 与平仓时间 ORDER_PROP_TIME_CLOSE 都以毫秒计,后者对应执行或撤单时刻(ORDER_TIME_DONE),做持仓时长统计时别拿秒级精度去近似。 挂单的有效期走 ORDER_PROP_TIME_EXP,而 ORDER_PROP_TYPE_TIME 描述的是订单 lifetime 分类(比如 GTC、当日、指定秒数),两者常被混为一谈——前者是具体到期时间戳,后者是经纪商端的生命周期模式。 高亮的 ORDER_PROP_TYPE_FILLING 返回余数执行方式(市价单部分成交后的处理规则),ORDER_PROP_TYPE_TIME 则决定订单在哪类时间框架下作废。这两者在跨平台迁移 EA 时最容易因为默认值不同而出诡异滑点,建议开 MT5 在订单池里打印这两个字段验证当前环境。 其余如 ORDER_PROP_DEAL_ENTRY(IN/OUT/INOUT 方向)、ORDER_PROFIT_PT(点数利润)、ORDER_PROP_CLOSE_BY_SL/TP(是否由止损止盈触发平仓)都是复盘用的硬指标。外汇与贵金属杠杆高,部分成交和 SL/TP 触发标记直接影响风险敞口测算,读之前先确认账户成交模式。

MQL5 / C++
ORDER_PROP_TIME_OPEN,                                                                     class=class="str">"cmt">// Open time in milliseconds(MQL5 Deal time)
ORDER_PROP_TIME_CLOSE,                                                                     class=class="str">"cmt">// Close time in milliseconds(MQL5 Execution or removal time - ORDER_TIME_DONE)
ORDER_PROP_TIME_EXP,                                                                       class=class="str">"cmt">// Order expiration date(for pending orders)
ORDER_PROP_TYPE_FILLING,                                                                   class=class="str">"cmt">// Type of execution by remainder
ORDER_PROP_TYPE_TIME,                                                                      class=class="str">"cmt">// Order lifetime
ORDER_PROP_STATUS,                                                                         class=class="str">"cmt">// Order status(from the ENUM_ORDER_STATUS enumeration)
ORDER_PROP_TYPE,                                                                           class=class="str">"cmt">// Order/deal type
ORDER_PROP_REASON,                                                                         class=class="str">"cmt">// Deal/order/position reason or source
ORDER_PROP_STATE,                                                                          class=class="str">"cmt">// Order status(from the ENUM_ORDER_STATE enumeration)
ORDER_PROP_POSITION_ID,                                                                    class=class="str">"cmt">// Position ID
ORDER_PROP_POSITION_BY_ID,                                                                 class=class="str">"cmt">// Opposite position ID
ORDER_PROP_DEAL_ORDER_TICKET,                                                              class=class="str">"cmt">// Ticket of the order that triggered a deal
ORDER_PROP_DEAL_ENTRY,                                                                     class=class="str">"cmt">// Deal direction – IN, OUT or IN/OUT
ORDER_PROP_TIME_UPDATE,                                                                    class=class="str">"cmt">// Position change time in milliseconds
ORDER_PROP_TICKET_FROM,                                                                    class=class="str">"cmt">// Parent order ticket
ORDER_PROP_TICKET_TO,                                                                      class=class="str">"cmt">// Derived order ticket
ORDER_PROP_PROFIT_PT,                                                                      class=class="str">"cmt">// Profit in points
ORDER_PROP_CLOSE_BY_SL,                                                                    class=class="str">"cmt">// Flag of closing by StopLoss
ORDER_PROP_CLOSE_BY_TP                                                                     class=class="str">"cmt">// Flag of closing by TakeProfit

订单枚举与排序宏定义怎么落到代码里

在 MT5 的订单属性体系里,整数类属性总数被固定为 26 个,宏 ORDER_PROP_INTEGER_TOTAL 直接写成 (26),而 ORDER_PROP_INTEGER_SKIP 为 0,代表没有整数属性被排除在排序之外。这两个宏是后续定位双精度、字符串属性的偏移基准。 排序枚举 ENUM_SORT_ORDERS_MODE 从 0 开始:SORT_BY_ORDER_TICKET 按订单号排,SORT_BY_ORDER_MAGIC 按魔术码归组,SORT_BY_ORDER_TIME_OPEN / CLOSE / EXP 分别按开仓、平仓、到期毫秒数排。其中 SORT_BY_ORDER_TYPE_FILLING 与 SORT_BY_ORDER_TYPE_TIME 负责按执行余量类型和订单生命周期分类。 FIRST_ORD_DBL_PROP 用 (ORDER_PROP_INTEGER_TOTAL - ORDER_PROP_INTEGER_SKIP) 算出双精度属性的起始下标,FIRST_ORD_STR_PROP 再叠加上双精度总数。你在写自定义订单池排序器时,直接引用这些宏就能避免硬写数字导致版本升级后错位。外汇与贵金属杠杆高,回测排序逻辑前先用历史 tick 验证属性偏移是否如预期。

MQL5 / C++
  ORDER_PROP_MAGIC_ID,                                                                     class=class="str">"cmt">// Order&class="macro">#x27;s "magic number" ID
  ORDER_PROP_GROUP_ID1,                                                                    class=class="str">"cmt">// First order/position group ID
  ORDER_PROP_GROUP_ID2,                                                                    class=class="str">"cmt">// Second order/position group ID
  ORDER_PROP_PEND_REQ_ID,                                                                  class=class="str">"cmt">// Pending request ID
  ORDER_PROP_DIRECTION,                                                                    class=class="str">"cmt">// Type by direction(Buy, Sell)
  };
class="macro">#define ORDER_PROP_INTEGER_TOTAL(class="num">26)                                                    class=class="str">"cmt">// Total number of integer properties
class="macro">#define ORDER_PROP_INTEGER_SKIP(class="num">0)                                                     class=class="str">"cmt">// Number of order properties not used in sorting
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Possible criteria of sorting orders and deals                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define FIRST_ORD_DBL_PROP(ORDER_PROP_INTEGER_TOTAL-ORDER_PROP_INTEGER_SKIP)
class="macro">#define FIRST_ORD_STR_PROP(ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL-ORDER_PROP_INTEGER_SKIP)
enum ENUM_SORT_ORDERS_MODE
  {
class=class="str">"cmt">//--- Sort by integer properties
  SORT_BY_ORDER_TICKET = class="num">0,                                                               class=class="str">"cmt">// Sort by an order ticket
  SORT_BY_ORDER_MAGIC,                                                                    class=class="str">"cmt">// Sort by an order magic number
  SORT_BY_ORDER_TIME_OPEN,                                                                class=class="str">"cmt">// Sort by an order open time in milliseconds
  SORT_BY_ORDER_TIME_CLOSE,                                                               class=class="str">"cmt">// Sort by an order close time in milliseconds
  SORT_BY_ORDER_TIME_EXP,                                                                 class=class="str">"cmt">// Sort by an order expiration date
  SORT_BY_ORDER_TYPE_FILLING,                                                             class=class="str">"cmt">// Sort by execution type by remainder
  SORT_BY_ORDER_TYPE_TIME,                                                                class=class="str">"cmt">// Sort by order lifetime
  SORT_BY_ORDER_STATUS,                                                                   class=class="str">"cmt">// Sort by an order status(market order/pending order/deal/balance and credit operation)

「订单排序枚举的尾部清单」

在 MT5 的 HistorySelect / OrdersHistorySelect 之后,若想用 ArraySort 对订单数组做自定义排列,枚举值从 SORT_BY_ORDER_TYPE 一路铺到 SORT_BY_ORDER_PRICE_CLOSE,覆盖类型、来源、状态、持仓 ID、挂单衍生关系等维度。 下面这一段是该类枚举的尾部片段,注意 SORT_BY_ORDER_PRICE_OPEN 被显式赋值为 FIRST_ORD_DBL_PROP,意味着它起头的是「按浮点属性排序」的分组,后续 close price 等也归在此类。 实盘里若用 EA 批量统计某 magic number 下的止损/止盈平仓笔数,可直接按 SORT_BY_ORDER_CLOSE_BY_SL 或 SORT_BY_ORDER_MAGIC_ID 先排,再遍历,能少写几层 if。外汇与贵金属杠杆高,回测结论只代表历史概率,不代表未来分布。

MQL5 / C++
   SORT_BY_ORDER_TYPE,                                                                     class=class="str">"cmt">// Sort by an order type
   SORT_BY_ORDER_REASON,                                                                     class=class="str">"cmt">// Sort by a deal/order/position reason/source
   SORT_BY_ORDER_STATE,                                                                      class=class="str">"cmt">// Sort by an order status
   SORT_BY_ORDER_POSITION_ID,                                                                class=class="str">"cmt">// Sort by a position ID
   SORT_BY_ORDER_POSITION_BY_ID,                                                             class=class="str">"cmt">// Sort by an opposite position ID
   SORT_BY_ORDER_DEAL_ORDER,                                                                 class=class="str">"cmt">// Sort by the order a deal is based on
   SORT_BY_ORDER_DEAL_ENTRY,                                                                 class=class="str">"cmt">// Sort by a deal direction – IN, OUT or IN/OUT
   SORT_BY_ORDER_TIME_UPDATE,                                                                class=class="str">"cmt">// Sort by position change time in seconds
   SORT_BY_ORDER_TICKET_FROM,                                                                class=class="str">"cmt">// Sort by a parent order ticket
   SORT_BY_ORDER_TICKET_TO,                                                                  class=class="str">"cmt">// Sort by a derived order ticket
   SORT_BY_ORDER_PROFIT_PT,                                                                  class=class="str">"cmt">// Sort by order profit in points
   SORT_BY_ORDER_CLOSE_BY_SL,                                                                class=class="str">"cmt">// Sort by the flag of closing an order by StopLoss
   SORT_BY_ORDER_CLOSE_BY_TP,                                                                class=class="str">"cmt">// Sort by the flag of closing an order by TakeProfit
   SORT_BY_ORDER_MAGIC_ID,                                                                   class=class="str">"cmt">// Sort by an order/position "magic number" ID
   SORT_BY_ORDER_GROUP_ID1,                                                                  class=class="str">"cmt">// Sort by the first order/position group ID
   SORT_BY_ORDER_GROUP_ID2,                                                                  class=class="str">"cmt">// Sort by the second order/position group ID
   SORT_BY_ORDER_PEND_REQ_ID,                                                                class=class="str">"cmt">// Sort by a pending request ID
   SORT_BY_ORDER_DIRECTION,                                                                  class=class="str">"cmt">// Sort by direction(Buy, Sell)
class=class="str">"cmt">//--- Sort by real properties
   SORT_BY_ORDER_PRICE_OPEN = FIRST_ORD_DBL_PROP,                                            class=class="str">"cmt">// Sort by open price
   SORT_BY_ORDER_PRICE_CLOSE,                                                                class=class="str">"cmt">// Sort by close price

常见问题

在抽象订单对象里增加平仓类型标记,把平仓请求像开仓一样推入延后队列,由统一调度在下一根K线或指定时间触发。
记录每次延后请求的订单ticket与预期修改值,在下一次轮询时比对终端订单属性,不一致就重入队或写日志。
可以,小布能按品种页扫描挂起的延后请求并比对终端实际订单状态,漏执行的会直接标红提醒你。
订单的挂单有效期与延后执行时间这两个字段最常被忽略,漏了会导致改单在错误时间点触发或不触发。
尾部枚举至少留2到3个未使用项,后续加执行类型或时间属性时不用动前面排序逻辑。