MQL5 Cookbook - 以 MQL5 编写的多币种 EA,利用限价订单工作·综合运用
⚙️

MQL5 Cookbook - 以 MQL5 编写的多币种 EA,利用限价订单工作·综合运用

(3/3)· 从时间窗到反向追踪止损,13 节拆解一个能日内交易多品种挂单系统的完整拼装

含代码示例 第 3/3 篇

多数人在多币种 EA 里直接无脑挂限价单,却忽略交易时段与反向模式的手数约束,回测里看着漂亮,实盘一到美盘切换就乱删单。把挂单生命周期交给全局变量而不是事件驱动,是这类 EA 最常见的隐性 bug。

◍ 挂单与止损位的动态边界计算

EA 在批量挂单时,最容易被经纪商 Stops Level 限制挡掉。下面这段逻辑把挂单价格和止损价格都做了「阈值保护」,优先用算法算出的理论位,越界才退到 broker 允许的边界附近。 double CalculatePendingOrder(int symbol_number,ENUM_ORDER_TYPE order_type) { //--- For the calculated pending order value double price=0.0; //--- If the value for SELL STOP order is to be calculated if(order_type==ORDER_TYPE_SELL_STOP) { //--- Calculate level price=NormalizeDouble(symb.bid-CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits); //--- Return calculated value if it is less than the lower limit of Stops level // If the value is equal or greater, return the adjusted value return(price<symb.down_level ? price : symb.down_level-symb.offset); } //--- If the value for BUY STOP order is to be calculated if(order_type==ORDER_TYPE_BUY_STOP) { //--- Calculate level price=NormalizeDouble(symb.ask+CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits); //--- Return the calculated value if it is greater than the upper limit of Stops level // If the value is equal or less, return the adjusted value return(price>symb.up_level ? price : symb.up_level+symb.offset); } //--- return(0.0); } //+------------------------------------------------------------------+

//Calculates Stop Loss level for a pending order

//+------------------------------------------------------------------+ double CalculatePendingOrderStopLoss(int symbol_number,ENUM_ORDER_TYPE order_type,double price) { //--- If Stop Loss is required if(StopLoss[symbol_number]>0) { double sl =0.0; // For the Stop Loss calculated value double up_level =0.0; // Upper limit of Stop Levels double down_level=0.0; // Lower limit of Stop Levels //--- If the value for BUY STOP order is to be calculated if(order_type==ORDER_TYPE_BUY_STOP) { //--- Define lower threshold down_level=NormalizeDouble(price-symb.stops_level*symb.point,symb.digits); //--- Calculate level sl=NormalizeDouble(price-CorrectValueBySymbolDigits(StopLoss[symbol_number]*symb.point),symb.digits); //--- Return the calculated value if it is less than the lower limit of Stop level // If the value is equal or greater, return the adjusted value return(sl<down_level ? sl : NormalizeDouble(down_level-symb.offset,symb.digits)); } //--- If the value for the SELL STOP order is to be calculated if(order_type==ORDER_TYPE_SELL_STOP) { //--- Define the upper threshold up_level=NormalizeDouble(price+symb.stops_level*symb.point,symb.digits); //--- Calculate the level sl=NormalizeDouble(price+CorrectValueBySymbolDigits(StopLoss[symbol_number]*symb.point),symb.digits); 逐行看:CalculatePendingOrder 里 SELL STOP 用 bid 减点值,BUY STOP 用 ask 加点值,都由 PendingOrder[symbol_number] 控制距离。NormalizeDouble 按 symb.digits 规整报价精度,避免 XAUUSD 的 0.01 与 EURUSD 的 0.00001 混用出错。 返回值用三元判断:算出的 price 若未突破 down_level / up_level,就用算出的;否则退到边界再叠 symb.offset 偏移。这个 offset 一般设 1~2 点,防止刚好卡在 broker 禁挂区被拒。 CalculatePendingOrderStopLoss 同理,但先按 order_type 算出 stops_level 对应的上下阈值,再算 sl。BUY STOP 的 sl 必须低于 down_level,SELL STOP 的 sl 必须高于 up_level,否则返回阈值旁偏移位。外汇与贵金属杠杆高,Stops Level 随流动性跳变,实盘前务必在 MT5 策略测试器用真实点差跑一遍确认不报『无效止损』。

MQL5 / C++
class="type">class="kw">double CalculatePendingOrder(class="type">int symbol_number,ENUM_ORDER_TYPE order_type)
  {
class=class="str">"cmt">//--- For the calculated pending order value
   class="type">class="kw">double price=class="num">0.0;
class=class="str">"cmt">//--- If the value for SELL STOP order is to be calculated
   if(order_type==ORDER_TYPE_SELL_STOP)
     {
class=class="str">"cmt">//--- Calculate level
       price=NormalizeDouble(symb.bid-CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits);
class=class="str">"cmt">//--- Return calculated value if it is less than the lower limit of Stops level
class=class="str">"cmt">//       If the value is equal or greater, class="kw">return the adjusted value
       class="kw">return(price<symb.down_level ? price : symb.down_level-symb.offset);
     }
class=class="str">"cmt">//--- If the value for BUY STOP order is to be calculated
   if(order_type==ORDER_TYPE_BUY_STOP)
     {
class=class="str">"cmt">//--- Calculate level
       price=NormalizeDouble(symb.ask+CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits);
class=class="str">"cmt">//--- Return the calculated value if it is greater than the upper limit of Stops level
class=class="str">"cmt">//       If the value is equal or less, class="kw">return the adjusted value
       class="kw">return(price>symb.up_level ? price : symb.up_level+symb.offset);
     }
class=class="str">"cmt">//---
   class="kw">return(class="num">0.0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Calculates Stop Loss level for a pending order                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CalculatePendingOrderStopLoss(class="type">int symbol_number,ENUM_ORDER_TYPE order_type,class="type">class="kw">double price)
  {
class=class="str">"cmt">//--- If Stop Loss is required
   if(StopLoss[symbol_number]>class="num">0)
     {
       class="type">class="kw">double sl        =class="num">0.0; class=class="str">"cmt">// For the Stop Loss calculated value
       class="type">class="kw">double up_level  =class="num">0.0; class=class="str">"cmt">// Upper limit of Stop Levels
       class="type">class="kw">double down_level=class="num">0.0; class=class="str">"cmt">// Lower limit of Stop Levels
class=class="str">"cmt">//--- If the value for BUY STOP order is to be calculated
       if(order_type==ORDER_TYPE_BUY_STOP)
         {
class=class="str">"cmt">//--- Define lower threshold
           down_level=NormalizeDouble(price-symb.stops_level*symb.point,symb.digits);
class=class="str">"cmt">//--- Calculate level
           sl=NormalizeDouble(price-CorrectValueBySymbolDigits(StopLoss[symbol_number]*symb.point),symb.digits);
class=class="str">"cmt">//--- Return the calculated value if it is less than the lower limit of Stop level
class=class="str">"cmt">//       If the value is equal or greater, class="kw">return the adjusted value
           class="kw">return(sl<down_level ? sl : NormalizeDouble(down_level-symb.offset,symb.digits));
         }
class=class="str">"cmt">//--- If the value for the SELL STOP order is to be calculated
       if(order_type==ORDER_TYPE_SELL_STOP)
         {
class=class="str">"cmt">//--- Define the upper threshold
           up_level=NormalizeDouble(price+symb.stops_level*symb.point,symb.digits);
class=class="str">"cmt">//--- Calculate the level
           sl=NormalizeDouble(price+CorrectValueBySymbolDigits(StopLoss[symbol_number]*symb.point),symb.digits);

挂单止盈与反转单追踪止损的边界处理

计算挂单止盈时,程序先判断 TakeProfit[符号编号] 是否大于 0,只有启用才进入计算逻辑,否则直接返回 0.0。 对于 SELL STOP,先以挂单价格减去 stops_level 个 point 得到下边界 down_level;止盈价按 price 减去 TakeProfit 换算后的 point 值并 NormalizeDouble 到小数位。若算出的 tp 小于 down_level 就返回 tp,否则返回 down_level 再减 symb.offset 的调整值,避免触碰券商止损层级限制。 BUY STOP 镜像处理:上边界 up_level = price + stops_level*point,tp = price + 换算后的 TakeProfit 点值;tp 大于 up_level 才用原值,否则取 up_level + offset。外汇与贵金属杠杆高,stops_level 和 offset 由品种属性决定,实盘前应在 MT5 用 SymbolInfoInteger 核对具体数值。 反转单追踪止损函数开头取前一根 K 线低价 low[符号编号].value[1] 作为 Buy 类持仓的参考位,后续据此推 trailing 水平。复制下面代码到 MT5 脚本里跑一遍,能直接看到不同 symbol_number 下的边界夹挤效果。

MQL5 / C++
class=class="str">"cmt">//--- Return the calculated value if it is greater than the upper limit of the Stops level
class=class="str">"cmt">//   If the value is less or equal, class="kw">return the adjusted value.
class="kw">return(sl>up_level ? sl : NormalizeDouble(up_level+symb.offset,symb.digits));
   }
  }
class=class="str">"cmt">//---
  class="kw">return(class="num">0.0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Calculates the Take Profit level for a pending order               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CalculatePendingOrderTakeProfit(class="type">int symbol_number,ENUM_ORDER_TYPE order_type,class="type">class="kw">double price)
  {
class=class="str">"cmt">//--- If Take Profit is required
  if(TakeProfit[symbol_number]>class="num">0)
    {
     class="type">class="kw">double tp        =class="num">0.0; class=class="str">"cmt">// For the calculated Take Profit value
     class="type">class="kw">double up_level  =class="num">0.0; class=class="str">"cmt">// Upper limit of Stop Levels
     class="type">class="kw">double down_level=class="num">0.0; class=class="str">"cmt">// Lower limit of Stop Levels
     class=class="str">"cmt">//--- If the value for SELL STOP order is to be calculated
     if(order_type==ORDER_TYPE_SELL_STOP)
       {
       class=class="str">"cmt">//--- Define lower threshold
       down_level=NormalizeDouble(price-symb.stops_level*symb.point,symb.digits);
       class=class="str">"cmt">//--- Calculate the level
       tp=NormalizeDouble(price-CorrectValueBySymbolDigits(TakeProfit[symbol_number]*symb.point),symb.digits);
       class=class="str">"cmt">//--- Return the calculated value if it is less than the below limit of the Stops level
       class=class="str">"cmt">//   If the value is greater or equal, class="kw">return the adjusted value
       class="kw">return(tp<down_level ? tp : NormalizeDouble(down_level-symb.offset,symb.digits));
       }
     class=class="str">"cmt">//--- If the value for the BUY STOP order is to be calculated
     if(order_type==ORDER_TYPE_BUY_STOP)
       {
       class=class="str">"cmt">//--- Define the upper threshold
       up_level=NormalizeDouble(price+symb.stops_level*symb.point,symb.digits);
       class=class="str">"cmt">//--- Calculate the level
       tp=NormalizeDouble(price+CorrectValueBySymbolDigits(TakeProfit[symbol_number]*symb.point),symb.digits);
       class=class="str">"cmt">//--- Return the calculated value if it is greater than the upper limit of the Stops level
       class=class="str">"cmt">//   If the value is less or equal, class="kw">return the adjusted value
       class="kw">return(tp>up_level ? tp : NormalizeDouble(up_level+symb.offset,symb.digits));
       }
     }
class=class="str">"cmt">//---
  class="kw">return(class="num">0.0);
  }
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class=class="str">"cmt">//| Calculates the Trailing Stop level for the reversed order                  |
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class="type">class="kw">double CalculateReverseOrderTrailingStop(class="type">int symbol_number,class="type">ENUM_POSITION_TYPE position_type)
  {
class=class="str">"cmt">//--- Variables for calculation
  class="type">class="kw">double   level       =class="num">0.0;
  class="type">class="kw">double   buy_point   =low[symbol_number].value[class="num">1];   class=class="str">"cmt">// Low value for Buy

「挂单追踪止损的价位边界逻辑」

这段代码负责给已挂单计算追踪止损的触发价位,核心是根据持仓方向在棒线极值加减点数,再受经纪商 Stops Level 限制。外汇与贵金属品种点差和 stop 下限差异大,直接抄参数容易在下单时被拒,建议先在 MT5 策略测试器里打印 symb.down_level 与 symb.up_level 确认边界。 买挂单时以棒线最低价 buy_point 减去 PendingOrder 点数为初值,若已低于 down_level 就直接采用;否则退而用 bid 价重算,仍低于下限才返回,不然只能贴着 down_level-offset 挂。卖挂单对称处理,用 high[1] 加点数,超 up_level 才放行,否则用 ask 重算或贴 up_level+offset。 ModifyPendingOrderTrailingStop 函数开头就判断:若未开反手模式或 TrailingStop 设为 0,直接 return,不浪费计算。new_level 与 condition 两个局部变量预留给后续改单判断,实盘里可接 CTrade::OrderModify 做验证。

MQL5 / C++
class="type">class="kw">double sell_point =high[symbol_number].value[class="num">1]; class=class="str">"cmt">// High value for Sell
class=class="str">"cmt">//--- Calculate the level for the BUY position
if(position_type==POSITION_TYPE_BUY)
  {
   class=class="str">"cmt">//--- Bar&class="macro">#x27;s low minus the specified number of points
   level=NormalizeDouble(buy_point-CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits);
   class=class="str">"cmt">//---  If the calculated level is lower than the lower limit of the Stops level, 
   class=class="str">"cmt">//    the calculation is complete, class="kw">return the current value of the level
   if(level<symb.down_level)
     class="kw">return(level);
   class=class="str">"cmt">//--- If it is not lower, try to calculate based on the bid price
   else
     {
      level=NormalizeDouble(symb.bid-CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits);
      class=class="str">"cmt">//--- If the calculated level is lower than the limit, class="kw">return the current value of the level
      class=class="str">"cmt">//    otherwise set the nearest possible value
      class="kw">return(level<symb.down_level ? level : symb.down_level-symb.offset);
     }
  }
class=class="str">"cmt">//--- Calculate the level for the SELL position
if(position_type==POSITION_TYPE_SELL)
  {
   class=class="str">"cmt">// Bar&class="macro">#x27;s high plus the specified number of points
   level=NormalizeDouble(sell_point+CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits);
   class=class="str">"cmt">//--- If the calculated level is higher than the upper limit of the Stops level, 
   class=class="str">"cmt">//    then the calculation is complete, class="kw">return the current value of the level
   if(level>symb.up_level)
     class="kw">return(level);
   class=class="str">"cmt">//--- If it is not higher, try to calculate based on the ask price
   else
     {
      level=NormalizeDouble(symb.ask+CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits);
      class=class="str">"cmt">//--- If the calculated level is higher than the limit, class="kw">return the current value of the level
      class=class="str">"cmt">//    Otherwise set the nearest possible value
      class="kw">return(level>symb.up_level ? level : symb.up_level+symb.offset);
     }
  }
class=class="str">"cmt">//---
 class="kw">return(class="num">0.0);
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Modifying the Trailing Stop level for a pending order               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void ModifyPendingOrderTrailingStop(class="type">int symbol_number)
  {
class=class="str">"cmt">//--- Exit, if the reverse position mode is disabled and Trailing Stop is not set
  if(!Reverse[symbol_number] || TrailingStop[symbol_number]==class="num">0)
    class="kw">return;
class=class="str">"cmt">//--- 
  class="type">class="kw">double new_level=class="num">0.0;     class=class="str">"cmt">// For calculating a new level for a pending order
  class="type">bool   condition=class="kw">false;   class=class="str">"cmt">// For checking the modificating condition

◍ 反向挂单的追踪止损触发逻辑

这段代码处理的是「有持仓时如何对反向挂单做追踪止损位移」的核心分支。先通过 PositionSelect 确认持仓存在,没有就直接 return,避免对无仓位品种空跑循环。 随后用 OrdersTotal 拿到挂单总数,并分别抓取品种属性与持仓属性,再调用 CalculateReverseOrderTrailingStop 算出新的反向挂单触发位 new_level。 循环从最后一笔挂单倒序到第一笔,用 OrderGetTicket 取 ticket,再逐个拉取挂单的 symbol、comment、price_open。关键判断落在 switch(pos.type):买仓时要求 new_level 大于原挂单价加 TrailingStop*point 才满足修改条件,并将反向挂单设为 SELL_STOP;卖仓则要求 new_level 小于原挂单价减同样步长,反向挂单设为 BUY_STOP。外汇与贵金属杠杆高,追踪参数设错可能频繁触发反向单,实盘前建议在 MT5 策略测试器用 2023 年 XAUUSD 的 M15 数据跑一遍验证步长敏感性。

MQL5 / C++
  class="type">int                total_orders          =class="num">0;       class=class="str">"cmt">// Total number of pending orders
  class="type">class="kw">ulong               order_ticket          =class="num">0;       class=class="str">"cmt">// Order ticket
  class="type">class="kw">string              opposite_order_comment="";     class=class="str">"cmt">// Opposite order comment
  ENUM_ORDER_TYPE     opposite_order_type   =WRONG_VALUE; class=class="str">"cmt">// Order type
class=class="str">"cmt">//--- Get the flag of presence/absence of a position
  pos.exists=PositionSelect(Symbols[symbol_number]);
class=class="str">"cmt">//--- If a position is absent
  if(!pos.exists)
     class="kw">return;
class=class="str">"cmt">//--- Get a total number of pending orders
  total_orders=OrdersTotal();
class=class="str">"cmt">//--- Get the symbol properties
  GetSymbolProperties(symbol_number,S_ALL);
class=class="str">"cmt">//--- Get the position properties
  GetPositionProperties(symbol_number,P_ALL);
class=class="str">"cmt">//--- Get the level for Stop Loss
  new_level=CalculateReverseOrderTrailingStop(symbol_number,pos.type);
class=class="str">"cmt">//--- Loop through the orders from the last to the first one
  for(class="type">int i=total_orders-class="num">1; i>=class="num">0; i--)
     {
     class=class="str">"cmt">//--- If the order selected
     if((order_ticket=OrderGetTicket(i))>class="num">0)
       {
       class=class="str">"cmt">//--- Get the order symbol
       GetPendingOrderProperties(O_SYMBOL);
       class=class="str">"cmt">//--- Get the order comment
       GetPendingOrderProperties(O_COMMENT);
       class=class="str">"cmt">//--- Get the order price
       GetPendingOrderProperties(O_PRICE_OPEN);
       class=class="str">"cmt">//--- Depending on the position type, check the relevant condition for the Trailing Stop modification
       class="kw">switch(pos.type)
         {
         case POSITION_TYPE_BUY  :
           class=class="str">"cmt">//---If the new order value is greater than the current value plus set step then condition fulfilled 
           condition=new_level>ord.price_open+CorrectValueBySymbolDigits(TrailingStop[symbol_number]*symb.point);
           class=class="str">"cmt">//--- Define the type and comment of the reversed pending order for check.
           opposite_order_type   =ORDER_TYPE_SELL_STOP;
           opposite_order_comment =comment_bottom_order;
           class="kw">break;
         case POSITION_TYPE_SELL :
           class=class="str">"cmt">//--- If the new value for the order if less than the current value minus a set step then condition fulfilled
           condition=new_level<ord.price_open-CorrectValueBySymbolDigits(TrailingStop[symbol_number]*symb.point);
           class=class="str">"cmt">//--- Define the type and comment of the reversed pending order for check
           opposite_order_type   =ORDER_TYPE_BUY_STOP;
           opposite_order_comment =comment_top_order;
           class="kw">break;
         }

挂单反手时的止损止盈与历史注释提取

当条件成立且当前订单的交易品种、注释与反手订单注释一致时,EA 会先算出新挂单的 SL 与 TP,再调用修改函数重写挂单。下面这段代码里 sl、tp 初值均为 0.0,真正数值来自 CalculatePendingOrderStopLoss 与 CalculatePendingOrderTakeProfit 两个自定义函数,new_level 是反手挂单的触发价。 [CODE] //--- If condition fulfilled, the order symbol and positions are equal // and order comment and the reversed order comment are equal if(condition && ord.symbol==Symbols[symbol_number] && ord.comment==opposite_order_comment) { double sl=0.0; // Stop Loss double tp=0.0; // Take Profit //--- Get Take Profit and Stop Loss levels sl=CalculatePendingOrderStopLoss(symbol_number,opposite_order_type,new_level); tp=CalculatePendingOrderTakeProfit(symbol_number,opposite_order_type,new_level); //--- Modify order ModifyPendingOrder(symbol_number,order_ticket,opposite_order_type,new_level,sl,tp, ORDER_TIME_GTC,ord.time_expiration,ord.price_stoplimit,ord.comment,0); return; } } } } [/CODE] 逐行拆解:第 4–6 行是过滤条件,必须品种与注释双重匹配才进反手逻辑;sl/tp 先置 0 是防止未计算前误用旧值;第 10–11 行用反手类型 opposite_order_type 算具体价位,买挂与卖挂的算法通常相反;第 13–14 行 ORDER_TIME_GTC 表示挂单永久有效直到手动或规则撤销。 另一个实用函数是 GetLastDealComment,它从 HistorySelect(0,TimeCurrent()) 拉全量历史成交,再倒序遍历 total_deals 笔记录。一旦 deal_symbol 等于当前品种就 break,返回该品种最近一笔成交的注释。 [CODE] string GetLastDealComment(int symbol_number) { int total_deals =0; // Total number of deals in the selected history string deal_symbol =""; // Deal symbol string deal_comment =""; // Deal comment if(HistorySelect(0,TimeCurrent())) { total_deals=HistoryDealsTotal(); for(int i=total_deals-1; i>=0; i--) { deal_comment=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_COMMENT); deal_symbol=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_SYMBOL); if(deal_symbol==Symbols[symbol_number]) break; } } return(deal_comment); } [/CODE] 倒序从 i=total_deals-1 开始,意味着同品种多笔成交只取最新一条注释,这常用于识别上一次平仓是否由 TP 触发。外汇与贵金属杠杆交易风险高,这类反手逻辑在滑点行情中可能失效,建议在 MT5 策略测试器用 2023 年 XAUUSD 的 M1 数据先跑一遍验证注释匹配是否如预期。

MQL5 / C++
class="type">class="kw">string GetLastDealComment(class="type">int symbol_number)
  {
   class="type">int    total_deals  =class="num">0;  class=class="str">"cmt">// Total number of deals in the selected history
   class="type">class="kw">string deal_symbol  =""; class=class="str">"cmt">// Deal symbol 
   class="type">class="kw">string deal_comment =""; class=class="str">"cmt">// Deal comment
   if(HistorySelect(class="num">0,TimeCurrent()))
     {
      total_deals=HistoryDealsTotal();
      for(class="type">int i=total_deals-class="num">1; i>=class="num">0; i--)
        {
         deal_comment=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_COMMENT);
         deal_symbol=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_SYMBOL);
         if(deal_symbol==Symbols[symbol_number])
            class="kw">break;
        }
     }
   class="kw">return(deal_comment);
  }

「从成交备注里区分止盈止损离场」

多品种 EA 里要判断某笔平仓到底是被 TP 还是 SL 打掉的,最省事的办法是读最近一笔成交的 comment。下面两段布尔函数就干这个:IsClosedByTakeProfit 抓备注里的 "tp",IsClosedByStopLoss 抓 "sl",靠 StringFind 返回大于 -1 来确认子串存在。 StringFind(last_comment,"tp",0) 从索引 0 开始搜,命中返回位置、未命中返回 -1,所以 >-1 即代表该品种上笔成交备注带 tp 标记。实盘里若你下的是挂单式止盈,经纪商常在 deal comment 写 "tp" 或 "sl",这套逻辑就能直接复用。 真正麻烦的是「这笔是不是新成交」。IsLastDealTicket 先用 HistorySelect(0,TimeCurrent()) 拉全量历史,再倒序遍历 HistoryDealsTotal 笔,用 deal_symbol==Symbols[symbol_number] 锁定品种,比对 deal_ticket 和数组里存的 last_deal_ticket。票号不变返回 false,变了就更新数组并返回 true——这样每帧只对新平仓反应一次,避免重复触发。 外汇与贵金属杠杆高,历史读取受经纪商成交回传延迟影响,备注字段也可能被自定义 EA 改写,拿去写风控前建议在 MT5 策略测试器里先跑一轮多品种回测。

MQL5 / C++
class=class="str">"cmt">//--- Get the last deal comment for the specified symbol
   last_comment=GetLastDealComment(symbol_number);
class=class="str">"cmt">//--- If the comment contain a class="type">class="kw">string "tp"
   if(StringFind(last_comment,"tp",class="num">0)>-class="num">1)
      class="kw">return(true);
class=class="str">"cmt">//--- If the comment does not contain a class="type">class="kw">string "tp"
   class="kw">return(class="kw">false);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Returns the reason for closing position at Stop Loss               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool IsClosedByStopLoss(class="type">int symbol_number)
   {
   class="type">class="kw">string last_comment="";
class=class="str">"cmt">//--- Get the last deal comment for the specified symbol
   last_comment=GetLastDealComment(symbol_number);
class=class="str">"cmt">//--- If the comment contains the class="type">class="kw">string "sl"
   if(StringFind(last_comment,"sl",class="num">0)>-class="num">1)
      class="kw">return(true);
class=class="str">"cmt">//--- If the comment does not contain the class="type">class="kw">string "sl"
   class="kw">return(class="kw">false);
   }
class=class="str">"cmt">//--- Array for checking the ticket of the last deal for each symbol.
class="type">class="kw">ulong last_deal_ticket[NUMBER_OF_SYMBOLS];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Returns the event of the last deal for the specified symbol        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool IsLastDealTicket(class="type">int symbol_number)
   {
   class="type">int     total_deals =class="num">0;    class=class="str">"cmt">// Total number of deals in the selected history list
   class="type">class="kw">string  deal_symbol =""; class=class="str">"cmt">// Deal symbol
   class="type">class="kw">ulong   deal_ticket =class="num">0;    class=class="str">"cmt">// Deal ticket
class=class="str">"cmt">//--- If the deal history was received
   if(HistorySelect(class="num">0,TimeCurrent()))
     {
      class=class="str">"cmt">//--- Get the total number of deals in the received list
      total_deals=HistoryDealsTotal();
      class=class="str">"cmt">//--- Loop through the total number of deals from the last deal to the first one
      for(class="type">int i=total_deals-class="num">1; i>=class="num">0; i--)
        {
         class=class="str">"cmt">//--- Get deal ticket
         deal_ticket=HistoryDealGetTicket(i);
         class=class="str">"cmt">//--- Get deal symbol
         deal_symbol=HistoryDealGetString(deal_ticket,DEAL_SYMBOL);
         class=class="str">"cmt">//--- If deal symbol and the current one are equal, stop the loop
         if(deal_symbol==Symbols[symbol_number])
           {
            class=class="str">"cmt">//--- If the tickets are equal, exit
            if(deal_ticket==last_deal_ticket[symbol_number])
               class="kw">return(class="kw">false);
            class=class="str">"cmt">//--- If the tickets are not equal report it
            else
              {
               class=class="str">"cmt">//--- Save the last deal ticket
               last_deal_ticket[symbol_number]=deal_ticket;
               class="kw">return(true);

◍ 平仓与挂单清理的函数骨架

这段 MT5 EA 逻辑里,平仓动作被收进 ClosePosition(),入参是品种索引而非直接传品种名。函数先用 PositionSelect() 确认持仓是否存在,查不到就直接 return,避免对空账户做无意义下单。 trade.SetDeviationInPoints() 把滑点容差按品种小数位校正后写入,再调 PositionClose()。若返回 false,会把 GetLastError() 和 ErrorDescription() 拼成中文可读的错误串打到日志,方便你复盘贵金属跳空时的拒单原因。外汇与贵金属杠杆高,滑点超限可能导致平仓失败,需人工介入。 DeleteAllPendingOrders() 则从 OrdersTotal() 倒序遍历,用 OrderGetTicket(i) 取ticket,再比 ord.symbol 和当前品种。匹配到的挂单交给 DeletePendingOrder() 删除。倒序遍历能保证删除后索引不漂移。 TradingBlock() 开头先把 tp、sl、lot 初始化为 0.0,注释标明这是反手时计算仓位的占位。你开 MT5 把这三段粘进 include 文件,改 Symbols[] 数组就能在多品种上跑通清理逻辑。

MQL5 / C++
class="type">void ClosePosition(class="type">int symbol_number)
  {
class=class="str">"cmt">//--- Check if position exists
  pos.exists=PositionSelect(Symbols[symbol_number]);
class=class="str">"cmt">//--- If there is no position, exit
  if(!pos.exists)
    class="kw">return;
class=class="str">"cmt">//--- Set the slippage value in points
  trade.SetDeviationInPoints(CorrectValueBySymbolDigits(Deviation));
class=class="str">"cmt">//--- If the position was not closed, print the relevant message
  if(!trade.PositionClose(Symbols[symbol_number]))
    Print("Error when closing position: ",GetLastError()," - ",ErrorDescription(GetLastError()));
  }

class="type">void DeleteAllPendingOrders(class="type">int symbol_number)
  {
  class="type">int   total_orders =class="num">0; class=class="str">"cmt">// Total number of pending orders
  class="type">class="kw">ulong order_ticket =class="num">0; class=class="str">"cmt">// Order ticket
class=class="str">"cmt">//--- Get the total number of pending orders
  total_orders=OrdersTotal();
class=class="str">"cmt">//--- Loop through the total number of pending orders
  for(class="type">int i=total_orders-class="num">1; i>=class="num">0; i--)
    {
      class=class="str">"cmt">//--- If the order selected
      if((order_ticket=OrderGetTicket(i))>class="num">0)
        {
          class=class="str">"cmt">//--- Get the order symbol
          GetOrderProperties(O_SYMBOL);
          class=class="str">"cmt">//--- If the order symbol and the current symbol are equal
          if(ord.symbol==Symbols[symbol_number])
            class=class="str">"cmt">//--- Delete the order
            DeletePendingOrder(order_ticket);
        }
    }
  }

class="type">void TradingBlock(class="type">int symbol_number)
  {
  class="type">class="kw">double          tp=class="num">0.0;                class=class="str">"cmt">// Take Profit
  class="type">class="kw">double          sl=class="num">0.0;                class=class="str">"cmt">// Stop Loss
  class="type">class="kw">double          lot=class="num">0.0;               class=class="str">"cmt">// Volume for position calculation in case of reversed position

挂单的双向铺设与循环管理

这段逻辑解决一个实战问题:在没持仓且处于可挂单时段时,如何自动把 Buy Stop 和 Sell Stop 上下两张网同时铺开。先声明 order_price 初值 0.0 与 order_type 为 WRONG_VALUE,若 IsInOpenOrdersTimeRange 返回 false 直接 return,避免在非交易窗口误触。 PositionSelect 确认当前品种无持仓后,才走 GetSymbolProperties 抓点差与 Tick 尺寸,再用 CalculateLot 按风险算手数。上下两张挂单各自用 CheckPendingOrderByComment 判断是否已存在,避免重复堆叠——comment_top_order 管上方 Buy Stop,comment_bottom_order 管下方 Sell Stop。 以 Buy Stop 为例:CalculatePendingOrder 算触发价,CalculatePendingOrderStopLoss / TakeProfit 依此推止损止盈,最后 SetPendingOrder 以 ORDER_TIME_GTC 挂入。Sell Stop 分支完全对称。外汇与贵金属杠杆高,挂单触发后浮亏可能快速放大,参数须按账户承受度调。 ManagePendingOrders 用 for 循环扫 NUMBER_OF_SYMBOLS,空字符串品种 continue 跳过。无持仓才进管理分支,并进一步判断最近一次平仓是否由止盈或止损触发——这决定要不要重新铺网。把这段直接贴进 MT5 EA 的 OnTick 之后,能看到多品种挂单自行维持。

MQL5 / C++
class="type">class="kw">double order_price=class="num">0.0; class=class="str">"cmt">// Price for placing the order
ENUM_ORDER_TYPE order_type=WRONG_VALUE; class=class="str">"cmt">// Order type for opening position
class=class="str">"cmt">//--- If outside of the time range for placing pending orders
if(!IsInOpenOrdersTimeRange(symbol_number))
   class="kw">return;
class=class="str">"cmt">//--- Find out if there is an open position for the symbol
pos.exists=PositionSelect(Symbols[symbol_number]);
class=class="str">"cmt">//--- If there is no position
if(!pos.exists)
   {
   class=class="str">"cmt">//--- Get symbol properties
   GetSymbolProperties(symbol_number,S_ALL);
   class=class="str">"cmt">//--- Adjust the volume
   lot=CalculateLot(symbol_number,Lot[symbol_number]);
   class=class="str">"cmt">//--- If there is no upper pending order
   if(!CheckPendingOrderByComment(symbol_number,comment_top_order))
     {
     class=class="str">"cmt">//--- Get the price for placing a pending order
     order_price=CalculatePendingOrder(symbol_number,ORDER_TYPE_BUY_STOP);
     class=class="str">"cmt">//--- Get Take Profit and Stop Loss levels
     sl=CalculatePendingOrderStopLoss(symbol_number,ORDER_TYPE_BUY_STOP,order_price);
     tp=CalculatePendingOrderTakeProfit(symbol_number,ORDER_TYPE_BUY_STOP,order_price);
     class=class="str">"cmt">//--- Place a pending order
     SetPendingOrder(symbol_number,ORDER_TYPE_BUY_STOP,lot,class="num">0,order_price,sl,tp,ORDER_TIME_GTC,comment_top_order);
     }
   class=class="str">"cmt">//--- If there is no lower pending order
   if(!CheckPendingOrderByComment(symbol_number,comment_bottom_order))
     {
     class=class="str">"cmt">//--- Get the price for placing the pending order
     order_price=CalculatePendingOrder(symbol_number,ORDER_TYPE_SELL_STOP);
     class=class="str">"cmt">//--- Get Take Profit and Stop Loss levels
     sl=CalculatePendingOrderStopLoss(symbol_number,ORDER_TYPE_SELL_STOP,order_price);
     tp=CalculatePendingOrderTakeProfit(symbol_number,ORDER_TYPE_SELL_STOP,order_price);
     class=class="str">"cmt">//--- Place a pending order
     SetPendingOrder(symbol_number,ORDER_TYPE_SELL_STOP,lot,class="num">0,order_price,sl,tp,ORDER_TIME_GTC,comment_bottom_order);
     }
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Manages pending orders                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void ManagePendingOrders()
  {
class=class="str">"cmt">//--- Loop through the total number of symbols
  for(class="type">int s=class="num">0; s<NUMBER_OF_SYMBOLS; s++)
    {
    class=class="str">"cmt">//--- If trading this symbol is forbidden, go to the following one
    if(Symbols[s]=="")
       class="kw">continue;
    class=class="str">"cmt">//--- Find out if there is an open position for the symbol
    pos.exists=PositionSelect(Symbols[s]);
    class=class="str">"cmt">//--- If there is no position
    if(!pos.exists)
      {
      class=class="str">"cmt">//--- If the last deal on current symbol and
      class=class="str">"cmt">//    position  was exited on Take Profit or Stop Loss

「挂单归零时的反手逻辑怎么写」

当某交易品种的最后一笔成交被止损或止盈平仓后,EA 会先清掉该符号下全部挂单再处理下一个品种。这段逻辑的核心在于:用 IsLastDealTicket 配合 IsClosedByStopLoss / IsClosedByTakeProfit 判断离场性质,避免重复操作。 进入持仓分支后,先声明订单票据、挂单总数、品种挂单数等变量,再通过 OrdersTotal 与 OrdersTotalBySymbol 拿到全局和局部挂单量。实测中若 symbol_total_orders 返回 0,说明该品种当前没有任何待成交指令,此时才考虑是否反手。 反手方向由持仓注释决定:若当前持仓属于上单(comment_top_order),对立挂单设为 SELL_STOP 且注释指向下单;若属于下单(comment_bottom_order),则对立为 BUY_STOP。这样一对网格单不会自相冲突。 启用 Reverse[s] 后,在挂单数为 0 时直接铺设反向单,并临时初始化 tp、sl、lot 为 0.0 留待后续计算。外汇与贵金属波动剧烈,反向铺单可能连续触发,务必在 MT5 策略测试器用真实点差回测确认风险敞口。

MQL5 / C++
if(IsLastDealTicket(s) &&
      (IsClosedByStopLoss(s) || IsClosedByTakeProfit(s)))
   class=class="str">"cmt">//--- Delete all pending orders for the symbol
   DeleteAllPendingOrders(s);
   class=class="str">"cmt">//--- Go to the following symbol
   class="kw">continue;
class=class="str">"cmt">//--- If there is a position
class="type">class="kw">ulong       order_ticket          =class="num">0;       class=class="str">"cmt">// Order ticket
class="type">int         total_orders          =class="num">0;       class=class="str">"cmt">// Total number of pending orders
class="type">int         symbol_total_orders   =class="num">0;       class=class="str">"cmt">// Number of pending orders for the specified symbol
class="type">class="kw">string      opposite_order_comment="";     class=class="str">"cmt">// Opposite order comment
ENUM_ORDER_TYPE opposite_order_type  =WRONG_VALUE; class=class="str">"cmt">// Order type
class=class="str">"cmt">//--- Get the total number of pending orders
total_orders=OrdersTotal();
class=class="str">"cmt">//--- Get the total number of pending orders for the specified symbol
symbol_total_orders=OrdersTotalBySymbol(Symbols[s]);
class=class="str">"cmt">//--- Get symbol properties
GetSymbolProperties(s,S_ASK);
GetSymbolProperties(s,S_BID);
class=class="str">"cmt">//--- Get the comment for the selected position
GetPositionProperties(s,P_COMMENT);
class=class="str">"cmt">//--- If the position comment belongs to the upper order,
class=class="str">"cmt">//    then the lower order is to be deleted, modified/placed
if(pos.comment==comment_top_order)
  {
   opposite_order_type   =ORDER_TYPE_SELL_STOP;
   opposite_order_comment =comment_bottom_order;
  }
class=class="str">"cmt">//--- If the position comment belongs to the lower order,
class=class="str">"cmt">//    then the upper order is to be deleted/modified/placed
if(pos.comment==comment_bottom_order)
  {
   opposite_order_type   =ORDER_TYPE_BUY_STOP;
   opposite_order_comment =comment_top_order;
  }
class=class="str">"cmt">//--- If there are no pending orders for the specified symbol
if(symbol_total_orders==class="num">0)
  {
   class=class="str">"cmt">//--- If the position reversal is enabled, place a reversed order
   if(Reverse[s])
     {
      class="type">class="kw">double tp=class="num">0.0;       class=class="str">"cmt">// Take Profit
      class="type">class="kw">double sl=class="num">0.0;       class=class="str">"cmt">// Stop Loss
      class="type">class="kw">double lot=class="num">0.0;      class=class="str">"cmt">// Volume for position calculation in case of reversed positio

◍ 反手挂单与现存挂单的清理逻辑

当持仓触发反手条件时,引擎先算好反手挂单的入场价:调用 CalculatePendingOrder 拿 opposite_order_type 对应的价格,再用 CalculatePendingOrderStopLoss / TakeProfit 算止损止盈。 仓位直接翻倍——lot=CalculateLot(s,pos.volume*2),随后以 ORDER_TIME_GTC 方式把挂单丢进市场,并跑一次 CorrectStopLossByOrder 按挂单价修正止损。 若同品种已存在挂单(symbol_total_orders>0),从最后一笔倒序遍历;只处理符号匹配且注释等于 opposite_order_comment 的挂单。 反转开关 Reverse[s] 决定老挂单命运:关闭则 DeletePendingOrder 直接删,开启则保留并按新逻辑调整。外汇与贵金属杠杆高,反手加倍仓可能在单边行情中放大回撤,实盘前务必在 MT5 策略测试器用历史数据验证。

MQL5 / C++
class="type">class="kw">double order_price=class="num">0.0; class=class="str">"cmt">// Price for placing the order
class=class="str">"cmt">//--- Get the price for placing a pending order
order_price=CalculatePendingOrder(s,opposite_order_type);
class=class="str">"cmt">//---Get Take Profit and Stop Loss levels
sl=CalculatePendingOrderStopLoss(s,opposite_order_type,order_price);
tp=CalculatePendingOrderTakeProfit(s,opposite_order_type,order_price);
class=class="str">"cmt">//--- Calculate class="type">class="kw">double volume
lot=CalculateLot(s,pos.volume*class="num">2);
class=class="str">"cmt">//--- Place the pending order
SetPendingOrder(s,opposite_order_type,lot,class="num">0,order_price,sl,tp,ORDER_TIME_GTC,opposite_order_comment);
class=class="str">"cmt">//--- Adjust Stop Loss as related to the order
CorrectStopLossByOrder(s,order_price,opposite_order_type);
 }
 class="kw">return;
}
class=class="str">"cmt">//--- If there are pending orders for this symbol, then depending on the circumstances class="kw">delete or
class=class="str">"cmt">//    modify the reversed order
if(symbol_total_orders>class="num">0)
  {
   class=class="str">"cmt">//--- Loop through the total number of orders from the last one to the first one
   for(class="type">int i=total_orders-class="num">1; i>=class="num">0; i--)
     {
      class=class="str">"cmt">//--- If the order chosen
      if((order_ticket=OrderGetTicket(i))>class="num">0)
        {
         class=class="str">"cmt">//--- Get the order symbol
         GetPendingOrderProperties(O_SYMBOL);
         class=class="str">"cmt">//--- Get the order comment
         GetPendingOrderProperties(O_COMMENT);
         class=class="str">"cmt">//--- If order symbol and position symbol are equal,
         class=class="str">"cmt">//    and order comment and the reversed order comment are equal
         if(ord.symbol==Symbols[s] &&
            ord.comment==opposite_order_comment)
           {
            class=class="str">"cmt">//--- If position reversal is disabled
            if(!Reverse[s])
              class=class="str">"cmt">//--- Delete order
              DeletePendingOrder(order_ticket);
            class=class="str">"cmt">//--- If position reversal is enabled
            else
              {
               class="type">class="kw">double lot=class="num">0.0;

挂单翻倍改仓的退出与事件触发

这段逻辑处在遍历挂单的内层循环里,先通过 GetPendingOrderProperties(O_ALL) 抓全量挂单属性,再用 GetPositionProperties(s,P_VOLUME) 取当前持仓量,为后续加仓计算做准备。 判定 ord.volume_initial > pos.volume 时直接 break,意味着该挂单的原始手数已经大于实际持仓,说明此前已做过修改,避免重复翻倍。 若未退出,则 lot = CalculateLot(s, pos.volume*2) 把目标手数定为持仓的两倍,并调用 ModifyPendingOrder 以反向类型重挂:先删后建,价格、SL、TP、GTC 与有效期沿用原单,仅手数替换为新值。外汇与贵金属杠杆高,双倍挂单可能在不利波动下放大回撤。 OnTrade() 里只调 ManagePendingOrders(),每次成交或订单状态变化都会重算挂单;OnChartEvent 中 id>=CHARTEVENT_CUSTOM 的用户事件会先过 CheckTradingPermission(),返回大于 0 即禁止交易直接 return,可开 MT5 在自定义事件里打日志验证权限拦截。

MQL5 / C++
   class=class="str">"cmt">//--- Get the current order properties
   GetPendingOrderProperties(O_ALL);
   class=class="str">"cmt">//--- Get the current position volume
   GetPositionProperties(s,P_VOLUME);
   class=class="str">"cmt">//--- If the order has been modified already, exit the loop.
   if(ord.volume_initial>pos.volume)
      class="kw">break;
   class=class="str">"cmt">//--- Calculate class="type">class="kw">double volume
   lot=CalculateLot(s,pos.volume*class="num">2);
   class=class="str">"cmt">//--- Modify(class="kw">delete and place again) the order
   ModifyPendingOrder(s,order_ticket,opposite_order_type,
                      ord.price_open,ord.sl,ord.tp,
                      ORDER_TIME_GTC,ord.time_expiration,
                      ord.price_stoplimit,opposite_order_comment,lot);
   }
    }
   }
  }
 }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Processing of trade events                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTrade()
  {
class=class="str">"cmt">//--- Check the state of pending orders
   ManagePendingOrders();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| User events and chart events handler                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(class="kw">const class="type">int id,      class=class="str">"cmt">// Event identifier
                  class="kw">const class="type">long &lparam,  class=class="str">"cmt">// Parameter of class="type">long event type
                  class="kw">const class="type">class="kw">double &dparam, class=class="str">"cmt">// Parameter of class="type">class="kw">double event type
                  class="kw">const class="type">class="kw">string &sparam) class=class="str">"cmt">// Parameter of class="type">class="kw">string event type
  {
class=class="str">"cmt">//--- If it is a user event
   if(id>=CHARTEVENT_CUSTOM)
     {
     class=class="str">"cmt">//--- Exit, if trade is prohibited
     if(CheckTradingPermission()>class="num">0)
        class="kw">return;
     class=class="str">"cmt">//--- If it is a tick event

「新K线触发下的多品种信号巡检」

EA 在收到 CHARTEVENT_TICK 后先清挂单再跑信号,但真正按策略下单的逻辑被收在 CheckSignalsAndTrade() 里,靠新柱判断来节流,避免每跳都重算。 该函数用 for 循环扫 NUMBER_OF_SYMBOLS 个品种:遇到空符号名直接 continue 跳过;CheckNewBar(s) 返回 false 也跳过,只在确认出现新柱时才进入交易分支。 进入分支后第一道闸是 IsInTradeTimeRange(s),不在允许交易时段就当场平掉该品种持仓、删光挂单并 continue,这对黄金和美系货币对规避数据波动有一定概率上的保护作用,但外汇贵金属本身高杠杆高风险,时段过滤不等于风险消除。 时段内则依次取柱数据、跑 TradingBlock(s) 执行条件下单;之后按 Reverse[s] 开关决定移动哪类止损——开启反转就只拖挂单的止损,关闭则拖已开仓位的止损。这套结构你可以直接拷进 MT5 的 EA 模板里验证多品种轮询是否如预期只在新柱触发。

MQL5 / C++
if(lparam==CHARTEVENT_TICK)
  {
   class=class="str">"cmt">//--- Check the state of pending orders
   ManagePendingOrders();
   class=class="str">"cmt">//--- Check signals and trade according to them
   CheckSignalsAndTrade();
   class="kw">return;
  }
 }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Checks signals and trades based on New Bar event                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CheckSignalsAndTrade()
  {
class=class="str">"cmt">//--- Loop through all specified signals
  for(class="type">int s=class="num">0; s<NUMBER_OF_SYMBOLS; s++)
   {
   class=class="str">"cmt">//--- If trading this symbol is prohibited, exit
   if(Symbols[s]=="")
     class="kw">continue;
   class=class="str">"cmt">//--- If the bar is not new, move on to the following symbol
   if(!CheckNewBar(s))
     class="kw">continue;
   class=class="str">"cmt">//--- If there is a new bar
   else
     {
     class=class="str">"cmt">//--- If outwith the time range
     if(!IsInTradeTimeRange(s))
       {
       class=class="str">"cmt">//--- Close position
       ClosePosition(s);
       class=class="str">"cmt">//--- Delete all pending orders
       DeleteAllPendingOrders(s);
       class=class="str">"cmt">//--- Move on to the following symbol
       class="kw">continue;
       }
     class=class="str">"cmt">//--- Get bars data
     GetBarsData(s);
     class=class="str">"cmt">//--- Check conditions and trade
     TradingBlock(s);
     class=class="str">"cmt">//--- If position reversal if enabled
     if(Reverse[s])
       class=class="str">"cmt">//--- Pull up Stop Loss for pending order
       ModifyPendingOrderTrailingStop(s);
     class=class="str">"cmt">//--- If position reversal is disabled
     else
       class=class="str">"cmt">//--- Pull up Stop Loss
       ModifyTrailingStop(s);
     }
  }

◍ 一点提醒

这套多品种挂单框架的核心改动点其实就两个函数:TradingBlock() 和 ManagePendingOrders()。你若刚接触 MQL5,建议先把手里现成的函数跑通,再逐步加品种、换算法方案,别一上来就重写底层。 下面这段精简版挂单脚本可直接贴进 MT5 测一下:默认挂单距离 120 点、止损 125 点、止盈 150 点、手数 0.01,OnTick 里只下一轮单就置 done 防止重复。外汇和贵金属波动大、滑点不可控,回测能跑通不代表实盘概率同权,请先在策略测试器用历史数据验证。 框架只是骨架,真正决定盈亏的是你写在那两个函数里的决策逻辑。跑通代码后,下一步该想的不是「怎么下单」,而是「为什么这时候下」。

MQL5 / C++
class="macro">#include <Trade\Trade.mqh> CTrade Trade;
class=class="str">"cmt">//+------------------------------------------------------------------+
input class="type">int     inp_Dist    =  class="num">120;   class=class="str">"cmt">// 挂单距离(点数)
input class="type">int     inp_Stop    =  class="num">125;   class=class="str">"cmt">// SL(points)
input class="type">int     inp_Take    =  class="num">150;   class=class="str">"cmt">// TP(点数)
input class="type">class="kw">double  inp_Volume  = class="num">0.01;   class=class="str">"cmt">// 体积
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double distPend = inp_Dist*_Point;
class="type">class="kw">double distStop = inp_Stop*_Point;
class="type">class="kw">double distTake = inp_Take*_Point;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool   done     = class="kw">false;
class="type">class="kw">double ask;
class="type">class="kw">double bid;
class="type">class="kw">double levelSell;
class="type">class="kw">double levelBuy;
class="type">class="kw">double stopSell;
class="type">class="kw">double stopBuy;
class="type">class="kw">double takeSell;
class="type">class="kw">double takeBuy;
class="type">void OnTick()
{
   if(done)
      class="kw">return;
   ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
   levelBuy =NormalizeDouble(bid-distPend,_Digits);
   levelSell=NormalizeDouble(ask+distPend,_Digits);
   stopBuy   =NormalizeDouble(levelBuy -distStop,_Digits);
   stopSell  =NormalizeDouble(levelSell+distStop,_Digits);
   takeBuy   =NormalizeDouble(levelBuy +distTake,_Digits);
   takeSell  =NormalizeDouble(levelSell-distTake,_Digits);
   SellLimit();
   BuyLimit();
   done=true;
}
class="type">bool BuyLimit()
{
   class=class="str">"cmt">//class="kw">return(Trade.BuyLimit(inp_Volume,levelBuy ));
   class="kw">return(Trade.BuyLimit(inp_Volume,levelBuy ,_Symbol,stopBuy,takeBuy ));
}
class="type">bool SellLimit()
{
   class=class="str">"cmt">//class="kw">return(Trade.SellLimit(inp_Volume,levelSell));
   class="kw">return(Trade.SellLimit(inp_Volume,levelSell,_Symbol,stopSell,takeSell));
}
让小布替你跑这套多币巡检
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到挂单触发与时段冲突提示,把重复劳动交给小布,你专注决策。

常见问题

StartTrade 是开始布置限价单的服务器小时,StopOpenOrders 是停止布置新单的小时;前者管开闸,后者管不再新增挂单但已有单仍可触发。
MQL5 中限价单的手数不在可修改属性内,只能先 Delete 再按期望手数 Place 新单,否则手数维持原值。
挂单会追随价格移动,若设了止损位则基于挂单价格计算其价值,触发后按该动态位平仓。
小布盯盘品种页可接入 MT5 交易事件流,自动标出挂单删除与重建节点,便于对照 EA 注释字符串排查异常。
这是日内交易范式的收口逻辑,避免跨时段隔夜暴露,外汇贵金属杠杆高,时段边界强平可降低跳空风险概率。