本文讨论如何在跨平台智能交易系统中设置自定义停止价位。它还讨论了一种紧密相关的方法, 即随着时间的推移, 定义停止位的演化。·进阶篇
⚙️

本文讨论如何在跨平台智能交易系统中设置自定义停止价位。它还讨论了一种紧密相关的方法, 即随着时间的推移, 定义停止位的演化。·进阶篇

(2/3)· 当距离入场价的固定点数不够用时,如何用 CStop 动态算价并让停止位随行情演化

进阶 第 2/3 篇

不少跨平台 EA 开发者仍把止损止盈写死成入场价加减固定点数,遇到依据指标或 OHLC 动态算价的策略就卡壳。更麻烦的是,行情走出来之后停止位还按原样挂着,不会随时间移动。本文接着前篇的 CStop 地基,把自定义停止位和随时间演化的尾随逻辑一次讲清。

◍ 追踪止损的基类判定逻辑

这段基类代码把「多策略追踪止损」的合并判定拆得很干净:CTrailsBase::Check 会遍历所有 trail 实例,只挑出与目标模式(止损或止盈)匹配的活跃对象,再按买卖方向取极值。买单的止损取最大回撤位,卖单的止损取最小回撤位,反向对止盈处理,避免多个 trail 互相打架。 COrderStopBase::CheckTrailing 是实际挂单调用的入口。它先排除订单已关闭、挂起或双向都已锁死的情况,再分别算 sl/tp,最后用 IsStopLossValid / IsTakeProfitValid 做「是否真变了」的过滤——返回值等于原值时直接判无效,不触发无谓的 Modify。 外汇与贵金属杠杆高,追踪止损只降低回撤概率,不保证不被滑点击穿。把下面代码贴进 MT5 的 include 层,断点跑一遍 CheckTrailing,能看到 ret 在多个 trail 共存时如何收敛成单值。

MQL5 / C++
  class="kw">return m_trails.Check(symbol,type,entry_price,price,mode);
  }
class="type">class="kw">double CTrailsBase::Check(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_ORDER_TYPE type,class="kw">const class="type">class="kw">double entry_price,class="kw">const class="type">class="kw">double price,class="kw">const ENUM_TRAIL_TARGET mode)
  {
   if(!Active())
      class="kw">return class="num">0;  
   class="type">class="kw">double val=class="num">0.0,ret=class="num">0.0;
   for(class="type">int i=class="num">0;i<Total();i++)
    {
     CTrail *trail=At(i);
     if(!CheckPointer(trail))
       class="kw">continue;
     if(!trail.Active())
       class="kw">continue;
     class="type">int trail_target=trail.TrailTarget();
     if(mode!=trail_target)
       class="kw">continue;
     val=trail.Check(symbol,type,entry_price,price,mode);
     if((type==ORDER_TYPE_BUY && trail_target==TRAIL_TARGET_STOPLOSS) || (type==ORDER_TYPE_SELL && trail_target==TRAIL_TARGET_TAKEPROFIT))
     {
       if(val>ret || ret==class="num">0.0)
         ret=val;
     }       
     else if((type==ORDER_TYPE_SELL && trail_target==TRAIL_TARGET_STOPLOSS) || (type==ORDER_TYPE_BUY && trail_target==TRAIL_TARGET_TAKEPROFIT))
     {
       if(val<ret || ret==class="num">0.0)
         ret=val;
     }       
    }
   class="kw">return ret;
  }
class="type">bool COrderStopBase::CheckTrailing(class="type">void)
  {
   if(!CheckPointer(m_stop) || m_order.IsClosed() || m_order.IsSuspended() ||
     (m_stoploss_closed && m_takeprofit_closed))
      class="kw">return class="kw">false;
   class="type">class="kw">double stoploss=class="num">0,takeprofit=class="num">0;
   class="type">class="kw">string symbol=m_order.Symbol();
   ENUM_ORDER_TYPE type=m_order.OrderType();
   class="type">class="kw">double price=m_order.Price();
   class="type">class="kw">double sl = StopLoss();
   class="type">class="kw">double tp = TakeProfit();
   if(!m_stoploss_closed)
     stoploss=m_stop.CheckTrailing(symbol,type,price,sl,TRAIL_TARGET_STOPLOSS);
   if(!m_takeprofit_closed)
     takeprofit=m_stop.CheckTrailing(symbol,type,price,tp,TRAIL_TARGET_TAKEPROFIT);
   if(!IsStopLossValid(stoploss))
     stoploss=class="num">0;
   if(!IsTakeProfitValid(takeprofit))
     takeprofit=class="num">0;
   class="kw">return Modify(stoploss,takeprofit);
  }
class="type">bool COrderStopBase::IsStopLossValid(class="kw">const class="type">class="kw">double stoploss) class="kw">const
  {
   class="kw">return stoploss!=StopLoss();
  }
class="type">bool COrderStopBase::IsTakeProfitValid(class="kw">const class="type">class="kw">double takeprofit) class="kw">const
  {
   class="kw">return takeprofit!=TakeProfit();
  }
class="type">bool COrderStopBase::Modify(class="kw">const class="type">class="kw">double stoploss,class="kw">const class="type">class="kw">double takeprofit)
  {
   class="type">bool stoploss_modified=class="kw">false,takeprofit_modified=class="kw">false;
   if(stoploss>class="num">0 && takeprofit>class="num">0)
    {

「止损止盈的分支改写逻辑」

这段 COrderStopBase 的成员函数展示了订单止损止盈修改的三种分支处理:同时改、只改止损、只改止盈。若 stoploss 与 takeprofit 均大于 0,调用 ModifyStops 并同时置两个 modified 标志为 true;若仅其一大于 0,则只调用对应的单边修改函数。

函数最终返回 stoploss_modifiedtakeprofit_modified,意味着只要任一边修改成功即算本次操作有效。外汇与贵金属杠杆高,修改挂单止损止盈前务必确认点差与冻结距离,否则 ModifyStopLoss 可能返回 false 导致逻辑漏改。

ModifyStops 内部用 ModifyStopLoss(stoploss) && ModifyTakeProfit(takeprofit) 做与运算,任一边失败整体即失败——这和上层「或」返回形成对比,写 EA 时要清楚双边改失败不会回退已成功的那一边。

MQL5 / C++
if(ModifyStops(stoploss,takeprofit))
     {
     stoploss_modified=true;
     takeprofit_modified=true;
     }
   }
 else if(stoploss>class="num">0 && takeprofit==class="num">0)
    stoploss_modified=ModifyStopLoss(stoploss);
 else if(takeprofit>class="num">0 && stoploss==class="num">0)
    takeprofit_modified=ModifyTakeProfit(takeprofit);
 class="kw">return stoploss_modified || takeprofit_modified;
}
class="type">bool COrderStopBase::ModifyStops(class="kw">const class="type">class="kw">double stoploss,class="kw">const class="type">class="kw">double takeprofit)
  {
  class="kw">return ModifyStopLoss(stoploss) && ModifyTakeProfit(takeprofit);
  }

把止损推到入场价才算真正脱险

盈亏平衡指一笔交易的盈利收入刚好覆盖成本(点差,或点差加佣金),此时离场盈亏为零。对不收佣金的做市商账户,这个点通常就是入场价;带佣金的账户,多头盈亏平衡价要高于入场价,空头则低于入场价。 在 EA 里把止损设到入场价,这笔交易就被标记为盈亏平衡,最大风险降为零,但后续仍可能按离场信号平仓(往往已是盈利)。这里有两道硬约束:动态止损方向限制与经纪商最小距离。多头止损必须低于市价、空头必须高于市价,所以 EA 只能在浮盈状态下把止损移向平衡点;最小距离在改单时同样生效,刚入场往往不能直接拉到平衡点,行情得朝持仓方向走出足够距离才行。 设计平衡功能至少要盯两个输入:激活价(触发把止损移向平衡点的行情价位,需逐笔按入场价评估)和激活后的新止损(通常是入场价,佣金账户则偏移)。下面这段 MQL5 函数按流程图预先算激活、失活和新止损价:若现价 ≥ 初始要求的最低价位,就暂用新止损;再比当前止损更优才返回 true 并输出该价,否则返零。 外汇与贵金属为高杠杆高风险品种,盈亏平衡仅消除该笔最大亏损风险,不预示后续必然盈利,请务必在 MT5 策略测试器实测参数。

MQL5 / C++
class="type">class="kw">double CalculateBreakEven(class="type">class="kw">double open_price, class="type">class="kw">double current_price, class="type">class="kw">double commission_per_lot, class="type">class="kw">double tick_size, class="type">class="kw">double min_distance)
{
   class="type">class="kw">double activation = open_price + min_distance * class="num">2;          class=class="str">"cmt">// 触发价:入场价加两倍最小距离
   class="type">class="kw">double deactivation = open_price;                          class=class="str">"cmt">// 失活参考:原入场价
   class="type">class="kw">double new_stop = open_price + commission_per_lot * tick_size; class=class="str">"cmt">// 新止损:入场价加佣金折算跳数
   if(current_price >= activation)                           class=class="str">"cmt">// 现价达到触发条件
   {
      class="type">class="kw">double tentative_stop = new_stop;                      class=class="str">"cmt">// 暂定新止损
      if(tentative_stop < current_price - min_distance)      class=class="str">"cmt">// 新止损优于当前且合规
         class="kw">return tentative_stop;                              class=class="str">"cmt">// 返回计算止损价
   }
   class="kw">return class="num">0;                                                 class=class="str">"cmt">// 不满足条件返零
}

◍ 尾随止损的三道闸门与失活点

尾随止损本质是一种可反复触发的特殊盈亏平衡。普通盈亏平衡一般只用一次,而尾随止损在激活后能贯穿持仓剩余生命周期,按设定节奏不断把止损向上推。设计它至少要盯住三个变量:激活价格、激活后的新止损位、以及尾随步长(频率)。 和前两类止损不同,尾随的激活价可以落在图表上一段浮亏或“资金超支”的区域。也就是说,单子还在亏的时候,止损一样能被改、能被尾随;但若行情直接扫到有效止损,因为还没跨过盈亏平衡门槛,该亏还是亏。 第三个变量决定初始尾随启动后,当前止损到下一档的距离(以点值计),逻辑上和激活价很像,但每一阶段算出的新止损会随步长持续抬升。部分系统还有第四个因素——失活价格:触达后 EA 停止尾随,需逐笔评估。 实操上,MT5 里把激活价设得离进场太近,容易在毛刺行情里过早锁损;建议先用历史数据看某品种日均波幅,再把步长设为 ATR(14) 的 0.5~1 倍做对照测试。外汇与贵金属杠杆高,尾随只是缩小回撤概率,不承诺避免亏损。

「把止盈也做成会跑的线」

尾随通常挂在止损上,但止盈一样能尾随:价格越靠近原止盈位,就把止盈往同方向推。理论上这条线永远碰不到,可一旦遇到暴涨或跳空、行情比 EA 反应条件跑得还快,尾随止盈仍可能被扫。 要让尾随止盈真有用,得卡住两条:激活价必须落在当前止盈价内侧——多头的激活价低于止盈、空头的高于止盈;下一阶段触发价也得在原止盈内侧,且只在首次激活后才逐级生效。否则价格会先撞原止盈,仓位在尾随启动前就平了。 步长不能太窄。尾随越频繁,止盈被推得越勤,行情触及止盈的概率反而越低。外汇与贵金属杠杆高、跳空常见,这类机制在重大数据行情里可能直接失效,开 MT5 前先想清楚滑点承受度。

尾随止损类的骨架与触发逻辑

CTrailBase 作为所有尾随类的基类, 把激活价、步长、终点等参数都收进类成员。Set 方法本质就是类构造的替代入口, 你可以在自定义构造函数里直接调它, 省去外部逐个赋值的麻烦。 它内部持有一个品种管理器实例 (CSymbolManager), 任何计算前必须先刷新品种数据, 否则拿到的报价可能是上一根 K 线的残留值。激活价由 start、step、end 这些点数参数结合图表现价反推, 失活价用 m_end 算; 若 m_end 设为零, 尾随会一直跑到主交易平仓才停。 Check 方法是对外暴露的公有接口, 由调用方在需要更新止损/止盈时触发。通过 ENUM_TRAIL_TARGET 枚举区分目标: 尾随止损在每个阶段向趋势方向收紧, 尾随止盈则反向把价位推离现价。外汇和贵金属波动剧烈, 这类逻辑实盘前请在 MT5 策略测试器用历史数据验证参数敏感性。 下面这段基类声明值得直接贴进你的 MQL5 项目对照看, 重点留意 m_symbol_man 和 m_target 这两个成员如何贯穿后续计算。

MQL5 / C++
class CTrailBase : class="kw">public CObject
  {
class="kw">protected:
   class="type">bool                m_active;
   ENUM_TRAIL_TARGET m_target;
   class="type">class="kw">double              m_trail;
   class="type">class="kw">double              m_start;
   class="type">class="kw">double              m_end;
   class="type">class="kw">double              m_step;
   CSymbolManager    *m_symbol_man;
   CSymbolInfo       *m_symbol;
   CEventAggregator *m_event_man;
   CObject           *m_container;
class="kw">public:
                     CTrailBase(class="type">void);
                    ~CTrailBase(class="type">void);
   class="kw">virtual class="type">int       Type(class="type">void) class="kw">const {class="kw">return CLASS_TYPE_TRAIL;}
   class=class="str">"cmt">//--- 初始化
   class="kw">virtual class="type">bool      Init(CSymbolManager*,CEventAggregator*);
   class="kw">virtual CObject *GetContainer(class="type">void);
   class="kw">virtual class="type">void      SetContainer(CObject*);
   class="kw">virtual class="type">bool      Validate(class="type">void) class="kw">const;
   class=class="str">"cmt">//--- 取值和赋值
   class="type">bool              Active(class="type">void) class="kw">const;
   class="type">void              Active(class="kw">const class="type">bool);
   class="type">class="kw">double            End(class="type">void) class="kw">const;
   class="type">void              End(class="kw">const class="type">class="kw">double);
   class="type">void              Set(class="kw">const class="type">class="kw">double,class="kw">const class="type">class="kw">double,class="kw">const class="type">class="kw">double,class="kw">const class="type">class="kw">double);
   class="type">class="kw">double            Start(class="type">void) class="kw">const;
   class="type">void              Start(class="kw">const class="type">class="kw">double);
   class="type">class="kw">double            Step(class="type">void) class="kw">const;
   class="type">void              Step(class="kw">const class="type">class="kw">double);
   class="type">class="kw">double            Trail(class="type">void) class="kw">const;
   class="type">void              Trail(class="kw">const class="type">class="kw">double);
   class="type">int               TrailTarget(class="type">void) class="kw">const;
把停止位演化交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到自定义停止位与尾随触发的实时状态,你专注决策就好。

常见问题

若 m_stoploss 非零,CStop 会依据入场价用该成员算真实止损;仅当其为零时才调用 StopLossCustom,二者不会叠加。
可以,CTrail 类设计上贴近自定义停止位,盈亏平衡、尾随止损与尾随止盈都能作为随时间演变的停止位由它统一承载。
能,小布盯盘对应品种页已内置停止位诊断,自定义价与尾随触发状态可读,省去自己写面板。
当止损来自指标线、形态边界或 OHLC 计算结果时,用图表绝对价格比相对点数更直观且不易受点值变动干扰。
CStop 的自定义停止位多为入场时一次性算定,CTrail 侧重随 tick 或柱线推进重算,实现停止位移动。