MQL5におけるイベント駆動型アーキテクチャ:エキスパートアドバイザーを本格的なトレードシステムに進化させる方法
はじめに
MQL5でEAを開発する際、多くの人は最も分かりやすい方法から始めます。すなわち、すべてのロジックをOnTickメソッドの中に配置する方法です。実際、この方法は開発を始めるうえでは簡単です。しかし、このアプローチには見えにくい代償があります。プロジェクトが成長するにつれて、売買ルール、条件チェック、注文処理、データ更新、インターフェース、計算、ログ出力などが、すべて単一のハンドラに集約されていきます。その結果、コードはやがてプログラムとして扱いにくい状態にまで肥大化し、実際には運任せで辛うじて動いているような状態になります。一箇所を変更すると、システムのまったく別の部分に影響が及び始めます。ビジュアルパネルを修正すると、突然トレードシナリオが壊れます。エントリーフィルタを変更すると、バックグラウンドチェックでエラーが発生します。このようなEAはすぐに脆弱なモノリスへと変わり、複雑さは開発者の自信が増す速度よりも速く増大していきます。
MetaTrader 5は単なる価格配信のストリームではありません。その基盤はイベントです。ターミナルは常に、ティック、タイマーシグナル、ユーザー操作、取引状態の変更、板情報更新イベントを受信しています。これらのメッセージは、それぞれ個別に処理されるべきものです。このため、MQL5にはさまざまなハンドラが用意されており、それぞれが独自の責任領域を持っています。OnTickは市場更新を担当します。OnTimerは定期的なタスクやバックグラウンドタスクを担当します。OnChartEventはGUIやユーザー操作への応答を担当します。ロジックがその目的に応じて分散されると、コードは雑然としたものではなくなります。各モジュールが自身の役割を果たし、隣接するモジュールに干渉しない、適切に設計されたシステムのようになります。
この設計は、EAが単一銘柄の範囲を超え、複数の機能を同時に実行し始めると特に重要になります。市場を監視し、インターフェースを維持し、ボタン操作に応答し、内部状態を同期し、コンポーネント間でシグナルを伝達し、場合によっては複数の銘柄を扱う必要があります。この段階では、イベントアーキテクチャはもはや魅力的な理論には見えません。実践上の必要性になります。バックグラウンド処理をOnTimerに移し、ユーザー操作への応答をOnChartEventに移すことで、メインのトレード処理は不要な負荷から解放されます。これにより、システムの動作はより予測可能になり、保守も大幅に容易になります。
このようなアーキテクチャにおいて、カスタムイベントとサービスは特別な役割を果たします。カスタムイベントはモジュール間の内部メッセージバスを構成する手段を提供し、サービスは補助的なロジックを特定のチャートの外部へ移動することを可能にします。これはもはや単なるEAではなく、すべてを一つの関数に混在させることなく、コンポーネント同士がコマンドやシグナルを交換できるシステムです。このアプローチにより、コントロールパネル、バックグラウンド解析、モジュール間通信、明確な役割分担を備えた、アプリケーションレベルのソリューションを構築できます。
さらに、イベント駆動アプローチはテスト容易性も大幅に向上させます。ロジックが複数のハンドラに分散されていれば、タイマーへの応答、UIイベントへの応答、取引トランザクションへの応答を個別にテストできます。これは、ミスが時間や資金の損失につながるタスクにおいて特に重要です。構造が優れているほど、システムの振る舞いを制御しやすくなり、障害の原因を迅速に特定できます。
本記事では、「すべてを OnTick に実装する」モデルから、より成熟したイベントアーキテクチャへ移行する方法について見ていきます。定義済みハンドラとカスタムイベントの役割、さらにチャートに依存しないサービスについても取り上げます。また、実際の開発が始まる前の段階でアーキテクチャを破綻させてしまう典型的な誤りについても詳しく見ていきます。ここでの主な考え方はシンプルです。MQL5を本来の目的に沿って活用すれば、単なる自動売買ロボットだけでなく、本格的なアプリケーションシステムを構築することができます。

定義済みイベント
MQL5における定義済みイベントは、プログラム全体のロジックが構築されるフレームワークです。これは単なるハンドラ関数の集合ではなく、環境の変化に対する厳密に定義された応答モデルです。そして開発者がこれらをOnTickへの付加機能として捉えるのをやめればやめるほど、コードはより早くアーキテクチャを備え始めます。
あらゆるプログラムのライフサイクルは、 OnInitで始まり、OnDeinitで終了します。これらはいわばエントリーポイントと終了ポイントです。OnInitでは基盤を構築します。EventSetTimerを使用してタイマーを設定し、グラフィカルオブジェクトを作成し、内部構造や外部コンポーネントを初期化します。また、サービス機能を起動し、実行環境を準備する場所としても適しています。
int OnInit() { EventSetTimer(30); // poll every 30 seconds return(INIT_SUCCEEDED); }
一方OnDeinitでは規律が求められます。作成したものはすべて適切に解放しなければなりません。オブジェクトを削除し、EventKillTimerを使用してタイマーを停止し、リソースをクローズします。この対称性を無視することは典型的な誤りであり、実行環境の汚染や原因の分かりにくい不具合を引き起こします。
void OnDeinit(const int reason) { EventKillTimer(); // disable the timer when unloading }
OnTickは伝統的にEAの心臓部と見なされています。これは部分的には正しいと言えます。その役割は、特定のシンボルの市場変化に反応することです。ここでシグナルを計算し、エントリー条件を確認し、ポジションを管理します。しかし、根本的に重要な点として、OnTickの存在は必須ではありません。ロジックを他のイベントへ移せば、EAは OnTick なしでも動作できます。さらに、成熟したアーキテクチャでは、OnTickはしばしば中心的存在ではなく、軽量なルーターになります。
OnTimerは、多くの開発者に過小評価されているツールです。ターミナルは指定された間隔でこのイベントを生成するため、市場関連の処理とバックグラウンド処理を分離できます。統計情報の収集、キャッシュの更新、複数シンボルのポーリング、インジケータの再計算などは、すべてタイマーに配置するのが理にかなっています。このアプローチによってOnTickの負荷が軽減され、特に高ボラティリティ時にシステムの動作がより安定します。ここで覚えておくべきシンプルかつ重要なルールがあります。タイマーはOnInitで設定するだけでなく、必ずOnDeinitで解除しなければなりません。
OnChartEventは、インタラクティブアプリケーションの世界への扉を開きます。これはGUIイベントのハンドラです。マウスクリック、キー入力、オブジェクトとのインタラクション、そして最も重要なカスタムイベントを処理します。コントロールパネル、ボタン、モード切り替えなどは、このハンドラを通じて実装されます。また、他のプログラムから送られてくる外部シグナルへの応答もここで実装されます。イベント駆動アーキテクチャの文脈では、これはもはや単なるUIハンドラではなく、本格的な通信手段です。
OnTradeTransactionは、取引状態を制御するためのポイントです。操作結果だけを確認する単純化されたアプローチとは異なり、ここではMqlTradeTransaction構造体全体にアクセスできます。これにより、注文のライフサイクルを正確に追跡できます。注文送信から約定、そしてその後の変更に至るまでを把握できます。このレベルの詳細性は、目的だけでなく、そこへ至る過程も重要となる複雑なポジション管理シナリオにおいて特に重要です。
OnBookEventは利用頻度こそ高くありませんが、特定のタスクでは不可欠です。このイベントは板情報の変化に反応し、MarketBookAdd によって有効化されます。ここはより高度な戦略の領域です。価格だけでなく、流動性の構造そのものが重要になります。高頻度取引アプローチや市場のミクロな動きを分析する場合、このハンドラは主要なシグナルソースとなります。
ここでの重要なポイントはシンプルです。MQL5では、各プログラムタイプがそれぞれ固有のイベントセットを受け取り、そのイベントを通じて振る舞いが形成されます。このモデルを無視し、すべてを単一のハンドラへ集約しようとすると、必然的に複雑性が増大します。反対に、ロジックをイベントごとに適切に分散すれば、明確で予測可能かつ拡張可能なシステムが構築されます。これは、さらに先へ進むために欠かせない基盤そのものです。
カスタムイベント
MQL5におけるカスタムイベントは、イベントアーキテクチャを構築するための最も実践的なツールの一つです。これを利用することで、プログラムはもはや自己完結した存在ではなくなります。単一システムのノード同士がサービスメッセージをやり取りするのと同じように、その構成要素間でシグナルを送受信できるようになります。これは、 EventChartCustom関数によって実現されます。この関数を使用すると、指定したチャートに独自のイベントをプログラムから送信し、その後OnChartEventで処理できます。これは内部メッセージバスです。特に、市場への応答、インターフェース、サービスモジュール、トレードロジックの動作を単一プログラム内で調整しなければならない場面で大きな価値を発揮します。
このアプローチの最大の利点は、モジュール間の責務を分離できることです。あるコンポーネントは市場分析を担当できます。別のコンポーネントはパネル状態の表示を担当できます。3つ目は取引処理の実行を担当できます。4つ目はバックグラウンド計算の管理を担当できます。これらすべてを直接呼び出しや共有グローバル変数で結び付けようとすると、コードはすぐに崩れ始めます。保守作業は、すべての歯車が互いに噛み合っている機械を修理するようなものになってしまいます。カスタムイベントはこの混乱を回避します。モジュールは他のモジュールの内部ロジックに干渉するのではなく、受信されるべき場所へ整理されたシグナルを送るだけです。
ターゲットを指定したイベント送信が可能である点も特筆に値します。EventChartCustomでは、現在のチャートだけでなく、別ウィンドウのchartIDも指定できます。これにより、異なるEA、インジケータ、サービス間で通信をおこなう道が開かれます。この仕組みは、複数銘柄および複数チャートシステムにおいて特に有用です。あるコンポーネントが情報を収集または生成し、別のコンポーネントがそれを受け取ってアクションを実行します。このとき、カスタムイベントは分散した各部分を単一のアーキテクチャへ結び付ける橋渡し役となります。
さらに、イベントのやり取りという考え方は初期化段階で特に有効です。場合によっては、 OnInitに大量の準備処理を詰め込みたくなることがあります。内部オブジェクトの生成、データの読み込み、キャッシュの構築、インターフェースの設定、サービスの起動などです。しかし、これらすべてを初期化処理の中で実行すると、メソッドは肥大化し、プログラムの起動を遅くし始めます。さらに、初期化に時間がかかりすぎると、時間制限に達したり、単に起動が重く扱いにくくなったりするリスクがあります。そのため、OnInitには最小限の準備処理だけを残し、直後にカスタムイベントを生成して、負荷の大きい処理を別のハンドラへ移すほうが理にかなっています。そうすればプログラムは素早く起動し、本格的な準備作業はイベントモードで実行されます。
これは別の理由からも重要です。MQL5では、関数が長時間実行されると、他のイベントの処理がブロックされます。小さな関数を複数続けて呼び出しても問題は解決しません。その処理チェーンが完了するまで、イベントフローへ他の処理が介入することはできないからです。これはユーザーインターフェースを持つプログラムで特に顕著です。ユーザーはパネルを操作して応答を待ちますが、プログラムは長い処理を実行しているため反応しません。実際にはシステムが停止しているわけではなく、イベントハンドラへ制御を渡す時間がないだけですが、ユーザーにはフリーズしているように見えます。まさにこの理由から、イベントモデルはアーキテクチャ上必要不可欠なのです。イベントハンドラにオーケストレーションを委譲することで、インターフェースをブロックしたりプログラムを無応答状態にしたりすることなく、各ステップを順番に実行できます。
ただし、この柔軟性には裏面もあります。カスタムイベントは無秩序に送信できるものではありません。明確なプロトコルが必要です。どのIDが何を意味するのか、メッセージコードをどこに格納するのか、そのイベントが自分のアプリケーション向けかどうかを判定するルールも定める必要があります。通常は、そのために専用のカスタムID範囲を割り当て、文字列パラメータには分かりやすいラベルを使用し、そのイベントが本当に対象モジュール向けであることを必ず確認します。そうしなければ、無関係なシグナルによってロジックが簡単に乱され、アーキテクチャ全体が混乱へ陥る可能性があります。
もう一つ、しばしば過小評価されるルールがあります。実際に必要でない限り、イベントを過剰に生成してはいけません。ターミナルのイベントキューは不要なノイズを好みません。イベントが頻繁に送信されすぎると、メッセージキューが過負荷になり、シグナル処理の効率が低下します。送信するのは、本当に意味のある状態変化だけにすべきです。たとえば、データの準備完了、シグナルの発生、インターフェース更新命令、処理実行の確認などです。そうすることで、イベントは雑然とした電報の洪水ではなく、整理された通知メッセージのように機能します。
このため、カスタムイベントは成熟したシステムにおいて特に価値があります。モジュール同士を強く結合させることなく接続し、明確な相互作用プロトコルを構築できるようにし、MQL5を本格的なイベント駆動開発のための環境へと変えるのです。
サービス
MQL5におけるサービスは、ターミナル上で動作するバックグラウンドサービスです。チャートいも銘柄にも紐付かず、ティックを待つ必要もありません。起動後は、自律的に動作します。何をすべきかをいちいち指示される必要のない、黙々と仕事をこなす裏方のような存在です。
ロジック全体はOnStart関数の中に配置されます。通常は、その内部でループを構成します。状態を確認し、必要な処理を実行し、Sleepで待機してから次の処理へ進む、という流れです。このリズムはシンプルで信頼性があります。市場イベントを必要とせず、チャートの活動にも依存しません。サービス自身が自分のペースを決めます。
実際の利点はすぐに見えてきます。外部ソースから定期的にデータをダウンロードする必要がある場合、サービスがその役割を担えます。カスタム銘柄向けのティックを生成したい場合も、サービスで対応できます。数秒ごとにシグナルを送信したり、システム状態を更新したりしたい場合も同様です。繰り返し実行される処理、市場のノイズに依存させるべきでない処理は、論理的にはここへ移すべきです。
サービスを特に便利なものにしている要素がもう一つあります。手動で停止しない限り、次回ターミナルを起動した際に自動的に再開されることです。これにより、サービスは定型処理のためのツールになります。起動時の状態確認、データの準備、同期処理などを、ユーザーの介入なしに実行できます。一度設定してしまえば、サービスは毎日、時計のように正確に動作します。
アーキテクチャの観点から見ると、これは非常に健全な構成です。チャート上のEAは市場に反応します。同時に、インターフェースはユーザーとの対話を担当します。その一方で、サービスはバックグラウンドで静かに、そして規則的に作業を続けます。無駄な動作はありません。OnTickの肥大化もありません。このような役割分担によって、システムは不要な負荷から解放され、より安定したものになります。それぞれの構成要素が自分の仕事を果たすようになると、システム全体の動作は目に見えて整理され、洗練されたものになります。
プログラム間の通信
トレードソリューションが複数のコンポーネントで構成される場合、それらの間にはメッセージング機構が必要になります。あるモジュールはデータを収集し、別のモジュールはそれをパネルに表示し、さらに別のモジュールはトレード判断をおこない、別のモジュールはバックグラウンドロジックを処理します。そして、それぞれが独立した世界で動作し始めると、結果はアーキテクチャではなく、フラグ、グローバル変数、ランダムなチェックの絡まりになります。だからこそ、イベント駆動構造がここで特に有効になります。
イベント駆動アーキテクチャのクリーンな形は、インジケータがシグナルのソースになることです。ただし従来のように「バッファを取得して値を読む」という方式ではなく、完全なイベント生成装置として扱います。
考え方はシンプルです。各インジケータは軽量なアダプタでラップされます。内部では1つのことだけをおこないます。シグナル発生を監視し、条件が成立した場合にカスタムイベントを送信します。これだけです。外部からのポーリングもなく、不要なノイズもありません。
int OnCalculate(const int32_t rates_total, const int32_t 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 int32_t &spread[]) { if(prev_calculated==rates_total) return prev_calculated; //--- if(BarsCalculated(handle) < rates_total) return(prev_calculated); vector<double> sig, main; if(!main.CopyIndicatorBuffer(handle, 0, 1, 2) || !sig.CopyIndicatorBuffer(handle, 1, 1, 2)) return(prev_calculated); if(sig[0] > main[0] && sig[1] <= main[1]) { if(!EventChartCustom(ChartID(), BuyID, MagicNumber, main[0], IndComment)) return(prev_calculated); } if(sig[0] < main[0] && sig[1] >= main[1]) { if(!EventChartCustom(ChartID(), SellID, MagicNumber, main[0], IndComment)) return(prev_calculated); } //--- return value of prev_calculated for the next call return(rates_total); }
例としてMACDとRSIという2つの典型的なインジケータを考えます。どちらも独立して動作し、それぞれの時間足を扱います。いずれも新しいバーに対してのみ処理をおこないます。MACDはヒストグラムとシグナルラインのクロスをチェックします。RSIは買われすぎ・売られすぎのレベルとの交差を扱います。シグナルが発生すると、それぞれは事前定義されたカスタムイベントを生成します。ここで重要なのは、インジケータはシグナルをバッファに保持するのではなく、イベントとして登録するという点です。
次にEAが登場しますが、その役割は従来とは大きく異なります。EAは毎ティックでインジケータをポーリングしません。履歴を走査することもなく、バッファ同期も行いません。シグナルの有無を推測することもしません。単にOnChartEventでイベントを受信するだけです。
void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- if(lparam != MagicNumber) return; switch(id - CHARTEVENT_CUSTOM) { case MACD1Buy: indSignals[0] = 1; break; case MACD1Sell: indSignals[0] = -1; break; case MACD2Buy: indSignals[1] = 1; CloseSellSignal = true; CloseBuySignal = false; break; case MACD2Sell: indSignals[1] = -1; CloseBuySignal = true; CloseSellSignal = false; break; case RSI1CrossOverBoughtDown: indSignals[2] = -1; break; case RSI1CrossOverSoldUp: indSignals[2] = 1; break; case RSI2CrossOverBoughtDown: indSignals[3] = -1; CloseBuySignal = true; CloseSellSignal = false; break; case RSI2CrossOverSoldUp: indSignals[3] = 1; CloseSellSignal = true; CloseBuySignal = false; break; } BuySignal = true; SellSignal = true; for(uint i = 0; i < indSignals.Size() && (BuySignal || SellSignal); i++) { BuySignal = BuySignal && (indSignals[i] == 1); SellSignal = SellSignal && (indSignals[i] == -1); } }
EAは受信したイベントを登録します。何も来なければ何もしません。
この構造は負荷の性質を大きく変えます。従来のポーリング型では、毎ティックで同じ処理が繰り返されます。インジケータバッファの取得、値のチェック、必要に応じて履歴の再走査も発生します。特に複数インジケータや異なる時間足が絡むと、シグナル同士の間には常に時間的なギャップが存在しており、それを捉えるために常に過去を遡って確認する必要があります。
イベントモデルは、このような余分な作業を取り除きます。各シグナルはあらかじめ用意されたイベントとして扱われます。シグナルを探す必要も、再計算する必要も、後から再構築する必要もありません。それは発生した瞬間にそのまま届きます。
結果として、構造は次のように整理されます。
- インジケータは自身の計算と自身のシグナルのみを担当します。
- イベントは状態変化の瞬間を記録します。
- EAはシグナルを集約し、意思決定をおこないます。
これは複数インジケータ戦略で特に顕著に現れます。たとえば、一方のシグナルがM5から、もう一方がM30から到来する場合です。従来のモデルでは、データの同期、ラグの考慮、履歴の確認が必要になります。しかしここでは状況はより単純です。各インジケータは適切なタイミングで状態を通知し、EAは状態を保持して条件が揃うのを待つだけです。
性能面では、これは明確な利点をもたらします。インジケータへの継続的な参照はなくなり、毎ティックでの空のチェックも不要になります。OnTickは軽量かつ高速になり、その結果として市場への反応も速くなります。トレードにおいてこれは単なる理論ではなく、実務上の明確な利点です。遅延が減り、ロジックが明確になり、執行精度が向上します。
void OnTick() { //--- if(BuySignal) { cSymbol.Refresh(); cSymbol.RefreshRates(); if(!cTrade.Buy(InpLot, cSymbol.Name(), cSymbol.Ask(), cSymbol.Ask() - SL * cSymbol.Point(), cSymbol.Ask() + TP * cSymbol.Point(), "Event Example")) { PrintFormat("Error open Buy position: %d", GetLastError()); return; } BuySignal = false; ArrayFill(indSignals, 0, indSignals.Size(), 0); } //--- if(SellSignal) { cSymbol.Refresh(); cSymbol.RefreshRates(); if(!cTrade.Sell(InpLot, cSymbol.Name(), cSymbol.Bid(), cSymbol.Bid() + SL * cSymbol.Point(), cSymbol.Bid() - TP * cSymbol.Point(), "Event Example")) { PrintFormat("Error open Sell position: %d", GetLastError()); return; } SellSignal = false; ArrayFill(indSignals, 0, indSignals.Size(), 0); } //--- if(CloseBuySignal) { if(cPosition.SelectByMagic(cSymbol.Name(), MagicNumber) && cPosition.PositionType() == POSITION_TYPE_BUY) { if(!cTrade.PositionClose(cPosition.Ticket())) { PrintFormat("Error close Buy position: %d", GetLastError()); return; } } } //--- if(CloseSellSignal) { if(cPosition.SelectByMagic(cSymbol.Name(), MagicNumber) && cPosition.PositionType() == POSITION_TYPE_SELL) { if(!cTrade.PositionClose(cPosition.Ticket())) { PrintFormat("Error close Sell position: %d", GetLastError()); return; } } } //--- }
さらに重要なのは、コードの透明性が向上するという点です。シグナルはもはや解釈を必要とするバッファ値ではなく、「発生した、記録された、処理された」という明確なイベントへと変わります。これはアーキテクチャそのものがコードの可読性とシステムの挙動の両方を直接改善する典型例です。
MetaTrader 5のストラテジーテスターでEAを実行した結果、興味深い結果が得られました。このシステムは明確な性質を持っており、積極的に利益を上げつつもリスクを適切な範囲に維持しています。


初期資金USD 1000.0に対して、結果はUSD 2427.52、すなわち3年間で+143%となりました。結果は非常に良好であり、特に中程度のドローダウンを考慮すると優れています。エクイティカーブは滑らかで、押し戻しはあるものの、長く続く深い落ち込みは見られません。最大ドローダウンは約12~13%であり、リカバリーファクターは約4.6です。このことから、ドローダウンからの回復能力も高いことが分かります。これは持続可能性を示す重要な指標です。
通常の利益メカニズムも機能しています。総取引数は18回で、勝率は約50%です。しかし平均利益が平均損失の約4倍であるため、プロフィットファクターは約3.9に達しています。この戦略は取引回数ではなく、シグナルの質とポジション保有によって利益を上げています。ただしこの点には制約もあります。取引数が少ないため、統計的な安定性は十分とは言えません。
エクイティカーブは比較的滑らかであり、不規則な急変は見られません。これはシグナルがランダムではないことを間接的に示しています。
そして重要なアーキテクチャ上のポイントとして、イベント駆動モデルはインジケータのポーリングを排除します。EAはシグナルのみに反応します。不要な処理が減ることで応答速度が向上します。テスター上ではこれは細かな差に見えますが、ライブトレードでは明確な優位性となります。
結論として、これはリスクリワード比が良好で、適切に設計されたアーキテクチャを持つシグナルモデルです。ただし現時点ではまだプロトタイプであり、アウトオブサンプルテストおよび統計の拡張が必要です。
典型的なミス
アーキテクチャがイベント駆動型になると、コードはよりクリーンで高速になります。しかし同時に、エラーの性質も変化します。エラーは表面上には現れず、ハンドラ間の接続やリソース管理の規律の中に潜むようになります。
最初で最も頻繁に見られる問題は、従来通りの過負荷なOnTickです。イベントモデルを理解した後でも、「念のため」という理由で一部のロジックを OnTick に残してしまう誘惑があります。その結果、タイマーやイベントは存在しているにもかかわらず、実際の処理の大部分は依然として単一のメソッドが担うハイブリッド構造になります。この妥協はすぐに振り出しに戻ってしまいます。つまり重く、保守しにくいコードです。経験上、役割分担を始めたのであれば、それを最後まで徹底する必要があります。
次のエラー層はリソース管理に関連します。イベントモデルではタイマー、オブジェクト、購読、補助構造など、管理対象が増加します。これらを適切に管理しない場合、システムは徐々に不安定になります。EventKillTimerの呼び忘れ、グラフィカルオブジェクトの未削除、グローバル変数の放置などは、すぐにプログラムを破壊するわけではありませんが、徐々に予測可能性を低下させます。その結果、典型的な問題として「原因と発生場所が一致しないエラー」が発生します。
void OnDeinit(const int reason) { //--- for(uint i = 0; i < handles.Size(); i++) if(handles[i] != INVALID_HANDLE) IndicatorRelease(handles[i]); }
イベントという仕組みそのものにも注意が必要です。ハンドラは逐次的に実行されるため、1つのイベント内で長時間処理をおこなうと他のイベントがブロックされます。これは特にインターフェースで顕著に現れます。ユーザーはパネルを操作しているにもかかわらず、プログラムは計算処理中で応答しません。ここでもアーキテクチャの重要な原則が現れます。すなわち、処理を段階的に分割し、スレッドを必要以上に占有しないことです。
システム内に複数のイベントソース(インジケータ、パネル、サービス)が存在する場合、識別の規律が重要になります。明確な命名規則やイベントIDの体系を導入しない場合、混乱が発生します。シグナルが重なり、ハンドラが誤った対象に反応するようになります。接頭辞やIDレンジのルールは一見単純ですが、システムが成長する際の秩序を維持するためには不可欠です。
#define MACD1Buy 1 #define MACD1Sell 2 #define MACD2Buy 3 #define MACD2Sell 4 #define RSI1CrossOverBoughtUp 5 #define RSI1CrossOverBoughtDown 6 #define RSI1CrossOverSoldUp 7 #define RSI1CrossOverSoldDown 8 #define RSI2CrossOverBoughtUp 9 #define RSI2CrossOverBoughtDown 10 #define RSI2CrossOverSoldUp 11 #define RSI2CrossOverSoldDown 12
最後に最も厄介なポイントはプログラムの起動処理です。イベント駆動モデルでは、初期化が完了する前にイベントが発生する可能性があります。その時点で構造体が準備できていない場合、未初期化データや不正な状態へアクセスする危険があります。そのため初期化処理は、イベント処理が始まる前に確実に完了させるか、あるいはより実用的には、段階的に分割し、カスタムイベントを用いて準備完了状態を制御する必要があります。
結論として、イベントそのものがシステムの信頼性を保証するわけではありません。イベントはあくまでツールです。信頼性は、それらを扱うエンジニアリング上の規律によってのみ生まれます。そしてその規律が存在する場合、イベント駆動アーキテクチャは単なる便利な手法ではなく、安定かつ予測可能なトレードシステムの基盤となります。
結論
プログラムのロジックがイベントを基盤として構築されると、プラットフォームは単なるEAの実行環境ではなく、本格的なアプリケーションソリューションの基盤へと変化します。このモデルでは、プログラミングはすでにクラシックなデスクトップアプリケーションの開発に近いものになります。ボタンやパネルを備えたインターフェースが現れ、バックグラウンドサービスが起動し、異なる種類のイベントに対して個別のハンドラが用意されます。すべてが適切な場所に配置されるため、システムは保守面で明らかに安定します。
イベント駆動のアプローチは、通常のEAがあらゆる用途を1つのファイルで担うという役割に対応しきれなくなったときに特に有効です。この手法により、複数銘柄のアシスタント、柔軟なコントロールパネル、状態の意味的変化に応答する複雑なコントローラを構築できます。このアーキテクチャでは、新機能の追加も容易になります。システム全体を作り直す必要はなく、単に新しいイベントやハンドラを追加するだけで対応できます。
これがイベント駆動アーキテクチャの本質的な利点です。すなわち、柔軟性を失うことなく秩序を提供することです。ハンドラ、サービス、通信チャネルを適切に分離することで、信頼性が高く、拡張性があり、そして実運用に耐えるシステムのように振る舞うトレードシステムを構築できます。ここでMQL5は従来のEAという枠を超え、より複雑なトレードアプリケーションのための環境へと拡張されます。そこでは市場への反応だけでなく、プログラム全体を支える成熟したエンジニアリング設計そのものが重要になります。
記事で使用されているプログラム
| # | 名前 | 種類 | 詳細 |
|---|---|---|---|
| 1 | EventExample.mq5 | EA | イベント処理用EA |
| 2 | EventMACD.mq5 | インジケータ | ライン交差時にイベントを生成するMACDインジケータ |
| 3 | EventRSI.mq5 | インジケータ | レベル交差時にイベントを生成するRSIインジケータ |
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/22383
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
MetaTrader 5とMQL5経済指標カレンダー:ニュースを再現性のあるトレードシステムに変える方法
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
Python-MetaTrader 5ストラテジーテスター(第1回):取引シミュレーター
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
バッファを使用しないインジケータを使用したExpert Advisorでの作業は、おそらく例としては問題ないでしょう。
例では、シンボルデータの厳密な更新が行われています(MQL_TESTERを使用するのが良いでしょう)。
しかし、イベントを通して計算されたシグナルとティックの関連性をチェックしません。これは本当に問題である。
非同期OrderSendによって弱めることはできますが、解決することはできません。したがって、このような例であっても、ChartEvent-eventでは、イベント生成が発生したティックのデータを追加で渡す必要があります。