MQL5のフォーラムにて公開されている記事を参考にまとめていきます。
CUSTOM INDICATORS IN MQL5 FOR NEWBIES
MQL5言語を理解し、自分の手でカスタムインディケーターを作成することが本稿の目的です。基礎を繰り返すということは大事なことです。基礎があるからこそ応用に対応できる底力をつけることが可能です。テクニカル分析の最も有名な指標である単純移動平均(SMA)を考えてみましょう。その計算方法は簡単です。
SMA = SUM (CLOSE (i), MAPeriod) / MAPeriod
- SUM – 値の合計
- CLOSE (i) – i番目のバーの終値
- MAPeriod – 平均化するバーの数(平均化期間)
//+------------------------------------------------------------------+
//| SMA.mq5 |
//| Copyright 2009, MetaQuotes Software Corp. |
//| http://www.mql5.com |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots 1
#property indicator_type1 DRAW_LINE
#property indicator_color1 Red
input int MAPeriod = 13;
input int MAShift = 0;
double ExtLineBuffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
void OnInit()
{
SetIndexBuffer(0, ExtLineBuffer, INDICATOR_DATA);
PlotIndexSetInteger(0, PLOT_SHIFT, MAShift);
PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, MAPeriod - 1);
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
if (rates_total < MAPeriod - 1)
return(0);
int first, bar, iii;
double Sum, SMA;
if (prev_calculated == 0)
first = MAPeriod - 1 + begin;
else first = prev_calculated - 1;
for(bar = first; bar < rates_total; bar++)
{
Sum = 0.0;
for(iii = 0; iii < MAPeriod; iii++)
Sum += price[bar - iii];
SMA = Sum / MAPeriod;
ExtLineBuffer[bar] = SMA;
}
return(rates_total);
}
//+------------------------------------------------------------------+
以下の2点を考慮する必要があります。
- コードの各文字列の目的
- プログラムコードとクライアント端末との相互作用
SMAコードの構造
今回のインディケーターのコード全体は、以下の3つの部分に分けられます。
1. グローバルレベルの変数で括弧を使わずに書かれたコード
2. OnInit()関数の説明
3. OnCalculate()関数の説明
MQL5において関数の意味は大変広いです。通常のプログラミング言語であれば関数は常にいくつかの入力パラメータを受け取り、計算された値を返しますが、MQL5の関数は、チャート操作、取引操作、ファイル操作などを行うことができます。
SMAとMT5端末との連携
ここでは、SMA.mq5を開いた状態でメタエディターの “コンパイル “キーを押して得られたコンパイル済みファイルSMA.ex5の動作を考えてみます。拡張子.mq5のテキストファイルは、単なるテキスト形式のソースコードであり、クライアント端末で使用するためには、コンパイルする必要があることに注意する必要があります。
ナビゲーターウィンドウからこのインディケーターをチャートに挿入すると、メタトレーダーはインディケーターの最初の部分のコードを実行します。その後、この関数の1回の実行のために関数OnInit()が呼び出され、さらに、ティックデータ(新しいクォートの到着後)で、OnCalculate()関数が呼び出され、この関数のコードが実行されます。 インディケーターにOnDeInit()があれば、メタトレーダーは、チャートからインディケータを切り離した後、またはタイムフレームを変更した後に、一度だけこの関数を呼び出します。
このように説明すると、インディケーターのすべての部分の意味と目的が明確になります。グローバルレベルのコードの最初の部分には、インディケーターの開始後に1回実行されるいくつかの単純な演算子があります。さらに、インディケーターのすべてのブロックで「見える」変数の宣言があり、インディケーターがチャートに表示されている間、その値を記憶します。
一度だけ実行される定数や関数は、OnInit()関数内に配置する必要があります。なぜなら、OnCalculate()関数のブロック内に配置するのは好ましくないからです。インディケーターの値をバーごとに計算するコードは、OnCalculate()関数の中に入れるべきです。
インディケーターがチャートから削除された後に、無駄なゴミ(もしあれば)をチャートから削除する手続きは、OnDeInit()の中に配置する必要があります。例えば、インディケーターが作成したグラフィック描画のオブジェクトを削除するために必要です。
//—- インディケーターがメインウィンドウにプロットされます。
#property indicator_chart_window
//—- 1つのバッファが インディケーターの計算とプロットに使用されます。
#property indicator_buffers 1
//—- グラフィックのプロットは1つのみ使用されます。
#property indicator_plots 1
//—- インディケーターはラインとしてプロットされます。
#property indicator_type1 DRAW_LINE
//—- インディケーターの線の色は赤です。
#property indicator_color1 Red
定数の定義なので行の終わりにセミコロン(;)がありません。
このシンプルな移動平均には、ユーザーが変更できるパラメータが2つです。平均化期間と、時間軸に沿ったインディケーターの水平方向のシフト(バー)です。これら2つのパラメータは、入力変数として宣言されています。
//---- インディケーターの入力パラメータ input int MAPeriod = 13; //平均期間 nput int MAShift = 0; //水平方向のシフト(バー)
最後のコード行で、カッコが付いていないのは、動的配列ExtLineBuffer[]の宣言です。
//---- 動的配列の宣言 //指標バッファとしてさらに使用される double ExtLineBuffer[];
これがグローバル変数として宣言されているのには、いくつかの理由があります。まず第1に、この配列は指標バッファに変換される必要があり、OnInit()関数のブロック内で実装されます。第2に、指標バッファ自体は、OnCalculate()関数内で使用されます。第3に、この配列はチャート上に曲線としてプロットされるインディケーターの値を格納します。グローバル変数として宣言されているため、インディケーターのすべてのブロックで利用可能で、インディケーターがチャートから切り離されるまで、常にその値を保持します。
OnInit()関数の中身は3つの演算子だけで表現されており、全てメタトレーダー5の組み込み関数です。最初の関数の呼び出しは、0番目の指標バッファに1次元の動的配列ExtLineBuffer[]を割り当てます。入力パラメータの値を変えて別の関数を2回呼び出すと、価格軸に沿ってインディケーターをシフトさせることができ、番号MAPeriodを持つバーからそのプロットを指定することができます。
void OnInit()
{
//----+//---- 動的配列ExtLineBufferに0番目のインディケーターのバッファを割り当てる
SetIndexBuffer(0,ExtLineBuffer,INDICATOR_DATA);
//---- MAShiftバーによる横軸方向のプロットシフトを設定する
PlotIndexSetInteger(0,PLOT_SHIFT,MAShift);
//---- MAPeriodの番号でバーから始まるプロットを設定する
PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,MAPeriod);
//----+
}
PlotIndexSetInteger()関数の最後の呼び出しでは、MAPeriodに等しい値(OnCalculate()関数のbeginパラメータ経由)を別のインディケーターに渡しています。理屈は簡単で、最初のMaPeriod-1のバーには平均化するものがないので、このインディケーターのプロットには意味がないからです。しかし、この値は、別のインディケーターの計算の原点をシフトするために渡されるべきです。これは、カスタムインディケーターで使用され、インディケーターのこのブロックに配置することができる組み込み関数の完全なリストではありません。
最後に、OnCalculate()関数のコードを考えてみましょう。この関数には、OnInit()関数のようなカスタムコールはありません。なぜなら、これらの関数はメタトレーダーのクライアント端末から呼び出されるからです。そのため、この関数の入力パラメータは定数として宣言されています。
int OnCalculate(
const int rates_total, // 現在のティックでの履歴の利用可能なバーの数
const int prev_calculated,// 前のティックで計算されたバーの数
const int begin, // 最初のバーのインデックス
const double &price[] // 計算に使う価格の配列
)
入力パラメータは変更できず、この値はMT5端末から渡され、関数のコードで使用されます。OnCalculate()関数は、return(rate_total)関数を用いて、MT5端末にその値を返します。MT5端末は、OnCalculate()の実行後、現在のティックのこの値を受け取り、返された値を別のパラメータprev_calculatedに渡します。したがって、バーインデックスの範囲を決定し、前のティックの後に現れたインディケーターの新しい値に対してのみ、一度に計算を実行することが常に可能です。
MT5端末でのバーの順序付けは、左から右の順に実行されることに注意する必要があります。チャートに表示される最も古いバー(左)のインデックスは0で、次のバーのインデックスは1です。指標バッファExtLineBuffer[]の要素も同じ順序です。インディケーターの関数OnCalculate内のシンプルなコード構造は、多くのテクニカル分析指標に共通するものです。
OnCalcualte()関数のロジックは以下の通りです。
- 計算に必要なバーの存在を確認する
- ローカル変数の宣言
- 計算を開始するバーのインデックスを取得します。
- 指標の計算のメインループ
- 演算子return()を使って、rate_totalの値をMT5端末に返す
移動平均の平均化期間が200であるにもかかわらず、クライアント端末に100本しかない場合、計算に必要なバーがないため、計算を行う必要がありません。そこで、演算子returnを使ってクライアント端末に0を返さなければなりません。インディケーターは、他のインディケーターのデータにも適用することができます。定数beginを使って中間計算の番号の橋渡しをすることができます。
ローカル変数は、OnCalculate()関数内の中間計算に使われます。メインループでのバーの処理は、昇順として自然に左から右へと行われます。逆向きも可能ですが、通常はは昇順を使うほうがよいでしょう。メインループの変数は「bar」と名付けられていますが、多くのプログラマーは「i」という名前を使います。メインループでは、その期間の前のバーの価格を累積的に合計し、その値を平均期間で割っています。これが繰り返されてSMAの値が得られます。
メインループが終了すると、OnCalculate関数は変数rate_totalから利用可能なバーの数を返します。OnCalculate()関数の呼び出しでは、この値がクライアント端末から変数prev_calculatedに渡されます。この値に1を加えたものが、メインループの開始インデックスとして使用されます。
//+------------------------------------------------------------------+
//| SMA.mq5
//| Copyright 2009, MetaQuotes Software Corp.
//| http://www.mql5.com
//+------------------------------------------------------------------+
//---- インディケーターをメインウィンドウにプロットします。
#property indicator_chart_window
//---- インディケーターの計算とプロットには、1つのバッファが使用されます。
#property indicator_buffers 1
//---- グラフィックプロットは1つのみ使用されます
#property indicator_plots 1
//---- インディケーターはラインとしてプロットされます。
#property indicator_type1 DRAW_LINE
//---- インディケーターの線の色は赤です。
#property indicator_color1 Red
//---- インディケーターの入力パラメータ
input int MAPeriod = 13; //平均期間
input int MAShift = 0; //水平方向のシフト(バー単位)
//---- 動的配列の宣言
//指標バッファとして使用されます。
double ExtLineBuffer[];
//+------------------------------------------------------------------+
//| カスタムインディケーターの初期化関数
//+------------------------------------------------------------------+
void OnInit()
{
//----+
//---- 動的配列ExtLineBufferに0番目のインディケーターのバッファを割り当てます。
SetIndexBuffer(0,ExtLineBuffer,INDICATOR_DATA);
//---- MAShiftバーによる横軸のプロットシフトを設定する。
PlotIndexSetInteger(0,PLOT_SHIFT,MAShift);
//---- MAPeriodという番号のバーからプロットを開始するように設定します。
PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,MAPeriod);
//----+
}
//+------------------------------------------------------------------+
//| カスタムインディケーターの繰り返し関数
//+------------------------------------------------------------------+
int OnCalculate(
const int rates_total, // 現在のティックのヒストリーで利用可能なバーの数
const int prev_calculated,// 前のティックで計算されたバーの数
const int begin, // 最初のバーのインデックス
const double &price[] // 計算対象となる価格配列
)
{
//----+
//---- 計算に必要なバーが存在するかどうかのチェック
if (rates_total < MAPeriod - 1 + begin)
return(0);
//---- ローカル変数の宣言
int first, bar, iii;
double Sum, SMA;
//---- メインループの最初の開始インデックスの計算
if(prev_calculated==0) // 指標の最初の開始をチェックする
first=MAPeriod-1+begin; // すべてのバーの開始インデックス
else first=prev_calculated-1; // 新しいバーの開始インデックス
//---- 計算のメインループ
for(bar = first; bar < rates_total; bar++)
{
Sum=0.0;
//---- 現在のバーの平均化のための和算ループ
for(iii=0;iii<MAPeriod;iii++)
Sum+=price[bar-iii]; // Sum = Sum + price[bar - iii]に等しくなります。
//---- 平均化された値を計算する
SMA=Sum/MAPeriod;
//---- 算出したSMAの値をインディケーターバッファの要素に設定する
ExtLineBuffer[bar]=SMA;
}
//----+
return(rates_total);
}
//+------------------------------------------------------------------+