English Русский 中文 Español Deutsch Português
preview
Goertzelアルゴリズムによるサイクル分析

Goertzelアルゴリズムによるサイクル分析

MetaTrader 5インディケータ | 8 8月 2023, 10:20
465 0
Francis Dube
Francis Dube


はじめに

Goertzelアルゴリズムは、特定の周波数成分を効率的に検出することで知られるデジタル信号処理技術です。その精度、リアルタイム性、計算効率の高さから、金融時系列分析に適しています。この記事では、ドミナントサイクルを分析し、戦略策定に役立てるための実践的な方法を検討し、実証します。MQL5におけるアルゴリズムの実装を見て、価格相場におけるサイクルを識別するためにコードを使用する方法の例を紹介します。

Goertzelアルゴリズム

Goertzelアルゴリズムは、離散フーリエ変換(DFT)の各項を効率的に計算するために利用されます。その名前はGerald Goertzel(ジェラルド・ゲルツェル)に由来します。この手法は1958年に初めて紹介され、それ以来、工学、数学、物理学などのさまざまな分野に適用されてきました。Goertzelアルゴリズムの主な用途は、デジタル信号内の特定の周波数成分を識別することであり、少数の周波数成分のみが重要なシナリオでは非常に価値があります。高速フーリエ変換(FFT)と比べて、限られた数の周波数成分を検出する場合に必要な計算回数が少なく、計算効率が高くなっています。

これは、次の式で表されます。

ゲルツェル式

ここで、
- Xは周波数kにおける累積マグニチュード
- cos()は余弦関数
- πは数学定数π(約3.14159)
- kは、関心のある周波数ビンのインデックス(サンプルの総数がNの場合、0からN - 1の範囲)
- Nは入力信号の長さ
- X[k-1]およびX[k-2]は、kにおける周波数について事前に計算されたXの値
- x[n]は入力信号のn番目のサンプル

Goertzelアルゴリズムを使って実数成分と虚数成分を計算するには、入力信号サンプルを繰り返し、以下の計算をおこなう必要があります。

  • 変数を初期化する
    • N :入力信号のサンプル数
    • k :注目する周波数ビンのインデックス(0 <= k < N)
    • ω : 希望する周波数ビンに対応する周波数(2 * pi * k / N)
    • coeff :計算に使用される係数(2 * cos(ω))
    • s_prev :状態変数の前回値
    • s_prev2 :状態変数の前回の前回の値
    • s:状態変数の現在値
  • 入力信号の各サンプルを反復処理する
    • 次の式を使用して、状態変数(s)の現在値を更新する

      状態変数初期化

  • ここで、x[n]は現在の入力サンプルです。

    状態変数の以前の値を更新します。

    前の状態変数の更新

    最後の状態変数の更新

    すべてのサンプルを反復した後、状態変数の最終値(S、Sprev、Sprev2)は、目的の周波数ビン(k)におけるDFTの実数成分と虚数成分を表します。

    実数成分は次の式で与えられます。
    実数成分の式

    虚数成分は次の式で与えられます。 

    虚数成分の式

    DFTで検出できる周波数は1/Nから(N/2)/Nの範囲であり、Nは系列内のデータポイントの数(この場合は分析対象の価格バーの数)を表します。価格相場を分析する場合、1/N間隔に限定された周波数帯域しか観察できないという限界があります。これが、J. Ehlersらがこの制約を克服するためにMESA(Maximum Entropy Spectral Analysis、最大エントロピースペクトル解析)技術を提案した理由です。

    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は一度に限られた数の周波数しかサンプリングしないので、興味のある周波数帯域を設定しなければなりません。このクラスでは、周波数帯域は周波数の最小周期と最大周期で設定されます。

    これらの値を設定するには2つの方法があります。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メソッドの2つのオーバーロードのいずれかを呼び出すときに設定できます。

    //+-----------------------------------------------------------------------------------+
    //| 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の実数成分と虚数成分は別々の配列で出力されるか、複素数型の1つの配列で出力されます。

    //+-----------------------------------------------------------------------------------+
    //| 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のサブセットであるため、高速フーリエ変換をおこなう前に必要な変換がここでも適用されます。これは特に、定期的でないデータに当てはまります。このような系列は、適切な窓関数で処理する必要があります。これは、スペクトルの漏れの可能性を最小限に抑えるために、系列の両端を適切にテーパーします。文献にあるGoertzeの使用例のほとんどは、終点平坦化アルゴリズムを採用しています。このテクニックの実装は次の式で与えられます。

    式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インクルードファイルには、Goertzelアルゴリズムでデータセットのサイクル分析をおこなうために使用されるCGoertzelCycleクラスが含まれます。

    //+------------------------------------------------------------------+
    //|   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[]);
      };

    コンストラクタは2つあります。パラメータ化されたコンストラクタは、カスタム設定によるインスタンスの初期化を可能にします。

    //+------------------------------------------------------------------+
    //|                     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:窓関数を適用するかどうかを指定するもう1つのブールパラメータ
    • min_period:Goertzelアルゴリズムによって解決される最短周期または最高周波数を定義(2以上が必要)
    • max_period :分析中に考慮される最長の期間(この値は、最長の期間を持つ周波数を表し、min_period以下にはできない)
    • squard_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は分析される生の系列です。2番目の配列は、振幅値が出力される場所です。変換に使われるパラメータは、パラメータ化されたコンストラクタを使ってインスタンスを初期化することで指定されるはずでした。

    //+------------------------------------------------------------------+
    //|         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;
    
      }

    もう1つのオーバーロードされた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()と同様にオーバーロードされます。2つの配列の他に、use_cycle_strengthというブール値のパラメータを指定する必要があります。これは、ドミナントサイクルを決定するための基準を示します。 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は指標バッファの1つです。

    //+-----------------------------------------------------------------------+
    //|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の可能な応用と、クラスの使用の両方を実証するために、ここでは2つの指標を紹介します。1つ目は、Dennis Meyersが書いたホワイトペーパーで紹介されている戦略の実装です。彼はこの戦略を「アダプティブ10サイクルGoertzel DFTシステム」と呼んでいます。戦略の正確な詳細は、この文書に記載されています。簡単に説明すると、この戦略はGoertzelを使って価格データの移動窓から上位10個の支配的なサイクルを抽出し、これらのサイクルを使って新しい価格曲線を構築します。これは、ノイズが取り除かれた価格の挙動を表していると思われます。この戦略の主要なパラメータは、サンプリングする周波数の最小周期と最大周期、およびフィルタリングされた信号を構成するために使用される周波数成分の最大数です。

    //--- 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

    この指標は、直近のピークまたはトラフとの相対的な動きの大きさに応じて、ロングとショートのシグナルをトリガーする2つのしきい値を使用します。このコードでは、入力変数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は周波数成分を抽出するデータを表す過去の棒グラフのサンプリング数で、移動窓サイズに相当します。この値は、考慮される最大周期サイクルの3倍に等しくなります。指標のコードは上に示されています。波動ポイントは、対応するインデックスのカーブの値です。 

    NCycleGoertzelDft指標

    取引ルールの詳細については、ホワイトペーパーを参照してください。

    適応型指標

    Goertzelを戦略開発に応用するもう1つの方法は、指標を適応的にするために使うことです。「 高度適応インディケータ理論および 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);
      }
    //+------------------------------------------------------------------+
    

    Adaptive RSI指標

    結論

    Goertzelアルゴリズムの能力は、その応用に有用なコードユーティリティとともに明確に実証されています。課題は、このテクニックをいかに効果的に金融データに適用し、意味のあるシグナルを抽出するかです。GAは、一度に1つの周波数しか分解できないため、MESA技術に比べて明らかに不利です。その実用化によって、金融市場におけるサイクル検知の弱点も浮き彫りになりました。というのも、実勢サイクルを測定し、それがいつ終わるかを正確に判断するのは難しいからです。こうした困難に対処するには、専門分野の知識、堅牢なデータ前処理技術、リスク管理戦略、変化する市場力学への継続的な監視と適応を組み合わせる必要があります。

    添付のZIPファイルには、記事で説明されているすべてのソースコードが含まれています。

    ファイル名 詳細
    Mql5/include/Goertzel.mqh Goertzelアルゴリズムを実装したCGoertzelクラスのインクルードファイル
    Mql5/include/GoertzelCycle.mqh Goertzelアルゴリズムを使用したサイクル分析用CGoertzelCycleクラスのインクルードファイル
    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のインタラクティブGUIで取引チャートを改善する(第2回):移動可能なGUI (II) MQL5のインタラクティブGUIで取引チャートを改善する(第2回):移動可能なGUI (II)
    MQL5で移動可能なGUIを作成するための詳細なガイドで、取引戦略やユーティリティでの動的なデータ表現の可能性を引き出しましょう。オブジェクト指向プログラミングの基本原理を理解し、同じチャート上に単一または複数の移動可能なGUIを簡単かつ効率的に設計実装する方法を発見してください。
    MetaTrader 5用のMQTTクライアントの開発:TDDアプローチ MetaTrader 5用のMQTTクライアントの開発:TDDアプローチ
    この記事では、MQL5のネイティブMQTTクライアント開発における最初の試みについて報告します。MQTTは、クライアントサーバーのパブリッシュ/サブスクライブメッセージングトランスポートプロトコルです。MQTTは軽量、オープン、シンプルで、簡単に実装できるように設計されています。これらの特性により、さまざまな状況での使用に最適です。
    アプリケーションを使用してMQL5の関数を理解する アプリケーションを使用してMQL5の関数を理解する
    関数はどのプログラミング言語においても重要なものです。関数は、開発者が同じことを繰り返さないことを意味するDRY (Do not Repeat Yourself)の概念を適用するのに役立つなどの多くのメリットを提供します。この記事では、関数に関する詳細情報と、物事を複雑にすることなく取引システムを強化するために、あらゆるシステムで使用または呼び出しできる簡単なアプリケーションを作成して、MQL5で独自の関数を作成する方法について説明します。
    MQL5オブジェクト指向プログラミング(OOP)について MQL5オブジェクト指向プログラミング(OOP)について
    開発者として、私たちは、特に異なる動作をするオブジェクトがある場合に、コードを重複せずに再利用可能で柔軟なソフトウェアを作成し開発する方法を学ぶ必要があります。これは、オブジェクト指向プログラミングのテクニックと原則を使うことでスムーズにおこなうことができます。この記事では、MQL5オブジェクト指向プログラミングの基本を紹介し、この重要なトピックの原則とプラクティスをソフトウェアでどのように使用できるかを説明します。