轻松快捷开发 MetaTrader 程序的函数库 (第 三十二部分) :延后交易请求 - 在特定条件下挂单(基础篇)
📘

轻松快捷开发 MetaTrader 程序的函数库 (第 三十二部分) :延后交易请求 - 在特定条件下挂单(基础篇)

第 1/3 篇

「把交易请求延后到条件触发再挂单」

在 MT5 里直接市价下单,常常错过你真正想要的入场位。把请求“延后”处理,就是先不立即成交,而是等价格或指标满足特定条件后,再自动发出挂单。 原文示例发布于 2020-04-15,在 MetaTrader 5 板块获得 4295 次查看、22 条讨论,说明这类延迟执行机制在实际 EA 开发里需求很实。 实现思路并不复杂:用一个函数库封装“待发送订单”的结构,主循环里只做条件判断,命中后才调用 TradeSend。这样能把策略逻辑和订单执行解耦,回测时也更方便定位漏单原因。 外汇与贵金属杠杆高、滑点随机,延后挂单只降低主观误操作,不消除行情跳空风险,条件触发也可能因流动性断裂而偏离预期价位。

把延后请求扩展成挂单触发器

上一篇我们已经能让 EA 在预设条件齐活时直接发市价开仓请求。但真实盘口里,很多策略并不想立刻吃滑点,而是等价格触碰到某个价位再挂单进场。 这篇要做的事很具体:把原来的「延后交易请求」对象改造成能容纳挂单全部必要参数的结构。也就是说,除了账户、品种、事件状态这些老条件,还要把挂单类型、挂单价、挂单有效期这些字段塞进同一个延迟判定逻辑里。 实现之后,EA 不再只在条件满足时市价追单,而是可以在条件满足的那一刻,按你设定的挂单参数丢出一张限价或止损单。外汇和贵金属杠杆高、跳空频繁,挂单触发价和实际成交价可能有偏差,这根红线得自己盯紧。 下一篇会接着聊怎么在测试环境里跑这套挂单逻辑,验证不同品种上的触发表现。

◍ 延后挂单的距离到底锚定哪一刻的价

延后请求对象内部维护着一个数组,存着它所有激活条件。交易管理类靠计时器持续扫这个列表,一旦条件全满足,就把订单发往服务器,参数取自触发时那条延后请求里设好的值。 开仓逻辑很简单:你只管盯条件,条件发生订单就走。但用延后请求下挂单有个坑——挂单天生带距离,而市价开仓用的是当下现价。所以在条件处理里,必须额外把挂单距离算进去。 问题出在创建延后请求时:距离要指定,可「以哪个价格为基准」?是建请求那一刻的价,还是所有条件满足瞬间的价?后者往往离建单价位很远,除「激活条件只有价格本身」这种特例,未来价根本不可知。 实操做法是:建请求时给定距离,这个距离用创建时刻的当前要价/出价(按挂单方向取)与请求属性里写的挂单价做差得到,随时可见。激活时两条路——相对现价重算挂单价,或死守建单基准价发单。选后者时,若等待期价格跑出有效范围,订单价会被自动调整。 外汇与贵金属杠杆高、滑点凶,这种距离重算逻辑在剧震行情可能让挂单落在意料外位置,上 MT5 用延期请求对象跑一遍不同波动场景最踏实。

「给延后挂单加上价格追随开关」

在 CPendRequest 抽象类的私有段里,先补一个 bool 型成员 m_follow,用来标记挂单是否按参考点数追随当前价格。值为 true 时,激活延后请求会按当前价重算挂单价;值为 false 则沿用对象属性里的原价格,仅当原价位已失效才做调整。 受保护段声明 SetAllMqlPrices(double shift),在类外实现时把挂单各价格属性减去偏移量。注意止损、止盈和 StopLimit 价位要先判非零,避免把空价位写脏。 构造函数里默认把 m_follow 置 false,也就是不追随,行为向后兼容旧逻辑。AdjustPendingPrices(double current) 方法接收挂单当前价,若 m_follow 为 false 或请求结构里根本没挂单,直接 return;否则用「建对象时的价格 - 当前价」算偏移,再调 SetAllMqlPrices 改写。 CPendReqControl 里把 OpenPositionPending / PlaceOrderPending 改名为 CreatePReqPosition / CreatePReqOrder,并在 CreatePReqOrder 增加组 ID 入参。处理延后请求时,若交易操作类型是下挂单,就调用调价方法——追随与否完全看 m_follow。 CreatePendingRequest() 在 Trading.mqh 中按订单方向取价:买用 Ask、卖用 Bid,并把该价传给延后对象作创建价。CEngine 公开段新增统一建挂单延后请求的方法,MQL4 因无 StopLimit 类直接返回 WRONG_VALUE。外汇与贵金属杠杆高,改完请在 MT5 策略测试器用历史 tick 验证调价偏移是否如预期。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Abstract pending trading request class                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
<span class="keyword">class</span> CPendRequest : <span class="keyword">class="kw">public</span> CBaseObj
&nbsp;&nbsp;{
<span class="keyword">class="kw">private</span>:
&nbsp;&nbsp; <span class="predefines">class="type">MqlTradeRequest</span>&nbsp;&nbsp; m_request;&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;&nbsp;&nbsp;&nbsp;&nbsp;class=class="str">"cmt">// Trade request structure
&nbsp;&nbsp; CPause&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_pause;&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;class=class="str">"cmt">// Pause class object
&nbsp;&nbsp; <span style="background-class="type">color:rgb(class="num">255, class="num">242, class="num">153);"><span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_follow;</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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; class=class="str">"cmt">// The flag of the pending order distance reference point following the price
/* Data on a pending request activation in the array:
<span class="comment">class=class="str">"cmt">//--- Return the number of decimal places of a controlled class="kw">property</span>
&nbsp;&nbsp; <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DigitsControlledValue(<span class="keyword">class="kw">const</span> <span class="keyword">class="type">uint</span> index)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">const</span>;
<span class="comment">class=class="str">"cmt">//--- Set a new value changed by the shift(+/-) for all order prices</span>
&nbsp;&nbsp; <span style="background-class="type">color:rgb(class="num">255, class="num">242, class="num">153);"><span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SetAllMqlPrices(<span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span> shift);</span>
<span class="keyword">class="kw">public</span>:
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Methods of a simplified access to the request object properties&nbsp;&nbsp;|</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//--- Return(class="num">1) request structure, (class="num">2) status, (class="num">3) type, (class="num">4) price at the moment of the request generation,</span>
<span class="comment">class=class="str">"cmt">//--- (class="num">5) request generation time, (class="num">6) next attempt activation time,</span>
<span class="comment">class=class="str">"cmt">//--- (class="num">7) waiting time between requests, (class="num">8) current attempt index,</span>
<span class="comment">class=class="str">"cmt">//--- (class="num">9) number of attempts, (class="num">10) request ID</span>
<span class="comment">class=class="str">"cmt">//--- (class="num">11) result a request is based on,</span>
<span class="comment">class=class="str">"cmt">//--- (class="num">12) order ticket, (class="num">13) position ticket, (class="num">14) trading operation type</span>

挂单请求对象的方法暴露

在 MT5 的挂单请求封装类里,这组 const 方法把内部状态直接吐给外部调用者,省去反复拆 m_request 的麻烦。 MqlRequest() 返回原始的 MqlTradeRequest 结构体引用,其余方法全部走 GetProperty 拿枚举属性,类型转换写死在 return 里。 Status() 回的是 ENUM_PEND_REQ_STATUS,TypeRequest() 回 ENUM_PEND_REQ_TYPE,两者都靠 PEND_REQ_PROP_* 常量定位。PriceCreate() 给挂单触发价,TimeCreate() 是下单毫秒时间戳,TimeActivate() 记录激活时刻,WaitingMSC() 返回排队等待的毫秒数。 CurrentAttempt() 与 TotalAttempts() 都是 uchar 强转,前者看当前第几次重试,后者是总重试上限。外汇与贵金属挂单受点差与滑点影响,重试次数并非越多越好,实盘里倾向把 TotalAttempts 压在 3 以内。 开 MT5 把这段贴进 EA 的挂单管理类,编译后打断点看 GetProperty 返回的裸值,能确认你的属性枚举是否和 broker 端一致。

MQL5 / C++
  class="type">MqlTradeRequest     MqlRequest(class="type">void)                                                                      class="kw">const { class="kw">return this.m_request;                                                                             }
  ENUM_PEND_REQ_STATUS Status(class="type">void)                                                                          class="kw">const { class="kw">return (ENUM_PEND_REQ_STATUS)this.GetProperty(PEND_REQ_PROP_STATUS);  }
  ENUM_PEND_REQ_TYPE   TypeRequest(class="type">void)                                                                     class="kw">const { class="kw">return (ENUM_PEND_REQ_TYPE)this.GetProperty(PEND_REQ_PROP_TYPE);        }
  class="type">class="kw">double               PriceCreate(class="type">void)                                                                     class="kw">const { class="kw">return this.GetProperty(PEND_REQ_PROP_PRICE_CREATE);                     }
  class="type">class="kw">ulong                TimeCreate(class="type">void)                                                                      class="kw">const { class="kw">return this.GetProperty(PEND_REQ_PROP_TIME_CREATE);                      }
  class="type">class="kw">ulong                TimeActivate(class="type">void)                                                                     class="kw">const { class="kw">return this.GetProperty(PEND_REQ_PROP_TIME_ACTIVATE);                    }
  class="type">class="kw">ulong                WaitingMSC(class="type">void)                                                                       class="kw">const { class="kw">return this.GetProperty(PEND_REQ_PROP_WAITING);                          }
  class="type">uchar                CurrentAttempt(class="type">void)                                                                   class="kw">const { class="kw">return (class="type">uchar)this.GetProperty(PEND_REQ_PROP_CURRENT_ATTEMPT);           }
  class="type">uchar                TotalAttempts(class="type">void)                                                                    class="kw">const { class="kw">return (class="type">uchar)this.GetProperty(PEND_REQ_PROP_TOTAL);                     }

◍ 挂单请求对象的属性读取接口

在 MQL5 的挂单请求封装类里,一组 const 成员函数把底层 GetProperty 调用暴露成直观接口,方便在 EA 里直接取回某次挂单请求的标识与成交细节。 下面这段声明覆盖了请求 ID、服务器返回码、关联订单与持仓号,以及交易动作类型: uchar ID(void) const { return (uchar)this.GetProperty(PEND_REQ_PROP_ID); } int Retcode(void) const { return (int)this.GetProperty(PEND_REQ_PROP_RETCODE); } ulong Order(void) const { return this.GetProperty(PEND_REQ_PROP_MQL_REQ_ORDER); } ulong Position(void) const { return this.GetProperty(PEND_REQ_PROP_MQL_REQ_POSITION); } ENUM_TRADE_REQUEST_ACTIONS Action(void) const { return (ENUM_TRADE_REQUEST_ACTIONS)this.GetProperty(PEND_REQ_PROP_MQL_REQ_ACTION); } 注释里点明,实际成交量、挂单/止损/止盈价格、填充模式、过期方式与存活时长共 8 类数据可通过同名函数取回,已列出的几个是 ActualVolume、ActualPrice、ActualStopLimit。 double ActualVolume(void) const { return this.GetProperty(PEND_REQ_PROP_ACTUAL_VOLUME); } double ActualPrice(void) const { return this.GetProperty(PEND_REQ_PROP_ACTUAL_PRICE); } double ActualStopLimit(void) const { return this.GetProperty(PEND_REQ_PROP_ACTUAL_STOPLIMIT); } 开 MT5 新建脚本,#include 对应交易库后,用 Order() 与 ActualPrice() 打印一笔挂单回执,就能验证这些接口返回的是成交时真实价格而非下单预设价,外汇与贵金属杠杆品种价格跳空时偏差可能明显。

MQL5 / C++
  class="type">uchar                 ID(class="type">void)                                                                              class="kw">const { class="kw">return (class="type">uchar)this.GetProperty(PEND_REQ_PROP_ID);                 }
  class="type">int                  Retcode(class="type">void)                                                                          class="kw">const { class="kw">return (class="type">int)this.GetProperty(PEND_REQ_PROP_RETCODE);             }
  class="type">class="kw">ulong                Order(class="type">void)                                                                            class="kw">const { class="kw">return this.GetProperty(PEND_REQ_PROP_MQL_REQ_ORDER);           }
  class="type">class="kw">ulong                Position(class="type">void)                                                                         class="kw">const { class="kw">return this.GetProperty(PEND_REQ_PROP_MQL_REQ_POSITION);        }
  ENUM_TRADE_REQUEST_ACTIONS Action(class="type">void)                                                                     class="kw">const { class="kw">return (ENUM_TRADE_REQUEST_ACTIONS)this.GetProperty(PEND_REQ_PROP_MQL_REQ_ACTION);  }
class=class="str">"cmt">//--- Return the actual(class="num">1) volume, (class="num">2) order, (class="num">3) limit order,
class=class="str">"cmt">//--- (class="num">4) stoploss order and(class="num">5) takeprofit order prices, (class="num">6) order filling type,
class=class="str">"cmt">//--- (class="num">7) order expiration type and(class="num">8) order lifetime
  class="type">class="kw">double               ActualVolume(class="type">void)                                                                    class="kw">const { class="kw">return this.GetProperty(PEND_REQ_PROP_ACTUAL_VOLUME);           }
  class="type">class="kw">double               ActualPrice(class="type">void)                                                                      class="kw">const { class="kw">return this.GetProperty(PEND_REQ_PROP_ACTUAL_PRICE);            }
  class="type">class="kw">double               ActualStopLimit(class="type">void)                                                                  class="kw">const { class="kw">return this.GetProperty(PEND_REQ_PROP_ACTUAL_STOPLIMIT);        }

常见问题

在延后交易请求里把触发价设为支撑位下方,条件满足时自动发挂单,不用人工盯盘守着。
默认锚定触发条件成立那一刻的现价来计算挂单距离,避免提前波动把单子挂歪。
小布盯盘的 AIGC 已内置条件触发诊断,打开对应品种页就能看延后挂单是否按设定触发,不用自己写代码。
开启价格追随开关后,挂单价会随触发后行情微调,降低脱离实时价过远导致的废单概率。
通过挂单请求对象暴露的读取接口直接取触发价、距离、开关状态等字段,便于日志排查和复用。