如何开发各种类型的追踪止损并将其加入到EA中·综合运用
📘

如何开发各种类型的追踪止损并将其加入到EA中·综合运用

第 3/3 篇

◍ 把指标句柄塞进追踪基类

做指标类追踪止损,第一步不是写具体指标,而是先抽一个基类把『取数』这件事统一掉。每个指标追踪子类都要面对货币对、周期、数据索引和句柄这几个相同变量,如果散落在各个派生类里重复写,后期改一个 CopyBuffer 调用就得全工程搜。 基类 CTrailingByInd 直接继承前面的 CSimpleTrailing,等于白捡了简单追踪的全部能力,只额外补三样东西:受保护区里放 timeframe / handle / data_index 等公共变量;一个通过句柄拿指标值的通用方法;以及一个重写的虚方法 GetStopLossValue。公共区则暴露设置周期、数据索引的 setter 和对应的 getter。 默认构造函数里给父类传 magic=-1(覆盖所有仓位)和零追踪参数,周期取当前图表;参数化构造函数允许外部传入具体货币对和周期。析构函数必须释放指标句柄并把 m_handle 置为 INVALID_HANDLE,否则 MT5 会残留指标实例吃内存。 GetDataInd 内部调用 CopyBuffer() 按索引取单根柱值,成功就返回数值,失败打日志并返回 EMPTY_VALUE。因为 CopyBuffer 不挑指标类型,这个方法在任意子类里都能原样复用。GetStopLossValue 在父类层面只按价格算偏移;在基类里若没拿到指标数据就返回当前价,这样止损不会被设到非法价位(受 StopLevel 限制),只是暂时不移动。 外汇与贵金属杠杆高、滑点跳空频繁,这类基于指标的移动止损在重大数据行情中可能连续数根柱取不到值,回测和实盘表现会有偏差,上 MT5 前先用策略测试器跑一遍极端点差环境。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Base class of indicator-based trailings                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CTrailingByInd : class="kw">public CSimpleTrailing
  {
class="kw">protected:
   ENUM_TIMEFRAMES   m_timeframe;          class=class="str">"cmt">// indicator timeframe
   class="type">int               m_handle;             class=class="str">"cmt">// indicator handle
   class="type">uint              m_data_index;         class=class="str">"cmt">// indicator data bar
   class="type">class="kw">string            m_timeframe_descr;    class=class="str">"cmt">// timeframe description
class=class="str">"cmt">//--- class="kw">return indicator data
   class="type">class="kw">double            GetDataInd(class="type">void) const { class="kw">return this.GetDataInd(this.m_data_index); }

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=class="str">"cmt">//--- set parameters
   class="type">void              SetTimeframe(const ENUM_TIMEFRAMES timeframe)
                     {
                        this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe);
                        this.m_timeframe_descr=::StringSubstr(::EnumToString(this.m_timeframe), class="num">7);
                     }
   class="type">void              SetDataIndex(const class="type">uint index)                { this.m_data_index=index;    }

class=class="str">"cmt">//--- class="kw">return parameters
   ENUM_TIMEFRAMES   Timeframe(class="type">void) const { class="kw">return this.m_timeframe;    }

跟踪类里的时间框架与句柄初始化

CTrailingByInd 这个类把「跟哪个周期、取指标第几号缓冲、句柄怎么管」全部塞进了构造函数。默认构造函数直接吃当前图表:m_timeframe 取 ::Period(),m_data_index 写死为 1,m_timeframe_descr 用 StringSubstr(EnumToString(Period()),7) 把 PERIOD_ 前缀砍掉只留后面那段。 带参构造函数允许你外部传 symbol、timeframe、magic、trail_start、trail_step、trail_offset,其中 m_data_index 仍是 1,m_handle 先置 INVALID_HANDLE,随后调 this.SetTimeframe(timeframe) 完成周期绑定。 析构函数是实打实要释放资源的:若 m_handle 不等于 INVALID_HANDLE,就调 ::IndicatorRelease(this.m_handle) 并重置句柄。MT5 里忘记这步,切换品种或重载 EA 时指标句柄泄漏会越积越多。 GetDataInd(const int index) 只给了声明没给实现,签名表明它是按时间序列索引取指标值——你后续验证时重点看 index 越界返回的是什么。

MQL5 / C++
class="type">uint              DataIndex(class="type">void)               const { class="kw">return this.m_data_index;    }
class="type">class="kw">string            TimeframeDescription(class="type">void)    const { class="kw">return this.m_timeframe_descr; }

class=class="str">"cmt">//--- class="kw">return indicator data from the specified timeseries index
class="type">class="kw">double            GetDataInd(const class="type">int index) const;

class=class="str">"cmt">//--- constructors
                  CTrailingByInd(class="type">void) : CSimpleTrailing(::Symbol(), -class="num">1, class="num">0, class="num">0, class="num">0), m_timeframe(::Period()),
                                       m_handle(INVALID_HANDLE), m_data_index(class="num">1), m_timeframe_descr(::StringSubstr(::EnumToString(::Period()), class="num">7)) {}

                  CTrailingByInd(const class="type">class="kw">string symbol, const ENUM_TIMEFRAMES timeframe, const class="type">long magic, const class="type">int trail_start, const class="type">uint trail_step, const class="type">int trail_offset) :
                    CSimpleTrailing(symbol, magic, trail_start, trail_step, trail_offset), m_data_index(class="num">1), m_handle(INVALID_HANDLE)
                    {
                     this.SetTimeframe(timeframe);
                    }
class=class="str">"cmt">//--- destructor
                 ~CTrailingByInd(class="type">void)
                    {
                     if(this.m_handle!=INVALID_HANDLE)
                       {
                        ::IndicatorRelease(this.m_handle);
                        this.m_handle=INVALID_HANDLE;
                       }
                    }
};

「用指标缓冲区算动态止损位」

把指标值当成移动止损的锚点,核心就在 GetDataInd() 与 GetStopLossValue() 这两个方法里。前者先判句柄是否有效,无效直接吐 EMPTY_VALUE 并在日志报错;有效则用 CopyBuffer 按索引取 1 个值到数组,失败同样回 EMPTY_VALUE,成功返回 array[0]。 GetStopLossValue() 拿到指标值后按持仓方向算止损:买仓用 data 减偏移点数,卖仓用 data 加偏移点数;若指标取空则退回到当前 bid/ask。m_offset 与 m_point 的乘积就是实际价格缓冲,外汇与贵金属杠杆高,参数设错可能频繁触发止损。 下面这段可直接贴进 EA 的跟踪止损类里验证,重点看 CopyBuffer 的返回值判断——很多自行改写的代码漏了这一步,导致句柄异常时返回 0 而非 EMPTY_VALUE,进而把止损钉死在 0 价。

MQL5 / C++
class="type">class="kw">double CTrailingByInd::GetDataInd(const class="type">int index) const
  {
class=class="str">"cmt">//--- if the handle is invalid, report this and class="kw">return "empty value"
   if(this.m_handle==INVALID_HANDLE)
     {
       ::PrintFormat("%s: Error. Invalid handle",__FUNCTION__);
       class="kw">return EMPTY_VALUE;
     }
class=class="str">"cmt">//--- get the value of the indicator buffer by the specified index
   class="type">class="kw">double array[class="num">1];
   ::ResetLastError();
   if(::CopyBuffer(this.m_handle, class="num">0, index, class="num">1, array)!=class="num">1)
     {
       ::PrintFormat("%s: CopyBuffer() failed. Error %d", __FUNCTION__, ::GetLastError());
       class="kw">return EMPTY_VALUE;
     }
class=class="str">"cmt">//--- class="kw">return the received value
   class="kw">return array[class="num">0];
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Calculate and class="kw">return the StopLoss level of the selected position |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CTrailingByInd::GetStopLossValue(class="type">ENUM_POSITION_TYPE pos_type, class="type">MqlTick &tick)
  {
class=class="str">"cmt">//--- get the indicator value as a level for StopLoss
   class="type">class="kw">double data=this.GetDataInd();
     
class=class="str">"cmt">//--- calculate and class="kw">return the StopLoss level depending on the position type
   class="kw">switch(pos_type)
     {
      case POSITION_TYPE_BUY  :  class="kw">return(data!=EMPTY_VALUE ? data - this.m_offset * this.m_point : tick.bid);
      case POSITION_TYPE_SELL :  class="kw">return(data!=EMPTY_VALUE ? data + this.m_offset * this.m_point : tick.ask);
      class="kw">default                 :  class="kw">return class="num">0;
     }
  }

◍ 用 SAR 柱子值拖着止损走

在 Trailings.mqh 里写 SAR 追踪止损类,第一件事是让它继承指标追踪基类 CTrailingByInd,否则拿不到指标句柄和通用的追踪框架。私有段只放两个参数变量:m_sar_step 和 m_sar_max,对应 SAR 的步长和上限,对外用 Set / Get 方法暴露。 默认构造函数会在当前品种和周期上建指标,步长写死 0.02、上限 0.2,追踪距离先填 0;参数化构造函数则把 symbol、周期、step、max 全从外部灌进来再 Initialize()。 实测时把终端自带的 ExpertMACD.mq5 另存为 ExpertMACDWithTrailingBySAR.mq5,加三处代码:包含头文件、声明类实例、在 OnInit 初始化、在 OnTick 调追踪。编译后测试器跑默认参数,持仓止损严格贴着 SAR 第一根柱子的值移动,说明类逻辑通了。 外汇和贵金属波动大,SAR 在震荡市会频繁反转,这类追踪止损可能连续触发、磨损本金,上真仓前先在 MT5 策略测试器用历史数据验一遍。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Parabolic SAR position StopLoss trailing class                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CTrailingBySAR : class="kw">public CTrailingByInd
  {
class="kw">private:
   class="type">class="kw">double            m_sar_step;           class=class="str">"cmt">// Parabolic SAR Step parameter
   class="type">class="kw">double            m_sar_max;            class=class="str">"cmt">// Parabolic SAR Maximum parameter
class="kw">public:
class=class="str">"cmt">//--- set Parabolic SAR parameters
   class="type">void              SetSARStep(const class="type">class="kw">double step)                  { this.m_sar_step=(step<class="num">0.0001 ? class="num">0.0001 : step);   }
   class="type">void              SetSARMaximum(const class="type">class="kw">double max)                { this.m_sar_max =(max <class="num">0.0001 ? class="num">0.0001 : max);    }

class=class="str">"cmt">//--- class="kw">return Parabolic SAR parameters
   class="type">class="kw">double            SARStep(class="type">void)            const { class="kw">return this.m_sar_step;                    }
   class="type">class="kw">double            SARMaximum(class="type">void)         const { class="kw">return this.m_sar_max;                     }

class=class="str">"cmt">//--- create Parabolic SAR indicator and class="kw">return the result
   class="type">bool              Initialize(const class="type">class="kw">string symbol, const ENUM_TIMEFRAMES timeframe, const class="type">class="kw">double sar_step, const class="type">class="kw">double sar_maximum);
class=class="str">"cmt">//--- constructors
                     CTrailingBySAR() : CTrailingByInd(::Symbol(), ::Period(), -class="num">1, class="num">0, class="num">0, class="num">0)
                     {
                       this.Initialize(this.m_symbol, this.m_timeframe, class="num">0.02, class="num">0.2);
                     }

SAR trailing 类的构造与句柄初始化

基于抛物线 SAR 的 trailing 止损封装,核心在 CTrailingBySAR 的带参构造:传入交易品种、周期、magic 号,以及 sar_step、sar_maximum 两个 SAR 参数,还有 trail_start、trail_step、trail_offset 三组移动止损触发距离。构造内直接调 Initialize,把参数落进成员并创建指标句柄。 Initialize 里先用 SetSymbol / SetTimeframe / SetSARStep / SetSARMaximum 固化配置,再调 iSAR() 拿句柄。iSAR 的入参顺序固定为(品种,周期,步长,最大值),若返回 INVALID_HANDLE 会用 PrintFormat 打出类似 "Failed to create iSAR(EURUSD, H1, 0.020, 0.20) handle. Error 4802" 的日志,方便在 MT5 Experts 标签里直接定位。 外汇与贵金属杠杆高,SAR 在震荡市会频繁反转,trail_start 设太小容易被毛刺扫损;建议先在策略测试器用 0.02 / 0.2 的默认 SAR 参数跑一轮,确认句柄创建无 Error 4802 后再接 trailing 逻辑。

MQL5 / C++
CTrailingBySAR(const class="type">class="kw">string symbol, const ENUM_TIMEFRAMES timeframe, const class="type">long magic,
                  const class="type">class="kw">double sar_step, const class="type">class="kw">double sar_maximum,
                  const class="type">int trail_start, const class="type">uint trail_step, const class="type">int trail_offset);
class=class="str">"cmt">//--- destructor
~CTrailingBySAR(){}
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Parametric constructor                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
CTrailingBySAR::CTrailingBySAR(const class="type">class="kw">string symbol, const ENUM_TIMEFRAMES timeframe, const class="type">long magic,
                               const class="type">class="kw">double sar_step, const class="type">class="kw">double sar_maximum,
                               const class="type">int trail_start, const class="type">uint trail_step, const class="type">int trail_offset) :
                               CTrailingByInd(symbol, timeframe, magic, trail_start, trail_step, trail_offset)
  {
   this.Initialize(symbol, timeframe, sar_step, sar_maximum);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| create Parabolic SAR indicator and class="kw">return the result             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CTrailingBySAR::Initialize(const class="type">class="kw">string symbol, const ENUM_TIMEFRAMES timeframe, const class="type">class="kw">double sar_step, const class="type">class="kw">double sar_maximum)
  {
   this.SetSymbol(symbol);
   this.SetTimeframe(timeframe);
   this.SetSARStep(sar_step);
   this.SetSARMaximum(sar_maximum);
   ::ResetLastError();
   this.m_handle=::iSAR(this.m_symbol, this.m_timeframe, this.m_sar_step, this.m_sar_max);
   if(this.m_handle==INVALID_HANDLE)
     {
      ::PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d",
                    this.m_symbol, this.TimeframeDescription(), this.m_sar_step, this.m_sar_max, ::GetLastError());
     }
   class="kw">return(this.m_handle!=INVALID_HANDLE);
  }

「给 MACD 专家顾问挂上 SAR 跟踪止损」

标准 MACD 信号专家顾问默认不带移动止损(TrailingNone),实盘里止损不动容易被扫后反转。把自写的 Trailings.mqh 加进 include 区,就能接一套抛物线 SAR 的跟踪逻辑。 输入区用两个 group 把参数分层:第一组管 ExpertMACD 本体,魔法码 10981、不每 tick 计算;第二组专给 SAR 跟踪,时间帧取当前周期,加速步长 0.02、最大值 0.2,TrailingStart 设 0 表示一开仓就启动跟踪。 外汇和贵金属杠杆高,SAR 跟踪在单边市能保利润,但震荡里会被反复触发平仓,回测前先想清楚品种波动属性。 下面这段是头文件与输入声明的核心,逐行看就明白模块怎么拼: // 版权与链接属性(MetaQuotes 标准头,可忽略) // 引入官方专家基类与 MACD 信号模块 #include <Expert\Expert.mqh> #include <Expert\Signal\SignalMACD.mqh> // 官方无移动止损占位 #include <Expert\Trailing\TrailingNone.mqh> #include <Expert\Money\MoneyNone.mqh> // 自写 SAR 跟踪模块 #include <Trailings\Trailings.mqh> // 专家参数组 input group " - ExpertMACD Parameters -" input string Inp_Expert_Title="ExpertMACD"; int Expert_MagicNumber=10981; bool Expert_EveryTick=false; // MACD 信号参数:快12 慢24 信号9 止盈50点 止损20点 input int Inp_Signal_MACD_PeriodFast=12; input int Inp_Signal_MACD_PeriodSlow=24; input int Inp_Signal_MACD_PeriodSignal=9; input int Inp_Signal_MACD_TakeProfit=50; input int Inp_Signal_MACD_StopLoss=20; // SAR 跟踪参数组 input group " - Trailing By SAR Parameters -" input ENUM_TIMEFRAMES InpTimeframeSAR=PERIOD_CURRENT; // SAR时间帧 input double InpStepSAR=0.02; // 步长 input double InpMaximumSAR=0.2; // 最大加速 input int InpTrailingStart=0; // 跟踪起点

MQL5 / C++
class="macro">#class="kw">property copyright "Copyright class="num">2000-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 <Expert\Expert.mqh>
class="macro">#include <Expert\Signal\SignalMACD.mqh>
class="macro">#include <Expert\Trailing\TrailingNone.mqh>
class="macro">#include <Expert\Money\MoneyNone.mqh>
class="macro">#include <Trailings\Trailings.mqh>
input group " - ExpertMACD Parameters -"
input class="type">class="kw">string Inp_Expert_Title="ExpertMACD";
class="type">int Expert_MagicNumber=class="num">10981;
class="type">bool Expert_EveryTick=false;
input class="type">int Inp_Signal_MACD_PeriodFast=class="num">12;
input class="type">int Inp_Signal_MACD_PeriodSlow=class="num">24;
input class="type">int Inp_Signal_MACD_PeriodSignal=class="num">9;
input class="type">int Inp_Signal_MACD_TakeProfit=class="num">50;
input class="type">int Inp_Signal_MACD_StopLoss=class="num">20;
input group " - Trailing By SAR Parameters -"
input ENUM_TIMEFRAMES InpTimeframeSAR=PERIOD_CURRENT;
input class="type">class="kw">double InpStepSAR=class="num">0.02;
input class="type">class="kw">double InpMaximumSAR=class="num">0.2;
input class="type">int InpTrailingStart=class="num">0;

◍ 用 SAR 逻辑接管移动止损

把移动止损交给 CTrailingBySAR 之后,EA 就不再依赖固定点数拖尾,而是按 SAR 的翻转节奏去推进止损位。外汇与贵金属波动剧烈,这种跟踪方式在趋势段能吃掉更多利润,但震荡市可能被反复扫损,属于高风险用法。 下面这段输入参数和初始化是把 Trailing 模块接进 Expert 的关键。InpTrailingStep 和 InpTrailingOffset 默认设 0,代表先不额外加步长和偏移,直接用 SAR 算出的止损距离;InpUseTrailing 默认 true,即启动跟踪。 初始化里先调 ExtTrailing.Initialize 绑定当前品种与周期,再把魔术码、启动位、步长、偏移和开关逐一写入。Run() 放在 OnTick 内,每笔报价都让止损模块跑一次。你可以直接把这段代码贴进 MT5 的 EA 源码里验证跟踪是否生效。

MQL5 / C++
input class="type">uint              InpTrailingStep   =  class="num">0;                class=class="str">"cmt">// Trailing Step
input class="type">int               InpTrailingOffset =  class="num">0;                class=class="str">"cmt">// Trailing Offset
input class="type">bool              InpUseTrailing     =  true;             class=class="str">"cmt">// Use Trailing Stop
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Global expert object                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
CExpert ExtExpert;
CTrailingBySAR ExtTrailing;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Initialization function of the expert                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit(class="type">void)
  {
class=class="str">"cmt">//--- Initializing trailing
   if(ExtTrailing.Initialize(NULL,PERIOD_CURRENT,InpStepSAR,InpMaximumSAR))
     {
      ExtTrailing.SetMagicNumber(Expert_MagicNumber);
      ExtTrailing.SetTrailingStart(InpTrailingStart);
      ExtTrailing.SetTrailingStep(InpTrailingStep);
      ExtTrailing.SetStopLossOffset(InpTrailingOffset);
      ExtTrailing.SetActive(InpUseTrailing);
     }
class=class="str">"cmt">//--- Initializing expert
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Function-event handler "tick"                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick(class="type">void)
  {
   ExtExpert.OnTick();
   ExtTrailing.Run();
  }

从AMA追踪类看清MA系止损的共性

移动平均线追踪止损类和抛物线转向追踪类的差别,只落在输入参数集和 Initialize() 创建的具体指标类型上。Initialize() 会按类生成对应的移动平均线指标句柄,逻辑骨架完全一致。 以自适应移动平均线(AMA)追踪类为例,它和抛物线追踪类的运行方式完全相同,区别只是多了一组专属变量存指标参数,以及配套的设值 / 取值方法。所有 MA 系追踪类都长这样,看 Trailings.mqh 里的代码就能理顺。 想实盘验证,把附件里的 ExpertMACDWithTrailingByAMA.mq5 测试 EA 拖进 MT5 回测,能直接跑基于 MA 的追踪。注意外汇和贵金属波动剧烈,这类追踪在高波动时段可能频繁触发止损,属高风险操作。 目前我们已经有简单追踪、标准指标追踪和头寸止损设置类,但还能玩出更细的:对多空分别指定独立止损位,比如按 K 线最高 / 最低价或分形指标追踪。下面这个类就是为「多空各给一个止损价」准备的。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Adaptive Moving Average position StopLoss trailing class          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CTrailingByAMA : class="kw">public CTrailingByInd
  {
class="kw">private:
   class="type">int               m_period;            class=class="str">"cmt">// Period AMA parameter
   class="type">int               m_fast_ema;          class=class="str">"cmt">// Fast EMA Period parameter
   class="type">int               m_slow_ema;          class=class="str">"cmt">// Slow EMA Period parameter
   class="type">int               m_shift;             class=class="str">"cmt">// Shift AMA parameter
   ENUM_APPLIED_PRICE m_price;            class=class="str">"cmt">// Applied Price AMA parameter
class="kw">public:
class=class="str">"cmt">//--- set AMA parameters
   class="type">void              SetPeriod(const class="type">uint period)                 { this.m_period=class="type">int(period<class="num">1 ? class="num">9 : period);    }
   class="type">void              SetFastEMAPeriod(const class="type">uint period)           { this.m_fast_ema=class="type">int(period<class="num">1 ? class="num">2 : period);  }
   class="type">void              SetSlowEMAPeriod(const class="type">uint period)           { this.m_slow_ema=class="type">int(period<class="num">1 ? class="num">30 : period); }
   class="type">void              SetShift(const class="type">int shift)                     { this.m_shift = shift;                        }
   class="type">void              SetPrice(const ENUM_APPLIED_PRICE price)      { this.m_price = price;                        }

class=class="str">"cmt">//--- class="kw">return AMA parameters
   class="type">int               Period(class="type">void)                                 const { class="kw">return this.m_period;                       }

「AMA trailing 类的参数接口与默认构造」

CTrailingByAMA 把移动平均的节奏参数封装成了只读访问器,外部只能取不能改。FastEMAPeriod 返回快线周期 m_fast_ema,SlowEMAPeriod 返回慢线 m_slow_ema,Shift 返回偏移量 m_shift,Price 返回应用的价种枚举 m_price,四个都是 const 方法。 默认无参构造里直接写死了一套参数:Initialize 传入 period=9、fast_ema=2、slow_ema=30、shift=0、PRICE_CLOSE。也就是说不显式指定时,追踪止损逻辑建立在 9 周期 AMA、快慢 EMA 2 与 30 的价差之上,以收盘价计算。 带参构造把 symbol、timeframe、magic 以及上述全部参数加 trail_start / trail_step / trail_offset 都暴露出来,方便在不同品种和周期上复用同一套类。外汇与贵金属杠杆高,这套默认参数在历史回测中仅代表一种可能行为,实盘前务必在 MT5 策略测试器里用对应品种验证。

MQL5 / C++
class="type">int FastEMAPeriod(class="type">void) const { class="kw">return this.m_fast_ema }
class="type">int SlowEMAPeriod(class="type">void) const { class="kw">return this.m_slow_ema }
class="type">int Shift(class="type">void) const { class="kw">return this.m_shift }
ENUM_APPLIED_PRICE Price(class="type">void) const { class="kw">return this.m_price }

class=class="str">"cmt">//--- create AMA indicator and class="kw">return the result
class="type">bool Initialize(const class="type">class="kw">string symbol, const ENUM_TIMEFRAMES timeframe,
                const class="type">int period, const class="type">int fast_ema, const class="type">int slow_ema, const class="type">int shift, const ENUM_APPLIED_PRICE price);

class=class="str">"cmt">//--- constructors
CTrailingByAMA() : CTrailingByInd(::Symbol(), ::Period(), -class="num">1, class="num">0, class="num">0, class="num">0)
  {
    this.Initialize(this.m_symbol, this.m_timeframe, class="num">9, class="num">2, class="num">30, class="num">0, PRICE_CLOSE);
  }

CTrailingByAMA(const class="type">class="kw">string symbol, const ENUM_TIMEFRAMES timeframe, const class="type">long magic,
               const class="type">int period, const class="type">int fast_ema, const class="type">int slow_ema, const class="type">int shift, const ENUM_APPLIED_PRICE price,
               const class="type">int trail_start, const class="type">uint trail_step, const class="type">int trail_offset);

◍ AMA追踪止损类的构造与句柄初始化

CTrailingByAMA 的带参构造函数把品种、周期、魔法码以及 AMA 专用参数(period、fast_ema、slow_ema、shift、price)一股脑传进去,并在初始化列表里先调基类 CTrailingByInd 处理 trail_start、trail_step、trail_offset 三个通用追踪字段。 构造体内只做一件事:调用 this.Initialize(...) 把上述参数落进成员并创建指标句柄,因此实际逻辑重心在 Initialize 而非构造函数本身。 Initialize 先通过 Set 系列方法写入符号、时间框架、AMA 周期与快慢 EMA 周期(fast_ema 与 slow_ema 为 int 型,常见取值如 2 与 30),随后用 ::iAMA() 向终端申请 AMA 句柄。 若返回 INVALID_HANDLE,代码会用 PrintFormat 打印包含品种、周期描述、period、快慢 EMA 及错误码的日志,便于在 MT5 Experts 标签里直接定位建柄失败原因;函数最终以句柄是否有效作为 bool 返回。 开 MT5 后把 fast_ema 从默认 2 改成 5 重新编译 EA,若日志不再报 INVALID_HANDLE 且追踪止损按 AMA 方向触发,说明句柄参数适配当前品种波动。外汇与贵金属杠杆高,AMA 追踪仅降低回撤概率,不保证避损。

MQL5 / C++
class=class="str">"cmt">//--- destructor
~CTrailingByAMA(){}
 };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Parametric constructor                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
CTrailingByAMA::CTrailingByAMA(const class="type">class="kw">string symbol, const ENUM_TIMEFRAMES timeframe, const class="type">long magic,
                               const class="type">int period, const class="type">int fast_ema, const class="type">int slow_ema, const class="type">int shift, const ENUM_APPLIED_PRICE price,
                               const class="type">int trail_start, const class="type">uint trail_step, const class="type">int trail_offset) :
                               CTrailingByInd(symbol, timeframe, magic, trail_start, trail_step, trail_offset)
  {
   this.Initialize(symbol, timeframe, period, fast_ema, slow_ema, shift, price);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| create AMA indicator and class="kw">return the result                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CTrailingByAMA::Initialize(const class="type">class="kw">string symbol, const ENUM_TIMEFRAMES timeframe,
                               const class="type">int period, const class="type">int fast_ema, const class="type">int slow_ema, const class="type">int shift, const ENUM_APPLIED_PRICE price)
  {
   this.SetSymbol(symbol);
   this.SetTimeframe(timeframe);
   this.SetPeriod(period);
   this.SetFastEMAPeriod(fast_ema);
   this.SetSlowEMAPeriod(slow_ema);
   this.SetShift(shift);
   this.SetPrice(price);
   ::ResetLastError();
   this.m_handle=::iAMA(this.m_symbol, this.m_timeframe, this.m_period, this.m_fast_ema, this.m_slow_ema, this.m_shift, this.m_price);
   if(this.m_handle==INVALID_HANDLE)
     {
      ::PrintFormat("Failed to create iAMA(%s, %s, %d, %d, %d, %s) handle. Error %d",
                    this.m_symbol, this.TimeframeDescription(), this.m_period, this.m_fast_ema, this.m_slow_ema,
                    ::StringSubstr(::EnumToString(this.m_price),class="num">6), ::GetLastError());
     }
   class="kw">return(this.m_handle!=INVALID_HANDLE);
  }

用固定数值驱动追踪止损

基于指定值的追踪止损,核心是把指标参数换成两个变量:多单止损价 m_value_sl_long 与空单止损价 m_value_sl_short。类里不再需要 Initialize(),因为没有指标句柄要创建,所有止损价直接由外部传入的变量算出。 调用 Run() 时,控制程序把具体的数值通过形参 value_sl_long、value_sl_short 灌进类成员,再触发父类 Run()。父类会回调虚方法 GetStopLossValue(),按头寸方向返回止损:买仓用 m_value_sl_long 减去偏移点差,卖仓用 m_value_sl_short 加上偏移点差。 下面这段类定义可直接丢进 MT5 的 Include 目录编译验证。注意构造函数里 trail_start 传了 -1,意味着不限制启动 trailing 的最小盈利距离,只要价格触到设定值就跟进。 别把偏移当摆设 m_offset * m_point 是真实跳动点,黄金 XAUUSD 的 m_point 通常是 0.01,若 m_offset 设 20,止损就离指定价差 0.20 美元;外汇 EURUSD 的 m_point 为 0.00001,同参数只差 0.00020。参数错位会让止损过近被扫,属高风险操作,建议先在策略测试器跑历史数据。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Trailing class based on a specified value                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CTrailingByValue : class="kw">public CSimpleTrailing
  {
class="kw">protected:
   class="type">class="kw">double            m_value_sl_long;     class=class="str">"cmt">// StopLoss level for class="type">long positions
   class="type">class="kw">double            m_value_sl_short;    class=class="str">"cmt">// StopLoss level for class="type">class="kw">short positions
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=class="str">"cmt">//--- class="kw">return StopLoss level for (class="num">2) class="type">long and(class="num">2) class="type">class="kw">short positions
   class="type">class="kw">double            StopLossValueLong(class="type">void)    const { class="kw">return this.m_value_sl_long;  }
   class="type">class="kw">double            StopLossValueShort(class="type">void)   const { class="kw">return this.m_value_sl_short; }
class=class="str">"cmt">//--- launch trailing with the specified StopLoss offset from the price
   class="type">bool              Run(const class="type">class="kw">double value_sl_long, class="type">class="kw">double value_sl_short);
class=class="str">"cmt">//--- constructors
                     CTrailingByValue(class="type">void) : CSimpleTrailing(::Symbol(), -class="num">1, class="num">0, class="num">0, class="num">0), m_value_sl_long(class="num">0), m_value_sl_short(class="num">0) {}
                     CTrailingByValue(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 trail_offset) :
                     CSimpleTrailing(symbol, magic, trail_start, trail_step, trail_offset), m_value_sl_long(class="num">0), m_value_sl_short(class="num">0) {}
class=class="str">"cmt">//--- destructor
                    ~CTrailingByValue(class="type">void){}
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Calculate and class="kw">return the StopLoss level of the selected position |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CTrailingByValue::GetStopLossValue(class="type">ENUM_POSITION_TYPE pos_type, class="type">MqlTick &tick)
  {
class=class="str">"cmt">//--- calculate and class="kw">return the StopLoss level depending on the position type
   class="kw">switch(pos_type)
     {
      case POSITION_TYPE_BUY  :  class="kw">return(this.m_value_sl_long  - this.m_offset * this.m_point);
      case POSITION_TYPE_SELL :  class="kw">return(this.m_value_sl_short + this.m_offset * this.m_point);

「用偏移量启动数值追踪止损」

这段代码片段定义了一个名为 CTrailingByValue::Run 的方法,作用是以指定的止损偏移数值启动追踪止损逻辑,分别接收多头与空头的偏移参数。 方法内部先把传入的 value_sl_long 与 value_sl_short 存为类成员变量 m_value_sl_long、m_value_sl_short,随后直接调用基类 CSimpleTrailing::Run() 并返回其结果,说明具体的价位计算与修改订单动作在基类中完成。 在 MT5 里验证时,可复制该 Run 方法并配合前文类结构编译,实盘或策略测试器中观察 m_value_sl_long 取值(例如设为 150 点)后止损线是否随价格按偏移量移动;外汇与贵金属杠杆品种波动剧烈,追踪止损仅降低回撤概率,不消除穿仓风险。

MQL5 / C++
   class="kw">default                 : class="kw">return class="num">0;
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Launch trailing with the specified StopLoss offset from the price|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CTrailingByValue::Run(const class="type">class="kw">double value_sl_long, class="type">class="kw">double value_sl_short)
  {
   this.m_value_sl_long =value_sl_long;
   this.m_value_sl_short=value_sl_short;
   class="kw">return CSimpleTrailing::Run();
  }

◍ 把基于K线极值的值追踪止损挂进EA

以标准目录 MQL5\Experts\Advisors\ 里的 ExpertMACD.mq5 为基底,复制另存为 ExpertMACDWithTrailingByValue.mq5,就能把按值追踪止损接进去。核心改动只有三处:包含追踪类头文件、加输入参数、声明基于值的追踪实例。 两个输入参数决定止损取自哪根K线:InpTimeframe 指定取最高/最低价的时间框架——空单用最高价、多单用最低价作止损参考;InpDataRatesIndex 是该框架图表上的柱形索引,定位具体哪根K线提供极值。 OnInit() 里要把 magic 号绑定给追踪类,只有 magic 匹配 EA 的头寸才会被跟损,避免动到别的策略开的仓。OnTick() 中按 InpDataRatesIndex 取柱数据,确认多空止损价后启动追踪。 测试器可视化跑默认设置,能看到止损位落在时间序列2对应索引K线的最高/最低价上。这种跟损能不能抬升系统胜率,得你自己独立回测;外汇和贵金属杠杆高、滑点跳空频繁,接之前先在策略测试器用历史数据验证。 不同追踪止损切换进EA时,唯独实例类型声明不同,其余包含、初始化、OnTick调用几乎一致。基于文中所给类,你也能按自己的算法写自定义追踪止损,空间基本不受限。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 ExpertMACD.mq5 |
class=class="str">"cmt">//|                      Copyright class="num">2000-class="num">2024, MetaQuotes Ltd. |
class=class="str">"cmt">//|                         [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2000-class="num">2024, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Include                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Expert\Expert.mqh>
class="macro">#include <Expert\Signal\SignalMACD.mqh>
class="macro">#include <Expert\Trailing\TrailingNone.mqh>
class="macro">#include <Expert\Money\MoneyNone.mqh>
class="macro">#include <Trailings\Trailings.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Inputs                                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- inputs for expert
input group   " - ExpertMACD Parameters -"
input class="type">class="kw">string Inp_Expert_Title           ="ExpertMACD";
class="type">int          Expert_MagicNumber         =class="num">10981;
class="type">bool         Expert_EveryTick            =false;
class=class="str">"cmt">//--- inputs for signal
input class="type">int    Inp_Signal_MACD_PeriodFast  =class="num">12;
input class="type">int    Inp_Signal_MACD_PeriodSlow  =class="num">24;
input class="type">int    Inp_Signal_MACD_PeriodSignal=class="num">9;
input class="type">int    Inp_Signal_MACD_TakeProfit  =class="num">50;
input class="type">int    Inp_Signal_MACD_StopLoss    =class="num">20;
input group   " - Trailing By Value Parameters -"

用历史K线极值驱动跟踪止损

MT5 里想让止损跟着某根已收盘 K 线的低点(多单)或高点(空单)走,可以借 CTrailingByValue 这个类。上面这段把外部输入和对象初始化串起来了,核心是把 Data Rates Index 设成 2,意味着取倒数第 3 根 K 线(索引从 0 起算,0 是最新未收杆)的极值做参考。 输入参数里 InpTimeframe 默认 PERIOD_CURRENT,即跟随当前图表周期;InpTrailingStep、InpTrailingStart、InpTrailingOffset 都为 0 时,跟踪逻辑不附加偏移,纯按传入极值贴线。InpUseTrailing 置 true 才激活,设 false 则 Run 虽调用也不生效。 OnInit 中 ExtTrailing.SetMagicNumber 绑定订单识别码,SetActive 接收开关;OnTick 每笔报价先跑专家主体,再用 CopyRates 抓指定周期、指定索引的 1 根 K 线存入 rates[0]。若拷贝成功,把 rates[0].low 与 rates[0].high 喂给 Run——多单止损倾向挂在 low,空单倾向挂在 high。 开 MT5 新建 EA 把这段塞进框架,把 InpDataRatesIndex 改成 1 对比下止损贴合速度,外汇与贵金属杠杆高,参数误设可能让止损过紧被毛刺扫掉。

MQL5 / C++
input ENUM_TIMEFRAMES  InpTimeframe       = PERIOD_CURRENT;   class=class="str">"cmt">// Data Rates Timeframe
input class="type">uint              InpDataRatesIndex = class="num">2;                class=class="str">"cmt">// Data Rates Index for StopLoss
input class="type">uint              InpTrailingStep   = class="num">0;                class=class="str">"cmt">// Trailing Step
input class="type">int               InpTrailingStart  = class="num">0;                class=class="str">"cmt">// Trailing Start
input class="type">int               InpTrailingOffset = class="num">0;                class=class="str">"cmt">// Trailing Offset
input class="type">bool              InpUseTrailing    = true;             class=class="str">"cmt">// Use Trailing Stop
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Global expert object                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
CExpert ExtExpert;
CTrailingByValue ExtTrailing;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Initialization function of the expert                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit(class="type">void)
  {
class=class="str">"cmt">//--- Initializing trailing
   ExtTrailing.SetMagicNumber(Expert_MagicNumber);
   ExtTrailing.SetTrailingStart(InpTrailingStart);
   ExtTrailing.SetTrailingStep(InpTrailingStep);
   ExtTrailing.SetStopLossOffset(InpTrailingOffset);
   ExtTrailing.SetActive(InpUseTrailing);

class=class="str">"cmt">//--- Initializing expert
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Function-event handler "tick"                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick(class="type">void)
  {
   ExtExpert.OnTick();
   class="type">MqlRates rates[class="num">1]={};
   if(CopyRates(ExtTrailing.Symbol(),InpTimeframe,InpDataRatesIndex,class="num">1,rates))
      ExtTrailing.Run(rates[class="num">0].low,rates[class="num">0].high);
  }

「别急着下结论」

这套追踪止损类库把多种尾随逻辑封装成可复用模块,直接挂进任意 EA 都行。给出的 Trailings.mqh 有 98.08 KB,配套四个样例 EA 里最小的 ExpertMACDWithTrailingByValue.mq5 也占了 14.06 KB,说明类结构本身比单文件脚本重得多。 文中只在全局区静态建对象,简单但缺灵活;真要在运行时按行情动态生成尾随实例,得让所有追踪类继承 CObject,再借标准库链表管理。这条路没铺开,留待你去试。 外汇和贵金属杠杆高、滑点跳空频繁,照搬任何尾随策略都可能因极端波动失效。拿 MT5 策略测试器跑一遍附带的 EA,比读十遍文档更管用。

常见问题

先在构造函数外单独写 Init 方法接收时间框架与指标参数,再在 ExpertTick 前调用;句柄为空就直接返回不交易,避免非法指针。
SAR在趋势反转时柱子值会跳到价格另一侧,需判断当前SAR与价格关系再决定只向盈利方向移动,别无脑赋值。
可以,把代码贴给小布,它能标出句柄初始化缺失、周期不匹配等常见坑,并给出改法建议。
用 CopyBuffer 取SAR缓冲区最新值,比较持仓方向与SAR位置,盈利超阈值就按该值重设止损。
不会,只要每个实例在Init时传入独立 timeframe 参数并各自保存句柄,多周期共存是安全的。