(備忘録)MQL4⇒MQL5で苦戦したこと。インジケータ関数。
前置き
MQL4からMQL5へ移植する際に苦戦したことを備忘録として残しておきます。
MQL4に関する日本語の情報はかなり出回っていますが、MQL5に関する日本語の情報は珍しいです。天地の差で少ないです。なんでや。
かみ砕いた文章で説明されてる情報はもっと少ないです。
ここに辿り着けた人は、とりあえず有難く読んでください(
というのも基本的な文法や、考え方はMQL4と同様です。
ですが、MQL5まで手を出している時点で、プログラミングに関してはかなりの技術者だと思います。基本的なところで詰まることは少ないのかもしれません。
前置きはともかく。
誰かの役に立つと嬉しいな、という軽いノリで情報を残しておきます。
MQL4とMQL5での書き方の違い
今回、自分が一番苦戦したのはMT4・MT5に初期から搭載されているインジケータの関数を使用するタイミングでした。
例えばですが、ボリンジャーバンド関数。
MQL4では、ボリンジャーバンドの関数(iBands関数)は以下のように引数を指定してあげることで使用できます。
//-- MQL4
double iBands(
string symbol, // 通貨ペア
int timeframe, // 時間軸
int period, // 平均期間
double deviation, // 標準偏差
int bands_shift, // バンドシフト
int applied_price, // 適用価格
int mode, // ラインインデックス
int shift // シフト
);
一方で、MQL5の場合、ボリンジャーバンドの関数(iBands)は以下のように指定します。
//-- MQL5
int iBands(
string symbol, // 銘柄名
ENUM_TIMEFRAMES period, // 期間
int bands_period, // 平均線の計算の期間
int bands_shift, // 指標の水平シフト
double deviation, // 標準偏差の数
ENUM_APPLIED_PRICE applied_price // 価格の種類かハンドル
);
MQL4とMQL5では引数の個数が異なるんですよね。
それどころか、戻り値の型についても「double」と「int」で異なります。
MQL4と同じ感覚でインジケータの関数を使用すると頭を抱えることになります。
MQL4でボリンジャーバンド関数(iBands関数)を使用する場合、以下のように書いてあげることで直接ボリンジャーバンドを算出することができます。
//-- MQL4
BB_Buffer_Upper[i] = (NULL, PERIOD_CURRENT, 20, 2.0, 0, PRICE_CLOSE, MODE_UPPER, 0);
BB_Buffer_Middle[i] = (NULL, PERIOD_CURRENT, 20, 2.0, 0, PRICE_CLOSE, MODE_MAIN, 0);
BB_Buffer_Lower[i] = (NULL, PERIOD_CURRENT, 20, 2.0, 0, PRICE_CLOSE, MODE_LOWER, 0);
これと同じ要領でMQL5で書くと、よくわからない値が返ってきます。
(「15」とか「135464352134543」みたいな。)
//-- MQL5
BB_Buffer[i] = iBands(NULL,PERIOD_CURRENT,BB_Period,0,2.0,PRICE_CLOSE);
MQL4で返ってくる値は「ボリンジャーバンド」の各種値を算出した結果なのですが、一方で、MQL5で返ってくる値は「指標ハンドル」の値です。
「指標ハンドル」の値??
はい、「指標ハンドル」の値です。
ボリンジャーバンドの算出結果ではありません。残念でした。
ここが苦戦する一番の要因です。
MQL4では必要なかった概念を理解する必要があります。
それが「ハンドル」というもの。
この概念を理解するまでにめちゃくちゃ時間を要しました。
MQL4とMQL5での関数の使用法の違いについて、
以下、順番に解説していきます。
MQL5でのコード例(ボリンジャーバンド)
結論から言うと、MQL5でボリンジャーバンドを算出する場合、以下のようなステップで書いてあげる必要があります。
//-- MQL5でボリンジャーバンドを使用する場合は以下のように記載する。
//-- step.1: ハンドル値を算出。
int BB_handle = iBands(NULL,PERIOD_M15,20,0,2.0,PRICE_CLOSE);
//-- step.2:ボリンジャーバンドの算出する値を格納するための配列を用意する。
double BB_Buffer_Upper[];
double BB_Buffer_Middle[];
double BB_Buffer_Lower[];
//-- step.3:ボリンジャーバンドの算出結果を上記配列に格納する。
CopyBuffer(BB_handle ,1,iTime(NULL,PERIOD_M15,2999),iTime(NULL,PERIOD_M15,0),BB_Buffer_Upper);
CopyBuffer(BB_handle ,0,iTime(NULL,PERIOD_M15,2999),iTime(NULL,PERIOD_M15,0),BB_Buffer_Middle);
CopyBuffer(BB_handle ,2,iTime(NULL,PERIOD_M15,2999),iTime(NULL,PERIOD_M15,0),BB_Buffer_Lower);
//-- 上記の様な指定方法だと15分足のボリンジャーバンドを現在の足から3000本前までを取得できる。
//-- 以下を実行すると値を確認できる。i: 0~2999で任意の数字。
Print(BB_Buffer_Upper[i]);
Print(BB_Buffer_Middle[i]);
Print(BB_Buffer_Lower[i]);
上記のコード例を解説いたします。
step.1: ハンドル値を算出。
//-- step.1: ハンドル値を算出。
int BB_handle = iBands(NULL,PERIOD_CURRENT,BB_Period,0,BB_deviation,PRICE_CLOSE);
まず、これで何をしているのかって話なのですが。
これは、簡単に言うとボリンジャーバンドの計算結果が格納されている場所を特定しています(指標ハンドル値の算出)。
指標ハンドル値とは、すごく簡単に言うと「各種インジケータの関数が算出した結果が保存されている場所」を示したものになります。
※ここで算出した「int BB_handle」には、ボリンジャーバンドの算出結果ではなく、よくわからない数字が入っています。
(「15」とか「135464352134543」みたいな。)
データが格納されてるパスをイメージすると解りやすいかもです。
>>(イメージ:C:\Users\Users\junico\files\xxxxx.txt)
・ハンドル値:「C:\Users\Users\junico\files\」
・ボリンジャーバンドの算出結果:「xxxxx.txt」
上記例で欲しいものは「xxxxx.txt」になるのですが、これを関数を使って直接呼び出すことはできず、計算結果を入手するための場所までしか、呼び出すことはできません。
この時、引数として「通貨ペア」・「時間軸」・「期間」・「シフト」・「偏差」・「適用価格」を指定します。
※ボリンジャーバンドの場合、上ライン・中央ライン・下ラインが存在しますが、このタイミングでは指定できません。
step.2:ボリンジャーバンドの算出する値を格納するための配列を用意する。
//-- step.2:ボリンジャーバンドの算出する値を格納するための配列を用意する。
double BB_Buffer_Upper[];
double BB_Buffer_Middle[];
double BB_Buffer_Lower[];
次に、ボリンジャーバンドの計算結果を格納する配列を用意してあげる必要があります。この配列に関してですが、MQLコードの上の方で指定している「#property indicator_buffers」で用意した配列を使ってあげても問題ありません。どちらでも良いです。
ボリンジャーバンドの計算結果がここに格納されます。ボリンジャーバンドの場合、上ライン・中央ライン・下ラインが存在しますので、配列は3つ必要ですね。全部使わない場合は3つ用意する必要はありません。
step.3:ボリンジャーバンドの算出結果を上記配列に格納する。
//-- step.3:ボリンジャーバンドの算出結果を上記配列に格納する。
CopyBuffer(BB_handle ,1,iTime(NULL,PERIOD_M15,2999),iTime(NULL,PERIOD_M15,0),BB_Buffer_Upper);
CopyBuffer(BB_handle ,0,iTime(NULL,PERIOD_M15,2999),iTime(NULL,PERIOD_M15,0),BB_Buffer_Middle);
CopyBuffer(BB_handle ,2,iTime(NULL,PERIOD_M15,2999),iTime(NULL,PERIOD_M15,0),BB_Buffer_Lower);
//-- 上記の様な指定だと15分足のボリンジャーバンドを現在の足から3000本前までを取得できる。
そして、(CopyBuffer関数)を使用することで、用意してあげた配列に計算結果を格納することができます。
//-- 引数の指定方法① 1 番目の位置と必要な要素数によっての呼び出し
int CopyBuffer(
int indicator_handle, // 指標ハンドル
int buffer_num, // 指標バッファ番号
int start_pos, // 開始位置
int count, // 複製する量
double buffer[] // 受け取り側の配列
);
//-- 引数の指定方法② 開始日と必要な要素数によっての呼び出し
int CopyBuffer(
int indicator_handle, // 指標ハンドル
int buffer_num, // 指標バッファ番号
datetime start_time, // 開始日と時刻
int count, // 複製する量
double buffer[] // 受け取り側の配列
);
//-- 引数の指定方法③ 必要な時間間隔の開始日と終了日によっての呼び出し
int CopyBuffer(
int indicator_handle, // 指標ハンドル
int buffer_num, // 指標バッファ番号
datetime start_time, // 開始日と時刻
datetime stop_time, // 終了日と時刻
double buffer[] // 受け取り側の配列
);
//-- ※指標バッファ番号について
//-- ボリンジャーバンドを使用する場合は、以下のように指定します。
//-- 0:BASE_LINE(中央ライン)、1:UPPER_BAND(上ライン)、2:LOWER_BAND(下ライン) です。
(CopyBuffer関数)の引数ですが、上記のように3通りあります。
今回紹介したコード例では③を使用しています。
開始日の時刻と終了日の時刻をiTime()関数を使用して指定しました。
※指標バッファ番号について
ボリンジャーバンドを使用する場合は、以下のように指定します。
・ 0:BASE_LINE(中央ライン)
・1:UPPER_BAND(上ライン)
・2:LOWER_BAND(下ライン)
ちなみに、(CopyBuffer関数)を使って受け取った配列は数字が大きい方が時間軸的に新しいデータになります。
・時系列:double buffer[0]よりも、double buffer[100]の方が時間軸的に新しい(現在の足により近い時間軸の値)
(画像引用元)https://www.mql5.com/ja/docs/series/copybuffer
「CopyBuffer関数」の処理としては、サーバから計算結果をダウンロードしてきているようです。戻り値が示す値ですが、計算結果の個数(配列の中の数字の数)を返します。
(※例:戻り値が3000の場合、配列は[0]~[2999])
この時、「-1」が結果として帰ってきた場合、エラーです。
エラーの原因ですが、サーバからのダウンロードが間に合わなかった場合に起きるようです。
以下のように工夫してあげる必要があります。
if(CopyBuffer(BB_handle ,1,iTime(NULL,PERIOD_M15,2999),iTime(NULL,PERIOD_M15,0),BB_Buffer_Upper) < 0
|| CopyBuffer(BB_handle ,0,iTime(NULL,PERIOD_M15,2999),iTime(NULL,PERIOD_M15,0),BB_Buffer_Middle) < 0
|| CopyBuffer(BB_handle ,2,iTime(NULL,PERIOD_M15,2999),iTime(NULL,PERIOD_M15,0),BB_Buffer_Lower < 0)
{
return(0);
}
この時、無限に「-1」を返すようなら、どこかおかしいので確認してください。
//-- 以下を実行すると値を確認できる。i: 任意の数字。
Print(BB_Buffer_Upper[i]);
Print(BB_Buffer_Middle[i]);
Print(BB_Buffer_Lower[i]);
上手くいけば、上記のように実行すると結果がきちんと返ってきます。
ここでもし、明らかに変な数字が返ってくるようだと(CopyBuffer関数)の引数を間違えてます。
たぶん、考えられる要因は「開始」・「終了」が逆とか。
「int count, // 複製する量」に対して「開始~終了」の期間が乖離している場合もそうです。また、期間に対して過去のローソク足の数が足りてない場合(過去のローソク足が5000しかないのに対して、範囲を99999で指定している)などですね。
ミスが少なくなる引数の指定方法は例で挙げた③が堅いと思います。
個人的な見解ですが。
まとめ
MQL5で各種関数を使用する場合、
①欲しい計算結果が保存されている場所を特定する。
②保存先のデータをコピーする。(CopyBuffer関数)
といったステップが必要になります。
今回はボリンジャーバンドの関数で紹介しましたが、移動平均線(iMA関数)といった他のインジケータ関数を使用する場合でも同じステップが必要になります。
参考リンク:https://www.mql5.com/ja https://yukifx.web.fc2.com/