MQL5のフォーラムにて公開されている記事を参考にまとめていきます。
MQL5: CREATE YOUR OWN INDICATOR
自分の手で独自のインディケーターを作成する方法を学ぶことが本稿の目的です。
インディケーターとは、計算された値の集合を便利な方法で画面に表示させるものです。プログラムでは、値の集合は配列として表現されます。
インディケーター:
価格の配列を処理し、処理した結果をインディケーターの出力値として別の配列に記録するアルゴリズムを書くこと
トゥルー・ストレングス・インデックス (TSI)
トゥルー・ストレングス・インデックス (TSI) は、二重平滑化されたモメンタムに基づいて、トレンドや売られすぎ・買われすぎのエリアを特定するインディケータです。ウィリアム・ブラウ氏が開発したこの有名なTSIを例に、インディケーターの作成について考えていきます。
TSI(CLOSE,r,s) =100*EMA(EMA(mtm,r),s) / EMA(EMA(|mtm|,r),s)
mtm = CLOSEcurrent – CLOSEprev: 現在のバーの終値と前のバーの終値の差を表す値の配列(mtm)で、CLOSE価格を使用しています。
EMA(mtm,r)はmtmの値を指数関数的に平滑化したもので、周期の長さを「r」としています。
EMA(EMA(mtm,r),s) = EMA(mtm,r)の値をさらに「s」の期間で指数平滑化したものです。|mtm| は mtmの絶対値を意味します。
- r = 25期間
- s = 13期間
指標の計算に影響を与える3つのパラメータは、期間r、期間s、計算に使用する価格の種類(CLOSE)です。
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots 1
//---- プロット TSI
#property indicator_label1 "TSI"
#property indicator_type1 DRAW_LINE
#property indicator_color1 Blue
#property indicator_style1 STYLE_SOLID
#property indicator_width1 1
//--- 入力パラメータ
inputint r=25;
inputint s=13;
//--- 指標バッファ
double TSIBuffer[];
//+------------------------------------------------------------------+
//| カスタムインディケーターの初期化機能
//+------------------------------------------------------------------+
int OnInit()
{
//--- 指標バッファのマッピング
SetIndexBuffer(0,TSIBuffer,INDICATOR_DATA);
//---
return(0);
}
//+------------------------------------------------------------------+
//| カスタムインディケーターの繰り返し機能
//+------------------------------------------------------------------+
int OnCalculate(constint rates_total,
constint prev_calculated,
constdatetime& time[],
constdouble& open[],
constdouble& high[],
constdouble& low[],
constdouble& close[],
constlong& tick_volume[],
constlong& volume[],
constint& spread[])
{
//---
//--- 次の呼び出しのためにprev_calculatedの値を返す
return(rates_total);
}
//+------------------------------------------------------------------+
OnCalculate()
OnCalculate()関数は、Calculateイベントのハンドラで、インディケーターの値を再計算してチャートに再描画をする必要があるときに表示されます。新しいティックデータの受信やシンボル履歴の更新などのイベントがあった場合に反応します。そのため、インディケーターの値のすべての計算のメインコードは、このOnCalculate()関数の中に配置する必要があります。もちろん、補助的な計算は他の別の関数で実装することができますが、それらの関数はOnCalculateハンドラ内で使用する必要があります。デフォルトでは、MQL5ウィザードはOnCalculate()の第2形式で定義され、すべてのタイプの時系列にアクセスできます。必要なデータ配列が1つの場合は、OnCalculate()の呼び出し形式を第1形式に変更します。
第1形式と第2形式のサンプル
「パラメータ」タブでCloseを選択すると(デフォルトで選択されています)、OnCalculate()に渡されるprice[]にはClose価格が含まれます。Typical Priceを選択した場合、price[]には各期間の(High+Low+Close)/3の価格が格納されます。
ratestotalパラメータは、price[]配列のサイズを示します。これは、サイクル内の計算を整理するのに便利です。price[]の要素のインデックスは、0から始まり、過去から未来へと向かっています。つまり、price[0]には最も古い値が、price[ratetotal-1]には最も新しい配列要素が格納されています。
補助指標バッファの整理
チャートに表示されるのは1本のライン、つまり1つのインディケーター配列のデータだけです。しかし、その前に中間計算を整理する必要があります。中間データは、INDICATOR_CALCULATIONS属性でマークされたインディケーター配列に格納されます。式から、追加の配列が必要であることがわかります。
- mtmの値 – 配列 MTMBuffer[]
- |mtm|の値 – 配列 AbsMTMBuffer[]
- EMA(mtm,r)- 配列 EMA_MTMBuffer[]
- EMA(EMA(mtm,r),s) – 配列 EMA2_MTMBuffer[]
- EMA(|mtm|,r) – 配列 EMA_AbsMTMBuffer[]
- MA(EMA(|mtm|,r),s) – 配列 EMA2_AbsMTMBuffer[]
以上のように、グローバルレベルでさらに6つのdouble型の配列を追加し、OnInit()関数でこれらの配列を指標バッファに組み込む必要があります。指標バッファのプロパティは7に等しくなければなりません(1つあって、さらに6つのバッファが追加されました)。
#property indicator_buffers 7
中間計算
MTMBuffer[]バッファとAbsMTMBuffer[]バッファの値の計算を整理するのはとても簡単です。ループの中で、price[1]からprice[rates_total-1]までの値を1つずつ調べて、差を1つの配列に、差の絶対値を2つ目の配列に書き込むだけです。
次の段階では、これら配列の指数平均値を計算します。この計算には2つの方法があります。1つ目の方法は,アルゴリズム全体をミスのないように書きます。2つ目の方法は、この目的のために用意された既にデバッグ済みの関数を使用します。
MQL5には、配列値で移動平均を計算する組み込み関数はありませんが、関数のライブラリMovingAverages.mqh(ヘッダファイル)が用意されています。このライブラリの完全なパスは、terminaldirectory/MQL5/Include/MovingAverages.mqhで、terminaldirectoryはメタトレーダー5のプラットフォームがインストールされている場所です。このライブラリはインクルードファイルと言われており、4つの従来通りの方法の1つを使って配列上の移動平均を計算するための関数が含まれています。
- 単純平均
- 指数平均
- 平滑平均
- 線形加重平均
これらの関数を使用するには、MQL5プログラムのコード見出しに次のように追加します。ただし、ExponentialMAOnBuffer()という関数が必要です。この関数は、値の配列に対して指数移動平均を計算し、平均の値を別の配列に記録します。
#include <MovingAverages.mqh>
配列の平滑化の関数
さて、インクルードファイルMovingAverages.mqhには、8つの関数が含まれていますが、同じ型の関数が4つずつ入った2つのグループに分けられます。最初のグループには、配列を受け取り、指定した位置の移動平均の値を単純に返す関数が含まれています。
- SimpleMA() – 単純移動平均の値を計算します。
- ExponentialMA() – 指数移動平均の値を計算します。
- SmoothedMA() – 平滑化された移動平均の値を計算します。
- LinearWeightedMA() – 線形加重移動平均の値を計算します。
これらの関数は、配列の平均値を一度だけ求めることを目的としており、複数回の呼び出しには最適化されていません。このグループの関数をループ内で使用する(平均の値を計算し、さらに計算した各値を配列に書き込む)必要がある場合は、最適なアルゴリズムを編成する必要があります。
2つ目の関数群は、初期値の配列に基づいて移動平均の値で受信者の配列を埋めるためのものです。
- SimpleMAOnBuffer() – 出力配列buffer[]をprice[]配列からの単純移動平均の値で埋めます。
- ExponentialMAOnBuffer() – 出力配列 buffer[]を price[]配列からの指数移動平均の値で埋めます。
- SmoothedMAOnBuffer() – price[]配列からの平滑化された移動平均の値で出力配列 buffer[]を埋めます。
- LinearWeightedMAOnBuffer() – 出力配列 buffer[]を price[]配列からの線形加重移動平均の値で埋めます。
配列 buffer[]、price[]、平均化期間を除くすべての関数は、OnCalculate()関数のパラメータと同様の目的で、さらに3つのパラメータ(ratetotal、prevcalculated、begin)を取得します。このグループの関数は、インデックスの方向(AS_SERIESフラグ)を考慮して、渡されたprice[]とbuffer[]の配列を正しく処理します。beginパラメータは、意味のあるデータが始まる元の配列のインデックス、つまり処理が必要なデータを示します。MTMBuffer[]配列の場合、MTMBuffer[1]=price[1]-price[0]なので、実データはインデックス1から始まります。MTMBuffer[0]の値は未定義であり、それがbegin=1の理由です。
//—最初の移動平均を計算する
ExponentialMAOnBuffer(ratetotal,prevcalculated,
1, // 平滑化のためのデータが利用可能な開始点となるインデックス
r, // 指数平均の期間
MTMBuffer, // 平均値を計算するためのバッファ
EMA_MTMBuffer); // このバッファに平均の値を入れる
ExponentialMAOnBuffer(ratetotal, prevcalculated, 1,r,AbsMTMBuffer,EMA_AbsMTMBuffer);
平均の計算をする際には、期間を考慮する必要があります。なぜなら、出力配列では、計算された値がわずかな遅延で埋められ、平均の計算期間が大きくなればなるほど遅延が大きくなるからです。例えば,10 期間の場合,結果として得られる配列の値は,begin+period-1=begin+10-1 で始まることになります.buffer[]のさらなる呼び出しでは、このことを考慮し、begin+period-1というインデックスで処理を開始する必要があります。
このように、MTMBuffer[]とAbsMTMBufferの配列から、第2次指数移動平均の値を簡単に取得することができます。
begin=1+r-1 (rは第1次指数移動平均の期間で、処理はインデックス1で開始)なので、beginの値はrに等しくなります。EMA2_MTMBuffer[]およびEMA2_AbsMTMBuffer[]の出力配列では、計算された値はインデックスr+s-1から始まります。これは、インデックスrで入力配列の処理を開始し、第2次指数移動平均の期間がsに等しいためです。すべての事前計算の準備ができたので、次はチャートにプロットされるインディケーターバッファTSIBuffer[]の値を計算します。
まとめ
トゥルー・ストレングス・インデックス (TSI) インディケーターの作成例をもとに、MQL5でインディケーターを作成する際の基本的な手順を説明しました。独自のカスタムインディケーターを作成するには、MQL5ウィザードを使用して、インディケーターの設定に関する予備的なルーチン操作を行うことができます。OnCalculate()関数の必要な形式(第1形式、第2形式)を選択します。
必要に応じて、中間計算用の配列を追加し、SetIndexBuffer()関数を使用して対応する指標バッファに組み込みます。これらのバッファには、INDICATOR_CALCULATIONSタイプを指定します。OnCalculate()での計算は、価格データが変化するたびに呼び出されるので、最適化が必要です(本稿では省略)。デバッグ済みの関数(ヘッダーファイル、インクルードファイル)を使うことで、コードを書きやすく、読みやすくすることが可能です。