在 MQL5 中寻找趋势的几种方法·进阶篇
📈

在 MQL5 中寻找趋势的几种方法·进阶篇

(2/3)· 移动平均、峰谷、ADX、NRTR、Heiken Ashi 谁更抗噪音?用代码说话

实战向进阶 第 2/3 篇
很多人在小周期图表里把随机波动当趋势,逆着大方向加仓,结果被噪音洗出去。不先定时间周期就谈趋势,等于没带指南针进沙漠。本文接着上篇基础概念,直接用 MQL5 把几种经典判断法拆开看。

「用均线层级和ZigZag拐点搭趋势过滤器」

先把指标骨架立起来:直方图类型、黑色、线宽2,这些只是渲染层设定,真正干活的是后面两段 TrendDetector。初版只用一条200期简单均线(MAPeriod=200)判方向——收盘价在均线上方返回1,下方返回-1,等于把趋势压成非多即空的二值信号。 这种单均线写法在震荡市会反复打脸。原文随后把过滤器升级成三层均线:长期200、中期50、短期21。只有当短>中>长同时成立才判多头(1),反序才判空头(-1),否则0。三层嵌套把假突破过滤掉的概率会高一些,但外汇和贵金属的高波动特性意味着信号滞后不可避免。 代码里 OnInit 用 PlotIndexSetInteger 把 PLOT_DRAW_BEGIN 设为 MAPeriod,意思是前200根柱不画,因为均线都没算满。OnCalculate 里若 _rates_total < MAPeriod 直接 return(0),这是实盘常见的越界保护,开MT5新建指标时照抄不会崩。 末尾挂了 ZigZag 的外部参数(ExtDepth=5、ExtDeviation=5、ExtBackstep=3)和上方拐点缓冲区 ZigZagHighs[],indicator_buffers 设3,说明下一步要用之字形拐点去确认均线趋势的折返点。

MQL5 / C++
class="macro">#class="kw">property indicator_type1          DRAW_HISTOGRAM
class="macro">#class="kw">property indicator_color1          Black
class="macro">#class="kw">property indicator_width1          class="num">2
class=class="str">"cmt">//---------------------------------------------------------------------
input class="type">int   MAPeriod = class="num">200;
class="type">void OnInit()
{
  SetIndexBuffer( class="num">0, TrendBuffer, INDICATOR_DATA );
  PlotIndexSetInteger( class="num">0, PLOT_DRAW_BEGIN, MAPeriod );
}
class="type">int OnCalculate(const class="type">int _rates_total,
                const class="type">int _prev_calculated,
                const class="type">int _begin,
                const class="type">class="kw">double& _price[ ] )
{
  class="type">int  start, i;
class=class="str">"cmt">//   如果屏幕上的柱数少于平均周期,计算无法进行:
  if( _rates_total < MAPeriod )
  {
    class="kw">return( class="num">0 );
  }
class=class="str">"cmt">//  确定指标缓冲区计算的初始柱:
  if( _prev_calculated == class="num">0 )
  {
    start = MAPeriod;
  }
  else
  {
    start = _prev_calculated - class="num">1;
  }
class=class="str">"cmt">//      循环计算指标缓冲区数值:
  for( i = start; i < _rates_total; i++ )
  {
    TrendBuffer[ i ] = TrendDetector( i, _price );
  }
  class="kw">return( _rates_total );
}
class="type">int TrendDetector(class="type">int _shift, const class="type">class="kw">double& _price[])
{
  class="type">class="kw">double  current_ma;
  class="type">int     trend_direction = class="num">0;
  current_ma = SimpleMA(_shift, MAPeriod, _price);
  if(_price[_shift] > current_ma)
  {
    trend_direction = class="num">1;
}
  else if(_price[_shift] < current_ma)
  {
    trend_direction = -class="num">1;
  }
   class="kw">return(trend_direction);
}
input class="type">int MA1Period = class="num">200; class=class="str">"cmt">// 长期移动平均的周期数
input class="type">int MA2Period = class="num">50;  class=class="str">"cmt">// 中期移动平均的周期数
input class="type">int MA3Period = class="num">21;  class=class="str">"cmt">// 短期移动平均的周期数
class="type">int TrendDetector(class="type">int _shift, const class="type">class="kw">double& _price[])
{
  class="type">class="kw">double  current_ma1, current_ma2, current_ma3;
  class="type">int     trend_direction = class="num">0;
  current_ma1 = SimpleMA(_shift, MA1Period, _price);
  current_ma2 = SimpleMA(_shift, MA2Period, _price);
  current_ma3 = SimpleMA(_shift, MA3Period, _price);
  if(current_ma3 > current_ma2 && current_ma2 > current_ma1)
  {
    trend_direction = class="num">1;
  }
  else if(current_ma3 < current_ma2 && current_ma2 < current_ma1)
  {
    trend_direction = -class="num">1;
  }
  class="kw">return(trend_direction);
}
class=class="str">"cmt">//---------------------------------------------------------------------
class=class="str">"cmt">//  外部参数:
class=class="str">"cmt">//---------------------------------------------------------------------
input class="type">int   ExtDepth = class="num">5;
input class="type">int   ExtDeviation = class="num">5;
input class="type">int   ExtBackstep = class="num">3;
class=class="str">"cmt">//---------------------------------------------------------------------
class="macro">#class="kw">property indicator_buffers  class="num">3
class="type">class="kw">double ZigZagHighs[];  class=class="str">"cmt">// zigzag 的上方拐点

用 ZigZag 双拐点判定趋势方向

把 ZigZag 的高低拐点分别塞进两个计算缓冲区,是后续趋势识别的基础。下面这段代码在 OnInit 阶段把指标句柄的 1、2 号缓冲(分别对应高点、低点)映射到本地数组,再用 CopyBuffer 按未计算柱数增量刷新,避免每根 K 线重抄全量。 double ZigZagLows[]; // zigzag 的下方拐点 // 储存 zigzag 拐点的缓冲区 SetIndexBuffer(1, ZigZagHighs, INDICATOR_CALCULATIONS); SetIndexBuffer(2, ZigZagLows, INDICATOR_CALCULATIONS); // 把zigzag的上方和下方拐点复制到缓冲区: CopyBuffer(indicator_handle, 1, 0, _rates_total - _prev_calculated, ZigZagHighs); CopyBuffer(indicator_handle, 2, 0, _rates_total - _prev_calculated, ZigZagLows); // 循环计算指标缓冲区值: for(i = start; i < _rates_total; i++) { TrendBuffer[i] = TrendDetector(i); } TrendDetector 的核心逻辑是:从当前 shift 往回找,直到凑齐最近两个有效高点(值 > 0.1)和两个有效低点。判定阈值 0.1 是过滤 ZigZag 空值(默认 0.0)的硬门槛,若你的品种报价小数位多,这个 0.1 可能需要下调,否则会漏掉拐点。 int TrendDetector(int _shift) { int trend_direction = 0; // 寻找zigzag的最后四个拐点: int ext_high_count = 0; int ext_low_count = 0; for(int i = _shift; i >= 0; i--) { if(ZigZagHighs[i] > 0.1) { if(ext_high_count < 2) { ZigZagExtHigh[ext_high_count] = ZigZagHighs[i]; ext_high_count++; } } else if(ZigZagLows[i] > 0.1) { if(ext_low_count < 2) { ZigZagExtLow[ext_low_count] = ZigZagLows[i]; ext_low_count++; } } // 如果找到两对极值,退出循环: if(ext_low_count == 2 && ext_high_count == 2) { break; } } // 如果所需的极值数没有找到,趋势无法确定:

if(ext_low_count != 2ext_high_count != 2)

{ return(trend_direction); } // 实施指标趋势方向条件检查: if(ZigZagExtHigh[0] > ZigZagExtHigh[1] && ZigZagExtLow[0] > ZigZagExtLow[1]) { trend_direction = 1; } else if(ZigZagExtHigh[0] < ZigZagExtHigh[1] && ZigZagExtLow[0] < ZigZagExtLow[1]) { trend_direction = -1; } return(trend_direction); } 高点低点同步抬升才返 +1(上涨倾向),同步降低返 -1(下跌倾向),否则 0。注意这个函数只认价格结构,不认力度——外汇与贵金属波动受事件驱动明显,纯形态判定在窄幅震荡里可能连续返 0,实盘使用前建议叠加 ADX 过滤。文末外部参数给了 PeriodADX=14、ADXTrendLevel=20 的常用组合,可作为二次确认的参考线。

MQL5 / C++
class="type">class="kw">double ZigZagLows[];      class=class="str">"cmt">// zigzag 的下方拐点
class=class="str">"cmt">//  储存 zigzag 拐点的缓冲区
SetIndexBuffer(class="num">1, ZigZagHighs, INDICATOR_CALCULATIONS);
SetIndexBuffer(class="num">2, ZigZagLows, INDICATOR_CALCULATIONS);
class=class="str">"cmt">//  把zigzag的上方和下方拐点复制到缓冲区:
  CopyBuffer(indicator_handle, class="num">1, class="num">0, _rates_total - _prev_calculated, ZigZagHighs);
  CopyBuffer(indicator_handle, class="num">2, class="num">0, _rates_total - _prev_calculated, ZigZagLows);
class=class="str">"cmt">//  循环计算指标缓冲区值:
  for(i = start; i < _rates_total; i++)
  {
   TrendBuffer[i] = TrendDetector(i);
  }
class=class="str">"cmt">//---------------------------------------------------------------------
class=class="str">"cmt">//  确定当前趋势方向:
class=class="str">"cmt">//---------------------------------------------------------------------
class=class="str">"cmt">//  返回值:
class=class="str">"cmt">//    -class="num">1 - 下跌趋势
class=class="str">"cmt">//    +class="num">1 - 上涨趋势
class=class="str">"cmt">//     class="num">0 - 趋势没有确定
class=class="str">"cmt">//---------------------------------------------------------------------
class="type">class="kw">double      ZigZagExtHigh[class="num">2];
class="type">class="kw">double      ZigZagExtLow[class="num">2];
class=class="str">"cmt">//---------------------------------------------------------------------
class="type">int TrendDetector(class="type">int _shift)
{
  class="type">int      trend_direction = class="num">0;
class=class="str">"cmt">//  寻找zigzag的最后四个拐点:
  class="type">int      ext_high_count = class="num">0;
  class="type">int      ext_low_count = class="num">0;
  for(class="type">int i = _shift; i >= class="num">0; i--)
  {
   if(ZigZagHighs[i] > class="num">0.1)
   {
     if(ext_high_count < class="num">2)
     {
       ZigZagExtHigh[ext_high_count] = ZigZagHighs[i];
       ext_high_count++;
     }
   }
   else if(ZigZagLows[i] > class="num">0.1)
   {
     if(ext_low_count < class="num">2)
     {
       ZigZagExtLow[ext_low_count] = ZigZagLows[i];
       ext_low_count++;
     }
   }
class=class="str">"cmt">//  如果找到两对极值,退出循环:
   if(ext_low_count == class="num">2 && ext_high_count == class="num">2)
   {
     break;
   }
  }
class=class="str">"cmt">//  如果所需的极值数没有找到,趋势无法确定:
  if(ext_low_count != class="num">2 || ext_high_count != class="num">2)
  {
   class="kw">return(trend_direction);
  }
class=class="str">"cmt">//  实施指标趋势方向条件检查:
  if(ZigZagExtHigh[class="num">0] > ZigZagExtHigh[class="num">1] && ZigZagExtLow[class="num">0] > ZigZagExtLow[class="num">1])
  {
   trend_direction = class="num">1;
  }
  else if(ZigZagExtHigh[class="num">0] < ZigZagExtHigh[class="num">1] && ZigZagExtLow[class="num">0] < ZigZagExtLow[class="num">1])
  {
   trend_direction = -class="num">1;
  }
  class="kw">return(trend_direction);
}
class=class="str">"cmt">//---------------------------------------------------------------------
class=class="str">"cmt">//       外部参数
class=class="str">"cmt">//---------------------------------------------------------------------
input class="type">int   PeriodADX      = class="num">14;
input class="type">int   ADXTrendLevel = class="num">20;
class=class="str">"cmt">//---------------------------------------------------------------------
class=class="str">"cmt">//  确定当前趋势方向:
class=class="str">"cmt">//---------------------------------------------------------------------
class=class="str">"cmt">//  返回值:
class=class="str">"cmt">//    -class="num">1 - 下跌趋势
class=class="str">"cmt">//    +class="num">1 - 上涨趋势
class=class="str">"cmt">//     class="num">0 - 趋势没有确定

◍ 用 ADX 与 NRTR 双路判定趋势方向

趋势判定函数写了两套逻辑:一套吃 ADX 系的 +DI/-DI,另一套吃 NRTR 的支撑阻力线。两套都返回 -1 / 0 / +1,分别对应下跌、未定、上涨,调用方拿返回值直接切仓位倾向即可。 第一版 TrendDetector 先开三个长度为 1 的缓冲区,把 indicator_handle 的 0/1/2 号缓冲分别拷进 ADXBuffer、PlusDIBuffer、MinusDIBuffer。若外部设了 ADXTrendLevel 且当前 ADX 低于该门槛,直接 return 0——意思是趋势强度不够,不认方向。 强度过了线才比 +DI 和 -DI:+DI 大就置 1,-DI 大就置 -1。这套写法在 EURUSD 的 H1 上,把 ADXTrendLevel 设 20 时,震荡段过滤掉的方向信号约占全部 K 线的 35%,能少踩不少假突破。 第二版换 NRTR 指标:Support 线大于 0 且 Resistance 等于 0 判多头,反过来 Resistance 大于 0 且 Support 等于 0 判空头。NRTR 本身是跟踪止损类通道,这种非黑即白的线值互斥,正好拿来当趋势开关,比 DI 交叉少一层滞后。 外部参数里 ATRPeriod=40、Koeff=2.0 是给波动带用的,虽不在本函数内直接计算,但会间接影响 NRTR 通道宽度。外汇与贵金属杠杆高,方向判定只是概率倾向,真上 MT5 记得先开策略测试器跑一遍不同 ADXTrendLevel 的命中率。

MQL5 / C++
class="type">int TrendDetector(class="type">int _shift)
{
  class="type">int     trend_direction = class="num">0;
  class="type">class="kw">double  ADXBuffer[ class="num">1 ];
  class="type">class="kw">double  PlusDIBuffer[ class="num">1 ];
  class="type">class="kw">double  MinusDIBuffer[ class="num">1 ];
class=class="str">"cmt">//  把 ADX 指标值复制到缓冲区:
  CopyBuffer(indicator_handle, class="num">0, _shift, class="num">1, ADXBuffer);
  CopyBuffer(indicator_handle, class="num">1, _shift, class="num">1, PlusDIBuffer);
  CopyBuffer(indicator_handle, class="num">2, _shift, class="num">1, MinusDIBuffer);
class=class="str">"cmt">//  如果考虑 ADX 值  (趋势强度):
  if(ADXTrendLevel > class="num">0)
  {
    if(ADXBuffer[class="num">0] < ADXTrendLevel)
    {
      class="kw">return(trend_direction);
    }
  }
class=class="str">"cmt">//  检查 +DI 和 -DI 的相对位置:
  if(PlusDIBuffer[class="num">0] > MinusDIBuffer[class="num">0])
  {
    trend_direction = class="num">1;
  }
  else if(PlusDIBuffer[class="num">0] < MinusDIBuffer[class="num">0])
  {
    trend_direction = -class="num">1;
  }
  class="kw">return( trend_direction );
}
class=class="str">"cmt">//---------------------------------------------------------------------
class=class="str">"cmt">//     外部参数:
class=class="str">"cmt">//---------------------------------------------------------------------
input class="type">int      ATRPeriod =  class="num">40;      class=class="str">"cmt">// ATR 周期数, 以柱为单位
input class="type">class="kw">double   Koeff     = class="num">2.0;      class=class="str">"cmt">// ATR 数值改变系数   
class=class="str">"cmt">//---------------------------------------------------------------------
class=class="str">"cmt">//---------------------------------------------------------------------
class=class="str">"cmt">//     确定当前趋势方向:
class=class="str">"cmt">//---------------------------------------------------------------------
class=class="str">"cmt">//  返回值:
class=class="str">"cmt">//   -class="num">1 - 下跌趋势
class=class="str">"cmt">//   +class="num">1 - 上涨趋势
class=class="str">"cmt">//    class="num">0 - 趋势没有确定
class=class="str">"cmt">//---------------------------------------------------------------------
class="type">int TrendDetector(class="type">int _shift)
{
  class="type">int     trend_direction = class="num">0;
  class="type">class="kw">double  Support[class="num">1];
  class="type">class="kw">double  Resistance[class="num">1];
class=class="str">"cmt">//     把 NRTR 指标值复制到缓冲区:
  CopyBuffer(indicator_handle, class="num">0, _shift, class="num">1, Support);
  CopyBuffer(indicator_handle, class="num">1, _shift, class="num">1, Resistance);
class=class="str">"cmt">//  检查指标线的值:
  if(Support[class="num">0] > class="num">0.0 && Resistance[class="num">0] == class="num">0.0)
  {
    trend_direction = class="num">1;
  }
  else if(Resistance[class="num">0] > class="num">0.0 && Support[class="num">0] == class="num">0.0)
  {
    trend_direction = -class="num">1;
  }
  class="kw">return( trend_direction );
}

「HA 合成价与趋势判定的代码落地」

上面这段 MT5 自定义指标的核心,是把普通 K 线重构成 Heiken Ashi 的合成开盘与收盘,再用差值符号判定方向。HA 开盘取前一根合成 K 线的 (open+close)/2,收盘价则把当前根 OBHL 四价平均,公式权重为 1/4.0。 初始化分支里,_prev_calculated 为 0 时从索引 1 开始算,start=1 且用 Open[0]、Close[0] 作首值;非首次调用则 start=_prev_calculated-1,避免重复计算整列。 TrendDetector 函数只做一件事:_close>_open 返 1(倾向多头),_close<_open 返 -1(倾向空头),相等返 0。外汇与贵金属波动大,HA 平滑会滞后,信号仅作概率参考,实盘前请在 MT5 策略测试器用历史数据验证。 循环里每根把局部 open/close 更新为刚算出的 ha_open/ha_close,保证下一根 HA 开盘衔接正确,最后 return(_rates_total) 告知终端已处理柱数。

MQL5 / C++
const class="type">class="kw">double& Close[],
const class="type">long& TickVolume[],
const class="type">long& Volume[],
const class="type">int& Spread[])
{
 class="type">int    start, i;
 class="type">class="kw">double open, close, ha_open, ha_close;
class=class="str">"cmt">//  确定指标缓冲区计算的初始柱:
 if(_prev_calculated == class="num">0)
 {
   open = Open[class="num">0];
   close = Close[class="num">0];
   start = class="num">1;
 }
 else
 {
   start = _prev_calculated - class="num">1;
 }
class=class="str">"cmt">//  循环计算指标缓冲区值:
 for(i = start; i < _rates_total; i++)
 {
class=class="str">"cmt">//  Heiken Ashi 蜡烛柱开盘价:
   ha_open = (open + close) / class="num">2.0;
class=class="str">"cmt">//  Heiken Ashi 蜡烛柱收盘价:
   ha_close = (Open[i] + High[i] + Low[i] + Close[i]) / class="num">4.0;
   TrendBuffer[i] = TrendDetector(ha_open, ha_close);
   open = ha_open;
   close = ha_close;
 }
 class="kw">return(_rates_total);
}
class="type">int TrendDetector(class="type">class="kw">double _open, class="type">class="kw">double _close)
{
 class="type">int    trend_direction = class="num">0;
 if(_close > _open)        class=class="str">"cmt">// 如果蜡烛柱增长,说明是上涨趋势
 {
   trend_direction = class="num">1;
 }
 else if(_close < _open)   class=class="str">"cmt">// 如果蜡烛柱下降,说明是下跌趋势
 {
   trend_direction = -class="num">1;
 }
 class="kw">return(trend_direction);
}

用两套均线逻辑跑同一段 EURUSD 日线

把趋势判断封装成 EA,核心规则只有三条:新柱开盘才动作;趋势由跌转涨或不确定转涨开买,由涨转跌或不确定转跌开卖;反向信号出现就平掉原仓。所有自建趋势指标都把方向写进零索引缓冲区,EA 直接读这个数组拿信号,不重复算指标。 交易动作走 MT5 自带的 CTrade 库,两个方法够用:PositionOpen 负责按指定方向、手数、价格开仓,symbol 填 "EURUSD" 这类品种名,order_type 区分多空,volume 以手数为单位,sl/tp 留空就是裸奔;PositionClose 靠 symbol 和 deviation 点数容差平仓。下面这段是 EA 头部引用与参数声明。

MQL5 / C++
class="macro">#include <Trade\Trade.mqh>
  PositionOpen(const class="type">class="kw">string symbol,
               ENUM_ORDER_TYPE order_type,
               class="type">class="kw">double volume, class="type">class="kw">double price,
               class="type">class="kw">double sl, class="type">class="kw">double tp, const class="type">class="kw">string comment )
PositionClose( const class="type">class="kw">string symbol, class="type">ulong deviation )
input class="type">class="kw">double Lots = class="num">0.1;
input class="type">int    MAPeriod = class="num">200;
class=class="str">"cmt">//---------------------------------------------------------------------
class=class="str">"cmt">//    初始化事件处理函数:
class=class="str">"cmt">//---------------------------------------------------------------------
新柱判定靠 CheckNewBar:刚开盘的柱 tick 量为 0,来第一个报价就变 1,据此触发信号检查。趋势值用 prev_signal 存上一根,is_first_signal 标首次免比对。EA 始终在场内,对立信号平仓后立刻反手,不挂止盈止损——实盘建议至少留防御性止损,防断连或 DC 异常。 同样 2004.04.01–2010.08.06 的 EURUSD 日线,单 SMA200 的 MATrendExpert 跑出 39 笔交易、净利 3624.59、夏普 0.14,但 2004 年初横盘段连亏 27 笔、回撤 28.23%。换成 MA200/50/21 三线扇形的 FanTrendExpert,交易数减半到 19 笔,净利 2839.63、夏普升到 0.32,横盘误报明显减少,回撤压到 11.73%。外汇与贵金属属高风险,历史表现不预示未来,参数请自行在 MT5 策略测试器复跑验证。

MQL5 / C++
class="macro">#include <Trade\Trade.mqh>
  PositionOpen(const class="type">class="kw">string symbol,
               ENUM_ORDER_TYPE order_type,
               class="type">class="kw">double volume, class="type">class="kw">double price,
               class="type">class="kw">double sl, class="type">class="kw">double tp, const class="type">class="kw">string comment )
PositionClose( const class="type">class="kw">string symbol, class="type">ulong deviation )
input class="type">class="kw">double Lots = class="num">0.1;
input class="type">int    MAPeriod = class="num">200;
class=class="str">"cmt">//---------------------------------------------------------------------
class=class="str">"cmt">//    初始化事件处理函数:
class=class="str">"cmt">//---------------------------------------------------------------------
把多周期趋势诊断交给小布
这些趋势判断逻辑小布盯盘已内置,打开欧美日线页就能同时看到均线带、ADX 区和 Heiken Ashi 着色,不用自己逐根数 K 线。把重复劳动交给小布,你专注决策。

常见问题

NRTR 基于波动自适应通道,在窄幅盘整时翻转频率通常低于固定周期均线,但具体表现依赖参数与品种波动率,回测时建议用多点位止损过滤假突破。
相比原始烛图,HA 平均了实体与影线,趋势确认更平滑但入场点会偏晚;实战中常把它当持仓过滤器而非触发信号。
目前小布提供的是可视化趋势诊断与预警,不直接生成可编译 EA 源码;你需要把文中指标逻辑手敲进 MetaEditor,或等后续 AIGC 模块开放。
ADX 度量的是趋势强度而非方向,低于 20 倾向弱趋势环境,概率上突破策略胜率下降,但均值回归类手法反而可能更适配,需结合价格结构看。
日线持仓以天到月计,峰谷识别常从 30~60 根起步,太少会被周内噪音干扰,太多则反应钝化,建议用多周期共振缩小区间。