轻松快捷开发 MetaTrader 程序的函数库 (第 三十二部分) :延后交易请求 - 在特定条件下挂单·进阶篇
(2/3)· 当激活条件触发时现价早已偏离,挂单距离该以哪个基准重算才不滑点失控
挂单请求里的实际成交参数怎么读
在 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 调试时能看到请求对象被实时改写。
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,保证后续发单时字段一致。
SetMqlTP 与 SetMqlStopLimit 是单行实现:前者写止盈价到 PEND_REQ_PROP_MQL_REQ_TP 和 m_request.tp,后者写 StopLimit 挂单触发价到对应属性与 m_request.stoplimit。
时间类参数分得较细:SetTimeCreate 除了记创建时间,还顺手调了 m_pause.SetTimeBegin 来框定挂单有效起始;SetTimeActivate 只写激活时间属性。SetWaitingMSC 同样双写——既存等待毫秒数,又下传到 m_pause 的等待计时。
重试控制由 SetCurrentAttempt 和 SetTotalAttempts 接管,类型都是 uchar,意味着单次对象最多标记 255 次尝试;SetID 则给请求一个 uchar 级别的身份标号。开 MT5 把这些方法塞进自己的 EA 请求类,改 m_request 字段时就能直接复用,不必每次手写结构体赋值。
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 用这两行改参数跑一遍更稳妥。
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 体系。
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 的所有写入口,确认每路挂单来源都显式设过标志。
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 时请求重写次数是否超出预期。
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,