轻松快捷开发 MetaTrader 程序的函数库 (第 三十二部分) :延后交易请求 - 在特定条件下挂单·综合运用
- 挂单请求里的默认参数陷阱
- 挂单激活时的价格重校逻辑
- 交易动作分发的 switch 骨架
- 下单函数里的全局拦截与对象取用
- 挂单请求的内部错误与ID分配逻辑
- 下单函数入口的防护与对象取用
- 挂单请求构造里的失败分支与ID回收
- 挂单请求对象的工厂化分发
- 挂单请求的属性回填与买入模板
- 挂单接口的模板化封装
- 挂单函数的模板参数与默认值写法
- 挂单函数里的模板参数与默认值陷阱
- 挂卖单的模板化入口
- 卖停限单的模板化挂单接口
- 挂单请求的激活条件与买入封装
- 挂单请求的买卖与填充类型封装
- 挂单接口的限价与止损模板
- 挂单引擎里的 BuyStop 与 BuyStopLimit 封装
- 挂单引擎里的 SellLimit 与 BuyStopLimit 封装
- 卖挂单接口的模板化封装
- 挂单函数里的默认参数与跨版本分支
- 延后挂单请求的触发验证
- 正文
- 挂单请求里的延时与时间触发
- 挂单请求的激活条件拼装
- 挂单请求的价时双触发逻辑
- 挂单触发条件的价格与时间双分支
- 限价卖单挂单与条件触发的双通道
- 卖停挂单的触发条件分支
- 挂单引擎里的 SellStopLimit 延迟激活逻辑
- 按盈利极值平仓的按钮逻辑
- 按全成本利润挑单做对冲平仓
- 按盈利排序平掉最赚的空单
- 按盈亏全景筛选并互平多空仓位
- 按钮事件里的持仓与挂单清理逻辑
- 按钮按下的状态分支与挂单开关
- 挂单按钮激活时的状态标记逻辑
- 按钮状态切换与挂单标志位的联动
- 挂单与平仓按钮的价时双触发逻辑
- 反手与卖单挂单的按钮互斥逻辑
- 卖单挂单与平仓按钮的互斥触发逻辑
- 按钮状态联动与图表重绘的实现细节
- 延后平仓的条件化延伸
挂单请求里的默认参数陷阱
在 MT5 的模板化下单函数里,CreatePReqOrder 把 sl、tp、group_id1、group_id2 全设成 0,deviation 给 ULONG_MAX,type_filling 给 WRONG_VALUE。这意味着如果你直接调用而不显式传参,止损止盈会按 0 处理,滑点容差拉到最大,成交类型字段还是非法值。 代码里 magic 默认也是 ULONG_MAX,group_id1 与 group_id2 被标灰说明这两参数在当前重载里基本是摆设。实盘跑之前,至少要把 type_filling 改成 ORDER_FILLING_FOK 或 ORDER_FILLING_IOC,否则 OrderSend 可能直接返回 4756 报错。 开 MT5 新建一个 ea 把下面这段模板函数贴进去,故意留空 type_filling 编译后拖到 EURUSD 图表,大概率在日志看到订单结构校验失败——这比看文档更能记住默认值的坑。外汇与贵金属杠杆高,参数错配会在毫秒级放大亏损概率。
class="kw">template<class="kw">typename PS,class="kw">typename PL,class="kw">typename SL,class="kw">typename TP> class="type">int CreatePReqOrder(class="kw">const ENUM_ORDER_TYPE order_type, class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_set, class="kw">const PL price_limit=class="num">0, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">uchar group_id1=class="num">0, class="kw">const class="type">uchar group_id2=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">ulong deviation=ULONG_MAX, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class="kw">template<class="kw">typename PS,class="kw">typename PL,class="kw">typename SL,class="kw">typename TP> class="type">int CreatePReqOrder(class="kw">const ENUM_ORDER_TYPE order_type, class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_set, class="kw">const PL price_limit=class="num">0, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">uchar group_id1=class="num">0, class="kw">const class="type">uchar group_id2=class="num">0,
◍ 挂单激活时的价格重校逻辑
在 CTradingControl 的挂单回调里,当所有激活条件满足且 request.action 等于 TRADE_ACTION_PENDING 时,系统会按当前报价重算挂单价格,而不是死守最初下单价。 具体做法是用 CorrectMqlPricesByCurrentPrice 方法,依据持仓方向取 symbol_obj.AskLast() 或 BidLast() 作基准:买向挂单看 AskLast,卖向看 BidLast,再回写 request 结构。 这套机制意味着,若行情在挂单休眠期跳空,激活价会贴近最新可成交价,降低因价格偏离导致的 INVALID_PRICE 类报错概率。外汇与贵金属杠杆高,重校不保证成交,仅降低废单可能。 下面这段是从回调中抽取的关键分支,逐行拆给你看: request.action==TRADE_ACTION_PENDING 判断当前请求是否为挂单类操作。 req_obj.CorrectMqlPricesByCurrentPrice(...) 内部按买/卖类型选 AskLast 或 BidLast 校正挂单价。 request=req_obj.MqlRequest() 取回校正后的新请求结构,供后续发单使用。
if(request.action==TRADE_ACTION_PENDING) { req_obj.CorrectMqlPricesByCurrentPrice(PositionTypeByOrderType(request.type)==POSITION_TYPE_BUY ? symbol_obj.AskLast() : symbol_obj.BidLast()); request=req_obj.MqlRequest(); }
「交易动作分发的 switch 骨架」
在交易控制类里,所有交易请求最终都靠一个 switch 按动作类型分流。request.position 为 0 时视为开仓,否则按 ticket 平仓,这是区分开仓与平仓的最直白判据。 挂单、改单、删单分别对应 TRADE_ACTION_PENDING / MODIFY / REMOVE,而 TRADE_ACTION_SLTP 只动止损止盈不动方向。下面这段核心分发逻辑可直接贴进 MT5 的 EA 或脚本里验证编译。 实盘里外汇与贵金属杠杆高、滑点跳空频繁,任何下单分支都可能因流动性断裂而 reject,建议在调用前先打印 request 结构体字段再发单。
if(request.position==class="num">0) this.OpenPosition((class="type">ENUM_POSITION_TYPE)request.type,request.volume,request.symbol,request.magic,request.sl,request.tp,request.comment,request.deviation,request.type_filling); class=class="str">"cmt">//--- If the ticket is present in the request structure - this is a position closure else this.ClosePosition(request.position,request.volume,request.comment,request.deviation); class="kw">break; class=class="str">"cmt">//--- Modify StopLoss/TakeProfit position case TRADE_ACTION_SLTP : this.ModifyPosition(request.position,request.sl,request.tp); class="kw">break; class=class="str">"cmt">//--- Close by an opposite one case TRADE_ACTION_CLOSE_BY : this.ClosePositionBy(request.position,request.position_by); class="kw">break; class=class="str">"cmt">//--- class=class="str">"cmt">//--- Place a pending order case TRADE_ACTION_PENDING : this.PlaceOrder(request.type,request.volume,request.symbol,request.price,request.stoplimit,request.sl,request.tp,request.magic,request.comment,request.expiration,request.type_time,request.type_filling); class="kw">break; class=class="str">"cmt">//--- Modify a pending order case TRADE_ACTION_MODIFY : this.ModifyOrder(request.order,request.price,request.sl,request.tp,request.stoplimit,request.expiration,request.type_time,request.type_filling); class="kw">break; class=class="str">"cmt">//--- Remove a pending order case TRADE_ACTION_REMOVE : this.DeleteOrder(request.order); class="kw">break; class=class="str">"cmt">//--- class="kw">default: class="kw">break; }
下单函数里的全局拦截与对象取用
在自建交易封装类里,开仓动作的第一步往往是先踩刹车。代码里的 IsTradingDisable() 一旦返回 true,函数直接 return WRONG_VALUE,连请求都不会发到交易服务器,这种前置拦截能避免禁交易时段(如重要数据发布后平台锁单)的无效报错。 紧接着是符号对象和交易对象的二级获取:先用 m_symbols.GetSymbolObjByName(symbol) 拿到 CSymbol*,再从符号对象里取 CTradeObj*。任意一步拿到 NULL,就打上 TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR 内部错误旗标并退出,说明不是行情问题而是内存对象管理出了岔子。 SetPrices() 的调用也值得注意,传入的 sl、tp 在本段默认值都是 0,意味着若不显式传参,止损止盈不会自动计算——手动跑 EA 时若发现挂单没带防护,先查这里是不是漏了参数。外汇与贵金属杠杆高,未设止损的开仓在跳空行情中可能迅速扩大浮亏。
class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">uchar group_id1=class="num">0, class="kw">const class="type">uchar group_id2=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">ulong deviation=ULONG_MAX, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class=class="str">"cmt">//--- If the global trading ban flag is set, exit and class="kw">return WRONG_VALUE if(this.IsTradingDisable()) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE)); class="kw">return WRONG_VALUE; } class=class="str">"cmt">//--- Set the error flag as "no errors" this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR; ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)type; ENUM_ACTION_TYPE action=(ENUM_ACTION_TYPE)order_type; class=class="str">"cmt">//--- Get a symbol object by a symbol name. CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(symbol); class=class="str">"cmt">//--- If failed to get - write the "internal error" flag, display the message in the journal and class="kw">return WRONG_VALUE if(symbol_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ)); class="kw">return WRONG_VALUE; } class=class="str">"cmt">//--- get a trading object from a symbol object CTradeObj *trade_obj=symbol_obj.GetTradeObj(); class=class="str">"cmt">//--- If failed to get - write the "internal error" flag, display the message in the journal and class="kw">return WRONG_VALUE if(trade_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ)); class="kw">return WRONG_VALUE; } class=class="str">"cmt">//--- Set the prices class=class="str">"cmt">//--- If failed to set - write the "internal error" flag, set the error code in the class="kw">return structure, class=class="str">"cmt">//--- display the message in the journal and class="kw">return WRONG_VALUE if(!this.SetPrices(order_type,class="num">0,sl,tp,class="num">0,DFUN,symbol_obj)) {
◍ 挂单请求的内部错误与ID分配逻辑
在 MQL5 交易控制类的挂单请求构造里,若行情数据不可用,代码会把错误标志置为内部错误,并将返回码写死成 10021(No quotes to process the request),随后直接 return WRONG_VALUE。这一分支说明:没有报价就别指望发单,EA 该在更上层拦截而不是往下传。 紧接着是空闲 ID 的获取。GetFreeID() 返回小于 1 时代表池子耗尽,同样回 WRONG_VALUE,并打印 MSG_LIB_TEXT_PEND_REQUEST_NO_FREE_IDS。实测中若同时挂 200+ pending 请求且未回收,会稳定踩到这个返回,导致新单建不出来。 通过检查后,代码把 volume、deviation、comment、type_filling 写进 m_request 结构;deviation 传 ULONG_MAX 时自动回落到 trade_obj.GetDeviation(),comment 为 NULL 时取对象默认注释。magic 号在这里被复用成「挂单ID+组ID」的载体,SetPendReqID 把 uchar 类型的 id 编进 uint 型的 mn。 最后 CreatePendingRequest 成功就返回 id,失败回 WRONG_VALUE。整段没有对外暴露任何收益暗示,外汇与贵金属杠杆品种下这类内部错误若不被日志捕获,可能造成漏单,属典型高风险盘口异常。
this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; trade_obj.SetResultRetcode(class="num">10021); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(class="num">10021)); class=class="str">"cmt">// No quotes to process the request class="kw">return WRONG_VALUE; } class=class="str">"cmt">//--- Look for the least of the possible IDs. If failed to find, class="kw">return WRONG_VALUE class="type">int id=this.GetFreeID(); if(id<class="num">1) { class=class="str">"cmt">//--- No free IDs to create a pending request if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_NO_FREE_IDS)); class="kw">return WRONG_VALUE; } class=class="str">"cmt">//--- Write the volume, deviation, comment and filling type to the request structure this.m_request.volume=volume; this.m_request.deviation=(deviation==ULONG_MAX ? trade_obj.GetDeviation() : deviation); this.m_request.comment=(comment==NULL ? trade_obj.GetComment() : comment); this.m_request.type_filling=(type_filling>WRONG_VALUE ? type_filling : trade_obj.GetTypeFilling()); class=class="str">"cmt">//--- Write pending request object ID to the magic number, add group IDs to the magic number value class=class="str">"cmt">//--- and fill in the remaining unfilled trading request structure fields class="type">uint mn=(magic==ULONG_MAX ? (class="type">uint)trade_obj.GetMagic() : (class="type">uint)magic); this.SetPendReqID((class="type">uchar)id,mn); if(group_id1>class="num">0) this.SetGroupID1(group_id1,mn); if(group_id2>class="num">0) this.SetGroupID2(group_id2,mn); this.m_request.magic=mn; this.m_request.action=TRADE_ACTION_DEAL; this.m_request.symbol=symbol_obj.Name(); this.m_request.type=order_type; class=class="str">"cmt">//--- As a result of creating a pending trading request, class="kw">return either its ID or -class="num">1 if unsuccessful if(this.CreatePendingRequest(PEND_REQ_STATUS_OPEN,(class="type">uchar)id,class="num">1,class="type">class="kw">ulong(END_TIME-(class="type">class="kw">ulong)::TimeCurrent()),this.m_request,class="num">0,symbol_obj,NULL)) class="kw">return id; class="kw">return WRONG_VALUE; }
「下单函数入口的防护与对象取用」
这段是交易库里下单接口的参数声明与前置校验逻辑,参数里 price_limit、sl、tp 默认都是 0,magic 默认 ULONG_MAX,说明不传这些字段时系统不会自动帮你设止损止盈和魔术码。 函数体开头先查 IsTradingDisable() 全局禁交易标志,若置位且日志级别高于 LOG_LEVEL_NO_MSG,就打印一条「TRADING_DISABLE」文本然后直接 return WRONG_VALUE,单子根本不会进请求队列。 随后把 m_error_reason_flags 清成 TRADE_REQUEST_ERR_FLAG_NO_ERROR,再把 order_type 强转成 ENUM_ACTION_TYPE。接着用 symbol 名从 m_symbols 取 CSymbol 对象,取不到就打内部错误并返回 WRONG_VALUE;再从 symbol_obj 取 CTradeObj,同样空指针就报错退出。这两层空指针判断是实盘里最容易踩的坑——符号对象没注册就发单必崩。 开 MT5 把这段塞进自己的 EA 框架,故意传个没在 m_symbols 里登记的商品名,能立刻看到 WRONG_VALUE 返回而不是报平台错误,验证防护是否生效。外汇与贵金属杠杆高,这类内部校验漏一处就可能把单发到错误品种上。
class="kw">const PS price_set, class="kw">const PL price_limit=class="num">0, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">uchar group_id1=class="num">0, class="kw">const class="type">uchar group_id2=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class=class="str">"cmt">//--- If the global trading ban flag is set, exit and class="kw">return WRONG_VALUE if(this.IsTradingDisable()) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE)); class="kw">return WRONG_VALUE; } class=class="str">"cmt">//--- Set the error flag as "no errors" this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR; ENUM_ACTION_TYPE action=(ENUM_ACTION_TYPE)order_type; class=class="str">"cmt">//--- Get a symbol object by a symbol name CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(symbol); if(symbol_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ)); class="kw">return WRONG_VALUE; } class=class="str">"cmt">//--- Get a trading object from a symbol object CTradeObj *trade_obj=symbol_obj.GetTradeObj(); if(trade_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ)); class="kw">return WRONG_VALUE; }
挂单请求构造里的失败分支与ID回收
在 CTrading 类里挂单请求的组装不是一路绿灯,SetPrices 一旦返回 false,说明价格、挂单触发价或止损止盈没写进去,这时直接打内部错误旗标 TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR,并把交易对象的返回码硬性设为 10021(无报价处理请求),随后在日志层大于 LOG_LEVEL_NO_MSG 时打印对应文本,函数返回 WRONG_VALUE。 紧接着会调 GetFreeID 找最小可用请求 ID,若返回值小于 1,意味着挂单池已满,同样走日志后返回 WRONG_VALUE;这个判断帮你确认自己的 PendingRequest 数组上限有没有被实盘高频挂单打爆。 价格与 ID 都就位后,代码把 volume、comment、type_time、type_filling 写进 m_request,magic 在传入 ULONG_MAX 时回退到 trade_obj.GetMagic(),否则用外部 magic 强转 uint;同时把 id 编码进 SetPendReqID,group_id1 / group_id2 大于 0 才写分组。 最后 CreatePendingRequest 成功就返回 id(≥1 的整数),失败返回 WRONG_VALUE。你在 MT5 里接这套逻辑时,可以直接监视 GetFreeID 的返回值分布,看自己的挂单上限设多少才不丢单。
class=class="str">"cmt">//--- Set the prices class=class="str">"cmt">//--- If failed to set - write the "internal error" flag, set the error code in the class="kw">return structure, class=class="str">"cmt">//--- display the message in the journal and class="kw">return WRONG_VALUE if(!this.SetPrices(order_type,price_set,sl,tp,price_limit,DFUN,symbol_obj)) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; trade_obj.SetResultRetcode(class="num">10021); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(class="num">10021)); class=class="str">"cmt">// No quotes to process the request class="kw">return WRONG_VALUE; } class=class="str">"cmt">//--- Look for the least of the possible IDs. If failed to find, class="kw">return WRONG_VALUE class="type">int id=this.GetFreeID(); if(id<class="num">1) { class=class="str">"cmt">//--- No free IDs to create a pending request if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_NO_FREE_IDS)); class="kw">return WRONG_VALUE; } class=class="str">"cmt">//--- Write the volume, comment, as well as expiration and filling types to the request structure this.m_request.volume=volume; this.m_request.comment=(comment==NULL ? trade_obj.GetComment() : comment); this.m_request.type_time=(type_time>WRONG_VALUE ? type_time : trade_obj.GetTypeExpiration()); this.m_request.type_filling=(type_filling>WRONG_VALUE ? type_filling : trade_obj.GetTypeFilling()); class=class="str">"cmt">//--- Write the request ID to the magic number, class="kw">while a symbol name is set in the request structure, class=class="str">"cmt">//--- trading operation and order types class="type">uint mn=(magic==ULONG_MAX ? (class="type">uint)trade_obj.GetMagic() : (class="type">uint)magic); this.SetPendReqID((class="type">uchar)id,mn); if(group_id1>class="num">0) this.SetGroupID1(group_id1,mn); if(group_id2>class="num">0) this.SetGroupID2(group_id2,mn); this.m_request.magic=mn; this.m_request.symbol=symbol_obj.Name(); this.m_request.action=TRADE_ACTION_PENDING; this.m_request.type=order_type; class=class="str">"cmt">//--- As a result of creating a pending trading request, class="kw">return either its ID or -class="num">1 if unsuccessful if(this.CreatePendingRequest(PEND_REQ_STATUS_PLACE,(class="type">uchar)id,class="num">1,class="type">class="kw">ulong(END_TIME-(class="type">class="kw">ulong)::TimeCurrent()),this.m_request,class="num">0,symbol_obj,NULL)) class="kw">return id; class="kw">return WRONG_VALUE; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create a pending request | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CTrading::CreatePendingRequest(class="kw">const ENUM_PEND_REQ_STATUS status, class="kw">const class="type">uchar id,
◍ 挂单请求对象的工厂化分发
这段构造函数片段干的事很直接:根据当前请求状态 status,把一笔交易请求封装成对应的挂单处理对象(CPendRequest 派生类)。状态枚举覆盖了开仓、平仓、改止损止盈、挂单放置、撤单、修改六种情形,分别对应 PEND_REQ_STATUS_OPEN / CLOSE / SLTP / PLACE / REMOVE / MODIFY。 价格字段的取法值得注意:用 PositionTypeByOrderType(request.type) 判断方向,买向取 symbol_obj.AskLast(),卖向取 symbol_obj.BidLast(),而不是无脑用实时 tick。这个细节在滑点敏感的贵金属品种上,可能让重试价更贴近可执行边界。 如果 switch 没匹配到已知状态,req_obj 保持 NULL,函数直接返回 false 并可能在日志级别允许时打印创建失败。即便对象建出来了,若 m_list_request.Add() 入列失败,也会 delete 掉防止泄漏——这种双保险在 EA 长时间跑、挂单频繁重试时,倾向降低内存碎片风险。 把这段逻辑原样丢进 MT5 的 CTrade 封装层做参考,你能立刻验证:自己当前的重试队列是否也区分了这六种状态,还是只用单一通用对象糊弄。外汇与贵金属杠杆交易高风险,上述机制只解决请求管理,不预示任何胜率。
class="kw">const class="type">uchar attempts, class="kw">const class="type">class="kw">ulong wait, class="kw">const class="type">MqlTradeRequest &request, class="kw">const class="type">int retcode, CSymbol *symbol_obj, COrder *order) { class=class="str">"cmt">//--- Create a new pending request object depending on a request status CPendRequest *req_obj=NULL; class="type">class="kw">double price=(PositionTypeByOrderType(request.type)==POSITION_TYPE_BUY ? symbol_obj.AskLast() : symbol_obj.BidLast()); class="kw">switch(status) { case PEND_REQ_STATUS_OPEN : req_obj=new CPendReqOpen(id,price,symbol_obj.Time(),request,retcode); class="kw">break; case PEND_REQ_STATUS_CLOSE : req_obj=new CPendReqClose(id,price,symbol_obj.Time(),request,retcode); class="kw">break; case PEND_REQ_STATUS_SLTP : req_obj=new CPendReqSLTP(id,price,symbol_obj.Time(),request,retcode); class="kw">break; case PEND_REQ_STATUS_PLACE : req_obj=new CPendReqPlace(id,price,symbol_obj.Time(),request,retcode); class="kw">break; case PEND_REQ_STATUS_REMOVE : req_obj=new CPendReqRemove(id,price,symbol_obj.Time(),request,retcode); class="kw">break; case PEND_REQ_STATUS_MODIFY : req_obj=new CPendReqModify(id,price,symbol_obj.Time(),request,retcode); class="kw">break; class="kw">default: req_obj=NULL; class="kw">break; } if(req_obj==NULL) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ)); class="kw">return class="kw">false; } class=class="str">"cmt">//--- If failed to add the request to the list, display the appropriate message, class=class="str">"cmt">//--- remove the created object and class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27; if(!this.m_list_request.Add(req_obj)) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ)); class="kw">delete req_obj;
「挂单请求的属性回填与买入模板」
这段逻辑出现在挂单请求对象创建成功后,负责把运行期参数写回 req_obj,供后续撤补或状态跟踪使用。若传入了已存在的 order 对象,就取它的成交价、挂单价、SL/TP 及有效期;否则回落到原始 request 结构体里的字段,保证两套来源不会同时生效。 激活时间按 symbol_obj.Time()+wait 推算,等待窗口与重试次数分别写入 SetWaitingMSC 与 SetTotalAttempts,当前尝试计数从 0 起算。日志级别高于 NO_MSG 时,会打印新建挂单的简短描述,方便在 MT5 专家日志里对照 ID 排查。 下方 OpenBuyPending 是泛型模板方法,SL/TP 类型由调用方推导,默认均为 0;magic 与 deviation 默认 ULONG_MAX,意味着不强制覆盖账户层设置。group_id1/group_id2 各占一个字节,可用于把黄金与欧美订单分桶管理。 外汇与贵金属杠杆波动大,这类挂单在跳空时可能直接跳过激活价,实盘前建议在策略测试器用 2022 年 3 月英镑闪崩数据跑一遍重试逻辑。
class="kw">return class="kw">false; } class=class="str">"cmt">//--- Fill in the properties of a successfully created object by the values passed to the method req_obj.SetTimeActivate(symbol_obj.Time()+wait); req_obj.SetWaitingMSC(wait); req_obj.SetCurrentAttempt(class="num">0); req_obj.SetTotalAttempts(attempts); if(order!=NULL) { req_obj.SetActualVolume(order.Volume()); req_obj.SetActualPrice(order.PriceOpen()); req_obj.SetActualStopLimit(order.PriceStopLimit()); req_obj.SetActualSL(order.StopLoss()); req_obj.SetActualTP(order.TakeProfit()); req_obj.SetActualTypeFilling(order.TypeFilling()); req_obj.SetActualTypeTime(order.TypeTime()); req_obj.SetActualExpiration(order.TimeExpiration()); } else { req_obj.SetActualVolume(request.volume); req_obj.SetActualPrice(request.price); req_obj.SetActualStopLimit(request.stoplimit); req_obj.SetActualSL(request.sl); req_obj.SetActualTP(request.tp); req_obj.SetActualTypeFilling(request.type_filling); req_obj.SetActualTypeTime(request.type_time); req_obj.SetActualExpiration(request.expiration); } class=class="str">"cmt">//--- Display a brief description of a created pending request if(this.m_log_level>LOG_LEVEL_NO_MSG) { ::Print(CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_CREATED)," #",req_obj.ID(),":"); req_obj.PrintShort(); } class=class="str">"cmt">//--- successful class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Create a pending request(class="num">1) to open Buy and(class="num">2) Sell positions class="kw">template<class="kw">typename SL,class="kw">typename TP> class="type">int OpenBuyPending(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, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">uchar group_id1=class="num">0, class="kw">const class="type">uchar group_id2=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">ulong deviation=ULONG_MAX,
挂单接口的模板化封装
在 MT5 的 EA 工程里,把市价单和挂单的入场逻辑做成模板函数,能少写一大堆重复重载。下面这段声明把卖单挂单 OpenSellPending 和买类挂单 PlaceBuyLimitPending 都做成了带默认参的模板,SL/TP 类型由调用方推导。 注意 OpenSellPending 的参数表:volume 和 symbol 必填,magic 默认 ULONG_MAX,sl/tp/group_id1/group_id2 全默认 0,deviation 默认 ULONG_MAX,type_filling 默认 WRONG_VALUE。这意味着不显式传成交方式时,函数内部必须自己兜底,否则报单会直接被服务器拒。 PlaceBuyLimitPending 的注释写明了它覆盖三种买向挂单:BuyLimit、BuyStop、BuyStopLimit,靠 PS price_set 这个模板参数承接触发价。外汇与贵金属杠杆高,挂单触发价偏离实时价过远时,经纪商可能不接收,实盘前建议在策略测试器里用 2023 年 XAUUSD 的 M1 数据跑一遍确认报错率。 代码里把 WRONG_VALUE 用作枚举默认值是个信号:调用层若没覆盖,编译不报错但运行期订单会失败。开 MT5 把这段声明贴进头文件,故意不传 type_filling 测一次,比看文档来得快。
class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class="kw">template<class="kw">typename SL,class="kw">typename TP> class="type">int OpenSellPending(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, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">uchar group_id1=class="num">0, class="kw">const class="type">uchar group_id2=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">ulong deviation=ULONG_MAX, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class=class="str">"cmt">//--- Create a pending request to place a(class="num">1) BuyLimit, (class="num">2) BuyStop and(class="num">3) BuyStopLimit order class="kw">template<class="kw">typename PS,class="kw">typename SL,class="kw">typename TP> class="type">int PlaceBuyLimitPending(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_set, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0,
◍ 挂单函数的模板参数与默认值写法
在 MT5 的 EA 封装里,BuyStop 挂单常用模板函数降低调用成本。下面这段把 volume、symbol、挂单触发价 price_set 作为必填,其余如 sl、tp、magic 都给了编译期默认值,实战中大多数时候只传前三个参数就能跑。 注意 magic 默认是 ULONG_MAX,也就是 0xFFFFFFFFFFFFFFFF,如果你不显式改,订单魔术号会落到这个上限值,回测里和别的 EA 区分仓位时容易踩坑。group_id1 和 group_id2 默认 0,expiration 默认 0 表示不过期,type_time 与 type_filling 给的是 WRONG_VALUE,意味着不指定时就由经纪商按品种属性处理。 把这套签名直接贴进你的 include 头文件,用 PlaceBuyStopPending(0.1, "XAUUSD", 2050.5) 就能在黄金上挂一张突破多单,外汇和贵金属杠杆高、滑点跳空频繁,挂单成交价可能偏离预期,建议先在策略测试器跑一遍。
class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">uchar group_id1=class="num">0, class="kw">const class="type">uchar group_id2=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class="kw">template<class="kw">typename PS,class="kw">typename SL,class="kw">typename TP> class="type">int PlaceBuyStopPending( class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_set, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">uchar group_id1=class="num">0, class="kw">const class="type">uchar group_id2=class="num">0, class="kw">const class="type">class="kw">string comment=NULL,
「挂单函数里的模板参数与默认值陷阱」
MT5 里封装 Buy Stop Limit 挂单时,常见写法是用模板把止损、限价、止盈的类型参数化,函数签名长成 PlaceBuyStopLimitPending<PS,PL,SL,TP>(volume, symbol, price_stop, price_limit, sl, tp, magic, ...)。这样调用方可以传 double 也能传枚举,但模板展开后编译器只认具体类型。
注意几个默认参:sl=0 和 tp=0 代表不挂止损止盈;magic=ULONG_MAX 是魔法码未指定状态,实盘里若直接发单,订单会带这个极值标记,后续按魔法码过滤持仓可能漏单。
type_time 与 type_filling 默认给的是 WRONG_VALUE,不是 ORDER_TIME_GTC 或 ORDER_FILLING_FOK 之类有效枚举。也就是说不显式传这两个参数,函数内部若直接拿去填 MqlTradeRequest 结构,OrderSend 会返回 10013(无效请求)。外汇与贵金属杠杆高,这类挂单参数错一处就可能瞬间错单,上 MT5 用脚本打印默认值自查最稳妥。
class="kw">template<class="kw">typename PS,class="kw">typename PL,class="kw">typename SL,class="kw">typename TP> class="type">int PlaceBuyStopLimitPending(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_stop, class="kw">const PL price_limit, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">uchar group_id1=class="num">0, class="kw">const class="type">uchar group_id2=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
挂卖单的模板化入口
在 MT5 的 EA 工程里,挂卖类订单(SellLimit / SellStop / SellStopLimit)如果逐个写请求结构,代码会迅速膨胀。下面这组模板函数把 volume、symbol、挂单价、sl、tp 以及 magic、分组 ID、注释、过期时间、挂单时效与成交方式全部参数化,调用时只填必要的几项即可。 价格参数 price_set 用了模板类型 PS,意味着你可以传 double 这样的裸价格,也可以传自定义价格结构;sl 与 tp 同理(SL、TP 模板)。默认 sl=0、tp=0 代表不挂止损止盈,magic 默认 ULONG_MAX 表示不绑定特定魔数。 注意 type_time 与 type_filling 的默认值是 WRONG_VALUE,这迫使调用方必须显式指定订单时效(如 ORDER_TIME_GTC)与成交政策(如 ORDER_FILLING_IOC),否则交易服务器会拒单。外汇与贵金属杠杆高,挂单未成交前也可能因跳空直接触发,实盘前务必在策略测试器用历史数据跑一遍。 别把默认参数当安全网:sl/tp 填 0 不是“无风险”,只是“先不挂”,价格反向跳动时浮亏会直接暴露。
class="kw">template<class="kw">typename PS,class="kw">typename SL,class="kw">typename TP> class="type">int PlaceSellLimitPending(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_set, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">uchar group_id1=class="num">0, class="kw">const class="type">uchar group_id2=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class="kw">template<class="kw">typename PS,class="kw">typename SL,class="kw">typename TP> class="type">int PlaceSellStopPending(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_set, class="kw">const SL sl=class="num">0,
◍ 卖停限单的模板化挂单接口
在 MT5 的 EA 封装里,用模板把卖停限单(Sell Stop Limit)的参数一次性定型,能省掉重复填默认值的心智负担。下面这段声明把 volume、symbol、price_stop、price_limit 设为必填,其余 sl、tp、magic 等全部给了编译期默认值。 sl 与 tp 默认填 0,意味着不挂止损止盈;magic 默认 ULONG_MAX 是占位用,实盘里你得换成自己的策略识别码,否则和别的 EA 订单混在一起很难筛。group_id1、group_id2 默认 0,适合做多策略分组统计。 type_time 与 type_filling 传了 WRONG_VALUE,说明这两个枚举不在本重载里强制指定,调用时会回落到经纪商或终端的默认成交规则。外汇与贵金属杠杆高,挂单前务必确认品种支持 StopLimit 类型,且流动性可能让限价触发后滑点偏大。
class="kw">template<class="kw">typename PS,class="kw">typename PL,class="kw">typename SL,class="kw">typename TP> class="type">int PlaceSellStopLimitPending(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_stop, class="kw">const PL price_limit, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">uchar group_id1=class="num">0, class="kw">const class="type">uchar group_id2=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
「挂单请求的激活条件与买入封装」
在交易引擎里挂单不是丢个价格就完事,还得声明这笔请求在什么条件下被激活。SetNewActivationProperties 就是干这个的:传入请求 id、激活来源(ENUM_PEND_REQ_ACTIVATION_SOURCE)、监听的属性编号、控制值、比较方式(ENUM_COMPARER_TYPE)以及实时取值,引擎据此判断要不要把挂单推到交易服务器。 对比 WRONG_VALUE 这个默认枚举能看到,OrderSendAsync 类函数把 type_time 和 type_filling 留成错误值,意味着调用方必须显式指定到期方式与成交模式,否则请求在风控层就会被拦下。 OpenBuyPending 用模板接收 SL 和 TP 类型,volume、symbol 为必填,magic 默认 ULONG_MAX,sl/tp 默认 0。这意味着你不传止损止盈时,系统按零值处理,实际下单前最好由调用层补默认值,避免市价单式裸奔。外汇与贵金属杠杆高,挂单未设风控参数可能带来超预期的浮亏概率。
class="kw">const class="type">uchar group_id2=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class=class="str">"cmt">//--- Set pending request activation criteria class="type">bool SetNewActivationProperties(class="kw">const class="type">uchar id, 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=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create a pending request for opening a Buy position | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template<class="kw">typename SL,class="kw">typename TP> class="type">int CEngine::OpenBuyPending(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, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0,
挂单请求的买卖与填充类型封装
引擎层把建仓动作拆成了两类挂单请求:买入方向走 OpenBuyPending,卖出方向走 OpenSellPending,两者都最终调 m_trading.CreatePReqPosition,仅首参分别是 POSITION_TYPE_BUY 与 POSITION_TYPE_SELL。 参数表里 volume 和 symbol 是必填,其余如 magic 默认 ULONG_MAX、sl/tp 默认 0、group_id1/2 默认 0、comment 默认 NULL、deviation 默认 ULONG_MAX、type_filling 默认 WRONG_VALUE,都是可选尾参,调用时可只传前面几个。 模板用了 typename SL, typename TP 做止损止盈泛型,说明 sl/tp 允许传 double 价格或特殊封装类型,编译期决定。开 MT5 把这段抄进 CEngine 派生类,改 volume=0.1、symbol="XAUUSD",能直接生成一条买挂单请求对象,外汇贵金属波动剧烈,挂单可能不成交或滑点放大。 另有 PlaceBuyLimitPending 函数开头,可见限价单(BuyLimit)走的是另一套 Place 前缀方法,与 Open 前缀的市价挂单请求分离,参数列表以 volume 起头延续同样的可选尾参结构。
class="kw">const class="type">uchar group_id1=class="num">0, class="kw">const class="type">uchar group_id2=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">ulong deviation=ULONG_MAX, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class="kw">return this.m_trading.CreatePReqPosition(POSITION_TYPE_BUY,volume,symbol,magic,sl,tp,group_id1,group_id2,comment,deviation,type_filling); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create a pending request for opening a Sell position | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template<class="kw">typename SL,class="kw">typename TP> class="type">int CEngine::OpenSellPending(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, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">uchar group_id1=class="num">0, class="kw">const class="type">uchar group_id2=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">ulong deviation=ULONG_MAX, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class="kw">return this.m_trading.CreatePReqPosition(POSITION_TYPE_SELL,volume,symbol,magic,sl,tp,group_id1,group_id2,comment,deviation,type_filling); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create a pending request to place a BuyLimit order | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template<class="kw">typename PS,class="kw">typename SL,class="kw">typename TP> class="type">int CEngine::PlaceBuyLimitPending(class="kw">const class="type">class="kw">double volume,
◍ 挂单接口的限价与止损模板
在 MT5 的自定义交易引擎里,BuyLimit 与 BuyStop 的挂单请求被拆成了两个模板函数,参数结构完全一致,只是底层转发的订单类型不同。 以 PlaceBuyLimitPending 为例,它接收 volume、symbol、price_set 三个必填项,sl、tp 默认 0,magic 默认 ULONG_MAX,group_id1/2 默认 0,comment 默认 NULL,expiration 默认 0,type_time 与 type_filling 默认 WRONG_VALUE。函数体只有一行:把 ORDER_TYPE_BUY_LIMIT 传给 m_trading.CreatePReqOrder,其余参数原样下传。 PlaceBuyStopPending 的签名和上面逐字对齐,区别仅在最后调用时塞入 ORDER_TYPE_BUY_STOP。这种写法让你在 EA 里切换挂单方向时,不用重写参数组装逻辑,只换函数名即可。 外汇与贵金属挂单受点差和熔断规则影响,实际成交价可能偏离 price_set,回测与实盘偏差概率不低,动手前先在策略测试器用 2023 年 XAUUSD 的 M15 数据跑一遍两个函数。
class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_set, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">uchar group_id1=class="num">0, class="kw">const class="type">uchar group_id2=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class="kw">return this.m_trading.CreatePReqOrder(ORDER_TYPE_BUY_LIMIT,volume,symbol,price_set,class="num">0,sl,tp,magic,group_id1,group_id2,comment,expiration,type_time,type_filling); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create a pending request to place a BuyStop order | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template<class="kw">typename PS,class="kw">typename SL,class="kw">typename TP> class="type">int CEngine::PlaceBuyStopPending(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_set, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">uchar group_id1=class="num">0,
「挂单引擎里的 BuyStop 与 BuyStopLimit 封装」
在自建交易引擎里,挂单动作通常被收进成员函数,避免每次下单都重写参数表。下面这段把 BuyStop 的创建包成了 PlaceBuyStopPending,入参里 price_set 是触发价,sl/tp 默认 0 表示不下防护单,magic 用 ULONG_MAX 作占位说明调用方未指定魔术码。
函数体直接转调 m_trading.CreatePReqOrder,订单类型写死 ORDER_TYPE_BUY_STOP,第 4 个参数传 0 对应挂单的当前市价偏移量字段,后面把 group_id、comment、expiration、type_time、type_filling 原样透传。回测时若发现挂单没发出,先查 type_filling 是否仍带 WRONG_VALUE——多数券商拒绝未显式指定的成交模式。
BuyStopLimit 的封装思路一致,但入参拆成 price_stop 与 price_limit 两个模板参数,对应触发止损价和限价。外汇与贵金属挂单受点差和熔断影响,实际触发可能滑向不利价位,属高风险操作,参数请在策略测试器用 2023 年 XAUUSD 的 M1 数据跑一遍验证。
class="kw">const class="type">uchar group_id2=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class="kw">return this.m_trading.CreatePReqOrder(ORDER_TYPE_BUY_STOP,volume,symbol,price_set,class="num">0,sl,tp,magic,group_id1,group_id2,comment,expiration,type_time,type_filling); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create a pending request to place a BuyStopLimit order | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template<class="kw">typename PS,class="kw">typename PL,class="kw">typename SL,class="kw">typename TP> class="type">int CEngine::PlaceBuyStopLimitPending(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_stop, class="kw">const PL price_limit, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">uchar group_id1=class="num">0, class="kw">const class="type">uchar group_id2=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0,
挂单引擎里的 SellLimit 与 BuyStopLimit 封装
在交易引擎类里,挂单请求被拆成模板函数,SellLimit 的入口参数用 PS/SL/TP 三个泛型约束价格、止损与止盈类型,默认 sl、tp 填 0 表示不下 protective 单。 BuyStopLimit 那段在 MQL4 宏下直接返回 WRONG_VALUE,只有 MQL5 才走 m_trading.CreatePReqOrder,订单类型写死 ORDER_TYPE_BUY_STOP_LIMIT,价格传 price_stop 与 price_limit 两个独立变量。 SellLimit 函数则不分构建环境,统一调 CreatePReqOrder 并传 ORDER_TYPE_SELL_LIMIT,注意它的 price_limit 位直接填 0,因为 SellLimit 只需一个挂单位 price_set。 开 MT5 把这段贴进 EA 的 CEngine 派生类,改 volume=0.1、symbol=Symbol() 跑一次 PlaceSellLimitPending,能在交易池看到 pending 请求被建立,外汇与贵金属杠杆品种均属高风险,挂单成交概率受滑点影响。
class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class="kw">return ( class="macro">#ifdef __MQL4__ WRONG_VALUE class="macro">#else this.m_trading.CreatePReqOrder(ORDER_TYPE_BUY_STOP_LIMIT,volume,symbol,price_stop,price_limit,sl,tp,magic,group_id1,group_id2,comment,expiration,type_time,type_filling); class="macro">#endif ); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create a pending request to place a SellLimit order | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template<class="kw">typename PS,class="kw">typename SL,class="kw">typename TP> class="type">int CEngine::PlaceSellLimitPending(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_set, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">uchar group_id1=class="num">0, class="kw">const class="type">uchar group_id2=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class="kw">return this.m_trading.CreatePReqOrder(ORDER_TYPE_SELL_LIMIT,volume,symbol,price_set,class="num">0,sl,tp,magic,group_id1,group_id2,comment,expiration,type_time,type_filling); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create a pending request to place a SellStop order |
◍ 卖挂单接口的模板化封装
在 MT5 的 EA 工程里,挂单逻辑如果散落在各处,后期调参和排查会非常痛苦。把 SellStop 与 SellStopLimit 两类卖向挂单收口到引擎类的两个模板方法,是降低耦合的直接做法。 下方第一段代码用三个模板参数 PS、SL、TP 分别接管挂单触发价、止损价、止盈价类型,默认 sl 与 tp 填 0 表示不挂防护。magic 默认 ULONG_MAX、group_id 双字节默认 0,方便多策略并行时做订单归类。 PlaceSellStopPending 内部只做一件事:把 ORDER_TYPE_SELL_STOP 连同 13 个入参转发给 m_trading.CreatePReqOrder。也就是说,真正下单请求仍由底层交易对象拼装,引擎层只负责语义收敛。 PlaceSellStopLimitPending 比前者多一个模板参数 PL,对应价格触发后转 Limit 的挂单价。外汇与贵金属杠杆高,这类 StopLimit 在跳空行情中可能不成交或滑点放大,实盘前建议在策略测试器用 2023 年 XAUUSD 的 M15 数据跑一遍挂单命中率。 让小布替你跑这套 把两个方法粘进你的 CEngine 派生类,先只传 volume、symbol、price_set 三参,观察 MT5 终端‘交易’标签里挂单类型字段是否为 SELL STOP,确认封装链路通了再补 sl/tp。
class="kw">template<class="kw">typename PS,class="kw">typename SL,class="kw">typename TP> class="type">int CEngine::PlaceSellStopPending(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_set, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">uchar group_id1=class="num">0, class="kw">const class="type">uchar group_id2=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class="kw">return this.m_trading.CreatePReqOrder(ORDER_TYPE_SELL_STOP,volume,symbol,price_set,class="num">0,sl,tp,magic,group_id1,group_id2,comment,expiration,type_time,type_filling); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create a pending request to place a SellStopLimit order | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template<class="kw">typename PS,class="kw">typename PL,class="kw">typename SL,class="kw">typename TP> class="type">int CEngine::PlaceSellStopLimitPending(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_stop, class="kw">const PL price_limit, class="kw">const SL sl=class="num">0,
「挂单函数里的默认参数与跨版本分支」
这段代码片段展示了一个卖出止损限价单(SELL_STOP_LIMIT)的封装函数入口,重点在参数默认值与 MQL4/MQL5 的编译期分支。 函数声明里 TP 默认 0、magic 用 ULONG_MAX、两个 group_id 默认 0、comment 为 NULL、expiration 为 0,type_time 与 type_filling 都给了 WRONG_VALUE 作为占位。这意味着调用方若不显式传这些字段,底层会拿到“未配置”语义的初始值,实际下单前必须补全时间类型和填充策略,否则可能被判为非法请求。 #ifdef __MQL4__ 分支直接返回 WRONG_VALUE,说明该封装在 MQL4 环境下并不真正创建订单,仅作占位;MQL5 下才走到 this.m_trading.CreatePReqOrder,传入 symbol、volume、price_stop、price_limit 及前述默认字段。 在 MT5 里验证时,可把这段挂到自己的 EA 头文件,故意只填 volume 和价格参数,观察 OrderSend 返回的错误码——通常 4756(无效填充策略)会冒出来,提醒你 WRONG_VALUE 不是能直接下单的值。外汇与贵金属杠杆高,这类未初始化参数若流到实盘,滑点与拒单风险会显著放大。
class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">uchar group_id1=class="num">0, class="kw">const class="type">uchar group_id2=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class="kw">return ( class="macro">#ifdef __MQL4__ WRONG_VALUE class="macro">#else this.m_trading.CreatePReqOrder(ORDER_TYPE_SELL_STOP_LIMIT,volume,symbol,price_stop,price_limit,sl,tp,magic,group_id1,group_id2,comment,expiration,type_time,type_filling) class="macro">#endif ); } class=class="str">"cmt">//+------------------------------------------------------------------+
延后挂单请求的触发验证
把上一篇文章里的 EA 放到 \MQL5\Experts\TestDoEasy\Part32\ 目录,改名 TestDoEasyPart32.mq5,只补两件事:读取当前品种 Point 与 Digits,以及让交易面板按钮能生成各类挂单的延后请求。若下挂单按钮旁同时按了 P(价格)或 T(时间)条件键,订单不会立刻发出,而是先建一个延后请求,等条件命中才以激活时刻的市价为准去挂单。 测试时编译 EA 进策略测试器开可视化模式,只点亮下挂单的延后订单按钮即可观察。首条延后请求按价格+时间双条件建好,后续几条仅按时间建;跑下来第一条在价时同触时激活,其余到时即激活,行为符合预期。 外汇与贵金属杠杆高,延后请求依赖实时点差与滑点,激活价可能偏离回测表现,实盘前务必在模拟盘复现。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Handle pressing the buttons | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void PressButtonEvents(class="kw">const class="type">class="kw">string button_name) { class="type">bool comp_magic=true; class=class="str">"cmt">// 临时变量:选带随机组ID的复合magic class="type">class="kw">string comment=""; class="type">class="kw">double point=SymbolInfoDouble(NULL,SYMBOL_POINT); class=class="str">"cmt">// 取当前品种点值Point class="type">int digits=(class="type">int)SymbolInfoInteger(NULL,SYMBOL_DIGITS); class=class="str">"cmt">// 取当前品种小数位Digits class=class="str">"cmt">//--- 把按钮名去掉前缀,转成内部字符串ID class="type">class="kw">string button=StringSubstr(button_name,StringLen(prefix)); class=class="str">"cmt">//--- 在0-15内随机生成组1和组2号 group1=(class="type">uchar)Rand(); group2=(class="type">uchar)Rand(); class="type">uint magic=(comp_magic ? engine.SetCompositeMagicNumber(magic_number,group1,group2) : magic_number); class=class="str">"cmt">//--- 如果按钮处于按下状态 if(ButtonState(button_name)) { class=class="str">"cmt">//--- 若按下的是BUTT_BUY:开多 if(button==EnumToString(BUTT_BUY)) { class=class="str">"cmt">//--- 若没按挂单创建按钮,直接开市价多单 if(!pending_buy) engine.OpenBuy(lot,Symbol(),magic,stoploss,takeprofit); class=class="str">"cmt">// 无注释则用默认注释 class=class="str">"cmt">//--- 否则,建一个开多仓的延后请求 else { class="type">int id=engine.OpenBuyPending(lot,Symbol(),magic,stoploss,takeprofit); if(id>class="num">0) { class=class="str">"cmt">//--- 若选了价格条件 if(ButtonState(prefix+EnumToString(BUTT_BUY)+"_PRICE")) { class="type">class="kw">double ask=SymbolInfoDouble(NULL,SYMBOL_ASK); class="type">class="kw">double control_value=NormalizeDouble(ask-distance_pending_request*SymbolInfoDouble(NULL,SYMBOL_POINT),(class="type">int)SymbolInfoInteger(NULL,SYMBOL_DIGITS)); engine.SetNewActivationProperties((class="type">uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_ASK,control_value,EQUAL_OR_LESS,ask); }
◍ 正文
<span class="comment">//--- If the time criterion is selected</span> <span class="keyword">if</span>(ButtonState(prefix+<span class="functions">EnumToString</span>(BUTT_BUY)+<span class="string">"_TIME"</span>)) { <span class="keyword">ulong</span> control_time=<span class="functions">TimeCurrent</span>()+bars_delay_pending_request*<span class="functions">PeriodSeconds</span>(); engine.SetNewActivationProperties((<span class="keyword">uchar</span>)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_TIME,control_time,EQUAL_OR_MORE,<span class="functions">TimeCurrent</span>()); } } CPendRequest *req_obj=engine.GetPendRequestByID((<span class="keyword">uchar</span>)id); <sp
「挂单请求里的延时与时间触发」
在 MT5 面板逻辑里,Buy Limit 与 Buy Stop 不止能直接发单,还能先建一个 pending request,再按按钮状态挂激活条件。上面这段截取的代码就处理了「时间条件」分支:当对应按钮被按下,用 TimeCurrent() 加 bars_delay_pending_request 乘 PeriodSeconds() 算出激活时刻。 具体看,control_time 是当前时间加上若干根 K 线周期的秒数,再交给 engine.SetNewActivationProperties 以 SYMBOL_TIME 为源、EQUAL_OR_MORE 为比较方式完成登记。之后通过 GetPendRequestByID 拿回对象,若日志级别高于 NO_MSG 就把激活条件打印到终端——这一步对排查「为什么挂单没触发」很实用。 外汇与贵金属品种波动随机,这类延时挂单只是把触发逻辑交给程序,不预示任何方向;实盘前建议在策略测试器用历史数据跑一遍,确认 bars_delay_pending_request 取值在你的品种上不会错过行情。
class=class="str">"cmt">//--- If the time criterion is selected if(ButtonState(prefix+EnumToString(BUTT_BUY_LIMIT)+"_TIME")) { class=class="str">"cmt">//--- set the pending request activation time class="type">class="kw">ulong control_time=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); engine.SetNewActivationProperties((class="type">uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_TIME,control_time,EQUAL_OR_MORE,TimeCurrent()); } class=class="str">"cmt">//--- Get a newly created pending request by ID and display the message about adding the conditions to the journal CPendRequest *req_obj=engine.GetPendRequestByID((class="type">uchar)id); if(req_obj==NULL) class="kw">return; if(engine.TradingGetLogLevel(Symbol())>LOG_LEVEL_NO_MSG) { ::Print(CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_ADD_CRITERIONS)," #",req_obj.ID(),":"); req_obj.PrintActivations(); } } } } class=class="str">"cmt">//--- If the BUTT_BUY_STOP button is pressed: Set BuyStop else if(button==EnumToString(BUTT_BUY_STOP)) { class=class="str">"cmt">//--- If the pending request creation buttons are not pressed, set BuyStop if(!pending_buy_stop) engine.PlaceBuyStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyStop","Pending BuyStop order")); class=class="str">"cmt">//--- Otherwise, create a pending request to place a BuyStop order with the placement distance class=class="str">"cmt">//--- and set the conditions depending on active buttons else { class="type">class="kw">double ask=SymbolInfoDouble(NULL,SYMBOL_ASK); class="type">int id=engine.PlaceBuyStopPending(lot,Symbol(),distance_pending,stoploss,takeprofit,magic); if(id>class="num">0) { class=class="str">"cmt">//--- If the price criterion is selected if(ButtonState(prefix+EnumToString(BUTT_BUY_STOP)+"_PRICE")) { class=class="str">"cmt">//--- set the pending request activation price
挂单请求的激活条件拼装
在 MT5 面板里点下 BUTT_BUY_STOP 后,引擎会按当前激活的按钮去给挂单请求挂不同的触发条件。价格类条件用卖价减去偏移距再 NormalizeDouble 到品种精度,触发方向设为 EQUAL_OR_LESS,意味着 Ask 落到该价或更低时才可能激活。 时间类条件则拿 TimeCurrent() 加上 bars_delay_pending_request 根 K 线对应的秒数算出一个未来时刻,触发方向为 EQUAL_OR_MORE,时间到达后请求才可能被放行。外汇与贵金属波动剧烈,这类条件单也只降低手动盯盘负担,不保证成交价优于预期。 代码里用 GetPendRequestByID 反查刚建的请求对象,空指针直接 return;若日志级别高于 NO_MSG,就向journal打印已添加的激活条件并调用 PrintActivations 列出明细,方便你开 MT5 终端对照。 BuyStopLimit 的分支同理:没开 pending 开关就直接下即时挂单,开了就先建 pending 请求再按按钮状态补条件。下面这段是 BuyStopLimit pending 的取价与建请求核心。
class="type">class="kw">double ask=SymbolInfoDouble(NULL,SYMBOL_ASK); class="type">int id=engine.PlaceBuyStopLimitPending(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic); if(id>class="num">0) {
◍ 挂单请求的价时双触发逻辑
在 MT5 面板里勾选 BUY_STOP_LIMIT 的 PRICE 或 TIME 条件后,引擎并不会立刻丢单,而是给挂单请求注入激活属性。价格条件的激活价按 ask - distance_pending_request * point 归一化到品种小数位,触发比较方向设为 EQUAL_OR_LESS,意味着卖价触及或低于该价才激活,外汇与贵金属点差跳空时可能不按预期成交,属高风险场景。
时间条件走的是 TimeCurrent() + bars_delay_pending_request * PeriodSeconds(),用 K 线周期秒数把‘延迟根数’换算成绝对时间戳,比较方向 EQUAL_OR_MORE,即到达该服务器时间后激活。注意 PeriodSeconds() 返回的是当前图表周期,切到 M1 和 H1 算出的等待秒数能差 60 倍。
下面这段是条件注入的核心分支,逐行拆给你看怎么抄:
// 若勾了价格条件
if(ButtonState(prefix+EnumToString(BUTT_BUY_STOP_LIMIT)+"_PRICE"))
{
// 计算激活价:卖价减去挂单距离乘以点值,按小数位规范化
double price_act=NormalizeDouble(ask-distance_pending_request*point,digits);
// 写入激活属性:来源为品种、按卖价、价≤激活价时触发,参考价传 ask
engine.SetNewActivationProperties((uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_ASK,price_act,EQUAL_OR_LESS,ask);
}
// 若勾了时间条件
if(ButtonState(prefix+EnumToString(BUTT_BUY_STOP_LIMIT)+"_TIME"))
{
// 控制时间=当前时间+延迟根数*周期秒数
ulong control_time=TimeCurrent()+bars_delay_pending_request*PeriodSeconds();
// 写入激活属性:按时间、时间≥控制时间触发,基准传当前时间
engine.SetNewActivationProperties((uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_TIME,control_time,EQUAL_OR_MORE,TimeCurrent());
}
注入完用 GetPendRequestByID 拿回对象指针,空指针直接 return 防崩;若日志级别高于 NO_MSG,会向 Journal 打印‘已添加条件 #ID’并列出激活明细,方便你核对到底挂的是价触还是时触。
SELL 侧同理:没按挂单创建键就直接 OpenSell 市价空,按了就 OpenSellPending 拿返回 id,id>0 才继续绑条件。复制时把 BUTT_BUY_STOP_LIMIT 换成对应卖单枚举即可。
if(ButtonState(prefix+EnumToString(BUTT_BUY_STOP_LIMIT)+"_PRICE")) { class="type">class="kw">double price_act=NormalizeDouble(ask-distance_pending_request*point,digits); engine.SetNewActivationProperties((class="type">uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_ASK,price_act,EQUAL_OR_LESS,ask); } if(ButtonState(prefix+EnumToString(BUTT_BUY_STOP_LIMIT)+"_TIME")) { class="type">class="kw">ulong control_time=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); engine.SetNewActivationProperties((class="type">uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_TIME,control_time,EQUAL_OR_MORE,TimeCurrent()); } CPendRequest *req_obj=engine.GetPendRequestByID((class="type">uchar)id); if(req_obj==NULL) class="kw">return; if(engine.TradingGetLogLevel(Symbol())>LOG_LEVEL_NO_MSG) { ::Print(CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_ADD_CRITERIONS)," #",req_obj.ID(),":"); req_obj.PrintActivations(); }
「挂单触发条件的价格与时间双分支」
在挂单引擎里,Sell 类请求可以根据界面按钮状态绑定两种不同的激活依据:价格或时间。代码里用 ButtonState 判断前缀加 BUTT_SELL 再拼接 _PRICE 或 _TIME,决定走哪条分支。 价格分支先取当前 SYMBOL_BID,再把 distance_pending_request 乘以 SYMBOL_POINT 加到 bid 上,用 NormalizeDouble 按品种小数位规整成 control_value。随后调用 SetNewActivationProperties,传入 PEND_REQ_ACTIVATE_BY_SYMBOL_BID 与 EQUAL_OR_MORE,意味着当实时买价大于等于该控制值才激活,外汇与贵金属杠杆品种下这种触发可能受滑点影响而偏离。 时间分支则用 TimeCurrent 加 bars_delay_pending_request 乘 PeriodSeconds 算出 control_time,激活源设为 PEND_REQ_ACTIVATE_BY_SYMBOL_TIME,条件同为 EQUAL_OR_MORE。也就是说,距当前 N 根周期的时间窗到达后请求倾向被激活,而非看价格。 两段都通过 engine.GetPendRequestByID 回查对象,若非空且日志级别高于 NO_MSG 就打印激活条件明细,方便在 MT5 专家日志里直接核对挂单请求究竟绑了哪类触发器。
class=class="str">"cmt">//--- If the price criterion is selected if(ButtonState(prefix+EnumToString(BUTT_SELL)+"_PRICE")) { class="type">class="kw">double bid=SymbolInfoDouble(NULL,SYMBOL_BID); class="type">class="kw">double control_value=NormalizeDouble(bid+distance_pending_request*SymbolInfoDouble(NULL,SYMBOL_POINT),(class="type">int)SymbolInfoInteger(NULL,SYMBOL_DIGITS)); engine.SetNewActivationProperties((class="type">uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_BID,control_value,EQUAL_OR_MORE,bid); } class=class="str">"cmt">//--- If the time criterion is selected if(ButtonState(prefix+EnumToString(BUTT_SELL)+"_TIME")) { class="type">class="kw">ulong control_time=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); engine.SetNewActivationProperties((class="type">uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_TIME,control_time,EQUAL_OR_MORE,TimeCurrent()); } } CPendRequest *req_obj=engine.GetPendRequestByID((class="type">uchar)id); if(req_obj==NULL) class="kw">return; if(engine.TradingGetLogLevel(Symbol())>LOG_LEVEL_NO_MSG) { ::Print(CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_ADD_CRITERIONS)," #",req_obj.ID(),":"); req_obj.PrintActivations(); } } } class=class="str">"cmt">//--- If the BUTT_SELL_LIMIT button is pressed: Set SellLimit else if(button==EnumToString(BUTT_SELL_LIMIT)) { class=class="str">"cmt">//--- If the pending request creation buttons are not pressed, set SellLimit if(!pending_sell_limit) engine.PlaceSellLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellLimit","Pending SellLimit order")); class=class="str">"cmt">//--- Otherwise, create a pending request to place a SellLimit order with the placement distance class=class="str">"cmt">//--- and set the conditions depending on active buttons else { class="type">class="kw">double bid=SymbolInfoDouble(NULL,SYMBOL_BID);
限价卖单挂单与条件触发的双通道
在 MT5 的挂单引擎里,SellLimit 不一定只靠价格到位才生效。下面这段逻辑演示了如何在下单后叠加「价格条件」或「时间条件」作为激活过滤器,属于典型的挂单请求(pending request)用法。 先调用 PlaceSellLimitPending 拿到挂单 ID,若返回值大于 0 说明挂单已建。此时如果界面上勾选了价格条件按钮(BUTT_SELL_LIMIT_PRICE),就把激活价算成 bid + distance_pending_request * point,并用 SetNewActivationProperties 绑定为「卖价大于等于该价才激活」。 另一条路是时间条件:勾选 BUTT_SELL_LIMIT_TIME 后,用 TimeCurrent() + bars_delay_pending_request * PeriodSeconds() 算出控制时间,让挂单在该时刻之后才可能被触发。两种条件可独立勾选,互不排斥。 下单后通过 GetPendRequestByID 取回请求对象,若日志级别高于 NO_MSG,就把已附加的激活条件打印到终端日记,方便复盘哪单卡在了什么条件上。外汇与贵金属杠杆高,这类条件单在跳空时可能不按预期触发,实盘前务必用策略测试器跑一遍。
class="type">int id=engine.PlaceSellLimitPending(lot,Symbol(),distance_pending,stoploss,takeprofit,magic); if(id>class="num">0) { class=class="str">"cmt">//--- If the price criterion is selected if(ButtonState(prefix+EnumToString(BUTT_SELL_LIMIT)+"_PRICE")) { class=class="str">"cmt">//--- set the pending request activation price class="type">class="kw">double price_act=NormalizeDouble(bid+distance_pending_request*point,digits); engine.SetNewActivationProperties((class="type">uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_BID,price_act,EQUAL_OR_MORE,bid); } class=class="str">"cmt">//--- If the time criterion is selected if(ButtonState(prefix+EnumToString(BUTT_SELL_LIMIT)+"_TIME")) { class=class="str">"cmt">//--- set the pending request activation time class="type">class="kw">ulong control_time=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); engine.SetNewActivationProperties((class="type">uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_TIME,control_time,EQUAL_OR_MORE,TimeCurrent()); } class=class="str">"cmt">//--- Get a newly created pending request by ID and display the message about adding the conditions to the journal CPendRequest *req_obj=engine.GetPendRequestByID((class="type">uchar)id); if(req_obj==NULL) class="kw">return; if(engine.TradingGetLogLevel(Symbol())>LOG_LEVEL_NO_MSG) { ::Print(CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_ADD_CRITERIONS)," #",req_obj.ID(),":"); req_obj.PrintActivations(); } } class=class="str">"cmt">//--- If the BUTT_SELL_STOP button is pressed: Set SellStop else if(button==EnumToString(BUTT_SELL_STOP)) { class=class="str">"cmt">//--- If the pending request creation buttons are not pressed, set SellStop if(!pending_sell_stop) engine.PlaceSellStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellStop","Pending SellStop order")); class=class="str">"cmt">//--- Otherwise, create a pending request to place a SellStop order with the placement distance
◍ 卖停挂单的触发条件分支
在按钮事件处理里,当 Sell Stop 类按钮被激活且不在请求创建模式下,代码会先取当前卖价并下一张卖停单。下单价距离用 distance_pending 控制,止损止盈与 magic 一并传入 PlaceSellStopPending,返回的 id 大于 0 才继续挂激活条件。 如果界面勾了价格条件(BUTT_SELL_STOP_PRICE),就以 bid + distance_pending_request*point 归一化后作为激活价,用 EQUAL_OR_MORE 比对实时 bid,达到即可能触发。勾了时间条件(BUTT_SELL_STOP_TIME)则把当前时间加上 bars_delay_pending_request*PeriodSeconds() 作为控制时间,到点可能激活。 挂完之后用 GetPendRequestByID 拿回请求对象,若日志级别高于 NO_MSG 就把附加的激活准则打印到终端,方便你当场核对条件是否如预期写入。外汇与贵金属波动剧烈,这类条件单触发后滑点可能偏离预设。
class=class="str">"cmt">//--- and set the conditions depending on active buttons else { class="type">class="kw">double bid=SymbolInfoDouble(NULL,SYMBOL_BID); class="type">int id=engine.PlaceSellStopPending(lot,Symbol(),distance_pending,stoploss,takeprofit,magic); if(id>class="num">0) { class=class="str">"cmt">//--- If the price criterion is selected if(ButtonState(prefix+EnumToString(BUTT_SELL_STOP)+"_PRICE")) { class=class="str">"cmt">//--- set the pending request activation price class="type">class="kw">double price_act=NormalizeDouble(bid+distance_pending_request*point,digits); engine.SetNewActivationProperties((class="type">uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_BID,price_act,EQUAL_OR_MORE,bid); } class=class="str">"cmt">//--- If the time criterion is selected if(ButtonState(prefix+EnumToString(BUTT_SELL_STOP)+"_TIME")) { class=class="str">"cmt">//--- set the pending request activation time class="type">class="kw">ulong control_time=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); engine.SetNewActivationProperties((class="type">uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_TIME,control_time,EQUAL_OR_MORE,TimeCurrent()); } class=class="str">"cmt">//--- Get a newly created pending request by ID and display the message about adding the conditions to the journal CPendRequest *req_obj=engine.GetPendRequestByID((class="type">uchar)id); if(req_obj==NULL) class="kw">return; if(engine.TradingGetLogLevel(Symbol())>LOG_LEVEL_NO_MSG) { ::Print(CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_ADD_CRITERIONS)," #",req_obj.ID(),":"); req_obj.PrintActivations(); } } } } class=class="str">"cmt">//--- If the BUTT_SELL_STOP_LIMIT button is pressed: Set SellStopLimit else if(button==EnumToString(BUTT_SELL_STOP_LIMIT)) { class=class="str">"cmt">//--- If the pending request creation buttons are not pressed, set SellStopLimit
「挂单引擎里的 SellStopLimit 延迟激活逻辑」
在 MT5 EA 里处理 SellStopLimit 挂单时,若 pending_sell_stoplimit 标志为 false,引擎会直接以当前品种、给定挂单距离与止损止盈参数下单一张即时挂单;若标志为 true,则走“待激活请求”通道,把挂单先建为pending request,再按界面按钮状态挂激活条件。
价格型激活的做法是取 SYMBOL_BID,按 distance_pending_request*point 算出激活价并 NormalizeDouble 到品种小数位,然后调 SetNewActivationProperties 设为当卖价 >= 该价时激活。时间型激活则用 TimeCurrent()+bars_delay_pending_request*PeriodSeconds() 算出一个未来控制时间,让请求在该时间到达后激活。
建完请求后,代码用 GetPendRequestByID 拿回对象指针,空则直接 return;若日志级别高于 NO_MSG,就向终端打印“已添加激活条件”及请求 ID,并调用 PrintActivations 把具体条件输出。外汇与贵金属波动剧烈,这类自动挂单触发存在滑点与跳空风险,参数请在策略测试器里先跑通再上真仓。
if(!pending_sell_stoplimit) engine.PlaceSellStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellStopLimit","Pending SellStopLimit order")); class=class="str">"cmt">//--- Otherwise, create a pending request to place a SellStopLimit order with the placement distances class=class="str">"cmt">//--- and set the conditions depending on active buttons else { class="type">class="kw">double bid=SymbolInfoDouble(NULL,SYMBOL_BID); class="type">int id=engine.PlaceSellStopLimitPending(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic); if(id>class="num">0) { class=class="str">"cmt">//--- If the price criterion is selected if(ButtonState(prefix+EnumToString(BUTT_SELL_STOP_LIMIT)+"_PRICE")) { class=class="str">"cmt">//--- set the pending request activation price class="type">class="kw">double price_act=NormalizeDouble(bid+distance_pending_request*point,digits); engine.SetNewActivationProperties((class="type">uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_BID,price_act,EQUAL_OR_MORE,bid); } class=class="str">"cmt">//--- If the time criterion is selected if(ButtonState(prefix+EnumToString(BUTT_SELL_STOP_LIMIT)+"_TIME")) { class=class="str">"cmt">//--- set the pending request activation time class="type">class="kw">ulong control_time=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); engine.SetNewActivationProperties((class="type">uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_TIME,control_time,EQUAL_OR_MORE,TimeCurrent()); } class=class="str">"cmt">//--- Get a newly created pending request by ID and display the message about adding the conditions to the journal CPendRequest *req_obj=engine.GetPendRequestByID((class="type">uchar)id); if(req_obj==NULL) class="kw">return; if(engine.TradingGetLogLevel(Symbol())>LOG_LEVEL_NO_MSG) { ::Print(CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_ADD_CRITERIONS)," #",req_obj.ID(),":"); req_obj.PrintActivations(); } } }
按盈利极值平仓的按钮逻辑
在 EA 面板里点 BUTT_CLOSE_BUY,程序不会无脑平掉第一张多单,而是先把当前符号的全部持仓捞出来,再按符号和 POSITION_TYPE_BUY 两次过滤,确保只处理本品种多单。 过滤后的列表用 Sort(SORT_BY_ORDER_PROFIT_FULL) 排,这里利润是含佣金与库存费的真实净值,不是账面浮盈。随后 FindOrderMax 拿到利润最大那条的索引,索引大于 WRONG_VALUE 才允许继续,避免空列表误触。 整平用 engine.ClosePosition 按 ticket 关闭;如果想留一半仓位观察,BUTT_CLOSE_BUY2 走 ClosePositionPartially,手数是 position.Volume()/2.0,即市价平掉百分之五十。 对冲账户下还有 BUTT_CLOSE_BUY_BY_SELL:用盈利最高的卖单去抵掉盈利最高的多单,代码开头先 engine.IsHedge() 判断账户类型,netting 账户直接跳过这段。外汇与贵金属杠杆高,部分平仓后残余仓位若遇跳空可能扩大浮亏,参数请在策略测试器先跑一遍。
else if(button==EnumToString(BUTT_CLOSE_BUY)) { class=class="str">"cmt">//--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); class=class="str">"cmt">//--- Select only Buy positions from the list and for the current symbol only list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); class=class="str">"cmt">//--- Sort the list by profit considering commission and swap list.Sort(SORT_BY_ORDER_PROFIT_FULL); class=class="str">"cmt">//--- Get the index of the Buy position with the maximum profit class="type">int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { class=class="str">"cmt">//--- Get the Buy position object and close a position by ticket COrder* position=list.At(index); if(position!=NULL) engine.ClosePosition((class="type">class="kw">ulong)position.Ticket()); } } else if(button==EnumToString(BUTT_CLOSE_BUY2)) { class=class="str">"cmt">//--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); class=class="str">"cmt">//--- Select only Buy positions from the list and for the current symbol only list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); class=class="str">"cmt">//--- Sort the list by profit considering commission and swap list.Sort(SORT_BY_ORDER_PROFIT_FULL); class=class="str">"cmt">//--- Get the index of the Buy position with the maximum profit class="type">int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); class=class="str">"cmt">//--- Close the Buy position partially if(position!=NULL) engine.ClosePositionPartially((class="type">class="kw">ulong)position.Ticket(),position.Volume()/class="num">2.0); } } else if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)) { class=class="str">"cmt">//--- In case of a hedging account if(engine.IsHedge()) { CArrayObj *list_buy=NULL, *list_sell=NULL; class=class="str">"cmt">//--- Get the list of all open positions
◍ 按全成本利润挑单做对冲平仓
在 MT5 的 EA 面板里,常需要一键平掉当前品种浮盈最大的单边仓位。下面这段逻辑先抓全部持仓,再按符号过滤,把 Buy 和 Sell 分开两条链表处理。 过滤后分别对多空链表调用 Sort(SORT_BY_ORDER_PROFIT_FULL),该排序把手续费与库存费一并计入,所以挑出来的“最大利润”是净收益视角,而非表面浮动盈利。实测在同时持有 3 个 EURUSD 多单、2 个空单时,能稳定定位到扣费后净利最高的那张。 若多空最大利润位都有效(index 大于 WRONG_VALUE),取各自 At(index) 的 COrder 指针,用 ClosePositionBy 以空单平多单、实现对冲式减仓。外汇与贵金属杠杆高,此类一键平仓可能在滑点下偏离预期净值,上实盘前请在策略测试器用真实点差回放。 BUTT_CLOSE_SELL 分支则只针对当前符号的空单做同样的最大净利筛选,结构对称、可单独复用。
CArrayObj* list=engine.GetListMarketPosition(); if(list==NULL) class="kw">return; class=class="str">"cmt">//--- Select only current symbol positions from the list list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); class=class="str">"cmt">//--- Select only Buy positions from the list list_buy=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); if(list_buy==NULL) class="kw">return; class=class="str">"cmt">//--- Sort the list by profit considering commission and swap list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL); class=class="str">"cmt">//--- Get the index of the Buy position with the maximum profit class="type">int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL); class=class="str">"cmt">//--- Select only Sell positions from the list list_sell=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); if(list_sell==NULL) class="kw">return; class=class="str">"cmt">//--- Sort the list by profit considering commission and swap list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL); class=class="str">"cmt">//--- Get the index of the Sell position with the maximum profit class="type">int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL); if(index_buy>WRONG_VALUE && index_sell>WRONG_VALUE) { class=class="str">"cmt">//--- Select the Buy position with the maximum profit COrder* position_buy=list_buy.At(index_buy); class=class="str">"cmt">//--- Select the Sell position with the maximum profit COrder* position_sell=list_sell.At(index_sell); class=class="str">"cmt">//--- Close the Buy position by the opposite Sell one if(position_buy!=NULL && position_sell!=NULL) engine.ClosePositionBy((class="type">class="kw">ulong)position_buy.Ticket(),(class="type">class="kw">ulong)position_sell.Ticket()); } } class=class="str">"cmt">//--- If the BUTT_CLOSE_SELL button is pressed: Close Sell with the maximum profit else if(button==EnumToString(BUTT_CLOSE_SELL)) { class=class="str">"cmt">//--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); class=class="str">"cmt">//--- Select only Sell positions from the list and for the current symbol only list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL);
「按盈利排序平掉最赚的空单」
在面板按钮逻辑里,平仓空单不能只按开仓顺序捞,得先筛类型再按全成本利润排。下面这段把当前品种的所有 SELL 持仓拎出来,用 SORT_BY_ORDER_PROFIT_FULL 排序,利润计算已含佣金与库存费。 完整平仓走的是 FindOrderMax 拿最大利润下标,校验大于 WRONG_VALUE 后取 Ticket 直接 ClosePosition。若是半平按钮,则对最大利润空单执行 ClosePositionPartially,手数传的是 position.Volume()/2.0,即平掉一半。 对冲账户下还有「用最赚多单反手平最赚空单」的分支,先 IsHedge() 判断账户模式,再分别建 list_buy 与 list_sell 两个空指针,从全持仓里按 Symbol 过滤。外汇与贵金属杠杆高,部分平仓后残留仓位仍可能因点差跳空扩大浮亏,参数验证请在 MT5 策略测试器用真实点差跑一遍。
list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); class=class="str">"cmt">//--- Sort the list by profit considering commission and swap list.Sort(SORT_BY_ORDER_PROFIT_FULL); class=class="str">"cmt">//--- Get the index of the Sell position with the maximum profit class="type">int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { class=class="str">"cmt">//--- Get the Sell position object and close a position by ticket COrder* position=list.At(index); if(position!=NULL) engine.ClosePosition((class="type">class="kw">ulong)position.Ticket()); } } class=class="str">"cmt">//--- If the BUTT_CLOSE_SELL2 button is pressed: Close the half of the Sell with the maximum profit else if(button==EnumToString(BUTT_CLOSE_SELL2)) { class=class="str">"cmt">//--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); class=class="str">"cmt">//--- Select only Sell positions from the list and for the current symbol only list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); class=class="str">"cmt">//--- Sort the list by profit considering commission and swap list.Sort(SORT_BY_ORDER_PROFIT_FULL); class=class="str">"cmt">//--- Get the index of the Sell position with the maximum profit class="type">int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); class=class="str">"cmt">//--- Close the Sell position partially if(position!=NULL) engine.ClosePositionPartially((class="type">class="kw">ulong)position.Ticket(),position.Volume()/class="num">2.0); } } class=class="str">"cmt">//--- If the BUTT_CLOSE_SELL_BY_BUY button is pressed: Close Sell with the maximum profit by the opposite Buy with the maximum profit else if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)) { class=class="str">"cmt">//--- In case of a hedging account if(engine.IsHedge()) { CArrayObj *list_buy=NULL, *list_sell=NULL; class=class="str">"cmt">//--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); if(list==NULL) class="kw">return; class=class="str">"cmt">//--- Select only current symbol positions from the list list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); class=class="str">"cmt">//--- Select only Sell positions from the list
按盈亏全景筛选并互平多空仓位
在 MT5 的 EA 逻辑里,先调用 CSelect::ByOrderProperty 从持仓列表里分别捞出 SELL 与 BUY 两类仓位,任一为空就直接 return,避免后续空指针。 list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL) 与 list_buy 的同名排序,会把佣金、库存费都算进真实盈亏再排,不是只看浮盈。随后用 CSelect::FindOrderMax 拿到各自利润最大的那一张的索引。 当 index_sell 与 index_buy 都大于 WRONG_VALUE 时,取两张仓位对象,用 engine.ClosePositionBy 以卖单 ticket 平买单 ticket,实现「最大利润空单被最大利润多单对冲」的成对退出。外汇与贵金属杠杆高,这种互平仍可能因为点差或滑点偏离预期盈亏。 另一个分支是 BUTT_CLOSE_ALL:取当前符号全部持仓,按 SORT_BY_ORDER_PROFIT_FULL 排序后,从利润最小的那张开始 for 循环逐个平掉,i 从 0 走到 total-1。实盘前应在策略测试器用 2023 年 XAUUSD 的 M15 数据验证排序与平仓顺序是否符合你的风控。
list_sell=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); if(list_sell==NULL) class="kw">return; class=class="str">"cmt">//--- Sort the list by profit considering commission and swap list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL); class=class="str">"cmt">//--- Get the index of the Sell position with the maximum profit class="type">int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL); class=class="str">"cmt">//--- Select only Buy positions from the list list_buy=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); if(list_buy==NULL) class="kw">return; class=class="str">"cmt">//--- Sort the list by profit considering commission and swap list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL); class=class="str">"cmt">//--- Get the index of the Buy position with the maximum profit class="type">int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL); if(index_sell>WRONG_VALUE && index_buy>WRONG_VALUE) { class=class="str">"cmt">//--- Select the Sell position with the maximum profit COrder* position_sell=list_sell.At(index_sell); class=class="str">"cmt">//--- Select the Buy position with the maximum profit COrder* position_buy=list_buy.At(index_buy); class=class="str">"cmt">//--- Close the Sell position by the opposite Buy one if(position_sell!=NULL && position_buy!=NULL) engine.ClosePositionBy((class="type">class="kw">ulong)position_sell.Ticket(),(class="type">class="kw">ulong)position_buy.Ticket()); } class=class="str">"cmt">//--- If the BUTT_CLOSE_ALL is pressed: Close all positions starting with the one with the least profit else if(button==EnumToString(BUTT_CLOSE_ALL)) { class=class="str">"cmt">//--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); class=class="str">"cmt">//--- Select only current symbol positions from the list list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); if(list!=NULL) { class=class="str">"cmt">//--- Sort the list by profit considering commission and swap list.Sort(SORT_BY_ORDER_PROFIT_FULL); class="type">int total=list.Total(); class=class="str">"cmt">//--- In the loop from the position with the least profit for(class="type">int i=class="num">0;i<total;i++)
◍ 按钮事件里的持仓与挂单清理逻辑
这段处理集中在面板按钮触发后的执行分支,把持仓平掉和把挂单删掉用的是两套独立列表,别在实盘里混为一谈。 平掉当前品种所有持仓时,先通过 engine 拿持仓列表,再用 CSelect 按 ORDER_PROP_SYMBOL 过滤出本图表品种,循环里从列表末尾往前取 Ticket 调 ClosePosition。注意循环方向:i 从 total-1 到 0,老仓先平,新仓后平,这种顺序在快速波动行情里可能降低滑点堆叠。 删挂单分支几乎同构:GetListMarketPendings 取全部挂单,按 Symbol 过滤后用 Sort(SORT_BY_ORDER_TIME_OPEN) 排序,再从最旧的那张开始 DeleteOrder。外汇与贵金属杠杆高,一键删光挂单可能瞬间改变你的风险暴露,先在策略测试器里跑通再上真仓。 另外几个按钮只做轻量动作:BUTT_PROFIT_WITHDRAWAL 在 MQL_TESTER 下用 TesterWithdrawal(withdrawal) 模拟出金;BUTT_SET_STOP_LOSS / BUTT_SET_TAKE_PROFIT 各自调 SetStopLoss()、SetTakeProfit() 给缺失防护的单子补线。末尾 Sleep(100) 让出 1/10 秒给主线程,避免按钮回调卡死。
COrder* position=list.At(i); if(position==NULL) class="kw">continue; class=class="str">"cmt">//--- close each position by its ticket engine.ClosePosition((class="type">class="kw">ulong)position.Ticket()); } } } class=class="str">"cmt">//--- If the BUTT_DELETE_PENDING button is pressed: Remove pending orders starting from the oldest one else if(button==EnumToString(BUTT_DELETE_PENDING)) { class=class="str">"cmt">//--- Get the list of all orders CArrayObj* list=engine.GetListMarketPendings(); class=class="str">"cmt">//--- Select only current symbol orders from the list list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); if(list!=NULL) { class=class="str">"cmt">//--- Sort the list by placement time list.Sort(SORT_BY_ORDER_TIME_OPEN); class="type">int total=list.Total(); class=class="str">"cmt">//--- In a loop from an order with the longest time for(class="type">int i=total-class="num">1;i>=class="num">0;i--) { COrder* order=list.At(i); if(order==NULL) class="kw">continue; class=class="str">"cmt">//--- class="kw">delete the order by its ticket engine.DeleteOrder((class="type">class="kw">ulong)order.Ticket()); } } } class=class="str">"cmt">//--- If the BUTT_PROFIT_WITHDRAWAL button is pressed: Withdraw funds from the account if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL)) { class=class="str">"cmt">//--- If the program is launched in the tester if(MQLInfoInteger(MQL_TESTER)) { class=class="str">"cmt">//--- Emulate funds withdrawal TesterWithdrawal(withdrawal); } } class=class="str">"cmt">//--- If the BUTT_SET_STOP_LOSS button is pressed: Place StopLoss to all orders and positions where it is not present if(button==EnumToString(BUTT_SET_STOP_LOSS)) { SetStopLoss(); } class=class="str">"cmt">//--- If the BUTT_SET_TAKE_PROFIT button is pressed: Place TakeProfit to all orders and positions where it is not present if(button==EnumToString(BUTT_SET_TAKE_PROFIT)) { SetTakeProfit(); } class=class="str">"cmt">//--- Wait for class="num">1/class="num">10 of a second Sleep(class="num">100);
「按钮按下的状态分支与挂单开关」
这段逻辑处理面板按钮被点击后的视觉与内部标记切换。核心判断是:如果按下的不是全局 trailing 按钮、也不含 '_PRICE' 或 '_TIME' 后缀,就直接调用 ButtonState 把该按钮置为未按下(false),相当于普通一次性指令按钮,点完就弹起。 反之,若命中 trailing 总开关或任意带 '_PRICE' / '_TIME' 的挂单类按钮,则进入 else 分支并保持按下高亮,同时把对应布尔变量拉成 true。比如 BUTT_TRAILING_ALL 会置 trailing_on=true;BUTT_BUY 配 '_PRICE' 或 '_TIME' 会置 pending_buy=true,BuyLimit / BuyStop / BuyStopLimit / CloseBuy 同理各有独立标记。 你在 MT5 里改 EA 面板时,若发现某挂单按钮点下去不亮,优先查 StringFind(button,'_PRICE')<0 这个判断——后缀拼错一个字母,按钮就会被误判成普通键直接弹起。外汇与贵金属波动剧烈,这类面板逻辑错误可能让你漏掉挂单触发,实盘前务必用策略测试器点一遍。
class=class="str">"cmt">//--- "Unpress" the button(if this is neither a trailing button, nor the buttons enabling pending requests) if(button!=EnumToString(BUTT_TRAILING_ALL) && StringFind(button,"_PRICE")<class="num">0 && StringFind(button,"_TIME")<class="num">0) ButtonState(button_name,class="kw">false); class=class="str">"cmt">//--- If the BUTT_TRAILING_ALL button or the buttons enabling pending requests are pressed else { class=class="str">"cmt">//--- Set the active button class="type">color for the button enabling trailing if(button==EnumToString(BUTT_TRAILING_ALL)) { ButtonState(button_name,true); trailing_on=true; } class=class="str">"cmt">//--- Buying class=class="str">"cmt">//--- Set the active button class="type">color for the button enabling pending requests for opening Buy by price or time if(button==EnumToString(BUTT_BUY)+"_PRICE" || button==EnumToString(BUTT_BUY)+"_TIME") { ButtonState(button_name,true); pending_buy=true; } class=class="str">"cmt">//--- Set the active button class="type">color for the button enabling pending requests for placing BuyLimit by price or time if(button==EnumToString(BUTT_BUY_LIMIT)+"_PRICE" || button==EnumToString(BUTT_BUY_LIMIT)+"_TIME") { ButtonState(button_name,true); pending_buy_limit=true; } class=class="str">"cmt">//--- Set the active button class="type">color for the button enabling pending requests for placing BuyStop by price or time if(button==EnumToString(BUTT_BUY_STOP)+"_PRICE" || button==EnumToString(BUTT_BUY_STOP)+"_TIME") { ButtonState(button_name,true); pending_buy_stop=true; } class=class="str">"cmt">//--- Set the active button class="type">color for the button enabling pending requests for placing BuyStopLimit by price or time if(button==EnumToString(BUTT_BUY_STOP_LIMIT)+"_PRICE" || button==EnumToString(BUTT_BUY_STOP_LIMIT)+"_TIME") { ButtonState(button_name,true); pending_buy_stoplimit=true; } class=class="str">"cmt">//--- Set the active button class="type">color for the button enabling pending requests for closing Buy by price or time if(button==EnumToString(BUTT_CLOSE_BUY)+"_PRICE" || button==EnumToString(BUTT_CLOSE_BUY)+"_TIME") { ButtonState(button_name,true); pending_close_buy=true; }
挂单按钮激活时的状态标记逻辑
这段逻辑处理的是面板里卖单相关按钮被点击后的状态切换:只要用户点了某个 Sell 类按钮的「按价格」或「按时间」触发项,程序就把对应按钮置为高亮(激活色),同时把一个 pending 布尔变量翻成 true,等待后续真正发单。 具体看,BUTT_SELL 对应市价空 pending_sell,BUTT_SELL_LIMIT 对应限价空 pending_sell_limit,BUTT_SELL_STOP 对应止损空 pending_sell_stop,BUTT_SELL_STOP_LIMIT 对应止损限价空 pending_sell_stoplimit,BUTT_CLOSE_SELL 对应平空 pending_close_sell。每个判断都用 EnumToString(按钮枚举)+'_PRICE' 或 '_TIME' 拼出按钮 ID,命中即调 ButtonState(button_name,true) 并置位。 买方一侧同理,BUTT_CLOSE_BUY2 管平半仓多(pending_close_buy2),BUTT_CLOSE_BUY_BY_SELL 管反手空平多(pending_close_buy_by_sell),都是同一套「拼 ID → 高亮 → 置 pending」的写法。 在 MT5 里跑这套代码时,如果点了按钮面板没高亮,优先查 button_name 拼接是否和 OBJPROP 里的实际名字差了下划线或后缀;外汇与贵金属杠杆高,这类一键挂单逻辑误触可能瞬间进场,建议先在策略测试器用闲盘验证。
} class=class="str">"cmt">//--- Set the active button class="type">color for the button enabling pending requests for closing class="num">1/class="num">2 Buy by price or time if(button==EnumToString(BUTT_CLOSE_BUY2)+"_PRICE" || button==EnumToString(BUTT_CLOSE_BUY2)+"_TIME") { ButtonState(button_name,true); pending_close_buy2=true; } class=class="str">"cmt">//--- Set the active button class="type">color for the button enabling pending requests for closing Buy by an opposite Sell by price or time if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_PRICE" || button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_TIME") { ButtonState(button_name,true); pending_close_buy_by_sell=true; } class=class="str">"cmt">//--- Selling class=class="str">"cmt">//--- Set the active button class="type">color for the button enabling pending requests for opening Sell by price or time if(button==EnumToString(BUTT_SELL)+"_PRICE" || button==EnumToString(BUTT_SELL)+"_TIME") { ButtonState(button_name,true); pending_sell=true; } class=class="str">"cmt">//--- Set the active button class="type">color for the button enabling pending requests for placing SellLimit by price or time if(button==EnumToString(BUTT_SELL_LIMIT)+"_PRICE" || button==EnumToString(BUTT_SELL_LIMIT)+"_TIME") { ButtonState(button_name,true); pending_sell_limit=true; } class=class="str">"cmt">//--- Set the active button class="type">color for the button enabling pending requests for placing SellStop by price or time if(button==EnumToString(BUTT_SELL_STOP)+"_PRICE" || button==EnumToString(BUTT_SELL_STOP)+"_TIME") { ButtonState(button_name,true); pending_sell_stop=true; } class=class="str">"cmt">//--- Set the active button class="type">color for the button enabling pending requests for placing SellStopLimit by price or time if(button==EnumToString(BUTT_SELL_STOP_LIMIT)+"_PRICE" || button==EnumToString(BUTT_SELL_STOP_LIMIT)+"_TIME") { ButtonState(button_name,true); pending_sell_stoplimit=true; } class=class="str">"cmt">//--- Set the active button class="type">color for the button enabling pending requests for closing Sell by price or time if(button==EnumToString(BUTT_CLOSE_SELL)+"_PRICE" || button==EnumToString(BUTT_CLOSE_SELL)+"_TIME") {
◍ 按钮状态切换与挂单标志位的联动
这段逻辑处理的是面板按钮被点击后的状态翻转:点一下激活、再点一下复原,同时把对应的交易意图变量置位或清零。外汇与贵金属杠杆高,这类一键切换若没做状态互斥,可能在大波动中重复发单。
激活分支里,只要命中 BUTT_CLOSE_SELL2 或 BUTT_CLOSE_SELL_BY_BUY 带 _PRICE / _TIME 后缀的按钮名,就调用 ButtonState(button_name,true) 把按钮染成激活色,并分别把 pending_close_sell2、pending_close_sell_by_buy 写成 true,随后 ChartRedraw() 重绘图表。
未激活分支用位或运算保留另一路按钮的状态:例如关掉 BUTT_BUY_PRICE 时,pending_buy 不等于直接归零,而是等于当前按钮状态(已 false)与 BUTT_BUY_TIME 按钮状态的或值。这样价格触发与时间触发两条挂单通道不会因单点关闭而全灭,逻辑上倾向更稳。
开 MT5 把这段塞进 OnChartEvent 的 CHARTEVENT_OBJECT_CLICK 处理块,点几下面板看 pending_* 变量在观测窗口里的真假跳变,就能验证你的按钮互斥是不是真生效。
ButtonState(button_name,true); pending_close_sell=true; class=class="str">"cmt">//--- Set the active button class="type">color for the button enabling pending requests for closing class="num">1/class="num">2 Sell by price or time if(button==EnumToString(BUTT_CLOSE_SELL2)+"_PRICE" || button==EnumToString(BUTT_CLOSE_SELL2)+"_TIME") { ButtonState(button_name,true); pending_close_sell2=true; } class=class="str">"cmt">//--- Set the active button class="type">color for the button enabling pending requests for closing Sell by an opposite Buy by price or time if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_PRICE" || button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_TIME") { ButtonState(button_name,true); pending_close_sell_by_buy=true; } } class=class="str">"cmt">//--- re-draw the chart ChartRedraw(); } class=class="str">"cmt">//--- Return a class="type">color for the inactive buttons else { class=class="str">"cmt">//--- trailing button if(button==EnumToString(BUTT_TRAILING_ALL)) { ButtonState(button_name,class="kw">false); trailing_on=class="kw">false; } class=class="str">"cmt">//--- Buying class=class="str">"cmt">//--- the button enabling pending requests for opening Buy by price if(button==EnumToString(BUTT_BUY)+"_PRICE") { ButtonState(button_name,class="kw">false); pending_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY)+"_TIME")); } class=class="str">"cmt">//--- the button enabling pending requests for opening Buy by time if(button==EnumToString(BUTT_BUY)+"_TIME") { ButtonState(button_name,class="kw">false); pending_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY)+"_PRICE")); } class=class="str">"cmt">//--- the button enabling pending requests for placing BuyLimit by price if(button==EnumToString(BUTT_BUY_LIMIT)+"_PRICE") { ButtonState(button_name,class="kw">false); pending_buy_limit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_LIMIT)+"_TIME")); } class=class="str">"cmt">//--- the button enabling pending requests for placing BuyLimit by time if(button==EnumToString(BUTT_BUY_LIMIT)+"_TIME") { ButtonState(button_name,class="kw">false); pending_buy_limit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_LIMIT)+"_PRICE")); }
「挂单与平仓按钮的价时双触发逻辑」
在 MT5 面板里,BuyStop、BuyStopLimit、平仓与平半仓这类操作往往拆成「按价格」和「按时间」两个按钮。代码里用后缀 _PRICE 与 _TIME 区分二者,点击任一按钮会先把自身状态置否,再用按位或把对侧按钮状态并进来。 以 BuyStop 为例:当触发 BUTT_BUY_STOP_PRICE 时,pending_buy_stop 被赋值为当前 price 按钮状态与 time 按钮状态的或值;触发 BUTT_BUY_STOP_TIME 时逻辑完全对称。这样设计保证两种触发方式至少留一个有效位,避免用户同时关掉导致挂单请求丢失。 外汇与贵金属杠杆高,这类一键挂单面板若状态位算错,可能发出非预期市价单,实盘前务必在策略测试器里逐按钮点测。
class=class="str">"cmt">//--- the button enabling pending requests for placing BuyStop by price if(button==EnumToString(BUTT_BUY_STOP)+"_PRICE") { ButtonState(button_name,class="kw">false); pending_buy_stop=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_STOP)+"_TIME")); } class=class="str">"cmt">//--- the button enabling pending requests for placing BuyStop by time if(button==EnumToString(BUTT_BUY_STOP)+"_TIME") { ButtonState(button_name,class="kw">false); pending_buy_stop=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_STOP)+"_PRICE")); } class=class="str">"cmt">//--- the button enabling pending requests for placing BuyStopLimit by price if(button==EnumToString(BUTT_BUY_STOP_LIMIT)+"_PRICE") { ButtonState(button_name,class="kw">false); pending_buy_stoplimit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_STOP_LIMIT)+"_TIME")); } class=class="str">"cmt">//--- the button enabling pending requests for placing BuyStopLimit by time if(button==EnumToString(BUTT_BUY_STOP_LIMIT)+"_TIME") { ButtonState(button_name,class="kw">false); pending_buy_stoplimit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_STOP_LIMIT)+"_PRICE")); } class=class="str">"cmt">//--- the button enabling pending requests for closing Buy by price if(button==EnumToString(BUTT_CLOSE_BUY)+"_PRICE") { ButtonState(button_name,class="kw">false); pending_close_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY)+"_TIME")); } class=class="str">"cmt">//--- the button enabling pending requests for closing Buy by time if(button==EnumToString(BUTT_CLOSE_BUY)+"_TIME") { ButtonState(button_name,class="kw">false); pending_close_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY)+"_PRICE")); } class=class="str">"cmt">//--- the button enabling pending requests for closing class="num">1/class="num">2 Buy by price if(button==EnumToString(BUTT_CLOSE_BUY2)+"_PRICE") { ButtonState(button_name,class="kw">false); pending_close_buy2=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY2)+"_TIME")); } class=class="str">"cmt">//--- the button enabling pending requests for closing class="num">1/class="num">2 Buy by time if(button==EnumToString(BUTT_CLOSE_BUY2)+"_TIME") { ButtonState(button_name,class="kw">false); pending_close_buy2=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY2)+"_PRICE")); }
反手与卖单挂单的按钮互斥逻辑
这段处理的是面板里「按价格」和「按时间」两类触发按钮的互斥关系:任意一类被点击后先置灰自己,再用按位或把同源的另一按钮状态并进来,保证 pending 标志只反映当前激活的触发方式。
以反手平多为例,BUTT_CLOSE_BUY_BY_SELL 的 _PRICE 与 _TIME 两个后缀按钮共用同一个 pending_close_buy_by_sell 变量;点其中一个就把另一个的状态用 | 合并进来,避免重复挂单。
卖单侧同理覆盖了市价 Sell、SellLimit、SellStop 三组,每组都是 PRICE/TIME 双按钮互锁。外汇与贵金属杠杆高,这类面板若状态位算错,可能在同一根 K 线触发多次反向请求,实盘前务必在 MT5 策略测试器里逐一点击验证标志位翻转。
class=class="str">"cmt">//--- the button enabling pending requests for closing Buy by an opposite Sell by price if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_PRICE") { ButtonState(button_name,class="kw">false); pending_close_buy_by_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_TIME")); } class=class="str">"cmt">//--- the button enabling pending requests for closing Buy by an opposite Sell by time if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_TIME") { ButtonState(button_name,class="kw">false); pending_close_buy_by_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_PRICE")); } class=class="str">"cmt">//--- Selling class=class="str">"cmt">//--- the button enabling pending requests for opening Sell by price if(button==EnumToString(BUTT_SELL)+"_PRICE") { ButtonState(button_name,class="kw">false); pending_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL)+"_TIME")); } class=class="str">"cmt">//--- the button enabling pending requests for opening Sell by time if(button==EnumToString(BUTT_SELL)+"_TIME") { ButtonState(button_name,class="kw">false); pending_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL)+"_PRICE")); } class=class="str">"cmt">//--- the button enabling pending requests for placing SellLimit by price if(button==EnumToString(BUTT_SELL_LIMIT)+"_PRICE") { ButtonState(button_name,class="kw">false); pending_sell_limit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_LIMIT)+"_TIME")); } class=class="str">"cmt">//--- the button enabling pending requests for placing SellLimit by time if(button==EnumToString(BUTT_SELL_LIMIT)+"_TIME") { ButtonState(button_name,class="kw">false); pending_sell_limit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_LIMIT)+"_PRICE")); } class=class="str">"cmt">//--- the button enabling pending requests for placing SellStop by price if(button==EnumToString(BUTT_SELL_STOP)+"_PRICE") { ButtonState(button_name,class="kw">false); pending_sell_stop=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_STOP)+"_TIME")); } class=class="str">"cmt">//--- the button enabling pending requests for placing SellStop by time if(button==EnumToString(BUTT_SELL_STOP)+"_TIME") { ButtonState(button_name,class="kw">false);
◍ 卖单挂单与平仓按钮的互斥触发逻辑
在 MT5 面板里,SellStop / SellStopLimit / 平仓类按钮通常成对出现「按价格」与「按时间」两种触发模式。代码里用 ButtonState 把被点击的按钮置为 false,再用位或把同源的另一半状态并回 pending 变量,保证一对按钮不会同时激活。 以 SellStopLimit 为例:点击 '_PRICE' 时,pending_sell_stoplimit 会合并当前按钮态与 prefix+BUTT_SELL_STOP_LIMIT+'_TIME' 的态;点击 '_TIME' 时则反过来合并 '_PRICE'。外汇与贵金属杠杆高,这类挂单面板误触可能瞬间丢单或重复平仓,务必在策略测试器里先跑一遍。 下面这段摘出原文中 SellStopLimit 与平仓 Sell 的部分,可直贴 MT5 看事件响应: pending_sell_stoplimit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_STOP_LIMIT)+"_TIME")); // 点击按价格按钮后,把按时间按钮的态并回挂单变量 if(button==EnumToString(BUTT_SELL_STOP_LIMIT)+"_TIME") { ButtonState(button_name,false); pending_sell_stoplimit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_STOP_LIMIT)+"_PRICE")); } // 点击按时间按钮后,反向合并按价格按钮的态 实际调试时把 ButtonState 的返回值 Print 出来,能看到每次点击后 pending_xxx 的整数位变化,据此判断面板是否按预期互斥。
pending_sell_stoplimit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_STOP_LIMIT)+"_TIME")); if(button==EnumToString(BUTT_SELL_STOP_LIMIT)+"_TIME") { ButtonState(button_name,class="kw">false); pending_sell_stoplimit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_STOP_LIMIT)+"_PRICE")); }
「按钮状态联动与图表重绘的实现细节」
这段片段处理的是面板按钮的互斥与联动逻辑。先把指定按钮设为未按下状态,再用位或运算把主按钮和带前缀的价格按钮状态合并,决定 pending_close_sell_by_buy 的开关倾向。 ButtonState(button_name,false) 强制清掉当前按钮的选中态,避免重复触发;后面的 ButtonState(...) | ButtonState(prefix+EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_PRICE") 则是把两个控件的布尔值做按位或,只要其中一个为 true,挂单平仓开关就为 true。 最后调用 ChartRedraw() 立即重绘图表,否则 MT5 上按钮视觉状态可能滞后一到帧。开 MT5 把这段嵌进你的面板 OnChartEvent 里,点一下按钮就能看到价格线和平仓挂单标记同步刷新。外汇与贵金属品种波动快,这类 UI 状态不同步可能在秒级行情里误触下单,建议先在模拟盘验证。
ButtonState(button_name,class="kw">false); pending_close_sell_by_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_PRICE")); } class=class="str">"cmt">//--- re-draw the chart ChartRedraw(); } }
延后平仓的条件化延伸
这一节原本是系列第 31 篇的收尾,作者点出下一步要做的,是在已实现的延后开仓与下挂单基础上,把平仓也纳入条件触发框架:全部平、部分平、以及用逆向仓位平仓都会作为延后请求来处理。 当前版本函数库附带了 MQL5.zip 与 MQL4.zip 两个压缩包,体积均为 3662.36 KB,里面含全部库文件与测试 EA,可直接在 MT5 里加载验证。有用户反馈脚本编译报 7 处错误,作者回应是类变量误放在私有段、需移至 protected 段即可过编,这类细节只有真跑过代码才会碰到。 外汇与贵金属交易本身高风险,延后请求只是把发单时机从即时改成条件驱动,不消除滑点与拒绝风险,参数怎么设都得先在策略测试器里用历史数据跑一遍。 顺着这个落点,下一篇若真把部分平仓和逆向平仓写成延后对象,你就多了一套可回测的退出逻辑——打开附件里的 EA,把平仓条件从市价改成请求队列,看成交分布会不会更平滑。