如何开发各种类型的追踪止损并将其加入到EA中(基础篇)
📘

如何开发各种类型的追踪止损并将其加入到EA中(基础篇)

第 1/3 篇

◍ 把追踪止损塞进EA的几种路子

在 MT5 里做 EA,止损不能只挂在开仓那一刻。价格跑起来之后,把止损位动态往有利方向挪,才是降低回撤的核心动作。本文先理清一类最基础的写法:不依赖任何指标,只根据持仓浮盈和预设步长去推止损。 这类基础追踪止损通常用一个步长参数(如 TrailStep=30 点)控制刷新频率。每 tick 判断当前止损与现价距离,若浮盈足够覆盖步长,就把止损移动到「现价 - 步长」位置(多单反向同理)。注意:外汇与贵金属杠杆高,滑点可能让追踪单成交在预期外价位,实际回撤可能大于理论值。 下面这段是 MQL5 里最精简的手动追踪逻辑骨架,可直接拷进 EA 的 OnTick 里改参数验证。

「三个参数定义追踪止损的骨架」

做追踪止损前,先把三件事钉死:触发起始点(盈利多少点开始跟)、追踪步长(价格再朝盈利走多少点才推一次止损)、追踪距离(止损与现价的固定间隔)。这三个量能覆盖绝大多数策略,但不是非全用不可——若算法用指标值定位止损,追踪距离就可以被替代,此时止损不是直接贴在指标价上,而是按距离指标价多少个点来挂。 实际移动止损时,有三道硬检查必须过:止损价不能比当前价更靠近品种的最小止损级别 SYMBOL_TRADE_STOPS_LEVEL;止损价不能和已挂的相同,多单要高于原止损、空单要低于原止损;如果用了前面三个参数,还得验证参数条件成立。任何追踪算法本质都一样——喂入目标止损价,过检查,通过就搬止损。 简单追踪的运行节奏很直白:盈利触到起始点后,止损挂在距现价追踪距离的位置;之后价格每朝盈利方向走一个步长,止损就跟着价格挪,始终保持那段距离;直到价格反向,止损停在原地,被触到就平仓。外汇和贵金属杠杆高、滑点跳空频繁,这套机制只是帮你在反转时锁利,不保证每次都跑在最高点。 把止损逻辑封装成类而不是散函数,好处是能在一个 EA 里挂多个实例、各跑各的算法,也能分品种独立追踪,不用每次都重写得去抓句柄和当前品种仓位。代价是每类追踪都会向终端查一次持仓,若要做全局共享持仓列表的通用算法,开发成本往往不划算,本文不展开。基础文件建议丢进 \MQL5\Include\ 或其子目录,EA 里直接 include 就能调。

把追踪止损收进一个类里

在 MT5 的 \MQL5\Include\ 下建一个 Trailings 子目录,里面放 Trailings.mqh,定义类 CSimpleTrailing 并公开继承标准库 CObject。继承 CObject 不是写 EA 实例化的硬性要求,但要让追踪对象进链表、被标准库容器统一管理,就必须从它派生——CObject 给所有子类提供了成为链表元素的能力,还预留了一批虚方法待重写。 类里私有方法只有三个:CheckCriterion() 返回布尔值表示改止损的前置检查是否通过;ModifySL() 按给定值改持仓止损;StopLevel() 返回品种 StopLevel 属性。原先这些全是独立函数,现在封进基类,后续各类追踪止损都从它继承。 保护变量里最易踩坑的是点差乘数 m_spread_mlt。服务器把 StopLevel 设成 0 不代表没限制,而是浮动止损级,通常等于 2 倍或 3 倍点差。默认乘数为 2,若经纪商按三倍点差算,就调 SetSpreadMultiplier(3)。 GetStopLossValue() 声明为虚函数,因为不同追踪类型的止损算法不同,子类按需重写。公开区放变量的 getter/setter、构造与析构。两个构造函数:默认版绑定当前品种、magic 设 -1、追踪参数归零;参数化版接收品种名、magic 及起始值/步长/偏移量。 Run() 是主引擎,遍历活跃持仓,按品种和 magic 过滤,把止损挪到计算距离,每单结果汇进 res,全成功才返 true。这是为了在 EA 外层对改止损动作做额外把控。 拿 \MQL5\Experts\Examples\ 里的 Moving Average.mq5 改名叫 MovingAverageWithSimpleTrail.mq5 做验证。EURUSD M15、去年时段、不含延迟的回测里:不开启追踪与开启追踪相比,后者统计数据轻微改善,说明这套基础类在默认参数下可能略微抬升表现,但外汇/贵金属属高风险,样本结论不能外推为普适。 [CODE] //+------------------------------------------------------------------+

//Trailings.mqh
//Copyright 2024, MetaQuotes Ltd.
//[MQL5官方文档]

//+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "[MQL5官方文档] #property version "1.00" class CSimpleTrailing : public CObject { private: public: CSimpleTrailing(); ~CSimpleTrailing(); }; //+------------------------------------------------------------------+

//

//+------------------------------------------------------------------+ CSimpleTrailing::CSimpleTrailing() { } //+------------------------------------------------------------------+

//

//+------------------------------------------------------------------+

◍ 简单追踪止损类的骨架与私有接口

在 MT5 里写一套可复用的追踪止损逻辑,通常从定义一个继承自 CObject 的类开始。下面这段头文件结构把持仓追踪止损的核心参数和隐藏方法都摆在了明面上:交易品种、EA 魔术码、点值、报价小数位,以及偏移距、启动盈利点数和步进值,全部作为受保护成员交给子类或实例去填。 私有段里三个方法才是真正干活的地方:CheckCriterion 负责判断当前 tick 是否满足移动止损条件并返回布尔标志;ModifySL 按持仓 ticket 改 SL;StopLevel 把券商要求的止损最小距离换算成点数。注意原文里 CSimpleTrailing 的析构函数体为空,说明该类不持有需要手动释放的资源,挂上 EA 跑完直接回收即可。 m_offset 与 m_trail_start 的单位都是 point,不是价格。以 EURUSD 为例,m_point=0.00001、m_digits=5,若设 m_trail_start=200 即浮盈 20 点才启动追踪,外汇保证金波动剧烈、杠杆风险高,参数过紧可能频繁触发改单被拒。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
CSimpleTrailing::~CSimpleTrailing()
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 Trailing.mqh |
class=class="str">"cmt">//|                                         Copyright class="num">2024, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                                 [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#include <Object.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class of the position StopLoss simple trailing                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CSimpleTrailing : class="kw">public CObject
  {
class="kw">private:
class=class="str">"cmt">//--- check the criteria for modifying the StopLoss position and class="kw">return the flag
   class="type">bool            CheckCriterion(class="type">ENUM_POSITION_TYPE pos_type, class="type">class="kw">double pos_open, class="type">class="kw">double pos_sl, class="type">class="kw">double value_sl, class="type">MqlTick &tick);
class=class="str">"cmt">//--- modify StopLoss of a position by its ticket
   class="type">bool            ModifySL(const class="type">ulong ticket, const class="type">class="kw">double stop_loss);
class=class="str">"cmt">//--- class="kw">return StopLevel in points
   class="type">int             StopLevel(class="type">void);
class="kw">protected:
   class="type">class="kw">string          m_symbol;                                          class=class="str">"cmt">// trading symbol
   class="type">long            m_magic;                                           class=class="str">"cmt">// EA ID
   class="type">class="kw">double          m_point;                                           class=class="str">"cmt">// Symbol Point
   class="type">int             m_digits;                                          class=class="str">"cmt">// Symbol digits
   class="type">int             m_offset;                                          class=class="str">"cmt">// stop distance from price
   class="type">int             m_trail_start;                                     class=class="str">"cmt">// profit in points for launching trailing
   class="type">uint            m_trail_step;                                      class=class="str">"cmt">// trailing step
  };

「跟踪止损类的参数注入接口」

这段类声明暴露了跟踪止损模块的核心成员变量与对外设参方法。m_spread_mlt 作为点差倍数参与 StopLevel 回算,m_active 控制 trailing 是否生效,二者直接决定 EA 在 MT5 实盘里会不会动止损。 SetSymbol() 里有个细节值得注意:传入空串或 NULL 时自动回退到 ::Symbol() 当前图表品种,并同步抓取 SYMBOL_POINT 与 SYMBOL_DIGITS。这意味着你切换图表品种后若没显式调 SetSymbol,对象仍可能绑着旧品种的点值。 其余 Set 系方法(MagicNumber、StopLossOffset、TrailingStart、TrailingStep、SpreadMultiplier、Active)都是单行赋值,没有校验上下界。外汇与贵金属杠杆高、滑点突变频繁,offset 或 trail_step 设成 0 可能导致止损瞬间贴价被扫,参数落地前建议在策略测试器里先跑一轮。 GetStopLossValue 声明为虚函数,说明子类可重写止损计算逻辑;Symbol() 与 MagicNumber() 的 const 取值器让你在外部只读访问,不破坏封装。

MQL5 / C++
class="type">uint              m_spread_mlt;             class=class="str">"cmt">// spread multiplier for returning StopLevel value
class="type">bool              m_active;                 class=class="str">"cmt">// trailing activity flag
class=class="str">"cmt">//--- calculate and class="kw">return the StopLoss level of the selected position
   class="kw">virtual class="type">class="kw">double     GetStopLossValue(class="type">ENUM_POSITION_TYPE pos_type, class="type">MqlTick &tick);
class="kw">public:
class="kw">public:
class=class="str">"cmt">//--- set trailing parameters
   class="type">void              SetSymbol(const class="type">class="kw">string symbol)
                     {
                        this.m_symbol = (symbol==NULL || symbol=="" ? ::Symbol() : symbol);
                        this.m_point  =::SymbolInfoDouble(this.m_symbol, SYMBOL_POINT);
                        this.m_digits = (class="type">int)::SymbolInfoInteger(this.m_symbol, SYMBOL_DIGITS);
                     }
   class="type">void              SetMagicNumber(const class="type">long magic)      { this.m_magic = magic;      }
   class="type">void              SetStopLossOffset(const class="type">int offset)   { this.m_offset = offset;   }
   class="type">void              SetTrailingStart(const class="type">int start)     { this.m_trail_start = start; }
   class="type">void              SetTrailingStep(const class="type">uint step)      { this.m_trail_step = step;  }
   class="type">void              SetSpreadMultiplier(const class="type">uint value){ this.m_spread_mlt = value;  }
   class="type">void              SetActive(const class="type">bool flag)            { this.m_active = flag;      }
class=class="str">"cmt">//--- class="kw">return trailing parameters
   class="type">class="kw">string            Symbol(class="type">void)                          const { class="kw">return this.m_symbol;      }
   class="type">long              MagicNumber(class="type">void)                     const { class="kw">return this.m_magic;        }

把移动止损参数塞进类里

CSimpleTrailing 这个类把移动止损需要的几个量都收进了成员变量,外部只通过一组 const 取值函数去读,不直接碰私有字段。StopLossOffset 返回 m_offset,也就是挂止损时离当前价多少个 point;TrailingStart 给 m_trail_start,表示盈利达到多少 point 才启动追踪;TrailingStep 和 SpreadMultiplier 分别回传步长与点差倍数,默认 m_spread_mlt 在构造函数里写死为 2。 默认构造函数 CSimpleTrailing() 用 Symbol()、Point()、Digits() 把当前图表品种和精度接进来,magic 默认 -1,trailing_start、trailing_step、offset 全置 0,相当于先建一个不干活的对象。带参构造函数要求调用方显式传 symbol、magic、trail_start、trail_step、offset,内部把 m_spread_mlt 仍固定为 2,再用 SetSymbol 单独设品种——注意它没在初始化列表里顺手设,而是在函数体里调,说明 SetSymbol 可能还做了其它校验或赋值。 Run() 是真正触发追踪止损的入口,声明里注释写明了“launch trailing with StopLoss offset from the price”,即按 offset 距离挂止损再随价移动。外汇与贵金属波动大、点差跳变频繁,这套默认 2 倍点差缓冲在重大数据行情中可能不够,开 MT5 把 m_spread_mlt 提到 3~4 回测 EURUSD 的 H1 才容易看出差异。

MQL5 / C++
class="type">int StopLossOffset(class="type">void) const { class="kw">return this.m_offset; }
class="type">int TrailingStart(class="type">void) const { class="kw">return this.m_trail_start; }
class="type">uint TrailingStep(class="type">void) const { class="kw">return this.m_trail_step; }
class="type">uint SpreadMultiplier(class="type">void) const { class="kw">return this.m_spread_mlt; }
class="type">bool IsActive(class="type">void) const { class="kw">return this.m_active; }
class=class="str">"cmt">//--- launch trailing with StopLoss offset from the price
class="type">bool Run(class="type">void);
class=class="str">"cmt">//--- constructors
CSimpleTrailing() : m_symbol(::Symbol()), m_point(::Point()), m_digits(::Digits()),
                   m_magic(-class="num">1), m_trail_start(class="num">0), m_trail_step(class="num">0), m_offset(class="num">0), m_spread_mlt(class="num">2) {}
CSimpleTrailing(const class="type">class="kw">string symbol, const class="type">long magic,
                const class="type">int trailing_start, const class="type">uint trailing_step, const class="type">int offset);
class=class="str">"cmt">//--- destructor
~CSimpleTrailing() {}
};
CSimpleTrailing::CSimpleTrailing(const class="type">class="kw">string symbol, const class="type">long magic,
                                 const class="type">int trail_start, const class="type">uint trail_step, const class="type">int offset) : m_spread_mlt(class="num">2)
  {
   this.SetSymbol(symbol);
   this.m_magic     = magic;
   this.m_trail_start= trail_start;
   this.m_trail_step = trail_step;
   this.m_offset     = offset;
  }
  class="type">void SetSymbol(const class="type">class="kw">string symbol)

常见问题

把追踪止损收进一个独立类,OnTick只负责调用类方法;类内部维护止损价计算与修改,主逻辑更干净也好复用。
至少定义触发距离、回撤距离、步长三个参数;触发距离管何时启动,回撤距离管跟多近,步长管每次挪多少点。
可以,小布能读你品种页上的止损设置与波动数据,提示参数在近期行情里可能过紧或过松,省去手动回测。
参数随类实例走,多货币多策略互不干扰;改参数不用翻全局区,单元测试和复制到其他EA都更省事。
私有接口留计算新止损价、判断触发条件、执行修改订单即可;外部只调公开Update,避免乱碰持仓底层数据。