轻松快捷开发 MetaTrader 程序的函数库 (第 三十二部分) :延后交易请求 - 在特定条件下挂单(基础篇)
「把交易请求延后到条件触发再挂单」
在 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 验证调价偏移是否如预期。
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 { <span class="keyword">class="kw">private</span>: <span class="predefines">class="type">MqlTradeRequest</span> m_request; class=class="str">"cmt">// Trade request structure CPause m_pause; class=class="str">"cmt">// Pause class object <span style="background-class="type">color:rgb(class="num">255, class="num">242, class="num">153);"><span class="keyword">class="type">bool</span> m_follow;</span> 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> <span class="keyword">class="type">int</span> DigitsControlledValue(<span class="keyword">class="kw">const</span> <span class="keyword">class="type">uint</span> index) <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> <span style="background-class="type">color:rgb(class="num">255, class="num">242, class="num">153);"><span class="keyword">class="type">void</span> 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 |</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 端一致。
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() 打印一笔挂单回执,就能验证这些接口返回的是成交时真实价格而非下单预设价,外汇与贵金属杠杆品种价格跳空时偏差可能明显。
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); }