初級から中級まで:イベント(I)
はじめに
前回の「初級から中級まで:構造体(II)」では、基本的な構造体について議論し、関数や手続きに値を渡すために構造体をどのように利用できるかを見てきました。構造体のテーマはまだ十分に掘り下げられてはいませんが、現時点で構造体で実装可能な特定の側面に踏み込むのは適切ではないと考えています。これは、私の観察によれば、MQL5では非常に高度なプログラミングを必要とする場面があまり多くないからです。誤解なさらないでください。
私が言いたいのは、わずかな知識でも、しっかりとした概念的基礎があれば、MetaTrader 5で使用するほぼすべての種類のアプリケーションを作成し、実装できるということです。そして、実際には、作業時間の95%(あるいはそれ以上)を単純なアプリケーションの開発に費やすことになるため現時点でより複雑な詳細に踏み込む理由はほとんどないと考えます。
したがって、基本的なプログラミング概念に関する説明はしばらく保留とし、代わりに純粋なMQL5を用いたMetaTrader 5向けアプリケーションの実装方法を見ていきます。
本記事は、このようなコード実装の基礎を理解することを目的としているため、まず最も基本的な実装から見ていきます。しかしその前に、もうひとつ別のテーマについて触れる必要があります。
イベントの基本概念
ここではあまり詳細に踏み込みません。というのも、このテーマのタイトルが指す内容を議論すると、文字通りプログラミングに関する一冊の本になってしまうほど広範な話だからです。この分野は非常に広く、多くのニュアンスがあります。しかし、MetaTrader 5で使用するアプリケーションの実装について実際に話を始める前に、理解を少し整理しておく必要があります。
ほとんどすべてのグラフィカルなアプリケーションはイベントを使用します。特定のイベントが発生する瞬間は、一般的には私たちが何を実装しているかには依存しません。自分のアプリケーション内からイベントを発生させることができる特殊なケースもあります。しかし、これは特殊なケースなので、コードを制御する方法として考えるべきではありません。実際、多くの場合、特定の環境で実行されるコードを実装しているプログラマは、伝統的な意味で「プログラミング」しているわけではありません。彼らがおこなっているのは、発生したイベントに対してコードがどのように反応するかを定義しているだけです。
あるアプリケーションで無視されるイベントが、別のアプリケーションでは他のアクティビティよりも優先されるかもしれません。これを理解するのは非常に複雑で、多くの初心者にとって難しいことです。なぜなら、これまでに示されたコードはイベントを処理することを意図していなかったからです。単にプログラムされた通りに実行されていました。
しかし、MetaTrader 5で使用するインジケーターやエキスパートアドバイザー(EA)をプログラムする場合、奇妙に聞こえるかもしれませんが、イベントを発生させることにはまったく関心がありません。私たちが関心を持つのは、与えられたイベントに対してインジケーターやEAがどのように反応するかを定義することです。
たとえば、Gimpのようにウィンドウ内で描画できるプログラムを使うと、プログラムがある特定の方法で動作していると想像するかもしれません。しかし実際には、マウスクリックやキー入力、マウスドラッグなど、あなたが生成するイベントに反応しているだけです。これらすべてがイベントです。プログラムはあなたが何をしているかを知るわけでも、監視するわけでもありません。ただイベントを待ち、それに応答します。最終的に、あなたが操作した画像が描画されるだけです。
MetaTrader 5でも同じことが起こります。MetaTrader 5は、ユーザーが何をしているかには関心がありません。単に何らかのイベントを受け取るのを待ち、そのイベントが発生したときに反応するだけです。
では、イベントを生成する責任は誰にあるのでしょうか。詳細には触れませんが、それはあなたです。しかし、どのグラフィカルアプリケーションがどのイベントを受け取るかを決定する責任はオペレーティングシステムにあります。ユーザーとして、これを自由に指示することはできません。オペレーティングシステムに対して、特定のアプリケーションが最小化や最大化をおこなう、または閉じるときにどう動作するかを伝えるだけです。
多くの人が誤解していますが、実際にアプリケーションがどのように閉じるかを決定するのはオペレーティングシステムではなくアプリケーション自身です。そのため、テキストを入力して編集ウィンドウを閉じる際に、編集内容を保存するかどうか尋ねられるのです。もしオペレーティングシステムがこれを管理していたら、状況ははるかに複雑になります。結局のところ、イベントにどう反応するかを決めるのは常にアプリケーション自身であり、オペレーティングシステムではありません。だから、あるシステムが他より優れていると主張するのは、実際の仕組みを理解していないことの表れだと私は考えます。
さて、MetaTrader 5はWindows以外のOSでも実行できますが、ここでは説明を簡単にするために、Windows上で動作している前提で話を進めます。たとえWeb版のMetaTrader 5ターミナルを使用していても、説明上、システムの基盤はWindowsとします。
なぜこの概念を定めるのかというと、MetaTrader 5向けのプログラミングを学ぶ場合、MetaTrader 5内で動作するアプリケーションは、Windowsから直接届くイベントに反応するのではなく、MetaTrader 5自体から届くイベントに反応することを理解する必要があるからです。繰り返しますが、MetaTrader 5内で動作するアプリケーションがWindowsに直接反応する場合もあります。しかしこれは非常に特殊で高度なプログラミングが必要なケースであり、現時点では考慮しません。
これらのポイントを理解した上で、ここまでの内容を図にまとめることができます。下の図です。

図01
この図は象徴的で、ユーザーが生成する可能性のあるイベントから、MetaTrader 5で動作するアプリケーションがそのイベントを受け取る瞬間までの経路を示しています。ここでアプリケーションとしてEAを例に挙げましたが、MetaTrader 5で実行可能な任意のアプリケーションでも構いません。ただし、EAはほとんどのイベントに反応するため、全体のシナリオでアプリケーションの位置を示す目安として使用しました。
ここまでは簡単な部分です。次は少し面白い話になります。イベントには同期イベントと非同期イベントの2種類があります。同期イベントは、一定周期で発生するタイプのもので、システムクロックと何らかの形で関連しています。たとえばチャート上に新しいバーが出現することは同期イベントの一例です。これは定期的に発生します。しかし、非同期イベントから発生する同期イベントもあり、これがいくつか興味深い状況を生みます。ただし、非同期イベントから発生する同期イベントを議論する前に、まず非同期イベントとは何かを理解する必要があります。
非同期イベントとは、ランダムに生成されるイベントのことです。たとえばキー入力、マウスの動き、あるいはポジションの新規建てや決済などです。この場合、ポジションは価格があるレベルに達したために建てられたり決済されたりします。要するに、非同期イベントとは、発生時間や順序を予測できないイベントのことです。
前述の通り、非同期イベントに基づく同期イベントもあります。たとえば、MetaTrader 5のチャート上でオブジェクトを選択し、その後ドラッグや削除をおこなう場合です。各イベントが発生した正確な瞬間は重要ではなく、特定の順序で発生したことが重要です。このため、多くの人が特定のタイプのアプリケーションをプログラミングする際に困難を感じます。非同期イベントに基づく同期イベントに対応する必要がある場合があるからです。ただし、現時点ではこれを心配する必要はありません。記事を進めるにつれて、このような状況にどう対処するかを学んでいきます。
実践での最初のイベント
これは、正直に言って少し難しい局面だと認めざるを得ません。イベントがどのように発生するのかをお見せしたい一方で、あまり意味をなさないものを作りたくないからです。そのため、理解しやすく、なおかつ自明な手段に頼る必要があります。そうすることで、イベントがどのように発生し、どのように処理されるのかを説明するために、あまり多くの時間を費やさずに済みます。この種のトピックは、イベントを理解している人にとっては非常に直感的ですが、そうでない人にとっては非常に混乱しやすいものです。そこで、まずはイベントを実際にキャプチャし、処理するための最初の試みをおこなってみましょう。
始める前に、MQL5において非常に重要な点を1つ理解しておく必要があります。それは、アプリケーションの種類の違いです。基本的に、MQL5には2種類のアプリケーションがあります。ユーザーイベントに応答できるものと、少なくとも直接的には応答できないものです。これはとてもシンプルです。ユーザーの操作イベントをキャプチャし、それに応答できるものとしては、インジケーターとEAがあります。一方で、ユーザーイベントをキャプチャできないため、それに応答できないものとして、スクリプトとサービスがあります。これを理解することは、最適な選択肢を選ぶために非常に重要です。
さて、ここまで私たちは基本的にスクリプトのみを使って作業してきました。スクリプトでは、OnStartという単一のエントリポイントがあり、そこから先はすべてプログラマであるあなた自身が生成し、制御しなければなりません。しかし今回の場合は、どのようなものであれイベントをキャプチャしたいのです。そのため、MetaTrader 5で利用可能な各モデルが何を提供してくれるのかを理解する必要があります。
この記事を読んでいる読者は、すでにMetaTrader 5を少なくとも部分的には使ったことがあるはずです。デフォルトで付属しているアプリケーションを使った経験もあるでしょう。その場合、同じチャート上に複数のインジケーターを配置できることに気づいているはずです。しかし、EAは1つしか配置できません。したがって、目的によっては、インジケーターとして実装するか、EAとして実装するかを選ぶことになります。
ただし、コードを書き始める前に理解しておかなければならない小さなポイントがあります。それは、インジケーターとEAに存在する制限についてです。そう、制限は確かに存在します。
たとえば、インジケーターは注文システムと通信することができません。これはEA専用の機能です。一方で、EAは、少なくとも簡単な方法では、ラインを描画したり、チャート上で何かを示すための計算をおこなったりすることができません。原則として、EAはチャートに描画するためのMQL5ライブラリ関数にアクセスする手段を持っていません。これらはインジケーター専用です。そのため、これらのモデルをどのように扱うかを理解することが非常に重要であり、これについては今後の記事の中で示していきます。
ここで読者は、「それでは、本当に役に立つものは作れないのではないか。制限が多すぎて、唯一無二のアプリケーションを作れないのではないか」と思うかもしれません。しかし実際には、MetaTrader 5は非常に安定性と安全性を重視して設計されています。そのため、異なるアプリケーション間で通信するための仕組みが用意されており、それを利用することで、長期的に見ても非常に興味深く、保守しやすいものを作ることが可能です。
この簡単な導入を踏まえた上で、次はイベントがどのようにキャプチャされるのかを見ていきましょう。現時点では、イベントの処理については心配せず、キャプチャすることのみに集中します。そのために、インジケーターを使用します。以下に示すのは、その最小構成のコードです。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. int OnInit() 05. { 06. return INIT_SUCCEEDED; 07. }; 08. //+------------------------------------------------------------------+ 09. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 10. { 11. return rates_total; 12. }; 13. //+------------------------------------------------------------------+
コード01
イベントという概念を理解しやすくするために、まずは最小限のコードから始めています。なぜそのように実装する必要があるのかを理解せずに、単にコードをコピーしても意味がありません。
このインジケーターをチャートに配置した瞬間、いくつかのことが論理的な順序で発生します。これを理解することは、自分自身でコードを書き始める上で非常に重要です。このコード01では、2つのイベントがキャプチャされます。そして、MetaTrader 5がこれらのイベントとどのように連携するかによって、私たちがどのようにそれらを扱うべきかが決まります。最初は少し混乱するかもしれません。しかし、MetaTrader 5の開発者が当初想定していなかったような処理であっても、アプリケーションに実行させることは可能です。そのためには、各イベントがいつ、どのようにトリガーされるのかを理解する必要があります。
もちろん、現段階では教育目的に重点を置いているため、多くの人が不可能だと考えるようなことをMetaTrader 5に「強制」させる方法については触れません。しかし、十分に学び、練習すれば、MQL5とMetaTrader 5を使ってほとんど何でもできるようになります。たとえば、動画や画像の編集といった、本来MetaTrader 5が想定していない用途であってもです。もっとも、MetaTrader 5の本来の目的は、電子市場で取引される資産を売買することにあります。
それでは、図01を拡張して、インジケーターであるコード01を示してみましょう。これにより、次のようなメッセージフローが得られます。

図02
図02は図01の続きです。図02で見ているのは、イベントが処理される最終段階で内部的に何が起きているかということです。ここでは特定の方向性に従って処理をおこなうため、この記事では扱わない他のタイプのイベントについても、少し理解しておくと良いでしょう。
そのためには、MQL5のドキュメントにあるイベント処理関数の項目を参照することをおすすめします。そこでは、MQL5がカバーしている多くのイベントについて、それぞれの目的や役割とともに詳しく説明されています。
話を戻して、コード01に含まれる各イベントがいつトリガーされるのかを理解してみましょう。これを理解するために、以前の記事でも使った小さなコマンドを追加するだけで十分です。その結果、同じコード01は次のようになります。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. int OnInit() 05. { 06. Print(__FUNCTION__); 07. 08. return INIT_SUCCEEDED; 09. }; 10. //+------------------------------------------------------------------+ 11. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 12. { 13. Print(__FUNCTION__); 14. 15. return rates_total; 16. }; 17. //+------------------------------------------------------------------+
コード02
これで、裏側で何が起きているのかが分かるようになりました。しかし、このコードを見て、「これは何の役にも立たないのではないか。なぜこのようなものを作るのでしょうか。」と思うかもしれません。実は、このコードは私たちの目的にとって非常に有用です。このコードがおこなうことは、キャプチャされたイベントの名前をターミナルに表示することだけです。これは6行目と13行目でおこなわれています。では、これをチャートに配置すると何が起こるでしょうか。

アニメーション01
アニメーション01では、MetaTrader 5がトリガーしたイベントが、コード02のインジケーターによってキャプチャされる様子が確認できます。見ての通り、一見すると何の役にも立たないこのコードが、イベントによって何かが起こったことを示してくれています。今回は、イベントを処理する方法として、単にターミナルにメッセージを表示しています。トリガーの順序にも注目してください。また、13行目が2回表示されている点にも注意してください。1回目はインジケーターをチャートに配置した直後、2回目は銘柄の価格が変化したときです。
これを理解することは非常に重要であり、今後大いに役立ちます。MetaTrader 5から発生するイベントを扱うということは、インジケーターやEAを書く際のコード実装の考え方を変える必要があることを意味します。これらは、MetaTrader 5によってトリガーされるイベントに応答する唯一の存在だからです。
次に、コード02にもう1つイベントハンドラを追加してみましょう。実行されるコードは以下のとおりです。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. int OnInit() 05. { 06. Print(__FUNCTION__); 07. 08. return INIT_SUCCEEDED; 09. }; 10. //+------------------------------------------------------------------+ 11. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 12. { 13. Print(__FUNCTION__); 14. 15. return rates_total; 16. }; 17. //+------------------------------------------------------------------+ 18. void OnDeinit(const int reason) 19. { 20. Print(__FUNCTION__); 21. }; 22. //+------------------------------------------------------------------+
コード03
再び、一見すると何の役にも立たないシンプルなコードです。しかし、実行してみると次のような結果が得られます。

アニメーション02
最初のケースと同様に、MetaTrader 5によってトリガーされたイベントに対してアプリケーションがどのように応答するかを観察しています。ここでは別の種類のイベントがキャプチャされています。理解してほしいのは、インジケーターがチャート上に存在し、そのチャートが特定の時間足を使用している状態で、時間足を変更するとイベントがトリガーされるという点です。そして、そのイベントがコード03のインジケーターによってキャプチャされます。
ここで重要な部分に入ります。コード02のインジケーターがチャート上にある状態で時間足を変更した場合、このイベントは単に失われていました。つまり、MetaTrader 5がイベントをトリガーしたという事実を無視していたのです。
「MetaTrader 5、すべてが機能し続けるために必要なことは何でもしてください。何の問題もありません。」
その結果、MetaTrader 5はイベントをトリガーしましたが、コード02はそれを無視しました。そのため、インジケーターが自動的に正しく振る舞っているように見えたのです。しかし、これは常に望ましい動作とは限りません。このような場合、コード03の18行目に示されている手続きを使うことで、そのイベントをキャプチャし、予期しない事態が起こらないように適切な対策を取ることができます。
MQL5にある程度慣れている人の中には、インジケーターではそれほど多くのイベントを気にする必要はないと考える人もいるでしょう。しかし、特定のイベントを無視した結果、MetaTrader 5から奇妙なエラーが報告されるケースも存在します。アプリケーションが適切に対応しない場合、MetaTrader 5はプラットフォーム全体の動作を守るために、独自の判断で処理をおこないます。
多くの人は、インジケーターではほとんどエラーが起こらないと考えていますが、実際にはエラーは発生します。そして、その際にイベントが無視されると、動作が遅くなったり、フリーズしたりといったパフォーマンス問題につながることがあります。これはMetaTrader 5の責任ではなく、イベントに正しく応答していないアプリケーション側の問題です。
この状況を示すために、非常にシンプルでありながら興味深い目的を持つコードを実装してみましょう。それは、一定期間内にOnCalculateイベントが何回トリガーされたかを教えてくれるものです。コードは次の通りです。
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. //+------------------------------------------------------------------+
コード04
このコードには、これまでに見てきた、そして議論してきた内容以外のものは含まれていません。そのため、これらの記事を学び、実践している人であれば十分に理解できるはずです。唯一疑問が生じるとすれば、FileLoadとFileSaveに関する部分かもしれませんが、これらは公式ドキュメントで詳しく説明されているため、ここでは触れません。
このコードを実行すると、次のような結果が得られます。

アニメーション03
時間軸を変更しても、カウンタが増え続けていることに注目してください。これは、30行目でMetaTrader 5がトリガーするイベントをキャプチャしているためです。この場合、イベントを引き起こした原因そのものを処理しているわけではありません。ただ単に、特定の変数をディスクに保存するようMetaTrader 5に伝えているだけです。これは他の方法でも実現できますが、ここでの目的は、あるイベントを処理する必要がある場合と、無視してもよい場合があることを示すことです。すべては、何を達成したいのか、そしてどのように目標に到達したいのかによって決まります。
30行目の手続きが存在しなかった場合、あるいは適切に処理されていなかった場合、時間軸を変更するたびにカウンタはゼロに戻ってしまいます。これは8行目でカウントを初期化しているためです。このコードはとても興味深いと思いませんか。シンプルでありながら、MetaTrader 5が生成するイベントに関する多くの重要な点を理解させてくれるのです。
最終的な考察
この記事で、内容がさらに楽しくなってきました。これは、イベント処理を探求していく一連の記事の第1回目です。私はこのテーマを、遊び心があり、シンプルで、かつ教育的な形で取り上げていくつもりです。そして、他のプログラマが提案した解決策を常にそのまま使う必要はないということをお見せしたいと思います。なぜなら、多くの状況において、ある人にとって最適な解決策が、あなたにとってその瞬間に最適とは限らないからです。
次回以降の記事の内容を、今から楽しみにしている方も多いと思います。しかし、これから徐々に複雑になっていく内容にいきなり飛び込む前に、まずはこの記事で扱った内容をしっかりと練習し、理解するようにしてください。ここで紹介したことを実際に試し、理解しようとする努力には、必ず大きな価値があります。考えるためのヒントと練習課題として、以下に添付されているコード04を改変してみてください。時間足が変更されたイベントが発生した場合にのみカウンタの値を保存し、それ以外のMetaTrader 5がトリガーするイベントでは、カウンタをゼロにリセットするようにしてみてください。
次の記事では、これを非常に安全かつシンプルに実現する方法をお見せします。それでは、作業に取りかかってみてください。楽しい学習を。
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/15732
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
市場シミュレーション(第9回):ソケット(III)
IBMの量子コンピュータを使ってすべての価格変動パターンを解析する
血液型遺伝最適化(BIO)
1世紀前の機能で取引戦略をアップデートする
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索