MetaTrader 5とMQL5経済指標カレンダー:ニュースを再現性のあるトレードシステムに変える方法
はじめに
現代のニューストレーダーが抱える主な問題は、ツールが分散していることと、アルゴリズムに基づく体系的な取引ワークフローが存在しないことです。ニュースサイトを閲覧するためのWebブラウザと、実際に取引をおこなうMetaTrader 5ターミナルの間で注意を切り替え続けるのは非常に困難です。
ニューストレーダーの一般的なワークフローは次のようになります。ニュースカレンダーを素早くブラウザで開き、イベントに変更がないか確認する → 直近のイベントを評価し、何をどのように取引するかを判断する → MetaTrader 5ターミナルへ移動し、保留注文(未決注文)を設定する、あるいはニュース発表を待ちながらリアルタイムで判断を下す。このような流れでは、トレーダーはしばしば「状況把握の途切れ」を経験します。その結果、ニュースへの反応が遅れ、最終的には損失につながります。
ニューストレードを、明確なルールと再現可能な結果、自動テストを備えた「工学的な課題」として扱いたいと思いませんか?本記事の目的は、MetaTrader 5向けニュースレイヤーの実用的なアーキテクチャを示すことです。具体的には、単一データソースの構築、カレンダーAPIの正しい利用方法、フィルタリングおよびキャッシュ機構、テスター向け履歴イベントのリソース化、ライブ環境とテスター環境の自動切り替えを扱います。
図1:手動トレーダーの最大の問題はツールの分断化である
手動のニューストレードは時代遅れです。
まず、外部要因に依存し、体系化されていないニュース戦略は、そのままではテストできません。トレード戦略の成功の半分は、過去データにおけるパフォーマンス検証に関係しています。戦略をテストできないということは、意思決定が主観的になることを意味します。その戦略は仮説のまま残り、検証のために数か月、あるいは数年もの時間を費やすことになります。
次に、手動トレードはスケーリングが非常に困難です。不安定で収益が不規則なビジネスを拡大しようとしても、問題が悪化するだけです。明確な構造化、システム化、最適化がなければ、スケーリングについて考えること自体が無意味です。
第三に、ニュースへの反応の遅れは手動トレードにおける大きな問題点です。たとえば、外国為替(FX)トレーダーが雇用統計(NFP)を取引しているとします。NFPデータは通常、月に一度発表されます。もし気を取られて発表を見逃してしまえば、利益を得る機会を逃してしまいます。このような状況は多くのトレーダーにとって身近なものです。
結論として、再現性のあるニューストレードを実現する唯一の方法は自動化です。

図2:ロボットは常により速い
MetaTrader 5内蔵の経済指標カレンダー:信頼できる単一のデータソース
ニューストレードをアルゴリズム化し、その結果としてヒストリカルテストを可能にするためには、MetaTrader 5に標準搭載された当社独自の経済カレンダーと、MQL5 API経由のニュースアクセスを活用します。これにより、ニューストレードは属人的な判断から検証可能なアルゴリズム運用へと変わります。リアルタイムイベントだけでなく、オフラインテスト用の過去のイベントデータも利用可能です。
表1:
MetaTrader 5に組み込まれている内蔵カレンダーの特長
| パラメータ | |
|---|---|
| アクセススピード | ニュースをターミナルへ読み込んだ後は100ms未満 |
| 統合 | ネイティブ対応:ターミナルコアレベル + MQL API |
| テスト | 完全対応:ニュースをターミナルへダウンロード後、ファイル、リソース、SQLite経由でストラテジーテスターからアクセス可能 |
| 信頼性 | 高い |
MetaTrader 5には優れたストラテジーテスターが搭載されています。高速でマルチアセットに対応し、ローカルネットワークおよびグローバルネットワーク上のコンピュータを活用したテストや最適化が可能です。経済指標ニュースを利用したトレードシステムを、ぜひ過去データでバックテストしてみてください。これによって、トレード手法に対する考え方が大きく変わるかもしれません。
MetaTrader 5経済指標カレンダー関数:MQL5 APIの概要
手動分析からアルゴリズム分析への移行は、まずデータアーキテクチャを理解することから始まります。MQL5において、経済指標カレンダーは単なる表形式データではなく、ネイティブAPIを通じてアクセス可能な構造化データベースです。本記事では、イベントを正しく取得する方法、EventとValueの違い、時刻同期が戦略の正確性にとって重要である理由を解説します。
MQL5の経済指標カレンダー機能は、複数の主要関数によって構成されています。それぞれの関数は異なる役割を持っています。重要なのは、経済指標カレンダーのすべてのデータを一度に取得できる「万能関数」は存在しないという点です。実際の運用では、複数の関数を組み合わせて利用する必要があります。
- CalendarValueHistory:初期読み込み用の主要ツール。指定した期間におけるイベント値の配列を取得します。これはエキスパートアドバイザー(EA)初期読み込み用の重量級関数であり、過去データの取得、今後数日〜1週間分のイベントデータ取得などをおこないます。
- CalendarValueLast:リアルタイム稼働EA向けの主要関数。前回リクエスト以降に変更または追加された値のみを取得します。この仕組みはchange_idメカニズムによって実現されています。これにより、毎ティックごとにデータ配列全体を要求することなく、通信量とサーバー負荷を削減できます。
- CalendarEventByCountry:指定した国に関連するイベント情報を取得します。ISO 3166-1 alpha-2に従って指定されたコードで特定される特定の国に対するイベント説明のリストを返します。フィルタ構築に必要です(たとえば、「US」(USA)、「RU」(ロシア)、「CA」(カナダ)などのイベントのみを表示する場合など)。
- CalendarEventByCurrency:通貨ごとのすべてのイベント情報を取得します。「USD」「EUR」などのコードで指定された特定の通貨に対するイベント説明のリストを返します。
- CalendarCountryById:idによって国のプロパティを取得します。
- CalendarEventById:idによってイベントのプロパティを取得します。
- CalendarValueById:idによって特定の値を取得します。
データ構造体:カレンダーMQL5 APIが返す値
経済指標カレンダーAPIのすべての関数は、構造体の配列または単一の構造体変数のいずれかを返します。以下に、構造体とそのフィールドを完全に列挙します。
イベント情報:CalendarEventById、CalendarEventByCountry、CalendarEventByCurrency関数で使用されます。
struct MqlCalendarEvent { ulong id; // event ID ENUM_CALENDAR_EVENT_TYPE type; // event type from the ENUM_CALENDAR_EVENT_TYPE enumeration ENUM_CALENDAR_EVENT_SECTOR sector; // sector an event is related to ENUM_CALENDAR_EVENT_FREQUENCY frequency; // event frequency (periodicity) ENUM_CALENDAR_EVENT_TIMEMODE time_mode; // event time mode ulong country_id; // country ID ENUM_CALENDAR_EVENT_UNIT unit; // economic indicator value's unit of measure ENUM_CALENDAR_EVENT_IMPORTANCE importance; // event importance ENUM_CALENDAR_EVENT_MULTIPLIER multiplier; // economic indicator value multiplier uint digits; // number of decimal places string source_url; // URL of a source where an event is published string event_code; // event code string name; // event text name in the terminal language (in the current terminal encoding) };
国情報:CalendarCountryByIdおよびCalendarCountries関数で使用されます。
struct MqlCalendarCountry { ulong id; // country ID (ISO 3166-1) string name; // country text name (in the current terminal encoding) string code; // country code name (ISO 3166-1 alpha-2) string currency; // country currency code string currency_symbol; // country currency symbol string url_name; // country name used in the mql5.com website URL };
イベント値:CalendarValueById、CalendarValueHistoryByEvent、CalendarValueHistory、CalendarValueLastByEvent、CalendarValueLast関数で使用されます。
struct MqlCalendarValue { ulong id; // value ID ulong event_id; // event ID datetime time; // event date and time datetime period; // event reporting period int revision; // revision of the published indicator relative to the reporting period long actual_value; // actual value in ppm or LONG_MIN if the value is not set long prev_value; // previous value in ppm or LONG_MIN if the value is not set long revised_prev_value; // revised previous value in ppm or LONG_MIN if the value is not set long forecast_value; // forecast value in ppm or LONG_MIN if the value is not set ENUM_CALENDAR_EVENT_IMPACT impact_type; // potential impact on the currency rate //--- functions for checking the values bool HasActualValue(void) const; // returns 'true' if actual_value is set bool HasPreviousValue(void) const; // returns 'true' if prev_value is set bool HasRevisedValue(void) const; // returns 'true' if revised_prev_value is set bool HasForecastValue(void) const; // returns 'true' if forecast_value is set //--- functions for getting values double GetActualValue(void) const; // return actual_value or nan if the value is not set double GetPreviousValue(void) const; // return prev_value or nan if the value is not set double GetRevisedValue(void) const; // returns revised_prev_value or nan if the value is not set double GetForecastValue(void) const; // returns forecast_value or nan if the value is not set };
注記:
なお、MqlCalendarValue構造体は、actual_value、forecast_value、prev_value、revised_prev_valueの各フィールドから値を確認し取得するためのメソッドを提供します。これらのフィールドには値が入っていない場合もあります。たとえば、actual_valueフィールドは、ニュースがまだ発表されていないため空になっていることがあります。そのため、値を取得する最も適切な方法は、構造体自体のメソッドを使って確認し、取得することです。
これらの構造体は、以下の関係によって相互に関連付けられています。

図3: カレンダー構造の関係
MqlCalendarCountry構造体は、国IDを介してMqlCalendarEventとリンクされています。この関係は「1対多(1..*)」です。
MqlCalendarEvent構造体は、イベントIDを介してMqlCalendarValueとリンクされています。この関係は「1対多(1..*)」です。
ニュースの発表時刻とサーバー時刻
経済指標カレンダーを扱うすべての関数は、トレードサーバー時間TimeTradeServerを使用します。つまり、MqlCalendarValue構造体の時刻およびCalendarValueHistoryByEvent()、CalendarValueHistory関数の入力時刻は、ユーザーのローカル時間ではなく、取引サーバーのタイムゾーンで設定されます。
時刻変換は不要です。MqlCalendarValue::periodの時刻フィールドは、TimeCurrent()やTimeTradeServer関数で取得した時刻と同じサーバー時間系で比較できます。テスター環境では、TimeTradeServer()関数はヒストリカルデータと同一のモデル時刻を返します。「ニュース30分前」といった時間ウィンドウのロジックは、リアルタイムとヒストリーの両方で同じように機能します。ブローカーが夏時間・冬時間の切り替えを考慮している場合、カレンダーもそれに自動的に対応します。
実例:本日(当日)のイベント一覧の取得
//+------------------------------------------------------------------+ //| Get calendar values for the current day | //+------------------------------------------------------------------+ void GetTodayUSD_Events() { //--- define the period boundaries in server time datetime server_now = TimeTradeServer(); datetime day_start = server_now - (server_now % 86400); datetime day_end = day_start + 86400; MqlCalendarValue values[]; MqlCalendarEvent event; MqlCalendarCountry country; //--- request values only for USD if(CalendarValueHistory(values, day_start, day_end, NULL, "USD")) { Print(" Events received for USD: ", ArraySize(values)); //--- iterate over the array of values for(int i = 0; i < ArraySize(values); i++) { //--- get event description if(CalendarEventById(values[i].event_id, event)) { //--- get country description if(CalendarCountryById(event.country_id, country)) { Print("✅ Event #", i); Print("Event ID: ", values[i].event_id); Print("Event name: ", event.name); Print("Sector: ", event.sector); Print("Source: ", event.source_url); Print("Country name: ", country.name); Print("Country URL: ", country.url_name); Print("Time: ", TimeToString(values[i].time, TIME_DATE | TIME_SECONDS)); Print("Impact: ", values[i].impact_type); // CHECK AND OUTPUT VALUES if(values[i].HasActualValue()) Print("Actual: ", values[i].GetActualValue()); if(values[i].HasRevisedValue()) Print("Revised: ", values[i].GetRevisedValue()); if(values[i].HasForecastValue()) Print("Forecast: ", values[i].GetForecastValue()); if(values[i].HasPreviousValue()) Print("Previous: ", values[i].GetPreviousValue()); } } } } else { int error = GetLastError(); if(error == 0) { Print("❌ CalendarValueHistory: No Events"); } else { Print("❌ Error CalendarValueHistory: ", error); } } } //+------------------------------------------------------------------+
この関数は、USD通貨に関する当日のイベント一覧と、MQL APIのカレンダーから取得した構造体の主要フィールドの内容を表示します。本記事に付属しているGetTodayEvents-S.mq5に完全なスクリプトコードが含まれています。
関数の実行結果は、MetaTrader 5ターミナルの[ツールボックス]→[エキスパート]タブで確認できます。USDについて2件のイベントが取得されていることが分かります。リクエスト時点では、これらのイベントはまだ発生しておらず(ニュースは未発表)、そのためMqlCalendarValue::actual_valueフィールドには値が入っていません。HasActualValue関数はfalseを返します。
これらのイベント種別では、HasForecastValue()で確認されるMqlCalendarValue::forecast_valueフィールドも存在しません(値を持ちません)。他のイベントでは、actual_value、forecast_value、prev_value、revised_prev_valueの4つすべてのフィールドが存在しない場合もあります。
Received events for USD:2件
✅ Event #0
Event ID: 840220005
Event name: 3-month treasury bills auction
Sector: 1
Source: https://home.treasury.gov/
Country name: USA
Country URL: united-states
Time: 2026.04.20 18:30:00
Impact: 0
Previous: 3.62
✅ Event #1
Event ID: 840220006
Event name: 6-month treasury bills auction
Sector: 1
Source: https://home.treasury.gov/
Country name: USA
Country URL: united-states
Time: 2026.04.20 18:30:00
Impact: 0
Previous: 3.61
提供コードの説明:
まず、指定した時間範囲内のすべてのイベントについて、USD通貨フィルタ付きで値の配列を取得します。次にループ処理で配列を走査し、各イベントのevent_id(MqlCalendarValue::event_id)をキーとしてCalendarEventById関数でイベント詳細を取得します。その後、MqlCalendarEvent::country_idを使いCalendarCountryById関数で国情報を取得します。
CalendarValueHistory ()関数がfalseを返しているにもかかわらずGetLastError()が0の場合、それは指定条件に該当するイベントが存在しないことを意味します。
カレンダー処理におけるエラー処理と制限
リモートデータを扱う際には、接続の中断やアクセス制限のリスクがあります。カレンダー関数は失敗時にfalseまたは0を返します。原因を特定するにはGetLastErrorを使用する必要があります。カレンダーモジュールには専用のエラーコード群が定義されています。
エラーコード:
- ERR_CALENDAR_TIMEOUT (5200):サーバー応答待機時間切れ。ネットワーク障害またはサーバー過負荷。対策:5〜10秒待って再試行。
- ERR_CALENDAR_NO_DATA (5201):カレンダーデータ未ロード。非同期初期化中。対策:1〜2秒待って再試行。
- ERR_CALENDAR_INVALID_DATE (5202):日付範囲不正(例:開始日 > 終了日)。対策:ロジック修正(再試行しても無意味)。
- ERR_CALENDAR_INVALID_COUNTRY (5203):国コード不正。パラメータにエラー。対策:コードを確認(たとえば、「USA」ではなく「US」)。
- ERR_CALENDAR_TOO_MANY_REQUESTS (5204):リクエスト過多。重大なエラー。対策:リクエスト間隔を増やす。
リクエスト制限について
MetaTrader 5ターミナルが接続するサーバーは過負荷防止のため制限を設けています。EAがOnTick()内で毎ティックCalendarValueHistory()を呼び出すような実装では、エラー5204が発生し、一時的にカレンダーアクセスがブロックされます。 推奨される方法は、OnInitで必要データを一度だけ読み込むことです。
リアルタイム更新にはCalendarValueLastを使用し、change_idを保持することで差分のみを取得できます。 更新はOnTimerで5〜10分間隔でおこなうのがベストプラクティスです。
カレンダーの読み込み時に発生する可能性のある問題への対処例を見てみましょう。
ターミナル(コールドスタート時)およびEAを起動した直後は、カレンダーはまだ利用可能な状態になっていません。データはバックグラウンドでサーバーから読み込まれます。そのため、OnInit()の最初の行でCalendarValueHistory関数を呼び出すと、高い確率でエラー5201(データなし)が発生します。この問題に対処するには、カレンダーの準備完了を確認するためのポーリング機構を実装する必要があります。
実践例:エラー処理を備えたカレンダーデータ読み込み関数
//+------------------------------------------------------------------+ //| Calendar loading function | //+------------------------------------------------------------------+ bool LoadCalendar(MqlCalendarValue& values[], const datetime from, const datetime to, const string country_code = NULL, const string currency = NULL, const int max_retries = 5) { int retry_count = 0; while(retry_count < max_retries) { ResetLastError(); //--- download attempt if(CalendarValueHistory(values, from, to, country_code, currency)) return true; // Success int error = GetLastError(); //--- in case of "No data" (5201) or "Timeout" (5200) error — wait and repeat if(error == 5201 || error == 5200) { retry_count++; Sleep(1000); // 1 second pause before repeating continue; } //--- if the error is critical (for example, an invalid date), interrupt the download immediately Print("❌ Critical Calendar Error: ", error); return false; } Print("❌ Failed to load calendar after ", max_retries, " attempts."); return false; } //+------------------------------------------------------------------+
提供コードの説明:
5201 (No data)および5200 (Timeout)エラーが発生する可能性を考慮してください。これらは回復可能なエラーです。そのため、1〜5秒間待機した後に再度データ取得を試みるように処理してください。一方で、回復不能なエラーが発生した場合は、ただちにダウンロード処理を中止する必要があります。回復不能なエラーには、無効な日付範囲、誤った通貨コードや国コードの指定などが含まれます。完全なスクリプトコードは、本記事に添付されているGetTodayEvents-S.mq5ファイルに掲載されています。
制限回避と応答速度の最大化のため、OnTimer内ではchange_idメカニズムを使用するべきです。起動時にはイベント履歴全体を読み込み、最後に取得したchange_idを保存しておきます。その後、タイマー処理ではCalendarValueLast関数を使用して新しいデータのみを取得します。サーバーは変更があったデータだけを返し、変更がない場合はfalseを返します。これにより、過去のデータを毎回転送する無駄がなくなり、通信量やサーバー負荷を削減できます。
実践例:エラー処理を備えたカレンダーデータ更新関数
//+------------------------------------------------------------------+ //| Timer: incremental update | //+------------------------------------------------------------------+ void OnTimer() { if(!is_initialized) return; MqlCalendarValue updates[]; ResetLastError(); //--- API automatically updates last_change_id by reference if(!CalendarValueLast(last_change_id, updates, (InpCountryCode == "") ? NULL : InpCountryCode, (InpCurrency == "") ? NULL : InpCurrency)) { int err = GetLastError(); // 0 = SUCCESS/NO_NEW_DATA, 5402 = ERR_CALENDAR_NO_CHANGES if(err != 0 && err != 5402) Print("🔴️ CalendarValueLast error: ", err); return; } int cnt = ArraySize(updates); if(cnt == 0) return; Print("🟢 Received ", cnt, " updates. New change_id: ", last_change_id); if(InpPrintChanges) ArrayPrint(updates); SyncCache(updates); } //+------------------------------------------------------------------+ //| Cache synchronization | //+------------------------------------------------------------------+ void SyncCache(const MqlCalendarValue &updates[]) { int upd_cnt = ArraySize(updates); int cache_sz = ArraySize(calendar_cache); for(int u = 0; u < upd_cnt; u++) { bool found = false; for(int c = 0; c < cache_sz; c++) { if(calendar_cache[c].id == updates[u].id) { calendar_cache[c] = updates[u]; found = true; break; } } if(!found) { ArrayResize(calendar_cache, cache_sz + 1); calendar_cache[cache_sz] = updates[u]; cache_sz++; total_events++; } } } //+------------------------------------------------------------------+
提供コードの説明:
イベントの増分更新は、OnTimer()タイマーのイベントハンドラによって実行されます。カレンダーAPIのCalendarValueLast()関数を呼び出し、新しいイベントが検出された場合は、事前に読み込まれているMqlCalendarValue構造体配列内の対応する要素を更新し、その内容をターミナルのジャーナルへ出力します。 更新対象のイベントが既存の配列内に見つからない場合は、そのイベントを新しい要素として配列へ追加します。
完全なスクリプトコードは、本記事に添付されているCalendarEventMonitor-EA.mq5に含まれています。
イベントのフィルタリング:全体から重要なものへ
なぜフィルタリングが必要なのか
経済指標カレンダーでは、1日に60〜90件ものイベントが公開されます。これは、市場への直接的な影響がほとんどない小規模な経済指標、祝日、要人発言などを含む大量の情報が絶えず流れている状態に似ています。それらすべてを取引対象にすると、過剰売買となり、資金を失う可能性が高くなります。ニュースを利用するアルゴリズムトレーダーの目的は、市場を本当に動かす3〜5件程度の重要イベントだけを抽出することです。
アルゴリズムニューストレードにおけるフィルタリングは、自動売買が利用可能なレベルまでシグナル対ノイズ比を高める作業に相当します。MQL5カレンダーAPIでは、この処理はMqlCalendarEventおよびMqlCalendarValue構造体配列に対して適用される多段階のフィルタリングシステムとして実装されます。
フィルタリング基準
市場が反応するのはニュースの発表そのものではなく、実際の結果、予想値、マクロ経済的な影響度の差異です。影響度の低いイベント(MqlCalendarValue::impact_type < 3)は、市場参加者やアルゴリズムによって無視されることが少なくありません。
フィルタリングをおこなわない場合、次のような問題が発生します。
- 二次的な統計指標による誤ったシグナル発生
- ボラティリティが低いにもかかわらずスプレッドだけが広い時間帯での取引
- 不要な判定処理によるEAロジックの肥大化
フィルタリングの目的は、データ量を約90%削減し、指定した通貨と時間帯において、ENUM_CALENDAR_EVENT_IMPORTANCE::CALENDAR_IMPORTANCE_HIGHに該当する高重要度イベントだけを残すことです。
ここでは、リアルタイム環境とストラテジーテスター環境の両方で同じように機能する、信頼性の高いフィルタを構築する方法を見ていきます。
多段階フィルタリングシステム
ターミナルからダウンロードした「生の」イベントデータは、未精製の鉱石のようなものです。その中には、ほとんど意味のない統計データ、市場が休場となる祝日情報、実際には取引判断に利用できないイベントなど、多数の不要情報が含まれています。
この大量のデータから有効なシグナルだけを抽出するには、多段階フィルタリングシステムが必要です。イベントフィルタは「ふるい」として機能し、生データを通過させながら、通貨、重要度、イベントコード、時間帯などの条件に一致するイベントだけを出力します。
効率的なフィルタは階層構造を持つべきです。まず地域や通貨による大まかな絞り込みをおこない、その後で時間帯などの詳細条件による微調整をおこないます。
ニュースMQL5 APIの重要な特徴として、MqlCalendarValue構造体にはcurrency_idフィールドおよびcountry_idフィールドが存在しません。この構造体に格納されるのは、event_id、time、指標値、および影響度の種類のみです。これはフィルタリングにどのような影響を与えるのでしょうか。
通貨によるフィルタリングはAPIリクエストの段階で実行されます。CalendarValueHistory(..., NULL, "USD")を呼び出すと、ターミナル自体がUSD以外のイベントを除外します。受け取る配列は、すでにUSDによってフィルタリングされた状態になっています。したがって、リアルタイム処理においてcurrency_idを確認する必要はありません。時間、重要度、および予想値の有無や重要性によるフィルタリングだけで十分です。
レベル1:通貨フィルタ
最初の障壁はイベントの地理的対象です。EURUSDを取引している場合、EAはユーロ圏(EUR)および米国(USD)に影響を与えるイベントのみに反応すべきです。フィルタリングは、取引対象銘柄の通貨コードを指定してイベントを取得することで実現されます。クロス通貨ペア(たとえば「GBPJPY」)の場合は、両方の通貨(「GBP」と「JPY」)に加え、「USD」のイベントも取得する必要があります。
レベル2:イベントの重要性
すべてのニュースは、経済、ひいては価格への影響度という点で重要性が異なります。たとえば、消費者物価指数(CPI)は市場を100ポイント動かすことがありますが、ZEW景況感指数は10ポイント程度しか動かさない場合があります。
MQL5 APIでは、重要度のレベルをENUM_CALENDAR_EVENT_IMPORTANCE列挙型によって表します。
enum ENUM_CALENDAR_EVENT_IMPORTANCE { CALENDAR_IMPORTANCE_NONE, // importance level is not set CALENDAR_IMPORTANCE_LOW, // low importance CALENDAR_IMPORTANCE_MODERATE, // medium importance CALENDAR_IMPORTANCE_HIGH // high importance };
ニュースEAは、流動性の低いニュースによる誤検出を避けるため、HIGH未満の重要度のイベントはすべて無視すべきです。
レベル3:イベントコード
重要度の高いイベントの中にも例外があります。たとえば、ECBやIMFの要人発言は重要イベントとして分類されることがありますが、具体的な経済指標の発表と比較すると、アルゴリズムによる処理はより困難です。そのため、イベントコードによる選別が必要になります。MqlCalendarEvent::event_codeフィールドには、一意の識別子(たとえば「NONFARM」、「CPI」、「GDP)が格納されています。私たちは、アンケート調査や要人発言ではなく、経済指標の数値発表に該当するイベントのみを選択します。
最初の3段階のチェックをすべて通過したイベントだけが、結果配列へコピーされ、実際の取引に利用されます。

図4:ニューストレード成功の鍵となる多段階フィルタリング
最初の3段階のフィルタリングを通過した後、EAはそのイベントが設定された時間ウィンドウ内に入っているかどうかを確認します。この処理は、EAの実行中におこなわれます。
レベル4:時間ウィンドウ
最終段階は関連性の判定です。1か月後に発表されるニュースは現時点では必要ありません。また、昨日発表されたニュースはすでに市場に織り込まれている可能性があります。そのため、イベントは、TimeCurrent()が「ニュース発表前の指定時間」と「ニュース発表後の指定時間」の範囲内にある場合にアクティブとみなされます。一般的には、発表の15~30分前にポジションを決済し、発表後60分にボラティリティおよび価格変動の方向性を分析するような設定が使用されます。
MQL5バイナリリソース:効率的なキャッシュのためにイベントをオフラインテストに移行する
ニュースベースのアルゴリズム取引をテストする際の問題の一つは、ストラテジーテスターがインターネットへアクセスできないことです。この制約により、テスト速度が向上し、非決定的な要因の影響を排除できますが、一方でニュースベースの戦略をテストすることが難しくなります。
EAがテスター内でCalendarValueHistory()関数を呼び出すと、エラーが返され、結果配列は空になります。そのため、戦略をテストするには、過去のニュースデータを.ex5実行ファイル内に埋め込む必要があります。そこで利用するのがMQL5バイナリリソースです。なぜこの方法を選ぶのでしょうか。それは、構造化データへアクセスする最も高速な方法だからです。アクセス速度は実質的にコンピュータのファイルシステム性能によってのみ制限されます。
ここでは、MqlCalendarValue []配列をコンパクトなバイナリファイルへ変換し、それをコードへ埋め込む方法を見ていきます。これにより、テスターはRAM上のデータへ極めて高速にアクセスできるようになります。最初のステップとして、現在の経済指標カレンダーデータをダウンロードし、バイナリファイルとして保存するエクスポート用スクリプトを作成します。
実践例:イベントをバイナリファイルへエクスポートするスクリプト
スクリプトの完全なコードは、記事に添付されているExportCalendarForTester.mq5ファイルに含まれています。
ステップ1:配列の収集とフィルタリング
リソース内には世界中のすべてのイベントを保存するわけではありません。バイナリファイルへエクスポートする段階で、EAと同じく、通貨、重要度、イベントタイプのフィルタを適用します。スクリプトの入力パラメータでは、対象通貨リスト、イベント取得期間、イベントコード、最低重要度のリストを指定します。
InpEventCodesパラメータに指定するイベントコード一覧は、デフォルトでは空になっています。この場合、すべてのイベントタイプが読み込まれます。フィルタを絞り込むことも可能です。たとえば、米国雇用統計(Non-Farm Payrolls)のみを対象とする場合は、NONFARMを指定します。
InpUseCommonDirフラグは、バイナリファイルの保存先を指定します。trueを設定すると、バイナリファイルはすべてのクライアントターミナルで共有される\Terminal\Common\Filesフォルダに保存されます。
//--- input parameters input string InpCurrencies = "USD"; input datetime InpDateFrom = D'2025.01.01'; input datetime InpDateTo = 0; input string InpEventCodes = ""; input ENUM_CALENDAR_EVENT_IMPORTANCE InpMinImportance = CALENDAR_IMPORTANCE_HIGH; input string InpOutputFile = "calendar_test_res.bin"; input bool InpUseCommonDir = true;
入力パラメータで指定された値を用いてイベントを読み込み、フィルタリングするスクリプトの該当部分は以下のとおりです。
Print("🔄 Calendar export:"); Print("----------------------------------"); Print("Filtering options:"); Print(" 🟨 Interval = ", InpDateFrom, " — ", InpDateTo); Print(" 🟨 Currencies = ", InpCurrencies, "\n 🟨 Event Codes = ", InpEventCodes, "\n 🟨 Min Importance = ", EnumToString(InpMinImportance)); Print("----------------------------------"); //--- initialize filter by currencies ArrayResize(currencies, 0); if(InpCurrencies == "") return; StringSplit(InpCurrencies, ',', currencies); currencies_size = ArraySize(currencies); for(int i = 0; i < currencies_size; i++) StringToUpper(currencies[i]); //--- initialize the filter by event codes ArrayResize(event_codes, 0); if(InpEventCodes != "") StringSplit(InpEventCodes, ',', event_codes); event_codes_Size = ArraySize(event_codes); //--- load calendar values with the CURRENCY FILTER (if specified) if(currencies_size > 0) { ArrayResize(values_size, currencies_size); ArrayFill(values_size, 0, currencies_size, 0); for(int i = 0; i < currencies_size; i++) { if(LoadCalendar(raw_values, InpDateFrom, InpDateTo, "", currencies[i])) { raw_values_size = ArraySize(raw_values); //--- load events with an IMPORTANCE AND EVENT CODE FILTER for(int k = 0; k < raw_values_size; k++) { //--- get event description if(CalendarEventById(raw_values[k].event_id, event)) { //--- take indicators only if(event.type != CALENDAR_TYPE_INDICATOR) continue; // nothing else to filter //--- check by IMPORTANCE if(event.importance < InpMinImportance) continue; // nothing else to filter //--- check by EVENT CODE (if specified) if(event_codes_Size > 0) { bool code_allowed = false; for(int c = 0; c < event_codes_Size; c++) { StringToUpper(event.event_code); if(StringFind(event.event_code, event_codes[c]) >= 0) { code_allowed = true; break; } } if(code_allowed == false) continue; } //--- replenish the array of filtered events int event_index = ArraySize(values); ArrayResize(values, event_index + 1); values[event_index] = raw_values[k]; values_size[i]++; } } Print("✅ Received values BY CURRENCY \"", currencies[i], "\": ", raw_values_size, " → Of these, filtered: ", values_size[i]); } else { int error = GetLastError(); if(error == 0) Print("⚠ LoadCalendar Info: No Events for ", currencies[i]); else Print("❌ LoadCalendar Error: ", error, " for ", currencies[i]); return; // nothing else to filter } } }
提供コードの説明:
- values_size []配列は、入力で指定された各通貨コードごとにフィルタリングされたイベント数を格納します。
- MQL5のdatetime型におけるゼロ値は、1970.01.01 00:00:00を表します。
- さらにイベントタイプによる追加フィルタリングがおこなわれます。ENUM_CALENDAR_EVENT_TYPE::CALENDAR_TYPE_INDICATOR型のイベントのみが処理対象となります。これらは経済指標の発表に該当するものであり、スピーチなどの定性的なニュースではありません。それ以外のニュースはすべて除外されます。この処理はフィルタリングの最初の段階におけるバリデーションでおこなわれます。
//--- take only indicators if(event.type != CALENDAR_TYPE_INDICATOR) continue; // Nothing else to filter
それでは、異なるフィルタリングパラメータの組み合わせでスクリプトを実行し、イベントの読み込みおよびフィルタリング処理がどの程度高速に動作するかを確認します。処理速度はコンピュータの性能や、インターネット経由でデータサーバーへ接続する速度に依存することは明らかです。それにもかかわらず、ストレステストを実施します。結果は以下の通りです。
利用可能なすべての履歴にわたる、高重要度のUSDイベント全件:
13:59:43.724 🔄 Calendar export:
13:59:43.724 ----------------------------------
13:59:43.724 Filtering options:
13:59:43.724 🟨 Interval = 1970.01.01 00:00:00 — 1970.01.01 00:00:00
13:59:43.724 🟨 Currencies = USD
13:59:43.724 🟨 Event Codes =
13:59:43.724 🟨 Min Importance = CALENDAR_IMPORTANCE_HIGH
13:59:43.724 ----------------------------------
13:59:45.511 ✅ Received values BY CURRENCY "USD":53346 → Of these, filtered:9009
13:59:45.511 ----------------------------------
13:59:45.513 ✅ Saved:USD_calendar_test_res.bin Size:1153152 bytes (9009 events)
フィルタリングされた値配列(MqlCalendarValue構造体)の取得時間は1.80 秒(1フィルタあたり0.20ms)でした。
利用可能なすべての履歴にわたる、高重要度のUSD、EUR、JPYイベント全件:
14:03:08.724 🔄 Calendar export:
14:03:08.724 ----------------------------------
14:03:08.724 Filtering options:
14:03:08.724 🟨 Interval = 1970.01.01 00:00:00 — 1970.01.01 00:00:00
14:03:08.724 🟨 Currencies = USD,EUR,JPY
14:03:08.724 🟨 Event Codes =
14:03:08.724 🟨 Min Importance = CALENDAR_IMPORTANCE_HIGH
14:03:08.724 ----------------------------------
14:03:10.488 ✅ Received values BY CURRENCY "USD":53346 → Of these, filtered:9009
14:03:10.947 ✅ Received values BY CURRENCY "EUR":45139 → Of these, filtered:1102
14:03:11.404 ✅ Received values BY CURRENCY "JPY":18907 → Of these, filtered:937
14:03:11.404 ----------------------------------
14:03:11.406 ✅ Saved:USD_calendar_test_res.bin Size:1153152 bytes (9009 events)
14:03:11.407 ✅ Saved:EUR_calendar_test_res.bin Size:141056 bytes (1102 events)
14:03:11.408 ✅ Saved:JPY_calendar_test_res.bin Size:119936 bytes (937 events)
フィルタリングされた値配列(MqlCalendarValue構造体)の取得時間は2.68秒(1フィルタあたり0.24ms)でした。
USDにおける高重要度のNFPイベント(「非農業部門雇用者数」)について、利用可能なすべての履歴にわたり抽出した結果は以下のとおりです。
14:07:22.203 ----------------------------------
14:07:22.203 Filtering options:
14:07:22.203 🟨 Interval = 1970.01.01 00:00:00 — 1970.01.01 00:00:00
14:07:22.203 🟨 Currencies = USD
14:07:22.203 🟨 Event Codes = NONFARM
14:07:22.203 🟨 Min Importance = CALENDAR_IMPORTANCE_HIGH
14:07:22.203 ----------------------------------
14:07:22.290 ✅ Received values BY CURRENCY "USD":53346 → Of these, filtered:473
14:07:22.290 ----------------------------------
14:07:22.291 ✅ Saved:USD_calendar_test_res.bin Size:60544 bytes (473 events)
フィルタリングされた値配列(MqlCalendarValue構造体)の取得時間は0.09秒(1フィルタあたり0.18ms)でした。
備考:
- フィルタにおいてイベントコード全体を指定する必要はありません。コード文字列の中に含まれる一意な部分を指定するだけで十分です。たとえば「非農業部門雇用者数(non-farms)」の場合、完全なコードは「NONFARM-PAYROLLS」ですが、「NONFARM」と指定するだけで条件として成立します。
- MetaTrader 5ターミナル上でリアルタイムトレードシステムを実行する場合、全期間にわたるすべてのイベントをダウンロードする必要はほとんどありません。通常は当日および翌週程度の直近イベントのみを取得します。そのため、ニュースEAアルゴリズムにおける読み込み時間は実質的に無視できるレベルになります。
ステップ2:イベント配列をバイナリファイルにエクスポートする
SaveToBinary関数は、フィルタリングされたイベントをバイナリファイルに保存します。生成されるファイル数は入力で指定された通貨コードの数と同じになり、各通貨ごとに個別のバイナリファイルへエクスポートされます。ファイル名は通貨コードの接頭辞から始まり、たとえば「USD_calendar_test_res.bin」のようになります。このようにして、通貨コードとそのイベントリストとの間に1対1の対応関係が得られます。
//+------------------------------------------------------------------+ //| Save an array to a binary file | //+------------------------------------------------------------------+ void SaveToBinary(MqlCalendarValue &values[], const int &vls_size[], const string filename, const string ¤cies[]) { if(ArraySize(values) == 0) { Print("⚠️ Nothing to save"); return; } Print("----------------------------------"); int offset = 0; for(int i = 0; i < ArraySize(vls_size); i++) { int file_handle = FileOpen(currencies[i] + "_" + filename, FILE_WRITE | FILE_BIN | (InpUseCommonDir ? FILE_COMMON : 0)); if(file_handle == INVALID_HANDLE) { Print("❌ FileSave failed: ", GetLastError()); return; } FileWriteArray(file_handle, values, offset, vls_size[i]); FileFlush(file_handle); FileClose(file_handle); Print("✅ Saved: ", currencies[i] + "_" + filename, " Size: ", vls_size[i] * sizeof(MqlCalendarValue), " bytes", " (", vls_size[i], " events)"); offset += vls_size[i]; } }
イナリファイルへイベントのエクスポートスクリプトを正常に実行すると、MetaTrader 5の[エキスパート]タブには次のようなメッセージが表示されます。
14:28:42.077 ----------------------------------
14:28:42.078 ✅ Saved:USD_calendar_test_res.bin Size:58240 bytes (455 events)
14:28:42.079 ✅ Saved:EUR_calendar_test_res.bin Size:7680 bytes (60 events)
ステップ3:リソースをEAにコンパイルする
MQL5/Files/にExportCalendarForTester.mq5スクリプトを実行した後、USD_calendar_test_res.binおよびEUR_calendar_test_res.binといったファイルが生成されます。次に、これらのファイルをEAに「埋め込み」ます。EAファイルの先頭(#propertyディレクティブの後)に、使用する通貨が1つの場合は1行、複数通貨の場合は複数行、以下の形式を追加します。
// Embed the binary file as a static resource at the compilation stage #resource "\\Files\\USD_calendar_test_res.bin" as MqlCalendarValue USD_res_calendar_data[] #resource "\\Files\\EUR_calendar_test_res.bin" as MqlCalendarValue EUR_res_calendar_data[]
注記:
- #resourceのパスはMQL5/Files/フォルダを基準として指定されます。\\Files\\接頭辞は必須です。
- EAをコンパイルすると、MetaEditorの[エラー]タブには次のようなメッセージが表示されます。
- 'USD_calendar_test_res.bin' as 'const MqlCalendarValue USD_res_calendar_data[455]'
- 'EUR_calendar_test_res.bin' as 'const MqlCalendarValue EUR_res_calendar_data[60]'
EA起動時には、エクスポート段階で保存されたイベント構造体が、それぞれUSD_res_calendar_data[]およびEUR_res_calendar_data配列に格納されます。
EAへの統合:自動モード切り替え
目的は、同一のEAコードをリアルタイム環境とストラテジーテスターの両方で動作させ、データソースを自動的に選択できるようにすることです。MQLInfoInteger (MQL_TESTER) 関数は、EAがストラテジーテスターまたは最適化環境で動作している場合にtrueを返します。
//+------------------------------------------------------------------+ //| Initialization: select a data source | //+------------------------------------------------------------------+ int OnInit() { Print("🔄 Initializing the news module:"); //--- initialize filter by currencies ArrayResize(currencies, 0); if(InpCurrencies == "") return INIT_FAILED; StringSplit(InpCurrencies, ',', currencies); currencies_size = ArraySize(currencies); for(int i = 0; i < currencies_size; i++) StringToUpper(currencies[i]); //--- initialize the filter by event codes ArrayResize(event_codes, 0); if(InpEventCodes != "") StringSplit(InpEventCodes, ',', event_codes); event_codes_Size = ArraySize(event_codes); //--- define the execution environment bool is_tester = MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_OPTIMIZATION) || MQLInfoInteger(MQL_VISUAL_MODE); if(is_tester) { //--- TESTER MODE: Load from resource if(!LoadFromResource()) { Print("❌ ERROR: Failed to load calendar from resource"); return INIT_FAILED; } is_live_mode = false; Print("⚠️ Mode: TESTER (data from resource)"); } else { //--- LIVE MODE: Load from API if(!LoadFromCalendarAPI()) { int error = GetLastError(); Print("❌ ERROR: Failed to load calendar from API - ", error); return INIT_FAILED; } is_live_mode = true; Print("⚠️ Mode: LIVE (data from API)"); } return INIT_SUCCEEDED; }
提供コードの説明:
主なチェックは、OnInit () EA初期化イベントハンドラでおこなわれます。
//--- define the execution environment bool is_tester = MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_OPTIMIZATION) || MQLInfoInteger(MQL_VISUAL_MODE); if(is_tester) { //--- TESTER MODE: Load from resource if(!LoadFromResource()) { ... } } else { //--- LIVE MODE: Load from API if(!LoadFromCalendarAPI()) { ... } }
提供コードの説明:
実行中のMQL5プログラム(ここではEA)がどのモードで動作しているかを判定します。ここで注目するフラグは以下のとおりです。
- MQL_TESTER:ストラテジーテスター上でプログラムが実行されていることを示すフラグ
- MQL_OPTIMIZATION:最適化処理中にプログラムが実行されていることを示すフラグ
- MQL_VISUAL_MODE:ビジュアルテストモードでプログラムが実行されていることを示すフラグ
14:42:18.894 🔄 Initializing news module:
14:42:18.905 ✅ Events from Server Loaded:455 events for:USD
14:42:18.914 ✅ Events from Server Loaded:60 events for:EUR
14:42:18.914 ⚠️ Mode:LIVE (data from API)
データ変換チェーンの検証:イベント → バイナリファイル → リソースにコンパイル → リソースからイベントを取得
残る作業は、ストラテジーテスター環境においてイベントが正しく取得されていることを確認し、リアルタイムで取得されるリストと完全に一致することを検証することです。これにより、ニューストレードアルゴリズムの自動テストおよび最適化が可能であることが確認できます。
そのために、サーバーから取得したイベントおよびリソースから読み込んだイベントの内容を、ターミナル/ストラテジーテスターのログへ出力するようにします。完全なImportTesterLog.txtファイル(テスター実行結果を含む)が添付されています。以下はそのログの一部であり、各通貨コードごとのイベント配列の先頭と末尾を示しています。
ストラテジーテスターのビジュアルモードでの単一実行結果:
16:48:22.527 InpCurrencies=USD,EUR
16:48:22.527 InpDateFrom=1735689600
16:48:22.527 InpDateTo=1767225600
16:48:22.527 InpEventCodes=
16:48:22.527 InpMinImportance=3
16:48:22.546 🔄 Initializing news module:
16:48:22.546 ✅ Events from Resource Loaded:455 events for:USD
16:48:22.546 ✅ Event #0
16:48:22.546 ID: 230215
16:48:22.546 Event ID: 840140001
16:48:22.546 Time: 2025.01.02 16:30:00
16:48:22.546 Impact: CALENDAR_IMPACT_POSITIVE
16:48:22.546 Revision: 0
16:48:22.546 Actual: 211.0
16:48:22.546 Revised: 220.0
16:48:22.546 Forecast: 219.0
16:48:22.546 Previous: 219.0
16:48:22.546 ✅ Event #1
16:48:22.546 ID: 230641
16:48:22.546 Event ID: 840500001
16:48:22.546 Time: 2025.01.02 17:45:00
16:48:22.546 Impact: CALENDAR_IMPACT_NEGATIVE
16:48:22.546 Revision: 3
16:48:22.546 Actual: 49.4
16:48:22.546 Forecast: 50.5
16:48:22.546 Previous: 49.7
...
16:48:22.553 ✅ Event #453
16:48:22.553 ID: 274419
16:48:22.553 Event ID: 840510001
16:48:22.553 Time: 2025.12.30 17:45:00
16:48:22.553 Impact: CALENDAR_IMPACT_POSITIVE
16:48:22.553 Revision: 0
16:48:22.553 Actual: 43.5
16:48:22.553 Forecast: 42.4
16:48:22.553 Previous: 36.3
16:48:22.553 ✅ Event #454
16:48:22.553 ID: 274484
16:48:22.553 Event ID: 840140001
16:48:22.553 Time: 2025.12.31 16:30:00
16:48:22.553 Impact: CALENDAR_IMPACT_POSITIVE
16:48:22.553 Revision: 0
16:48:22.553 Actual: 199.0
16:48:22.553 Revised: 215.0
16:48:22.553 Forecast: 227.0
16:48:22.553 Previous: 214.0
...
16:48:22.553 ✅ Events from Resource Loaded:60 events for:EUR
16:48:22.553 ✅ Event #0
16:48:22.553 ID: 231322
16:48:22.553 Event ID: 999030013
16:48:22.553 Time: 2025.01.07 13:00:00
16:48:22.553 Impact: CALENDAR_IMPACT_NA
16:48:22.553 Revision: 1
16:48:22.553 Actual: 2.4
16:48:22.553 Forecast: 2.4
16:48:22.553 Previous: 2.2
16:48:22.553 ✅ Event #1
16:48:22.553 ID: 187384
16:48:22.553 Event ID: 999040007
16:48:22.553 Time: 2025.01.08 13:00:00
16:48:22.553 Impact: CALENDAR_IMPACT_POSITIVE
16:48:22.553 Revision: 0
16:48:22.553 Actual: 21.0
16:48:22.553 Revised: 17.8
16:48:22.553 Forecast: 15.7
16:48:22.553 Previous: 17.7
...
16:48:22.555 ✅ Event #58
16:48:22.555 ID: 204746
16:48:22.555 Event ID: 999010006
16:48:22.555 Time: 2025.12.18 16:15:00
16:48:22.555 Impact: CALENDAR_IMPACT_NA
16:48:22.555 Revision: 0
16:48:22.555 Actual: 2.0
16:48:22.555 Previous: 2.0
16:48:22.555 ✅ Event #59
16:48:22.555 ID: 204762
16:48:22.555 Event ID: 999010007
16:48:22.555 Time: 2025.12.18 16:15:00
16:48:22.555 Impact: CALENDAR_IMPACT_NA
16:48:22.555 Revision: 0
16:48:22.555 Actual: 2.15
16:48:22.555 Previous: 2.15
16:48:22.555 ⚠️ Mode:TESTER (data from resource)
リソースから読み込まれたイベント配列は約10msで完全にロードされることが確認できます。これは予想通りの結果です。リソースはEAのコードに組み込まれており、テスト開始時に同時に読み込まれるためです。
同じEAの実行(起動)をMetaTrader 5ターミナルのライブモードでおこなった場合にも、同様にイベント一覧を含むログファイルが生成されます。完全なLoadLiveLog.txtにはライブ取得結果が記録されています。以下はそのログの一部であり、各通貨コードごとのイベント配列の先頭と末尾を示しています。
サーバーからのライブイベント取得結果:
17:27:25.250 🔄 Initializing news module:
17:27:25.258 ✅ Received values BY CURRENCY "USD":3589 → Of these, filtered:455
17:27:25.258 ✅ Events from Server Loaded:455 events for:USD
17:27:25.258 ✅ Event #0
17:27:25.258 ID: 230215
17:27:25.258 Event ID: 840140001
17:27:25.258 Time: 2025.01.02 16:30:00
17:27:25.258 Impact: CALENDAR_IMPACT_POSITIVE
17:27:25.258 Revision: 0
17:27:25.258 Actual: 211.0
17:27:25.258 Revised: 220.0
17:27:25.258 Forecast: 219.0
17:27:25.258 Previous: 219.0
17:27:25.258 ✅ Event #1
17:27:25.258 ID: 230641
17:27:25.258 Event ID: 840500001
17:27:25.258 Time: 2025.01.02 17:45:00
17:27:25.258 Impact: CALENDAR_IMPACT_NEGATIVE
17:27:25.258 Revision: 3
17:27:25.258 Actual: 49.4
17:27:25.258 Forecast: 50.5
17:27:25.258 Previous: 49.7
...
17:27:25.288 ✅ Event #453
17:27:25.288 ID: 274419
17:27:25.288 Event ID: 840510001
17:27:25.288 Time: 2025.12.30 17:45:00
17:27:25.288 Impact: CALENDAR_IMPACT_POSITIVE
17:27:25.288 Revision: 0
17:27:25.288 Actual: 43.5
17:27:25.288 Forecast: 42.4
17:27:25.288 Previous: 36.3
17:27:25.288 ✅ Event #454
17:27:25.288 ID: 274484
17:27:25.288 Event ID: 840140001
17:27:25.288 Time: 2025.12.31 16:30:00
17:27:25.288 Impact: CALENDAR_IMPACT_POSITIVE
17:27:25.288 Revision: 0
17:27:25.288 Actual: 199.0
17:27:25.288 Revised: 215.0
17:27:25.288 Forecast: 227.0
17:27:25.288 Previous: 214.0
17:27:25.301 ✅ Received values BY CURRENCY "EUR":3116 → Of these, filtered:0
17:27:25.301 ✅ Events from Server Loaded:60 events for:EUR
17:27:25.301 ✅ Event #0
17:27:25.301 ID: 231322
17:27:25.301 Event ID: 999030013
17:27:25.301 Time: 2025.01.07 13:00:00
17:27:25.301 Impact: CALENDAR_IMPACT_NA
17:27:25.301 Revision: 1
17:27:25.301 Actual: 2.4
17:27:25.301 Forecast: 2.4
17:27:25.301 Previous: 2.2
17:27:25.301 ✅ Event #1
17:27:25.301 ID: 187384
17:27:25.301 Event ID: 999040007
17:27:25.301 Time: 2025.01.08 13:00:00
17:27:25.301 Impact: CALENDAR_IMPACT_POSITIVE
17:27:25.301 Revision: 0
17:27:25.301 Actual: 21.0
17:27:25.301 Revised: 17.8
17:27:25.301 Forecast: 15.7
17:27:25.301 Previous: 17.7
...
17:27:25.303 ✅ Event #58
17:27:25.303 ID: 204746
17:27:25.303 Event ID: 999010006
17:27:25.303 Time: 2025.12.18 16:15:00
17:27:25.303 Impact: CALENDAR_IMPACT_NA
17:27:25.303 Revision: 0
17:27:25.303 Actual: 2.0
17:27:25.303 Previous: 2.0
17:27:25.303 ✅ Event #59
17:27:25.303 ID: 204762
17:27:25.303 Event ID: 999010007
17:27:25.303 Time: 2025.12.18 16:15:00
17:27:25.303 Impact: CALENDAR_IMPACT_NA
17:27:25.303 Revision: 0
17:27:25.303 Actual: 2.15
17:27:25.303 Previous: 2.15
17:27:25.303 ⚠️ Mode:LIVE (data from API)
フィルタリングとログ出力を含むフルダウンロード処理は約50msで完了します。それでも依然として十分に高速であり、イベントのダウンロードおよびフィルタリング時間は無視できるレベルです。両方のログファイルを比較すると、イベント数および各フィールドの値の両方が完全に一致していることが確認できます。これにより、データ変換チェーンの検証は正常に完了しました。
典型的な間違いと誤った期待の分析
MQL5の経済指標カレンダー統合において、開発者がよく犯す6つの典型的なミスを見ていきます。これらはすべて実運用で検証されており、場合によっては資金損失につながる可能性があります。
1. 「カレンダーは価格変動を予測する」
誤った期待:
高重要度のニュースが発表され、実績値が予想値から大きく乖離した場合、価格は必ずその乖離方向に動く。たとえば「実績 > 予想」であれば単純に買うべきだ、という考え方です。
現実:
経済指標カレンダーは売買シグナル生成器ではなく、データソースにすぎません。カレンダーは「何が起きたか」を示すだけであり、「市場がどう反応するか」を示すものではありません。「直感的に正しい」と思われる反応と異なる動きが発生する理由は以下の通りです。
- 市場は発表数日前から期待値を織り込み済みであり、発表時点では「材料出尽くし」が発生することがある
- インフレ指標は、金融政策が引き締め局面か緩和局面かによって、同じ結果でも通貨高・通貨安のどちらにも作用し得る
- 良好な指標であっても、前回値が下方修正された場合、全体としてはネガティブに解釈されることがある
- 複数通貨・複数指標が同時に発表されることでクロス影響が発生し、単純な一方向解釈が不可能になる
したがって、正しいアプローチはエントリーシグナルとしてではなく、ボラティリティフィルタとしてカレンダーを利用することです。
//--- instead of if(actual > forecast) OrderSend(...); //--- use: if(IsHighImpactNewsComingSoon(30)) { //--- reduce the position size or temporarily suspend trading ReduceRiskExposure(); }
2. 「すべてのHIGHイベントは同じ重要度である」
誤った期待:
CALENDAR_IMPORTANCE_HIGHは、すべてのイベントが市場を50pips以上動かすことを保証する指標であり、すべての高重要度ニュースを同一アルゴリズムで取引できる、という考え方です。
現実:
ENUM_CALENDAR_EVENT_IMPORTANCEにおける重要度の値は、あくまでカレンダー編集者による定性的な評価であり、実際の市場インパクトを定量的に保証するものではありません。たとえば、どちらもHIGHと分類されるイベントであっても、市場への影響度は大きく異なります。
一つ目の例として、米国の非農業部門雇用者数は、雇用状況を示す重要指標であり、FRBの政策判断にも直接影響するため、80〜150ポイント程度の大きなボラティリティ反応を示します。一方で、ユーロ圏の製造業PMIは、特定セクターに限定された指標であり、ECB政策への影響も相対的に小さいため、10〜30ポイント程度の反応にとどまることが多いです。
したがって、正しいアプローチは重要度フィルタだけに依存するのではなく、イベントコードの優先リストを併用することです。
//--- a list of events that are really worth reacting to bool IsGoodEvent(const string event_code) { static const string tier1_codes[] = { "NONFARM", "CPI", "GDP", "RATE", "FOMC", "ECB_RATE", "RETAIL_SALES", "UNEMPLOYMENT", "PMI_MANUFACTURING" }; for(int i = 0; i < ArraySize(tier1_codes); i++) if(StringFind(event_code, tier1_codes[i]) != -1) return true; return false; } //--- use in filter if(event.importance == CALENDAR_IMPORTANCE_HIGH && IsGoodEvent(event.event_code)) { //--- handle only truly significant news }
推奨事項:
過去のボラティリティ分析に基づいて独自のイベントランキングを作成することが最も信頼性が高く、プリセットのラベルよりも実用的です。
3. TimeLocal() を TimeTradeServer() の代わりに使用する
誤った期待:
MqlCalendarValue::time 構造体内に格納されるイベント時刻はローカルタイム(またはUTC)であり、そのためTimeLocal()やTimeGMT()と比較すればよい、という考え方です。
現実:
経済指標カレンダーのすべての時間データは、取引サーバーの時間帯 (TimeTradeServer())に基づいて返されます。これは設計上の仕様であり、追加の手動変換を不要にする一方で、開発者側には一貫した時間管理を要求します。
この誤りの影響は重大です。たとえばサーバーがEET (UTC+2)で動作しているのに対し、開発環境でTimeLocal()(たとえばMSK、UTC+3)を使用した場合、常に1時間のズレが発生します。その結果、EAはニュースを見逃したり、逆に発表後に反応してしまう可能性があります。
したがって、正しいアプローチはすべての時間比較においてTimeTradeServer()を使用することです。
//--- WRONG — risk of desynchronization datetime now = TimeLocal(); if(event.time - now < 1800) { ... } //--- RIGHT — guaranteed synchronization datetime now = TimeTradeServer(); if(event.time - now < 1800) { ... } //--- in the tester, TimeTradeServer() returns the model time, so the logic works identically to live
推奨事項:
デバッグログにTimeToString(TimeTradeServer(), TIME_MINUTES)の出力を追加し、イベント時刻と比較してください。追加の変換なしで一致するはずです。
4. 「イベントフィールドが空になる可能性を忘れる」
誤った期待:
actual_valueやforecast_valueなどの数値フィールドには常に正しい数値が格納されており、それらをそのまま使用したり、単純に1,000,000で割れば安全に比較できるという考え方です。
現実:
ドキュメントによると、MqlCalendarValue構造体の数値フィールドは実際の値が1,000,000倍された形で格納されているか、値が存在しない場合はLONG_MINが格納されます。このチェックを無視した場合に起こる問題は以下のとおりです。
//--- error double actual = values[i].actual_value / 1000000.0; // if actual_value == LONG_MIN, result: -9223372036.854776 if(actual > forecast) // comparison with a "garbage" number causes a false alarm OpenBuy();
正しいアプローチは、MqlCalendarValue構造体に用意されている組み込みメソッドを使用して、値を安全に取得することです。
//--- the right approach is built-in methods if(values[i].HasActualValue() && values[i].HasForecastValue()) { double actual = values[i].GetActualValue(); double forecast = values[i].GetForecastValue(); if(!MathIsNaN(actual) && !MathIsNaN(forecast)) { double deviation = actual - forecast; // ... analysis logic } } //+------------------------------------------------------------------+
注記:
GetActualValue()、GetForecastValue()などの関数は、データが存在しない場合にはNaNを返します。そのため、計算に使用する前に必ずMathIsValidNumber()またはMathIsNaN()によって結果をチェックする必要があります。
5. 「OnTick()内でCalendar API関数を呼び出す」
誤った期待:
最新データを常に取得するために、毎ティックCalendarValueHistory()を呼び出せば、重要なニュースを見逃さないという考え方です。
現実:
カレンダーAPIはリモートサーバーと通信する仕組みであり、MetaTrader 5には厳格なリクエスト制限が存在します。そのため、OnTick()(1秒間に数十回発火する可能性がある)内で呼び出すと、以下の問題が発生します。
- エラー5204(ERR_CALENDAR_TOO_MANY_REQUESTS)により、一時的にカレンダーアクセスがブロックされる
- ネットワークリクエストが取引ループに混入し、実行遅延やスリッページ増加を引き起こす
- 同じデータを繰り返し取得することで不要な通信負荷が発生する
//--- global variables MqlCalendarValue calendar_cache[]; bool cache_initialized = false; long last_change_id = 0; //+------------------------------------------------------------------+ int OnInit() { //--- initial history loading (once at startup) datetime from = TimeCurrent() - 7 * 86400; datetime to = TimeCurrent() + 30 * 86400; if(CalendarValueHistory(calendar_cache, from, to, "USD")) { cache_initialized = true; } //--- set a timer for periodic updates (no more than 5-10 minutes) EventSetTimer(300); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnTimer() { if(!cache_initialized) return; MqlCalendarValue updates[]; //--- only request changes since the last update if(CalendarValueLast(last_change_id, updates, "USD") > 0) { if(ArraySize(updates) > 0) { //--- merge new data with the cache MergeUpdates(calendar_cache, updates); last_change_id = updates[ArraySize(updates) - 1].change_id; } } } //+------------------------------------------------------------------+ void OnTick() { //--- work only with local cache - no network delays if(cache_initialized) ProcessNewsSignals(calendar_cache); }
推奨事項:
OnTick ()はローカルデータの処理のみに使用すべきです。ネットワークリクエストはOnInit、OnTimer、またはユーザーイベント経由でのみ実行するのが適切です。
6. 「新しいイベントでテストする際にEAの再コンパイルを忘れる」
誤った期待:
Filesフォルダ内のcalendar_test_res.binリソースファイルを更新すれば、ストラテジーテスターは自動的に新しいイベントを認識し、EAの再コンパイルは不要であるという考え方です。
現実:
#resourceディレクティブは、データをコンパイル時に.ex5実行ファイルへ埋め込みます。そのため、外部の.binファイルを変更しても動的には反映されず、EAはビルド時に埋め込まれた古いリソースを引き続き使用します。
エラーの症状:
- ストラテジーテスターのログに、リソース更新後に追加されたイベントが表示されない
- 重要なニュースがファイルには存在するにもかかわらず、EAが認識しない
- コード上は正しいにもかかわらず、テスト結果が期待と一致しない
したがって正しい対応は、リソースを更新した後は必ずEAを再コンパイルすることです。
//--- at the beginning of the EA file #resource "\\Files\\USD_calendar_test_res.bin" as MqlCalendarValue USD_res_calendar_data[] //--- after USD_calendar_test_res.bin update: // 1. Save changes to the resource file. // 2. Press F7 in MetaEditor (or Compile in the menu). // 3. Make sure there are no errors in the compilation log. // 4. Run the test again.
推奨事項:
コンパイルログには、リソースのバージョン情報(たとえばハッシュ値や最終更新日時)を出力し、.ex5内で使用されているデータの鮮度を視覚的に確認できるようにすることが推奨されます。
備考:
本セクションで取り上げたエラーは、MQL5の構文理解不足によって発生しているのではなく、経済データ(指標)の本質に対する単純化された理解に起因しています。ここで説明した6つの誤りを回避できるようになれば、単に動作するコードではなく、市場変化や経済当局の要件にも適応可能な安定したトレードシステムを構築できるようになります。この視点こそが、アルゴリズム取引におけるアマチュアとプロフェッショナルの違いを決定づけます。
結論
MetaTrader 5ターミナルの経済指標カレンダーは非常に強力なツールですが、他のツールと同様に、その利用には文脈理解・運用上の規律・プラットフォーム制約の考慮が不可欠です。
本記事では、EAにおけるニュースAPIの具体的な実装および利用構造について整理しました。以下の要素を組み合わせることで、手動のニューストレードを再現性のあるモジュールへと変換できます。
- カレンダーAPIの理解(EventとValueの違い、MqlCalendarEvent / MqlCalendarValue構造体、サーバー時刻の扱い)
- 安全なデータ取得と更新方法(初期読み込みにCalendarValueHistory、インクリメンタル更新にCalendarValueLast + change_idを使用)
- リクエスト制限と典型的エラーの管理
- 通貨、重要度、イベントコード、時間ウィンドウによる多段フィルタリング(ノイズ削減と重要イベント3〜5件への絞り込み)
- フィルタ済みデータをバイナリリソースへエクスポートし、「リアルタイム ↔ ストラテジーテスター」を自動切り替えする仕組み(同一データによる一貫した動作保証)
モジュールの完成条件は、指定された期間のイベントが正常にロードされること、change_idを用いたインクリメンタル更新が制限を超えることなく正しく動作すること、そしてストラテジーテスター環境においてEAがリソースを読み込み、Live環境と同一の入力データに基づいて同一の判断を出力できることです。
推奨される次のステップ:エクスポーターの実装、.binの#resourceへの組み込み、OnInit / OnTimerによる更新ロジックの検証、そして「30分取引停止 → 60分後再開」などの制御条件を含むバックテストを実施することが推奨されます。
これにより、仮説レベルのアイデアから、検証可能でスケーラブルなニューストレードシステムへと移行できます。ニュースEAの開発は単なるプログラミングではなく、マクロ経済理論と市場実践を結ぶ実装作業です。
以下は、MetaTrader 5カレンダーAPI関数およびニューストレードへの応用について、より深く学ぶための推奨リソースです。
- ドキュメント:経済指標カレンダー関数
- 「MQL5経済指標カレンダーを使った取引(第1回):MQL5経済指標カレンダーの機能をマスターする」
- 「MQL5経済指標カレンダーを使った取引(第7回):リソースベースのニュースイベント分析による戦略テストの準備」
- 「MQL5経済指標カレンダーを使った取引(第8回):ニュース駆動型バックテストの最適化 - スマートなイベントフィルタリングと選択的ログ」
- 「MQL5クックブック - 経済指標カレンダー」
記事に添付されているファイルの一覧:
| ファイル名 | 説明 |
|---|---|
| CalendarEventMonitor-EA.mq5 | カレンダーデータの更新機能をテストするためのテストスクリプト |
| ExportCalendarForTester-S.mq5 | 指定されたフィルタ条件に基づいてイベントをバイナリファイルへエクスポートする処理を検証するためのテストスクリプトのコード |
| GetTodayEvents-S.mq5 | 当日分のイベント取得処理を確認するためのテストスクリプトのコード |
| ImportCalendarValidation-EA.mq5 | ストラテジーテスター環境においてリソースからニュースを取得する動作を検証するためのテストEAのコード |
| ImportTesterLog.txt | ImportCalendarValidation-EA.mq5をビジュアルモードでストラテジーテスター実行した際の単一実行結果 |
| LoadLiveLog.txt | ImportCalendarValidation-EA.mq5をLive環境で実行した際のイベント取得結果のログ |
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/22196
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
MQL5におけるイベント駆動型アーキテクチャ:エキスパートアドバイザーを本格的なトレードシステムに進化させる方法
Python-MetaTrader 5ストラテジーテスター(第1回):取引シミュレーター
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
OpenCLを用いたMQL5におけるCPUからGPUへの実践的移行パス
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索

執筆者
MQL5のAPIに何か変更があったのであれば教えてほしいのですが、以前はカレンダーはサマータイムを考慮して現在のタイムゾーンに調整されていました。つまり、夏の機能リクエストはUTC+3ゾーンのタイムスタンプを返しますが、冬の同じ履歴セクションのリクエストは、サーバーがサマータイムに切り替わるとUTC+2ゾーンのスタンプを持つイベントを取得します。したがって、6ヶ月以上のカレンダー履歴を正確にアップロードするには、ブローカーのタイムゾーン 変換履歴を分析する必要があります。詳細はコードベースにて。
また、カレンダーキャッシュをリソースとしてリンクするアプローチはあまり実用的でないように思われ、特に、記事自体に記載されているエラー(EAの再コンパイルを忘れずに、など)を引き起こす。なぜ、すべてのエージェントで利用可能な Common フォルダにあるファイルとして、入力パラメータにカレンダーキャッシュを設定しないのでしょうか?
なぜ気配値(一般的にすべてのタイムスタンプ)はUTCで保存されないのですか?
なぜディーラーのティックは、1つの同じタイムリファレンスの異なる数値表現を持っているのですか?
哲学的な質問です :-) それは間違っているし、その場で問題を引き起こすが、それが行われている方法です。
ZЫ.だから、「歴史的なニュース」は他のソースから取った方がいい。
そして、決して「時計の翻訳の歴史を分析する」必要はない。それはすべてそこにある、それはOSまたはシステムライブラリの機能である。グーグルtzdata
自転車は何台作れるか?
以前は、カレンダーはサマータイムを考慮して現在のタイムゾーンに調整されていました。つまり、夏の機能リクエストでは、例えばUTC+3ゾーンのタイムスタンプが返され、冬の同じ履歴セクションへのリクエストでは、サーバーがサマータイムに切り替わったり戻ったりすると、UTC+2ゾーンのスタンプを持つイベントが返されます。
ZЫ.だから、「歴史的ニュース」は他のソースから取った方がいい。
そして、決して自分で「時計の翻訳履歴を分析」してはいけない。すべてそこにある、OSやシステムライブラリの機能なのだから。グーグルtzdata
自転車は何台作れるか?
他のソースでは、この問題は解決できない。なぜなら、MT5で引用符が保存される方法に埋もれているからだ。
まずはまっすぐな自転車を見せて、それからアドバイスをしてください。
現代のニューストレーダーの主な問題は、断片的なツールセットと、体系的なアルゴリズム取引のワークフローの欠如である。インターネット・ブラウザ(ニュース・サイトの閲覧)と取引端末の間で注意を分散させながら取引を行うのはかなり難しい。
ニューストレーダーのワークフローは次のようなものだ:ウェブブラウザでニュースカレンダーを素早く開き、イベントの変更をチェックする → 今後のイベントを素早く評価し、何をどのように取引するかを決定する → MetaTrader 5ターミナルに移動し、未決注文を発注するか、ターミナルに座ってニュースリリースを待ち、決定を下す。このシナリオでは、トレーダーはしばしば文脈を見失い、ニュースへの反応の遅れにつながり、ひいては損失につながる。
明確なルール、再現可能な結果、自動化されたテストなど、ニュース取引をエンジニアリングの問題のように機能させたいとお考えだろうか?この記事の目的は、MetaTrader 5用のニュースレイヤーの動作アーキテクチャを実証することです:単一のデータソース、カレンダーAPIの適切な使用、フィルタリングとキャッシュのメカニズム、テスター用のリソースへのヒストリカルイベントのエクスポート、ライブとテスターの自動切り替え - 同じコードがリアルタイムとヒストリカルデータの両方で確定的な結果を生成するように。
そう、これはニュース取引にとって本当に悩ましい点だ。
ブラウザ、カレンダー、ターミナルを切り替えると、集中力が途切れて反応が遅くなる。ニュースを直接取引 環境に取り込む統一されたワークフローやシステムがあれば、執行がより速く、より一貫したものになるのは間違いない。
それをテストと自動化によって、構造化された再現可能な「エンジニアリング・スタイル」のセットアップに変えるというアイデアは、感情的な判断や遅れを避けるために、実に理にかなっている。