在 MQL5 中寻找趋势的几种方法·进阶篇
(2/3)· 移动平均、峰谷、ADX、NRTR、Heiken Ashi 谁更抗噪音?用代码说话
「用均线层级和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,说明下一步要用之字形拐点去确认均线趋势的折返点。
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 != 2 | ext_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 的常用组合,可作为二次确认的参考线。
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 的命中率。
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) 告知终端已处理柱数。
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 头部引用与参数声明。
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">//---------------------------------------------------------------------
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">//---------------------------------------------------------------------