MQL5経済指標カレンダーを用いたニュースフィルタリング(第2回):ニュースリリース中に管理ポジションを停止する
はじめに
第1回では、重要度の高いニュースイベント中に新規取引のエントリーを禁止するニュースフィルタを導入しました。このレイヤーによって、異常なボラティリティに対するエントリー時のリスクは軽減されます。しかし、ニュースウィンドウが始まる前に保有していたポジションについては、依然としてスプレッドの拡大、一時的な価格急変、一時的な価格の異常などによって、SLやTPが意図せず執行される可能性があります。すべてのポジションをニュースイベントのたびに決済する方法は、多くの場合、現実的ではありません。なぜなら、取引構造が崩れ、統計結果に偏りが生じ、長期的なロジックとも整合しなくなるためです。
本記事では、この具体的な技術設計上のギャップを扱います。すなわち、制限されたニュースウィンドウ中は、すでに保有しているポジションのSLとTPを一時的に停止し、ニュースウィンドウ終了後にそれらを決定論的に復元する、制御可能で可逆的なストップ管理レイヤーを実装する方法を説明します。成功条件は明確です。各ニュースウィンドウにつき一度だけ処理を実行すること(変更を繰り返さないこと)、元のSLとTPの値を保存し、技術的に可能であれば復元すること、ブローカーの最小ストップ距離ルールを遵守すること(無効なストップを設定しないこと)、さらに、EA(マジックナンバー)によるフィルタリングが可能であり、対象範囲が明確であること(単一銘柄での動作は文書化されています)です。目的は、市場方向を予測することではなく、ストップロスの早期執行を軽減することです。
解決策、目的、および原則
第2回の目的は、制限されたニュースウィンドウ中にSLとTPを一時的に停止し、ニュースウィンドウ終了後に安全に復元することです。
このアプローチは、ニュースによる相場の方向性を判断しようとするものではありません。異常な市場環境によって発生するストップロスの早期執行を防ぐことだけを目的としています。
ストップ復元の原則
ストップ復元のロジックは決定論的です。
- 価格が保存された元のSLまたはTP水準をまだ通過していない場合、ストップは元の値へそのまま復元される。
- 価格がすでに元のストップロス水準を超えている場合、ストップは現在価格の直近で有効な水準へ復元される。
- 同じロジックがテイクプロフィットにも適用される。
これにより、次の性質が保証されます。
- 無効なストップの設定がおこなわれないこと
- 価格水準をまたぐことによるブローカーからの拒否が発生しないこと
- 論理的に復元不可能な値が設定されないこと
- 技術的に可能な限り、元の取引構造が維持されること
この仕組みは一貫してルールベースで動作します。
実装要件
このアプローチは実際のポジションを変更するため、実装では直接または間接的に、いくつかの単純ではないケースへ対応する必要があります。
- 複数銘柄にまたがる複数の保有ポジション
- ブローカーの最小ストップ距離制約
- 復元時点ですでに価格が元のストップまたはターゲットを通過しているケース
堅牢性を維持するため、システムは以下を満たす必要があります。
- ストップロスおよびテイクプロフィットを解除する前に元の値を安全に保存すること
- 現在停止状態にある取引を追跡すること
- 繰り返しまたは不要な変更を回避すること
- イベントサイクルごとに一度だけストップを復元すること
したがって、戦略状態の維持と、制御された変更ロジックが、本稿における中核的な構成要素となります。
解決策の概要と本記事の対象範囲
本記事で紹介する解決策では、制限されたニュースウィンドウが有効な場合にのみ動作するストップ管理レイヤーを追加します。
処理の流れは次のとおりです。
- ニュースウィンドウが有効であることを検出(第1回)します。
- 元のSL/TP値を保存します。
- SL/TPレベルを一時的に削除します。
- 停止状態を追跡します。
- ウィンドウの終了を検出します。
- SL/TPを決定論的に復元します。
- 安全に状態をリセットします。
この構造により、ニュースウィンドウ終了後、取引本来の構造を恒久的に変更することなく、取引戦略が再び全面的に管理できるようになります。
ストップ停止アプローチの制限事項
このアプローチは有効ですが、いくつかの制限があります。
- ストップによる保護がない状態でのエクスポージャー:制限されたニュースウィンドウ中は、取引は一時的にストップロスによる保護を持ちません。極端または継続的な一方向の価格変動が発生した場合、含み損が拡大する可能性があります。そのため、ニュース制限ウィンドウは、たとえば3~10分程度の短い時間に設定することを推奨します。
- ギャップリスク:停止期間中に大きな価格ギャップが発生した場合、ストップの復元は当初よりも不利な水準で行われる可能性があります。
- ボラティリティ予測システムではないこと:この仕組みは、市場の反応や方向性を予測するものではありません。異常なスプレッドや一時的な価格急変によって発生するストップロスの早期執行を軽減するだけです。
- ブローカーの約定制約:ストップの復元は、ブローカーの最小ストップ距離ルールおよび銘柄固有の制約を満たす必要があります。
- 戦略との適合性:このアプローチは、ストップロスが常に存在することを前提とする超短期スキャルピング戦略には適さない場合があります。
このレイヤーの目的はリスクを排除することではなく、重要なニュースイベント中の自動売買における特定の構造的な弱点へ対処することです。
実装
実装手順を開始する前に、まずワークフローの構成を整理する必要があります。
ワークフローの構造
検出フェーズ
- 今後予定されているニュースイベントを特定する(第1回で説明済み、第2回ではこれを利用)
- 現在時刻がニュースウィンドウ(第1回を参照)内にあるかどうかを確認する
停止フェーズ
- 保有ポジションを順番に処理する
- 元のSL/TPをメモリ上の配列へ保存する
- SL/TPを削除する
- 取引を停止状態としてマークする
監視フェーズ
- 重複した変更を防止する
- 復元時刻まで停止状態を維持する
復元フェーズ
- 停止状態にある各取引について、価格が元のSL/TP水準をまだ通過していない場合は、それらを元の値へそのまま復元。一方、価格が元のSL/TP水準をすでに通過している場合は、現在価格の直前にある最も近い有効な水準へ復元。
- 停止状態を解除する

構成図
ステップ1:ストップ状態コンテナの設計
何かを解除または保存する前に、まずシステムはストップを解除した後、元のSLおよびTPの値をどこに保存するのかという単純な問いに答える必要があります。
ストップを安全に保存せずに解除した場合、復元は不可能になります。
追跡構造が必要な理由
ニュースウィンドウが開始されると、複数の取引がすでにオープンしている可能性があります。それぞれ異なるチケット番号、異なるストップロス水準、異なるテイクプロフィット水準を持っています。各取引は、それぞれ自身の元の設定を保持する必要があります。そのため、以下を保存する状態コンテナを導入します。
- チケット番号を保存する
- 元のSLを保存する
- 元のTPを保存する
これにより、復元は近似的ではなく、取引ごとに正確になります。
ストップ状態構造
停止された各取引のコンテナとして機能する構造体を定義します。
必須コード
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ //--- Structure to store removed stop information struct SavedStops { ulong ticket; // Trade ticket number double sl; // Original stop loss double tp; // Original take profit };
保存配列
複数のオープン取引が存在する可能性があるため、この構造体の配列を使用します。
SavedStops savedStops[]; // Each element of this array represents one suspended trade
停止状態フラグ
ストップ値を保存することに加えて、システムが現在停止サイクル内にあるかどうかを追跡する必要があります。これにより、以下を防止します。
- ストップの繰り返し解除
- 重複した復元の試行
- 不要なポジション変更
ブール型の状態フラグを導入します。
bool newsSuspended = false; // When true stops are currently removed, when false stops are active
この設計が重要な理由
この小さな基盤によって、決定論的な復元、チケットごとの追跡、取引間の混同の防止、および検出ロジックと変更ロジックの分離が保証されます。
この段階では、まだ何も解除されません。ここでは、EAが依存するメモリシステムを構築しているだけです。
すべてのグローバル変数はOnInit()関数の定義より前に配置する必要があります。以下のようになります。
//+------------------------------------------------------------------+ //| News integration part2.mq5 | //| soloharbinger | //| https://www.mql5.com/ja/users/soloharbinger | //+------------------------------------------------------------------+ #property copyright "soloharbinger" #property link "https://www.mql5.com/ja/users/soloharbinger" #property version "1.00" #include <Trade/Trade.mqh> //+------------------------------------------------------------------+ //| GLOBAL VARIABLES | //+------------------------------------------------------------------+ CTrade trade; //--- Structure to store removed stop information struct SavedStops { ulong ticket; // Trade ticket number double sl; // Original stop loss double tp; // Original take profit }; SavedStops savedStops[]; bool newsSuspended = false; // when true stops are currently removed, and when false stops are active //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { return(INIT_SUCCEEDED); }
ステップ2:停止フェーズ - ニュース中のストップ解除
ストップデータを保存するための構造を用意したので、実際の解除フェーズを実装できます。
ニュースウィンドウが有効になると、
- 保有ポジションを反復処理する
- 元のSL/TP値を保存する
- ブローカーからSL/TPを削除する
- システムを一時停止状態としてマークする
重複した停止を防止する
ニュースが有効な間、毎ティックでストップを解除すると、保存された値を繰り返し上書きし、繰り返し変更を試みることになり、ブローカーによる拒否のリスクが発生し、保存された元の水準が破損する可能性があります。したがって、停止ロジックは各ニュースサイクルにつき1回だけ実行されなければなりません。
以下を使用します。
//--- Already in the desired state, prevent duplicate execution if (newsSuspended) { return; }
SuspendStops()関数
//+------------------------------------------------------------------+ //| Suspend Stops During News | //+------------------------------------------------------------------+ void SuspendStops(bool suspend) { if (suspend == newsSuspended) return; newsSuspended = suspend; if(suspend) { ArrayResize(savedStops, 0); // Clear previous list for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); //--- If needed, filter by magic number here if(PositionSelectByTicket(ticket)) { // Save current SL/TP int idx = ArraySize(savedStops); ArrayResize(savedStops, idx + 1); savedStops[idx].ticket = ticket; savedStops[idx].sl = PositionGetDouble(POSITION_SL); savedStops[idx].tp = PositionGetDouble(POSITION_TP); // Remove stops from broker side if(trade.PositionModify(ticket, 0.0, 0.0)) { PrintFormat("Frozen trade #%I64u: SL/TP removed for news.", ticket); } } } } else // Unsuspended using the RestoreStops() function then reset array { for(int k = 0; k < ArraySize(savedStops); k++) { RestoreStops(savedStops[k].ticket, savedStops[k].sl, savedStops[k].tp); } ArrayResize(savedStops, 0); } }
この段階では、取引はオープン状態のままであり、アクティブなストップは存在しません。元の水準はメモリ上に保存されています。
ステップ3:復元フェーズ - 決定論的なストップ復元
ニュースウィンドウが終了した場合、ストップを安全に復元する必要があります。しかし、復元は単純に次のように実行するものではありません。
trade.PositionModify(ticket, originalSL, originalTP);
なぜなら、価格がすでに動いている可能性があるためです。
価格が元のストップ水準を通過している場合、ブローカーはその設定を拒否する可能性があります。また、さらに悪い場合には、論理的に無効な状態を復元することになります。そのため、復元は価格を考慮し、かつブローカーの制約に適合する必要があります。
復元ロジックの原則
停止状態にある各取引について以下をおこないます。
- チケットによってポジションを選択する
- 現在の市場価格を取得する
- 価格を保存されたSL/TPと比較する
- 元の水準が依然として有効である場合、その値をそのまま復元する
- 価格がその水準を通過している場合、価格の直前にある最も近い有効な水準へ復元する
- ブローカーのストップ距離ルールを遵守する
復元関数
//+------------------------------------------------------------------+ //| Restore Stops After News | //+------------------------------------------------------------------+ bool RestoreStops(ulong ticket, double sl, double tp) { if (!PositionSelectByTicket(ticket)) return(false); ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); double price = (type == POSITION_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_BID) : SymbolInfoDouble(_Symbol, SYMBOL_ASK); double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double minStopDist = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * point; // Safety check: If price has moved past the saved SL/TP, adjust to a valid level if(type == POSITION_TYPE_BUY) { if(sl != 0 && price <= sl) sl = price - minStopDist; if(tp != 0 && price >= tp) tp = price + minStopDist; } else // SELL { if(sl != 0 && price >= sl) sl = price + minStopDist; if(tp != 0 && price <= tp) tp = price - minStopDist; } // Full restore of stops if(trade.PositionModify(ticket, sl, tp)) { PrintFormat("Restored SL/TP for #%I64u | SL=%.5f, TP=%.5f", ticket, sl, tp); return true; } else { PrintFormat("Failed to restore SL/TP for #%I64u | Error=%d", ticket, GetLastError()); return false; } }
これがおこなうこと
停止状態にあるすべての取引について:
- 元のSL/TPが依然として有効な場合、それらを復元する
- 価格がすでに通過している場合、最も近い有効な水準へ調整する
- ブローカーの最小ストップ距離を遵守する
統合セクション - 第2回を第1回へ追加
具体的には、以下をおこないます。
- 停止用の入力項目を追加する
- 状態変数を追加する
- SuspendStops()関数とRestoreStops()関数を追加する
- 状態遷移ロジックを使用してOnTick()へ統合する
第1回コードを参考にしてください。
既存の入力に新しいサスペンション入力を追加する
input bool SuspendStopsDuringNews = false; // Temporarily remove and later restore SL/TP during the news window
//EXISTING INPUTS input bool EnableNewsFilter = false; // Enable Economic News Filter input int NewsMinutesBefore = 5; // Minutes before news to restrict input int NewsMinutesAfter = 5; // Minutes after news to restrict input bool RestrictNewTradesDuringNews = true; // Block new trades during news window input bool CloseOpenTradesBeforeHighImpactNews = false; // Close all trades before news input string SymbolCurrencyOverride = ""; // Manual currency override e.g. "USD,JPY" enum ENUM_NEWS_IMPORTANCE_MODE { NEWS_HIGH_ONLY = 0, NEWS_MODERATE_ONLY, NEWS_HIGH_AND_MODERATE }; input ENUM_NEWS_IMPORTANCE_MODE NewsImportanceMode = NEWS_HIGH_ONLY; // Which importance levels to consider // Cache reload interval input int CacheReloadHours = 6; // How often to reload calendar cache (hours)
先ほどの構造体とグローバル変数を追加します。
// EXISTING GLOBALS MqlCalendarValue TodayEvents[]; // Calendar cache datetime lastCalendarLoad = 0; CTrade trade; // CTrade instance for order management // NEWLY ADDED [part 2] // Structure to store removed stop information struct SavedStops { ulong ticket; // Trade ticket number double sl; // Original stop loss double tp; // Original take profit }; SavedStops savedStops[]; bool newsSuspended = false;
先ほどの停止関数と復元関数を追加する
先ほどの停止関数と復元関数を第1回の関数の下に追加します。
4. OnTickに統合する
OnTickの再構成が必要な理由
第1回では、現在bool isNews、取引を決済するためのラッチ、およびニュース終了時のリセット処理を使用しています。これは取引を決済する場合には機能しますが、第2回では、より高度な処理を導入します。つまり、ニュースウィンドウが開始した正確なタイミングと、ニュースウィンドウが終了した正確なタイミングを検出する必要があります。これは、ストップ解除処理をニュースウィンドウ開始時に1回実行し、ニュースウィンドウ終了時にも1回実行する必要があるためです。
ここでは2つの変数を導入します。
currentNewsState // システムが現在認識している状態
lastNewsState // 前回のティックでシステムが認識していた状態
この2つの値が異なる場合、状態遷移が発生したことを意味します。その遷移は、ニュースが開始した場合、またはニュースが終了した場合のどちらかです。
最終的なOnTick関数
以下は、ニュース処理ブロック部分の第1回を置き換える統合処理です。これにより、第1回と第2回を統一した形で維持できます。
//+------------------------------------------------------------------+ //| OnTick | //+------------------------------------------------------------------+ void OnTick() { // Main News Logic if(EnableNewsFilter) { // Track previous state of the news window static bool lastNewsState = false; bool currentNewsState = (EnableNewsFilter && IsNewsTime(_Symbol)); // Detect state transition if(currentNewsState != lastNewsState) { if(currentNewsState) // News window has just started { Print("--- NEWS WINDOW STARTED ---"); // Part 1 Feature: Close trades if enabled if(CloseOpenTradesBeforeHighImpactNews) { Print("Closing all trades due to high impact news setting."); CloseAllTradesForSymbol(_Symbol); } // Part 2 Feature: Suspend SL/TP if enabled if(SuspendStopsDuringNews) { Print("Freezing trade management (SL/TP)."); SuspendStops(true); } } else // News window has just ended { Print("--- NEWS WINDOW ENDED ---"); // Part 2 Feature: Restore SL/TP if(SuspendStopsDuringNews) { Print("Restoring normal trade management."); SuspendStops(false); } } // Update last state after handling transition lastNewsState = currentNewsState; } } }
tradesClosedForThisNewsWindowラッチ変数を削除し、ティックごとの決済チェックを排除しました。そして、ニュースウィンドウの開始または終了時に1回だけ実行される状態遷移モデルへ置き換えました。
新しいシステムの動作
ニュースウィンドウが開始すると、必要に応じて取引を決済し、必要に応じてSL/TPを解除します。 ニュースウィンドウが終了したら、SL/TPを復元するだけです。
下の図は、ニュースフィルタ設定を示します。

ニュースフィルタ設定

ニュースウィンドウ中のPrint文出力
重要な注意事項
- エントリーロジックまたは取引処理ロジックの開始部分で、以下を呼び出します。
if(!CanOpenNewTrade(_Symbol)) return;
- すべての実装が、このEAによって取得された取引のみに影響するように、マジックナンバーを設定します。
input int magicNumber = 12345; //--- in SuspendStops and CloseAllTradesForSymbol, filter by magic: if (PositionSelectByTicket(ticket) && PositionGetInteger(POSITION_MAGIC) == magicNumber)
- この実装は、単一銘柄EA向けに設計されています。チャートの現在の銘柄に依存してストップを復元するため、複数銘柄を同時に取引するマルチ銘柄EAでは正しく動作しません。
結論
ニュース周辺における「新規エントリーをブロックすること」と「既存ポジションを安全に管理すること」の間に存在していた実装上のギャップを解消しました。提供した設計は、決定論的であり、統合可能な構造になっています。具体的には、元のSL/TPをメモリ上に保持するための、チケット単位のSavedStopsコンテナ、ニュースウィンドウ開始時および終了時に1回だけ実行されることを保証する、停止フラグおよび状態遷移モデル、SL/TPを1回だけ解除するSuspendStops()、ブローカーの最小距離を遵守しながら、価格を考慮してストップを復元するRestoreStops()を含みます。この仕組みにより、可能な限り元の取引構造が維持されます。また、価格が保存された水準を通過している場合には、最も近い有効な水準を設定することで、予測可能な形で対応します。
実用上の制約は明確です。対象範囲を制限するためにマジックナンバーを使用すること、実装上は単一銘柄動作を前提とすること、停止ウィンドウ中に一時的なエクスポージャーが発生することを受け入れることです。このレイヤーはボラティリティ予測システムではありません。これは、EAの統計的整合性を維持しながら、スプレッド拡大や価格急変によって発生する誤ったストップ執行を防ぐための、限定された範囲のルールベースの軽減策です。次の部分では、EAまたはターミナルを再起動した場合でも停止中のストップ状態を維持できるように、永続ストレージを追加します。
下の図は、ニュースウィンドウが動作している際のPrint文を示します。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/21446
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
グラフ理論:取引における深さ優先探索(DFS)の応用
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
MQL5における切断ニュートン共役勾配(TNC)アルゴリズムの実装
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
これらの機能をどのように活用するつもりか:
プロップファームで稼働させているすべてのEAにおいて、「第1部: ニュース制限時間帯中の新規トレードの開始および既存ポジションの決済を停止する」 という記事の内容を適用する予定です。
一方、「パート2および パート3(ニュース制限時間帯の前後でSL/TPを一旦解除し、その後再設定する)」については、別のEAで使用する予定です。このコードは口座内の全建玉を順次処理するため、複数のチャートを同時に開いている場合でも、必要以上に何度も実行する必要がないようにするためです。シンボル選択を「USD、GBP、EUR」に手動で上書きします。これは、経験上、すでにフィルタリングが必要だと分かっている「赤いフォルダ」のニュースがこれらだけだからです(皆様の場合は異なるでしょう)。
そこで、RestoreStops()関数に1行の修正を加えました。
SuspendStops()関数については修正の必要はありませんでした。
状況を見る限り、Part3については何も変更する必要はなさそうです。必要なのは、チケット番号、SL、TPだけだからです。
@Solomon Anietie Sundayさん、どうもありがとうございました。
デモ口座で実際にテストしてみます!
// I won't need this if statement, in the 2nd EA, as the 2nd EA only modifies trades, // only need this is statement in the first EA (that opens trades) if(!CanOpenNewTrade(_Symbol)) return;