テクニカルインディケータとデジタルフィルター

GT788 | 24 11月, 2015

はじめに

数年にわたりコードベースは多数のインディケータを集積してきました。それらの多くはわずかな変更を加えただけのお互いのコピーです。チャート上で何時間もインディケータ同士を目で比較したら尋ねずにはいられませんでした。「もっと客観的で効率的に比較できないの?」と。実にそれは可能なのです。インディケータはデジタルフィルターーであることを認める必要があります。ウィキペディアを参照します。

フィルター(化学)、他の物質を通過させて、特定の物体や物質をブロックするように作られたデバイス(通常膜または層)

インディケータはいくつか『不必要な』物体をブロックし重要なものに焦点を当てることができるということに同意しますか?ではデジタルフィルターとは何か見ていきましょう。

電子工学、コンピュータ科学、数学ではデジタルフィルターはサンプル化された離散時間信号についてその信号の特定面を低減したり強化するために数学的処理を行うシステムです。

いわゆるデジタルフィルターは離散信号を処理するフィルターです。ターミナルに見る価格は離散信号として処理することができます。それはそれらの値が継続的でなく一定の時間間隔でレコードされるためです。たとえば、価格値は H1 チャート上に毎時レコードされます。また M5では5分ごとにレコードされます。多くのインディケータは線形フィルターとして処理することができます。これはまさに本稿で述べられているインディケータタイプです。 

デジタルフィルターを取り上げていることがわかったところで、どのパラメータが比較されるべきか明確にするために理論を検討します。


1. 振幅と期間

まずなによりも曲線はどれもサイン波の合計として表すことができることをお話します。

振動期間は同じポジション、同じ方向で 本体が連続して2度通過する時間間隔をいいます。この値は周波数の逆数です。

この定義はサイン波を用いてもっとも簡単に理解することができます。バー10本の周期を考察します。簡単にするためにバーで計算を行います。

図1  

 図1 サンプル周期信号

見てわかるようにラインは全周期を10個で完了します。また11本目のバーは新たな周期の最初のポイントです。

サイン波の振幅とは何でしょうか?定義によると、周期は周波数値の逆数です。周期が10(バー)であれば、周波数は1/10=0.1(バー分の1)となります。

物理学では周期(T)は秒で計測され、一方周波数はヘルツ(Hz)で計測されます。分足のタイムフレームで処理するなら T=60*10=600 秒、一方 f=1/Т=1/600=0.001667 ヘルツとなります。ヘルツと秒はおもにアナログフィルターで使用されます。デジタルフィルターでは通常カウントが使用されます(われわれがバーを使用したように)。必要に応じそれらは必要な秒数でかけ算されます。

これがサイン波とどう関係しているのだろう、と疑問に思うかもしれません。サイン波はフィルターの物理的意味と周波数に対する変動を説明するのに必要なのです。というのもこのコンセプトは適切な処理で利用されるためです。ここでは1個ではなく7個のサイン波を周期10~70、バー10ステップで取ります。図2の上側のサブウィンドウにあるバーはカウント数を視覚的に推定するのを導きます。

図2  

 図2 同じ振幅周期10、20、... 70のサイン波7個です。

スケールは十分大きいですがまだ混乱する可能性があります。そしてサイン波が多ければ混乱しやすくなります。

サイン波の合計は以下に示します。

図3   

図3 図2に表示されている7個のサイン波合計

周波数は以下の方法で表示されます。

 図4  

図4 サイン波合計のスペクトル(周波数)

7本のサイン波を表示するには7カウントで十分です。色に注目します。それらは前の図に対応しています。速いサイン波が遅いサイン波に続きます。起こりうる最も低い周波数は0(定数成分)で、一方もっとも高いものは0.5(バー分の1)です。周期に対しては逆です。

 図5 

 図5 サイン波合計のスペクトル(周期)

周波数は周期分の1でした。よって周期範囲は2~無限となります。それなのになぜ0.5と2なのでしょうか?1個のサイン波は最低カウント2個で記述することができます(ナイキスト・シャノンのサンプリング定理を参照)。アナログ(連続)信号を復元するには1つのサイン波に対して2個以上のカウントが必要です(1/2から0.5が受け取られます)。

周期と周波数の混乱を避けるため、次の表を考察しましょう。

周期
  
 100     50
   16
  10
   4
   2
周波数
0
 0.01 
 0.02 
0.0625
  0.1 
 0.25 
  0.5 

 

周期と周波数のコンセプトを最大化しました。というのもこれらが基本だからです。以降の情報はすべてこれら用語に関わります。

 

2. デジタルフィルター

ということでついにフィルターについてお話する準備ができました。50未満の周期のサイン波は除外するとします。

図6  

 図6 サイン波合計(周期50、60、70バー)の遅い(低い周波数)成分

最初の成分を知るとすべては比較的簡単です。ですが合計しかわかっていなければどうでしょうか?その場合には 1/45(バー分の1)のカットオフ周波数のローパスフィルター(LPF)が必要です。

以下はフィルターにかけた結果表示です。

図7  

 図7 LPFを用いたサイン波合計ろ過の結果(ブルーの線)

周期10、 20、 30だけを持つサイン波だけ残します。これにはカットオフ周波数1/35(バー分の1)のハイパスフィルター(HPF)が必要です。 

図8  

 図8 サイン波合計(周期10、 20、 30バー)の高い周波数成分

図9  

図9 HPFを用いたサイン波合計ろ過の結果(ブルーの線)

周期 30、40、50を残すにはカットオフ周波数1/25 と1/55(バー分の1)のバンド幅フィルターが必要です。 

図10  

 図10 周期 30、40、50バーのサインライン

 

図11  

図11 サイン波合計ろ過の結果(30~50バー)

周期 30、40、50を除外する場合、同じカットオフ周波数1/25 と1/55(バー分の1)の帯域除去(排除)フィルターが必要です。 

図12  

図12 周期 10、20、60、70バーのサイン波

 

図13  

図13 サイン波合計に応じる排除フィルター処理(30~50バー)結果

以下の画像で中間結果を要約してみましょう。 

図14 
 

図14 理想的フィルターの周波数パラメータ:低い周波数(LPF)、高い周波数(HPF)、バンド幅(BF)、排除(RF)。

上記で検討されるフィルターは理想化されています。現実はもっと難しいものです。

 図15  

図15 フィルターの遷移帯域

バンドの受け入れ帯域と減衰帯域の間には遷移帯域があります。その傾きはデシベル/オクターブまたはデシベル/ディケードで計測されます。オクターブは周波数の乱数値とダブル値の間のセグメントです。ディケードは周波数の乱数値と10倍値の間のセグメントです。正式には遷移帯域はカットオフ周波数と減衰帯域の間にあります。以降、スペクトルによるカットオフ周波数は3デシベルのレベルでもっとも頻繁に確定されると言えます。

帯域外排除はデシベルで計測される減衰帯域において周波数を抑制します。

受け付け帯域ではビートが検出されます。われわれは現実のフィルター、すなわち受け付け帯域でのゆがみを処理しているので、周波数の中には振幅によって大きくなるものもあります。また一方で低くなるものもあります。値はデシベルで計測されます。

以下の表はデシベル値をレンダーリングするのに役立ちます。

dB
振幅比
0.5    
1.06
1
1.12
3
1.41
6 2
10
3.16
20 10
30 31.6
40
100
60  1000

 

たとえば、60デシベルに対する結果を受け取りたい場合、20デシベルと40デシベルに対する値を見つけそれをかけ算します。

フィルターの基本パラメータがわかったところで本稿の実践部分に参ります。


3. カーネル検索

デジタルフィルターはそのインパルス応答(カーネル)によって完全に記述されると言えます。インパルス応答は1インパルスに対するフィルターの応答です。フィルターは IIR 対イプ(無限インパルス応答。たとえば 指数移動平均、EMA) および FIRタイプ(有限インパルス応答。たとえば 単純移動平均、SMA) がありえます。 

ここで MetaEditorに注目します。まず単一インパルスを作成します。それは1に等しい1カウントのみ表示するひじょうにシンプルなインディケータです。MetaEditorでは「新規」をクリックし『カスタムインディケータ』を選択したら「次へ」をクリックします。

図16 
 

図16 MQL5 ウィザードでのカスタムインディケータ作成

名前としての『インパルス』指定

図17 
 

図17 インディケータの一般的特性

イベントハンドラの選択

図18 
 

 図18 インディケータのイベントハンドラ

ここでインディケータラインを追加し、個別のウィンドウに表示します。すべて準備できました。

 図19 

 図19  インディケータの特性描写

以下がインディケータのコード記述です。

//+------------------------------------------------------------------+
//|                                                      Impulse.mq5 |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots   1
//--- plot Label1
#property indicator_label1  "Label1"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- input parameters

//--- indicator buffers
double         Label1Buffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,Label1Buffer,INDICATOR_DATA);
   ArraySetAsSeries(Label1Buffer,true);
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
              const int prev_calculated,
              const int begin,
              const double &price[])
  {
//---
   ArrayInitialize(Label1Buffer,0.0);
   Label1Buffer[1023]=1.;
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

以下を OnInit() 関数に追加します。

ArraySetAsSeries(Label1Buffer,true);

そうしてインデックスが配列の最後から付けられるようにします。

OnCalculate()関数で

ArrayInitialize(Label1Buffer,0.0);
Label1Buffer[1023]=1.;

値をすべてゼロ設定とし1023番目のインディケータ配列セルに1を追加します。

コンパイル(F7)すると結果は以下のようになります。 

図20  

 図20 インパルスインディケータ

これでなんらかのインディケータを採用するなら、そのインパルス応答を1024カウントまで確認することができます(例を参照)。

もちろんフィルターのカーネルを閲覧できればそれにこしたことはありませんが、周波数領域表示からだけだとより多くのデータを取得することができます。このためスペクトル解析を作成するか、あまり労力をかけず既定のソリューションを利用します。後者を得欄で記事『スペクトル解析機能の構築』に記載のあるSpecAnalyzer インディケータを使用します。

以下がそのインディケータです。

図21  

図21 SpecAnalyzer

使用前にじゃっかんの準備が必要です。必要な手順を以下に記します。


4. スペクトル解析機能の適応

『外部データ』ボタンによりSAInpData インディケータからのデータを使用することができます。

元データにはフィルターのカーネルを表す配列があります。そのファイルを作り直し、スペクトルアナライザーにどんなチャートインディケータも渡せるようにします。自動またはマニュアルモードを変更済みインディケータに備えます。自動モードでは最初に見つかるチャートインディケータが使用されます。マニュアルモードではユーザーがサブウィンドウとリスト上のインディケータインデックスを設定することができます。この場合、Impulseインディケータはマニュアルでチャートに追加する必要があります。その後、カーネルを受け取るのに必要なインディケータが適用されます。

では始めましょう。Impulseインディケータと共に、同じアルゴリズムに従う新しいインディケータを作成する必要があります。入力パラメータを追加します。

input bool Automatic=true; // Autosearch
input int  Window=0;       // Subwindow index
input int  Indicator=0;    // Indicator index

Automatic=真であれば、自動モードが使われ、その際他の入力パラメータは無視されます。Automatic=偽であれば、マニュアルモードがサブウィンドウとインディケータインデックスで使用されます。

次にハンドルを格納するためグローバルレベルで整数タイプの変数を追加します。

int Impulse=0; // single impulse's handle
int Handle=0;  // required indicator's handle
int Kernel=0;  // filter kernel's handle

インパルスインディケータのハンドルは Impulseに格納されます。インディケータのハンドル、すなわちスペクトルアナライザーで閲覧したいカーネルは Handleに格納されます。インパルスインディケータを基に構築されている対象インディケータのハンドル、いわゆる対象インディケータのカーネルは Kernelに格納されます。

OnInit() 関数

int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,DataBuffer,INDICATOR_DATA);

   Impulse=iCustom(NULL,0,"SpecAnalyzer\\Impulse");//get the single impulse handle
   if(Impulse==INVALID_HANDLE)
     {
      Alert("Impulse initialization failed"); 
       return(INIT_FAILED);
     }
//---
   return(0);
  }

インパルスインディケータはプログラム処理中変更されることはないため、インディケータハンドルは OnInit() 内で受け取られます。またエラーを受け取るハンドルは確認される必要があります。うまくいかなかった場合は 『インパルス初期化失敗』のメッセージが表示され、インディケータの処理は INIT_FAILED キーで中断されます。

OnDeinit() 関数

void OnDeinit(const int reason)
  {
//--- delete the indicators
   IndicatorRelease(Impulse);
   IndicatorRelease(Handle);
   IndicatorRelease(Kernel);
  }

使用されるインディケータは OnDeinit() 関数で削除されます。

OnCalculate() 関数

static bool Flag=false;        //error flag
if(Flag) return(rates_total); //exit in case of the flag

この関数の冒頭にflag静的変数が追加されます。プログラム実行中にエラーが発生すれば Flag は となりその後の OnCalculate() 関数の反復はすべて開始からただちに中断されます。

以下がマニュアルモードに関わるコードブロックです。

   string Name;  //short name of the required indicator
   if(!Automatic)//in case of the manual mode
     {
      if(ChartIndicatorsTotal(0,Window)>0)//if an indicator is present
        {
         Name=ChartIndicatorName(0,Window,Indicator);//search for its name
         Handle=ChartIndicatorGet(0,Window,Name);//search for the handle
        }
      else//otherwise
        {
         Alert("No indicator");
         Flag=true;
         return(rates_total);
        }

      if(Handle==INVALID_HANDLE)//in case of a handle receiving error
        {
         Alert("No indicator");
         Flag=true;
         return(rates_total);
        }

      CopyBuffer(Handle,0,0,1024,DataBuffer);//display the kernel on the chart
      return(rates_total);
     }

Automatic=偽であれば、マニュアルモードが起動します。インディケータの有無が確認されます。問題なければ、名前とハンドルを検索、ハンドルのエラー確認インディケータバッファへのデータコピーが開始します。失敗の場合、『インディケータはありません』のメッセージが表示され、Flag は に切り替わり、OnCalculate() 関数の実行が中断します。

自動モードのブロックはもっとずっと興味深いものです。それはチャート上のインディケータ検索とカーネル作成で構成されています。

インディケータ検索を考察します。おもな目的はハンドルの受け取りです。

   if(ChartIndicatorsTotal(0,0)>0)//if the indicator is in the main window
     {
      Name=ChartIndicatorName(0,0,0);//search for its name
      if(Name!="SpecAnalyzer")//if it is not SpecAnalyzer
         Handle=ChartIndicatorGet(0,0,Name);//look for a handle
      else
        {
         Alert("Indicator not found");
         Flag=true;
         return(rates_total);
        }
     }
   else//otherwise
   if(ChartIndicatorsTotal(0,1)>0)//if the indicator is in the first subwindow
     {
      Name=ChartIndicatorName(0,1,0);//search for its name
      if(Name!="SAInpData")//if it is not SAInpData
         Handle=ChartIndicatorGet(0,1,Name);//look for a handle
      else//otherwise
        {
         Alert("Indicator not found");
         Flag=true;
         return(rates_total);
        }
     }

   if(Handle==INVALID_HANDLE)//in case of a handle receiving error
     {
      Alert("No indicator");
      Flag=true;
      return(rates_total);
     }

まずチャートの主なサブウィンドウでインディケータを検索し、それがSpecAnalyzerでないことを確認します。インディケータがメインウィンドウに見つからなければ、次のサブウィンドウで検索します(ここにSAInpData があると考えて)。その他のアクションはマニュアルモードと類似しています。

ではインディケータ作成を行います。取得するインディケータのパラメータを受け取り、Impulseを基に同様のインディケータを作成します。

   ENUM_INDICATOR indicator_type;//obtained indicator's type
   MqlParam parameters[];      //parameters
   int parameters_cnt=0;      //number of parameters

//--- receive the indicator's type, parameter values and amount
   parameters_cnt=IndicatorParameters(Handle,indicator_type,parameters);
//--- define that a single impulse is to be sent to the indicator's input
   parameters[parameters_cnt-1].integer_value=Impulse;
//--- receive the indicator's handle from the single impulse - filter's kernel
   Kernel=IndicatorCreate(NULL,0,indicator_type,parameters_cnt,parameters);

   if(Kernel==INVALID_HANDLE)//in case of a handle receiving error
     {
      Alert("Kernel initialization failed");
      Flag=true;
      return(rates_total);
     }

   CopyBuffer(Kernel,0,0,1024,DataBuffer);//display the kernel on the chart

indicator_type -特別な列挙の ENUM_INDICATOR タイプの変数この変数はインディケータタイプを受け取るために作成されています。

parameters[] -- the array of MqlParam タイプの配列で、インディケータパラメータを格納し転送するための特殊なストラクチャ

IndicatorParameters 関数によりチャートインディケータ上のデータを受け取ることができます。それからパラメータ配列に少し変更を加えます。インパルスインディケータのハンドルは integer_value 内で時系列名(クローズ、ロー、ハンドル等)が格納される最終セルにインクルードされます。 IndicatorCreate 関数を使用し、新規インディケータ(これもまたカーネル)を作成します。ここでハンドルの確認およびチャートへのカーネル表示が必要です。

SpecAnalyzer もまたわずかに変更を加えられました。以下の入力パラメータが追加されました。

input bool Automatic=true; //Autosearch
input int Window=0;        //Subwindow index
input int Indicator=0;    //Indicator index

SAInpData 呼び出しも変更されています。

ExtHandle=iCustom(NULL,0,"SpecAnalyzer\\SAInpData",Automatoc,Window,Indicator);

SAInpData はインパルス応答を閲覧するために単独で使用することができます。

 

5. 例示

ものごとが機能するように SpecAnalyzer folder to MetaTrader 5\MQL5\Indicatorsを貼りつけます。MetaTrader 5を起動し、新しいEURUSD チャートを開きます。

図22 

図22 新規 EURUSD チャートのオープン

たとえば MA(16)など必要なインディケータを採り入れます。

図23 

図23 EURUSD チャートへの移動平均インディケータ適用

SpecAnalyzer起動します。

図24 

図24 SpecAnalyzerの起動

パラメータウィンドウが表示されます。

図25 

図25 SpecAnalyzer インディケータパラメータ

自動モードでは OKをクリックします。マニュアルモードではと置き換えられ、必要なインディケータの場所を指定する必要があります。

それでわれわれはOKをクリックしました。新たに表示された「スペクトルアナライザーウィンドウ」で『外部データ』をクックします。

図26 

図26 SpecAnalyzer インディケータ用入力データ選択

ここではマニュアルモードでの作業を考察します。まずチャートに Impulse インディケータを追加します。

図27 

図27  Impulse インディケータの追加

このインディケータを対象インディケータ作成のために利用します。これにはマウスでインディケータを Impulse ウィンドウまでドラッグし、パラメータの『適用する』フィールドで前のインディケータを選択します

図28 

図28 Impulse インディケータデータを利用した移動平均インディケータの作成

以下の結果が取得されます。

図29  

図29 単一 Impulseでの移動平均インディケータ計算結果

ここでインディケータリストを閲覧するため右クリックします。

図30 

図30 リスト上のインディケータ

われわれのインディケータはサブウィンドウ1にあり、通し番号は1(インデックスは1ではなく0からつけられることを忘れないでください)です。それでは SpecAnalyzerを起動しましょう。、1、1を設定します。 『外部データ』をクリックします。

インディケータのプロパティはオンザフライで変更することができます。インディケータリストを利用して期間を変更し、「スペクトルアナライザー」がどのように応答するか確認してみます。 

例に移る前にSpecAnalyzer インディケータのある特徴についてお話する必要があります。そのスケール上での読み取りは周期ではなくグリッドがマークする周波数です。スペクトルアナライザーは1024までの読み取りの長さを持つカーネルと連携しています。それはグリッド周波数のピッチは 1/1024=0.0009765625に等しいということです。そのためスケール上の値128 は周波数 0.125 または周期 8に相当します。

スケール
周期
16   64
32   32
64   16
128
8
256
4
384
2.67
512
2

SMA (16)

 Fig. 31

Fig. 31. 単純移動平均インディケータのインパルス応答(FIR フィルター)

図32  

図32 単純移動平均インディケータの周波数応答

低周波数が優先されるため、これはローパスフィルターであることが判ります。減衰帯域における抑圧は弱いものです。

  

EMA (16)

図33 

図33 指数移動平均インディケータのインパルス応答 (IIR フィルター)

図34  

図34 指数移動平均インディケータの周波数応答

指数移動平均インディケータもまたローパスフィルターです。線はひじょうになめらかですが、前出のインディケータと異なり遷移帯域は広くなっています。抑制はほぼ同レベルです。

それでは 汎用デジタルフィルターの結果を検討します。

ローパスフィルター

図35 

図35 ローパスフィルターのインパルス応答(カーネル)

図36 

図36 ローパスフィルター
周波数応答

ハイパスフィルター

 図37  

図37 ハイパスフィルターのインパルス応答(カーネル)

 

 図38  

図38 ハイパスフィルター周波数応答

 

バンドパスフィルター

 図39  

図39 バンドパスフィルターのインパルス応答(カーネル)

 

図40  

  図40  バンドパスフィルター周波数応答


おわりに

結論としてフィルターパラメータは強相互接続されていることに注意が必要です。そのうちのいくつかを改善することは他のパラメータを劣化させることを意味します。そのためパラメータは対象タスクに応じて選択する必要があります。

たとえば、減衰帯域での周波数抑制を高めたければ、下降曲線の急峻を犠牲にする必要がありますどちらのパラメータも良好であれば、カーネル長を増やします。それは今度はインディケータと価格の差に影響を与えるか、受け入れ帯域のゆがみを増すことになります。