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

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

(2/3)· 当激活条件触发时现价早已偏离,挂单距离该以哪个基准重算才不滑点失控

含代码示例偏理论 第 2/3 篇
很多人在写延后挂单时直接拿创建请求那一刻的现价算距离,等条件真触发了,价格可能已跑出几百点,挂单直接变市价追单。还有人图省事写死挂单价,结果等待期价格越界,订单被服务器拒掉。这两种坑在 MT5 库开发里极其常见。

挂单请求里的实际成交参数怎么读

在 MT5 的挂单请求封装类里, broker 实际接受的止损、止盈与成交方式,往往和本地提交的不一致。类里用一组 Actual* 方法把服务器回写的值暴露出来,方便后续比对与重发。 ActualSL() 与 ActualTP() 直接返回 double 类型的实际止损、止盈价;ActualTypeFilling() 和 ActualTypeExpiration() 则把属性强转成对应的枚举,读的是 PEND_REQ_PROP_ACTUAL_TYPE_FILLING 与 PEND_REQ_PROP_ACTUAL_EXPIRATION。若 broker 拒单后改了填充策略,这里拿到的是改后的值。 CorrectMqlPricesByCurrentPrice() 是个关键修正入口:当现价跳变导致挂单价越界,调用它按当前价重算请求价。外汇与贵金属点差扩大时这类越界概率偏高,属于高风险场景下的必须处理。 SetMqlPrice() 与 SetMqlSL() 不只是写属性,还同步改了内部 m_request 结构体的 price 和 sl 字段。下面对这两段做逐行拆解,复制进 EA 调试时能看到请求对象被实时改写。

MQL5 / C++
class="type">class="kw">double ActualSL(class="type">void) class="kw">const { class="kw">return this.GetProperty(PEND_REQ_PROP_ACTUAL_SL); }
class="type">class="kw">double ActualTP(class="type">void) class="kw">const { class="kw">return this.GetProperty(PEND_REQ_PROP_ACTUAL_TP); }
ENUM_ORDER_TYPE_FILLING ActualTypeFilling(class="type">void) class="kw">const { class="kw">return (ENUM_ORDER_TYPE_FILLING)this.GetProperty(PEND_REQ_PROP_ACTUAL_TYPE_FILLING); }
ENUM_ORDER_TYPE_TIME ActualTypeTime(class="type">void) class="kw">const { class="kw">return (ENUM_ORDER_TYPE_TIME)this.GetProperty(PEND_REQ_PROP_ACTUAL_TYPE_TIME); }
class="type">class="kw">datetime ActualExpiration(class="type">void) class="kw">const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(PEND_REQ_PROP_ACTUAL_EXPIRATION); }
class=class="str">"cmt">//--- Modify order prices by the current price
class="type">void CorrectMqlPricesByCurrentPrice(class="kw">const class="type">class="kw">double price);
class=class="str">"cmt">//--- Set(class="num">1) the price when creating a request, (class="num">2) setting, (class="num">3) StopLoss, (class="num">4) TakeProfit, (class="num">5) stoplimit,
class=class="str">"cmt">//--- (class="num">6) request creation time, (class="num">7) current attempt time, (class="num">8) waiting time between requests, (class="num">9) current attempt index,
class=class="str">"cmt">//---  (class="num">10) number of attempts,(class="num">11) id, (class="num">12) order ticket, (class="num">13) position ticket, (class="num">14) pending request type
class="type">void SetPriceCreate(class="kw">const class="type">class="kw">double price) { this.SetProperty(PEND_REQ_PROP_PRICE_CREATE,price); }
class="type">void SetMqlPrice(class="kw">const class="type">class="kw">double price) { this.SetProperty(PEND_REQ_PROP_MQL_REQ_PRICE,price); this.m_request.price=price; }
class="type">void SetMqlSL(class="kw">const class="type">class="kw">double sl) { this.SetProperty(PEND_REQ_PROP_MQL_REQ_SL,sl); this.m_request.sl=sl; }

「挂单请求对象的属性写入接口」

在 MT5 的挂单请求封装类里,这组 setter 负责把交易参数写进内部状态。每一个方法都同时更新属性映射表 SetProperty 与原始请求结构体 m_request,保证后续发单时字段一致。 SetMqlTPSetMqlStopLimit 是单行实现:前者写止盈价到 PEND_REQ_PROP_MQL_REQ_TPm_request.tp,后者写 StopLimit 挂单触发价到对应属性与 m_request.stoplimit。 时间类参数分得较细:SetTimeCreate 除了记创建时间,还顺手调了 m_pause.SetTimeBegin 来框定挂单有效起始;SetTimeActivate 只写激活时间属性。SetWaitingMSC 同样双写——既存等待毫秒数,又下传到 m_pause 的等待计时。 重试控制由 SetCurrentAttemptSetTotalAttempts 接管,类型都是 uchar,意味着单次对象最多标记 255 次尝试;SetID 则给请求一个 uchar 级别的身份标号。开 MT5 把这些方法塞进自己的 EA 请求类,改 m_request 字段时就能直接复用,不必每次手写结构体赋值。

MQL5 / C++
class="type">void SetMqlTP(class="kw">const class="type">class="kw">double tp) { this.SetProperty(PEND_REQ_PROP_MQL_REQ_TP,tp); this.m_request.tp=tp; }
class="type">void SetMqlStopLimit(class="kw">const class="type">class="kw">double stoplimit) { this.SetProperty(PEND_REQ_PROP_MQL_REQ_STOPLIMIT,stoplimit); this.m_request.stoplimit=stoplimit; }
class="type">void SetTimeCreate(class="kw">const class="type">class="kw">ulong time)
{
   this.SetProperty(PEND_REQ_PROP_TIME_CREATE,time);
   this.m_pause.SetTimeBegin(time);
}
class="type">void SetTimeActivate(class="kw">const class="type">class="kw">ulong time) { this.SetProperty(PEND_REQ_PROP_TIME_ACTIVATE,time); }
class="type">void SetWaitingMSC(class="kw">const class="type">class="kw">ulong miliseconds)
{
   this.SetProperty(PEND_REQ_PROP_WAITING,miliseconds);
   this.m_pause.SetWaitingMSC(miliseconds);
}
class="type">void SetCurrentAttempt(class="kw">const class="type">uchar number) { this.SetProperty(PEND_REQ_PROP_CURRENT_ATTEMPT,number); }
class="type">void SetTotalAttempts(class="kw">const class="type">uchar number) { this.SetProperty(PEND_REQ_PROP_TOTAL,number); }
class="type">void SetID(class="kw">const class="type">uchar id) { this.SetProperty(PEND_REQ_PROP_ID,id); }

◍ 挂单请求对象的属性写入接口

在封装挂单请求的类里,一组 Set 方法负责把交易要素写进内部属性表,而不是每次都直接调 OrderSendAsync。先绑定 ticket 与请求类型,再填充实际成交参数,逻辑上把「身份」和「内容」分开处理。 下面这段是核心写入接口的原文,注意所有方法都走同一个 this.SetProperty 入口,属性键名以 PEND_REQ_PROP_ 前缀做命名空间隔离: void SetOrder(const ulong ticket) { this.SetProperty(PEND_REQ_PROP_MQL_REQ_ORDER,ticket); } void SetPosition(const ulong ticket) { this.SetProperty(PEND_REQ_PROP_MQL_REQ_POSITION,ticket); } void SetTypeRequest(const ENUM_PEND_REQ_TYPE type) { this.SetProperty(PEND_REQ_PROP_TYPE,type); } //--- Set the actual (1) volume, (2) order, (3) limit order, //--- (4) stoploss order and (5) takeprofit order prices, (6) order filling type, //--- (7) order expiration type and (8) order lifetime void SetActualVolume(const double volume) { this.SetProperty(PEND_REQ_PROP_ACTUAL_VOLUME,volume); } void SetActualPrice(const double price) { this.SetProperty(PEND_REQ_PROP_ACTUAL_PRICE,price); } void SetActualStopLimit(const double price) { this.SetProperty(PEND_REQ_PROP_ACTUAL_STOPLIMIT,price); } void SetActualSL(const double price) { this.SetProperty(PEND_REQ_PROP_ACTUAL_SL,price); } void SetActualTP(const double price) { this.SetProperty(PEND_REQ_PROP_ACTUAL_TP,price); } void SetActualTypeFilling(const ENUM_ORDER_TYPE_FILLING type) { this.SetProperty(PEND_REQ_PROP_ACTUAL_TYPE_FILLING,type); } void SetActualTypeTime(const ENUM_ORDER_TYPE_TIME type) { this.SetProperty(PEND_REQ_PROP_ACTUAL_TYPE_TIME,type); } 逐行看:SetOrder / SetPosition 接收 ulong 类型的 ticket,分别对应挂单与持仓的关联标识;SetTypeRequest 决定这是 limit、stop 还是其他挂单类别。SetActualVolume 到 SetActualTP 五个方法写入的是真实成交用的量价与止损止盈,和「请求层」的预设值解耦。 最后两个枚举型写入值得留意:ENUM_ORDER_TYPE_FILLING 控制成交回退规则(IOC/FOK/RETURN),ENUM_ORDER_TYPE_TIME 控制 GTC/DAY/指定过期。外汇与贵金属杠杆品种跳空频繁,填错 filling 类型可能在流动性中断时直接被拒单,开 MT5 用这两行改参数跑一遍更稳妥。

MQL5 / C++
class="type">void SetOrder(class="kw">const class="type">class="kw">ulong ticket) { this.SetProperty(PEND_REQ_PROP_MQL_REQ_ORDER,ticket); }
class="type">void SetPosition(class="kw">const class="type">class="kw">ulong ticket) { this.SetProperty(PEND_REQ_PROP_MQL_REQ_POSITION,ticket); }
class="type">void SetTypeRequest(class="kw">const ENUM_PEND_REQ_TYPE type) { this.SetProperty(PEND_REQ_PROP_TYPE,type); }

class=class="str">"cmt">//--- Set 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">void SetActualVolume(class="kw">const class="type">class="kw">double volume) { this.SetProperty(PEND_REQ_PROP_ACTUAL_VOLUME,volume); }
class="type">void SetActualPrice(class="kw">const class="type">class="kw">double price) { this.SetProperty(PEND_REQ_PROP_ACTUAL_PRICE,price); }
class="type">void SetActualStopLimit(class="kw">const class="type">class="kw">double price) { this.SetProperty(PEND_REQ_PROP_ACTUAL_STOPLIMIT,price); }
class="type">void SetActualSL(class="kw">const class="type">class="kw">double price) { this.SetProperty(PEND_REQ_PROP_ACTUAL_SL,price); }
class="type">void SetActualTP(class="kw">const class="type">class="kw">double price) { this.SetProperty(PEND_REQ_PROP_ACTUAL_TP,price); }
class="type">void SetActualTypeFilling(class="kw">const ENUM_ORDER_TYPE_FILLING type) { this.SetProperty(PEND_REQ_PROP_ACTUAL_TYPE_FILLING,type); }
class="type">void SetActualTypeTime(class="kw">const ENUM_ORDER_TYPE_TIME type) { this.SetProperty(PEND_REQ_PROP_ACTUAL_TYPE_TIME,type); }

挂单请求的激活条件怎么写

在 MT5 的 pending request 封装类里,激活条件不是写死在 EA 主循环里的,而是一组「源 + 属性 + 比较方式 + 控制值 + 实际值」的配对。源可以是账户、品种或交易事件三种,由 ENUM_PEND_REQ_ACTIVATION_SOURCE 指定。 SetNewActivationProperties() 一次性把控制属性、比较类型、对象值和实际值全塞进去;如果后面要改其中某一项,就用 SetActivationProperty()、SetActivationComparerType() 等四个独立 setter 按 index 改,返回 bool 表示改没改成功。 读的时候对称地用 GetActivationSource() 到 GetActivationActualValue() 五个 getter 把第 index 条条件拉出来。实盘里建议先打印这五个返回值,确认控制值和实际值偏差在预期内再放行下单——外汇和贵金属杠杆高,条件误触可能瞬间吃掉浮盈。 SetActualExpiration() 单独管过期时间,它走的是 PEND_REQ_PROP_ACTUAL_EXPIRATION 这个内部属性,和激活条件数组是两码事,别混进 index 体系。

MQL5 / C++
class="type">void SetActualExpiration(class="kw">const class="type">class="kw">datetime expiration) { this.SetProperty(PEND_REQ_PROP_ACTUAL_EXPIRATION,expiration); }

class="type">void SetNewActivationProperties(class="kw">const ENUM_PEND_REQ_ACTIVATION_SOURCE source,
                                class="kw">const class="type">int class="kw">property,
                                class="kw">const class="type">class="kw">double control_value,
                                class="kw">const ENUM_COMPARER_TYPE comparer_type,
                                class="kw">const class="type">class="kw">double actual_value);

class="type">bool SetActivationProperty(class="kw">const class="type">uint index,class="kw">const ENUM_PEND_REQ_ACTIVATION_SOURCE source,class="kw">const class="type">int class="kw">property);
class="type">bool SetActivationComparerType(class="kw">const class="type">uint index,class="kw">const ENUM_COMPARER_TYPE comparer_type);
class="type">bool SetActivationControlValue(class="kw">const class="type">uint index,class="kw">const class="type">class="kw">double value);
class="type">bool SetActivationActualValue(class="kw">const class="type">uint index,class="kw">const class="type">class="kw">double value);

ENUM_PEND_REQ_ACTIVATION_SOURCE GetActivationSource(class="kw">const class="type">uint index) class="kw">const;
class="type">int GetActivationProperty(class="kw">const class="type">uint index) class="kw">const;
ENUM_COMPARER_TYPE GetActivationComparerType(class="kw">const class="type">uint index) class="kw">const;
class="type">class="kw">double GetActivationControlValue(class="kw">const class="type">uint index) class="kw">const;
class="type">class="kw">double GetActivationActualValue(class="kw">const class="type">uint index) class="kw">const;

「挂单请求对象里的价格跟随开关」

在封装挂单请求的类里,IsFollowThePrice 这组存取函数控制着一个关键行为:参考点是否随报价移动。m_follow 为 true 时,挂单距离计算的基准会跟着现价走,适合做突破挂单的动态追踪;置 false 则锁定建仓时的静态参考价。 构造函数 CPendRequest 初始化时顺手把账户与品种精度算清楚了:用 AccountInfoInteger(ACCOUNT_MARGIN_MODE) 判断是否对冲账户(MQL4 下直接 true),再用 SymbolInfoInteger 取 SYMBOL_DIGITS 赋给 m_digits,DigitsLots 的返回值若为 0 则 m_digits_lot 兜底成 1。这两个精度字段直接影响后续价格与手数比较的容差。 别把正态当圣经:m_follow 的默认语义依赖上层调用 SetFollowThePrice 显式设定,若忘了置位,回测里挂单可能以错误参考距触发,外汇与贵金属杠杆高,这类隐性偏差会放大滑点风险。开 MT5 把这段类声明贴进 IDE,搜 m_follow 的所有写入口,确认每路挂单来源都显式设过标志。

MQL5 / C++
class=class="str">"cmt">//--- Return the flag of a successful check of all controlled object properties and the appropriate actual properties
  class="type">bool                IsAllComparisonCompleted(class="type">void)  class="kw">const;
class=class="str">"cmt">//--- Return/set the flag of the pending order distance reference point following the price
  class="type">bool                IsFollowThePrice(class="type">void)                                                    class="kw">const { class="kw">return this.m_follow; }
  class="type">void                SetFollowThePrice(class="kw">const class="type">bool flag)                                          { this.m_follow=flag;  }

class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Descriptions of request object properties                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constructor                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
CPendRequest::CPendRequest(class="kw">const ENUM_PEND_REQ_STATUS status,
                           class="kw">const class="type">uchar id,
                           class="kw">const class="type">class="kw">double price,
                           class="kw">const class="type">class="kw">ulong time,
                           class="kw">const class="type">MqlTradeRequest &request,
                           class="kw">const class="type">int retcode)
  {
   this.CopyRequest(request);
   this.m_is_hedge=class="macro">#ifdef __MQL4__ true class="macro">#else class="type">bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) class="macro">#endif;
   this.m_digits=(class="type">int)::SymbolInfoInteger(this.GetProperty(PEND_REQ_PROP_MQL_REQ_SYMBOL),SYMBOL_DIGITS);
   class="type">int dg=(class="type">int)DigitsLots(this.GetProperty(PEND_REQ_PROP_MQL_REQ_SYMBOL));
   this.m_digits_lot=(dg==class="num">0 ? class="num">1 : dg);
   this.SetProperty(PEND_REQ_PROP_STATUS,status);
   this.SetProperty(PEND_REQ_PROP_ID,id);
   this.SetProperty(PEND_REQ_PROP_RETCODE,retcode);
   this.SetProperty(PEND_REQ_PROP_TYPE,this.GetProperty(PEND_REQ_PROP_RETCODE)>class="num">0 ? PEND_REQ_TYPE_ERROR : PEND_REQ_TYPE_REQUEST);
   this.SetProperty(PEND_REQ_PROP_TIME_CREATE,time);
   this.SetProperty(PEND_REQ_PROP_PRICE_CREATE,price);
   this.m_pause.SetTimeBegin(this.GetProperty(PEND_REQ_PROP_TIME_CREATE));
   this.m_pause.SetWaitingMSC(this.GetProperty(PEND_REQ_PROP_WAITING));
   ::ArrayResize(this.m_activated_control,class="num">0,class="num">10);

◍ 挂单价格随现价整体平移的实现

在挂单类里给所有价格成员做同步偏移,核心思路是统一减去一个 shift 量。SetAllMqlPrices 会对挂单触发价、SL、TP、StopLimit 四个字段分别处理,前提是对应属性不等于 0,避免把未设置的 0 当成真实价格去平移。 代码里 CorrectMqlPricesByCurrentPrice 接收当前价 price,先判断 m_follow 开关与订单类型区间。只有开启跟随且类型落在 ORDER_TYPE_BUY_LIMIT 到 ORDER_TYPE_SELL_STOP_LIMIT 之间,才用 PriceCreate() 算出的基准价减去当前价,把差值喂给 SetAllMqlPrices 实现整组价格重定位。 外汇与贵金属挂单受点差和滑点影响明显,这类自动平移在高波动时段可能触发频繁重写请求,实盘前建议在 MT5 策略测试器用 2023 年 XAUUSD 的 M1 数据跑一遍,观察 m_follow 置 true 时请求重写次数是否超出预期。

MQL5 / C++
this.m_follow=true;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Set a new value changed by the shift(+/-),                      |
class=class="str">"cmt">//| for all order prices(+/-)                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CPendRequest::SetAllMqlPrices(class="kw">const class="type">class="kw">double shift)
  {
   this.SetMqlPrice(this.GetProperty(PEND_REQ_PROP_MQL_REQ_PRICE)-shift);
   if(this.GetProperty(PEND_REQ_PROP_MQL_REQ_SL)!=class="num">0)
      this.SetMqlSL(this.GetProperty(PEND_REQ_PROP_MQL_REQ_SL)-shift);
   if(this.GetProperty(PEND_REQ_PROP_MQL_REQ_TP)!=class="num">0)
      this.SetMqlTP(this.GetProperty(PEND_REQ_PROP_MQL_REQ_TP)-shift);
   if(this.GetProperty(PEND_REQ_PROP_MQL_REQ_STOPLIMIT)!=class="num">0)
      this.SetMqlStopLimit(this.GetProperty(PEND_REQ_PROP_MQL_REQ_STOPLIMIT)-shift);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Adjust order prices by the current price                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CPendRequest::CorrectMqlPricesByCurrentPrice(class="kw">const class="type">class="kw">double price)
  {
   ENUM_ORDER_TYPE type=this.m_request.type;
   if(!this.m_follow || (type<ORDER_TYPE_BUY_LIMIT && type>ORDER_TYPE_SELL_STOP_LIMIT))
      class="kw">return;
   this.SetAllMqlPrices(this.PriceCreate()-price);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- (class="num">1) Create a pending request(class="num">1) to open a position, (class="num">2) to place a pending order
   class="kw">template<class="kw">typename SL,class="kw">typename TP>
   class="type">int                 CreatePReqPosition(class="kw">const class="type">ENUM_POSITION_TYPE type,
                                          class="kw">const class="type">class="kw">double volume,
                                          class="kw">const class="type">class="kw">string symbol,
                                          class="kw">const class="type">class="kw">ulong magic=ULONG_MAX,
把距离重算交给小布
小布盯盘已内置基于延后请求属性的挂单距离推演,打开对应品种页就能看到不同激活场景下的重算价区间,你只管定方向与阈值。

常见问题

相对现价重算会在条件满足那一刻按当前买价或卖价加减预设距离下挂单,避免等待期价格漂移导致距离失真;写死基准价则以创建请求时价位为锚,若触发前价格越界需库内自动调整,否则订单无效。
开仓以触发时当前价成交,无偏移;挂单必须与现价保持最小距离限制,且距离随价格波动含义变化,因此延后请求对象需额外存距离参数与重算规则。
通常在延后请求对象中设枚举字段标 buy/sell stop 或 limit,并分别存创建时的 ask/bid 与订单价,由计时器在激活时读取计算差值。
可以,小布的 AIGC 看板能按你库里的距离规则模拟多情景激活价,省去手动回放 tick 数据核对。
此时未来价已知,可直接用指定价下单,但依然建议保留距离字段以应对 broker 最小距离变更,减少重编译。