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

初級から中級まで:イベント(II)

MetaTrader 5 |
9 0
CODE X
CODE X

はじめに

前回の「初級から中級まで:イベント(I)」では、イベント駆動型プログラミングについての議論を始めました。多くのプログラマにとって、このアプローチはそのままの名前で馴染みがありますが、複雑で難しいと感じられることもあります。その理由は、メカニズムや概念を十分に理解していないということです。

しかし、イベント駆動型プログラミングを用いることで、グラフィカルユーザーインターフェース(GUI)に関連する様々な問題やタスクから解放されます。MetaTrader 5はGUIプラットフォームであるため、イベント駆動型プログラミングを使用することにより、イベント駆動型アプリケーションの開発が大幅に簡素化されます。

前回の記事では、まず最初の一歩を踏み出し、簡単なインジケーターを使って発生したイベントを追跡する方法を体感しました。しかし、プログラミングの概念を理解し、実際に応用できるかを試したい方のために、少しだけ取り組む課題が用意されています。

提案された課題はそれほど複雑ではなかったため、少し時間と労力をかければ、比較的簡単に解決策を考案できるはずです。

次に、同じ課題に対する2つの解法を紹介し、それぞれのアプローチの利点と欠点を議論します。では、今日の内容に集中しましょう。そして、いつものように、新しいトピックを始めて整理していきます。


考えられる最初の解法

説明を始めるにあたり、前回の記事で示したインジケーターのコード例から確認してみましょう。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. uint gl_Counter;
05. //+------------------------------------------------------------------+
06. int OnInit()
07. {
08.    gl_Counter = 0;
09. 
10.    Print(__FUNCTION__);
11. 
12.    return INIT_SUCCEEDED;
13. };
14. //+------------------------------------------------------------------+
15. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
16. {
17.    if (!gl_Counter)
18.    {
19.       uint arr[];
20. 
21.       if (FileLoad(_Symbol, arr) > 0)
22.          gl_Counter = arr[0];
23.    }
24.    
25.    Print(__FUNCTION__, " :: ", ++gl_Counter);
26. 
27.    return rates_total;
28. };
29. //+------------------------------------------------------------------+
30. void OnDeinit(const int reason)
31. {
32.    uint arr[1];
33. 
34.    arr[0] = gl_Counter;
35.    FileSave(_Symbol, arr);
36. 
37.    Print(__FUNCTION__);
38. };
39. //+------------------------------------------------------------------+

コード01

提案された課題は、このコードの04行目で宣言された変数gl_Counterが、チャートの時間足が変更された場合のみ現在のカウンタ値を保持し、それ以外の場合は0にリセットされるようにする方法を作ることでした。つまり、時間足が変わらない限り、カウンタはリセットされません。

課題自体は比較的シンプルですが、ここでの目的は、より複雑な課題に対して読者の準備がどれだけできているかを確認することです。もし解くのに詰まっても心配はいりません。重要なのは、解決策を模索したという点です。それが違いをもたらします。課題を解ければ「ボーナス」ですが、それだけでプログラミングができるかどうかは証明されません。ただ1つの解法を見つけたに過ぎないからです。それでも解けた場合は、心からおめでとうございます。

さて、少し考えてみましょう。チャートの時間足を変更するたびに、MetaTrader 5はDeinitイベントを発生させます。このイベントは特定のチャートに対して呼び出されます。この仕組みについては今後さらに探っていきますが、今はMetaTrader 5がどのように動作し、私たちがどのように対応できるかを理解することが重要です。このイベントはそのチャート上の全アプリケーションに送信されます。すべてのアプリケーションが受け取り、それぞれ最適な方法で処理する必要があります。

では、このイベントを受け取るのは誰でしょうか。このイベントはOnDeinit手続きのみが受け取ることになります。ここが重要なポイントです。コード01の30行目を見てみると、この手続きはreasonという値を受け取っています。この値はMetaTrader 5が、アプリケーションがチャートから削除された理由を知らせるために埋められています。ここで、「チャートから削除」とはどういう意味なのかと疑問に思うかもしれません。時間足を変えるだけでインジケーターを削除するわけではないのに、と思うのは当然です。ある意味では、読者の皆さんは正しいのです。

しかし、MetaTrader 5の実装上の都合により、チャート上の全要素を一度削除し、再描画した後に元の要素を再配置する方が簡単なのです。もちろん、この過程でスクリプトは再配置されません(イベントを扱えないため)が、イベントに対応可能なインジケーターやEAは自動的にチャートに再接続されます。これは、プラットフォーム起動時にチャート上のインジケーターやEAが自動的に配置される動作と同じです。すべての要素がチャート上に配置された直後、MetaTrader 5はそのチャート用のInitイベントを発行します。このイベントはOnInit関数(コード01の06行目)で受け取ることができます。これがMetaTrader 5がおこなうすべての作業です。あとは私たちのアプリケーションが、このイベントにどう反応するかを決めることになります。

これは非常に重要なので、よく理解してください。これがMQL5におけるイベント駆動型プログラミングの基礎です。ランタイム中にgl_Counterの値を知ることはできますが、Initイベントが発生する前の値はわかりません。なぜなら、チャートを削除して再作成する過程で、MetaTrader 5がそのチャート上でアプリケーションが使用していたメモリ領域をすべてクリアしてしまうからです。

状況によって保持できるのはstatic変数のみです。しかし、インジケーター向けに設計されたアプリケーションでstatic変数を使うと注意が必要です。使用は可能ですが、いくつかの制約があります。この件については後で詳しく説明します。今は、インジケーターでのstatic変数はひとまず置いておきましょう。EAの場合はメカニズムが異なるため、適切に理解する必要があります。まずはインジケーターに注目するのが簡単です。インジケーターの方が単純だからです。

つまり、この段階ではstatic変数は使えず、gl_Counterの値はインジケーターがチャート上にある間だけ有効です。さらに、チャートの時間足を変更すると、30行目の手続きで捕捉されるイベントが発生します。このイベントのreasonパラメータはDeinitイベントの原因を示します。ここでドキュメントを確認し、gl_Counterを保持すべきイベントの種類がどの値で表されるかを調べることで、解決策が得られます。

ドキュメントを確認すると、REASON_CHARTCHANGE定数が条件を満たしています。これはまさにチャート時間足の変更を意味します。ただし、銘柄が変更された場合も同じ定数が返されます。まずは時間足の問題に集中しましょう。OnDeinit手続きに簡単な条件分岐を追加することで、新しいコードを作ることができます。

                   .
                   .
                   .
29. //+------------------------------------------------------------------+
30. void OnDeinit(const int reason)
31. {
32.    uint arr[1];
33. 
34.    arr[0] = (reason == REASON_CHARTCHANGE ? gl_Counter : 0);
35.    FileSave(_Symbol, arr);
36. 
37.    Print(__FUNCTION__);
38. };
39. //+------------------------------------------------------------------+

コード02

残りのコードはコード01と同じなので、変更点だけに注目します。変更は非常にシンプルで、34行目を少し修正して、使用したい定数かどうかを確認するだけです。もし該当すればカウンタの値は保持されます。それ以外の場合は0にリセットされ、同じ銘柄でインジケーターを再度使用したときにカウンタが再スタートします。

次に、この方法で実装した場合の利点と欠点を見てみましょう。もちろん、これが実装上のすべての利点や欠点ではありません。概念を理解し、実際に試すことが学習過程の一部です。

簡潔にするため、利点と欠点をそれぞれ1つずつ示します。残りは皆さん自身で試行し、特定の実装ケースを学ぶ中で理解が深まります。

利点は、処理が比較的「クリーン」で、いくつかのパラメータにわたって容易に制御可能であることです。たとえば、特定のイベントが特定の期間内に何回発生したかを把握したい場合などに便利です。

一方、値をファイルに保存する方法は、解析が必要になるため多くのケースで問題が生じる可能性があります。たとえば、同じ銘柄に同じインジケーターが同一のMetaTrader 5セッションで開かれている場合、チャートの時間足を頻繁に変更すると値がずれることがあります。プログラミングでは、この種の問題は「レースコンディション」と呼ばれ、非常に複雑です。実際、このテーマで博士論文や専門書が書かれています。初心者としては、コードでレースコンディションに遭遇するのは避けたいところです。

さて、読者の皆さん、この方法が最初の解法です。コードに大きな変更を加えず、ドキュメントを少し調べ、前回の記事で紹介した要素を借用するだけで済むため、非常に手軽です。

次に、カウンタ値の管理の一部をMetaTrader 5に任せる、少し異なる解法を見てみましょう。これは新しいトピックとして紹介します。


考えられる第2の解法

第2の解法は、実務的にも興味深く、カウンタ値の管理責任をMetaTrader 5側に委ねる点が特徴です。「なぜ前回はこの方法を考慮しなかったのか」と思うかもしれませんが、まずはこの解法の利点と欠点を確認してから、実装方法を見ます。前回と同様、説明を簡潔にするため利点と欠点を1つずつ示します。

第2の解法の利点は、変数の値をどこかに保存する責任(多くの場合ファイルを使用)が軽減される点です。一方で、変数の値の管理をMetaTrader 5に委ねるため、目的によっては少し複雑になることもあります。なぜなら、責任が私たちではなくMetaTrader 5側にあるからです。

どうして同じことが利点にも欠点にもなるのかと、疑問に思うかもしれません。その理由は、この解法の性質にあります。利点と欠点はほぼ同じですが、MetaTrader 5の隠れた機能を利用することで、非常に便利に見えるのです。私も過去に別の目的でこの方法を使いましたが、MetaTrader 5の開発者は誰かがこのように使うとは想定していなかったでしょう。

この手法の例は「一からの取引エキスパートアドバイザーの開発(第17部):Web上のデータにアクセスする(III)」でも紹介されています。当時、MetaTrader 5の機能は多くのプログラマーによって広く研究されていると思っていました。しかし、実際には表面的な知識しかない人が多く、MQL5を本来の用途とは違う形で無理に使おうとしていました。そのため、私は立ち止まり、考えを整理し、このような記事を書き、楽しみながらMQL5を学べる形にしました。知識欲の旺盛な聴衆を励ましたいのですが、初心者が学びやすい情報源は非常に少ないからです。

これらの記事が皆さんにとって良い出発点になればと思います。コンテンツを改善するための質問や改善案も歓迎です。

さて、話を元に戻しましょう。ここで使うのはターミナルのグローバル変数です。これはMQL5ライブラリで定義された関数や手続きを通してアクセスできる非常に便利な機能です。簡単で実用的であり、さまざまな場面で役立ちます。次に、この機能を使ったインジケーターのコード例を見てみましょう。こちらです。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. union u_01
05. {
06.     double  d_value;
07.     uint    u_Counter;
08. }gl_Union;
09. //+------------------------------------------------------------------+
10. int OnInit()
11. {
12.     ZeroMemory(gl_Union);
13. 
14.     if (!GlobalVariableGet(_Symbol, gl_Union.d_value))
15.         if (!GlobalVariableTemp(_Symbol))
16.             return INIT_FAILED;
17. 
18.     GlobalVariableSet(_Symbol, gl_Union.d_value);
19. 
20.     Print(__FUNCTION__);
21. 
22.     return INIT_SUCCEEDED;
23. };
24. //+------------------------------------------------------------------+
25. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
26. {
27.     Print(__FUNCTION__, " :: ", ++gl_Union.u_Counter);
28. 
29.     return rates_total;
30. };
31. //+------------------------------------------------------------------+
32. void OnDeinit(const int reason)
33. {
34.     switch(reason)
35.     {
36.         case REASON_CHARTCHANGE:
37.             GlobalVariableSet(_Symbol, gl_Union.d_value);
38.             break;
39.         default:
40.             GlobalVariableDel(_Symbol);
41.     }
42. 
43.     Print(__FUNCTION__);
44. };
45. //+------------------------------------------------------------------+

コード03

コード03を実行すると、下のアニメーションのような挙動が確認できます。


アニメーション01

ここで注目してほしいのは、途中でチャートの時間足を変更したことにより、前回のトピックで説明したのと同じ一連のイベントが発生している点です。これはターミナルに出力された情報から確認できます。しかし、ここで起こっていることはそれだけではありません。前回のコード03とは異なり、一時的な情報の保存(この場合はカウンタ値)にファイルを使用するのではなく、MetaTrader 5に特定の方法で情報を保存させている点に注目してください。

これを確認するため、コード03を説明する前に、MetaTrader 5内にどのグローバル変数が存在するかをチェックできます。方法は簡単で、F3キーを押すか、下の画像のパスを辿ります。


図01

コード03のインジケーターが動作していれば、開いたウィンドウには次のような変数が表示されます。


図02

コード03のインジケーターが動作していない場合、図02のウィンドウには情報が表示されない可能性があります。なぜなら、一般的に、グローバルターミナル変数を使う人はあまり多くないからです。しかし、リソースが存在し利用可能であれば、創造的に活用すればよいのです。

ここまでの画像とアニメーションから、コード03の動作を理解するための材料は十分揃っています。厳密に言えば、コード02の単純な修正に過ぎないので、説明は必須ではありません。しかし、グローバルターミナル変数は特定の用途に使うリソースであり、ここでその活用方法を学ぶことは有意義です。

まず、グローバルターミナル変数はdouble型の値しか保持できないことを理解する必要があります。文字列や整数は直接格納できません。一般的にはできません。ただし、MQL5では共用体を作ることができるため、これを利用すれば非浮動小数点値も格納可能です。コード04行目では、この方法を用いてグローバル変数に非double値を格納できるようにしています。

これにより、double型に収められる範囲であれば、短い文字列なども変数に組み込むことが可能になります。内容が理解しづらい場合は、前回までの記事を参照すると良いでしょう。

また、グローバルターミナル変数への不要なアクセスを避けるため、この共用体をグローバル変数として宣言しています(08行目)。なお、この宣言は必須ではありません。コード内でグローバル変数を使わない方法も可能ですが、その場合はコード内で宣言した変数を外部に移動させ、グローバルターミナル変数として扱う必要があります。この方法では全体的なパフォーマンスに影響が出る場合があります。ただし、特定の状況では、このような実装方法を採用することが有効であり、場合によっては必要になることもあります。ケースごとに最適な方法は異なるため、常に一つのやり方だけを押し付けるべきではありません。いつもそうであるとは限りません。

実際には、12行目でカウンター用のメモリ領域をクリアしています。他の方法でも可能なので、いくつか試して結果を確認してみてください。その直後の14行目では、図02で見えた変数を読み取ろうとしています。存在しなければ、15行目で作成を試みます。変数名は、インジケーターを使っている銘柄名になります。作成に失敗すると、MetaTrader 5はエラー値を返し、新たなイベント(この場合は初期化失敗によるDeinitイベント)が発生します。その結果、32行目の手続きが実行され、インジケーターはチャートから削除されます。

面白いことに、やるべきこととその発生メカニズムが理解できれば、すべて簡単に実装できます。しかし、ここで理解してほしいのは、15行目の関数は「一時的なグローバルターミナル変数」を作成するものであり、18行目の呼び出し前に実行する必要があるということです。そうでないと、変数は一時的ではなくなります。それでも、他の条件ではグローバル変数扱いになりますが、意図した「一時的」という意味ではありません。

一時的なグローバルターミナル変数は、MetaTrader 5が開いている間のみ存在します。何らかの理由でプラットフォームが閉じられると、15行目で作成した変数はすべて削除されます。この性質は、MetaTrader 5が開いている間だけ値を保持したい場合に便利です(実行中の他の処理に関係なく)。ただし、これはグローバルターミナル変数がGlobalVariableTemp関数によって作成された場合に限ります。

同じ変数を作る場合でも、削除条件なしで作成することもできます(18行目)。この場合、MetaTrader 5は一定時間経過後に削除します。ドキュメントを参照すると、長期間値を保持したいがファイルに保存したくない場合に、グローバルターミナル変数が有効であることがわかります。

OnCalculate関数については特に追加の説明はありません。ただ、OnDeinit手続きについてはもう少し詳しく触れておきましょう。次の点に注意してください。34行目では、Deinitイベントが発生した原因となる状況を確認しています。もしそれがチャートの時間足の変更であれば、期待通り37行目でカウンタの現在値を保存します。それ以外の状況であれば、40行目を使って作成されたグローバルターミナル変数を削除します。では、なぜ作成した変数が一時的なのに削除するのでしょうか。その理由は純粋に教育的なものです。コード03で実験し、独自の状況を作成して削除する練習をするためです。コード上でグローバルターミナル変数を直接作成し削除できることを示すのが最も公平な方法です。そのために40行目が存在します。

この例の狙いは、同じ結果でも異なる方法で達成できることを示す点にあります。どの方法を選ぶかは、直面しているシナリオや状況の種類によって決まります。


まとめ

さて、今回の記事もこれで終わりです。前回の記事の補足的内容となっているので、両方の記事をあわせて学ぶことをおすすめします。前回の記事で紹介された内容を理解し、シリーズ内の他の記事から得た知識を応用することで、今回の記事の説明や実例をより深く理解できるようになります。コードの1行1行の詳細な動作については触れませんでしたが、それが目的ではありません。私の意図は、読者に「釣りの仕方を学ばせる」ことです。知識と教材を提供するので、それを使って練習し、学習し、自分の目標を達成できるようにしてほしいのです。提示された内容はすべて、実際に手を動かして確認することが重要です。

したがって、次回の記事では、これまで学んだ内容を応用して実際のインジケーターを開発する方法を見ていきます。ただし、次回の記事の本質を正しく理解するためには、イベントがどのように発生し、MetaTrader 5がどれだけ助けてくれるかを理解しておく必要があります。理解していないと、多くの扉が閉じたままになってしまいます。

付録には、今回の記事で扱ったコードへのアクセスも用意しています。ご検討を祈ります。次回の記事でお会いしましょう。

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

添付されたファイル |
Anexo.zip (1.66 KB)
リスク管理(第3回):リスク管理のメインクラスの構築 リスク管理(第3回):リスク管理のメインクラスの構築
本記事では、システム内のリスクを管理するための重要な基盤となるコアのリスク管理クラスを作成し始めます。今回は、基礎の構築に焦点を当て、基本的な構造、変数、関数を定義します。加えて、最大損益値を設定するために必要なメソッドを実装し、リスク管理の土台を築きます。
初心者からエキスパートへ:MQL5リスク強制EAによる取引規律の自動化 初心者からエキスパートへ:MQL5リスク強制EAによる取引規律の自動化
多くのトレーダーにとって、口座が破綻する最大の要因は、リスクルールを理解していることと、それを一貫して守ることの間にあるギャップです。感情による判断の上書き、リベンジトレード、あるいは単純な見落としによって、どれほど優れた戦略であっても容易に崩壊してしまいます。本記事では、リスク強制エキスパートアドバイザー(Risk Enforcement EA)を開発することで、MetaTrader 5プラットフォームを、あなたの取引ルールを一切の例外なく執行する揺るぎない監督者へと変えていきます。ディスカッションにぜひご参加ください。
取引におけるニューラルネットワーク:ハイブリッドグラフシーケンスモデル(最終部) 取引におけるニューラルネットワーク:ハイブリッドグラフシーケンスモデル(最終部)
引き続き、異なるアーキテクチャの利点を統合し、高い分析精度と計算リソースの効率的な配分を実現するハイブリッドグラフシーケンスモデル(GSM++)を検討します。これらのモデルは、隠れたパターンを効果的に識別し、市場ノイズの影響を低減して予測精度を向上させます。
コードベースにコードを公開する方法:実践ガイド コードベースにコードを公開する方法:実践ガイド
本記事では、MQL5ソースコードベースにさまざまな種類のターミナルプログラムを投稿する方法を、実際の事例を用いて解説します。