MT4入門(4) – カスタムインディケータの作り方(2)

MT4入門の第4回目で、前回の「カスタムインディケータの作り方」の続きです。
「カスタムインディケータの作り方(2) – プログラミングの基礎」
というお題です。基礎ということでちょっと堅い部分もありますが、やっぱり基礎をしっかり勉強しておかないと応用がききませんので、ここは焦らずにお付き合い下さい。
MQL4のプログラムはC言語と同様に関数というブロックを並べることで構成されます。前回のサンプルプログラム(Sample1_Ind.mq4) 中に
int init()
{
}

とか、
int start()
{
}

のような書式が出てきましたが、このかたまりを関数と呼びます。
関数はユーザーが自由に名前をつけて作ることができますが、MQL4では、以下の3つの関数が特別な関数として予約されています。
init()
start()
deinit()

カスタムインディケータで必ず必要なのは、init()start() です。
start()インディケータの本体で、init()初期化のための関数です。
deinit() は後始末が必要なときに使いますが、Sample1_Ind では不要なので使っていません。
では、次に関数の書き方について見ていきます。
関数の中身は基本的にはインディケータの計算なので、
・変数の定義
・計算式の記述
・戻り値の指定

の順に書いていきます。
ますは変数の定義です。
変数を定義するときにはその変数の型を指定しなくてはいけません。
MQL4で使える変数の型としては色々ありますが、ここでは、数を表す型として intdouble を紹介します。
int は integer の略で整数を表します。
double実数を表すのですが、なぜ double というのかというと、C言語で実数を表す型として単精度(float)と倍精度(double)というのがあり、その倍精度の方をそのまま使ったのだと思います。
要するに、整数はint、プライスやインディケータの値など小数がつくものは double を使えばいいというわけです。
実際の変数の定義の仕方は
int i;
double x;

などと書くだけでもいいですが、
int i=0;
double x=1.05;

のように初期値を指定することもできます。
定義した後にすぐ具体的な値が入力される場合は定義だけでもいいですが、初期化しないと何の値が入っているかわからないので、できれば初期値を書いておいた方が確実です。
あ、ここで言い忘れましたが、MQL4 では、定義や式の最後にセミコロン ; を書くのを忘れないようにしましょう。これもC言語と同じスタイルです。
ただ、関数のように {} で囲んだときは最後に ; を書く必要はありません。
慣れてもセミコロンはよく忘れることがあるので、コンパイルでエラーが出たときにはエラーの行の前でセミコロンが抜けてないかチェックしてみてください。
コンパイルした後、エラーの行をダブルクリックするとその行に印がつきますが、下の図のように直接セミコロンが抜けているというエラーが出なくて、その次の行でエラーが出る場合もあるので注意が必要です。

次に変数の中でも配列変数について説明します。
配列変数は変数をいくつか並べたものですが、それぞれの変数に対して同じ計算をさせたり、変数をまとめて別の関数に渡したりするときに使います。
関数の中で配列変数を宣言する場合、通常確保する配列のサイズを指定します。例えば、
double x[10];
と書くと、10個の実数の変数を確保したことになります。
但し、実際に使える変数は、x[0] から x[9] までの10個となります。x[10] は使えないので注意してください。
では、ここまで説明したところでサンプルの Sample1_Ind.mq4 に戻ってみます。
start() の関数の中で Buf0[i] というように配列を使っていますが、よく見ると、start() の中で Buf0 の定義を行っていません。通常定義しない変数は使えないので、コンパイルでエラーが出るはずですが、この場合エラーは出ません。
どうしてかというと、関数の外側の #property 命令の後に
double Buf0[];
という形で定義しているからです。
なぜ、こんなところで定義するのかいうと、関数の中と外ではちょっと意味が違うのです。
関数の内部で定義されている変数はその関数の中だけで有効です。言い換えると、同じ名前の変数を別の関数の中で使ってもお互い影響を受けないということです。これは変数の有効な範囲を関数内に限定することで、プログラムの間違いを少なくするためです。
それに対して関数の外部で宣言した変数はそれぞれの関数の中も含めてプログラム全体で有効になります。
サンプルプログラムでは、Buf0 の配列にインディケータの値を計算させて入れるのですが、このようにチャートに表示させる値はプログラム全体で使う必要があるので、外部で宣言するのです。
あと、もう一つ注意する点は、宣言時に Buf0[] のように配列のサイズを指定していない点です。
これはちょっと特殊な指定の仕方ですが、チャート上では、過去に何個のデータがあるかわからないし、時間が経てば次々に増えていくので、予め配列のサイズが決められないのです。
MQL4では、外部で宣言したサイズなしの配列の最初の値、つまり、Buf0[0] は常に一番最新のバーでの値になります。そして、Bu0f[1]は1つ前のバー、Buf0[2]は二つ前のバーでの値ということになります。新しいバーが出ると、同じようなルールで配列の値がすべて置き換わります。
チャート上の始値、安値、高値、終値も同様のルールで、予約された配列変数に格納されます。つまり、最新のバーの始値、安値、高値、終値がそれぞれ、Open[0], Low[0], High[0], Close[0] という配列に入っていて、一つ前のバーは、Open[1], Low[1], High[1], Close[1]、二つ前のバーはOpen[2], Low[2], High[2], Close[2]となります。
Bars.png
このようにチャート上のバーの位置と配列の対応がわかったところでサンプルプログラムを見てみます。
for(i=limit-1; i>=0; i–)
{
Buf0[i] = (Close[i]+Close[i+1]+Close[i+2]+Close[i+3])/4;
}

の部分は繰り返し計算を表します。
変数 ilimit-1 から 1ずつ減らして i>=0 の範囲で {} の中身を繰り返すというものです。i– というのは i=i-1 という意味です。
これは言い換えると i=0 から limit-1 まで1ずつ増やして繰り返すのと同じことなので、
for(i=0; i < limit; i++)
と書くこともできます。同じく i++i=i+1 の意味です。要するに for の後に、(変数の初期値;繰り返す範囲;変数の増やし方)を指定して繰り返しの制御を行っているのです。
この書き方で説明すると、この繰り返しは
Buf0[0] = (Close[0]+Close[1]+Close[2]+Close[3])/4;
Buf0[1] = (Close[1]+Close[2]+Close[3]+Close[4])/4;
Buf0[2] = (Close[2]+Close[3]+Close[4]+Close[5])/4;
  :
  :

の順に、それぞれのバーで、そのバーから過去に遡った4つのバーの終値を平均しています。いわゆる4バーの単純移動平均です。
こういう例では、i=0 から計算を始めても問題はありませんが、EMAのようにインディケータそのものの過去の値を計算に利用する場合、
Buf0[0] = (Close[0]+Buf[1])/2;
Buf0[1] = (Close[1]+Buf[2])/2;
Buf0[2] = (Close[2]+Buf[3])/2;
 :
 :

の順番だと、Buf0[0] の計算にBuf0[1]を使っていますが、この時点では Buf0[1] はまだ計算されていません。これはプログラムの記述上間違いではないのでコンパイルではエラーは出ませんが、当然ながら正しい結果にはなりません。コンパイルでエラーが出ないけど結果がおかしいというのが最も間違いを見つけにくいパターンです。
なので、こういう場合があるということも想定して、インディケータの繰り返し計算では、
 :
 :
Buf0[2] = (Close[2]+Buf[3])/2;
Buf0[1] = (Close[1]+Buf[2])/2;
Buf0[0] = (Close[0]+Buf[1])/2;

となるように、i=limit-1 から始めていく順番の方が間違いがなくてがいいのです。
ところで、limit という変数は、
limit = Bars-IndicatorCounted();
と定義されています。Bars は、Open, Close などと同様に予め定義されている特別な変数で、チャート上のバーの総数を表します。なので、limit=Bar; だけでもいいのですが、レートが変化する度に全部のバーの計算をすると大変なので、IndicatoCounted()という過去の変化しなかったバーの数を返す関数を使って、それをBars から引くことで、変化したバーの部分だけ計算させるようにするのです。
最後にそれぞれの関数の最後に return(0) と書かれていますが、これが戻り値の指定です。この場合、0を戻すので、init()、 start()の値がそれぞれ0となりますが、なぜ0を戻すかということには深い意味はありません。
今日はこんなところです。次回はインディケータの表示の変え方などを説明する予定です。今回よりは実践的になると思います。
この記事、書くのに結構時間がかかります。次回の記事も読んでみたい方は下のクリックにご協力ください。
にほんブログ村 為替ブログへ為替ブログランキング 現在10位
へーっと思った方はクリックにご協力ください↑



コメントは受け付けていません