使用格兹尔算法的循环分析·综合运用
(3/3)·把离散傅里叶单项检测搬进MT5,用代码抓价格主导周期而非盲猜波段
用Goertzel把价格噪声压成波形
自适应周期Goertzel DFT的思路,是从价格的滑动窗口里抓前10个主导周期,再拼回一条新曲线。这条曲线被认为更接近去掉噪声后的真实价格行为,外汇和贵金属这种高频噪音多的品种,滤波前后差别可能很明显。 关键参数就三个:采样频率的最小周期、最大周期,以及用来构造滤波信号的最大频率分量数。原文默认最大周期72、最小周期5、最多取10个分量,移动窗口大小等于最大周期的三倍,也就是216根K线。 信号触发靠两个阈值:pntup和pntdn,单位是百分比,默认都是0.5。曲线相对最近波峰或波谷的移动幅度超过这个值,才可能给出多或空指示。外汇贵金属波动剧烈,阈值过小容易反复假触发,实盘前建议在MT5里调大观察。 波点就是曲线在对应索引处的值,代码里Wave缓冲画出黑线,Peak和Trough设为DRAW_NONE只用于内部计算,Long画蓝线宽度2。直接把下面代码丢进MT5指标目录编译,就能看到分离窗口里的波形。
class=class="str">"cmt">//--- input parameters input class="type">bool Detrend=true; input class="type">bool EndFlatten=true; input class="type">bool SquaredAmp=true; input class="type">bool UseCycleStrength=false; input class="type">uint Maxper=class="num">72; input class="type">uint Minper=class="num">5; input class="type">uint MaxCycles=class="num">10; input class="type">class="kw">double pntup=class="num">0.5;class=class="str">"cmt">//Percent increase threshold input class="type">class="kw">double pntdn=class="num">0.5;class=class="str">"cmt">//Percent decrease threshold class=class="str">"cmt">//--- indicator buffers class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| NCycleGoertzelDft.mq5 | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include<GoertzelCycle.mqh> class="macro">#class="kw">property indicator_separate_window class="macro">#class="kw">property indicator_buffers class="num">5 class="macro">#class="kw">property indicator_plots class="num">5 class=class="str">"cmt">//--- plot Wave class="macro">#class="kw">property indicator_label1 "Wave" class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 clrBlack class="macro">#class="kw">property indicator_style1 STYLE_SOLID class="macro">#class="kw">property indicator_width1 class="num">1 class=class="str">"cmt">//--- plot Up class="macro">#class="kw">property indicator_label2 "Peak" class="macro">#class="kw">property indicator_type2 DRAW_NONE class=class="str">"cmt">//--- plot Dwn class="macro">#class="kw">property indicator_label3 "Trough" class="macro">#class="kw">property indicator_type3 DRAW_NONE class=class="str">"cmt">//--- plot Up class="macro">#class="kw">property indicator_label4 "Long" class="macro">#class="kw">property indicator_type4 DRAW_LINE class="macro">#class="kw">property indicator_color4 clrBlue class="macro">#class="kw">property indicator_style4 STYLE_SOLID class="macro">#class="kw">property indicator_width4 class="num">2 class=class="str">"cmt">//--- plot Dwn
◍ 给周期指标铺好五条画线轨道
在 MT5 里写自定义指标,先得把第 5 条缓冲(Short)的视觉属性钉死:标签写 "Short",画实线、红色、宽度 2。前面 0~4 号缓冲分别对应 Wave、Peak、Trough、Long、Short,共用同一套 indicator_label/type/color 声明逻辑,只是序号和颜色不同。 输入参数决定了 Goertzel 周期检测的边界与触发灵敏度:Maxper=72、Minper=5 限定搜索 5~72 根 K 线内的周期,MaxCycles=10 控制叠加谐波数量;pntup 与 pntdn 各设 0.5,代表涨跌阈值按 0.5% 百分比判定。Detrend、EndFlatten、SquaredAmp 三个布尔开关直接喂给 CGoertzelCycle 构造函数。 OnInit 里用 SetIndexBuffer 把五个 double 数组绑到 0~4 号数据缓冲,IndicatorSetInteger 把报价精度锁为 5 位,PlotIndexSetDouble 给每条线设 0.0 为空值——不画的地方直接断线而不是贴零。随后 new 一个 CGoertzelCycle 对象,若返回 NULL 就 Print 报错并返回 INIT_FAILED,否则 INIT_SUCCEEDED。 OnDeinit 只做一件事:用 CheckPointer 确认 Gc 是动态指针后 delete 掉,避免指标移除时内存泄漏。外汇与贵金属波动大、杠杆高,这类周期检测仅作辅助,实盘信号失效概率不低。
class="macro">#class="kw">property indicator_label5 "Short" class="macro">#class="kw">property indicator_type5 DRAW_LINE class="macro">#class="kw">property indicator_color5 clrRed class="macro">#class="kw">property indicator_style5 STYLE_SOLID class="macro">#class="kw">property indicator_width5 class="num">2 class=class="str">"cmt">//--- input parameters input class="type">bool Detrend=true; input class="type">bool EndFlatten=true; input class="type">bool SquaredAmp=true; input class="type">bool UseCycleStrength=false; input class="type">uint Maxper=class="num">72; input class="type">uint Minper=class="num">5; input class="type">uint MaxCycles=class="num">10; input class="type">class="kw">double pntup=class="num">0.5;class=class="str">"cmt">//Percent increase threshold input class="type">class="kw">double pntdn=class="num">0.5;class=class="str">"cmt">//Percent decrease threshold class=class="str">"cmt">//--- indicator buffers class="type">class="kw">double Wave[],Peak[],Trough[],Long[],Short[]; CGoertzelCycle *Gc; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- indicator buffers mapping SetIndexBuffer(class="num">0,Wave,INDICATOR_DATA); SetIndexBuffer(class="num">1,Peak,INDICATOR_DATA); SetIndexBuffer(class="num">2,Trough,INDICATOR_DATA); SetIndexBuffer(class="num">3,Long,INDICATOR_DATA); SetIndexBuffer(class="num">4,Short,INDICATOR_DATA); class=class="str">"cmt">//--- IndicatorSetInteger(INDICATOR_DIGITS,class="num">5); class=class="str">"cmt">//--- PlotIndexSetDouble(class="num">0,PLOT_EMPTY_VALUE,class="num">0.0); PlotIndexSetDouble(class="num">1,PLOT_EMPTY_VALUE,class="num">0.0); PlotIndexSetDouble(class="num">2,PLOT_EMPTY_VALUE,class="num">0.0); PlotIndexSetDouble(class="num">3,PLOT_EMPTY_VALUE,class="num">0.0); PlotIndexSetDouble(class="num">4,PLOT_EMPTY_VALUE,class="num">0.0); class=class="str">"cmt">//--- Gc=new CGoertzelCycle(Detrend,SquaredAmp,EndFlatten,Minper,Maxper); if(Gc==NULL) { Print("Invalid Goertzel object"); class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Indicator DeInitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { if(CheckPointer(Gc)==POINTER_DYNAMIC) class="kw">delete Gc; }
「波峰波谷判定与多空缓冲的落地循环」
这段 OnCalculate 是自定义指标的核心迭代体,负责把合成波 Wave[] 转成可视的 Peak/Trough 以及多空参考线 Long/Short。初值处理上,prev_calculated<=0 时 lim 直接拉到 Maxper*3+2,相当于用三倍最大周期加两根 K 线做预热,避免前端数据稀薄导致误判。 循环从 lim 扫到 rates_total-1,先用左右邻点比较给 Peak 和 Trough 赋值:Wave[i] 同时大于前后一根就记峰值,同时小于就记谷值,其余清零。这里 i<Maxper*3*2 的区段纯属跳过,不给 Long/Short 写值,实盘里对应着约六倍 Maxper 的‘冷启动禁区’。 越过冷启动后,在边界点 i==Maxper*3*2 调 getlastPeakTrough 抓最近峰谷:若 Wave 拐头向上且存在有效谷,则挂多线清空短线;反之挂空线清多线。其后每个 i 用‘前一根非零就续写’的规则维持 Long/Short 的连续性,并在 Short 存续且波值抬升时触发状态切换判断。 外汇与贵金属市场高杠杆、跳空频繁,这套峰谷识别在震荡市可能频繁假突破,建议把 Maxper 调小后在 MT5 策略测试器里用 EURUSD 的 M15 跑一遍,观察冷启动禁区长度对信号滞后的实际影响。
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 lim=class="num">0; if(prev_calculated<=class="num">0) lim=(class="type">int)(Maxper*class="num">3)+class="num">2; else lim=prev_calculated; class=class="str">"cmt">//--- Gc.CalculateWave(prev_calculated,rates_total,MaxCycles,UseCycleStrength,price,Wave); class=class="str">"cmt">//--- for(class="type">int i=lim;i<rates_total-class="num">1;i++) { Peak[i]=Trough[i]=class="num">0.0; if(Wave[i]>Wave[i+class="num">1] && Wave[i]>Wave[i-class="num">1]) Peak[i]=Wave[i]; else if(Wave[i]<Wave[i+class="num">1] && Wave[i]<Wave[i-class="num">1]) Trough[i]=Wave[i]; if(i<class="type">int(Maxper*class="num">3*class="num">2)) { class="kw">continue; } class="type">class="kw">double lp,lt; lp=lt=class="num">0; if(i==class="type">int(Maxper*class="num">3*class="num">2)) { lp=getlastPeakTrough(i,Peak); lt=getlastPeakTrough(i,Trough); if(Wave[i]>Wave[i-class="num">1] && lt) { Long[i]=Wave[i]; Short[i]=class="num">0.0; } else if(Wave[i]<Wave[i-class="num">1] && lp) { Short[i]=Wave[i]; Long[i]=class="num">0.0; } } else { Long[i]=(Long[i-class="num">1]!=class="num">0)?Wave[i]:Long[i-class="num">1]; Short[i]=(Short[i-class="num">1]!=class="num">0)?Wave[i]:Short[i-class="num">1]; if(Short[i-class="num">1]!=class="num">0 && Wave[i]>Wave[i-class="num">1]) {
多空翻转的阈值判定与回溯取点
这段逻辑解决的是 Wave 数组在每根 K 线上如何归属 Long 或 Short 通道。核心看两点:前一根线处于哪一侧,以及当前价相对最近峰/谷的回撤是否越过 pntup 或 pntdn 的百分比门槛(代码里除以 100,说明参数单位是百分数,例如 pntup=2 代表 2%)。 以空头转多为例:若 Short[i-1] 非零且 Wave[i] 已高于前一根,就调用 getlastPeakTrough 取最近谷值 lt;当 (Wave[i]-lt)/lt > pntup/100 时,判定为多头启动,Long[i] 写值、Short[i] 清零,否则维持空头标记。 getlastPeakTrough 是个向后扫描器:从 shift-1 往回找,直到 (Maxper*3)+2 为止,跳过 buffer 中为 0.0 的占位,返回第一个非零峰或谷值;若全程没遇到有效点,返回 0.0。这意味着若历史窗口内无锚点,阈值比较会失效,调用方必须用 lp&& / lt&& 做空值保护。 在 MT5 里把 Maxper 设为 50 时,回溯上限是 152 根;如果你的品种波动慢、峰谷间隔常超 150 根,翻转信号可能漏触发,需要调大 Maxper 或缩短统计周期。外汇与贵金属属高风险市场,该逻辑仅描述信号生成条件,不预示任何方向确定性。
lt=getlastPeakTrough(i,Trough); if(lt && (Wave[i]-lt)/lt > pntup/class="num">100 ) { Long[i]=Wave[i]; Short[i]=class="num">0.0; } else { Short[i]=Wave[i]; Long[i]=class="num">0.0; } } else if(Long[i-class="num">1]!=class="num">0 && Wave[i]<Wave[i-class="num">1]) { lp=getlastPeakTrough(i,Peak); if(lp && (lp-Wave[i])/lp > pntdn/class="num">100 ) { Short[i]=Wave[i]; Long[i]=class="num">0.0; } else { Long[i]=Wave[i]; Short[i]=class="num">0.0; } } } class=class="str">"cmt">//--- class="kw">return value of prev_calculated for next call class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| helper function that returns either last peak or trough | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double getlastPeakTrough(class="type">int shift, class="type">class="kw">double &buffer[]) { class="type">uint j; class="type">class="kw">double value=class="num">0.0; for(j=shift-class="num">1;j>(Maxper*class="num">3)+class="num">2;j--) { if(buffer[j]==class="num">0.0) class="kw">continue; else class="kw">return(buffer[j]); } class="kw">return(value); }
◍ 用周期检测给RSI装上自适应开关
把 Goertzel 周期检测塞进指标里,能让传统摆动指标跟着市场主周期变步长,而不是死守固定窗口。下面这份相对强度指标的自适应改写,就是拿 Ehlers 的思路套到 RSI 上:主导周期由 Goertzel 实时算出来,RSI 的平滑长度随之伸缩。 代码里 Maxper=72、Minper=5 这两个输入限定了周期搜索边界,意味着指标只在 5~72 根 K 线范围内抓 dominant cycle。外汇与贵金属波动跳变快,边界设太宽会让自适应变迟钝,实盘前建议在 MT5 里按品种特性收紧。 初始化段先映射两个 buffer:RSiBuffer 画线,DomPeriodBuffer 只做内部计算不显示。随后 new 一个 CGoertzelCycle 对象,若返回 NULL 直接 INIT_FAILED,避免空指针把整轮报价拖崩。开 MT5 把这段挂上 EURUSD 的 M15,看主导周期在 20~40 之间游走时 RSI 拐头是否比原版提前 1~2 根。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| AdaptiveGARSI.mq5 | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property indicator_separate_window class="macro">#include<GoertzelCycle.mqh> class="macro">#class="kw">property indicator_buffers class="num">2 class="macro">#class="kw">property indicator_plots class="num">1 class=class="str">"cmt">//--- plot RSi class="macro">#class="kw">property indicator_label1 "RSi" class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 clrRed class="macro">#class="kw">property indicator_style1 STYLE_SOLID class="macro">#class="kw">property indicator_width1 class="num">1 class=class="str">"cmt">//--- class="macro">#class="kw">property indicator_level1 class="num">70 class="macro">#class="kw">property indicator_level2 class="num">50 class="macro">#class="kw">property indicator_level3 class="num">30 class=class="str">"cmt">//--- input class="type">bool Detrend=true; input class="type">bool EndFlatten=true; input class="type">bool SquaredAmp=true; input class="type">bool UseCycleStrength=false; input class="type">uint Maxper=class="num">72; input class="type">uint Minper=class="num">5; class=class="str">"cmt">//--- indicator buffers class="type">class="kw">double RSiBuffer[]; class="type">class="kw">double DomPeriodBuffer[]; CGoertzelCycle *Gc; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- indicator buffers mapping SetIndexBuffer(class="num">0,RSiBuffer,INDICATOR_DATA); SetIndexBuffer(class="num">1,DomPeriodBuffer,INDICATOR_CALCULATIONS); class=class="str">"cmt">//--- Gc=new CGoertzelCycle(Detrend,SquaredAmp,EndFlatten,Minper,Maxper); class=class="str">"cmt">//--- if(Gc==NULL) { Print("Invalid Goertzel object"); class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED);
「主导周期下的动态 RSI 计算落点」
指标卸载时先确认 Gc 指针是否为动态分配,是则 delete 释放,避免 MT5 终端反复加载卸载时出现内存泄漏。 OnCalculate 里第一件事是调用 Gc.CalculateDominantCycles,把收盘价序列喂给主导周期检测器,结果写进 DomPeriodBuffer。这个 buffer 存的是每个 bar 当前判定出的主导周期长度,后面 RSI 的回溯窗口直接用它,而不是固定 14。 首次计算(prev_calculated<=0)时,起始下标 lim 取 Maxper*3 再加 DomPeriodBuffer 在 (Maxper*3-1) 处的值再减 1,相当于给周期检测留了 3 倍最大周期的热身余量;后续刷新则直接从 prev_calculated 继续,只算新 bar。 核心循环里,对每个 i 取 p=DomPeriodBuffer[i],向前 j 从 0 到 int(p) 累加 close[i-j]-close[i-j-1] 的正负段,分别得到 cu(上涨动能)和 cd(下跌动能)。若 cu+cd 不为 0,RSiBuffer[i]=100*cu/(cu+cd),否则填 0。外汇与贵金属波动结构多变,这种随周期伸缩的 RSI 对震荡市假突破的过滤概率可能优于固定窗口,但高频切换周期也会带来重绘,实盘前务必在 MT5 用历史数据验证 DomPeriodBuffer 的稳定性。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Indicator DeInitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { if(CheckPointer(Gc)==POINTER_DYNAMIC) class="kw">delete Gc; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator iteration function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">class="kw">datetime &time[], const class="type">class="kw">double &open[], const class="type">class="kw">double &high[], const class="type">class="kw">double &low[], const class="type">class="kw">double &close[], const class="type">long &tick_volume[], const class="type">long &volume[], const class="type">int &spread[]) { class=class="str">"cmt">//--- class="type">uint lim=class="num">0; class=class="str">"cmt">//--- Gc.CalculateDominantCycles(prev_calculated,rates_total,UseCycleStrength,close,DomPeriodBuffer); class=class="str">"cmt">//--- if(prev_calculated<=class="num">0) lim=class="type">int(Maxper*class="num">3)+class="type">int(DomPeriodBuffer[(Maxper*class="num">3)-class="num">1]) - class="num">1; else lim=prev_calculated; class=class="str">"cmt">//--- class="type">class="kw">double cu,cd; for(class="type">int i =class="type">int(lim);i<rates_total;i++) { cd=class="num">0.0; cu=class="num">0.0; class="type">class="kw">double p=DomPeriodBuffer[i]; class="type">int j=class="num">0; for(j=class="num">0;j<class="type">int(p);j++) { if(close[i-j]-close[i-j-class="num">1]>class="num">0)cu=cu+(close[i-j]-close[i-j-class="num">1]); if(close[i-j]-close[i-j-class="num">1]<class="num">0)cd=cd+(close[i-j-class="num">1]-close[i-j]); } if(cu+cd!=class="num">0) RSiBuffer[i]=class="num">100*cu/(cu+cd); else RSiBuffer[i]=class="num">0.0; } class=class="str">"cmt">//--- class="kw">return value of prev_calculated for next call class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+
把这条线请下神坛
Goertzel 算法在单频解析上的局限已经摊开:它一次只能锁一个频率,面对外汇或贵金属这种多周期叠加、主周期随时退场的盘面,单独拿出来用基本抓不准拐点。对比 MESA 那类同步估多频的框架,GA 在实际行情里明显吃亏,主周期寿命和结束位置都难量化。 附带工程文件里,Goertzel.mqh 仅 6.31 KB,GoertzelCycle.mqh 14.56 KB,NCycleGoertzelDft.mq5 与 AdaptiveGARSI.mq5 分别 6.16 KB、3.62 KB,可直接丢进 MT5 的 Mql5/include 与 indicators 目录编译验证。外汇贵金属属高风险品种,任何周期信号都只是概率倾向,别拿回测当保本凭证。 真要落地,建议把域知识、数据预处理、仓位风控和三周期监测绑一起跑,而不是神化这条单频线。开 MT5 把 AdaptiveGARSI 加载到 XAUUSD 的 H1,看动态回溯长度怎么随波动扭动,比读十遍结论都实在。