前回はインジケータを使ってトレードシグナルを生成する方法の前段階として、インジケータの値取得について解説をしました。
まず、そもそもインジケータとは何か?という部分を説明し、その上でインジケータにはシングルバッファインジケータとマルチバッファインジケータがあることをお伝えしました。
※詳細は前回記事の、
「シングルバッファインジケータとは?」セクション
及び
「マルチバッファインジケータとは?」セクションをご覧ください。
シングルバッファインジケータの値取得の例として、単純移動平均(SMA)を取り上げ、そのコード記述例を順を追って解説を行いました。
※詳細はこれも前回の、
「シングルバッファインジケータの値を取得する記述」セクションをご覧ください。
また、マルチバッファインジケータの値取得例としてはストキャスティクスを取り上げ、これもまたコード記述例を紹介しました。
※詳細はこれも前回の、
「マルチバッファインジケータの値を取得する記述」セクションをご覧ください。
さて、今回からは前回までのインジケータに関する前提を踏まえて、インジケータの値取得を簡略化できるクラスを作っていきます。
前回の内容を振り返ってもらうとわかるように、インジケータの値取得には
- インジケータの値を格納する配列の宣言
- 組み込みインジケータ 関数によるハンドル(インジケータの値を取得する鍵のようなもの)の取得
- ArraySetAsSeries関数による、配列の時系列セット
- CopyBuffer関数による、インジケータの値情報を配列にコピー
という手順が必要になります。
薄々感じている方もいるかと思うのですが、EAを自作するたびに、メインプログラムに上記の記述を行うのは正直かなり煩わしいですよね。
その煩わしさを取り除いて、インジケータの値取得をメインプログラムでより簡単に行うためのクラスを作ってしまおう!というのが今回の趣旨です。
これまでの講座で作ってきたクラスについては、以下のようなものがある訳ですが・・・
今回はそれにもう一つ新しいクラスが加わる事になり、できることがさらに増えます。
徐々にクラスというものが、
<なにやら複雑でとらえどころのないもの>
から
<プログラミングの拡張性を担う便利なもの>
と捉えることが段々できるようになってきたのではないでしょうか?
今回の講座記事を読み終わる頃には、その確信が強まるのではないか思いますので、是非今回も楽しんで学習して頂ければと思います
インジケ-タクラス作成のロードマップ
MT5に組み込まれているインジケータには構造的に共通しているいくつかの特徴があります。
- 全てのインジケータは少なくとも1つの配列を用意する必要がある。
- インジケータの値を格納する配列はArraySetAsSeries関数によって時系列セットされる。
- インジケータ系関数によって取得したハンドルを格納する変数が必要となる。
上記の工程はMT5に組み込みインジケータの値取得にあたって必ず必要な手順になります。
従って、①まずは上記の工程を処理する親クラスを作ります。
そして、親クラスができあがった後は、
②各インジケータごとに異なった処理が必要な工程を担う、派生クラスを作ります。
今回の記事では親クラスを作る工程を解説し、派生クラスについては次回取り上げます。
※派生クラスとは特定のクラス(=親クラス)の機能を引き継ぎつつ、さらに新しい機能をクラスに追加させたクラスの事です。詳細については下記の記事をご覧ください。
インジケ-タクラスを記述していくインクルードファイルの作成
まずはインジケータ クラスを記述していくインクルードファイル(mqhファイル)を作成します。
//+------------------------------------------------------------------+
//| OriginalIndicators.mqh |
//| MQL5ssei |
//| https://mqlinvestmentlab.com/ |
//+------------------------------------------------------------------+
#property copyright "MQL5ssei"
#property link "https://mqlinvestmentlab.com/"
ファイル名を「OriginalIndicators.mqh」としました。
このファイルに、これから必要なクラス記述を追加していきます。
※インクルードファイルについては↓の記事をご参照ください。
・MQL5 EA講座 第56回「#include命令(#include directive)」
define命令により、インジケータ値を配列にコピーする定数を宣言する
続いて、propety命令記述の下のグローバル領域(関数の外の領域)に、define命令で定数を1つ宣言します。
※propety命令については↓の記事をご参照ください。
・MQL5 EA講座 第4回「#プロパティ命令(#property directive)」
※define命令及び定数については↓の記事をご参照ください。
・MQL5 EA講座 第17回「定数(Constant)について」
//インジケータ値を配列にコピーす数を規定
#define MAX_COPY 100
定数名を「MAX_COPY」としました。
この定数は、CopyBuffer関数により、インジケータの値情報を配列にコピーする数を設定するのに使います。配列にコピーする数は、CopyBuffer関数の第4引数で指定するので、「MAX_COPY」はそこ(=CopyBuffer関数の第4引数)に記述される想定の定数となります。
インジケータに共通する処理を担う基礎クラスを宣言する
続いてインジケータに共通する処理を担う基礎クラスの宣言を行います。
//インジケータに共通する処理を担う基礎クラス
class CBaseIndicator
{
};
クラス名を「CBaseIndicator」としました。これをたたき台にして、必要なメンバを追加していきます。
※クラスの作り方に関しては↓の記事をご参照ください。
・MQL5 EA講座 第48回「クラスについて1-クラスとは?-」
「CBaseIndicator」クラスに必要なメンバを追加する
「CBaseIndicator」クラス内に以下のメンバを追加します。
//インジケータに共通する処理を担う基礎クラス
class CBaseIndicator
{
protected:
int handle;//インジケータのハンドル値を格納する変数
double main[];//インジケータの値を格納する配列
public:
CBaseIndicator(void);//コンストラクタ
double Main(int parIndex=0);
void Release();
virtual int Init() { return(handle); }
};
アクセスレベル=protectedに変数「handle」と、配列「main」という2つのメンバを、
アクセスレベル=publicに関数「Main」、「Release」、「Init」という3つのメンバを宣言しました。
それぞれのメンバについて順を追って解説していきます。
※アクセスレベル=protectedのメンバは、その利用有効範囲が、宣言したクラスと、派生クラスに制限されます。一方、アクセスレベル=publicのメンバはクラスの外からでもアクセスできます。
private,protected,publicなどのアクセス指定子については以下の記事をご参照ください。
・MQL5 EA講座 第50回「クラスについて3 -アクセス指定子-」
メンバ変数「handle」はインジケ-タのハンドル値を格納する
アクセスレベル=protectedに宣言した、int型のメンバ変数「handle」はインジケータのハンドル値を格納する為に使います。
ここまで講座記事を読み続けてきた方であれば、
「MQL5でのインジケータ値取得には、いくつかの工程を踏む」
という事にはだいぶ慣れてきたのではないかな、と思いますが、まだピンとこないという方は前回の講座記事↓
・第108回「インジケータを使ってエントリーシグナルを生成する」
をご覧ください。
移動平均線だったら、iMA関数、ストキャスティクスだったらiStochastic関数・・・といった具合ですね。各インジケータ 関数の戻り値として返ってくるハンドル値を格納し、最終的にCopyBuffer関数の第1引数に記述されることを想定したメンバ変数となります。
メンバ変数配列「main」はインジケータの値を格納する
メンバ変数配列「main」はインジケータの値を格納するのに使います。
CopyBuffer関数の第5引数に記述される想定です。
CBaseIndicator関数はインスタンス生成時に、配列の時系列をセットするコンストラクタ
アクセスレベル=publicに宣言したCBaseIndicator関数はインスタンス生成時に、配列の時系列をセットするコンストラクタです。
コンストラクタは覚えていますでしょうか?
コンストラクタとはクラスのインスタンスを生成した段階で、発動するクラスのメンバ関数のことです。
コンストラクタの決まりとして、「関数名は同じである」という原則が有る為、クラス名と同じCBaseIndicatorが関数名になっています。
※コンストラクタについては以下の記事をご参照ください。
・MQL5 EA講座 第52回「クラスについて5 -コンストラクタ-」
それでは、インスタンスとは何だったでしょう?
インスタンスとは、構造体、クラスなどを実際に利用する時に宣言する変数名のようなもの、と考えてください。
※インスタンスについては、以下の記事をご参照ください。
コンストラクタであるCBaseIndicator関数は、アクセスレベル=protectedの配列「main」に時系列セットを施す処理を実装します。
グローバル領域(関数の外の領域)にCBaseIndicator関数の処理実装記述を行います。
コンストラクタ,CBaseIndicator関数の処理実装記述について
コンストラクタ,CBaseIndicator関数の処理実装記述は以下の通りです。
CBaseIndicator::CBaseIndicator(void)
{
ArraySetAsSeries(main,true);
}
コンストラクタは戻り値を取らないので、データ型はvoid型(=型なし)になります
配列「main」に時系列セットを施すには当然ArraySetAsSeries関数を使います。
『配列「main」なんてどこにも宣言されていないじゃないか!』
という勘違いはしないよう注意してください。
アクセスレベル=protectedの領域で既に宣言済みです。コンストラクタであるCBaseIndicator関数はCBaseIndicatorクラスのメンバ関数なので、配列「main」に対しては当然アクセス権を持っています。
コンストラクタにこの処理を施しておくことによって、メインプログラムでこのクラスのインスタンスを宣言した時点で、配列が時系列セットされるので、今後メインプログラムでArraySetAsSeries関数を呼び出す必要がなくなります。(今の時点ではピンとこなくても、大丈夫です。次回の講座記事で、CBaseIndicatorクラスの派生クラスを作り終わった後には腑に落ちるように解説していきます)
メンバ関数「Main」はインジケータの値を取得して戻り値として返す
メンバ関数「Main」はインジケータの値を取得して戻り値として返す関数です。
メインプログラムで呼び出す想定なので、アクセスレベルはpublicとなります。
インジケータ値は、小数点を含むことが多々ありますから、戻り値のデータ型はdouble型となります。
Main関数の処理実装記述について
Main関数の処理実装記述は以下のようになっています。
double CBaseIndicator::Main(int parIndex=0)
{
CopyBuffer(handle,0,0,MAX_COPY,main);
double value = NormalizeDouble(main[parIndex],_Digits);
return(value);
}
仮引数「parIndex」には、インジケータの値を取得したい時系列のインデックス番号を指定します。
仮引数「parIndex」の初期値は「0」にしてあります。従って、メインプログラムで引数の記述を省略した場合、現在足のインジケータ値を取得する事になります。
{}内の処理実装記述についてですが、CopyBuffer関数を使い、アクセスレベル=protectedの配列「main」にインジケータ値をコピーします。CopyBuffer関数の第1引数には、これもアクセスレベル=protectedの変数「handle」を記述します。
配列「main」の[]内に仮引数[parIndex]を記述します。これでメインプログラムからMain関数を呼び出せば、parIndexに記述したインデックス(通し番号)のインジケータ値が取得できるようになります。
配列「main」に格納されている値は、NormalizeDouble関数を使って正規化します。
正規化とは、プログラムが取り扱うルールに則って値を整える作業
のことを指します。
NormalizeDouble関数の第1引数には正規化する対象、すなわちmain[parIndex]を記述します。
※定義済み変数については↓の記事をご参照ください。
正規化して、値を整えたmain[parIndex]を、double型のローカル変数「value」 に代入します。
最後に変数「value」をreturn演算子で戻り値として返して処理実装記述は終了です。
メンバ関数「Release」はインジケータの値をメモリから解放する時に使う。
メンバ関数「Release」はインジケータの値をメモリから解放する時に使います。
インジケータの値がもはや必要なくなった局面や、条件分岐で使います。
Release関数の処理実装記述について
Release関数の処理実装記述は以下の通りです。
void CBaseIndicator::Release(void)
{
IndicatorRelease(handle);
}
Release関数の処理実装記述は極めてシンプルです。戻り値はないのでvoid型とします。
{}内にはメモリからインジケータの値を開放する処理を担うIndicatorRelease関数を呼び出し、その引数にprotectedの変数「handle」をあてがえばそれで終了です。
IndicatorRelease関数について
IndicatorRelease関数は指標ハンドルを削除し、インジケータの値をメモリから解放する処理を担う関数です。IndicatorRelease関数の引数と戻り値は以下の通りです。
※MT5には「ストラテジーテスター」という、EAの性能を過去相場で多角的に検証できる機能が実装されているのですが、ストラテジーテスターでの作業においてはIndicatorRelease関数は実行されません。
bool IndicatorRelease(
int indicator_handle // 指標ハンドル
);戻り値は処理が成功した場合→true 失敗した場合はfalseを返す
MQL5リファレンスのIndicatorRelease関数説明ページにはサンプルコードがあります。↓
※MQL5リファレンスの見方については第47回「MQL5公式リファレンスの見方」をご覧ください。
上記リンクにもう少し丁寧なコメントアウト解説を追加したものが以下のサンプルコードになります。
//--- 入力パラメータ
input int MA_Period=15;
input int MA_shift=0;
input ENUM_MA_METHOD MA_smooth=MODE_SMA;
input ENUM_APPLIED_PRICE price=PRICE_CLOSE;
//--- 指標ハンドルを格納する
int MA_handle=INVALID_HANDLE;
//+------------------------------------------------------------------+
//| エキスパート初期化に使用される関数 |
//+------------------------------------------------------------------+
int OnInit()
{
//--- 指標ハンドルを作成する
MA_handle=iMA(Symbol(),0,MA_Period,MA_shift,MA_smooth,PRICE_CLOSE);
//--- グローバル変数を削除する
if(GlobalVariableCheck("MA_value")) //"MA_value"を確認できたら、"MA_value"を削除する
GlobalVariableDel("MA_value");
//---
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| エキスパートティック関数 |
//+------------------------------------------------------------------+
void OnTick()
{
//--- グローバル変数"MA_value"の値が存在しない場合 、"MA_value"を作成する
if(!GlobalVariableCheck("MA_value"))
{
//--- 最後の 2 つのバーの指標値を取得する
if(MA_handle!=INVALID_HANDLE) //OnInit関数内で無事にハンドルを取得出来ている場合、
{
//--- 指標値を格納する動的配列 の宣言
double values[];
//配列に2要素分の情報をコピーする
if(CopyBuffer(MA_handle,0,0,2,values)==2 && values[0]!=EMPTY_VALUE) //取得要素が2で、インデックス「0」の値が空でない時
{
//--- 最後から2 番目のバーをグローバル変数の値に保存する
if(GlobalVariableSet("MA_value",values[0])) //GlobalVariableSet関数の戻り値は最終更新時刻→無事にグローバル変数を設定出来た時
{
//--- 指標ハンドルを解放する
if(!IndicatorRelease(MA_handle)) //開放作業が失敗した場合、エラーをログ出力する
Print("IndicatorRelease() failed. Error ",GetLastError());
else MA_handle=INVALID_HANDLE; //解放作業が成功した場合、ハンドルにINVALID_HANDLEを代入する
} //if(CopyBuffer(MA_handle,0,0,2,values)==2 && values[0]!=EMPTY_VALUE) //取得要素が2で、インデックス「0」の値が空でない時
else
Print("GlobalVariableSet failed. Error ",GetLastError());
} //if(CopyBuffer(MA_handle,0,0,2,values)==2 && values[0]!=EMPTY_VALUE) //取得要素が2で、インデックス「0」の値が空でない時
} //if(CopyBuffer(MA_handle,0,0,2,values)==2 && values[0]!=EMPTY_VALUE) //取得要素が2で、インデックス「0」の値が空でない時
}
//---
}
<参照>
このサンプルコードの実行結果は以下のようになります。
サンプルコード内にはGlobalVariableCheck関数やGlobalVariableSet関数など見慣れない関数が混じっていますが、これらはGlobal変数というものに関する関数群になります。
Global変数というのは、MT5をシャットダウン後も、一定期間値の記憶を保つことができる変数の事を言います。
Global変数についてはこの後の講座記事にて、1つの単元として取り上げる予定なので今は細かい意味はわからなくても大丈夫です。
※先にGlobal変数について詳しく知りたいという方は↓の記事をご参照ください
上記のサンプルコードを実行した動画はGlobal変数にインジケータの値を保存した後は、インジケータ値のメモリ保存は不要、としてIndicatorRelease関数を使いメモリを開放しているという挙動になります。
※注意:Global変数はMQL5 EA講座 第27回「グローバル変数について」で取り上げたグローバル変数とは別物になります。
当サイトではMT5をシャットダウン後も、一定期間値の記憶を保つことができる変数をアルファベット表記のGlobal変数、関数の外(=グローバル領域)に作られた(=宣言された)変数のことをカタカナ表記のグローバル変数として区別したいと思います。
メンバ関数「Init」は戻り値だけを定めた仮想関数
メンバ関数「Init」は戻り値だけを定めた仮想関数となります。
仮想関数とは、「大まかな処理内容だけ元のクラスで決めて、細かい処理内容は、各派生クラスで決める」為の関数になります。
※仮想関数については以下の記事をご参照ください。
MQL5 EA講座 第53回「クラスについて6 -仮想関数-」
virtual int Init() { return(handle); }
仮想関数につき、関数名とデータ型の前に virtualキーワードがついています。
処理実装記述は現時点ではインライン定義で
と記載しているのみです。
※インライン定義とは、本来グローバル領域で行うメンバ関数の処理実装記述を、クラスの{}内にて、メンバ関数の設定と同時に行ってしまう事です。
アクセスレベル=protectedのメンバ変数「handle」はインジケータのハンドル値を格納する変数ですが、インジケータの種類だけ、ハンドル値を取得するインジケータ系の関数は存在し、それらインジケータ系関数の引数もインジケータごとに異なります。
従って、具体的な処理実装記述は次回の講座記事で解説する予定の、各インジケータの派生クラスで記述していきます。
現時点では、戻り値を返すだけのインライン定義で済ませているのはその為です。
次回講座記事の
・『仮想関数「Init」の仮引数と処理実装記述をCDerivediMAクラスで再定義する』セクション
及び
・『仮想関数「Init」の仮引数と処理実装記述をCDerivediStochasticクラスで再定義する』セクション箇所に特に注意を払ってお読みいただければ幸いです。
まとめ
今回は、インジケータの値取得を簡略化できるクラスを作る解説の第1回目でした。
MT5に組み込まれているインジケータには構造的に共通している処理を担る基礎クラスと、各インジケータごとに異なった処理が必要な工程を担う、派生クラスを作っていくというロードマップをまずは示し、今回はまず基礎クラスの作成に取り掛かりました。
※詳細は今回の記事の「インジケ-タクラス作成のロードマップ」セクションをご覧ください。
今回作成した基礎クラス、CBaseIndicatorクラスには以下のメンバを追加しました。
・メンバ変数「handle」→インジケータのハンドル値を格納する、アクセスレベル=protectedの変数
・メンバ変数配列「main」→インジケータ値を格納する、アクセスレベル=protectedの配列
・CBaseIndicator関数→インスタンス生成時に、配列の時系列をセットする、アクセスレベル=publicのコンストラクタ
・メンバ関数「Main」→インジケータの値を取得して戻り値として返す、アクセスレベル=publicのメンバ関数。
・メンバ関数「Release」→インジケータの値をメモリから解放する時に使う、アクセスレベル=publicのメンバ関数。
・メンバ関数「Init」→戻り値だけを定めた、アクセスレベル=publicの仮想関数。細かい処理内容は、各派生クラスで定義する。
現時点での「OriginalIndicators.mqh」ファイルの記述内容全体は以下のようになっています。
//+------------------------------------------------------------------+
//| OriginalIndicators.mqh |
//| MQL5ssei |
//| https://mqlinvestmentlab.com/ |
//+------------------------------------------------------------------+
#property copyright "MQL5ssei"
#property link "https://mqlinvestmentlab.com/"
//インジケータ値を配列にコピーす数を規定
#define MAX_COPY 100
//インジケータに共通する処理を担う基礎クラス
class CBaseIndicator
{
protected:
int handle;//インジケータのハンドル値を格納する変数
double main[];//インジケータの値を格納する配列
public:
CBaseIndicator(void);//コンストラクタ
double Main(int parIndex=0);
void Release();
virtual int Init() { return(handle); }
};
CBaseIndicator::CBaseIndicator(void)
{
ArraySetAsSeries(main,true);
}
double CBaseIndicator::Main(int parIndex=0)
{
CopyBuffer(handle,0,0,MAX_COPY,main);
double value = NormalizeDouble(main[parIndex],_Digits);
return(value);
}
void CBaseIndicator::Release(void)
{
IndicatorRelease(handle);
}
今回は以上とさせていただきます。
最後までお読みいただきありがとうございました。