English Русский Español Português
preview
初級から中級まで:インジケーター(II)

初級から中級まで:インジケーター(II)

MetaTrader 5 |
51 2
CODE X
CODE X

前回の「初級から中級まで:インジケーター(I)」では、最小限の知識で非常にシンプルかつ軽量なインジケーターを作成する方法を実践的に解説しました。多くの人は、成果を出すには高度なコーディング知識が必要だと思いがちです。しかし実際には、少し努力すれば初心者でも比較的シンプルで実用的なものを作ることができます。しかも、その大部分の処理はMetaTrader 5が担ってくれます。私たちはイベントに正しく応答するだけでよく、その際に処理した情報をもとに、残りの部分はMetaTrader 5が適切に処理してくれます。

「作成したコードは、これまで見てきたどのインジケーターとも似ていない。確かにチャートに何かは表示できるが、期待していたものとは違う。もっと多くのことができるはずだ」と感じている方もいるかもしれません。

しかし、読者の皆さん。ここまでの内容があまり印象的でないように思えたとしても、忘れてはならないのは、知識は一夜にして身につくものではないということです。知識は時間をかけて育てていくものです。本連載の目的はまさにそこにあります。知識を育み、広げていくことです。「こうすれば動くから、その通りにしなさい」という考え方ではありません。それでは、前回の記事でどこまで進んだのかを振り返ってみましょう。


期間Xの移動平均を実装する方法

前回の記事では、チャート上に情報をプロットする方法を見てきました。非常にシンプルでわかりやすい形でおこないましたが、記事の目的はMetaTrader 5のリソースをより効果的に活用する方法があることを示すことにあり、具体的な条件や可能性にはあまり踏み込みませんでした。

とはいえ、前回示した内容は他のやり方でも実現できます。しかし、多くの方は、より一般的な移動平均システムの実装方法を知りたいのではないでしょうか。言い換えれば、たとえば有名な9期間の指数平滑移動平均のようなものを作れると面白いでしょう。これは非常にシンプルながらも高収益な取引手法として知られており、その扱いは簡単ではないものの、広く価値が認められています。

ここで、前回作った地味なコードを、もっと面白いものに変えることを考えられているかもしれません。それを実現するには、かなりの挑戦が必要になるでしょう。前回のように短くシンプルなコードではなく、もっと多くの行数のコードを作らなければならないかもしれません。

さて、それでも挑戦です。本日は、最小限のコードで移動平均インジケーターを作成してみます。コードは少ないほどよいでしょう。しかし、多くの方にとってこれが挑戦であっても、私自身は少々退屈で単調に感じます。それでは、具体的な実装方法を見る前に、前回の記事のコードを振り返ってみましょう。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property indicator_type1           DRAW_LINE
05. #property indicator_color1          clrBlack
06. //+----------------+
07. #property indicator_buffers         1
08. #property indicator_plots           1
09. //+----------------+
10. double   gl_buffer[];
11. //+------------------------------------------------------------------+
12. int OnInit()
13. {
14.    SetIndexBuffer(0, gl_buffer, INDICATOR_DATA);
15. 
16.    return INIT_SUCCEEDED;
17. };
18. //+------------------------------------------------------------------+
19. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
20. {
21.    for (int c = prev_calculated; c < rates_total; c++)
22.       gl_buffer[c] = price[c];
23. 
24.    return rates_total;
25. };
26. //+------------------------------------------------------------------+

コード01

前回のコード01は非常にシンプルで面白く、他の方法では理解が難しいことを理解する助けにもなるため、すでに試したことがあると思います。さて本題です。このコードを使って移動平均を計算し、チャートにプロットするにはどうすればよいでしょうか。そのためには、まずいくつかの判断をおこなう必要があります。必須ではありませんが、実装したいコードを考える練習として非常に有用です。

最初に決めるべきは、どのタイプの計算を使うかということです。移動平均の計算には違いがあり、加重平均と単純平均があります。この加重は、出来高、価格、時間に基づく場合があります。これを理解し、どのように選ぶかを知ることは非常に重要です。例えば、指数平滑移動平均の場合は、時間や期間数に応じて加重されます。VWAP(出来高加重平均)は出来高に応じて価格を加重するのか、それともその逆か、正解は誰にもわかりません。いずれにせよ、時間は考慮しません。単純移動平均の場合は特別な加重はなく、データそのもののみを考慮して計算され、他のデータとの相対的な変化は考慮されません。便宜上、ここではそれぞれ「指数移動平均(EMA)」と「単純移動平均(SMA)」と呼ぶことにします。

加重係数は、あるデータと別のデータの関係によって決まります。しかし、今回は面倒な数学的詳細には立ち入りません。それは必要ないからです。ただし、実装する計算タイプの式くらいは知っておく必要があります。計算方法を理解せずに実装することはできませんから。単純移動平均は計算が非常に簡単で、ある期間の値を足したり引いたりするだけで済みますが、今回は少し複雑なケース、すなわち多くの人が「指数移動平均」と呼ぶ計算を扱います。

私たちが採用する式は以下の通りです。

図01

図01には、任意の期間で指数移動平均を計算するための式が示されています。この式では、Pが現在の価格値、Mが前回の指数移動平均の値、Nが平均化に使用される期間数を表します。この情報があれば、計算の実装段階に進むことができます。まず、先ほど少し触れた算術平均の計算方法について確認しておきましょう。算術平均は、X個の値を合計し、その合計をXで割ることで求められます。

次に、X + 1、つまり次の期間を計算する場合は、合計から最初の値を引き、新しい値を加えるだけで済みます。その後、結果をXで割ればよいのです。だから私は算術平均は退屈だと言ったわけです。足して引くだけで済むのですから。一方、指数平均は膨大な計算が必要だと思われがちですが、実際には算術平均とほとんど変わりません。

では、図01の式が示す意味を見てみましょう。現在のEMA(指数移動平均)を計算するには、前回の平均値が必要です。ここで問題が生じます。計算を開始する時点では、その値がまだ存在しないのです。そこで、小さな数学的な工夫が必要になります。その解決策は単純で、初期値としてMを0に設定するだけです。これで計算が可能になります。具体的には、コード01のどこを変更すればよいかを確認すれば十分です。今回の場合は、22行目だけを修正すればよいのです。しかし、誤解を避け、コードを複雑にする必要がないことを示すために、ここで完全なコードを提示します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property indicator_type1           DRAW_LINE
05. #property indicator_color1          clrBlack
06. //+----------------+
07. #property indicator_buffers         1
08. #property indicator_plots           1
09. //+----------------+
10. double   gl_buffer[];
11. //+------------------------------------------------------------------+
12. int OnInit()
13. {
14.    SetIndexBuffer(0, gl_buffer, INDICATOR_DATA);
15. 
16.    return INIT_SUCCEEDED;
17. };
18. //+------------------------------------------------------------------+
19. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
20. {
21.    for (int c = prev_calculated; c < rates_total; c++)
22.       gl_buffer[c] = (price[c] - (c ? gl_buffer[c - 1] : 0)) * (2. / (1.0 + 9)) + (c ? gl_buffer[c - 1] : 0);
23. 
24.    return rates_total;
25. };
26. //+------------------------------------------------------------------+

コード02

ここで注意してほしいのは、コードで変更したのは22行目だけであり、他に修正は加えていないということです。しかし、このコードを実行すると、次のような結果が得られます。

図02

おそらく、この図02に本当に9期間の指数移動平均が表示されているのか。こんな少しの変更で指数移動平均を表示できるとは信じがたいと思われているのでしょう。これを確認するために、MetaTrader 5に標準で搭載されているインジケーターを使います。これにより、計算が正しくおこなわれていることを検証できます。以下のアニメーションで示します。

アニメーション01

このアニメーションでは、背景に図02の平均値が表示され、前景に検証用インジケーターが表示されています。使用している値に注目してください。これらの値に基づき、インジケーターはアニメーションの通りチャート上に表示されます。すべて問題ありません。実際、コード02は9期間の指数移動平均を正しく計算しています。では、なぜこれが可能なのでしょうか。

これまでの記事で提示した概念を理解していればわかると思います。私たちはただ図01に示された式を使っているだけです。しかし先述の通り、計算チェーンの最初の値を0に設定する小さな調整が必要です。そのため、一見するとこの式は少し分かりにくいかもしれません。ただし、必要であれば22行目のコードをより読みやすい形に書き換えることもできます。こうすると、Calculateイベントハンドラは次のようになります。

                   .
                   .
                   .
18. //+------------------------------------------------------------------+
19. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
20. {
21. #define def_N     9
22.    
23.    for (int c = prev_calculated; c < rates_total; c++)
24.    {
25.       double M = (c ? gl_buffer[c - 1] : 0);
26.    
27.       gl_buffer[c] = (price[c] - M) * (2. / (1.0 + def_N)) + M;
28.    }
29. 
30.    return rates_total;
31. 
32. #undef def_N
33. };
34. //+------------------------------------------------------------------+

フラグメント01

フラグメント01は、コード02と同じことをおこないます。違いは、少し行数が多いことだけです。非常に良いのですが、先に進む前に、このコードには小さな問題があることをお伝えしておきます。ただし、この問題が具体的に何かは説明しません。少し好奇心を刺激するためです。こうすることで、皆さん自身でこの問題を考え、プログラミングのいくつかの側面について、たとえば「数学的な式に従うからといって、常にその通りに実装すべきではない」という点について、より明確な意見を持つことができるでしょう。

問題を修正するには、コードを以下のように変更する必要があります。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property indicator_type1           DRAW_LINE
05. #property indicator_color1          clrBlack
06. //+----------------+
07. #property indicator_buffers         1
08. #property indicator_plots           1
09. //+----------------+
10. double   gl_buffer[];
11. //+------------------------------------------------------------------+
12. int OnInit()
13. {
14.    SetIndexBuffer(0, gl_buffer, INDICATOR_DATA);
15. 
16.    return INIT_SUCCEEDED;
17. };
18. //+------------------------------------------------------------------+
19. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
20. {
21.    for (int c = (prev_calculated > 0 ? prev_calculated - 1 : 0); c < rates_total; c++)
22.       gl_buffer[c] = (c ? ((price[c] - gl_buffer[c - 1]) * 2. / (1 + 9)) + gl_buffer[c - 1] : price[c] * 2. / (1 + 9));
23. 
24.    return rates_total;
25. };
26. //+------------------------------------------------------------------+

コード03

いずれにせよ、心配はいりません。付録には両方のコードが掲載されているので、実際に試して問題点を確認することができます。コードを見ただけでは意味がないように思えるかもしれませんが、実際に触れてみることで理解が深まります。さて、ここで別の話題に移ってもよいでしょう。おそらく、「異なる期間の指数移動平均を使いたい場合はどうすればいいのか。期間を変えるたびにコードを再コンパイルしなければならないのでは、現実的ではない」と思われているのでしょう。しかし、ここまで見てきた実装方法は、あくまで非常に限定的なモデルにしか対応していません。ただし、各ポイントの実装を正しく理解していなければ、新しい概念を理解するのは難しいでしょう。

とはいえ、これまで説明した内容を踏まえれば、次のレベルの実装に進む準備は整っています。ただし、混乱を避けるため、ここで話題を切り替えましょう。


ユーザー操作

前回のトピックで示したようなコードを実際に使おうと考える人は、ほとんどいません。計算期間の数を変更できないからです。確かに、たとえばVWAPの計算のように、チャート上に描画される平均値が時間に依存しないケースもあります。しかし、それはあくまで特殊なケースであり、このトピックで解説し、理解すべき内容の価値や必要性が損なわれるわけではありません。

次に、コードを再コンパイルすることなく、ユーザーが計算パラメータを変更できるようにする方法を見ていきます。そのためには、MQL5コンパイラに対して、ユーザーや他のアプリケーションから内部で実装した値を操作できるようにすることを通知する必要があります。言い換えれば、ユーザーからは変更可能な値として見えますが、コード上では定数として扱う仕組みです。もちろん、この変更は後ほど別のアプリケーションから直接おこなうことも可能ですが、原則としてこの機能は、ユーザーとの直接的なやり取りを実現するためのものです。

まずは、コード03を調整する必要があります。現状のままでは、混乱を招く可能性があるからです。したがって、教育的目的のため(パフォーマンス向上のためではありません)、コードを以下のように変更します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property indicator_type1           DRAW_LINE
05. #property indicator_color1          clrBlack
06. //+----------------+
07. #property indicator_buffers         1
08. #property indicator_plots           1
09. //+----------------+
10. double   gl_buffer[];
11. //+------------------------------------------------------------------+
12. int OnInit()
13. {
14.    SetIndexBuffer(0, gl_buffer, INDICATOR_DATA);
15. 
16.    return INIT_SUCCEEDED;
17. };
18. //+------------------------------------------------------------------+
19. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
20. {
21.    double k = 2. / (1 + 9);
22. 
23.    for (int c = (prev_calculated > 0 ? prev_calculated - 1 : 0); c < rates_total; c++)
24.       gl_buffer[c] = (c ? ((price[c] - gl_buffer[c - 1]) * k) + gl_buffer[c - 1] : price[c] * k);
25. 
26.    return rates_total;
27. };
28. //+------------------------------------------------------------------+

コード04

ここで注意してほしいのは、変更自体は非常に簡単であるという点です。追加したのは21行目だけで、ここで指数移動平均の計算を調整するための定数を作成しています。さて、重要なポイントです。ここで変更すべき値は、まさにコード04の21行目にある「9」です。この値を変えることで、平均を計算する際の期間を指定できます。具体的には、コード04に新しい行を追加するだけで済みます。これにより、任意の期間を指定して平均を計算できるようになります。この変更は、以下の通りです。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property indicator_type1           DRAW_LINE
05. #property indicator_color1          clrBlack
06. //+----------------+
07. #property indicator_buffers         1
08. #property indicator_plots           1
09. //+----------------+
10. input uchar user01 = 9;
11. //+----------------+
12. double   gl_buffer[];
13. //+------------------------------------------------------------------+
14. int OnInit()
15. {
16.    SetIndexBuffer(0, gl_buffer, INDICATOR_DATA);
17. 
18.    return INIT_SUCCEEDED;
19. };
20. //+------------------------------------------------------------------+
21. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
22. {
23.    double k = 2. / (1 + user01);
24. 
25.    for (int c = (prev_calculated > 0 ? prev_calculated - 1 : 0); c < rates_total; c++)
26.       gl_buffer[c] = (c ? ((price[c] - gl_buffer[c - 1]) * k) + gl_buffer[c - 1] : price[c] * k);
27. 
28.    return rates_total;
29. };
30. //+------------------------------------------------------------------+

コード05

これで完了です。ユーザーは、使用する指数移動平均の期間を自由に指定できるようになりました。しかし、まだ1つ小さなポイントを説明する必要があります。このインジケーターをチャートに配置すると、次のような画面が表示されます。

図03

この画像では、新しいタブが赤枠で強調表示されていることに注目してください。このタブは以前は存在せず、コード05の10行目によって作成されたものです。つまり、ここがユーザーとのインターフェースとなり、コード内で使用するいくつかの定数をユーザーが設定できるようになっています。ここまでは問題ありません。しかし、新しいタブを選択すると、次のような画面が表示されます。

図04

ここに問題があります。図04で強調表示されている情報に注目してください。この値が何を意味するかは、私たち開発者にとっては明確です。しかし、値が増えてくると少しわかりにくくなり、コードを実装した本人でさえ、各値が何を表しているのか理解しにくくなる場合があります。ここで、もう1つ小さなポイントに注目する必要があります。この情報は、コンパイラに少し特殊な方法で渡されることになります。少なくとも私にとっては最初は少し違和感がありましたが、後で慣れました。

図04に示されている通り、強調表示された行は、コード05の10行目で宣言された定数に表示される名前と対応しています。そして、10行目で宣言しているのは変数ではなく定数です。単純なケースでは、コード上およびアプリケーションユーザーに対して代表的な名前を付けることができます。しかし、多くの場合、ユーザーに短いテキストを表示することが最適です。では、図04のこの目立つ位置にテキストを配置するにはどうすればよいのでしょうか。

ここが最も特殊な部分です。そのために、文字列型のコメントを追加する必要があります。この型のコメントは定数宣言の後に置く必要があるため、次のように記述します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property indicator_type1           DRAW_LINE
05. #property indicator_color1          clrBlack
06. //+----------------+
07. #property indicator_buffers         1
08. #property indicator_plots           1
09. //+----------------+
10. input uchar user01 = 9;             //Exponential average periods
11. //+----------------+
                   .
                   .
                   .

コード06

コード06、特に10行目をよく見てください。この行に追加されたのが、文字列コメントと呼ばれるものです。これを、図04のタブに表示される定数に対しておこなうことで、コンパイラに対して、このテキストをユーザーに表示する文字列として使うよう指示していることになります。より明確に理解してもらうために、図04で示された同じタブを、コード06の表示内容で確認した場合にどうなるかを見てみましょう。以下の画像をご覧ください。

図05

図05のおかげで、誰でもアプリケーションで何が設定されているのか一目で分かるようになり、使いやすさが大幅に向上します。

インジケーターを作るのがいかに簡単か、気づいたでしょうか。必要なものは非常に少なく、特定の目的に合わせて要素をぴったり動作させることができます。インジケーターをさらに有用にしたり、MetaTrader 5への統合をより良くしたり、他の用途にも使えるようにするには、もう少し作業が必要です。しかし、これらは段階的に解説していくので、MQL5標準ライブラリの関数とインジケーターがどのように結びつくかを理解できるようになります。ご覧の通り、インジケーターを動作させるにはいくつかの関数が必要です。しかし、現時点ではインジケーターはまだ完全に汎用的ではありません。

さて、これが最初の部分でした。次に、OnCalculateの2番目のバージョンの使い方を見ていきます。1番目のバージョンでも特定の要件は満たせますが、より多くの情報が必要になる場合があります。そこで、別のバージョンが必要になるのです。ただし、内容が混ざらないよう、これは別のトピックで解説します。


OnCalculateの2番目のバージョンの使い方

前回までの記事でも触れましたが、OnCalculate関数は、MQL5標準ライブラリにおいて唯一オーバーロードされている関数です。これは、場合によっては1番目のバージョンを使い、別の場合には2番目のバージョンを使うことができるためです。しかし、どちらのバージョンを使うかによって、ユーザーインターフェースに表示される内容だけでなく、実装方法も変わってきます。MetaTrader 5の補助なしで実装する場合は、より注意が必要です。なぜなら、2番目のバージョンは1番目のバージョンより多くのデータを扱うことができ、使い方次第で異なる結果を得られるからです。

2番目のバージョンのOnCalculateはやや複雑であるため、ここでは同じインジケーターを2番目のバージョンで実装した場合のコードを確認するにとどめます。重要なのは、どちらのバージョンを使うかによる違いは存在するものの、その違いはプログラマ自身が作り出すものであるという点です。したがって、前回のトピックで扱ったコード06は、2番目のバージョンのOnCalculateを使うと次のようになります。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property indicator_type1           DRAW_LINE
05. #property indicator_color1          clrBlack
06. //+----------------+
07. #property indicator_buffers         1
08. #property indicator_plots           1
09. //+----------------+
10. input uchar user01 = 9;             //Exponential average periods
11. //+----------------+
12. double   gl_buffer[];
13. //+------------------------------------------------------------------+
14. int OnInit()
15. {
16.    SetIndexBuffer(0, gl_buffer, INDICATOR_DATA);
17. 
18.    return INIT_SUCCEEDED;
19. };
20. //+------------------------------------------------------------------+
21. 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 &TickVolume[], const long &Volume[], const int &Spread[])
22. {
23.    double k = 2. / (1 + user01);
24. 
25.    for (int c = (prev_calculated > 0 ? prev_calculated - 1 : 0); c < rates_total; c++)
26.       gl_buffer[c] = (c ? ((Close[c] - gl_buffer[c - 1]) * k) + gl_buffer[c - 1] : Close[c] * k);
27. 
28.    return rates_total;
29. };
30. //+------------------------------------------------------------------+

コード07

ここで1つ注意してほしい点があります。前回のケースでは、平均を計算する際にどの種類の情報を使用するかを選択できました。しかし、コード07ではもはやそれができません。計算を特定の値に固定しているからです。この場合はバーの終値を使用するように固定しています。他の情報は無視され、計算には使用されません。しかし、これだけが変更点ではありません。前回、「ユーザーインターフェースも変わる」と述べたことを覚えていらっしゃいますか。コード07のインジケーターをチャートに配置すると、前回あったタブの1つが表示されなくなることに気づきます。以下の画像をご覧ください。

図06

図06を見ると、以前あったタブの1つが消えていることが分かります。これは、インジケーターの計算が特定の種類の値に固定されているためで、ユーザーは以前のように計算に使用する値の種類を変更することができません。しかし、他の種類の計算ができないわけではありません。ユーザーが計算に使用する入力値の種類を選択できるようにすればよいのです。そのためには、新しい入力を追加するだけで十分です。

この部分は非常に面白いです。手順自体は比較的シンプルなので、残りの記事ではその方法を解説します。これにより、さまざまな解決策を面白く、かつ創造的に作成できるようになります。

ユーザーに他の種類の入力値を使えるようにする方法はいくつかありますが、私は列挙型を使うのが好みです。列挙型は実装も修正も簡単で、扱いやすいためです。本質的には、少しの手順で、実装者であるあなたにも、アプリケーションを使うユーザーにも、十分に直感的でエレガントな仕組みを提供できます。

まず最初のステップは、列挙型を作成することです。わかりやすくシンプルにするために、非常に基本的なバージョンを作ります。したがって、コード07は以下のように修正されます。

                   .
                   .
                   .
09. //+----------------+
10. enum Enum_TypeInput {
11.             HIGH,
12.             OPEN,
13.             CLOSE,
14.             LOW
15.                     };
16. //+----------------+
17. input uchar user01 = 9;             //Exponential average periods
18. input uchar user02 = HIGH;          //Data type for calculation
19. //+----------------+
                   .
                   .
                   .

コード08

コード08で指定した修正を加えたコード07を実行すると、次のような結果が得られます。

図07

おや?これは思った通りではありません。ユーザーに、10行目で定義した列挙型の項目を表示して、それに基づいて選択できるようにしたかったのに、なぜうまくいかなかったのでしょう。原因は、18行目で期待されているデータ型にあります。ここで注意してください。列挙型を宣言したのは正しいのですが、問題はその列挙型が整数値に変換されてしまうことです。コンパイラは私たちが意図したことを正しく理解していませんでした。この問題を解決するには、期待される値の型を定数(列挙型)に変更すればよいのです。また、タイプをHIGHから多くの場合で最も一般的に使用されるCLOSEに変更します。これにより、コード08は次のように修正されます。

                   .
                   .
                   .
16. //+----------------+
17. input uchar             user01 = 9;             //Exponential average periods
18. input Enum_TypeInput    user02 = CLOSE;         //Data type for calculation
19. //+----------------+
                   .
                   .
                   .

コード09

コード08の修正を適用してコード09とした後、ターミナルでコードを実行すると、次のような結果が得られます。

図08

動作をご覧ください。ただし、これだけではありません。コンパイラが私たちの意図を理解したことで、コード08の10行目で宣言した列挙型のテキストを使って項目を選択できるようになりました。しかし、ここで注意が必要です。以前MetaTrader 5の支援に頼っていたのとは異なり、今は自分たちで制御する必要があります。つまり、上記のように設定しただけでは、すぐに動作するわけではありません。そこで必要になるのが、イベントハンドラの修正です。今回は、ユーザーが選択した値を実際にコード内で利用できるようにします。そのため、コードを次のように変更します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property indicator_type1           DRAW_LINE
05. #property indicator_color1          clrBlack
06. //+----------------+
07. #property indicator_buffers         1
08. #property indicator_plots           1
09. //+----------------+
10. enum Enum_TypeInput {
11.             HIGH,
12.             OPEN,
13.             CLOSE,
14.             LOW
15.                     };
16. //+----------------+
17. input uchar             user01 = 9;             //Exponential average periods
18. input Enum_TypeInput    user02 = CLOSE;         //Data type for calculation
19. //+----------------+
20. double   gl_buffer[];
21. //+------------------------------------------------------------------+
22. int OnInit()
23. {
24.    SetIndexBuffer(0, gl_buffer, INDICATOR_DATA);
25. 
26.    return INIT_SUCCEEDED;
27. };
28. //+------------------------------------------------------------------+
29. 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 &TickVolume[], const long &Volume[], const int &Spread[])
30. {
31.    double   k = 2. / (1 + user01),
32.             price = 0;
33. 
34.    for (int c = (prev_calculated > 0 ? prev_calculated - 1 : 0); c < rates_total; c++)
35.    {
36.         switch (user02)
37.         {
38.             case HIGH   :
39.                 price = High[c];
40.                 break;
41.             case OPEN   :
42.                 price = Open[c];
43.                 break;
44.             case CLOSE  :
45.                 price = Close[c];
46.                 break;
47.             case LOW    :
48.                 price = Low[c];
49.                 break;
50.         }
51.         gl_buffer[c] = (c ? ((price - gl_buffer[c - 1]) * k) + gl_buffer[c - 1] : price * k);
52.    }
53. 
54.    return rates_total;
55. };
56. //+------------------------------------------------------------------+

コード10

このコードでは、32行目に新しい変数を追加していることが分かります。この変数の目的は、すべての要素を管理しやすくすることです。36行目では、switch文を追加して、price変数に代入される値を決定しています。計算コード自体に大きな変更は必要ありません。計算の行を、51行目のようにprice変数を使う形に調整するだけで済みます。便利ですよね。しかし、この記事を締めくくる前に、もう1つ工夫できることがあります。

10行目で宣言した列挙型を見ると、いくつかの値はそのままインターフェースで使えるほど明確です。しかし、場合によっては別のコードを使いたいこともあるでしょう。その場合、将来のユーザーや自分自身のために、どの値が計算に使用されるのかを明確にしておく必要があります。そのためには、入力用の行でおこなったのと同じように、文字列コメントを追加します。これにより、コードは次のようになります。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property indicator_type1           DRAW_LINE
05. #property indicator_color1          clrBlack
06. //+----------------+
07. #property indicator_buffers         1
08. #property indicator_plots           1
09. //+----------------+
10. enum Enum_TypeInput {
11.             en_HIGH,           //Use maximum prices
12.             en_OPEN,           //Use opening prices
13.             en_CLOSE,          //Use closing prices
14.             en_LOW             //Use minimum prices
15.                     };
16. //+----------------+
17. input uchar             user01 = 9;             //Exponential average periods
18. input Enum_TypeInput    user02 = en_CLOSE;      //Data type for calculation
19. //+----------------+
20. double   gl_buffer[];
21. //+------------------------------------------------------------------+
22. int OnInit()
23. {
24.    SetIndexBuffer(0, gl_buffer, INDICATOR_DATA);
25. 
26.    return INIT_SUCCEEDED;
27. };
28. //+------------------------------------------------------------------+
29. 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 &TickVolume[], const long &Volume[], const int &Spread[])
30. {
31.    double   k = 2. / (1 + user01),
32.             price = 0;
33. 
34.    for (int c = (prev_calculated > 0 ? prev_calculated - 1 : 0); c < rates_total; c++)
35.    {
36.         switch (user02)
37.         {
38.             case en_HIGH   :
39.                 price = High[c];
40.                 break;
41.             case en_OPEN   :
42.                 price = Open[c];
43.                 break;
44.             case en_CLOSE  :
45.                 price = Close[c];
46.                 break;
47.             case en_LOW    :
48.                 price = Low[c];
49.                 break;
50.         }
51.         gl_buffer[c] = (c ? ((price - gl_buffer[c - 1]) * k) + gl_buffer[c - 1] : price * k);
52.    }
53. 
54.    return rates_total;
55. };
56. //+------------------------------------------------------------------+

コード11

最終的な結果を見てみましょう。このコードは付録に掲載されており、学習や実践の機会を提供します。実行すると、MetaTrader 5のターミナルでは次のように表示されます。

図09

前述のように、コードの内部構造に関連するすべての情報はユーザーから隠されています。ユーザーにとっては何も変わりません。しかし、開発者にとっては、実装がやや複雑になります。以前よりもMetaTrader 5からの支援が少なく、多くのルールを考慮する必要があるからです。しかし、各ケースは少しずつ異なるため、シンプルなインジケーターであっても、多くのことを学びながら実装できることが理解できるでしょう。


まとめ

本記事では、一連の手順に従うことで、比較的簡単にシンプルなインジケーターを実装する方法を紹介しました。プロットラインを移動させるといった操作については触れていませんが、これは非常に簡単におこなえます(コードにもう1つ定数を追加し、OnCalculateイベント関数内で使用するだけです)。こうした作業は、実践してインジケーターの理解を深めたい方に任せることにしましょう。

次回の記事では、インジケーターに関連した別の内容を扱います。それでは、またお会いしましょう。

MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/15802

添付されたファイル |
Anexo.zip (3.04 KB)
最後のコメント | ディスカッションに移動 (2)
Osmar Sandoval Espinosa
Osmar Sandoval Espinosa | 11 2月 2026 において 03:21

こんにちは!


記事はとても良かった!


各ステップで何をやっているのか、もっと画像を使った方が楽しめると思います。

CODE X
CODE X | 12 2月 2026 において 11:19
Osmar Sandoval Espinosa # :

オーイ!


この記事を愛しています!


各ステップであなたがしていることを説明するために、より多くの画像を使用することは非常に興味深いことです。

これについては、ヒントがあります:ある時点で、通常は画像が表示されるときに、表示されているコードを一時停止していることにお気づきかもしれません。これは後で変更できるようにするためです。そこで、添付ファイルにある既成のコードを使う代わりに、私からのアドバイスです:記事を開き、表示されているとおりにコードを入力し、ビルドしながら何が起こるかをテストすることです。こうすることで、何が起こっているのかをよりよく追うことができ、なぜそのパスが取られ、別のパスが取られなかったのか、画像には示されていないかもしれないが、その理由まで理解できるようになる。こうすることで、仮説を検証し、特定の目標に従って実際にものを作る方法について、より多くを学ぶことができる。
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
市場シミュレーション(第10回):ソケット(V) 市場シミュレーション(第10回):ソケット(V)
これからExcelとMetaTrader 5の接続の実装を始めますが、その前にいくつか押さえておくべき重要なポイントがあります。これを理解しておくことで、なぜ動くのか、なぜ動かないのかで悩む必要がなくなります。そして、PythonとExcelを組み合わせることに尻込みする前に、xlwingsを使ってExcelからMetaTrader 5をある程度操作できる方法を見てみましょう。ここで紹介する内容は主に教育目的ですが、もちろん、ここで取り上げることだけに制限されるわけではありません。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
初級から中級まで:インジケーター(I) 初級から中級まで:インジケーター(I)
本記事では、初めてとなる完全に実用的かつ機能的なインジケーターを作成していきます。目的はアプリケーションの作り方そのものを示すことではありません。皆さんがご自身のアイデアをどのように開発できるのかを理解し、安全でシンプルかつ実践的な方法でそれを適用する機会を提供することにあります。