通用智能交易系统:自定义追踪止损(第六章)(基础篇)
📘

通用智能交易系统:自定义追踪止损(第六章)(基础篇)

第 1/3 篇

「把追踪止损从EA里抽出来」

MetaTrader 5 自带的追踪止损只能挂在客户端手动单上,对通过 EA 开出的持仓常常鞭长莫及。把追踪逻辑写成独立的通用智能交易系统,就能让任何 EA 下的单都吃到同一套移动止损规则。 Vasiliy Sokolov 在 2016 年 7 月 5 日发布的示例被下载 12 463 次、收藏 10 次,说明这套思路在 MT5 社区里需求很实。它的核心不是重画指标,而是用 EA 的 OnTick 去轮询持仓、按自定义距离重设 SL。 做验证时,先在 MT5 策略测试器里挂一个裸 EA 下市价单,再加载这个通用追踪止损 EA,观察持仓止损是否随价格反向移动。外汇与贵金属杠杆高,回测没跑过的参数直接上真仓可能放大回撤,先用模拟盘确认距离设置。

本篇要拆的追踪止损模块清单

这一节先把整篇要落地的追踪止损相关模块列清楚,方便后面逐个去 MT5 里对照验证。 涉及的内容包括:追踪止损函数的底层实现方式、标准化封装后的 CTrailing 类、它和策略其他模块的调用关系,以及一个经典函数的实际用例。 后续还会讲到如何把追踪止损接进 CImpulse 策略、在 EA 输入参数里暴露开关与阈值、用移动平均线做追踪基准、对单一头寸做独立追踪,以及新版交易引擎的改动点。 你打开 MT5 的 MetaEditor,建一个空 EA,就可以按这个清单逐项把模块补全,先认结构再写逻辑。

◍ 把追踪止损做成外部模块

通用智能交易系统本质是一组类构成的交易引擎,用户能在上面拼装自己的策略。前面几篇已经把 CStrategy 类里的核心模型讲清了:不论是内建在类里的逻辑,还是挂在类实例上的实现对象,基本功能都能顺着这套骨架扩。 这一节要加的新能力是追踪止损。它和之前那些模块不一样——算法对引擎来说是外部的,挂上或撤掉都不该干扰 CStrategy 原本的运转。 实现手段是组合(composition)而非继承:继续用额外的类去接新功能,严格贴着面向对象的原则走,不让追踪止损的逻辑渗进引擎主体。这样你在 MT5 里改止损策略时,核心引擎文件可以一行不动。

「把追踪止损从基类里拆出去」

追踪止损本质是一套保护头寸、把止损推到特定价位的算法。它不属于交易引擎的必需环节,只是用来简化过程的外挂功能;缺了它,策略类不该失效,加了也不该拖垮基类可读性。 直接塞进 CStrategy 或 CPosition 都会出问题。示例代码里 TrailingMode1 既能被策略调用返回新止损价,也能挂在持仓对象上自己跑,但这么做只是把一堆怪异又有用的算法从策略类转移到了持仓类,变量越积越多。

MQL5 / C++
<span style="class="type">color:rgb(class="num">0, class="num">0, class="num">255);">class </span>CStrategy
&nbsp;&nbsp; {
<span class="keyword">class="kw">public</span>:
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> TrailingMode1(CPosition* pos);
&nbsp;&nbsp; };
<span class="keyword">class="type">void</span> CMovingAverage::SupportBuy(<span class="keyword">class="kw">const</span> MarketEvent &amp;<span class="keyword">event</span>,CPosition *pos)
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> new_sl = TrailingMode1(pos);
&nbsp;&nbsp; pos.StopLossValue(new_sl);
&nbsp;&nbsp;}
<span class="keyword">class="type">void</span> CMovingAverage::SupportBuy(<span class="keyword">class="kw">const</span> MarketEvent &amp;<span class="keyword">event</span>,CPosition *pos)
&nbsp;&nbsp;{
&nbsp;&nbsp; pos.TrailingMode1();
&nbsp;&nbsp;}
逐行看:class CStrategy 里声明 TrailingMode1 接收持仓指针并返回 double 止损价;第一个 SupportBuy 里调用它拿到 new_sl 再写进持仓止损;第二个 SupportBuy 则让持仓自己调 TrailingMode1,两种都耦合进了原有类。 经典算法还要配距离极值多少点的参数,若把这些变量塞进基类,内部就会和大量跟踪止损参数搅在一起。正确做法是独立成单独类和文件,用时再挂接,外汇和贵金属波动大、滑点频繁,这种解耦能降低出错概率。

MQL5 / C++
<span style="class="type">color:rgb(class="num">0, class="num">0, class="num">255);">class </span>CStrategy
&nbsp;&nbsp; {
<span class="keyword">class="kw">public</span>:
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> TrailingMode1(CPosition* pos);
&nbsp;&nbsp; };
<span class="keyword">class="type">void</span> CMovingAverage::SupportBuy(<span class="keyword">class="kw">const</span> MarketEvent &amp;<span class="keyword">event</span>,CPosition *pos)
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> new_sl = TrailingMode1(pos);
&nbsp;&nbsp; pos.StopLossValue(new_sl);
&nbsp;&nbsp;}
<span class="keyword">class="type">void</span> CMovingAverage::SupportBuy(<span class="keyword">class="kw">const</span> MarketEvent &amp;<span class="keyword">event</span>,CPosition *pos)
&nbsp;&nbsp;{
&nbsp;&nbsp; pos.TrailingMode1();
&nbsp;&nbsp;}

把追踪止损收进一个基类里

追踪止损逻辑如果散落在策略主类里,会和 CStrategy、CPosition 这些核心对象搅在一起,后期调参和扩展都容易出岔子。把它单独抽成一个类,内部只存运行参数、把移动止损当成方法,结构就清爽了。 所有追踪算法都离不开两样东西:要改的那个头寸(用 CPosition 表示),以及一个统一名称的修改入口函数。我们把这两点提进基类 CTrailing,派生类各写各的算法,但对外都按同一个按钮触发。 基类里 Modify 是虚方法,默认返回 false,真实算法在子类重写;Copy 虚方法留作后面克隆实例用。参数不通过 Modify 传,而是在子类里用独立方法提前设好,调用 Modify 时所需数据已就位。 下面这段是基类骨架,含日志模块挂钩和最小接口。外汇、贵金属波动剧烈,追踪止损只降低回撤概率,不消除穿仓风险,上 MT5 跑之前先想清楚参数怎么注入。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 Trailing.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 <Object.mqh>
class="macro">#include "..\PositionMT5.mqh"
class="macro">#include "..\Logs.mqh"
class CPosition;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 追踪止损的基类                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CTrailing : class="kw">public CObject
  {
class="kw">protected:
   CPosition        *m_position;      class=class="str">"cmt">// 你要修改止损的头寸
   CLog             *Log;
class="kw">public:
                     CTrailing(class="type">void);
   class="type">void             SetPosition(CPosition *position);
   CPosition        *GetPosition(class="type">void);
   class="kw">virtual class="type">bool     Modify(class="type">void);
   class="kw">virtual CTrailing* Copy(class="type">void);
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 构造函数。接收日志模块                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
CTrailing::CTrailing(class="type">void)
  {
   Log=CLog::GetLog();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 追踪止损方法                                                        |
class=class="str">"cmt">//| 应在派生类中重写                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CTrailing::Modify(class="type">void)
  {
   class="kw">return false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 返回实例的副本                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
CTrailing* CTrailing::Copy(class="type">void)
{
   class="kw">return new CTrailing();
}

◍ 给追踪止损类挂上当前持仓指针

在写自己的追踪止损模块时,最容易被忽略的一步是「谁在被拖」。上面这段把 CPosition 指针存进类成员 m_position,外部每次调 SetPosition 就把正在处理的订单对象喂进来,GetPosition 再原样吐出,类内部其余逻辑就能直接读持仓的开仓价、方向、当前止损位。 实际跑起来,EA 在 OnTick 里遍历 PositionSelect 后,应当先 SetPosition(&pos) 再调 trailing 计算;漏掉这步,m_position 可能是上一张单甚至空指针,回测里会看到止损完全不跟随,胜率虚高。外汇与贵金属波动大,这类指针错挂会在实盘放大滑点风险。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 设置要修改止损的头寸                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CTrailing::SetPosition(CPosition *position)
  {
   m_position=position;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 返回要修改止损的头寸                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
CPosition *CTrailing::GetPosition(class="type">void)
  {
   class="kw">return m_position;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
逐行看:SetPosition 形参是 CPosition 指针,函数体只有一行赋值,把外部持仓地址塞给成员;GetPosition 无参,直接 return 成员指针,调用方拿到的就是同一对象,没有拷贝开销。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 设置要修改止损的头寸                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CTrailing::SetPosition(CPosition *position)
  {
   m_position=position;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 返回要修改止损的头寸                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
CPosition *CTrailing::GetPosition(class="type">void)
  {
   class="kw">return m_position;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

至少拆出持仓指针绑定、止损价计算、触发判断、执行平仓/改价四个模块,各自独立成函数或类便于复用。
基类只管止损规则与当前持仓指针,具体下单接口留给派生类;注意高风险,外汇贵金属波动大,先模拟盘验证。
小布可读取你的持仓与止损参数,自动比对价格走势标出未触发或误触发点,省去手动回放。
多半是指针未随新tick刷新,或持仓选择条件写死;应在每根K线或每次OnTick里重绑当前持仓。
纯逻辑抽离几乎不增开销;若频繁跨文件读写才可能影响,建议内存内计算、仅改价时调用交易接口。