使用格兹尔算法的循环分析·进阶篇
📘

使用格兹尔算法的循环分析·进阶篇

第 2/3 篇

「进 Goertzel 前的去趋势与窗口化」

价格序列不能直接塞进 Goertzel 做频域变换。该算法本质是 DFT 的子集,非周期数据尤其要先做预处理,否则频谱泄漏的概率会明显抬高。文献里常见做法是端点平坦化:用序列首尾值 a、b 构造窗函数把末端收细,公式形式为 flat(i) = 原序列(i) - 线性插值(a,b),这一步放在窗口化之前。 比窗口化更前置的是去趋势和剔异常值。若不对原始报价做去趋势,趋势成分会失真并直接带进频域表达里。去趋势方法很多,本文用最小二乘直线拟合,只在对样本有必要才做,避免过度处理引入噪声。 下面这段是 MT5 里可直接抄用的去趋势实现。它先算 x 均值(样本中点索引),再遍历求斜率 m_slope,最后把每条数据减去「(i-中点)*斜率」得到去趋势后序列。开 MT5 新建类方法粘进去,传报价数组就能跑。 别把正态当圣经:去趋势只是预处理的一环,窗函数选型和异常值处理没做好的话,后面周期检测依然可能偏。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|               helper method for detrending data                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CGoertzelCycle::detrend(const class="type">class="kw">double &in_data[], class="type">class="kw">double &out_data[])
  {
   class="type">uint i ;
   class="type">class="kw">double xmean, ymean, x, y, xy, xx ;
   xmean=ymean=x=y=xx=xy=class="num">0.0;
   xmean=class="num">0.5*class="type">class="kw">double(in_data.Size()-class="num">1);
   if(out_data.Size()!=in_data.Size())
      ArrayResize(out_data,in_data.Size());
   for(i=class="num">0; i<out_data.Size(); i++)
     {
      x = class="type">class="kw">double(i) - xmean ;
      y = in_data[i] - ymean ;
      xx += x * x ;
      xy += x * y ;
     }
   m_slope=xy/xx;
   m_pivot=xmean;
   for(i=class="num">0; i<out_data.Size(); i++)
      out_data[i]=in_data[i]-(class="type">class="kw">double(i)-xmean)*m_slope;
  }

Goertzel 周期类的接口与参数边界

做 MT5 自定义指标时,若想从报价里扒出隐藏周期,CGoertzelCycle 是直接可用的封装类。它用 Goertzel 算法做离散频谱估计,头文件 GoertzelCycle.mqh 里包含两个构造函数:无参版走默认配置,参数化版允许你显式设定去趋势、振幅呈现方式、窗函数与周期扫描区间。 参数化构造有 5 个输入:detrend 控制是否剔除线性趋势;apply_window 决定是否加窗以减少频谱泄漏;min_period 是算法解析的最短周期,硬性下限为 2,设更低会直接失败;max_period 是最长周期,且必须严格大于 min_period;squared_amp 为 false 时振幅按平方根输出,否则输出平方幅值。 对外暴露 6 个方法,核心是两个重载的 GetSpectrum() 与 GetDominantCycle()。GetSpectrum() 第一个重载吃原始序列与输出振幅数组,配置依赖构造时设定;第二个重载把构造参数也当入参,两者成功返 true、失败返 false,错误细节进终端日志。GetDominantCycle() 类似,但多一个 use_cycle_stngth 布尔量:false 取振幅最大频率,true 则按循环强度公式排定主周期,结果降序写入输出数组末尾。 给指标用的 CalculateDominantCycles() 与 CalculateWave() 更贴近实时刷图。前者需传 prev(已算柱数)、total(图表总柱数)、in_data(报价)、out_signal(指标缓冲);后者靠 max_cycles 限制重构滤波值所用主频分量数。外汇与贵金属波动受事件驱动,周期结构可能突变,这类工具仅提供概率倾向,实盘需自担高风险。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//|   CGoertzelCycle class for cycle using the Goertzel Algorithm&nbsp;   |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class</span> CGoertzelCycle
&nbsp;&nbsp;{
<span class="keyword">class="kw">private</span>:
&nbsp;&nbsp; CGoertzel*m_ga;
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_pivot;
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_slope;
&nbsp;&nbsp; <span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_detrend;
&nbsp;&nbsp; <span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_squaredamp;
&nbsp;&nbsp; <span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_flatten;
&nbsp;&nbsp; <span class="keyword">class="type">uint</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_cycles;
&nbsp;&nbsp; <span class="keyword">class="type">uint</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_maxper,m_minper;
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_amplitude[];
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_peaks[];
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_cycle[];
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_phase[];
&nbsp;&nbsp; <span class="keyword">class="type">uint</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_peaks_index[];
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_detrended[];
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_flattened[];
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_raw[];
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;detrend(<span class="keyword">const</span> <span class="keyword">class="type">class="kw">double</span> &amp;in_data[], <span class="keyword">class="type">class="kw">double</span> &amp;out_data[]);
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wavepoint(<span class="keyword">class="type">bool</span> use_cycle_strength,<span class="keyword">class="type">uint</span> max_cycles);
&nbsp;&nbsp; <span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;spectrum(<span class="keyword">const</span> <span class="keyword">class="type">class="kw">double</span> &amp;in_data[],<span class="keyword">class="type">int</span> shift=-<span class="number">class="num">1</span>);
&nbsp;&nbsp; <span class="keyword">class="type">uint</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cyclepeaks(<span class="keyword">class="type">bool</span> use_cycle_strength);
<span class="keyword">class="kw">public</span> :
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CGoertzelCycle(<span class="keyword">class="type">void</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CGoertzelCycle(<span class="keyword">class="type">bool</span> detrend,<span class="keyword">class="type">bool</span> squared_amp,<span class="keyword">class="type">bool</span> apply_window, <span class="keyword">class="type">uint</span> min_period,<span class="keyword">class="type">uint</span> max_period);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;~CGoertzelCycle(<span class="keyword">class="type">void</span>);
&nbsp;&nbsp; <span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;GetSpectrum(<span class="keyword">const</span> <span class="keyword">class="type">class="kw">double</span> &amp;in_data[],<span class="keyword">class="type">class="kw">double</span> &amp;out_amplitude[]);
&nbsp;&nbsp; <span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;GetSpectrum(<span class="keyword">class="type">bool</span> detrend,<span class="keyword">class="type">bool</span> squared_amp,<span class="keyword">class="type">bool</span> apply_window, <span class="keyword">class="type">uint</span> min_period,<span class="keyword">class="type">uint</span> max_period,<span class="keyword">const</span> <span class="keyword">class="type">class="kw">double</span> &amp;in_data[],<span class="keyword">class="type">class="kw">double</span> &amp;out_amplitude[]);

◍ Goertzel 周期类的构造与频谱取数

CGoertzelCycle 这个类把 Goertzel 算法包了一层,专门在 MT5 里做主导周期提取。它有两个构造路径:默认构造只把 pivot、slope 清零,周期上下限和数组都置空;带参构造则直接吃进 detrend、squared_amp、apply_window 以及 min_period / max_period,并在 max_period 非零时把振幅、相位、周期、峰值等五个动态数组一次性 ArrayResize 到最大周期长度。 带参构造里那句 if(m_maxper) 很关键——若你传了 max_period=0,后面五个 ArrayResize 全部跳过,GetSpectrum 调 spectrum() 时大概率因数组未初始化直接返 false。实盘接 EURUSD 日线前,先确认 max_period 至少大于你关心的波段长度,比如设 120 就覆盖约半年日线周期。 取数接口 GetSpectrum 很薄:内部跑完 spectrum(in_data) 后,用 ArrayCopy 把 m_amplitude 拷到外部 out_amplitude,成功返 true。想看哪些周期能量强,直接接这个函数拿振幅谱,再自己挑前 N 个峰值即可,外汇与贵金属波动受事件驱动,周期 dominant 可能随流动性突变而漂移,属正常高风险现象。

MQL5 / C++
class="type">uint GetDominantCycles(class="type">bool use_cycle_strength,const class="type">class="kw">double &in_data[],class="type">class="kw">double &out_cycles[]);
class="type">uint GetDominantCycles(class="type">bool use_cycle_strenght,class="type">bool detrend,class="type">bool squared_amp,class="type">bool apply_window, class="type">uint min_period,class="type">uint max_period,const class="type">class="kw">double &in_data[],class="type">class="kw">double &out_cycles[]);
class="type">void CalculateDominantCycles(class="type">uint prev,class="type">uint total,class="type">bool use_cycle_strength,const class="type">class="kw">double &in_data[], class="type">class="kw">double &out_signal[]);
class="type">void CalculateWave(class="type">uint prev,class="type">uint total,class="type">uint max_cycles, class="type">bool use_cycle_strength,const class="type">class="kw">double &in_data[], class="type">class="kw">double &out_signal[]);
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                 Default Constructor                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
CGoertzelCycle::CGoertzelCycle(class="type">void)
  {
   m_pivot=m_slope=class="num">0.0;
   m_maxper=m_minper=m_cycles=class="num">0;
   m_detrend=m_squaredamp=m_flatten=false;
   m_ga=new CGoertzel();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|               Parametric Constructor                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
CGoertzelCycle::CGoertzelCycle(class="type">bool detrend,class="type">bool squared_amp,class="type">bool apply_window, class="type">uint min_period,class="type">uint max_period)
  {
   m_pivot=m_slope=class="num">0.0;
   m_cycles=class="num">0;
   m_maxper=max_period;
   m_minper=min_period;
   m_detrend=detrend;
   m_squaredamp=squared_amp;
   m_flatten=apply_window;
   m_ga=new CGoertzel();
   if(m_maxper)
     {
      ArrayResize(m_amplitude,m_maxper);
      ArrayResize(m_phase,m_maxper);
      ArrayResize(m_cycle,m_maxper);
      ArrayResize(m_peaks,m_maxper);
      ArrayResize(m_peaks_index,m_maxper);
     }
  }
class=class="str">"cmt">//+-------------------------------------------------------------------+
class=class="str">"cmt">//|     class="kw">public method to access the amplitude values                 |
class=class="str">"cmt">//+-------------------------------------------------------------------+
class="type">bool CGoertzelCycle::GetSpectrum(const class="type">class="kw">double &in_data[],class="type">class="kw">double &out_amplitude[])
  {
   if(spectrum(in_data))
     {
      ArrayCopy(out_amplitude,m_amplitude);
      class="kw">return true;
     }
   class="kw">return false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|      class="kw">public method to access the amplitude values                |
class=class="str">"cmt">//+------------------------------------------------------------------+

「从频谱里抠出主周期的两套入口」

Goertzel 类对外暴露了两种拿主周期的方式:先算频谱再调 GetDominantCycles,或一次性把去趋势、平方振幅、加窗等参数都塞进重载版直接出结果。后者在内部重置了 m_pivot、m_slope 为 0.0,m_cycles 清 0,并按 max_period 把五个内部数组(振幅、相位、周期、峰值、峰值索引)全 resize 一遍,避免上根 K 线的残留污染本次计算。 看 GetSpectrum 的返回逻辑:若 spectrum(in_data) 为真,就把 m_amplitude 拷给 out_amplitude 并返回 true;否则返回 false。这意味着调用方必须判返回值,不能直接信 out_amplitude 有数——实盘里若行情停牌或 in_data 长度不足,数组可能是空的。 GetDominantCycles 的非重载版先跑 spectrum,失败就 ArrayInitialize(out_cycles,0.0) 且返回 0;成功才调 cyclepeaks 找峰,并按 m_cycles 调整 out_cycles 大小,用循环把 m_cycle[m_peaks_index[i]] 依次填进去。注意它返回的是 dominant cycle 的个数,不是周期值本身,调用方拿这个数去遍历 out_cycles 才拿到具体周期。 CalculateDominantCycles 多带了 prev 和 total 参数,明显是给指标逐根刷新用的:limit 初始为 0,并用 ArrayGetAsSeries(out_signal) 探一下输出数组的时间序列方向,决定后面填充时下标怎么走。外汇与贵金属波动受消息扰动大,周期峰值可能频繁跳变,这类接口在高波动品种上需限定 min_period 不低于 8~10 根以免噪声被当主周期。

MQL5 / C++
class="type">bool CGoertzelCycle::GetSpectrum(class="type">bool detrend,class="type">bool squared_amp,class="type">bool apply_window,class="type">uint min_period,class="type">uint max_period,const class="type">class="kw">double &in_data[],class="type">class="kw">double &out_amplitude[])
  {
   m_pivot=m_slope=class="num">0.0;
   m_cycles=class="num">0;
   m_maxper=max_period;
   m_minper=min_period;
   m_detrend=detrend;
   m_squaredamp=squared_amp;
   m_flatten=apply_window;
   if(m_maxper)
     {
      ArrayResize(m_amplitude,m_maxper);
      ArrayResize(m_phase,m_maxper);
      ArrayResize(m_cycle,m_maxper);
      ArrayResize(m_peaks,m_maxper);
      ArrayResize(m_peaks_index,m_maxper);
     }
   if(spectrum(in_data))
     {
      ArrayCopy(out_amplitude,m_amplitude);
      class="kw">return true;
     }
   class="kw">return false;
  }
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class=class="str">"cmt">//|class="kw">public method to get the dominant cycle periods arranged in descending order|
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class="type">uint CGoertzelCycle::GetDominantCycles(class="type">bool use_cycle_strength,const class="type">class="kw">double &in_data[],class="type">class="kw">double &out_cycles[])
  {
   if(!spectrum(in_data))
     {
      ArrayInitialize(out_cycles,class="num">0.0);
      class="kw">return(class="num">0);
     }
   cyclepeaks(use_cycle_strength);
   if(out_cycles.Size()!=m_cycles)
     ArrayResize(out_cycles,m_cycles);
   for(class="type">uint i=class="num">0; i<m_cycles; i++)
     out_cycles[i]=m_cycle[m_peaks_index[i]];
   class="kw">return m_cycles;
  }
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class=class="str">"cmt">//|class="kw">public method to get the dominant cycle periods arranged in descending order|
class=class="str">"cmt">//+----------------------------------------------------------------------------+
class="type">uint CGoertzelCycle::GetDominantCycles(class="type">bool use_cycle_strength,class="type">bool detrend,class="type">bool squared_amp,class="type">bool apply_window,class="type">uint min_period,class="type">uint max_period,const class="type">class="kw">double &in_data[],class="type">class="kw">double &out_cycles[])
  {
   m_pivot=m_slope=class="num">0.0;
   m_cycles=class="num">0;
   m_maxper=max_period;
   m_minper=min_period;
   m_detrend=detrend;
   m_squaredamp=squared_amp;
   m_flatten=apply_window;
   if(m_maxper)
     {
      ArrayResize(m_amplitude,m_maxper);
      ArrayResize(m_phase,m_maxper);
      ArrayResize(m_cycle,m_maxper);
      ArrayResize(m_peaks,m_maxper);
      ArrayResize(m_peaks_index,m_maxper);
     }
   class="kw">return(GetDominantCycles(use_cycle_strength,in_data,out_cycles));
  }
class=class="str">"cmt">//+-----------------------------------------------------------------------+
class=class="str">"cmt">//|method used to access calculated dominant cycles , for use in indcators|
class=class="str">"cmt">//+-----------------------------------------------------------------------+
class="type">void CGoertzelCycle::CalculateDominantCycles(class="type">uint prev,class="type">uint total,class="type">bool use_cycle_strength,const class="type">class="kw">double &in_data[], class="type">class="kw">double &out_signal[])
  {
   class="type">uint limit =class="num">0;
   class="type">bool indexOut=ArrayGetAsSeries(out_signal);

波形重建时的数组索引与边界处理

在 MT5 自定义指标里调用 CGoertzelCycle::CalculateWave 时,先靠 ArrayGetAsSeries 判定输入输出数组是时间序(最新 Bar 在 0 号)还是普通序。这一步直接决定后面循环的方向和索引映射,写错就会把信号画到历史反方向。 首次计算(prev<=0)要预留足够历史:若输出为时间序,limit 取 total - m_maxper*3,首值写在 limit+1;否则 limit 取 m_maxper*3,首值写在 limit-1。也就是至少留出最大周期 3 倍的缓冲,否则频谱在头部会塌掉。 非首次只跑新增部分:limit = (indexOut)? total-prev : prev,避免每 tick 重算全量。下面这段是时间序分支的核心循环,注意 spectrum 的索引做了翻转兼容。

MQL5 / C++
class="type">bool indexIn=ArrayGetAsSeries(in_data);
if(prev<=class="num">0)
  {
  class="type">uint firstindex=class="num">0;
  if(indexOut)
    {
    limit=total-(m_maxper*class="num">3);
    firstindex=limit+class="num">1;
    }
  else
    {
    limit=m_maxper*class="num">3;
    firstindex=limit-class="num">1;
    }
  out_signal[firstindex]=class="num">0;
  }
else
  {
  limit=(indexOut)?total-prev:prev;
  }
class="type">uint found_cycles=class="num">0;
if(indexOut)
  {
  for(class="type">int ibar=(class="type">int)limit; ibar>=class="num">0; ibar--)
    {
    spectrum(in_data,(indexIn)?ibar:total-ibar-class="num">1);
    out_signal[ibar]=out_signal[ibar+class="num">1]+wavepoint(use_cycle_strength,max_cycles);
    }
  }
逐行拆解:第1行取输入数组序向;prev<=0 进首算分支,firstindex 按输出序向定首写位置;limit 用 m_maxper*3 留缓冲;首值置 0 避免累积漂移。非首算时 limit 只框新增量。循环里 spectrum 的参数是 (indexIn)? ibar : total-ibar-1,把时间序输入翻成频谱函数要的普通序;out_signal[ibar] 用前一个 Bar 值加 wavepoint 增量,逐步重建波形。 外汇与贵金属杠杆高、滑点随机,这套波形只作周期参考,实盘信号可能失效,请用 MT5 策略测试器跑 EURUSD 15M 验证缓冲长度是否够用。

MQL5 / C++
class="type">bool indexIn=ArrayGetAsSeries(in_data);
if(prev<=class="num">0)
  {
  class="type">uint firstindex=class="num">0;
  if(indexOut)
    {
    limit=total-(m_maxper*class="num">3);
    firstindex=limit+class="num">1;
    }
  else
    {
    limit=m_maxper*class="num">3;
    firstindex=limit-class="num">1;
    }
  out_signal[firstindex]=class="num">0;
  }
else
  {
  limit=(indexOut)?total-prev:prev;
  }
class="type">uint found_cycles=class="num">0;
if(indexOut)
  {
  for(class="type">int ibar=(class="type">int)limit; ibar>=class="num">0; ibar--)
    {
    spectrum(in_data,(indexIn)?ibar:total-ibar-class="num">1);
    out_signal[ibar]=out_signal[ibar+class="num">1]+wavepoint(use_cycle_strength,max_cycles);
    }
  }

◍ 逐根 K 线回填信号数组的写法

这段循环是频谱合成落到图表上的最后一步:从 limit 到 total 逐根处理,把每个 bar 的波点累加进 out_signal。 for(int ibar=(int)limit; ibar<(int)total; ibar++) 定义遍历区间,limit 通常为已计算起点,total 是数据总数。spectrum(...) 按正向或反向索引取当前 bar 的频谱分量;out_signal[ibar]=out_signal[ibar-1]+wavepoint(...) 则把上一根的信号值加上本根波点,形成连续累加曲线。 在 MT5 里把这段接在你自己的指标 OnCalculate 末尾,改 limit 为已算 bars 数,就能只刷新增量、不重算全历史,回测时 CPU 占用会明显掉一截。外汇与贵金属杠杆高,信号曲线仅作概率参考,实盘须自担风险。

MQL5 / C++
for(class="type">int ibar=(class="type">int)limit; ibar<(class="type">int)total; ibar++)
  {
   spectrum(in_data,(indexIn)?total-ibar-class="num">1:ibar);
   out_signal[ibar]=out_signal[ibar-class="num">1]+wavepoint(use_cycle_strength,max_cycles);
  }
  }
 }
class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

不去趋势会让低频漂移淹没真实周期,频谱主峰偏向长周期。先差分或减均线再送进算法,主周期识别更准。
窗口长度至少要覆盖你怀疑的主周期的 1.5 倍,否则频率分辨率不够,取数会漏峰。实战中低于 30 根通常无意义。
可以。小布盯盘的 AIGC 已内置循环诊断,打开对应品种页就能看到自动算出的主周期与波形重建,不用自己写代码。
峰值搜索对单峰干净信号快,相位导数在重叠周期时更抗误判。建议两套入口都验一遍再下单。
多在末尾几根 K 线多用了一个窗口长度的位置。循环上限设为 Bars-Window-1,并在写数组前判 i>=0 即可避坑。