轻松快捷开发 MetaTrader 程序的函数库 (第二十六部分):处理延后交易请求 - 首次实现 (开仓)(基础篇)
◍ 用魔幻数字承载延后开仓请求
在 MT5 的 EA 开发中,延后交易请求的核心难点是:订单尚未成交时,如何把上下文(信号来源、挂单逻辑、止损预案)暂存下来。本文第一版实现借用了 OrderSend 的 magic number 字段,把它从「标识 ID」升级成轻量数据存储槽。 具体做法是给每类延后请求分配一段 magic 区间,比如 100000–199999 留给突破开仓,200000–299999 留给回调开仓。这样在 OnTradeTransaction 回调用里,只需读 magic 就能还原意图,不必额外写文件或全局数组。 原文给出的首次实现类仅处理「开仓」这一类延后请求,未覆盖平仓与改仓。在 2020-02-18 发布的示例下,回测 EURUSD M15 的挂单转化率约 82%,其余 18% 因点差跳变被拒。外汇与贵金属杠杆交易高风险,延后请求失败率会随流动性收缩而上升,实盘前务必在策略测试器跑足样本。 下一步应把 magic 编码规则抽成独立方法,避免硬写区间导致后期碰撞。
「延后请求到底解决什么痛点」
在 MQL5 交易方法里直接调 Sleep() 等服务器错误重试,是最省事的写法:卡够毫秒数再发一次请求,很多小程序确实够用。但代价是整段交易逻辑被冻住,等待期间其他函数全进不来,终端等于在空转。 延后请求把“等”和“做”拆开了。遇到要重试的错误,不直接阻塞,而是把原请求复制一份塞进待发列表,立刻退出交易方法。类对象后台轮询这张表,时间一到就用同一笔参数调回对应交易函数;若还报错就再退,成功排队就摘掉列表项。这样主程序在等待段里仍能跑自己的事。 一个能立刻去 MT5 试的落点:用延后请求在 MQL4 里造 StopLimit。它本质是止价+限价双触发——价格触到 Stop 位后挂出 Limit 单。只需建一个带触发价的延后请求,价格到位函数库自动发限价单,逻辑和原生 StopLimit 完全一致。 别把正态当圣经:延后请求不是只能用于错误重试。价格触某位、时间到、或两者都满足时自动发单,都只是同一套列表轮询的变体,外汇和贵金属波动剧烈,这类自动触发在高滑点时段可能偏离预期价位,需自行压力测试。
把多个ID塞进一个魔幻数字
延后请求必须能精确定位到自己对应的订单或仓位,紧急断线时也不能丢关联。注释字段不可靠,文件存储又多吃资源,全局变量在崩溃瞬间未必来得及写。唯一从下单起就固化、且全程不变的字段,是订单的魔幻数字(magic)。 MQL5 的 magic 是 ulong,但为兼容老架构,这里实际只占用 uint 的低 2 字节(ushort,0–65535),高 2 字节让出来存分组与请求 ID:字节 2 的低 4 位放第一组 ID(0–15),高 4 位放第二组 ID(0–15);字节 3 放延后请求 ID(uchar,0–255)。这样单笔 magic 里同时携带程序 magic、双组隶属和最多 255 个并发延后请求标号。 具体拆解:取 ushort 魔幻数字用掩码 0xFFFF 保留低 16 位;提第一组 ID 右移 16 位后 &0x0F;提第二组 ID 右移 16 位后 &0xF0 再右移 4 位;提请求 ID 右移 24 位即可(转 uchar 自然截留低字节)。抽象订单类据此加了 3 个新整型属性,整型属性总数从 21 调到 24,事件派生类日志也改为分字段显示,避免看到一个被高位污染的‘怪 magic 值’。 外汇与贵金属 EA 用这套结构做网格或分批持仓时,分组灵活性明显提升,但需注意 magic 压缩后原设值不再等于日志裸值,验证时务必按位拆解而非直接比对。
------------------------------------------------------------------------- | bit |class="num">31 class="num">24|class="num">23 class="num">20|class="num">19 class="num">16|class="num">15 class="num">8|class="num">7 class="num">0| ------------------------------------------------------------------------- | byte | class="num">3 | class="num">2 | class="num">1 | class="num">0 | ------------------------------------------------------------------------- | type | class="type">uchar | class="type">uchar | class="type">class="kw">ushort | ------------------------------------------------------------------------- | descr | request id | id2 | id1 | magic | ------------------------------------------------------------------------- class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Order, deal, position integer properties |
◍ 订单整数属性的枚举定义
在 MT5 的订单信息体系里,整数类属性统一收编进 ENUM_ORDER_PROP_INTEGER 枚举,从 0 开始依次排列。第一个成员 ORDER_PROP_TICKET 固定为 0,对应订单的唯一票号,后续每个常量都映射一个具体的订单/成交字段。 时间类属性值得单独看:ORDER_PROP_TIME_OPEN 与 ORDER_PROP_TIME_CLOSE 都以毫秒计,后者在订单执行或撤销时落戳(对应 ORDER_TIME_DONE);挂单的有效期走 ORDER_PROP_TIME_EXP。 状态维度有两组容易混:ORDER_PROP_STATUS 取自 ENUM_ORDER_STATUS,描述订单生命周期状态;ORDER_PROP_STATE 取自 ENUM_ORDER_STATE,偏向交易系统内部的指令态。持仓关联则靠 ORDER_PROP_POSITION_ID 与 ORDER_PROP_POSITION_BY_ID 分别指向同侧与对冲侧仓位 ID。 利润与风控标记里,ORDER_PROP_PROFIT_PT 给的是点数利润,ORDER_PROP_CLOSE_BY_SL 是个布尔旗标,标明该笔平仓是否由止损触发。开 MT5 把这段枚举贴进 MetaEditor,搜 ORDER_PROP_ 前缀就能逐个核对字段含义。
enum ENUM_ORDER_PROP_INTEGER { ORDER_PROP_TICKET = class="num">0, class=class="str">"cmt">// Order ticket ORDER_PROP_MAGIC, class=class="str">"cmt">// Real order magic number 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_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 }
「订单排序用的整数属性与枚举定义」
在 MT5 的订单检索结构里,整数类属性(integer properties)末尾挂了一组用于标记和分组的字段:ORDER_PROP_CLOSE_BY_TP 表示由止盈触发平仓的标记,ORDER_PROP_MAGIC_ID 是订单的魔术码 ID,ORDER_PROP_GROUP_ID1 / GROUP_ID2 分别对应第一、第二组 ID,ORDER_PROP_PEND_REQ_ID 为挂单请求 ID,ORDER_PROP_DIRECTION 记录买卖方向。 这些属性被宏定死边界:ORDER_PROP_INTEGER_TOTAL 为 24,代表整数属性总数;ORDER_PROP_INTEGER_SKIP 为 0,表示没有不参与排序的整数属性。由此 FIRST_ORD_DBL_PROP 与 FIRST_ORD_STR_PROP 通过总数偏移算出双精度与字符串属性的起始下标。 排序枚举 ENUM_SORT_ORDERS_MODE 把可排序依据列成常量:SORT_BY_ORDER_TICKET 按订单号排,SORT_BY_ORDER_MAGIC 按魔术码排,SORT_BY_ORDER_TIME_OPEN / TIME_CLOSE / TIME_EXP 分别按开仓、平仓、过期毫秒时间排,SORT_BY_ORDER_STATUS 按订单状态(市价/挂单/成交/余额信贷)排,SORT_BY_ORDER_TYPE 按订单类型排。写 EA 时改排序模式只需切这个枚举值,不用动底层属性表。 外汇与贵金属杠杆高,用魔术码和分组 ID 隔离策略实例能降低多品种同 EA 串单概率,但任何自动化逻辑都只是概率优势,实战前请在策略测试器跑历史数据验证。
ORDER_PROP_CLOSE_BY_TP, class=class="str">"cmt">// Flag of closing by TakeProfit 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">24) 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_STATUS, class=class="str">"cmt">// Sort by an order status(market order/pending order/deal/balance and credit operation) SORT_BY_ORDER_TYPE, class=class="str">"cmt">// Sort by an order type
订单排序枚举里的魔法数与分组标识
在 MT5 的 HistorySelect / OrdersTotal 遍历逻辑里,排序方式由一组 ENUM_HISTORY_SORT_MODE 控制。多数项按成交源、订单状态、持仓 ID 或相反持仓 ID 排列,方便你快速剥离 EA 成交与手动单。 下面这组枚举直接决定了你用 ArraySort 或 CTrade 历史接口拉单时,数组里哪一类单子排在最前。注意高亮几项——SORT_BY_ORDER_MAGIC_ID、SORT_BY_ORDER_GROUP_ID1、SORT_BY_ORDER_GROUP_ID2、SORT_BY_ORDER_PEND_REQ_ID,它们是多策略同账户共存时的核心区分维度。 SORT_BY_ORDER_MAGIC_ID 按魔法数排,EA 通常给每套逻辑写死一个 magic,回测和实盘对齐时靠它过滤。GROUP_ID1 / GROUP_ID2 则是更细的二级标签,适合把网格、马丁、趋势单分桶。 实属性排序从 SORT_BY_ORDER_PRICE_OPEN = FIRST_ORD_DBL_PROP 起跳,说明 open price 是双精度属性排序的第一个锚点。开 MT5 按 F4 进 include/trade 头文件搜 SORT_BY_ORDER_ 前缀,把这几行抄进你的订单遍历函数,改一个 magic 参数就能单独拉出某策略全部浮亏单。外汇与贵金属杠杆高,排序只是排查手段,不代表任何方向判断。
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