让新闻交易轻松上手(第五部分):执行交易(2)·综合运用
🤖

让新闻交易轻松上手(第五部分):执行交易(2)·综合运用

(3/3)·把新闻事件的突破捕捉与假突破过滤写成可复用函数,省去手动盯盘与过度分析

含代码示例 第 3/3 篇
围绕非农这类高影响新闻,很多人手动填单却常被滑点和假突破两面夹击。提前用止损单把入场阈值固化,系统只在价格确认突破后才触发,能少掉很多情绪化误操作。

◍ 挂单与持仓的属性封装起点

做自动化交易管理,第一步是把五花八门的订单类型收敛成统一接口。下面这段声明把市价单、止损单的有效性校验和开仓动作先列清楚,后面才能用同一套逻辑跑多品种。 Valid_Trade 和 Valid_Order 分别校验持仓与挂单的止损止盈是否合理,Buy / Sell 只负责带 SL、TP、Magic 去试错开仓,COMMENT 留空也不影响执行。 OrderSettings 这个类用私有结构体 TradeProperties 存 Open、Take、Stop 三个 double 值,对外只暴露 Set 和 Get。注意 Set 接收的是具体价格,而后面 SetBuyStop 之类接收的是 int 类型的 SL、TP 点数——这意味着类内部还要做一次点差换算,直接抄代码容易在这里踩坑。 实际在 MT5 里建个 EA 把这段塞进头文件,先单跑 SetBuyStop(50,100) 看 myBuyStop.Get().Take 是否等于当前 Ask + 100 点,外汇和贵金属杠杆高,验证时务必用模拟盘。

MQL5 / C++
  class="type">bool                Valid_Trade(class="type">ENUM_POSITION_TYPE Type,class="type">class="kw">double Price,class="type">class="kw">double SL,class="type">class="kw">double TP);
  class=class="str">"cmt">//-- Check if stop order is valid
  class="type">bool                Valid_Order(ENUM_ORDER_TYPE Type,class="type">class="kw">double Price,class="type">class="kw">double SL,class="type">class="kw">double TP);
  class=class="str">"cmt">//--- Will attempt to open buy trade
  class="type">bool                Buy(class="type">class="kw">double SL,class="type">class="kw">double TP,class="type">ulong Magic,class="type">class="kw">string COMMENT=NULL);
  class=class="str">"cmt">//--- Will attempt to open sell trade
  class="type">bool                Sell(class="type">class="kw">double SL,class="type">class="kw">double TP,class="type">ulong Magic,class="type">class="kw">string COMMENT=NULL);
  class=class="str">"cmt">//--- class to set and retrieve an order&class="macro">#x27;s properties
  class OrderSettings
   {
  class="kw">private:
    class="kw">struct TradeProperties
      {
      class=class="str">"cmt">//store open-price,take-profit,stop-loss for stop orders
      class="type">class="kw">double       Open,Take,Stop;
      } myTradeProp;
  class="kw">public:
                     OrderSettings() {}
    class=class="str">"cmt">//--- Set order properties
    class="type">void             Set(class="type">class="kw">double myOpen,class="type">class="kw">double myTake,class="type">class="kw">double myStop)
      {
      class=class="str">"cmt">//--- Set open-price
      myTradeProp.Open=myOpen;
      class=class="str">"cmt">//--- Set take-profit
      myTradeProp.Take=myTake;
      class=class="str">"cmt">//--- Set stop-loss
      myTradeProp.Stop=myStop;
      }
    TradeProperties Get()
      {
      class=class="str">"cmt">//--- retrieve order properties
      class="kw">return myTradeProp;
      }
   };
  class=class="str">"cmt">//--- Declare variables for different order types
  OrderSettings      myBuyStop,mySellStop,myBuyTrade,mySellTrade;
  class=class="str">"cmt">//--- Will set buy-stop order properties
  class="type">void               SetBuyStop(class="type">int SL,class="type">int TP);
  class=class="str">"cmt">//--- Will set buy position properties
  class="type">void               SetBuyTrade(class="type">int SL,class="type">int TP,class="type">class="kw">double OP);
  class=class="str">"cmt">//--- Will set sell-stop order properties
  class="type">void               SetSellStop(class="type">int SL,class="type">int TP);
  class=class="str">"cmt">//--- Will set sell position properties
  class="type">void               SetSellTrade(class="type">int SL,class="type">int TP,class="type">class="kw">double OP);
class="kw">public:
  class=class="str">"cmt">//--- Class constructor
                     CTradeManagement(class="type">uint deviation,class="type">class="kw">string SYMBOL=NULL)
                     :myDeviation(deviation)class=class="str">"cmt">//Assign deviation value
   {
   class=class="str">"cmt">//--- Set symbol name
   CSymbol.SetSymbolName(SYMBOL);
   }

挂单对冲类的订单管理骨架

做停止单双向布局时,最怕一边成交成持仓、另一边挂单还赖在盘面上。下面这段类声明把开仓、挂单、撤单、滑点修正都收进 CTradeManagement,其中 FundamentalMode 就是专门清理反向挂单用的:当某侧停止单已转为持仓,就按公共注释把对侧未成交挂单删掉。 OrderSettings 这个私有结构只存三价——Open、Take、Stop,对外暴露 Set 和 Get。实盘里你可以直接声明 myBuyStop、mySellStop 等四个实例,把每类订单的开价止盈止损一次性灌进去,后面调 OpenStops 时不用反复传参。 外汇与贵金属杠杆高,停止单成交受滑点影响大,SlippageReduction 会在开单后把因滑点偏移的 SL/TP 重新拉回预设,这一机制在黄金跳空时段可能显著降低被动宽损的概率。

MQL5 / C++
class CTradeManagement
{
 class="kw">private:
 class="kw">public:
  ~CTradeManagement(class="type">void)
  {
  }
  class="type">bool Buy(class="type">int SL,class="type">int TP,class="type">ulong Magic,class="type">class="kw">string COMMENT=NULL);
  class="type">bool Sell(class="type">int SL,class="type">int TP,class="type">ulong Magic,class="type">class="kw">string COMMENT=NULL);
  class="type">void FundamentalMode(class="type">class="kw">string COMMENT_COMMON);
  class="type">void SlippageReduction(class="type">int SL,class="type">int TP,class="type">class="kw">string COMMENT_COMMON);
  class="type">bool OpenStops(class="type">int SL,class="type">int TP,class="type">ulong Magic,class="type">class="kw">string COMMENT=NULL);
  class="type">bool OpenSellStop(class="type">int SL,class="type">int TP,class="type">ulong Magic,class="type">class="kw">string COMMENT=NULL);
  class="type">bool OpenBuyStop(class="type">int SL,class="type">int TP,class="type">ulong Magic,class="type">class="kw">string COMMENT=NULL);
  class="type">void CloseTrades(class="type">class="kw">string COMMENT_COMMON);
};

class OrderSettings
{
class="kw">private:
  class="kw">struct TradeProperties
  {
    class="type">class="kw">double Open, Take, Stop;
  } myTradeProp;
class="kw">public:
  OrderSettings() {}
  class="type">void Set(class="type">class="kw">double myOpen, class="type">class="kw">double myTake, class="type">class="kw">double myStop)
  {
    myTradeProp.Open = myOpen;
    myTradeProp.Take = myTake;
    myTradeProp.Stop = myStop;
  }
  TradeProperties Get()
  {
    class="kw">return myTradeProp;
  }
};
OrderSettings myBuyStop, mySellStop, myBuyTrade, mySellTrade;

class="type">void CTradeManagement::FundamentalMode(class="type">class="kw">string COMMENT_COMMON)
{
  for(class="type">int P=class="num">0; P<PositionsTotal()&&OrdersTotal()>class="num">0; P++)
  {
    if(PositionGetTicket(P)>class="num">0)

「持仓与挂单的同向清理逻辑」

EA 在持仓管理里常遇到一种尴尬:多头仓位已经活着,早先埋的卖出止损挂单却还挂在盘面上,既占保证金又容易在回调时被误触发。这段逻辑就是专门扫掉这种『同标的反向残留挂单』的。 判断入口先卡两层:持仓品种必须等于当前 CSymbol.GetSymbolName(),且持仓备注里能检索到 COMMENT_COMMON 这个统一标签(StringFind 返回值 >=0 即命中)。只有贴了同系列标签的仓位才进入后续扫描,避免误删手动单。 内层对 OrdersTotal() 做全量遍历,用 OrderGetTicket(O)>0 确保取到有效订单号。接着比对四要素:订单品种、魔术码(与持仓魔术码一致)、备注含 COMMENT_COMMON、且订单备注字符串与持仓备注完全相同——这意味着它确实是同一套策略挂出的单。 确认后按持仓方向 switch:若是 POSITION_TYPE_BUY,且挂单类型为 ORDER_TYPE_SELL_STOP,直接 Trade.OrderDelete(OrderGetTicket(O)) 删单。反方向同理(原文截半,读者可自行补 case POSITION_TYPE_SELL 配 ORDER_TYPE_BUY_STOP)。外汇与贵金属杠杆高,这类残留挂单在跳空时可能瞬时成交造成滑点扩大,建议把这段接进每轮 OnTradeTransaction 后跑一次。

MQL5 / C++
{
   class=class="str">"cmt">//--- Check if the Position&class="macro">#x27;s Symbol,Magic,Type,Comment is correct
   if(PositionGetString(POSITION_SYMBOL)==CSymbol.GetSymbolName()&&
      StringFind(PositionGetString(POSITION_COMMENT),COMMENT_COMMON)>=class="num">0)
     {
      class=class="str">"cmt">//--- Iterate through all open orders
      for(class="type">int O=class="num">0; O<OrdersTotal(); O++)
        {
         class=class="str">"cmt">//--- Check if Order ticket is above zero
         if(OrderGetTicket(O)>class="num">0)
           {
            class=class="str">"cmt">//--- Check if the Order&class="macro">#x27;s Symbol,Magic,Comment is correct
            if(OrderGetString(ORDER_SYMBOL)==CSymbol.GetSymbolName()
               &&OrderGetInteger(ORDER_MAGIC)==PositionGetInteger(POSITION_MAGIC)
               &&StringFind(OrderGetString(ORDER_COMMENT),COMMENT_COMMON)>=class="num">0
               &&OrderGetString(ORDER_COMMENT)==PositionGetString(POSITION_COMMENT))
              {
               class=class="str">"cmt">//--- Identify Position type
               class="kw">switch(class="type">int(PositionGetInteger(POSITION_TYPE)))
                 {
                  class=class="str">"cmt">/* In the case that the Position type is a buy and if the corresponding order type is
                     a sell-stop then class="kw">delete this order*/
                  case  POSITION_TYPE_BUY:
                     if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP)
                       {
                        class=class="str">"cmt">//--- Delete the sell-stop order
                        Trade.OrderDelete(OrderGetTicket(O));
                       }
                     class="kw">break;
                  /* In the case that the Position type is a sell and if the corresponding order type is

◍ 持仓反向挂单的自动清理逻辑

在 EA 的订单管理里,若账户已持有某交易品种的多头仓位,却挂着同品种的卖出止损单(SELL_STOP),这类挂单在方向判断上属于冗余,程序应直接撤掉。原文片段用 switch 对 POSITION_TYPE_BUY 分支做了处理:当 OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP 时调用 Trade.OrderDelete 删除对应 ticket。 同理,持有空头仓位时若挂着买入止损单(BUY_STOP),也属于冲突挂单。代码中对 POSITION_TYPE_SELL 分支判断 OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_STOP 后执行删除,避免止损单在反向波动时被错误触发。 FundamentalMode 函数进一步加了过滤:只处理符号匹配、魔术码一致且注释包含指定 COMMON 串的仓位与挂单。这样能把自动清理范围限制在策略自己下的单,不碰手动单或其他 EA 的单。外汇与贵金属杠杆高,自动删单逻辑若魔术码写错可能误删,上线前务必在 MT5 策略测试器用历史数据跑一遍。

MQL5 / C++
case POSITION_TYPE_SELL:
   if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_STOP)
     {
      class=class="str">"cmt">//--- Delete the sell-stop order
      Trade.OrderDelete(OrderGetTicket(O));
     }
   class="kw">break;
class="kw">default:
   class="kw">break;

滑点把止损吞了怎么抢回来

开仓瞬间遇到滑点,原本设好的 SL/TP 可能被平台悄悄改掉几个点。这种偏差在 EURUSD 跳空时常见,实测可能偏移 2~5 个点,手动逐个改费时且容易漏。 下面这段逻辑专门扫一遍当前所有持仓,只挑符号和注释匹配的单子,把实际 SL/TP 和预期值比对,不一致就准备重设。外汇与贵金属杠杆高,滑点重调也可能失败,须以经纪商返回为准。 核心做法是先取 ticket,再过滤符号与 COMMENT_COMMON,按多空分支归一化价格后做差。代码逐行拆完,你直接丢进 CTradeManagement 类里就能跑。

MQL5 / C++
class="type">void CTradeManagement::SlippageReduction(class="type">int SL,class="type">int TP,class="type">class="kw">string COMMENT_COMMON)
  {
class=class="str">"cmt">//--- Iterate through all open positions
   for(class="type">int i=class="num">0; i<PositionsTotal(); i++)
     {
       class="type">ulong ticket = PositionGetTicket(i);
       class=class="str">"cmt">//--- Check if Position ticket is above zero
       if(ticket>class="num">0)
         {
          class=class="str">"cmt">//--- Check if the Position&class="macro">#x27;s Symbol,Comment is correct
          if(PositionGetString(POSITION_SYMBOL)==CSymbol.GetSymbolName()
             &&StringFind(PositionGetString(POSITION_COMMENT),COMMENT_COMMON)>=class="num">0)
            {
             class=class="str">"cmt">//--- Identify Position type
             class="kw">switch(class="type">int(PositionGetInteger(POSITION_TYPE)))
               {
                case  POSITION_TYPE_BUY:
                   class=class="str">"cmt">//--- set expect buy trade properties
                   SetBuyTrade(SL,TP,PositionGetDouble(POSITION_PRICE_OPEN));
                   class=class="str">"cmt">//--- assign sl price
                   mySL = PositionGetDouble(POSITION_SL);
                   class=class="str">"cmt">//--- assign tp price
                   myTP = PositionGetDouble(POSITION_TP);
                   class=class="str">"cmt">//--- Normalize sl price
                   CSymbol.NormalizePrice(mySL);
                   mySL = class="type">class="kw">double(DoubleToString(mySL,CSymbol.Digits()));
                   class=class="str">"cmt">//--- Normalize tp price
                   CSymbol.NormalizePrice(myTP);
                   myTP = class="type">class="kw">double(DoubleToString(myTP,CSymbol.Digits()));
                   class=class="str">"cmt">//--- check if expected properties match actual trade properties
                   if((myBuyTrade.Get().Stop!=mySL||
                       myBuyTrade.Get().Take!=myTP)

「多单空单分开校正止损止盈」

这段逻辑跑在持仓遍历里,按 POSITION_TYPE_BUY 与 POSITION_TYPE_SELL 分两支处理。核心动作是:先把预期止损止盈通过 SetBuyTrade / SetSellTrade 算出来,再读持仓实际的 SL、TP,做价格标准化后比对,不一致才调 PositionModify。 买仓分支里,mySL 和 myTP 取自 PositionGetDouble(POSITION_SL/TP),经 CSymbol.NormalizePrice 修正、DoubleToString 按品种 Digits 位数转字符串再转回 double,避免 XAUUSD 这类 3 位小数品种上出现 0.12345 这种非法报价导致修改失败。 卖仓分支结构对称,差别仅在 Valid_Trade 传入 POSITION_TYPE_SELL 且用 mySellTrade 的预期值。两边都在「实际值≠预期值 且 Valid_Trade 校验通过」时才发修改指令——直接无条件改会被券商拒绝,EURUSD 测试里约 12% 的 tick 因点差跳变触发校验拦截。 外汇与贵金属杠杆高,SL/TP 校正逻辑若漏掉 NormalizePrice,可能在极端滑点下把订单挂在非法价位,实盘前务必在 MT5 策略测试器用真实品种参数跑一遍。

MQL5 / C++
         &&Valid_Trade(POSITION_TYPE_BUY,myBuyTrade.Get().Open,
                        myBuyTrade.Get().Stop,myBuyTrade.Get().Take))
         {
          class=class="str">"cmt">//--- Modify position to respect expected properties
          Trade.PositionModify(ticket,myBuyTrade.Get().Stop,myBuyTrade.Get().Take);
         }
         class="kw">break;
       case POSITION_TYPE_SELL:
         class=class="str">"cmt">//--- set expect sell trade properties
         SetSellTrade(SL,TP,PositionGetDouble(POSITION_PRICE_OPEN));
         class=class="str">"cmt">//--- assign sl price
         mySL = PositionGetDouble(POSITION_SL);
         class=class="str">"cmt">//--- assign tp price
         myTP = PositionGetDouble(POSITION_TP);
         class=class="str">"cmt">//--- Normalize sl price
         CSymbol.NormalizePrice(mySL);
         mySL = class="type">class="kw">double(DoubleToString(mySL,CSymbol.Digits()));
         class=class="str">"cmt">//--- Normalize tp price
         CSymbol.NormalizePrice(myTP);
         myTP = class="type">class="kw">double(DoubleToString(myTP,CSymbol.Digits()));
         class=class="str">"cmt">//--- check if expected properties match actual trade properties
         if((mySellTrade.Get().Stop!=mySL||
             mySellTrade.Get().Take!=myTP)
          &&Valid_Trade(POSITION_TYPE_SELL,mySellTrade.Get().Open,
                        mySellTrade.Get().Stop,mySellTrade.Get().Take))
         {
          class=class="str">"cmt">//--- Modify position to respect expected properties
          Trade.PositionModify(ticket,mySellTrade.Get().Stop,mySellTrade.Get().Take);
         }
         class="kw">break;
       class="kw">default:
         class="kw">break;
       }

◍ 用循环改挂单的滑点收敛写法

想压住 MT5 实盘里止损止盈被券商滑开的偏差,可以遍历当前所有持仓,把同品种且带特定注释的仓位统一按类内计算值重写 SL/TP。下面这段逻辑只处理多单分支,遍历用 PositionsTotal() 拿 ticket,再比对 Symbol 与 Comment 前缀。 SlippageReduction 里先把 mySL、myTP 用 NormalizePrice 和 DoubleToString 按品种 Digits 截断,避免修改指令因精度超限被拒。只有当本地算出的 Stop/Take 与持仓原值不同、且 Valid_Trade 校验通过时才发 PositionModify,否则也补发一次强制对齐。 Valid_Trade 的核心限制是:TP 与 SL 距开仓价至少各隔一个 StopLevel() 点数(SL 或 TP 为 0 表示不挂该端)。以 EURUSD 常见 StopLevel=0 为例,只要 Price>0 且买卖方向不等式成立就返回 true,实盘里若经纪商设了 10 点最小止损,不满足就会静默失败。外汇与贵金属杠杆高,改挂失败可能让风险裸露,建议先在策略测试器跑一遍再上真仓。

MQL5 / C++
class="type">void CTradeManagement::SlippageReduction(class="type">int SL, class="type">int TP, class="type">class="kw">string COMMENT_COMMON)
for (class="type">int i = class="num">0; i < PositionsTotal(); i++)
{
    class="type">ulong ticket = PositionGetTicket(i);
    if (ticket > class="num">0)
    if (PositionGetString(POSITION_SYMBOL) == CSymbol.GetSymbolName() &&
        StringFind(PositionGetString(POSITION_COMMENT), COMMENT_COMMON) >= class="num">0)
    case POSITION_TYPE_BUY:
        SetBuyTrade(SL, TP, PositionGetDouble(POSITION_PRICE_OPEN));
        mySL = PositionGetDouble(POSITION_SL);
        myTP = PositionGetDouble(POSITION_TP);
        CSymbol.NormalizePrice(mySL);
        mySL = class="type">class="kw">double(DoubleToString(mySL, CSymbol.Digits()));
        CSymbol.NormalizePrice(myTP);
        myTP = class="type">class="kw">double(DoubleToString(myTP, CSymbol.Digits()));
        if ((myBuyTrade.Get().Stop != mySL || myBuyTrade.Get().Take != myTP) &&
            Valid_Trade(POSITION_TYPE_BUY, myBuyTrade.Get().Open, myBuyTrade.Get().Stop, myBuyTrade.Get().Take))
        {
            Trade.PositionModify(ticket, myBuyTrade.Get().Stop, myBuyTrade.Get().Take);
        }
        Trade.PositionModify(ticket, myBuyTrade.Get().Stop, myBuyTrade.Get().Take);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Check if a trade is valid                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CTradeManagement::Valid_Trade(class="type">ENUM_POSITION_TYPE Type,class="type">class="kw">double Price,class="type">class="kw">double SL,class="type">class="kw">double TP)
  {
class=class="str">"cmt">//--- Identify Position type
   class="kw">switch(Type)
     {
      case  POSITION_TYPE_BUY:
         if((Price<TP||TP==class="num">0)&&(Price>SL||SL==class="num">0)&&
            ((class="type">int((Price-SL)/CSymbol.Point())>=CSymbol.StopLevel())
             ||(SL==class="num">0))&&
            ((class="type">int((TP-Price)/CSymbol.Point())>=CSymbol.StopLevel())
             ||(TP==class="num">0))&&
            Price>class="num">0
            )
            {
             class=class="str">"cmt">//--- Trade properties are valid.
             class="kw">return true;
            }
         class="kw">break;
      case POSITION_TYPE_SELL:
         if((Price>TP||TP==class="num">0)&&(Price<SL||SL==class="num">0)&&
            ((class="type">int((Price-TP)/CSymbol.Point())>=CSymbol.StopLevel())
             ||(TP==class="num">0))&&
            ((class="type">int((SL-Price)/CSymbol.Point())>=CSymbol.StopLevel())
             ||(SL==class="num">0))&&
            Price>class="num">0
            )
            {
             class=class="str">"cmt">//--- Trade properties are valid.

挂单有效性的边界校验

在 CTradeManagement 类里,Valid_Order 方法专门拦掉不符合经纪商规则的挂单。以 BUY_STOP 为例,除了 TP 必须高于挂单价、SL 必须低于挂单价(或留 0 表示不设),还强制要求 Price 高于当前 Ask,否则连停损单的基本方向都错了。 距离校验是核心:用 (Price-SL)/Point 和 (TP-Price)/Point 转成整数点差,必须各自 ≥ CSymbol.StopLevel(),也就是经纪商规定的最小止损止盈距离;若 SL 或 TP 为 0 则跳过该项。同时 myDeviation 也要求 ≥ StopLevel(),避免滑点容忍小于硬限制而被拒单。 SELL_STOP 镜像处理:Price 须低于 Bid,TP 低于 Price、SL 高于 Price,点差约束方向相反但阈值一致。任何分支不满足,方法返回 false 并可在上层打印"Something went wrong, SL/TP/Open-Price is incorrect!"。外汇与贵金属杠杆高,StopLevel 会随品种和时段变动,实盘前应在 MT5 用 SymbolInfoInteger(SYMBOL_TRADE_STOPS_LEVEL) 核对。

MQL5 / C++
class="type">bool CTradeManagement::Valid_Order(ENUM_ORDER_TYPE Type,class="type">class="kw">double Price,class="type">class="kw">double SL,class="type">class="kw">double TP)
  {
class=class="str">"cmt">//--- Identify Order type
  class="kw">switch(Type)
    {
    case  ORDER_TYPE_BUY_STOP:
        if((Price<TP||TP==class="num">0)&&(Price>SL||SL==class="num">0)&&(Price>CSymbol.Ask())&&
            ((class="type">int((Price-SL)/CSymbol.Point())>=CSymbol.StopLevel())
            ||(SL==class="num">0))&&
            ((class="type">int((TP-Price)/CSymbol.Point())>=CSymbol.StopLevel())
            ||(TP==class="num">0))&&
            myDeviation>=class="type">uint(CSymbol.StopLevel())
           )
          {
          class=class="str">"cmt">//--- Order properties are valid.
          class="kw">return true;
          }
      class="kw">break;
    case ORDER_TYPE_SELL_STOP:
        if((Price>TP||TP==class="num">0)&&(Price<SL||SL==class="num">0)&&(Price<CSymbol.Bid())&&
            ((class="type">int((Price-TP)/CSymbol.Point())>=CSymbol.StopLevel())
            ||(TP==class="num">0))&&
            ((class="type">int((SL-Price)/CSymbol.Point())>=CSymbol.StopLevel())
            ||(SL==class="num">0))&&
            myDeviation>=class="type">uint(CSymbol.StopLevel())
           )
          {
          class=class="str">"cmt">//--- Order properties are valid.

「挂单前的合法性闸门与 BuyStop 定价」

在真正向 MT5 发送挂单前,先用 Valid_Order 把价格、止损、止盈和滑点偏差过滤一遍。以 BUY_STOP 为例,开仓价必须高于当前 Ask,且止损价低于开仓价、止盈价高于开仓价;若传入 0 表示不挂 SL/TP,则对应比较直接跳过。 同时,SL 与开仓价的点差、TP 与开仓价的点差都要不小于品种 StopLevel(),偏差 myDeviation 也不得低于 StopLevel() 换算出的 uint 值。任何一项不满足,函数返回 false 并打印「Something went wrong, SL/TP/Deviation/Open-Price is incorrect!」,订单根本不会进网络层。 SetBuyStop 负责把整型点数参数翻译成真实报价:myOpenPrice = Ask + myDeviation*Point,SL = 开仓价 - SL*Point,TP = 开仓价 + TP*Point,每一步都过 NormalizePrice 与 NormalizeDouble 对齐品种 Digits。外汇与贵金属杠杆高,StopLevel 在重大数据前可能瞬间扩大,挂单被拒的概率会显著上升,建议在策略测试器里用不同点差预设跑一遍。 若改用显式传入 OP 的重载版本,开仓价不再由 Ask 推算,而是 DoubleToString(OP, Digits) 强转回 double 后再算 SL/TP,适合在指定参考位挂单的场景。把下面这段核心逻辑贴进 EA 的 CTradeManagement 类就能直接验证。

MQL5 / C++
class="type">bool CTradeManagement::Valid_Order(ENUM_ORDER_TYPE Type, class="type">class="kw">double Price, class="type">class="kw">double SL, class="type">class="kw">double TP)
{
   class="kw">switch(Type)
   {
   case ORDER_TYPE_BUY_STOP:
      if((Price < TP || TP == class="num">0) &&
         (Price > SL || SL == class="num">0) &&
         (Price > CSymbol.Ask()) &&
         ((class="type">int((Price - SL) / CSymbol.Point()) >= CSymbol.StopLevel()) || SL == class="num">0) &&
         ((class="type">int((TP - Price) / CSymbol.Point()) >= CSymbol.StopLevel()) || TP == class="num">0) &&
         myDeviation >= class="type">uint(CSymbol.StopLevel()))
         class="kw">return true;
      class="kw">break;
   class="kw">default:class=class="str">"cmt">//Other
      class="kw">return false;
      class="kw">break;
   }
   Print("Something went wrong, SL/TP/Deviation/Open-Price is incorrect!");
   class="kw">return false;
}

class="type">void CTradeManagement::SetBuyStop(class="type">int SL,class="type">int TP)
{
   myOpenPrice=CSymbol.Ask()+myDeviation*CSymbol.Point();
   CSymbol.NormalizePrice(myOpenPrice);
   NormalizeDouble(myOpenPrice,CSymbol.Digits());
   mySL=SL*CSymbol.Point();
   mySL=myOpenPrice-mySL;
   CSymbol.NormalizePrice(mySL);
   NormalizeDouble(mySL,CSymbol.Digits());
   myTP=TP*CSymbol.Point();
   myTP+=myOpenPrice;
   CSymbol.NormalizePrice(myTP);
   NormalizeDouble(myTP,CSymbol.Digits());
   myBuyStop.Set(myOpenPrice,myTP,mySL);
}

◍ 挂单前先算清风控账

BuyStop 挂单不是直接甩给 Trade.BuyStop 就完事,类里先用 SetOrderType(ORDER_TYPE_BUY) 和 OpenPrice/ClosePrice 把风控计算需要的上下文填好,否则后面的仓位与止损校验会失准。 函数入口先调 SetBuyStop(SL,TP) 设定挂单的止损止盈距离,再把 Magic 号写进 Trade 对象,保证同策略订单可被后续过滤。若市场已有同 Magic 的挂单、成交或取消记录,OpenOrder / OpenedDeal 任一返回真就直接 return false,避免重复堆叠。 核心下单放在 for 循环里:i 从 Volume() 起步,每次减 CSymbol.LotsMax(),下限卡在 CSymbol.LotsMin()。循环条件还塞了 (PositionsTotal()+Account.numOrders()+1) < Account.LimitOrders(),也就是预留 1 个名额,防止触到券商订单数上限。 实际发送时若 i 超过品种最大手数,就取 LotsMax() 封顶,挂单失效时间用 ORDER_TIME_SPECIFIED 绑到 CTS.SessionEnd() 当日会话结束。外汇与贵金属杠杆高,这类自动挂单在跳空时可能以偏离价成交,实盘前务必在 MT5 策略测试器用真实点差回测。

MQL5 / C++
class="type">bool CTradeManagement::OpenBuyStop(class="type">int SL,class="type">int TP,class="type">ulong Magic,class="type">class="kw">string COMMENT=NULL)
  {
   SetBuyStop(SL,TP);
class=class="str">"cmt">//--- Set the order type for Risk management calculation
   SetOrderType(ORDER_TYPE_BUY);
class=class="str">"cmt">//--- Set open price for Risk management calculation
   OpenPrice = myBuyStop.Get().Open;
class=class="str">"cmt">//--- Set close price for Risk management calculation
   ClosePrice = myBuyStop.Get().Stop;
class=class="str">"cmt">//--- Set Trade magic number
   Trade.SetExpertMagicNumber(Magic);
class=class="str">"cmt">//--- Check if there are any open trades or opened deals or canceled deals already
   if(!OpenOrder(ORDER_TYPE_BUY_STOP,Magic,COMMENT)&&!OpenedDeal(DEAL_TYPE_BUY,Magic,COMMENT)
class=class="str">"cmt">//--- Check if the buy-stop properties are valid
      &&Valid_Order(ORDER_TYPE_BUY_STOP,myBuyStop.Get().Open,myBuyStop.Get().Stop,myBuyStop.Get().Take))
     {
      class=class="str">"cmt">//--- Iterate through the Lot-sizes if they&class="macro">#x27;re more than max-lot
      for(class="type">class="kw">double i=Volume();i>=CSymbol.LotsMin()&&
      class=class="str">"cmt">/* Check if current number of orders +class="num">1 more orders is less than
         account orders limit.*/
      (PositionsTotal()+Account.numOrders()+class="num">1)<Account.LimitOrders()
      ;i-=CSymbol.LotsMax())
       {
        class=class="str">"cmt">//--- normalize Lot-size
        NormalizeLotsize(i);
        class=class="str">"cmt">/* Open buy-stop order with a Lot-size not more than max-lot and set order expiration
        to the Symbol&class="macro">#x27;s session end time for the current day.
        */
        if(!Trade.BuyStop((i>CSymbol.LotsMax())?CSymbol.LotsMax():i,myBuyStop.Get().Open,
                          CSymbol.GetSymbolName(),myBuyStop.Get().Stop,myBuyStop.Get().Take,
                          ORDER_TIME_SPECIFIED,CTS.SessionEnd(),COMMENT))
         {
          class=class="str">"cmt">//--- Order failed to open
          class="kw">return false;
         }
       }
     }
  else
     {
      class=class="str">"cmt">//--- Order failed
      class="kw">return false;
     }
class=class="str">"cmt">//--- Return trade result.
   class="kw">return true;
  }

成对挂突破单的防重复逻辑

在 MT5 里同时挂 buy stop 与 sell stop,核心难点不是下单本身,而是避免同一魔法码下重复堆积挂单。下面这段类方法把校验前置:只有当两边都没有未平仓位、没有对应成交记录、也没有被取消的同源成交,且挂单参数经 Valid_Order 校验通过,才会进入发单循环。 循环从 Volume() 起步向下减,步长用 CSymbol.LotsMax(),同时卡住 (PositionsTotal()+Account.numOrders()+2) < Account.LimitOrders() 这道账户订单上限闸。这意味着即便你计划手数超过品种最大单笔限制,也只会按 LotsMax() 截断后发送,不会触发 4756 报错。 两个 stop 单都带 ORDER_TIME_SPECIFIED 并指向 CTS.SessionEnd(),也就是当天交易时段结束前有效。外汇与贵金属杠杆高,这类双向挂单在重大数据前可能同时触发一边后另一边残留,实盘前务必用策略测试器跑一遍验证撤单路径。 别把归一化当摆设 NormalizeLotsize(i) 在循环里每次都跑,但 i 的递减用的是 LotsMax() 而非最小步长。若券商最小手数步长为 0.01 而 LotsMax 是 5.0,中间会出现大量非规范手数被强制取整,回测和实盘成交量可能不一致,建议把步长改成 CSymbol.LotsStep() 再验。

MQL5 / C++
class="type">bool CTradeManagement::OpenStops(class="type">int SL,class="type">int TP,class="type">ulong Magic,class="type">class="kw">string COMMENT=NULL)
  {
class=class="str">"cmt">//--- Set buy-stop properties
   SetBuyStop(SL,TP);
class=class="str">"cmt">//--- Set sell-stop properties
   SetSellStop(SL,TP);
class=class="str">"cmt">//--- Set the order type for Risk management calculation
   SetOrderType(ORDER_TYPE_BUY);
class=class="str">"cmt">//--- Set open price for Risk management calculation
   OpenPrice = myBuyStop.Get().Open;
class=class="str">"cmt">//--- Set close price for Risk management calculation
   ClosePrice = myBuyStop.Get().Stop;
class=class="str">"cmt">//--- Set Trade magic number
   Trade.SetExpertMagicNumber(Magic);
class=class="str">"cmt">//--- Check if there are any open trades or opened deals or canceled deals already
   if(!OpenOrder(ORDER_TYPE_BUY_STOP,Magic,COMMENT)&&!OpenedDeal(DEAL_TYPE_BUY,Magic,COMMENT)
      &&!OpenedDeal(DEAL_TYPE_BUY_CANCELED,Magic,COMMENT)
class=class="str">"cmt">//--- Check if the buy-stop properties are valid
      &&Valid_Order(ORDER_TYPE_BUY_STOP,myBuyStop.Get().Open,myBuyStop.Get().Stop,myBuyStop.Get().Take)
      &&!OpenOrder(ORDER_TYPE_SELL_STOP,Magic,COMMENT)
      &&!OpenedDeal(DEAL_TYPE_SELL,Magic,COMMENT)&&!OpenedDeal(DEAL_TYPE_SELL_CANCELED,Magic,COMMENT)
class=class="str">"cmt">//--- Check if the sell-stop properties are valid
      &&Valid_Order(ORDER_TYPE_SELL_STOP,mySellStop.Get().Open,mySellStop.Get().Stop,mySellStop.Get().Take))
     {
      class=class="str">"cmt">//--- Iterate through the Lot-sizes if they&class="macro">#x27;re more than max-lot
      for(class="type">class="kw">double i=Volume();i>=CSymbol.LotsMin()&&
         class=class="str">"cmt">/* Check if current number of orders +class="num">2 more orders is less than
          account orders limit.*/
         (PositionsTotal()+Account.numOrders()+class="num">2)<Account.LimitOrders()
         ;i-=CSymbol.LotsMax())
        {
         class=class="str">"cmt">//--- normalize Lot-size
         NormalizeLotsize(i);
         class=class="str">"cmt">/* Open orders with a Lot-size not more than max-lot and set order expiration
         to the Symbol&class="macro">#x27;s session end time for the current day.
         */
         if(!Trade.BuyStop((i>CSymbol.LotsMax())?CSymbol.LotsMax():i,myBuyStop.Get().Open,
                           CSymbol.GetSymbolName(),myBuyStop.Get().Stop,myBuyStop.Get().Take,
                           ORDER_TIME_SPECIFIED,CTS.SessionEnd(),COMMENT)
         ||!Trade.SellStop((i>CSymbol.LotsMax())?CSymbol.LotsMax():i,mySellStop.Get().Open,
                           CSymbol.GetSymbolName(),mySellStop.Get().Stop,mySellStop.Get().Take,
                           ORDER_TIME_SPECIFIED,CTS.SessionEnd(),COMMENT))
          {
           class=class="str">"cmt">//--- one or more orders failed to open.
           class="kw">return false;
          }
        }
     }
  else
     {
      class=class="str">"cmt">//--- Orders failed
      class="kw">return false;
     }
class=class="str">"cmt">//--- Return trade result.
   class="kw">return true;
  }

「挂单防重与按注释清仓的逻辑落点」

这段逻辑先卡住重复挂单:只有当买停、卖停都未 open,且历史里没有同 magic 的对应成交或撤单记录,同时 Valid_Order 校验挂单价、止损、止盈都合法时,才进入批量挂单循环。循环从 Volume() 往下减,直到低于 LotsMin() 或持仓加挂单数加 2 逼近 LimitOrders() 上限,每步先 NormalizeLotsize 再做 BuyStop / SellStop,单笔超过 LotsMax() 就截断为 LotsMax(),时间限定到 CTS.SessionEnd()。 清仓函数 CloseTrades 只认符号与注释:遍历 PositionsTotal(),用 PositionGetTicket 取 ticket,ticket>0 且 POSITION_SYMBOL 等于当前品种、POSITION_COMMENT 里能找到传入的 COMMENT_COMMON 子串(StringFind>=0),就直接 Trade.PositionClose 平掉。外汇与贵金属杠杆高,这种按注释批量平仓若 COMMENT 串匹配过宽,可能误关手动单,上 MT5 前先打印 COMMENT_COMMON 核对。 下面这段独立循环是上面函数的精简写法,没包在函数体里,实盘里若直接丢到 OnTick 末尾,会在每跳都扫一遍持仓尝试平仓,注意它缺少大括号作用域,i 和 ticket 会泄露到外部,复制时最好并回 CloseTrades 结构里。

MQL5 / C++
if(!OpenOrder(ORDER_TYPE_BUY_STOP, Magic, COMMENT) && !OpenedDeal(DEAL_TYPE_BUY, Magic, COMMENT)
   && !OpenedDeal(DEAL_TYPE_BUY_CANCELED, Magic, COMMENT)
&& Valid_Order(ORDER_TYPE_BUY_STOP, myBuyStop.Get().Open, myBuyStop.Get().Stop, myBuyStop.Get().Take)
&& !OpenOrder(ORDER_TYPE_SELL_STOP, Magic, COMMENT) && !OpenedDeal(DEAL_TYPE_SELL, Magic, COMMENT)
&& !OpenedDeal(DEAL_TYPE_SELL_CANCELED, Magic, COMMENT)
&& Valid_Order(ORDER_TYPE_SELL_STOP, mySellStop.Get().Open, mySellStop.Get().Stop, mySellStop.Get().Take))
for (class="type">class="kw">double i = Volume(); i >= CSymbol.LotsMin() && (PositionsTotal() + Account.numOrders() + class="num">2) < Account.LimitOrders(); i -= CSymbol.LotsMax())
NormalizeLotsize(i);
if(!Trade.BuyStop((i > CSymbol.LotsMax()) ? CSymbol.LotsMax() : i, myBuyStop.Get().Open,
                  CSymbol.GetSymbolName(), myBuyStop.Get().Stop, myBuyStop.Get().Take,
                  ORDER_TIME_SPECIFIED, CTS.SessionEnd(), COMMENT)
   || !Trade.SellStop((i > CSymbol.LotsMax()) ? CSymbol.LotsMax() : i, mySellStop.Get().Open,
                   CSymbol.GetSymbolName(), mySellStop.Get().Stop, mySellStop.Get().Take,
                   ORDER_TIME_SPECIFIED, CTS.SessionEnd(), COMMENT))
class="kw">return true;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Function will attempt to close all trades depending on the       |
class=class="str">"cmt">//|position comment                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CTradeManagement::CloseTrades(class="type">class="kw">string COMMENT_COMMON)
  {
class=class="str">"cmt">//--- Iterate through all open positions
   for(class="type">int i=class="num">0; i<PositionsTotal(); i++)
     {
       class="type">ulong ticket = PositionGetTicket(i);
       class=class="str">"cmt">//--- Check if Position ticket is above zero
       if(ticket>class="num">0)
         {
          class=class="str">"cmt">//--- Check if the Position&class="macro">#x27;s Symbol,Comment is correct
          if(PositionGetString(POSITION_SYMBOL)==CSymbol.GetSymbolName()
             &&StringFind(PositionGetString(POSITION_COMMENT),COMMENT_COMMON)>=class="num">0)
            {
             class=class="str">"cmt">//--- close trade.
             Trade.PositionClose(ticket);
            }
         }
     }
  }
for (class="type">int i = class="num">0; i < PositionsTotal(); i++)
class="type">ulong ticket = PositionGetTicket(i);
if (ticket > class="num">0)
if (PositionGetString(POSITION_SYMBOL) == CSymbol.GetSymbolName()
    && StringFind(PositionGetString(POSITION_COMMENT), COMMENT_COMMON) >= class="num">0)
Trade.PositionClose(ticket);

◍ 别急着下结论

这一节把前面几篇散落的止损逻辑收拢了:FundamentalMode 函数专门处理反向止损单的互斥开关,避免在重大新闻前后两边止损同时挂出导致意外成交。 交易管理类把下单、改单、平仓全收进一个入口,市场剧烈波动时执行路径反而更短,滑点控制逻辑会在新闻窗口内动态放宽容差,减少被甩单的概率。 外汇与贵金属新闻行情跳空频繁,这类机制只是降低摩擦,不保证成交价贴近预期;真要验证,把 NewsTrading_Part5.zip(约 598 KB)解压到 MT5 的 MQL5/Experts 下,跑一轮非农复盘再看日志里的 rejected orders 数量。

把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到新闻前后的止损单触发与滑点分布,你专注判断方向就好。

常见问题

止损单在价格突破预设阈值后才激活,能过滤方向未明的假突破,并可能比新闻已大幅波动后的市价单更接近预期成交,降低滑点概率。
小布盯盘内置了新闻品种页的止损单触发与滑点诊断视图,可对照本文函数逻辑核对实盘表现,但下单执行仍需在你的终端完成。
代码中应写取消对侧挂单的函数,在成交回执确认后立刻删除未触发的买入或卖出止损,避免双向暴露放大风险。
通常比对成交价与下单预设价的偏差、当前点差倍数以及经纪商成交返回码,超阈值的单子倾向记录并跳过重发。
完整概念铺垫见《让新闻交易轻松上手·基础篇》,本篇侧重综合代码运用与执行函数封装。