English Русский Español Deutsch 日本語 Português
preview
使用格兹尔算法的循环分析

使用格兹尔算法的循环分析

MetaTrader 5指标 | 16 二月 2024, 14:13
575 0
Francis Dube
Francis Dube


概述

格兹尔算法(Goertzel algorithm)是一种数字信号处理技术,以其在检测特定频率分量方面的效率而闻名。其精度、实时性和计算效率使其适用于金融时间序列分析。在这篇文章中,我们将研究并演示该方法可用于分析主导周期,从而可能以此开发策略。我们将研究MQL5中算法的实现,并提供一个如何使用代码来识别价格报价中的周期的示例。

格兹尔算法

Goertzel算法的名字来源于Gerald Goertzel,它被用来以有效的方式计算离散傅立叶变换(Discrete Fourier Transform,DFT)的各个项。这项技术最初于1958年引入,后来被应用于各个领域,包括工程、数学和物理。Goertzel算法的主要应用是识别数字信号中的特定频率分量,这使得它在只有少数频率分量很重要的情况下非常有价值。与快速傅立叶变换(Fast Fourier Transform,FFT)相比,当检测有限数量的频率分量时,它需要更少的计算,使其在计算上高效。

它由以下公式表示:

Goertzel 公式

其中:- X是频率k时的累积幅度
- cos()是余弦函数
- π是数学常数pi(约3.14159)
- k是您感兴趣的频率仓的索引(范围从0到N-1,其中N是样本总数)
- N 是输入信号的长度- X[k-1]和X[k-2]是在k时频率的X的先前计算值
- x[n]是输入信号的第n个样本

要使用Goertzel算法计算实部和虚部,我们需要迭代输入信号样本,并执行以下计算:

  • 初始化变量:
    • N:输入信号中的采样数。
    • k:感兴趣的频率仓的索引(0<=k<N)。
    • omega:与期望频率 bin(2*pi*k/N)相对应的频率。
    • coeff:计算中使用的系数(2*cos(omega))。
    • s_prev:“state”变量的上一个值。
    • s_prev2:“state”变量的上一个值之前的值。
    • s:“state”变量的当前值。
  • 对输入信号中的每个样本进行迭代:
    • 使用以下公式更新“state”变量的当前值:

      初始化的 State 变量

  • 其中x[n]是当前输入样本。

    更新“state”变量以前的值:

    更新上一个state变量

    更新上一个state变量

    在迭代所有样本之后,“state”变量(S,Sprev,Sprev2)的最终值表示在所需频率bin(k)处DFT的实部和虚部。

    实部由以下公式给出:
    实分量公式

    虚部由下式给出:

    虚分量公式

    DFT可以检测到的频率范围从1/N到(N/2)/N,其中N表示序列中数据点的数量,或者在我们的情况下表示正在分析的价格柱的数量。在分析报价时,它可能被限制为只能观察到限制在1/N间距内的频带。这就是J.Ehlers等人提出最大熵谱分析(MESA)技术来克服这一限制的原因。

    Goertzel算法可以作为Ehler的MESA的替代方案,根据D.Meyers撰写的一篇研究论文,该算法能够在某些条件下获得更好的结果(就光谱分辨率而言)。这些条件中的一个恰好与信号中包含的噪声量有关。根据Meyers的说法,goertzel算法能够优于MESA技术,尤其是在处理噪声信号时,这是金融时间序列的一个常见问题。感兴趣的读者可以阅读pdf格式的白皮书。

    CGoertzel 类

    CGoertzel类是goertzel算法的一个简单实现,该算法能够在数据集上对一系列频率进行采样。

    //+------------------------------------------------------------------+
    //| CGoertze class implementing Goertzel algorithm                   |
    //+------------------------------------------------------------------+
    class CGoertzel
      {
    private:
       uint              m_minPeriod;
       uint              m_maxPeriod;
    public:
       //constructor
                         CGoertzel(void)
         {
          m_minPeriod=m_maxPeriod=0;
         }
       //destructor
                        ~CGoertzel(void)
         {
         }
       //get methods
       uint              GetMinPeriodLength(void) {   return m_minPeriod;}
       uint              GetMaxPerodLength(void) {   return m_maxPeriod;}
       //set methods
       bool              SetMinMaxPeriodLength(const uint min_wavePeriod, const uint max_wavePeriod);
       // goertzel dft transform methods
       bool              Dft(const double &in_data[],complex &out[]);
       bool              Dft(const double &in_data[], double &out_real[], double &out_imaginary[]);
       bool              Dft(const uint min_wavePeriod,const uint max_wavePeriod,const double &in_data[],complex &out[]);
       bool              Dft(const uint min_wavePeriod,const uint max_wavePeriod,const double &in_data[], double &out_real[], double &out_imaginary[]);
      };
    

    由于goertzel一次只采样有限的频率,我们必须设置我们感兴趣的频带。在该类中,频带是根据频率的最小和最大周期来设置的。

    有两种方法可以设置这些值:使用SetMinMaxPeriodLength(),可以指定最小周期和最大周期。

    //+------------------------------------------------------------------+
    //| Set the Minimum and maximum periods of selected frequency band   |
    //+------------------------------------------------------------------+
    bool CGoertzel::SetMinMaxPeriodLength(const uint min_wavePeriod,const uint max_wavePeriod)
      {
       if(min_wavePeriod<2 || min_wavePeriod>=max_wavePeriod)
         {
          Print("Critical error min_wavePeriod cannot be less than max_wavePeriod or less than 2");
          return false;
         }
    
       m_minPeriod=min_wavePeriod;
       m_maxPeriod=max_wavePeriod;
    
       return true;
      }

    此外,当调用Dft方法的两个重载方法之一时,可以设置频带。

    //+-----------------------------------------------------------------------------------+
    //| Calculate the frequency domain representation of input array as complex array     |
    //+-----------------------------------------------------------------------------------+
    bool CGoertzel::Dft(const uint min_wavePeriod,const uint max_wavePeriod,const double &in_data[],complex &out[])
      {
       if(!SetMinMaxPeriodLength(min_wavePeriod,max_wavePeriod))
          return(false);
    
       return Dft(in_data,out);
      }
    //+------------------------------------------------------------------------------------+
    //| Calculate the frequency domain representation of input array as two separate arrays|
    //+------------------------------------------------------------------------------------+
    bool CGoertzel::Dft(const uint min_wavePeriod,const uint max_wavePeriod,const double &in_data[], double &out_real[], double &out_imaginary[])
      {
       if(!SetMinMaxPeriodLength(min_wavePeriod,max_wavePeriod))
          return(false);
    
       return Dft(in_data,out_real,out_imaginary);
    
      }
    

    根据所使用的Dft方法的版本,Dft的实部和虚部以单独的阵列或典型复数的单个阵列输出。

    //+-----------------------------------------------------------------------------------+
    //| Calculate the frequency domain representation of input array as complex array     |
    //+-----------------------------------------------------------------------------------+
    bool CGoertzel::Dft(const double &in_data[],complex &out[])
      {
       uint minsize=(3*m_maxPeriod);
       uint fullsize=in_data.Size();
    
       if(fullsize<minsize)
         {
          Print("Sample size too small in relation to the largest period cycle parameter");
          return false;
         }
    
       if(m_minPeriod>=m_maxPeriod || m_minPeriod<2)
         {
          Print("Critical error: Invalid input parameters :- max_period should be larger than min_period and min_period cannot be less than 2");
          return false;
         }
    
       if(out.Size()!=m_maxPeriod)
          ArrayResize(out,m_maxPeriod);
       
       double v0,v1,v2,freq,coeff,real,imag;
    
    
       for(uint i=0; i<m_maxPeriod; i++)
         {
          if(i<m_minPeriod)
            {
             out[i].imag=out[i].real=0.0;
             continue;
            }
    
          v0=v1=v2=0.0;
          freq=MathPow(i,-1);
          coeff=2.0*MathCos(2.0*M_PI*freq);
          for(uint k=minsize-1; k>0; k--)
            {
             v0=coeff*v1-v2+in_data[k];
             v2=v1;
             v1=v0;
            }
    
          real=v1-v2*0.5*coeff;
    
          imag=v2*MathSin(2*M_PI*freq);
    
          out[i].real=real;
          out[i].imag=imag;
    
         }
    
       return true;
      }
    //+------------------------------------------------------------------------------------+
    //| Calculate the frequency domain representation of input array as two separate arrays|
    //+------------------------------------------------------------------------------------+
    bool CGoertzel::Dft(const double &in_data[],double &out_real[],double &out_imaginary[])
      {
       uint minsize=(3*m_maxPeriod);
       uint fullsize=in_data.Size();
    
       if(fullsize<minsize)
         {
          Print("Sample size too small in relation to the largest period cycle parameter");
          return false;
         }
    
       if(m_minPeriod>=m_maxPeriod || m_minPeriod<2)
         {
          Print("Critical error: Invalid input parameters :- max_period should be larger than min_period and min_period cannot be less than 2");
          return false;
         }
    
       if(out_real.Size()!=m_maxPeriod)
          ArrayResize(out_real,m_maxPeriod);
    
       if(out_imaginary.Size()!=m_maxPeriod)
          ArrayResize(out_imaginary,m_maxPeriod);
    
       double v0,v1,v2,freq,coeff,real,imag;
    
    
       for(uint i=0; i<m_maxPeriod; i++)
         {
          if(i<m_minPeriod)
            {
             out_real[i]=out_imaginary[i]=0.0;
             continue;
            }
    
          v0=v1=v2=0.0;
          freq=MathPow(i,-1);
          coeff=2.0*MathCos(2.0*M_PI*freq);
          for(uint k=minsize-1; k>0; k--)
            {
             v0=coeff*v1-v2+in_data[k];
             v2=v1;
             v1=v0;
            }
    
          real=v1-v2*0.5*coeff;
    
          imag=v2*MathSin(2*M_PI*freq);
    
          out_real[i]=real;
          out_imaginary[i]=imag;
    
         }
    
       return true;
      }
    

    预处理

    数据不能总是简单地插入goertzel算法并转换到频域中。根据数据样本的性质,可能需要执行一些预处理步骤。Goertzel算法是dft的子集,因此在执行快速傅立叶变换之前所需的变换也适用于此。对于非周期性数据尤其如此。这样的系列需要用适当的窗口函数来处理。这将使该系列的末端充分变细,从而将频谱泄漏的可能性降至最低。Goertzel在文献中的大多数应用通常采用端点平坦化算法。该技术的实现由以下公式给出:

    公式6

    公式7

    公式8

    其中a是要分析的序列中的第一个价格值,b是最后一个,flat(i)是将传递给goertzel算法的序列。

    在窗口化数据之前,最好去除任何明显的趋势和异常值。应该对原始数据进行解压缩,但仅在必要时进行解压缩。未排列的去趋势会导致失真,失真将被带到样本的频域表示上。去趋势是一个广泛的话题,可以采用多种类型的去趋势方法。每一种都有自己的优点和缺点。从业者应该努力彻底调查最合适的应用方法。为了本文的目的,我们将使用一个简单的最小二乘拟合来给报价去趋势。代码如下所示。

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

    CGoertzelCycle 类

    包含文件GoertzelCycle.mqh将包含CGoertzelCycle类,该类用于使用goertzel算法对数据集进行循环分析。

    //+------------------------------------------------------------------+
    //|   CGoertzelCycle class for cycle using the Goertzel Algorithm    |
    //+------------------------------------------------------------------+
    class CGoertzelCycle
      {
    private:
       CGoertzel*m_ga;
       double            m_pivot;
       double            m_slope;
       bool              m_detrend;
       bool              m_squaredamp;
       bool              m_flatten;
       uint              m_cycles;
       uint              m_maxper,m_minper;
       double            m_amplitude[];
       double            m_peaks[];
       double            m_cycle[];
       double            m_phase[];
       uint              m_peaks_index[];
       double            m_detrended[];
       double            m_flattened[];
       double            m_raw[];
    
    
       void              detrend(const double &in_data[], double &out_data[]);
       double            wavepoint(bool use_cycle_strength,uint max_cycles);
       bool              spectrum(const double &in_data[],int shift=-1);
       uint              cyclepeaks(bool use_cycle_strength);
    
    public :
                         CGoertzelCycle(void);
                         CGoertzelCycle(bool detrend,bool squared_amp,bool apply_window, uint min_period,uint max_period);
                        ~CGoertzelCycle(void);
    
    
       bool              GetSpectrum(const double &in_data[],double &out_amplitude[]);
       bool              GetSpectrum(bool detrend,bool squared_amp,bool apply_window, uint min_period,uint max_period,const double &in_data[],double &out_amplitude[]);
       uint              GetDominantCycles(bool use_cycle_strength,const double &in_data[],double &out_cycles[]);
       uint              GetDominantCycles(bool use_cycle_strenght,bool detrend,bool squared_amp,bool apply_window, uint min_period,uint max_period,const double &in_data[],double &out_cycles[]);
       void              CalculateDominantCycles(uint prev,uint total,bool use_cycle_strength,const double &in_data[], double &out_signal[]);
       void              CalculateWave(uint prev,uint total,uint max_cycles, bool use_cycle_strength,const double &in_data[], double &out_signal[]);
      };

    它有两个构造函数。参数化构造函数允许使用自定义设置初始化实例。

    //+------------------------------------------------------------------+
    //|                     Default Constructor                          |
    //+------------------------------------------------------------------+
    CGoertzelCycle::CGoertzelCycle(void)
      {
       m_pivot=m_slope=0.0;
       m_maxper=m_minper=m_cycles=0;
       m_detrend=m_squaredamp=m_flatten=false;
       m_ga=new CGoertzel();
      }
    //+------------------------------------------------------------------+
    //|                      Parametric Constructor                      |
    //+------------------------------------------------------------------+
    CGoertzelCycle::CGoertzelCycle(bool detrend,bool squared_amp,bool apply_window, uint min_period,uint max_period)
      {
       m_pivot=m_slope=0.0;
       m_cycles=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);
         }
    
      }

    输入参数如下所示:

    • detrend - 一个布尔型输入参数,指示需要删除趋势。
    • apply_window -另一个布尔参数,指定是否应应用窗口函数。
    • min_period -此参数定义goertzel算法将解析的最短周期或最高频率。请注意,此值不能设置为低于2。
    • max_period -分析过程中考虑的最长周期。此值表示周期最长的频率,不能小于或等于min_period。
    • squared_amp -指示振幅值应该如何呈现。选择false会将振幅计算为平方根。

    该类有6个用户应该感兴趣的可访问方法。

    //+-------------------------------------------------------------------+
    //|          public method to access the amplitude values             |
    //+-------------------------------------------------------------------+
    bool CGoertzelCycle::GetSpectrum(const double &in_data[],double &out_amplitude[])
      {
       if(spectrum(in_data))
         {
          ArrayCopy(out_amplitude,m_amplitude);
          return true;
         }
       return false;
      }

    GetSpectrum()需要2个输入数组,其中第一个in_data是要分析的原始序列。第二个数组是输出振幅值的地方。应该通过使用参数化构造函数初始化实例来指定用于执行转换的参数。

    //+------------------------------------------------------------------+
    //|         public method to access the amplitude values             |
    //+------------------------------------------------------------------+
    bool CGoertzelCycle::GetSpectrum(bool detrend,bool squared_amp,bool apply_window,uint min_period,uint max_period,const double &in_data[],double &out_amplitude[])
      {
       m_pivot=m_slope=0.0;
       m_cycles=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);
          return true;
         }
       return false;
    
      }

    另一个重载的GetSpectrum()方法采用与参数化构造函数相同的附加参数。两者都在成功时返回 true,在失败时返回 false。任何错误数据都将写入终端的日志。

    //+----------------------------------------------------------------------------+
    //|public method to get the dominant cycle periods arranged in descending order|
    //+----------------------------------------------------------------------------+
    uint CGoertzelCycle::GetDominantCycles(bool use_cycle_strength,const double &in_data[],double &out_cycles[])
      {
       if(!spectrum(in_data))
         {
          ArrayInitialize(out_cycles,0.0);
          return(0);
         }
    
       cyclepeaks(use_cycle_strength);
    
       if(out_cycles.Size()!=m_cycles)
          ArrayResize(out_cycles,m_cycles);
    
       for(uint i=0; i<m_cycles; i++)
          out_cycles[i]=m_cycle[m_peaks_index[i]];
    
       return m_cycles;
    
      }
    //+----------------------------------------------------------------------------+
    //|public method to get the dominant cycle periods arranged in descending order|
    //+----------------------------------------------------------------------------+
    uint CGoertzelCycle::GetDominantCycles(bool use_cycle_strength,bool detrend,bool squared_amp,bool apply_window,uint min_period,uint max_period,const double &in_data[],double &out_cycles[])
      {
       m_pivot=m_slope=0.0;
       m_cycles=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);
         }
    
       return(GetDominantCycles(use_cycle_strength,in_data,out_cycles));
    
      }

    GetDominantCycle() 方法也会被重载,与GetSpectrum() 类似。除了这两个数组之外,还需要指定一个布尔参数use_cycle_stngth,它指出了确定优势周期的标准。设置为false意味着使用振幅最大的频率,而备选方案通过计算公式给出的循环强度来确定主循环:

    公式9

    主循环以降序排列的值输出到输入阵列的最后一个。

    //+-----------------------------------------------------------------------+
    //|method used to access calculated dominant cycles , for use in indcators|
    //+-----------------------------------------------------------------------+
    void CGoertzelCycle::CalculateDominantCycles(uint prev,uint total,bool use_cycle_strength,const double &in_data[], double &out_signal[])
      {
    
       uint limit =0;
       bool indexOut=ArrayGetAsSeries(out_signal);
       bool indexIn=ArrayGetAsSeries(in_data);
    
       if(prev<=0)
         {
          uint firstindex=0;
          if(indexOut)
            {
             limit=total-(m_maxper*3);
             firstindex=limit+1;
            }
          else
            {
             limit=m_maxper*3;
             firstindex=limit-1;
            }
          out_signal[firstindex]=0;
         }
       else
         {
          limit=(indexOut)?total-prev:prev;
         }
    
       uint found_cycles=0;
       if(indexOut)
         {
          for(int ibar=(int)limit; ibar>=0; ibar--)
            {
             spectrum(in_data,(indexIn)?ibar:total-ibar-1);
             out_signal[ibar]=(cyclepeaks(use_cycle_strength))?m_cycle[m_peaks_index[0]]:0.0;
            }
         }
       else
         {
          for(int ibar=(int)limit; ibar<(int)total; ibar++)
            {
             spectrum(in_data,(indexIn)?total-ibar-1:ibar);
             out_signal[ibar]=(cyclepeaks(use_cycle_strength))?m_cycle[m_peaks_index[0]]:0.0;
            }
         }
    
    
      }

    前缀为Calculate的方法最好用于指标。CalculateDominantCycles()输出对应条形图的主循环。prev应当被设置为先前计算的指示符值的数目。Total应该是图表上可用条形图的数量。in_data是传入报价的地方,out_signal应该是指标缓冲区之一。

    //+-----------------------------------------------------------------------+
    //|method used to access newly formed wave form, for use in indcators     |
    //+-----------------------------------------------------------------------+
    void CGoertzelCycle::CalculateWave(uint prev,uint total,uint max_cycles,bool use_cycle_strength,const double &in_data[], double &out_signal[])
      {
       uint limit =0;
       bool indexOut=ArrayGetAsSeries(out_signal);
       bool indexIn=ArrayGetAsSeries(in_data);
    
       if(prev<=0)
         {
          uint firstindex=0;
          if(indexOut)
            {
             limit=total-(m_maxper*3);
             firstindex=limit+1;
            }
          else
            {
             limit=m_maxper*3;
             firstindex=limit-1;
            }
          out_signal[firstindex]=0;
         }
       else
         {
          limit=(indexOut)?total-prev:prev;
         }
    
       uint found_cycles=0;
       if(indexOut)
         {
          for(int ibar=(int)limit; ibar>=0; ibar--)
            {
             spectrum(in_data,(indexIn)?ibar:total-ibar-1);
             out_signal[ibar]=out_signal[ibar+1]+wavepoint(use_cycle_strength,max_cycles);
            }
         }
       else
         {
          for(int ibar=(int)limit; ibar<(int)total; ibar++)
            {
             spectrum(in_data,(indexIn)?total-ibar-1:ibar);
             out_signal[ibar]=out_signal[ibar-1]+wavepoint(use_cycle_strength,max_cycles);
            }
         }
    
      }
    //+------------------------------------------------------------------+

    CalculateWave()的大部分参数与CalculateDominantCycles()不相关。它输出根据goertzel算法揭示的主频分量计算的滤波值。频率分量的最大数量由max_cycles参数设置。

    波形构造

    为了演示类的使用以及goertzel在策略开发中的可能应用,我们将提出两个指标。第一个是Dennis Meyers撰写的白皮书中提出的策略的实现。他将这种策略称为自适应10周期Goertzel DFT系统。有关该策略的确切细节可在该文件中查阅。简而言之,该策略使用goertzel从价格数据的移动窗口中提取前十个主导周期,然后使用这些周期构建新的价格曲线。据推测,这代表了噪音被过滤掉后的价格行为。该策略的关键参数是要采样的频率的最小周期和最大周期,连同要用于构造滤波信号的最大数量的频率分量。

    //--- input parameters
    input bool     Detrend=true;
    input bool     EndFlatten=true; 
    input bool     SquaredAmp=true;
    input bool     UseCycleStrength=false;
    input uint     Maxper=72;
    input uint     Minper=5;
    input uint     MaxCycles=10;
    input double   pntup=0.5;//Percent increase threshold
    input double   pntdn=0.5;//Percent decrease threshold
    //--- indicator buffers

    该指标使用两个阈值来触发长信号和短信号,这取决于相对于最近峰值或波谷的移动幅度。在代码中,输入变量pntuppntdn表示百分比。

    //+------------------------------------------------------------------+
    //|                                            NCycleGoertzelDft.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #include<GoertzelCycle.mqh>
    #property indicator_separate_window
    #property indicator_buffers 5
    #property indicator_plots   5 
    //--- plot Wave
    #property indicator_label1  "Wave"
    #property indicator_type1   DRAW_LINE
    #property indicator_color1  clrBlack
    #property indicator_style1  STYLE_SOLID
    #property indicator_width1  1
    //--- plot Up
    #property indicator_label2  "Peak"
    #property indicator_type2   DRAW_NONE
    //--- plot Dwn
    #property indicator_label3  "Trough"
    #property indicator_type3   DRAW_NONE
    //--- plot Up
    #property indicator_label4  "Long"
    #property indicator_type4   DRAW_LINE
    #property indicator_color4  clrBlue
    #property indicator_style4  STYLE_SOLID
    #property indicator_width4  2
    //--- plot Dwn
    #property indicator_label5  "Short"
    #property indicator_type5   DRAW_LINE
    #property indicator_color5  clrRed
    #property indicator_style5  STYLE_SOLID
    #property indicator_width5  2
    //--- input parameters
    input bool     Detrend=true;
    input bool     EndFlatten=true; 
    input bool     SquaredAmp=true;
    input bool     UseCycleStrength=false;
    input uint     Maxper=72;
    input uint     Minper=5;
    input uint     MaxCycles=10;
    input double   pntup=0.5;//Percent increase threshold
    input double   pntdn=0.5;//Percent decrease threshold
    //--- indicator buffers
    
    double  Wave[],Peak[],Trough[],Long[],Short[];
    CGoertzelCycle *Gc;
    //+------------------------------------------------------------------+
    //| Custom indicator initialization function                         |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- indicator buffers mapping
       SetIndexBuffer(0,Wave,INDICATOR_DATA);
       SetIndexBuffer(1,Peak,INDICATOR_DATA);
       SetIndexBuffer(2,Trough,INDICATOR_DATA);
       SetIndexBuffer(3,Long,INDICATOR_DATA);
       SetIndexBuffer(4,Short,INDICATOR_DATA);
    //---   
       IndicatorSetInteger(INDICATOR_DIGITS,5);
    //---   
       PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0);
       PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0.0);
       PlotIndexSetDouble(2,PLOT_EMPTY_VALUE,0.0);
       PlotIndexSetDouble(3,PLOT_EMPTY_VALUE,0.0);
       PlotIndexSetDouble(4,PLOT_EMPTY_VALUE,0.0);
    //---
       Gc=new CGoertzelCycle(Detrend,SquaredAmp,EndFlatten,Minper,Maxper);
       
       if(Gc==NULL)
        {
         Print("Invalid Goertzel object");
         return(INIT_FAILED);
        }  
    //---     
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+
    //| Indicator DeInitialization function                              |
    //+------------------------------------------------------------------+
    void OnDeinit (const int reason)
     {
      if(CheckPointer(Gc)==POINTER_DYNAMIC)
          delete Gc;
     }  
    //+------------------------------------------------------------------+
    //| Custom indicator iteration function                              |
    //+------------------------------------------------------------------+
    int OnCalculate(const int rates_total,
                    const int prev_calculated,
                    const int begin,
                    const double &price[])
      {
       int lim=0;
       
       if(prev_calculated<=0)
          lim=(int)(Maxper*3)+2;
       else 
          lim=prev_calculated;
       
       
    //---
        Gc.CalculateWave(prev_calculated,rates_total,MaxCycles,UseCycleStrength,price,Wave);
    //---
       for(int i=lim;i<rates_total-1;i++)
          {
           Peak[i]=Trough[i]=0.0;   
           
           if(Wave[i]>Wave[i+1] && Wave[i]>Wave[i-1])
             Peak[i]=Wave[i];
           else 
             if(Wave[i]<Wave[i+1] && Wave[i]<Wave[i-1])
               Trough[i]=Wave[i];
             
           if(i<int(Maxper*3*2)) 
              {
               continue;
              } 
              
           double lp,lt;
              lp=lt=0;  
                   
           if(i==int(Maxper*3*2))
             {
              lp=getlastPeakTrough(i,Peak);
              lt=getlastPeakTrough(i,Trough);
              if(Wave[i]>Wave[i-1] && lt)
                {
                 Long[i]=Wave[i];
                 Short[i]=0.0;
                } 
              else
                if(Wave[i]<Wave[i-1] && lp)
                  {
                   Short[i]=Wave[i];
                   Long[i]=0.0;
                  } 
             }
           else
             {
             
               Long[i]=(Long[i-1]!=0)?Wave[i]:Long[i-1];
               Short[i]=(Short[i-1]!=0)?Wave[i]:Short[i-1];
              
              if(Short[i-1]!=0 && Wave[i]>Wave[i-1])
               {
                lt=getlastPeakTrough(i,Trough);
                if(lt && (Wave[i]-lt)/lt > pntup/100 )
                  {
                   Long[i]=Wave[i];
                   Short[i]=0.0;
                  }
                else
                  {
                   Short[i]=Wave[i];
                   Long[i]=0.0;
                  }    
               }
              else
               if(Long[i-1]!=0 && Wave[i]<Wave[i-1])
               {
                lp=getlastPeakTrough(i,Peak);
                if(lp && (lp-Wave[i])/lp > pntdn/100 )
                  {
                    Short[i]=Wave[i];
                    Long[i]=0.0;
                  }
                 else
                  {
                    Long[i]=Wave[i];
                    Short[i]=0.0;
                  }    
               }
             }  
          
          }
    //--- return value of prev_calculated for next call
       return(rates_total);
      }
    //+------------------------------------------------------------------+
    //| helper function that returns either last peak or trough          |
    //+------------------------------------------------------------------+
    double getlastPeakTrough(int shift, double &buffer[])
      {
       uint j;
       double value=0.0;
       for(j=shift-1;j>(Maxper*3)+2;j--)
         {
          if(buffer[j]==0.0)
             continue;
          else
             return(buffer[j]);
         }
       return(value);
      }
    //+-----------------------------------------------------------------------------------------------++
            

    使用以下公式计算信号:

    公式 10

    公式 11

    公式 12

    其中:-a是振幅-phi是相位-f是频率-B是过去采样的条数,表示从中提取频率分量的数据,相当于移动窗口大小。该值等于所考虑的最大周期周期的三倍。指标的代码如上所示。波点是曲线在相应索引处的值。

    NCycleGoertzelDft 指标

    如果感兴趣,白皮书中会提供更多交易规则的详细信息

    自适应指标

    将goertzel应用于策略开发的另一种方法是使用它使指标具有自适应性。文章Mql5中的高级自适应理论和实现,详细介绍了使用J.Ehlers的著名技术的这种方法。我们可以用goertzel做同样的事情。以下是相对强度指标的自适应版本的实现。

    //+------------------------------------------------------------------+
    //|                                                AdaptiveGARSI.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property indicator_separate_window
    #include<GoertzelCycle.mqh>
    #property indicator_buffers 2
    #property indicator_plots   1
    //--- plot RSi
    #property indicator_label1  "RSi"
    #property indicator_type1   DRAW_LINE
    #property indicator_color1  clrRed
    #property indicator_style1  STYLE_SOLID
    #property indicator_width1  1
    //---
    #property indicator_level1        70
    #property indicator_level2        50
    #property indicator_level3        30
    //---
    input bool     Detrend=true;
    input bool     EndFlatten=true; 
    input bool     SquaredAmp=true;
    input bool     UseCycleStrength=false;
    input uint     Maxper=72;
    input uint     Minper=5;
    //--- indicator buffers
    double         RSiBuffer[];
    double         DomPeriodBuffer[];
    
    CGoertzelCycle *Gc;
    //+------------------------------------------------------------------+
    //| Custom indicator initialization function                         |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- indicator buffers mapping
       SetIndexBuffer(0,RSiBuffer,INDICATOR_DATA);
       SetIndexBuffer(1,DomPeriodBuffer,INDICATOR_CALCULATIONS);
    //---
       Gc=new CGoertzelCycle(Detrend,SquaredAmp,EndFlatten,Minper,Maxper);
    //---   
       if(Gc==NULL)
        {
         Print("Invalid Goertzel object");
         return(INIT_FAILED);
        }  
    //---    
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+
    //| Indicator DeInitialization function                              |
    //+------------------------------------------------------------------+
    void OnDeinit (const int reason)
     {
      if(CheckPointer(Gc)==POINTER_DYNAMIC)
          delete Gc;
     }    
    //+------------------------------------------------------------------+
    //| Custom indicator iteration function                              |
    //+------------------------------------------------------------------+
    int OnCalculate(const int rates_total,
                    const int prev_calculated,
                    const datetime &time[],
                    const double &open[],
                    const double &high[],
                    const double &low[],
                    const double &close[],
                    const long &tick_volume[],
                    const long &volume[],
                    const int &spread[])
      {
    //---
       uint lim=0;
    //---
       Gc.CalculateDominantCycles(prev_calculated,rates_total,UseCycleStrength,close,DomPeriodBuffer);
    //---
       if(prev_calculated<=0)
           lim=int(Maxper*3)+int(DomPeriodBuffer[(Maxper*3)-1]) - 1;
       else
           lim=prev_calculated; 
           
    //---
       double cu,cd;
       for(int i =int(lim);i<rates_total;i++)
        {
          cd=0.0;
          cu=0.0;
          double p=DomPeriodBuffer[i];
          int j=0;
          for(j=0;j<int(p);j++)
            {
             if(close[i-j]-close[i-j-1]>0)cu=cu+(close[i-j]-close[i-j-1]);
             if(close[i-j]-close[i-j-1]<0)cd=cd+(close[i-j-1]-close[i-j]);
            }
          if(cu+cd!=0)
             RSiBuffer[i]=100*cu/(cu+cd);
          else 
             RSiBuffer[i]=0.0;
        
        }        
    //--- return value of prev_calculated for next call
       return(rates_total);
      }
    //+------------------------------------------------------------------+
    

    自适应 RSI 指标

    结论

    Goertzel算法的功能及其应用程序的有用代码实用程序已经得到了明确的演示。挑战在于如何将这项技术有效地应用于金融数据并提取有意义的信号。与MESA技术相比,GA显然处于劣势,因为它一次只能解析一个频率。它的实际应用也突显了金融市场周期检测的弱点,因为很难测量一个主要周期并准确确定它何时结束。解决这些困难需要结合领域知识、稳健的数据预处理技术、风险管理策略以及持续监测和适应不断变化的市场动态。

    所附的zip文件包含文章中描述的所有源代码。

    文件名 描述
    Mql5/include/Goertzel.mqh 实现goertzel算法的CGoertzel类的include文件
    Mql5/include/ GoertzelCycle.mqh 使用goertzel算法进行循环分析的CGoertzelCycle类的include文件
    Mql5/indicators/ NCycleGoertzelDft.mq5 部分实现D.Meyers开发的使用goertzel算法的策略的指标
    Mql5/indicators/ AdaptiveGARSI.mq5 通过goertzel算法计算动态回溯的相对强度指标的自适应版本的实现


    本文由MetaQuotes Ltd译自英文
    原文地址: https://www.mql5.com/en/articles/975

    附加的文件 |
    MQL5.zip (7.33 KB)
    AdaptiveGARSI.mq5 (3.62 KB)
    Goertzel.mqh (6.31 KB)
    GoertzelCycle.mqh (14.56 KB)
    MQL5 中的范畴论 (第 11 部分):图论 MQL5 中的范畴论 (第 11 部分):图论
    本文是以 MQL5 实现范畴论系列的续篇。于此,我们验证在开发交易系统的平仓策略时,图论如何与幺半群和其它数据结构集成。
    开发回放系统 — 市场模拟(第 17 部分):跳价和更多跳价(I) 开发回放系统 — 市场模拟(第 17 部分):跳价和更多跳价(I)
    于此,我们将见识到如何实现一些非常有趣的东西,但同时也会因某些可能十分令人困惑的关键点而极其困难。可能发生的最糟糕的事情是,一些自诩专业人士的交易者却对这些概念在资本市场中的重要性一无所知。好吧,尽管我们在这里专注于编程,但理解市场交易中涉及的一些问题,对于我们将要实现的内容至关重要。
    您需要了解的有关MQL5程序结构的所有信息 您需要了解的有关MQL5程序结构的所有信息
    使用任何编程语言的任何程序都有特定的结构。在本文中,您将通过了解MQL5程序结构每个部分的编程基础知识来学习MQL5计划结构的重要部分,这些基础知识在创建可在MetaTrader 5中执行的MQL5交易系统或交易工具时非常有用。
    MQL5中的结构及其数据打印方法 MQL5中的结构及其数据打印方法
    在本文中,我们将研究MqlDateTime、MqlTick、MqlRates和MqlBookInfo结构,以及从它们打印数据的方法。为了打印结构的所有字段,有一个标准的ArrayPrint()函数,它以方便的表格格式显示数组中包含的数据以及处理结构的类型。