
MQL5: 自分のインディケーターの作成
はじめに
インディケーターとは何でしょう? チャート上に便利な方法で表示したい計算値の一式です。値の一式はプログラムでarraysと表示されます。そのため、インディケーターの作成は配列 (価格配列) を取り扱い、他の配列 (インディケーター 値)に結果を記録するアルゴリズムを書くことを意味します。
すぐに使えるインディケーターが多くあるにもかかわらず(それはもうお馴染みになりましたが)、自分のインディケーターを作る必要性が常に存在します。このような自分のアルゴリズムで作ったインディケーターはカスタムインディケーターと呼ばれます。本記事はシンプルなカスタム・インディケーターの作成方法について説明します。
それぞれ異なるインディケーター
インディケーターによっては、カラーラインや領域、ポジションエンターの最適な時を指す特別ラベルポインターであったりします。またこれらのタイプは組み合わせることができるので、さらに多くのインディケータータイプが存在します。William Blauが開発した有名なTrue Strength Indexの例をとってインディケーターの作成について考察しましょう。
True Strength Index
TSIインディケーターは、トレンドまたは買われすぎ/売られすぎの領域を特定するために、ダブル平滑化モメンタムに基づいています。数学的説明はWilliam BlauによるMomentum, Direction, and Divergenceにあります。ここではその計算式だけを書きます。
TSI(CLOSE,r,s) =100*EMA(EMA(mtm,r),s) / EMA(EMA(|mtm|,r),s)
ここで:
- mtm = CLOSEcurrent – CLOSprev, 配列値は現在のバーと以前のバーの終値の相違を表す。;
- EMA(mtm,r) = 周期長が rに等しいmtm値の指数平滑;
- EMA(EMA(mtm,r),s) = s 期間のEMA(mtm,r) 値の指数平滑;
- |mtm| = mtm;
- r = 25,
- s = 13.
この式から、インディケーター計算に影響する3つのパラメータを抽出できます。それらは r と s期間、そして計算に使われる価格タイプです。ここでは終値を使います。
MQL5 ウィザード
TSIをブルーラインで表示しましょう。 ここではMQL5 ウィザードをスタートする必要があります。最初のステージでは作成したいプログラムのタイプ、カスタムインディケーターを示す必要があります。第2のステージではプログラム名、r と s パラメーターとその値を設定しましょう。
次に、インディケーターが別のウインドウでブルーラインとして表示されるように定義し 、このライン用にTSI ラベルを設定しましょう。
全ての初期 データが入力されたので完了を押し、インディケーターのドラフトを入手します。
//+------------------------------------------------------------------+ //| True Strength Index.mq5 | //| Copyright 2009, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2009, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 //---- plot TSI #property indicator_label1 "TSI" #property indicator_type1 DRAW_LINE #property indicator_color1 Blue #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- input parameters input int r=25; input int s=13; //--- indicator buffers double TSIBuffer[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping SetIndexBuffer(0,TSIBuffer,INDICATOR_DATA); //--- return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int& spread[]) { //--- //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+
MQL5 ウィザードはインディケータープロパティーを書くインディケーターヘッダーを作成します。つまり、
- インディケーターは別のウインドウで表示されます。;
- インディケーターバッファーの数, indicator_buffers=1;
- プロットの数 , indicator_plots= 1;
- プロットNo 1の名前, indicator_label1="TSI";
- 最初のプロットのスタイル -ライン, indicator_type1=DRAW_LINE;
- プロット No 1の色, indicator_color1=Blue;
- ラインのスタイル, indicator_style1=STYLE_SOLID;
- プロット1のライン幅 , indicator_width1=1.
全ての準備が整ったので、これで私たちのコードを洗練・改良できます。
OnCalculate()
OnCalculate() 関数は、Calculate イベントのハンドラーで、インディケーター 値の再計算とそれをチャートへの再度作画が必要な時に現れます。これは新しい ティック受領やシンボルヒストリー更新などのイベントです。そのため、インディケーター 値のすべての計算メインコードは必ずこの関数の中になければなりません。
もちろん、補助計算は他の別の関数でも実行できますが、これらの関数はOnCalculateハンドラーで使用されなければなりません。
デフォルトでは、 MQL5 ウィザードは全タイプの時系列へのアクセスを提供するOnCalculate()の第二のフォームを作ります。
- 始値・高値・安値・終値;
- ボリューム (リアルそして/または ティック);
- スプレッド;
- オープニング時間期間
しかし我々のケースでは一つのデータ配列のみ必要ですから、OnCalculate() 第一のフォーム の呼び出しを変えましょう。
int OnCalculate (const int rates_total, // size of the price[] array const int prev_calculated, // number of available bars at the previous call const int begin, // from what index in price[] authentic data start const double& price[]) // array, on which the indicator will be calculated { //--- //--- return value of prev_calculated for next call return(rates_total); }
これによってインディケーターを価格データにさらに適用できるだけでなく、他のインディケーター値に基づいてインディケーターを作ることができます。
price[] もし、パラメータータブでClose を選択すると(これはデフォルトで表示されます)、OnCalculate() に渡された価格[] が終値を含みます。例えばもし Typical Priceを選択すると、価格[]が各期間の(High+Low+Close)/3の価格を含みます。
rates_total パラメーターは価格[] 配列のサイズを表し、サイクル計算の組織化に便利です。価格[]における要素のインデックス作成はゼロから始まり過去から将来に向かっています。例えば、価格[0] 要素は一番古い値を含み、一方、価格[rates_total-1]は最新の配列エレメントを含みます。
補助インディケーターバッファーの組織化
インディケーター配列データなど、一本のラインのみがチャートに表示されます。でもその前に中間計算をまとめる必要があります。中間データはインディケーター配列に保管され 、INDICATOR_CALCULATIONS 属性でマークされます。この式から追加の配列が必要なことが見えます。
- for values mtm - array MTMBuffer[];
- for values |mtm| - array AbsMTMBuffer[];
- for EMA(mtm,r) - array EMA_MTMBuffer[];
- for EMA(EMA(mtm,r),s) - array EMA2_MTMBuffer[];
- for EMA(|mtm|,r) - array EMA_AbsMTMBuffer[];
- for EMA(EMA(|mtm|,r),s) - array EMA2_AbsMTMBuffer[].
また、ダブルタイプの配列をさらに6つグローバルレベルに追加し、これらの配列をインディケーターバッファーとOnInit()関数で組み合わせる必要があります。インディケーターバッファーの新しい数を示すのを忘れないで下さい。 indicator_buffers プロパティは7に等しくなければなりません。 (1つだったのでさらに6つのバッファーが追加)
#property indicator_buffers 7
今、インディケーターコードはこのように見えます。
#property indicator_separate_window #property indicator_buffers 7 #property indicator_plots 1 //---- plot TSI #property indicator_label1 "TSI" #property indicator_type1 DRAW_LINE #property indicator_color1 Blue #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- input parameters input int r=25; input int s=13; //--- indicator buffers double TSIBuffer[]; double MTMBuffer[]; double AbsMTMBuffer[]; double EMA_MTMBuffer[]; double EMA2_MTMBuffer[]; double EMA_AbsMTMBuffer[]; double EMA2_AbsMTMBuffer[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping SetIndexBuffer(0,TSIBuffer,INDICATOR_DATA); SetIndexBuffer(1,MTMBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(2,AbsMTMBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(3,EMA_MTMBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(4,EMA2_MTMBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(5,EMA_AbsMTMBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(6,EMA2_AbsMTMBuffer,INDICATOR_CALCULATIONS); //--- return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate (const int rates_total, // size of the price[] array; const int prev_calculated,// number of available bars; // during the previous call; const int begin, // from what index in // price[] authentic data start; const double& price[]) // array, on which the indicator will be calculated; { //--- //--- return value of prev_calculated for next call return(rates_total); }
中間計算
バッファー MTMBuffer[] と AbsMTMBuffer[]のための計算値を組織化することはとても簡単です。ループで、一つ一つ価格[1]から価格[rates_total-1]まで、一つの配列に相違を書き、2つ目に相違の絶対値を書きます。
//--- calculate values of mtm and |mtm| for(int i=1;i<rates_total;i++) { MTMBuffer[i]=price[i]-price[i-1]; AbsMTMBuffer[i]=fabs(MTMBuffer[i]); }
次のステージはこれらの配列の指数平均計算です。それには2つの方法があります。1つはアルゴリズム全体を間違いなく書こうとする事です。 2つ目はデバッグ済でまさにこの目的のためにすぐに使える関数を使う事です。
MQL5では、配列値による移動平均計算用の内蔵関数はありません。しかし、すぐに使えるライブラリ関数MovingAverages.mqhがあります。その完全なパスは terminal_directory/MQL5/Include/MovingAverages.mqhで、terminal_directory はMetaTrader 5 ターミナルがインストールされたカタログです。そのライブラリは インクルードファイルで、4つの古典的方法を使って配列の移動平均 を計算する関数を含みます。
- 単純平均
- 指数平均
- 平滑平均
- 線形加重平均
MQL5プログラムでこれらの関数を使うには、次をコードヘッダーに追加します。
#include <MovingAverages.mqh>
配列値の指数移動平均を計算し、その平均を別の配列に記録する ExponentialMAOnBuffer()関数が必要です。
配列平滑関数
MovingAverages.mqhのインクルードファイルは8つの関数を含み、それは2つの同じ関数を持つグループに分けることができますが、それぞれ4つ含みます。 最初のグループは配列を受け取り、指定ポジションの移動平均値を単に戻す関数を含みます。
- SimpleMA() - 単純平均値計算
- ExponentialMA() - 指数平均値計算;
- SmoothedMA() - 平滑平均値計算;
- LinearWeightedMA() - 線形加重平均値計算;
これらの関数は配列平均値の一回入手を目的としており、複数セルには最適でありません。もしループでこのグループからの関数を使う必要がある場合(平均値計算とさらに配列に各計算値を書くため)、最適アルゴリズムを組織化しなければなりません。
第二の関数グループは初期値配列に基づいた移動平均値で受信配列を記入することを目的としています。
- SimpleMAOnBuffer() - 価格[] 配列からの単純平均値で出力配列バッファー[]を記入
- ExponentialMAOnBuffer() - 価格[] 配列からの指数平均値で出力配列バッファー[]を記入
- SmoothedMAOnBuffer() - 価格[] 配列からの平滑平均値で出力配列バッファー[]を記入
- LinearWeightedMAOnBuffer() - 価格[] 配列からの線形加重平均値で出力配列バッファー[]を記入
配列バッファー[]・価格[] ・平均期間 period 以外の全ての指定関数は、さらに3つのパラメーターを入手し、その目的はOnCalculate() 関数、rates_total・ prev_calculated そして beginパラメーターに似ています。 このグループの関数はインデックス(AS_SERIES フラグ)の方向 を考慮に入れながら、渡された価格[] とバッファー[] の配列を正常に処理します。
begin パラメーターはソース配列のインデックスを示しますが、そこから取り扱わなければならないデータなど重要データが始まります。MTMBuffer[] 配列は、MTMBuffer[1]=price[1]-price[0]なのでリアルデータはインデックス1で始まります。MTMBuffer[0]値は未定義なのでbegin=1です。
//--- calculate the first moving ExponentialMAOnBuffer(rates_total,prev_calculated, 1, // index, starting from which data for smoothing are available r, // period of the exponential average MTMBuffer, // buffer to calculate average EMA_MTMBuffer); // into this buffer locate value of the average ExponentialMAOnBuffer(rates_total,prev_calculated, 1,r,AbsMTMBuffer,EMA_AbsMTMBuffer);
平均化時、出力配列では計算値が少し遅れて記入されるため、期間値を考慮しなければなりません。 その遅れは大きな平均化期間では大きくなります。例えば、もしperiod=10の場合、その配列の値はbegin+period-1=begin+10-1で始まります。バッファー[]のさらなる呼び出しでは、 インデックス begin+period-1で処理を始めなければならないことに考慮するべきです。
このようにしてMTMBuffer[] と AbsMTMBuffer配列から簡単に第二指数平均を入手できます。
//--- calculate the second moving average on arrays ExponentialMAOnBuffer(rates_total,prev_calculated, r,s,EMA_MTMBuffer,EMA2_MTMBuffer); ExponentialMAOnBuffer(rates_total,prev_calculated, r,s,EMA_AbsMTMBuffer,EMA2_AbsMTMBuffer);
begin=1+r-1 なのでbegin値は今 r に等しいです。 (r は主要指数平均の期間。 インデックス 1で処理が始まる)。インデックス rで入力配列の処理を開始し、第二指数平均期間は s に等しいので、EMA2_MTMBuffer[] と EMA2_AbsMTMBuffer[]の出力配列では、計算値はindex r+s-1で始まります。
全て事前計算が整ったので、チャートにプロットされるインディケーター バッファー値TSIバッファー[]を計算できます。
//--- now calculate values of the indicator for(int i=r+s-1;i<rates_total;i++) { TSIBuffer[i]=100*EMA2_MTMBuffer[i]/EMA2_AbsMTMBuffer[i]; }F7 キーを押してコードをコンパイルし、MetaTrader 5 ターミナルを開始します。うまくいきました!
それでも疑問点が残りました。
計算の最適化
実は、実働インディケーターを書くだけでは十分ではありません。OnCalculate()の現在の実行を注意してみると最適でないのが見えます。
int OnCalculate (const int rates_total, // size of the price[] array; const int prev_calculated,// number of available bars; // at the previous call; const int begin,// from what index of the // price[] array true data start; const double &price[]) // array, at which the indicator will be calculated; { //--- calculate values of mtm and |mtm| MTMBuffer[0]=0.0; AbsMTMBuffer[0]=0.0; for(int i=1;i<rates_total;i++) { MTMBuffer[i]=price[i]-price[i-1]; AbsMTMBuffer[i]=fabs(MTMBuffer[i]); } //--- calculate the first moving average on arrays ExponentialMAOnBuffer(rates_total,prev_calculated, 1, // index, starting from which data for smoothing are available r, // period of the exponential average MTMBuffer, // buffer to calculate average EMA_MTMBuffer); // into this buffer locate value of the average ExponentialMAOnBuffer(rates_total,prev_calculated, 1,r,AbsMTMBuffer,EMA_AbsMTMBuffer); //--- calculate the second moving average on arrays ExponentialMAOnBuffer(rates_total,prev_calculated, r,s,EMA_MTMBuffer,EMA2_MTMBuffer); ExponentialMAOnBuffer(rates_total,prev_calculated, r,s,EMA_AbsMTMBuffer,EMA2_AbsMTMBuffer); //--- now calculate values of the indicator for(int i=r+s-1;i<rates_total;i++) { TSIBuffer[i]=100*EMA2_MTMBuffer[i]/EMA2_AbsMTMBuffer[i]; } //--- return value of prev_calculated for next call return(rates_total); }
各関数のスタートでは、 我々はMTMBuffer[] と AbsMTMBuffer[]配列値を計算します。この場合、もし価格[]サイズが数百、数千、または数百万の場合、 不必要な計算の繰り返しがどんなパワフルなCPUだとしても全てのCPUリソースをとってしまいます。
最適計算の組織化には、prev_calculated 入力パラメーターを使います。それは以前の呼び出し時のOnCalculate() によって戻された値に等しいです。関数の最初の呼び出しでは、 prev_calculatedは常に0に等しいです。この場合、インディケーターバッファー内のすべての値を計算します。次の呼び出し中、バッファー全体を計算する必要はありません。最後の値だけ計算されます。以下のように書きましょう。
//--- if it is first call if(prev_calculated==0) { //--- set zero values to zero indexes MTMBuffer[0]=0.0; AbsMTMBuffer[0]=0.0; } //--- calculate values of mtm and |mtm| int start; if(prev_calculated==0) start=1; // start filling out MTMBuffer[] and AbsMTMBuffer[] from the 1st index else start=prev_calculated-1; // set start equal to the last index in the arrays for(int i=start;i<rates_total;i++) { MTMBuffer[i]=price[i]-price[i-1]; AbsMTMBuffer[i]=fabs(MTMBuffer[i]); }
EMA_MTMBuffer[], EMA_AbsMTMBuffer[], EMA2_MTMBuffer[] そして EMA2_AbsMTMBuffer[]の計算ブロックは計算の最適化を必要としません。 なぜなら ExponentialMAOnBuffer()はすでに最適な方法で書かれているからです。TSIBuffer[] 配列用の計算値を最適化すればいいだけです。MTMBuffer[]に使用されるものと同じ方法を使います。
//--- now calculate the indicator values if(prev_calculated==0) start=r+s-1; // set the starting index for input arrays for(int i=start;i<rates_total;i++) { TSIBuffer[i]=100*EMA2_MTMBuffer[i]/EMA2_AbsMTMBuffer[i]; } //--- return value of prev_calculated for next call return(rates_total);
最適化手順の最後の注意点: OnCalculate() はrates_total値を戻します。それはインディケーター計算に使用される価格[] 入力配列の要素数を意味します。
OnCalculate()で戻された値はターミナルメモリに保存され、OnCalculate()の次の呼び出し時に 入力 パラメーター prev_calculated値として関数に渡されます。
このことで、以前のOnCalculate()の呼び出し時の入力配列サイズを常に知り、不要な計算なく正しいインデックスからインディケーター バッファーの計算を始めることができます。
入力データの確認
OnCalculate() が完全に操作するためにもう一つやらなければならない事があります。インディケーター値が計算される価格[] 配列の確認を追加しましょう。もし配列(rates_total) のサイズ が小さすぎると、計算が要求されません。データが十分な時OnCalculate()が次に呼び出されるまで待たなければなりません。
//--- if the size of price[] is too small if(rates_total<r+s) return(0); // do not calculate or draw anything //--- if it's first call if(prev_calculated==0) { //--- set zero values for zero indexes MTMBuffer[0]=0.0; AbsMTMBuffer[0]=0.0; }
True Strength Indexを計算するために指数平滑が2度順に使われるため、価格[]のサイズは 少なくとも r と s期間の合計に等しいか、それよりも大きくなければなりません。そうでないと実行が終了し、OnCalculate() が0に戻ってしまいます。戻ったゼロ値はインディケーターの値が計算されなかったためインディケーターがチャートにプロットされないことを意味します。
表現セットアップ
計算の正確性については、インディケーターが使用可能です。しかし、もし別のmql5-プログラムからそれを呼ぶ場合、デフォルトにより終値で作られます。別のデフォルト価格タイプを指定できます。 indicator_applied_price のインディケータープロパティ にあるENUM_APPLIED_PRICE 列挙から値を指定します。
例えば、ある価格に典型的な価格 ( (high+low+close)/3) を設定するには以下を書きましょう。
#property indicator_applied_price PRICE_TYPICAL
もし iCustom() または IndicatorCreate() 関数を使ってその値だけを使うつもりの場合、それ以上手を加える必要はありません。しかし、チャート内にプロットなど直接使われた場合、追加設定が推奨されます。:
- インディケーターがプロットされ始めるバー数;
- データウインドウに反映されるTSIBuffer[]値のラベル;
- インディケーター ライン上にマウスカーソルをポイントしている時に別のウインドウとポップアップヘルプ現れるインディケーターの短い名前;
- インディケーター値に表示される桁数後の桁数 (これは正確性に影響しません。)
これらの設定はOnInit() ハンドラーでカスタムインディケーター グループからの関数を使って調整できます。新しいラインを追加して、True_Strength_Index_ver2.mq5としてインディケーター を保存します。
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping SetIndexBuffer(0,TSIBuffer,INDICATOR_DATA); SetIndexBuffer(1,MTMBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(2,AbsMTMBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(3,EMA_MTMBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(4,EMA2_MTMBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(5,EMA_AbsMTMBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(6,EMA2_AbsMTMBuffer,INDICATOR_CALCULATIONS); //--- bar, starting from which the indicator is drawn PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,r+s-1); string shortname; StringConcatenate(shortname,"TSI(",r,",",s,")"); //--- set a label do display in DataWindow PlotIndexSetString(0,PLOT_LABEL,shortname); //--- set a name to show in a separate sub-window or a pop-up help IndicatorSetString(INDICATOR_SHORTNAME,shortname); //--- set accuracy of displaying the indicator values IndicatorSetInteger(INDICATOR_DIGITS,2); //--- return(0); }
もし両方のインディケーターのバージョンを開始し、 チャートを始めにスクロールすると違いが見えます。
結論
True Strength Index インディケーターの作成例に基づき、MQL5でインディケーターを書く基本プロセスを概説できます。
- 自分自身のカスタムインディケーター作るためには、インディケーター セットアップで予備的ルーチン動作の実行を助けるMQL5 ウィザードを使います。OnCalculate() 関数の必要変数を選択。
- 必要な場合、中間計算にもっと配列を追加し、SetIndexBuffer()関数を使って 対応するインディケーター バッファーと結合します。これらのバッファー用にINDICATOR_CALCULATIONS タイプ を示します。
- 価格データが変わるたびに、この関数が呼ばれるのでOnCalculate()での計算を最適化します。 コードの作成を楽にし可読性を増すために、デバッグ済ですぐに使える関数を使います。.
- インディケーターのさらなる視覚チューニングを行い、他のmql5 プログラムとユーザーの両方にとってプログラムを使いやすくします。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/10





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索