使用格兹尔算法的循环分析·综合运用
📡

使用格兹尔算法的循环分析·综合运用

(3/3)·把离散傅里叶单项检测搬进MT5,用代码抓价格主导周期而非盲猜波段

含代码示例实战向 第 3/3 篇
很多交易者把傅里叶类工具当黑箱,直接加载指标看彩线,却不知道频仓分辨率受样本数N死死限制。用错周期窗口,所谓“主导周期”只是统计幻觉。本文收尾篇带你把算法拆开,自己控参数。

用Goertzel把价格噪声压成波形

自适应周期Goertzel DFT的思路,是从价格的滑动窗口里抓前10个主导周期,再拼回一条新曲线。这条曲线被认为更接近去掉噪声后的真实价格行为,外汇和贵金属这种高频噪音多的品种,滤波前后差别可能很明显。 关键参数就三个:采样频率的最小周期、最大周期,以及用来构造滤波信号的最大频率分量数。原文默认最大周期72、最小周期5、最多取10个分量,移动窗口大小等于最大周期的三倍,也就是216根K线。 信号触发靠两个阈值:pntup和pntdn,单位是百分比,默认都是0.5。曲线相对最近波峰或波谷的移动幅度超过这个值,才可能给出多或空指示。外汇贵金属波动剧烈,阈值过小容易反复假触发,实盘前建议在MT5里调大观察。 波点就是曲线在对应索引处的值,代码里Wave缓冲画出黑线,Peak和Trough设为DRAW_NONE只用于内部计算,Long画蓝线宽度2。直接把下面代码丢进MT5指标目录编译,就能看到分离窗口里的波形。

MQL5 / C++
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 掉,避免指标移除时内存泄漏。外汇与贵金属波动大、杠杆高,这类周期检测仅作辅助,实盘信号失效概率不低。

MQL5 / C++
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 跑一遍,观察冷启动禁区长度对信号滞后的实际影响。

MQL5 / C++
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 或缩短统计周期。外汇与贵金属属高风险市场,该逻辑仅描述信号生成条件,不预示任何方向确定性。

MQL5 / C++
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 根。

MQL5 / C++
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 的稳定性。

MQL5 / C++
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,看动态回溯长度怎么随波动扭动,比读十遍结论都实在。

把周期扫描交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时格兹尔频仓输出,你只需判断哪条谱线值得信。

常见问题

FFT算全部频仓,Goertzel只算你指定的少数k值,样本量大时更省算力,适合只盯几段候选周期的交易者。
离散化天然限制,J.Ehlers用MESA缓解;本篇实战里可用零填充或重叠窗口近似,但边界仍可能偏。
倾向不行。周期长度是统计均值,价格在高波动段会偏离,外汇贵金属高风险,需另设波动过滤器。
能。小布盯盘的品种页内置了AIGC周期诊断,不需要你自己写MQL5,打开就看到候选谱峰和置信。
用来验证代码没写错:已知频率注入噪声,看算法能否还原,再上真实报价才不容易被假峰骗。