如何创建自己的追踪止损·进阶篇
📉

如何创建自己的追踪止损·进阶篇

(2/3)· 标准追踪止损之外,用指标逻辑自动拖止损线到底差在哪

案例拆解新手友好 第 2/3 篇
很多人开了仓就只靠终端右键里的内置追踪止损,价格一回头才发现它只会机械跟距离。指标类追踪止损能按波动结构拉线,却要自己写进EA才跑得稳,这篇先把两条路拆给你看。

◍ 按趋势方向推止损的实盘逻辑

这段代码实现了一个跟随指标趋势移动持仓止损的核心方法 DoStoploss()。当追踪开关 m_onoff 为 false 时直接放行,不做任何操作;若设为逐根 K 线模式(m_eachtick 为 false),先用 CopyTime 取最新柱时间,若与上次执行时间相同则跳过,避免同一根 bar 内重复发单。 趋势判断交给 Trend():返回 1 代表上升,只处理买入持仓。此时用 BuyStoploss() 算出理论止损位,再与 SYMBOL_BID 减去 stops level 点数得到的最小允许位做 MathMin,保证不违反经纪商止损距离限制。仅当新 sl 大于原持仓 sl 才填 request 并 OrderSend,否则不动——这意味着止损只会向上垫,不会回撤。 下降趋势返回 -1 时对称处理卖单:理论位由 SellStoploss() 给出后需加上 ask-bid 点差(卖单靠买价平仓),再用 MathMax 锁住最小允许位。这里判断条件写成 sl<possl 或 possl==0,所以原止损为空(0)时也会首次挂上。 外汇与贵金属杠杆高、滑点突发行情多,止损被拒单(retcode!=TRADE_RETCODE_DONE)的概率不低。上面两段都在发送失败时用 printf 打出错误码并 return(false),开 MT5 把这段接进 EA,跑 EURUSD M15 就能看到日志里具体的 #错误码,据此调 SYMBOL_TRADE_STOPS_LEVEL 容忍度。

MQL5 / C++
class="type">bool DoStoploss()
{
class=class="str">"cmt">//--- 如果追踪止损被关闭
if(!m_onoff)
{
class="kw">return(true);
}
class="type">class="kw">datetime tm[class="num">1];
class=class="str">"cmt">//--- 在每个bar模式下,获取最近一个柱形的时间
if(!m_eachtick)
{
class=class="str">"cmt">//--- 如果没能成功复制时间,结束方法,在下一个tick到来时重复 
if(CopyTime(m_symbol,m_timeframe,class="num">0,class="num">1,tm)==-class="num">1)
{
class="kw">return(class="kw">false);
}
class=class="str">"cmt">//--- 如果柱形的时间和本方法最后执行时间一致,结束
if(tm[class="num">0]==m_lasttime)
{
class="kw">return(true);
}
}
class=class="str">"cmt">//--- 获取指标值
if(!Refresh())
{
class="kw">return(class="kw">false);
}
class="type">class="kw">double sl;
class=class="str">"cmt">//--- 取决于指标显示的趋势,进行多种操作
class="kw">switch(Trend())
{
class=class="str">"cmt">//--- 上升趋势
case class="num">1:
class=class="str">"cmt">//--- 选择持仓如果成功,那么持仓存在
if(PositionSelect(m_symbol))
{
class=class="str">"cmt">//---如果为买入持仓
if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
{
class=class="str">"cmt">//--- 获取买入持仓的止损值
sl=BuyStoploss();
class=class="str">"cmt">//--- 找到买入持仓被允许设置止损的位置
class="type">class="kw">double minimal=SymbolInfoDouble(m_symbol,SYMBOL_BID)-m_point*SymbolInfoInteger(m_symbol,SYMBOL_TRADE_STOPS_LEVEL);
class=class="str">"cmt">//--- 对值进行标准化
sl=NormalizeDouble(sl,m_digits);
class=class="str">"cmt">//--- 对值进行标准化
minimal=NormalizeDouble(minimal,m_digits);
class=class="str">"cmt">//---如果不能在由指标获得的水平上设置止损, 
class=class="str">"cmt">//   那么止损将被放置在离可能止损位最近的地方
sl=MathMin(sl,minimal);
class=class="str">"cmt">//--- 持仓的止损值
class="type">class="kw">double possl=PositionGetDouble(POSITION_SL);
class=class="str">"cmt">//--- 对值进行标准化
possl=NormalizeDouble(possl,m_digits);
class=class="str">"cmt">//--- 如果新的止损值大于当前止损值, 
class=class="str">"cmt">//    那么将尝试移动止损到新的水平上
if(sl>possl)
{
class=class="str">"cmt">//--- 填充请求结构体
m_request.sl=sl;
class=class="str">"cmt">//--- 填充请求结构体
m_request.tp=PositionGetDouble(POSITION_TP);
class=class="str">"cmt">//--- 请求
OrderSend(m_request,m_result);
class=class="str">"cmt">//--- 检查请求结果
if(m_result.retcode!=TRADE_RETCODE_DONE)
{
class=class="str">"cmt">//--- 日志错误信息
printf("Unable to move Stop Loss of position %s, error #%I64u",m_symbol,m_result.retcode);
class=class="str">"cmt">//--- 未能移动止损,结束
class="kw">return(class="kw">false);
}
}
}
}
class="kw">break;
class=class="str">"cmt">//--- 下降趋势
case -class="num">1:
if(PositionSelect(m_symbol))
{
if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
{
sl=SellStoploss();
class=class="str">"cmt">//--- 加上点差,因为卖单是通过买价平仓的
sl+=(SymbolInfoDouble(m_symbol,SYMBOL_ASK)-SymbolInfoDouble(m_symbol,SYMBOL_BID));
class="type">class="kw">double minimal=SymbolInfoDouble(m_symbol,SYMBOL_ASK)+m_point*SymbolInfoInteger(m_symbol,SYMBOL_TRADE_STOPS_LEVEL);
sl=NormalizeDouble(sl,m_digits);
minimal=NormalizeDouble(minimal,m_digits);
sl=MathMax(sl,minimal);
class="type">class="kw">double possl=PositionGetDouble(POSITION_SL);
possl=NormalizeDouble(possl,m_digits);
if(sl<possl || possl==class="num">0)
{
m_request.sl=sl;
m_request.tp=PositionGetDouble(POSITION_TP);
OrderSend(m_request,m_result);
if(m_result.retcode!=TRADE_RETCODE_DONE)
{
printf("Unable to move Stop Loss of position %s, error #%I64u",m_symbol,m_result.retcode);
class="kw">return(class="kw">false);
}
}
}
}
class="kw">break;
}
class=class="str">"cmt">//--- 记住方法的最近一次执行时间
m_lasttime=tm[class="num">0];
class="kw">return(true);
}

「用 SAR 线做追踪止损的子类落地」

把抛物线转向(SAR)接进 CTrailingStop 体系,核心就是写一个叫 CParabolicStop 的子类。声明里写 class CParabolicStop: public CTrailingStop,继承后基类的虚拟方法调用会自动走继承版本,不用在子类里重写调度逻辑。 构造函数 CParabolicStop() 只干一件事:把 m_typename 设成 "SAR"。这个变量后面被基类 Init() 拿去生成按钮名和标题,所以追踪止损在面板里显示成什么,全看这里赋的字符串。 SetParameters() 接收 sarstep 和 sarmaximum 两个参数,默认 0.02 和 0.2,然后 iSAR() 加载指标句柄。句柄若返回 -1 直接返回 false;m_indicator 为真时用 ChartIndicatorAdd(0,0,m_handle) 把 SAR 挂到当前图表 0 号窗口。 Refresh() 里 pricebuf 和 indbuf 都只存 1 个元素,靠 m_shift 决定取的是正在形成还是已形成的柱。CopyBuffer 取指标值、CopyClose 取收盘价,任一失败就返回 false,这保证了止损计算前数据是最新的。 Trend() 的判断很直白:pricebuf[0] 大于 indbuf[0] 返回 1(偏多),小于返回 -1(偏空),相等返回 0。价格贴着 SAR 线跑的概率虽低,但实盘里偶尔会出现,返回 0 能让调用方决定不翻转止损。 因为 SAR 只有一条线,BuyStoploss() 和 SellStoploss() 都直接 return indbuf[0],多空止损参考同一数值。写完这个类存成 Sample_TrailingStop.mqh 扔进 .\MQL5\Include,EA 里 include 一下就能跑,外汇和贵金属波动大,SAR 步进参数设太大会让止损被快速扫掉,建议先在 MT5 策略测试器用默认 0.02/0.2 验证一次再上真仓。 下面这段是子类完整代码,逐行已经标了注释,复制进 include 目录即可编译: class CParabolicStop: public CTrailingStop —— 声明继承自 CTrailingStop 的 SAR 追踪止损类 void CParabolicStop() { m_typename="SAR"; } —— 构造函数,把类型名写成 SAR 供面板使用 bool SetParameters(double sarstep=0.02,double sarmaximum=0.2) —— 设步进与上限并加载指标 m_handle=iSAR(m_symbol,m_timeframe,sarstep,sarmaximum); —— 按品种周期和参数取 SAR 句柄 if(m_handle==-1) return(false); —— 句柄失败直接退出 if(m_indicator) ChartIndicatorAdd(0,0,m_handle); —— 允许时把 SAR 画到图表 bool Refresh() —— 刷新价格和指标缓冲 CopyBuffer(m_handle,0,m_shift,1,indbuf) —— 拷 1 根 SAR 值到 indbuf CopyClose(m_symbol,m_timeframe,m_shift,1,pricebuf) —— 拷 1 根收盘价到 pricebuf int Trend() —— 判趋势方向 pricebuf[0]>indbuf[0] return(1) —— 价在线上偏多 pricebuf[0]<indbuf[0] return(-1) —— 价在线下偏空 return(0) —— 相等情形 virtual double BuyStoploss() return(indbuf[0]) —— 多单止损取 SAR 值 virtual double SellStoploss() return(indbuf[0]) —— 空单止损同取 SAR 值

MQL5 / C++
class CParabolicStop: class="kw">public CTrailingStop
class="type">void CParabolicStop()
  {
   m_typename="SAR"; class=class="str">"cmt">// 设置追踪止损类型的名称
  };
class=class="str">"cmt">// 设置参数和加载指标的方法
class="type">bool SetParameters(class="type">class="kw">double sarstep=class="num">0.02,class="type">class="kw">double sarmaximum=class="num">0.2)
  {
   m_handle=iSAR(m_symbol,m_timeframe,sarstep,sarmaximum); class=class="str">"cmt">// 加载指标
   if(m_handle==-class="num">1)
     {
      class="kw">return(class="kw">false); class=class="str">"cmt">// 如果未能加载指标,方法返回false
     }
   if(m_indicator)
     {
      ChartIndicatorAdd(class="num">0,class="num">0,m_handle); class=class="str">"cmt">// 将指标添加到图标上
     }
   
   class="kw">return(true);
  }
class=class="str">"cmt">//获取指标值的方法
class="type">bool Refresh()
  {
   if(CopyBuffer(m_handle,class="num">0,m_shift,class="num">1,indbuf)==-class="num">1)
     {
      class="kw">return(class="kw">false); class=class="str">"cmt">// 如果不能复制数组值,返回false
     }
   
   if(CopyClose(m_symbol,m_timeframe,m_shift,class="num">1,pricebuf)==-class="num">1)
     {
      class="kw">return(class="kw">false); class=class="str">"cmt">// 如果不能复制数组值,返回false
     }    
   class="kw">return(true); 
  }
class=class="str">"cmt">// 找到趋势的方法
class="type">int Trend()
  {
   if(pricebuf[class="num">0]>indbuf[class="num">0])
     { class=class="str">"cmt">// 价格比指标线高,上升趋势
      class="kw">return(class="num">1);
     }
   if(pricebuf[class="num">0]<indbuf[class="num">0])
     { class=class="str">"cmt">// 价格比指标线低,下降趋势
      class="kw">return(-class="num">1);
     }    
   class="kw">return(class="num">0);
  }
class=class="str">"cmt">// 找到买入持仓止损水平的方法
class="kw">virtual class="type">class="kw">double BuyStoploss()
  {
   class="kw">return(indbuf[class="num">0]);
  };
class=class="str">"cmt">// 找到卖出持仓止损水平的方法
class="kw">virtual class="type">class="kw">double SellStoploss()
  {
   class="kw">return(indbuf[class="num">0]);
  };

给初学版 EA 挂上抛物线止损

把抛物线追踪止损接进现成 EA,最省事的做法是另存改名的办法。以 My_First_EA 为例,在 MetaEditor 里另存为 My_First_EA_SARTrailing,避免动到原文件。 代码顶部先引头文件,再声明一个 CParabolicStop 实例。外部变量里放 Step 和 Maximum 两个输入参数,默认 0.02 与 0.2,对应 SAR 的加速步长和上限,这两个值直接决定止损跟价的快慢。 OnInit 里做类初始化:Init 设定品种与周期并打开开仓/平仓控制,setparameters 把上面两个参数灌进去,失败就弹窗并返回 -1;随后 StartTimer 与 On 打开计时器和激活开关。 原 EA 没有 OnTimer,要自己补一个只调 Refresh 的函数,让止损线随行情刷新;OnTick 顶部加一句 DoStoploss 调用即生效。编译后回测对比:无追踪止损见原图 7,挂上后见原图 8,权益曲线回撤明显被压扁,效果在测试环境里很直观。 外汇与贵金属波动大,抛物线止损被毛刺扫掉的概率不低,实盘前请在 MT5 策略测试器用历史数据跑一遍验证参数。

MQL5 / C++
class="macro">#include <Sample_TrailingStop.mqh> class=class="str">"cmt">// 包含追踪止损类
CParabolicStop Trailing; class=class="str">"cmt">// 创建类的实例
input class="type">class="kw">double TrailingSARStep=class="num">0.02;
input class="type">class="kw">double TrailingSARMaximum=class="num">0.2;
Trailing.Init(_Symbol,PERIOD_CURRENT,true,true,class="kw">false); class=class="str">"cmt">//初始化(设置基本参数)
if(!trailing.setparameters(TrailingSARStep,TrailingSARMaximum))
  { class=class="str">"cmt">// 设置所使用的追踪止损类型的参数
   Alert("trailing error");
   class="kw">return(-class="num">1);
  } 
Trailing.StartTimer(); class=class="str">"cmt">// 启动计时器
Trailing.On();         class=class="str">"cmt">// 激活
class="type">void OnTimer()
  {
   Trailing.Refresh();
  }

◍ 在 MT5 里把 NRTR 接成追踪止损类

NRTR(Nick Rypock Trailing Reverse)本身画两条线:基线(支撑或阻力)和目标线。价格碰穿目标线,基线就顺着价格方向迁移,小幅振荡被过滤掉;一旦价格回穿基线,视为趋势反转,两线整体换位。上行时用蓝线、下行用红线,这种结构天然适合拿来做 trailing stop 的触发参考。 我们建一个 CNRTRStop 类,继承自通用追踪止损基类 CTrailingStop。它和抛物线版本的方法集一致,只是构造函数里把 m_typename 写死成 "NRTR",方便运行时识别类型。 SetParameters() 接收 period 和 k 两个参数,用 iCustom 加载 "NRTR" 指标;句柄为 -1 就直接返回 false。若 m_indicator 为真,还会用 ChartIndicatorAdd(0,0,m_handle) 把线挂到当前图表上,方便肉眼核对。 Refresh() 负责把指标两个缓冲区拷进类内数组 sup[] 和 res[]:缓冲区 0 是支撑线、缓冲区 1 是阻力线,各取 1 根柱(m_shift 偏移)。任何一次 CopyBuffer 返回 -1 都判失败。 Trend() 看 sup[0] 非零就返回 1(偏多),res[0] 非零返回 -1(偏空),都零则返回 0。BuyStoploss() 直接吐 sup[0],SellStoploss() 吐 res[0]——也就是说多单止损挂在支撑线、空单挂在阻力线,外汇与贵金属波动大,这种止损被扫的概率随波动率上升而升高,实盘前先在策略测试器跑一轮。 下面这段是类的核心代码,逐行拆完就能直接塞进 EA 的 include 里编译验证。

MQL5 / C++
class CNRTRStop: class="kw">public CTrailingStop
class="type">void CNRTRStop()
  {
   m_typename="NRTR"; class=class="str">"cmt">// 设置追踪止损类型的名称
  };
class=class="str">"cmt">// 设置参数和加载指标的方法
class="type">bool SetParameters(class="type">int period,class="type">class="kw">double k)
  {
   m_handle=iCustom(m_symbol,m_timeframe,"NRTR",period,k); class=class="str">"cmt">// 加载指标
   if(m_handle==-class="num">1)
     { class=class="str">"cmt">// 如果未能加载指标,方法返回false
      class="kw">return(class="kw">false); 
     }
   if(m_indicator)
     {
      
      ChartIndicatorAdd(class="num">0,class="num">0,m_handle); class=class="str">"cmt">// 将指标添加到图标上
     }
   class="kw">return(true);
  }
 class=class="str">"cmt">//获取指标值的方法
class="type">bool Refresh()
  {
   if(CopyBuffer(m_handle,class="num">0,m_shift,class="num">1,sup)==-class="num">1)
     {
      class="kw">return(class="kw">false); class=class="str">"cmt">// 如果不能复制数组值,返回false
     }
    
   if(CopyBuffer(m_handle,class="num">1,m_shift,class="num">1,res)==-class="num">1)
     {
      class="kw">return(class="kw">false); class=class="str">"cmt">// 如果不能复制数组值,返回false
     }
    
   class="kw">return(true);
  }
class="kw">protected:
class="type">class="kw">double sup[class="num">1]; class=class="str">"cmt">// 支撑位的值
class="type">class="kw">double res[class="num">1]; class=class="str">"cmt">// 阻力位的值
class=class="str">"cmt">// 找到趋势的方法
class="type">int Trend()
  {
   if(sup[class="num">0]!=class="num">0)
     { class=class="str">"cmt">// 若有支撑线,那么是上升趋势
      class="kw">return(class="num">1);
     }
   if(res[class="num">0]!=class="num">0)
     { class=class="str">"cmt">// 若有阻力线,那么是下降趋势
      class="kw">return(-class="num">1);
     }
   
   class="kw">return(class="num">0);
  }
class=class="str">"cmt">// 找到买入持仓止损水平的方法
class="type">class="kw">double BuyStoploss()
  {
   class="kw">return(sup[class="num">0]);
  }
class=class="str">"cmt">// 找到卖出持仓止损水平的方法
class="type">class="kw">double SellStoploss()
  {
   class="kw">return(res[class="num">0]);
  }

「把 NRTR 止损接进现成 EA」

在 MetaEditor 里把 My_First_EA_SARTrailing 另存为 My_First_EA_NRTRTrailing,外部参数换成 NRTR 的周期与倍数,再把原来 CParabolicStop 的实例改成 CNRTRStop。实例创建位置放在外部变量之后即可,OnInit() 里对应改掉 SetParameters() 的传参,编译后跑一轮测试。 附在文中的测试图(图10)显示,换上 NRTR 追踪止损后,该 EA 的净值曲线与无追踪止损版本(图7)几乎重叠,变动极小。同条件下此前抛物线 SAR 追踪止损的改善更明显,说明这套策略里 SAR 段更适配。 外汇与贵金属杠杆高、滑点跳空频繁,追踪止损只是概率上的回撤控制手段,不保证盈利。NRTR 周期 40、倍数 2 是原文用例,你可以直接开 MT5 改这两个数看曲线反应,找出更贴合自己品种的档位。

MQL5 / C++
input class="type">int TrailingNRTRPeriod = class="num">40;
input class="type">class="kw">double TrailingNRTRK   =  class="num">2;
CNRTRStop Trailing; class=class="str">"cmt">// 创建类的实例 
Trailing.SetParameters(TrailingNRTRPeriod,TrailingNRTRK)
把重复劳动交给小布
这些指标子类接EA的样板逻辑,小布盯盘的AIGC已内置常见品种页,打开对应页就能比对你的参数有没有踩坑,你只管调策略。

常见问题

挂单阶段拉追踪止损没意义,只有成交成持仓后保护逻辑才生效;一种工具只能有一个持仓,但挂单可多层,混淆会导致修改错对象。
可以,小布盯盘对应品种页内置了指标子类诊断,你能贴EA片段让它标出止损触发条件和潜在滑点区间,省去手算。
NRTR按真实波幅动态重算通道,止损线不会在窄幅震荡里被毛刺频繁触发,更倾向过滤噪音而非固定点距跟随。
标准追踪由终端侧EA或客户端执行, broker端只守固定止损;网络断了终端指令发不出,追踪暂停但原止损仍在,风险敞口可能临时扩大。