2015年6月2日火曜日

MT4インジケータ ウェーブレット変換を実装する。その1

2015.6.9 このプログラム、逆変換に失敗しています。そのままでは使えませんのでご注意ください。

うーん。なかなかGewinn2君が動作してくれるような値動きになってくれません。
5/30夜のロンドンフィキシング後の動きでポジションをとるシグナルが発生していたのですが、週末フィルタでポジションを取りませんでした。

Gewinn2は1年ほど放置するつもりで設計しているため、気長に運用します。
今週から実運用を準備していますので、そちらも順次報告していきたいと思います。

さて、相場はノイズとトレンドを伴った値動きをすると仮定します。
ノイズ成分とトレンド成分はどうやって分離するのが良いのでしょうか?S/Nフィルタを実装するというインジケータも存在しますが、あれは正しい信号に対してのノイズ成分を抽出する為、正しい信号が決まらない為替相場には考え方が難しくなります。

フーリエ変換で周波数成分を分析して振幅が大きい成分だけで再構築する手法もよくとられているようですが時間成分が抜け落ちるという問題があります。ウィンドウフーリエ変換を行って詳細に解析していくことも可能ですがマシンパワーが必要になります。たぶん超大手のシステムトレード屋さんではミニスパコンクラスを使って行っている事でしょう。

もうちょっと簡単にPCのパワーでも解析可能な手法はないかということで、ウェーブレット変換を実装していきたいと思います。ウェーブレット変換は画像処理や信号解析で使われる手法ですが、これを金融に応用できないかを検討したレポートが日本銀行 金融研究所から公開されています。
http://www.imes.boj.or.jp/research/papers/japanese/kk23-1-1.pdf

このレポートを参考にプログラムを実装していきます。
このレポート、離散ウェーブレット入門から記載されているうえ、ウェーブレット変換した結果から原油やドル円に影響を与えている周波数期間を検討したりと実例に富んでいます。信号解析技術を使ったインジケータやEAを開発しようと目標にする方は一読することをお勧めします。

まず、ウェーブレット変換の基礎となる変換部分の関数実装とその内容をインジケータに表示しました。
ウェーブレットフィルタはハールフィルタを使用しています。
結果画像は次の通りです。

レベル1詳細成分
webletDetail.JPG

レベル1スケール成分
webletScale.JPG

//------------------------------------------------------------------
// ウェーブレットインジケータ(テスト用)

#property copyright "Copyright 2015,  Daisuke"
#property link      "http://mt4program.blogspot.jp/"
#property version   "1.00"
#property strict

#property indicator_separate_window

//バッファーを指定する。
#property indicator_buffers 1

//プロット数を指定する。
#property indicator_plots   1

#property indicator_label1  "reverseWavelet"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrYellow
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//入力パラメータ 表示レベル
input int Level = 1;

//入力パラメータ 0ウェブレットディテール or 1スケール
input int DetailOrScale = 0;

//バッファ
double waveletBuffer[];

//必要データ数
int dataCount = 0;

//------------------------------------------------------------------
//初期化
int OnInit()
{
dataCount = (int)MathPow(2, Level);
SetIndexBuffer(0, waveletBuffer);
SetIndexDrawBegin(0, dataCount);

string short_name;
short_name = "WV("+ IntegerToString(Level)  +")";
IndicatorShortName(short_name);

SetIndexDrawBegin(0, dataCount);

return(INIT_SUCCEEDED);
}

//------------------------------------------------------------------
//計算イベント
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[])            //スプレット
{
// フィルタ
double wavebletFilter[];
double scalingFilter[];

int filterSize = 2;

ArrayResize(wavebletFilter, filterSize);
ArrayResize(scalingFilter, filterSize);

// ハールフィルタ
wavebletFilter[0] = 0.70710678;
wavebletFilter[1] = -0.70710678;
scalingFilter[0] = 0.70710678;
scalingFilter[1] = 0.70710678;

//元となる値を計算する。
for( int i = (rates_total - prev_calculated - 1); i>=0 ; i-- )
{
if( i >= (rates_total - dataCount - filterSize -1) )
{
continue;
}

WaveletValues value = CalulateWavelet(close, wavebletFilter, scalingFilter, dataCount , Level, i);
waveletBuffer[i] = DetailOrScale == 0 ? value.wavelet : value.scaling;
}

return(rates_total - 1);
}

// ウェーブレット値構造体
struct WaveletValues
{
// ウェーブレット
double wavelet;
double scaling;
};

//------------------------------------------------------------------
//ウェーブレット値を計算する。
WaveletValues CalulateWavelet(
const double &values[],          // 元値
const double &wavebletFilter[],  // ウェーブレットフィルタ
const double &scalingFilter[],   // スケーリングフィルタ
int count,                       // ウェーブレット計算サイズ
int targetLevel,                 // ターゲットレベル
int shift                        // 起点インデックス
)
{
// count/2 の配列を用意する。
int total = count / 2;

double wavelet[];
double scaling[];
ArrayResize(wavelet, total);
ArrayResize(scaling, total);

int size = ArraySize(wavebletFilter);

for( int i = 0 ; i < total; i++)
{
wavelet[i] = 0;
scaling[i] = 0;
for( int k = 0 ; k < size; k++ )
{
wavelet[i] += wavebletFilter[k] * values[ i * 2 + k + shift];
scaling[i] += scalingFilter[k] * values[ i * 2 + k + shift];
}
}

targetLevel --;

if( targetLevel <= 0 )
{
WaveletValues result ;
result.wavelet = wavelet[0];
result.scaling = scaling[0];
return result ;
}
else
{
return CalulateWavelet(scaling, wavebletFilter, scalingFilter, total, targetLevel, 0);
}
}


実装は離散ウェーブレット変換となっています。
プログラムをよーく眺めると、ハールフィルタの場合、移動平均と差分をとっているだけです。

ウェーブレット変換はハイパスフィルタとローパスフィルタを元信号に対して適用して、これをダウンサンプリングしながら再帰的に行う事で周波数成分を抽出していく手法のようです。

現在進行形のデータに対してウェーブレット変換を行う為、対象となっているデータがウェーブレット変換値に影響を与える範囲だけ毎回計算しています。
そのため、本来ダウンサンプリングされて2^levelごとにしか値が生成されていないはずですが、毎足に値が生成されるようになっています。

次回の記事では、これがなんの役に立つの?ということを検証していきたいとおもいます。

※記事を書いている段階では、これ何の役にたつのか書いている本人も判っていません。やってみよう!ってかんじで作っています(^^;