10 款趋势策略的比较分析·进阶篇
📈

10 款趋势策略的比较分析·进阶篇

(2/3)· 从ADXCloud到Keltner通道,10套趋势系统的优劣与离场逻辑一次看透

含代码示例 第 2/3 篇
很多交易者把趋势策略当成“挂上指标就能跟单”的懒人包,结果在横盘里反复止损。趋势确认天然带延迟,入场永远是一个区域而非一个点,忽略这点就会把时间成本算漏。

用标准差和 RVI 卡住横盘突破

这套逻辑把「波动放大」和「动量方向」绑在一起才动手。横盘级别 FlatLevel 默认 10,趋势阈值 TrendLevel 取 Maximum,意味着只有标准差明显抬升、且快慢 RVI 分出方向时,才可能触发新单。 核心判定写在两个函数里:BuySignal 要求 stdev[0] 大于 trend 并且 rvi_fast[0] 大于 rvi_slow[0];SellSignal 则是 stdev[0] 大于 trend 但 rvi_fast[0] 小于 rvi_slow[0]。也就是说,光有波动率不够,必须快慢线也确认倾斜。 OnTick 先查 IsOpened 避免重复开仓,再调 GetIndValue 拉取三个缓冲区各 2 根柱的数据;只要任一 CopyBuffer 返回小于等于 0 就直接 return,不拿脏数据交易。外汇与贵金属杠杆高,这种条件单在震荡转趋势时可能生效,但假突破概率不低,建议先在 MT5 策略测试器跑 EURUSD 15M 验证。

MQL5 / C++
class="kw">input class="type">int                FlatLevel=class="num">10;                 class=class="str">"cmt">//横盘级别
class="kw">input Trend              TrendLevel=Maximum;               class=class="str">"cmt">//使用的趋势
class="type">void OnTick()
  {
class=class="str">"cmt">//--- 检查 EA 以前的开单
   if(!Trade.IsOpened(Inp_MagicNum))
     {
      class=class="str">"cmt">//--- 获取数据进行计算
      if(!GetIndValue())
         class="kw">return;
      class=class="str">"cmt">//--- 如果是买入信号, 开单
      if(BuySignal())
         Trade.BuyPositionOpen(true,Symbol(),Inp_Lot,Inp_MMode,Inp_Deviation,Inp_StopLoss,Inp_TakeProfit,Inp_MagicNum,Inp_EaComment);
      class=class="str">"cmt">//--- 如果是卖出信号, 开单
      if(SellSignal())
         Trade.SellPositionOpen(true,Symbol(),Inp_Lot,Inp_MMode,Inp_Deviation,Inp_StopLoss,Inp_TakeProfit,Inp_MagicNum,Inp_EaComment);
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 买入条件                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool BuySignal()
  {
   class="kw">return(stdev[class="num">0]>trend && rvi_fast[class="num">0]>rvi_slow[class="num">0])?true:class="kw">false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 卖出条件                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool SellSignal()
  {
   class="kw">return(stdev[class="num">0]>trend && rvi_fast[class="num">0]<rvi_slow[class="num">0])?true:class="kw">false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 获取指标的当前值                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool GetIndValue()
  {
   class="kw">return(CopyBuffer(InpInd_Handle1,class="num">0,class="num">0,class="num">2,stdev)<=class="num">0 ||
          CopyBuffer(InpInd_Handle2,class="num">0,class="num">0,class="num">2,rvi_fast)<=class="num">0 ||
          CopyBuffer(InpInd_Handle2,class="num">1,class="num">0,class="num">2,rvi_slow)<=class="num">0
          )?class="kw">false:true;
  }

「用快慢AC和布林能量过滤开仓」

这套逻辑只在当前无同魔法码持仓时才去拉指标、判信号,避免重复开单。OnTick 里先走 Trade.IsOpened(Inp_MagicNum) 判断,通不过就直接跳过,实盘里能少很多垃圾单。 买入要求快线 AC 在慢线 AC 上方,且 bb_power 当前值大于前一根,同时两根 bb_power 都为负。卖出反过来:快线低于慢线、bb_power 递减且两根都为正。这是一种在零轴下方找做多动能、零轴上方找做空动能的过滤思路。 GetIndValue 用 CopyBuffer 分别取 bb_power 2根、ac_fast 2根、ac_slow 2根,只要有任一缓冲区拷贝返回 ≤0 就放弃本轮计算。MT5 上若自定义指标句柄无效,这里会直接 return,不会误开仓。 外汇与贵金属杠杆高,这类信号只是概率倾向,真要跑先把 Inp_StopLoss、Inp_TakeProfit 按品种波动调好,再去策略测试器用 2023 年 XAUUSD 的 M15 数据回测一遍看成交密度。

MQL5 / C++
class="type">bool BuySignal()
  {
   class="kw">return(ac_fast[class="num">0]>ac_slow[class="num">0] && bb_power[class="num">0]>bb_power[class="num">1] && (bb_power[class="num">0]<class="num">0 && bb_power[class="num">1]<class="num">0))?true:class="kw">false;
  }

class="type">bool SellSignal()
  {
   class="kw">return(ac_fast[class="num">0]<ac_slow[class="num">0] && bb_power[class="num">0]<bb_power[class="num">1] && (bb_power[class="num">0]>class="num">0 && bb_power[class="num">1]>class="num">0))?true:class="kw">false;
  }

class="type">bool GetIndValue()
  {
   class="kw">return(CopyBuffer(InpInd_Handle1,class="num">0,class="num">0,class="num">2,bb_power)<=class="num">0 ||
          CopyBuffer(InpInd_Handle2,class="num">0,class="num">0,class="num">2,ac_fast)<=class="num">0 ||
          CopyBuffer(InpInd_Handle2,class="num">1,class="num">0,class="num">2,ac_slow)<=class="num">0
          )?class="kw">false:true;
  }

class="type">void OnTick()
  {
   if(!Trade.IsOpened(Inp_MagicNum))
     {
      if(!GetIndValue())
        class="kw">return;
      if(BuySignal())
        Trade.BuyPositionOpen(true,Symbol(),Inp_Lot,Inp_MMode,Inp_Deviation,Inp_StopLoss,Inp_TakeProfit,Inp_MagicNum,Inp_EaComment);
      if(SellSignal())
        Trade.SellPositionOpen(true,Symbol(),Inp_Lot,Inp_MMode,Inp_Deviation,Inp_StopLoss,Inp_TakeProfit,Inp_MagicNum,Inp_EaComment);
     }
  }

◍ 用重心与速度过滤假突破

这套 EA 的逻辑把重心指标(cog)和平均速度(avr_speed)绑死:只有当 avr_speed[0] 大于 Trend_lev、且前后两根重心都在零轴同侧并发生转向时,才吐出买卖信号。比如 BuySignal 要求 cog[1]<cog[0] 且两者皆为负,意味着重心在零轴下方拐头向上,配合速度过阈,倾向判定为空头动能衰减后的反弹。 GetIndValue 负责把两个指标的近 2 根缓冲拉进数组,只要任一 CopyBuffer 返回值 ≤0 就直接 false,避免取到空数据瞎开仓。OnTick 里先查 IsOpened 防重复下单,再调信号函数,分别走 BuyPositionOpen / SellPositionOpen,参数里的 StopLoss、TakeProfit 由外部输入决定。 后面补的另一种写法更朴素:wave[0]>0 且 wave[1]<0 即买,wave[0]<0 且 wave[1]>0 即卖,相当于看单根穿越零轴。外汇与贵金属杠杆高,这类信号在震荡市可能频繁挨耳光,上 MT5 用策略测试器跑 EURUSD 的 M15 先验证触发频率再谈实盘。

MQL5 / C++
class="type">bool BuySignal()
  {
   class="kw">return(avr_speed[class="num">0]>Trend_lev && cog[class="num">1]<cog[class="num">0] &&(cog[class="num">1]<class="num">0 && cog[class="num">0]<class="num">0))?true:class="kw">false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 卖出条件                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool SellSignal()
  {
   class="kw">return(avr_speed[class="num">0]>Trend_lev && cog[class="num">1]>cog[class="num">0] &&(cog[class="num">1]>class="num">0 && cog[class="num">0]>class="num">0))?true:class="kw">false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 获取指标的当前值                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool GetIndValue()
  {
   class="kw">return((CopyBuffer(InpInd_Handle1,class="num">0,class="num">0,class="num">2,cog)<=class="num">0 ||
           CopyBuffer(InpInd_Handle2,class="num">0,class="num">0,class="num">2,avr_speed)<=class="num">0
          )?class="kw">false:true);
  }
class="type">void OnTick()
  {
class=class="str">"cmt">//--- 检查 EA 以前的开单
   if(!Trade.IsOpened(Inp_MagicNum))
     {
      class=class="str">"cmt">//--- 获取数据进行计算
      if(!GetIndValue())
        class="kw">return;
      class=class="str">"cmt">//--- 如果是买入信号, 开单
      if(BuySignal())
        Trade.BuyPositionOpen(true,Symbol(),Inp_Lot,Inp_MMode,Inp_Deviation,Inp_StopLoss,Inp_TakeProfit,Inp_MagicNum,Inp_EaComment);
      class=class="str">"cmt">//--- 如果是卖出信号, 开单
      if(SellSignal())
        Trade.SellPositionOpen(true,Symbol(),Inp_Lot,Inp_MMode,Inp_Deviation,Inp_StopLoss,Inp_TakeProfit,Inp_MagicNum,Inp_EaComment);
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 买入条件                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool BuySignal()
  {
   class="kw">return(wave[class="num">0]>class="num">0 && wave[class="num">1]<class="num">0)?true:class="kw">false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 卖出条件                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool SellSignal()
  {
   class="kw">return(wave[class="num">0]<class="num">0 && wave[class="num">1]>class="num">0)?true:class="kw">false;
  }

信号判定与指标取数的底层写法

EA 只在当前魔法码无持仓时才去读指标并试图开单,这是避免重复加仓的第一道闸门。OnTick 里先调 Trade.IsOpened 做存在性检查,再走 GetIndValue 拉数据,任一环节失败直接 return,不浪费后续计算。 取数函数有两版。第一版只 CopyBuffer 一个句柄的 0 号缓冲、取 2 根柱,逻辑过于单薄;第二版补齐了四个序列:指标1的 6 号缓冲进 obj_q3_b、2 号缓冲进 obj_q3_s,指标2的 0 号缓冲进 wo,再用 CopyClose 取最新 2 根收盘价进 close。只要其中任一 Copy 返回 ≤0,整体视为取数失败。 买卖信号都建立在『波浪方向 + 价格突破分位线』的双条件上。BuySignal 要求 wo[0]>wo[1] 且 close[0]>obj_q3_b[0],即动量转上且收盘价站上上分位;SellSignal 反之,wo[0]<wo[1] 且 close[0]<obj_q3_s[0]。外汇与贵金属波动剧烈,这类条件只提高概率,不保证方向。 把下面这段代码直接贴进 MT5 的 EA 源文件,改 InpInd_Handle1/2 为你实际加载的指标句柄,就能在策略测试器里验证信号触发频率。

MQL5 / C++
class="type">bool GetIndValue()
  {
   class="kw">return(CopyBuffer(InpInd_Handle,class="num">0,class="num">0,class="num">2,wave)<=class="num">0)?class="kw">false:true;
  }
class="type">void OnTick()
  {
class=class="str">"cmt">//--- 检查 EA 以前的开单
   if(!Trade.IsOpened(Inp_MagicNum))
     {
      class=class="str">"cmt">//--- 获取数据进行计算
      if(!GetIndValue())
        class="kw">return;
      class=class="str">"cmt">//--- 如果是买入信号, 开单
      if(BuySignal())
        Trade.BuyPositionOpen(true,Symbol(),Inp_Lot,Inp_MMode,Inp_Deviation,Inp_StopLoss,Inp_TakeProfit,Inp_MagicNum,Inp_EaComment);
      class=class="str">"cmt">//--- 如果是卖出信号, 开单
      if(SellSignal())
        Trade.SellPositionOpen(true,Symbol(),Inp_Lot,Inp_MMode,Inp_Deviation,Inp_StopLoss,Inp_TakeProfit,Inp_MagicNum,Inp_EaComment);
     }
  }
class="type">bool BuySignal()
  {
   class="kw">return(wo[class="num">0]>wo[class="num">1] && close[class="num">0]>obj_q3_b[class="num">0])?true:class="kw">false;
  }
class="type">bool SellSignal()
  {
   class="kw">return(wo[class="num">0]<wo[class="num">1] && close[class="num">0]<obj_q3_s[class="num">0])?true:class="kw">false;
  }
class="type">bool GetIndValue()
  {
   class="kw">return(CopyBuffer(InpInd_Handle1,class="num">6,class="num">0,class="num">2,obj_q3_b)<=class="num">0 ||
          CopyBuffer(InpInd_Handle1,class="num">2,class="num">0,class="num">2,obj_q3_s)<=class="num">0 ||
          CopyBuffer(InpInd_Handle2,class="num">0,class="num">0,class="num">2,wo)<=class="num">0 ||
          CopyClose(Symbol(),PERIOD_CURRENT,class="num">0,class="num">2,close)<=class="num">0
          )?class="kw">false:true;
  }
class="type">void OnTick()
  {
class=class="str">"cmt">//--- 检查 EA 以前的开单
   if(!Trade.IsOpened(Inp_MagicNum))
     {

「双指标过滤的开单判定逻辑」

这段逻辑把通道类和均线类指标叠在一起做入场筛选:快线穿慢线只给方向,价格突破通道边界才确认信号,避免在无趋势区间频繁碰单。外汇与贵金属杠杆高,这种组合在震荡市仍可能产生连续假信号,实盘前建议用 MT5 策略测试器跑至少 3 个月 tick 数据。 主流程先调 GetIndValue() 拉取四个缓冲区与收盘价,任一 Copy 失败就直接 return,防止用空数组计算。随后 BuySignal() 与 SellSignal() 各自独立判断,互不排斥但同根 K 线只会触发一边。 BuySignal 要求快线大于慢线且收盘价大于上轨,SellSignal 反之。注意两者都只看 [0] 当前柱,未做跨柱确认,因此信号可能在收盘前抖动——想降频可在调用处加一根柱收盘判定。 GetIndValue 里 CopyBuffer 的 2 代表取最近两根,但函数只用了 [0],[1] 是为后续做斜率预留。若你只做当前柱策略,把 2 改成 1 能省一点开销。

MQL5 / C++
  class=class="str">"cmt">//--- 获取数据进行计算
  if(!GetIndValue())
     class="kw">return;
  class=class="str">"cmt">//--- 如果是买入信号, 开单
  if(BuySignal())
     Trade.BuyPositionOpen(true,Symbol(),Inp_Lot,Inp_MMode,Inp_Deviation,Inp_StopLoss,Inp_TakeProfit,Inp_MagicNum,Inp_EaComment);
  class=class="str">"cmt">//--- 如果是卖出信号, 开单
  if(SellSignal())
     Trade.SellPositionOpen(true,Symbol(),Inp_Lot,Inp_MMode,Inp_Deviation,Inp_StopLoss,Inp_TakeProfit,Inp_MagicNum,Inp_EaComment);
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 买入条件                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool BuySignal()
  {
   class="kw">return(cao_fast[class="num">0]>cao_slow[class="num">0] && close[class="num">0]>dcs_up[class="num">0])?true:class="kw">false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 卖出条件                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool SellSignal()
  {
   class="kw">return(cao_fast[class="num">0]<cao_slow[class="num">0] && close[class="num">0]<dcs_low[class="num">0])?true:class="kw">false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 获取指标的当前值                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool GetIndValue()
  {
   class="kw">return(CopyBuffer(InpInd_Handle1,class="num">0,class="num">0,class="num">2,dcs_up)<=class="num">0 ||
          CopyBuffer(InpInd_Handle1,class="num">1,class="num">0,class="num">2,dcs_low)<=class="num">0 ||
          CopyBuffer(InpInd_Handle2,class="num">0,class="num">0,class="num">2,cao_fast)<=class="num">0 ||
          CopyBuffer(InpInd_Handle2,class="num">1,class="num">0,class="num">2,cao_slow)<=class="num">0 ||
          CopyClose(Symbol(),PERIOD_CURRENT,class="num">0,class="num">2,close)<=class="num">0
          )?class="kw">false:true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 专家交易系统输入参数                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">input class="type">class="kw">string         Inp_EaComment="Strategy #class="num">8";                 class=class="str">"cmt">//EA 注释

◍ 把参数和开单守卫钉进 EA 骨架

下面这段 input 区块定义了策略可调的全部外部变量,直接决定 Schaff 趋势周期 EA 的下单行为与风控边界。手数固定 0.01,止损 400 点、止盈 600 点,偏离容差 20 点,属于偏宽幅的贵金属/外汇波段设定,实盘前应在 MT5 策略测试器按品种点值重算保证金占用。 超买 80、超卖 20 沿用了经典震荡通道,但 Schaff 趋势周期本身已做 MACD 式平滑,所以这两个阈值在 20/30 快慢均线组合下触发频率会低于裸 RSI。直方图平滑用 SMMA、快线 20、慢线 30、平滑相位 100、收盘价计算、随机周期 10,这组参数若改快,信号会前移但假突破概率倾向升高。 OnTick 里的 IsOpened 守卫是关键:同一 MagicNum 1111 下只允许一笔持仓,避免重复追单。外汇与贵金属杠杆高,这类无加仓逻辑的骨架在极端跳空时仍可能触发止损,验证时建议先开模拟盘跑一周再谈参数微调。

MQL5 / C++
class="kw">input class="type">class="kw">double Inp_Lot=class="num">0.01; class=class="str">"cmt">//手数
class="kw">input MarginMode Inp_MMode=LOT; class=class="str">"cmt">//保证金模式
class="kw">input class="type">int Inp_MagicNum=class="num">1111; class=class="str">"cmt">//魔幻数字
class="kw">input class="type">int Inp_StopLoss=class="num">400; class=class="str">"cmt">//止损(点数)
class="kw">input class="type">int Inp_TakeProfit=class="num">600; class=class="str">"cmt">//止盈(点数)
class="kw">input class="type">int Inp_Deviation = class="num">20; class=class="str">"cmt">//偏离(点数)
class="kw">input class="type">class="kw">double Overbuying=class="num">80; class=class="str">"cmt">//超买区域
class="kw">input class="type">class="kw">double Overselling=class="num">20; class=class="str">"cmt">//超卖区域
class=class="str">"cmt">//--- Schaff 趋势周期指标参数
class="kw">input Smooth_Method MA_SMethod=MODE_SMMA_; class=class="str">"cmt">//直方图平滑方法
class="kw">input class="type">int Fast_XMA = class="num">20; class=class="str">"cmt">//快速均线周期
class="kw">input class="type">int Slow_XMA = class="num">30; class=class="str">"cmt">//慢速均线周期
class="kw">input class="type">int SmPhase= class="num">100; class=class="str">"cmt">//均线平滑参数
class="kw">input Applied_price_ AppliedPrice=PRICE_CLOSE_; class=class="str">"cmt">//应用价格
class="kw">input class="type">int Cycle=class="num">10; class=class="str">"cmt">//随机振荡器周期
class="type">void OnTick()
  {
class=class="str">"cmt">//--- 检查 EA 以前的开单
   if(!Trade.IsOpened(Inp_MagicNum))
     {
class=class="str">"cmt">//--- 获取数据进行计算
把回测排雷交给小布盯盘
这10套系统的绩效对比和离场信号诊断,小布盯盘的AIGC已内置,打开对应品种页即可看到历史回测分布,你只管挑符合自己风险偏好的那套。

常见问题

确认耗时随工具不同差异很大,重心类指标通常快于通道类,但都要牺牲部分早期空间换可靠性,外汇贵金属波动快时这种时间成本直接变成滑点。
小布盯盘内置了对应品种的回测视图与信号诊断,不要求你手写EA,打开页面对比各系统的胜率和最大回撤即可,高频切换品种时省掉重复编译。
用威廉姆斯AO替代原价格序列,能滤掉部分微观噪音,使通道突破信号更贴近动量本质,但也可能在陡趋势里晚于原始通道出场。
动态目标按趋势强度收缩止盈,强势延伸时确实倾向提前锁利,概率上能躲过反转,但也常错过一段惯性行情,属风险换确定性的取舍。
重心类工具对周期敏感,横盘里相位反复交叉,确认信号和价格实际方向脱节,这类组合在无明显单边时胜率可能快速跌破盈亏平衡。