自動売買·

MT4のEAにニュースフィルターを追加する方法

MetaTrader 4には、EAから使える経済指標カレンダーがありません。無料のカレンダーAPI、インクルードファイル1つ、エントリー前に呼ぶ関数1つで、10分あれば組み込めます。MQL4のコードを全文掲載、APIキーは不要です。

米雇用統計(NFP)や政策金利発表のさなかにもエントリーを続けるEA(エキスパートアドバイザー)は、もう戦略どおりに動いているとはいえません。広がったスプレッドの中で、コイン投げをしているのと同じです。そこで役に立つのがニュースフィルターです。相場を動かす指標の発表前後の数分間だけ新規エントリーを止め、それ以外の時間は戦略に任せます。

MetaTrader 5には経済指標カレンダーが内蔵されていますが、MetaTrader 4にはありません。そのためEAは、ニュースをどこかから取ってくる必要があります。このガイドで使うのは、無料のShowMyTrades経済指標カレンダーAPIです。口座登録もAPIキーも不要で、ターミナルで許可するURLは1つだけ。どのEAにも貼り付けられるインクルードファイルを1つ用意します。

🧭 作るものの全体像

15分ごと
  今後2日分のニュースをダウンロード
  (CSV、リクエスト1回)
          │
          ▼
ティックごと
  現在時刻の[-30分, +30分]の範囲に発表があるか?
    ある → 新規エントリーしない
    ない → 戦略を実行
決めることコードの初期値理由
対象の通貨EUR,USD取引している通貨ペアを構成する2通貨。空欄にすると主要9通貨すべてが対象になります
対象の重要度high重要度「中」も対象にして、より慎重に止めたいならhigh,medium
発表前の停止30分発表前はスプレッドが広がり、流動性も薄くなります
発表後の停止30分最初の値動きも、その後の反転も、この時間帯に起こります
ダウンロード間隔15分カレンダーが変わるのは1日に数回程度です。しかもAPI側で15分間キャッシュしています

🔑 ステップ1:MetaTraderでURLを許可する

インターネットからデータを取得するMQL4の関数WebRequest()は、あらかじめ許可したURLにしかアクセスできません。ターミナルでツール → オプション → エキスパートアドバイザ(Tools → Options → Expert Advisors)を開き、WebRequestを許可するURLリスト(Allow WebRequest for listed URL)にチェックを入れて、次のURLを追加します。

https://showmytrades.com

この設定を忘れると、ダウンロードは-1を返し、エラー4060になります。そのときはインクルードファイルが「エキスパート」タブ(Experts)に注意を表示するので、フィルターがなぜ反応しないのかと悩まずに済みます。

📄 ステップ2:インクルードファイルを保存する

次のコードをSMT_NewsFilter.mqhという名前で、ターミナルのMQL4\Includeフォルダー(ファイル → データフォルダを開く、File → Open Data Folder)に保存します。APIリファレンスに載せているものと同じファイルで、MQL5でもそのままコンパイルできます。

SMT_NewsFilter.mqh
//+------------------------------------------------------------------+
//| SMT_NewsFilter.mqh - MT4・MT5用ニュースフィルター
//| データ:https://showmytrades.com/api/public/calendar/v1(無料)
//| ドキュメント:https://showmytrades.com/docs/api/economic-calendar
//| 設定:ツール > オプション > エキスパートアドバイザ >
//|       WebRequestを許可するURLリストに https://showmytrades.com を追加
//+------------------------------------------------------------------+
#property strict

input string SMT_Currencies     = "EUR,USD"; // ニュース:通貨(カンマ区切り、空欄 = すべて)
input string SMT_Impact         = "high";    // ニュース:重要度(high | high,medium | all)
input int    SMT_MinutesBefore  = 30;        // ニュース:発表前の停止時間(分)
input int    SMT_MinutesAfter   = 30;        // ニュース:発表後の停止時間(分)
input int    SMT_RefreshMinutes = 15;        // ニュース:ダウンロード間隔(分、最小15)

datetime smt_eventTime[];
string   smt_eventTitle[];
datetime smt_nextDownload = 0;
long     smt_serverTime   = 0;     // 前回ダウンロード時のサーバーのUTC時刻(Unix秒)
uint     smt_syncTick     = 0;     // その時点のGetTickCount()
bool     smt_clockSynced  = false;

//--- 本当のUTC時刻:サーバーの時刻に、ダウンロードからの経過ミリ秒を足したもの。
//--- 経過時間はGetTickCount()で数えるので、パソコンのタイムゾーン、夏時間、
//--- 時計の補正の影響を受けない(カウンターが一周してもuintの引き算で正しく求まる)。
//--- 最初のダウンロードまではTimeGMT()で代用する。TimeCurrent()は使わない:
//--- ブローカーのGMTオフセットは分からず、夏時間で変わるため。
datetime SMT_Now()
{
   if(!smt_clockSynced) return TimeGMT();
   uint elapsedMs = GetTickCount() - smt_syncTick;
   return (datetime)(smt_serverTime + elapsedMs / 1000);
}

//--- ブローカーの時計とUTCの差(秒、7200 = GMT+2)。これ単体でも便利:
//--- ほとんどのEAはこの値を知らない。TimeCurrent()は最後のティックの時刻なので、
//--- 週末ではなく、市場が開いているあいだに読むこと。
int SMT_BrokerGmtOffsetSeconds()
{
   return (int)(TimeCurrent() - SMT_Now());
}

//--- n番目のカンマより後ろすべて(CSVのタイトル。タイトル自体にカンマを含むことがある)
string SMT_AfterComma(const string line, const int n)
{
   int pos = -1;
   for(int i = 0; i < n; i++)
   {
      pos = StringFind(line, ",", pos + 1);
      if(pos < 0) return "";
   }
   string rest = StringSubstr(line, pos + 1);
   StringReplace(rest, "\"\"", "\"");
   if(StringLen(rest) >= 2 && StringGetCharacter(rest, 0) == '"')
      rest = StringSubstr(rest, 1, StringLen(rest) - 2);
   return rest;
}

//--- 今後2日分をダウンロード(範囲には直近6時間も含まれる)
bool SMT_Download()
{
   string url = "https://showmytrades.com/api/public/calendar/v1?format=csv&days=2&impact=" + SMT_Impact;
   if(StringLen(SMT_Currencies) > 0) url += "&currency=" + SMT_Currencies;

   char   post[], result[];
   string headers;
   ResetLastError();
   int status = WebRequest("GET", url, "", NULL, 10000, post, 0, result, headers);
   if(status != 200)
   {
      Print("[SMT] calendar download failed: HTTP ", status, ", error ", GetLastError(),
            ". Is https://showmytrades.com in Tools > Options > Expert Advisors > Allow WebRequest?");
      smt_nextDownload = TimeCurrent() + 60; // 1分後に再試行
      return false;
   }

   string body = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);
   string lines[];
   int n = StringSplit(body, '\n', lines);

   ArrayResize(smt_eventTime, 0);
   ArrayResize(smt_eventTitle, 0);
   for(int i = 1; i < n; i++) // 0行目はヘッダー
   {
      // 最終行 "# server_time=1760702400 server_time_iso=...":サーバーのUTC時刻
      if(StringFind(lines[i], "# server_time=") == 0)
      {
         long serverTime = StringToInteger(StringSubstr(lines[i], 14, 10));
         if(serverTime > 0)
         {
            smt_serverTime  = serverTime;
            smt_syncTick    = GetTickCount();
            smt_clockSynced = true;
         }
         continue;
      }
      string f[];
      if(StringSplit(lines[i], ',', f) < 8) continue;
      int k = ArraySize(smt_eventTime);
      ArrayResize(smt_eventTime, k + 1);
      ArrayResize(smt_eventTitle, k + 1);
      smt_eventTime[k]  = (datetime)StringToInteger(f[0]);      // Unix秒、UTC
      smt_eventTitle[k] = f[2] + " " + f[3] + ": " + SMT_AfterComma(lines[i], 7);
   }
   smt_nextDownload = TimeCurrent() + MathMax(SMT_RefreshMinutes, 15) * 60;
   Print("[SMT] calendar: ", ArraySize(smt_eventTime), " events loaded. UTC now ",
         TimeToString(SMT_Now(), TIME_DATE|TIME_MINUTES), ", broker clock is GMT",
         (SMT_BrokerGmtOffsetSeconds() >= 0 ? "+" : ""), DoubleToString(SMT_BrokerGmtOffsetSeconds() / 3600.0, 1));
   return true;
}

//--- 停止時間帯に発表があるあいだtrue。reasonにはどの発表かが入る
bool SMT_IsNewsTime(string &reason)
{
   reason = "";
   if(MQLInfoInteger(MQL_TESTER)) return false;   // テスターではネットワークが使えない
   if(TimeCurrent() >= smt_nextDownload) SMT_Download();

   datetime now = SMT_Now();
   for(int i = 0; i < ArraySize(smt_eventTime); i++)
   {
      if(now >= smt_eventTime[i] - SMT_MinutesBefore * 60 && now <= smt_eventTime[i] + SMT_MinutesAfter * 60)
      {
         reason = smt_eventTitle[i];
         return true;
      }
   }
   return false;
}

処理の流れは次のとおりです。入力パラメーターからリクエストを組み立て、CSVをダウンロードし、イベントごとに時刻とラベルを1つずつ保持します。そのうえで、EAが知りたいただ1つの問い、いま停止時間帯に発表が入っているかに答えます。

🔌 ステップ3:エントリーの前に毎回呼び出す

EAでこのファイルをインクルードし、ポジションを持つ前に確認します。

YourEA.mq4
#include <SMT_NewsFilter.mqh>

void OnTick()
{
   string news;
   if(SMT_IsNewsTime(news))
   {
      Comment("News filter active: ", news);
      return;                       // ニュースの時間帯は新規エントリーしない
   }
   Comment("");
   // ... エントリーのロジック ...
}

5つの入力パラメーター(SMT_Currencies、SMT_Impact、分単位の3つの値)は、ほかの設定と同じくEAのパラメーター画面に表示されます。チャートごとに別の値を設定できます。

再コンパイルしてEAをチャートにセットしたら、「エキスパート」タブを見てください。最初の行に[SMT] calendar: N events loadedと出ていれば動いています。指標の少ない日はNが0になることもありますが、それは正常で、エラーではありません。

⏱️ 時刻、時計、ストラテジーテスター

フィルターが正しい分に作動するかどうかは、次の3点で決まります。

APIはUTCで返し、現在時刻も教えてくれるtsはUTCのUnixタイム(秒)です。ダウンロードのたびに末尾にサーバー自身の時刻(# server_time=…)が付き、SMT_Now()がそれをパソコンのティックカウンターで進めていきます。このカウンターはタイムゾーンにも夏時間にも影響されません。ブローカーのタイムゾーン、どこかの国の夏時間、VPSのタイムゾーン設定が正しいかどうか、そのどれにも左右されない仕組みです
TimeCurrent()は使わないブローカー時間はUTCからずれていて、そのずれはEAには分からず、しかも年に2回変わります。SMT_BrokerGmtOffsetSeconds()がこのずれを測ってくれるので、それ単体でも役に立ちますが、フィルター自体はこの値に頼っていません
ストラテジーテスターではインターネットが使えないテスターではWebRequest()が無効になるため、バックテストでは関数が「ニュースなし」を返します。過去のデータでニュースフィルターを試すには過去のイベントを収めたローカルファイルが必要で、それはまた別の作業になります

🎛️ 調整のしかた

  • 保有ポジション:このコードが止めるのは新規エントリーだけです。発表前に決済するか、損切りの幅を狭めるか広げるかは、戦略で決めることです。そのタイミングは同じSMT_IsNewsTime()で分かります。
  • 通貨ペアの片方だけ:ドルのニュースだけを気にするなら、SMT_CurrenciesをUSDにします。
  • 重要度「中」も含める:SMT_Impact = "high,medium"にすると、対象のイベントはおよそ3倍になります。スキャルパーには向いていますが、スイングトレードのシステムには多すぎることがよくあります。
  • 祝日:impact=allにすると、市場の祝日が終日のイベントとして含まれます。ほとんどのフィルターには不要なので、初期設定では除外しています。

🔍 データを確かめる

同じカレンダーは経済指標カレンダーのページでも見られます。EAが「USD high: Nonfarm Payrolls」で停止したと表示したら、そのページで同じイベントと予想を確認でき、発表後は結果も確認できます。

❓ よくある質問

ShowMyTradesのアカウントは必要ですか?

不要です。経済指標カレンダーAPIは無料で公開されていて、キーもいりません。認証済みの公開ページが欲しいならShowMyTradesで口座を記録するとよいですが、ニュースフィルター自体はそれに依存しません。

通信量はどのくらいですか?

チャート1枚につき15分に1回のリクエストで、1回あたり数KBです。1つのターミナルでEAを10個動かしても、1時間に40リクエストです。上限はIPアドレスごとに毎分600リクエストです。

Forex Factoryを直接読めばよいのでは?

それもできますし、実際に多くのEAがそうしています。ただ、Forex Factoryの週次ファイルはIPアドレスごとに5分間で2回までしかダウンロードできないため、多数のターミナルを動かすVPSでは破綻します。actual(結果)の値もありません。ShowMyTradesのエンドポイントは通貨と重要度をサーバー側で絞り込み、返すデータは数KBだけで、定期的な取得(ポーリング)を前提に作っています。

VPSの時計やタイムゾーンがずれています。それでもフィルターは正しい分に作動しますか?

はい。サーバーはダウンロードのたびにUTCの現在時刻をEAに伝え、EAはその時刻を自分で進め続けます。VPSのタイムゾーンが違っていても、時計が数分ずれていても影響はありません。VPSで夏時間の切り替えがあっても同じです。EAは経過時間を、時計ではなくティックカウンターで数えているからです。

ダウンロードに失敗したら?

フィルターは最後に読み込んだカレンダーを使い続け、1分後に再試行します。一度も読み込めていない場合(起動したばかりで、インターネットにつながらないときなど)は「ニュースなし」を返すため、EAは取引します。それが困るなら、ArraySize(smt_eventTime) == 0をチェックして動作を決めてください。

同じファイルはMT5でも使えますか?

はい、そのまま使えます。MT5には独自の経済指標カレンダーもあるので、使いやすいほうを選んでください。

🔗 関連ガイド


ニュースフィルターに必要なのは、1ページ分のコードとURLが1つだけです。EAがすでにShowMyTradesで記録されているなら、ニュース前後で見送った取引の分だけ、ドローダウンが浅く済んでいることが記録に表れます。