通用智能交易系统:自定义追踪止损(第六章)·综合运用
🧩

通用智能交易系统:自定义追踪止损(第六章)·综合运用

(3/3)·从移动平均线追踪到单头寸独立止损,收尾整套交易引擎的模块化扩展思路

偏理论 第 3/3 篇

把追踪止损硬写进策略基类,是多数自研 EA 维护成本飙升的起点。当算法变体一多,基类的可读性会迅速崩坏,而交易逻辑反而被淹没。本篇作为系列收尾,只谈如何把它干净地剥离出去。

「把均线值直接焊死在止损位上」

CImpulse 策略里价格穿越均线就平仓,那只用了 CIndMovingAverage 的计算能力。这个类跟追踪止损类高度相似,差别仅在它不含头寸管理逻辑、也没有 Modify() 方法。CTrailing 反之,管仓位管得齐全,就是缺一套算均线的算法。把两边拼起来,就能得到一个「止损价 = 均线值」的追踪止损子类。 核心就在 Modify():取前一根收盘柱的均线输出 OutValue(1),多头时若均线值高于当前价就市价平掉,空头时若均线值低于当前价也平掉;否则只要当前止损不等于该均线值,就把它刷成新止损。之所以用 1 号索引而非 0 号,是因为当前柱均线实时跳动,用闭合的上一根才不会因为报价抖动反复改单。 Moving 成员被声明成指针,意味着你能在运行时挂任意 CIndMovingAverage 实例上去——换周期、换均线类型都不用改这个类。外汇与贵金属杠杆高,这类跟踪止损在单边里吃得到趋势,震荡里可能被来回扫,实盘前务必在 MT5 策略测试器跑一遍。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 TrailingMoving.mqh |
class=class="str">"cmt">//|                                                     Copyright class="num">2016, Vasiliy Sokolov. |
class=class="str">"cmt">//|                                                                 [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2016, Vasiliy Sokolov."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#include "Trailing.mqh"
class="macro">#include "..\Indicators\MovingAverage.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 基于移动平均的追踪止损算法设置头寸的止损为    
class=class="str">"cmt">//| MA值                                    
class=class="str">"cmt">//+------------------------------------------------------------------+
class CTrailingMoving : class="kw">public CTrailing
{
class="kw">public:
   class="kw">virtual class="type">bool       Modify(class="type">void);
   CIndMovingAverage* Moving;
   class="kw">virtual CTrailing* Copy(class="type">void);
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 将头寸的止损设置为MA值                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CTrailingMoving::Modify(class="type">void)
{
   if(CheckPointer(Moving) == POINTER_INVALID)
      class="kw">return false;
   class="type">class="kw">double value = Moving.OutValue(class="num">1);
   if(m_position.Direction() == POSITION_TYPE_BUY &&
      value > m_position.CurrentPrice())
      m_position.CloseAtMarket();
   else if(m_position.Direction() == POSITION_TYPE_SELL &&
      value < m_position.CurrentPrice())
      m_position.CloseAtMarket();
   else if(m_position.StopLossValue() != value)
      class="kw">return m_position.StopLossValue(value);
   class="kw">return false;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 返回CTrailingMoving实例的副本                        
class=class="str">"cmt">//+------------------------------------------------------------------+
CTrailing* CTrailingMoving::Copy(class="type">void)
{
   CTrailingMoving* mov = new CTrailingMoving();
   mov.Moving = Moving;
   class="kw">return mov;
}

◍ 多空分治的追踪止损重写

统一虚拟函数 Modify 能自动给每个头寸挂追踪止损,但碰到多空要用不同算法时,引擎侧就管不过来了。此时得在策略层重写 BuySupport 和 SellSupport,别再在构造函数里初始化默认追踪止损对象。 以某脉冲策略为例:多头走移动平均线追踪止损,空头走经典追踪止损,二者参数集互不干扰。代码里一行就把计算止损位的对象指针塞进 Moving,空头则直接写死极值距离 0.001(即 10 点,外汇黄金品种需留意点值高风险)。 完整实现见 ImpulseTrailingManual.mqh。开 MT5 把下面两段粘进自己的 CImpulseTrailing 类,编译后分别对多空单挂单观察,止损线应该会按各自逻辑移动,而非统一平移。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 根据移动平均管理多头头寸                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CImpulseTrailing::SupportBuy(class="kw">const MarketEvent &event,CPosition *pos)
{
   if(!IsTrackEvents(event))
      class="kw">return;
   if(pos.Trailing == NULL)
   {
      CTrailingMoving* trailing = new CTrailingMoving();   class=class="str">"cmt">// 新建基于移动平均的追踪止损实例
      trailing.Moving = GetPointer(this.Moving);           class=class="str">"cmt">// 绑定策略里的移动平均对象用于算止损位
      pos.Trailing = trailing;                             class=class="str">"cmt">// 挂到当前多头头寸上
   }
   pos.Trailing.Modify();                                  class=class="str">"cmt">// 执行止损修正
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 根据移动平均管理空头头寸                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CImpulseTrailing::SupportSell(class="kw">const MarketEvent &event,CPosition *pos)
{
   if(!IsTrackEvents(event))
      class="kw">return;
   if(pos.Trailing == NULL)
   {
      CTrailingClassic* trailing = new CTrailingClassic(); class=class="str">"cmt">// 新建经典追踪止损实例
      trailing.SetDiffExtremum(class="num">0.00100);                   class=class="str">"cmt">// 设极值距离 class="num">0.001(class="num">10 点)
      pos.Trailing = trailing;                             class=class="str">"cmt">// 挂到当前空头头寸上
   } 
   pos.Trailing.Modify();                                  class=class="str">"cmt">// 执行止损修正
}

第六版引擎里被修掉的几个坑

CStrategy 交易引擎从初版到现在改动很大,新函数和新模块不断加进来,但也带来和老版不兼容的断裂。前面几章因为兼容性 bug 把策略面板禁了,到第五版重新加回却跑不起来,直到第六版才把面板完全恢复。 面板自身有个显示错误:把“BuyOnly”模式列了两次,漏掉了“SellOnly”,第六版已修正。更早的版本里,你在面板上切交易模式,策略内部的模式其实没跟着变,这个隐性 bug 也在第六版堵上了。 第六版顺手加了一套模式切换的清算逻辑:切到 SellOnly 时,本策略所有的 Buy 市价单被平仓、Buy 挂单被删除;BuyOnly 反之清掉 Sell 挂单;选 Stop 则双边挂单全删。外汇和贵金属杠杆高,这种自动清仓逻辑可能在波动中触发滑点,实盘前建议在 MT5 策略测试器里跑一遍验证。 如果在 MT5 里跑这套引擎还撞上别的异常,直接反馈,修了的 bug 会进后续版本。

「把工具请下神坛」

这套交易引擎把追踪止损拆成了独立类,每个类自带标准化的 Modify 方法去改止损线,还塞进了算法所需的专属参数。它支持两种用法:要么在个性化策略里手动调,要么开自动模式让默认追止直接挂到头寸上,后者连策略都不用写。 个性化策略模式能玩出复杂逻辑,比如多空头寸套不同的追止规则。由于每个追止类都握有头寸完整信息,且修改方法随时能平仓,算法形式上可以任意变形——改止盈而不是止损也不会打断引擎运转。外汇与贵金属杠杆高,这类灵活是一把双刃剑,实盘前务必在 MT5 策略测试器里跑通。 下面这段经典追止的 Modify 实现,编译报错常出在 CTrailing 基类没挂类型、Trailing 指针未声明上,逐行看能少走弯路: bool CTrailingClassic::Modify(void) { if(CheckPointer(m_position)==POINTER_INVALID) { string text="Invalid position for current trailing-stop. Set position with 'SetPosition' method"; CMessage *msg=new CMessage(MESSAGE_WARNING,__FUNCTION__,text); Log.AddMessage(msg); return false; } if(m_diff_extremum<=0.0) { string text="Set points trailing-stop with 'SetDiffExtremum' method"; CMessage *msg=new CMessage(MESSAGE_WARNING,__FUNCTION__,text); Log.AddMessage(msg); return false; } double extremum=FindExtremum(m_position); if(extremum == 0.0)return false; double n_sl = 0.0; if(m_position.Direction()==POSITION_TYPE_BUY) n_sl=extremum-m_diff_extremum; else n_sl=extremum+m_diff_extremum; if(n_sl!=m_position.StopLossValue()) return m_position.StopLossValue(n_sl); return false; } 开头先校验持仓指针是否有效,无效就写警告日志并退出;接着查极值差 m_diff_extremum 有没有设,没设也退出。FindExtremum 拿到极值为 0 同样放弃。买仓新止损 = 极值减差,卖仓 = 极值加差,和原止损不同才调用 StopLossValue 去改。复制进 MT5 前,确认 PositionMT5.mqh 里 CTrailing 基类已正确定义类型,否则那几行编译错误会卡住你。

MQL5 / C++
class="type">bool CTrailingClassic::Modify(class="type">void)
  {
  if(CheckPointer(m_position)==POINTER_INVALID)
    {
    class="type">class="kw">string text="Invalid position for current trailing-stop. Set position with &class="macro">#x27;SetPosition&class="macro">#x27; method";
    CMessage *msg=new CMessage(MESSAGE_WARNING,__FUNCTION__,text);
    Log.AddMessage(msg);
    class="kw">return false;
    }
  if(m_diff_extremum<=class="num">0.0)
    {
    class="type">class="kw">string text="Set points trailing-stop with &class="macro">#x27;SetDiffExtremum&class="macro">#x27; method";
    CMessage *msg=new CMessage(MESSAGE_WARNING,__FUNCTION__,text);
    Log.AddMessage(msg);
    class="kw">return false;
    }
  class="type">class="kw">double extremum=FindExtremum(m_position);
  if(extremum == class="num">0.0)class="kw">return false;
  class="type">class="kw">double n_sl = class="num">0.0;
  if(m_position.Direction()==POSITION_TYPE_BUY)
    n_sl=extremum-m_diff_extremum;
  else
    n_sl=extremum+m_diff_extremum;
  if(n_sl!=m_position.StopLossValue())
    class="kw">return m_position.StopLossValue(n_sl);
  class="kw">return false;
  }
把模块验证交给小布盯盘
这些追踪止损类与引擎的交互结构,小布盯盘的 AIGC 已内置诊断视图,打开对应品种页即可对照自己的 EA 模块划分是否合理,你专注决策而非翻代码。

常见问题

追踪止损对交易引擎是外部功能,缺失不应影响基类运作;放入基类会让多种算法拖垮可读性与扩展性,独立类通过组合挂载更合理。
前者以均线位置为移动参考,对趋势过滤更敏感;后者依赖价格极值与止损间距,偏向纯点位保护,两者可独立挂载互不影响。
倾向适合,因为它解耦了头寸与全局引擎,每个持仓可挂不同算法,但需注意参数配置分散带来的回测复杂度。
目前小布盯盘提供的是模块划分诊断与范例对照,不直接解析用户源码;你可参照文中组合模式自行比对,高危外汇贵金属请先模拟验证。
多为基类接口留出组合挂载点、头寸类去除算法耦合、设置面板支持外部参数注入,具体以本文第三节清单为准。