2015年4月9日木曜日

メディアンフィルタを適用した移動平均インジケータ

前回の例では、クローズ値のメディアンフィルタ値と移動平均を表示させましたが、基本クローズ値と移動平均を比較するような物なのであまり意味がありません。

そこで、期間の短い移動平均の角度が暴れるのを防ぐ為、元となるデータにメディアンフィルタを適用した後、移動平均を求める短期向け移動平均インジケータを作成してみたいと思います。

先に結果から。
medianMA.PNG
赤線が期間5のクローズ値移動平均、水色線がメディアンフィルタ長3、期間5のクローズ値移動平均です。9:20頃チャートが暴れていますが、単純な移動平均の場合チャートに引っ張られて暴れていますが、メディアンフィルタを適用した移動平均だと暴れず描画されています。メディアンフィルタのノイズ除去効果が表れていることが分かります。
短期の移動平均で角度を見るような場合、効果があるように思われます。

今回は真面目に自分でも使うように移動平均に必要なパラメータ等もきっちり設定しました。

なお前回の記事で作成した、GetMedianValue関数はNavigatorウィンドウ内のIncludeというフォルダにCustomというフォルダを作成した後、MedianFilter.mqhというファイルに中身を丸ごとコピーしました。
getMedianValue.PNG

さて、コードの中身です。
//+------------------------------------------------------------------+
//メディアンフィルタを適用した移動平均
//+------------------------------------------------------------------+
#property copyright "Copyright 2015,  Daisuke"
#property link      "http://mt4program.blogspot.jp/"
#property version   "1.00"
#property strict
#property indicator_chart_window

#include <Custom/MedianFilter.mqh>

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

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

//移動平均
#property indicator_label1  "Avg"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrAqua
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2

#property indicator_type2   DRAW_NONE
#property indicator_type3   DRAW_NONE


//入力パラメータ メディアンフィルター長
input int   FilterLength = 3;

//入力パラメータ 期間
input int   AveragingPeriod = 5;

//入力パラメータ 表示移動
input int   MaShift = 0;

//入力パラメータ 移動平均種別
input ENUM_MA_METHOD MaMethod = MODE_SMA;

//入力パラメータ 適用価格
input ENUM_APPLIED_PRICE MaPrice = PRICE_CLOSE;

//インジケーター バッファ
double         avgBuffer[];
double         baseValues[];
double         medianBuffer[];

//------------------------------------------------------------------
//初期化
int OnInit()
{
// メディアンフィルタ長は奇数の必要がある。
if( FilterLength % 2 == 0 || FilterLength < 3 )
{
return(INIT_PARAMETERS_INCORRECT);
}

//インジケーターバッファを初期化する。
SetIndexBuffer(0,avgBuffer);
SetIndexBuffer(1,baseValues);
SetIndexBuffer(2,medianBuffer);

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[])            //スプレット
{
//(1)元となる値を計算する。
for( int i = 0; i < (rates_total - prev_calculated); i++ )
{
switch( MaPrice )
{
case PRICE_CLOSE:
baseValues[i] = close[i];
break;
case PRICE_OPEN:
baseValues[i] = open[i];
break;
case PRICE_HIGH:
baseValues[i] = high[i];
break;
case PRICE_LOW:
baseValues[i] = low[i];
break;
case PRICE_MEDIAN:
baseValues[i] = (high[i] + low[i]) / 2;
break;
case PRICE_TYPICAL:
baseValues[i] = (high[i] + low[i] + close[i]) / 3;
break;
case PRICE_WEIGHTED:
baseValues[i] = (high[i] + low[i] + close[i] + close[i]) / 4;
break;
}
}

//要素の0が一番新しいデータ
//(2)要素数-処理済み要素数-1~0のデータを更新する。
for( int i = (rates_total - prev_calculated - 1); i >= 0; i-- )
{
//(3)メディアンフィルタを適用する。
medianBuffer[i] = GetMedianValue(baseValues, rates_total, FilterLength, i);
//(4)移動平均を計算する。
avgBuffer[i] = iMAOnArray(medianBuffer, rates_total, AveragingPeriod, MaShift, MaMethod, i);
}

//中央値インデックスを計算する。
int halfIndex = (FilterLength - (FilterLength % 2)) / 2;

//メディアンフィルタの影響範囲はすべて更新する。
return(rates_total - 1 - halfIndex);
}


まず、今回はバッファを3つ用意しました。
移動平均を計算する元の値として7個値を定義できるため、その計算結果を格納しておくのばbaseValuesです。baseValuesに対して、メディアンフィルタを適用した値を保持しておくのがmedianValuesです。最後にmedianValuesの移動平均をとったものがavgValuesとなります。

コードが長くなってきた為(1)等、コード内のコメントの番号部分について解説するようにします。

��1)では、baseValuesに対して、パラメータで指定された値を保存します。メディアンフィルタは前後の値を参照する為、個別にループを作成して内容をすべて埋めています。
��2)ループを回していますが、前回までと違い今回は古い値からループを回しています。これは、medianBufferの内容が古いものから正しく埋まっていないと移動平均が求められない為です。
��3)メディアンフィルタを適用しています。この部分は前回とほぼ同じですが、対象がbaseValuesとなっています。
��4)移動平均を計算しています。前回はiMAという関数を使いましたが、これは指定された通貨ペアに対して移動平均をとります。今回は、iMAOnArrayというプログラム内の配列に対して移動平均を算出する関数を使用します。
iMAOnArray
使い方はiMAとよく似ていますが、第1引数に移動平均を求める元となる配列、第2引数に配列の大きさを指定します。
double iMAOnArray(
double array[], // 移動平均を求める元になる配列
int total, // 配列の大きさ
int ma_period, // 期間
int ma_shift, // 移動平均を右方向にシフトする数
int ma_method, // averaging method移動平均計算方法
int shift // 移動平均算出位置(最新位置からのシフト量)
);

外部ファイル化したMedianFilter.mqhの内容も掲載しておきます。
//+------------------------------------------------------------------+
//|                                                 MedianFilter.mqh |
//|                                         Copyright 2015,  Daisuke |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015,  Daisuke"
#property link      "http://mt4program.blogspot.jp/"
#property strict

//------------------------------------------------------------------
//メディアンフィルター適用後の値を取得する。
double GetMedianValue(
const double &targetValues[],   //メディアンフィルタを計算する元配列
int valuesCount,                //targetValuesの要素数
int filterLength,               //メディアンフィルタ長
int targetIndex)                //メディアンフィルタ値取得の対象インデックス
{
//ソート用領域を確保
double workValues[];
ArrayResize(workValues, filterLength);

//中央値インデックスを計算する。
int halfIndex = (filterLength - (filterLength % 2)) / 2;

int setIndex = 0 ;
// targetIndexを中央値として前後filterLength/2(小数点切り捨て)分だけ取得する。
for( int i = targetIndex - halfIndex; i <= targetIndex + halfIndex; i++ )
{
// データ端対策
int valueIndex = i ;
if( i < 0 ) valueIndex = 0;
if( i >= valuesCount ) valueIndex = valuesCount - 1;

workValues[setIndex] = targetValues[valueIndex];
setIndex++;
}

//データをソートして、ソート結果の中央値を取得する。
ArraySort(workValues);
double returnValue = workValues[halfIndex];

//領域解放・・・不要? なんとなく職業プログラマ的に確保したものは解放したくなる。
ArrayFree(workValues);

return returnValue;
}