通用智能交易系统:自定义追踪止损(第六章)·综合运用
(3/3)·从移动平均线追踪到单头寸独立止损,收尾整套交易引擎的模块化扩展思路
把追踪止损硬写进策略基类,是多数自研 EA 维护成本飙升的起点。当算法变体一多,基类的可读性会迅速崩坏,而交易逻辑反而被淹没。本篇作为系列收尾,只谈如何把它干净地剥离出去。
「把均线值直接焊死在止损位上」
CImpulse 策略里价格穿越均线就平仓,那只用了 CIndMovingAverage 的计算能力。这个类跟追踪止损类高度相似,差别仅在它不含头寸管理逻辑、也没有 Modify() 方法。CTrailing 反之,管仓位管得齐全,就是缺一套算均线的算法。把两边拼起来,就能得到一个「止损价 = 均线值」的追踪止损子类。 核心就在 Modify():取前一根收盘柱的均线输出 OutValue(1),多头时若均线值高于当前价就市价平掉,空头时若均线值低于当前价也平掉;否则只要当前止损不等于该均线值,就把它刷成新止损。之所以用 1 号索引而非 0 号,是因为当前柱均线实时跳动,用闭合的上一根才不会因为报价抖动反复改单。 Moving 成员被声明成指针,意味着你能在运行时挂任意 CIndMovingAverage 实例上去——换周期、换均线类型都不用改这个类。外汇与贵金属杠杆高,这类跟踪止损在单边里吃得到趋势,震荡里可能被来回扫,实盘前务必在 MT5 策略测试器跑一遍。
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 类,编译后分别对多空单挂单观察,止损线应该会按各自逻辑移动,而非统一平移。
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 基类已正确定义类型,否则那几行编译错误会卡住你。
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; }