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

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

第 3/3 篇
本章目录
  1. 挂单请求里的默认参数陷阱
  2. 挂单激活时的价格重校逻辑
  3. 交易动作分发的 switch 骨架
  4. 下单函数里的全局拦截与对象取用
  5. 挂单请求的内部错误与ID分配逻辑
  6. 下单函数入口的防护与对象取用
  7. 挂单请求构造里的失败分支与ID回收
  8. 挂单请求对象的工厂化分发
  9. 挂单请求的属性回填与买入模板
  10. 挂单接口的模板化封装
  11. 挂单函数的模板参数与默认值写法
  12. 挂单函数里的模板参数与默认值陷阱
  13. 挂卖单的模板化入口
  14. 卖停限单的模板化挂单接口
  15. 挂单请求的激活条件与买入封装
  16. 挂单请求的买卖与填充类型封装
  17. 挂单接口的限价与止损模板
  18. 挂单引擎里的 BuyStop 与 BuyStopLimit 封装
  19. 挂单引擎里的 SellLimit 与 BuyStopLimit 封装
  20. 卖挂单接口的模板化封装
  21. 挂单函数里的默认参数与跨版本分支
  22. 延后挂单请求的触发验证
  23. 正文
  24. 挂单请求里的延时与时间触发
  25. 挂单请求的激活条件拼装
  26. 挂单请求的价时双触发逻辑
  27. 挂单触发条件的价格与时间双分支
  28. 限价卖单挂单与条件触发的双通道
  29. 卖停挂单的触发条件分支
  30. 挂单引擎里的 SellStopLimit 延迟激活逻辑
  31. 按盈利极值平仓的按钮逻辑
  32. 按全成本利润挑单做对冲平仓
  33. 按盈利排序平掉最赚的空单
  34. 按盈亏全景筛选并互平多空仓位
  35. 按钮事件里的持仓与挂单清理逻辑
  36. 按钮按下的状态分支与挂单开关
  37. 挂单按钮激活时的状态标记逻辑
  38. 按钮状态切换与挂单标志位的联动
  39. 挂单与平仓按钮的价时双触发逻辑
  40. 反手与卖单挂单的按钮互斥逻辑
  41. 卖单挂单与平仓按钮的互斥触发逻辑
  42. 按钮状态联动与图表重绘的实现细节
  43. 延后平仓的条件化延伸

挂单请求里的默认参数陷阱

在 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 图表,大概率在日志看到订单结构校验失败——这比看文档更能记住默认值的坑。外汇与贵金属杠杆高,参数错配会在毫秒级放大亏损概率。

MQL5 / C++
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() 取回校正后的新请求结构,供后续发单使用。

MQL5 / C++
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 结构体字段再发单。

MQL5 / C++
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 时若发现挂单没带防护,先查这里是不是漏了参数。外汇与贵金属杠杆高,未设止损的开仓在跳空行情中可能迅速扩大浮亏。

MQL5 / C++
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。整段没有对外暴露任何收益暗示,外汇与贵金属杠杆品种下这类内部错误若不被日志捕获,可能造成漏单,属典型高风险盘口异常。

MQL5 / C++
   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 返回而不是报平台错误,验证防护是否生效。外汇与贵金属杠杆高,这类内部校验漏一处就可能把单发到错误品种上。

MQL5 / C++
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 的返回值分布,看自己的挂单上限设多少才不丢单。

MQL5 / C++
  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 封装层做参考,你能立刻验证:自己当前的重试队列是否也区分了这六种状态,还是只用单一通用对象糊弄。外汇与贵金属杠杆交易高风险,上述机制只解决请求管理,不预示任何胜率。

MQL5 / C++
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 月英镑闪崩数据跑一遍重试逻辑。

MQL5 / C++
  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 测一次,比看文档来得快。

MQL5 / C++
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) 就能在黄金上挂一张突破多单,外汇和贵金属杠杆高、滑点跳空频繁,挂单成交价可能偏离预期,建议先在策略测试器跑一遍。

MQL5 / C++
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=0tp=0 代表不挂止损止盈;magic=ULONG_MAX 是魔法码未指定状态,实盘里若直接发单,订单会带这个极值标记,后续按魔法码过滤持仓可能漏单。 type_timetype_filling 默认给的是 WRONG_VALUE,不是 ORDER_TIME_GTCORDER_FILLING_FOK 之类有效枚举。也就是说不显式传这两个参数,函数内部若直接拿去填 MqlTradeRequest 结构,OrderSend 会返回 10013(无效请求)。外汇与贵金属杠杆高,这类挂单参数错一处就可能瞬间错单,上 MT5 用脚本打印默认值自查最稳妥。

MQL5 / C++
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 不是“无风险”,只是“先不挂”,价格反向跳动时浮亏会直接暴露。

MQL5 / C++
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 类型,且流动性可能让限价触发后滑点偏大。

MQL5 / C++
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。这意味着你不传止损止盈时,系统按零值处理,实际下单前最好由调用层补默认值,避免市价单式裸奔。外汇与贵金属杠杆高,挂单未设风控参数可能带来超预期的浮亏概率。

MQL5 / C++
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 起头延续同样的可选尾参结构。

MQL5 / C++
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 数据跑一遍两个函数。

MQL5 / C++
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_stopprice_limit 两个模板参数,对应触发止损价和限价。外汇与贵金属挂单受点差和熔断影响,实际触发可能滑向不利价位,属高风险操作,参数请在策略测试器用 2023 年 XAUUSD 的 M1 数据跑一遍验证。

MQL5 / C++
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 请求被建立,外汇与贵金属杠杆品种均属高风险,挂单成交概率受滑点影响。

MQL5 / C++
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。

MQL5 / C++
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 不是能直接下单的值。外汇与贵金属杠杆高,这类未初始化参数若流到实盘,滑点与拒单风险会显著放大。

MQL5 / C++
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 进策略测试器开可视化模式,只点亮下挂单的延后订单按钮即可观察。首条延后请求按价格+时间双条件建好,后续几条仅按时间建;跑下来第一条在价时同触时激活,其余到时即激活,行为符合预期。 外汇与贵金属杠杆高,延后请求依赖实时点差与滑点,激活价可能偏离回测表现,实盘前务必在模拟盘复现。

MQL5 / C++
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);
                 }

◍ 正文

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">//--- If the time criterion is selected</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(ButtonState(prefix+<span class="functions">EnumToString</span>(BUTT_BUY)+<span class="string">"_TIME"</span>)) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">ulong</span> control_time=<span class="functions">TimeCurrent</span>()+bars_delay_pending_request*<span class="functions">PeriodSeconds</span>(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;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>()); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPendRequest *req_obj=engine.GetPendRequestByID((<span class="keyword">uchar</span>)id); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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 取值在你的品种上不会错过行情。

MQL5 / C++
   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 的取价与建请求核心。

MQL5 / C++
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 换成对应卖单枚举即可。

MQL5 / C++
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 专家日志里直接核对挂单请求究竟绑了哪类触发器。

MQL5 / C++
   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,就把已附加的激活条件打印到终端日记,方便复盘哪单卡在了什么条件上。外汇与贵金属杠杆高,这类条件单在跳空时可能不按预期触发,实盘前务必用策略测试器跑一遍。

MQL5 / C++
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 就把附加的激活准则打印到终端,方便你当场核对条件是否如预期写入。外汇与贵金属波动剧烈,这类条件单触发后滑点可能偏离预设。

MQL5 / C++
      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 把具体条件输出。外汇与贵金属波动剧烈,这类自动挂单触发存在滑点与跳空风险,参数请在策略测试器里先跑通再上真仓。

MQL5 / C++
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 账户直接跳过这段。外汇与贵金属杠杆高,部分平仓后残余仓位若遇跳空可能扩大浮亏,参数请在策略测试器先跑一遍。

MQL5 / C++
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 分支则只针对当前符号的空单做同样的最大净利筛选,结构对称、可单独复用。

MQL5 / C++
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 策略测试器用真实点差跑一遍。

MQL5 / C++
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 数据验证排序与平仓顺序是否符合你的风控。

MQL5 / C++
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 秒给主线程,避免按钮回调卡死。

MQL5 / C++
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 这个判断——后缀拼错一个字母,按钮就会被误判成普通键直接弹起。外汇与贵金属波动剧烈,这类面板逻辑错误可能让你漏掉挂单触发,实盘前务必用策略测试器点一遍。

MQL5 / C++
  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 里的实际名字差了下划线或后缀;外汇与贵金属杠杆高,这类一键挂单逻辑误触可能瞬间进场,建议先在策略测试器用闲盘验证。

MQL5 / C++
   }
   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_SELL2BUTT_CLOSE_SELL_BY_BUY_PRICE / _TIME 后缀的按钮名,就调用 ButtonState(button_name,true) 把按钮染成激活色,并分别把 pending_close_sell2pending_close_sell_by_buy 写成 true,随后 ChartRedraw() 重绘图表。 未激活分支用位或运算保留另一路按钮的状态:例如关掉 BUTT_BUY_PRICE 时,pending_buy 不等于直接归零,而是等于当前按钮状态(已 false)与 BUTT_BUY_TIME 按钮状态的或值。这样价格触发与时间触发两条挂单通道不会因单点关闭而全灭,逻辑上倾向更稳。 开 MT5 把这段塞进 OnChartEventCHARTEVENT_OBJECT_CLICK 处理块,点几下面板看 pending_* 变量在观测窗口里的真假跳变,就能验证你的按钮互斥是不是真生效。

MQL5 / C++
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 时逻辑完全对称。这样设计保证两种触发方式至少留一个有效位,避免用户同时关掉导致挂单请求丢失。 外汇与贵金属杠杆高,这类一键挂单面板若状态位算错,可能发出非预期市价单,实盘前务必在策略测试器里逐按钮点测。

MQL5 / C++
   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 策略测试器里逐一点击验证标志位翻转。

MQL5 / C++
   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 的整数位变化,据此判断面板是否按预期互斥。

MQL5 / C++
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 状态不同步可能在秒级行情里误触下单,建议先在模拟盘验证。

MQL5 / C++
  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,把平仓条件从市价改成请求队列,看成交分布会不会更平滑。

常见问题

默认参数常把挂单价、止损止盈留空或取当前市价,导致单子被系统拒或立刻成交。下单前务必显式传入挂单价与偏差,别依赖库里的初始值。
激活时若市价与挂单价偏差超容忍度,单子会废单或滑点成交。应在分发逻辑里加价格重校,按当前可成交价重写挂单参数再发。
可以。把交易动作日志贴给小布,它能标出默认参数遗漏、ID 冲突和拦截点,并给出重校价格的改法。
在 default 分支打印未覆盖的请求类型与 ID,确认枚举是否齐全。重点看限价单与止损单的 case 是否都接了对象取用。
拦截早于对象初始化就会拿到空指针。把对象取用放到防护校验通过之后,并在错误分支返回明确内部错误码便于追踪。