MQL5時間フィルターEA実装ガイド【セッション・夏冬時間完全対応】
最終更新: 2026年06月
この記事でわかること
TimeCurrent()・TimeLocal()・TimeGMT()の違いとVPS運用時の使い分け基準- 東京・ロンドン・NYセッション別の時間フィルター実装パターン
- 夏冬時間(GMTオフセット変動)への対応方法とinputパラメータ設計
- 金曜夜・週末の安全クローズ実装(ポジション強制決済を含む)
- 経済指標前後の取引停止パターンと完全統合サンプルコード
時間フィルターがないEAは24時間365日稼働し続け、スプレッドが広いアジア早朝も売買する。ブローカーによっては東京早朝のEUR/USDスプレッドが大幅に広がることも珍しくなく、その時間帯にエントリーしたポジションは最初から不利なコストを背負う。深夜の薄商いは価格操作的なスパイクが出やすい——バックテスト上では問題なかった戦略がフォワードで謎の連敗を重ねる原因として、この時間帯の問題は見落とされがちだ。
本稿は「時間フィルターをMQL5でどう実装するか」に絞った技術解説だ。基本的な時刻取得関数の挙動から、夏冬時間のGMTオフセット対応、金曜夜クローズ、経済指標前後の停止パターンまで、実装上の落とし穴を含めて順に解説する。筆者はアルゴリズムファンドでのシステム開発経験をもとに、「動くが危ういコード」と「実際の運用に耐えるコード」の違いを意識しながら書いている。
コード例はMT5(MetaTrader 5)環境を前提とする。MT4のMQL4環境では一部関数の挙動が異なる点に注意してほしい。
時間フィルターがEAに必要な理由
スプレッド拡大・ニュース時のボラティリティ・週末ギャップという3種類のリスクが時間帯依存で発生し、適切なフィルター設計がフォワード成績の安定につながる。
スプレッドと流動性の時間依存性
FX市場は24時間取引可能だが、流動性は時間帯によって大きく異なる。ロンドン・ニューヨーク重複時間帯(おおむね日本時間21時前後〜翌早朝)は流動性が最も高くスプレッドが締まる。逆にアジア早朝(日本時間3時〜7時頃)はEUR/USDのような主要通貨ペアでも流動性が薄く、スプレッドが拡大するブローカーが多い。
バックテストで使用するスプレッド設定が一定値(たとえば2pips固定)の場合、深夜の実際のスプレッド拡大を反映できていない。この乖離こそが、時間フィルターなしEAのバックテスト結果を楽観的に見せる主因の一つだ。
関連記事: MQL5バックテストのスプレッド設定とフォワード乖離を減らす方法
ニュース・ロールオーバー前後のボラティリティ
特定時間帯には構造的なボラティリティ上昇がある。
- ロールオーバー時刻(通常GMT 22:00前後): スワップ計上に伴うブローカー処理で一時的なスプレッド拡大が起きやすい
- 経済指標発表(NFP・CPIなど): 発表数分前後にスプレッドが急拡大し、約定スリッページも増える
- 市場オープン直後(特にロンドン): 前夜のギャップを埋める動きで急変動が起きやすい
これらの時間帯を外すだけで同じエントリーロジックでもドローダウンが改善されるケースがある。ただし「避ければ必ず改善する」という話ではない。戦略によって最適な取引時間帯はまるで違い、結局はバックテストで検証するほかないのが現実だ。
週末リスク
土日の市場クローズ中に発生した地政学リスクや経済イベントは、月曜オープン時のギャップとして現れる。週末にポジションを保有し続けることはこのギャップリスクを丸ごと引き受ける行為だ。多くのシステムトレーダーが金曜夜にポジションを強制クローズする設計を採用しているのはこのためで、筆者もほぼ全てのEAでこの設計を採っている。
TimeCurrentとTimeLocalの違い(使い分けの基準)
TimeCurrent()はブローカーサーバー時刻・TimeLocal()はPC時刻・TimeGMT()はUTC換算時刻を返す。VPS運用ではTimeLocal()への依存を排除し、TimeCurrent()またはTimeGMT()を基準にするのが原則だ。
ここは最初に正確に押さえておかないと後で痛い目に遭う。VPS運用時に時間フィルターが完全に機能しなくなる原因の大半がこの混同だ。筆者もシステム開発の初期にやらかしたので、先に整理しておく。
TimeCurrent() — ブローカーサーバー時刻
datetime server_time = TimeCurrent();
TimeCurrent()が返すのはブローカーのトレードサーバーが保持する時刻だ。MT5が最後に受信したティックのサーバータイムスタンプが基準になる。
落とし穴が二つある。
一つ目。土日など市場が閉まってティックが来ない間はTimeCurrent()が更新されない。週末にOnTimer()でEAが動いていても、返す時刻は金曜最終ティックの時刻のままだ。週末処理をTimeCurrent()に依存させると誤動作する。
二つ目。ブローカーによってサーバーのGMTオフセットが異なる。多くはGMT+2(冬時間)またはGMT+3(夏時間)を採用しているが、GMT+0のブローカーも存在する。EA内でハードコードした時間条件がブローカー変更で全てズレるのはこのせいだ。
TimeLocal() — PCのローカル時刻
datetime local_time = TimeLocal();
TimeLocal()はEAが動いているPC(またはVPS)のシステム時刻を返す。ティックが来なくても更新される点はメリットに見えるが、VPSのタイムゾーン設定に引っ張られるという問題がある。
VPS環境ではOSのタイムゾーンがUTCに設定されていることが多く、ローカル開発機の日本標準時(JST、UTC+9)とは9時間も乖離する。TimeLocal()に依存したEAをVPSに載せ替えたとたん時間フィルターが9時間ズレる——この事故は珍しくなく、筆者が関わったプロジェクトでも一度やらかしている。
TimeGMT() と TimeGMTOffset()
MT5にはさらにTimeGMT()とTimeGMTOffset()という関数がある。
datetime gmt_time = TimeGMT(); // クライアント端末のローカル時刻をDST込みでGMT換算した値
int gmt_offset = TimeGMTOffset(); // クライアント端末のローカル時刻とGMTの差(秒単位)
TimeGMT()はクライアント端末(PC/VPS)のローカル時刻をDST込みでGMTに換算して返す。サーバー時刻ではなくローカル時計が基準のため、VPSの時刻設定が狂っている場合は誤差が生じる点に注意してほしい。
TimeGMTOffset()はクライアント端末(PC/VPS)のローカル時刻とGMTの差を秒単位で返す(TimeGMT() - TimeLocal()に相当)。ブローカーサーバーのGMTオフセットを取得したい場合は、TimeCurrent()とTimeGMT()の差分で求める必要があり、TimeGMTOffset()を直接使うと意図とズレる。ここは混同しやすいので注意が必要だ。
実務上の判断基準
| 用途 | 推奨する関数 |
|---|---|
| 通常のエントリー時間判定(ティックドリブン) | TimeCurrent() |
| 週末・祝日の判定(ティックなし期間含む) | TimeGMT() |
| VPS運用でブローカー非依存にしたい | TimeGMT() + オフセット手動管理 |
| PCローカル環境のみで使う簡易EA | TimeLocal()(VPS移行時は要注意) |
筆者の実装ではほぼTimeCurrent()を基準にし、GMTオフセットはinputパラメータで手動指定する構成を採っている。ブローカーを変えるたびにinputを1箇所変えるだけで済み、コードの可読性も高い。
セッション別時間フィルターの基本実装
MqlDateTime構造体とTimeToStruct()を組み合わせた判定関数を各セッションに用意し、OnTick()の先頭でフィルタリングするのが基本パターンだ。日跨ぎ(NYセッション等)への対応を最初から組み込んでおくことが後で効いてくる。
実装の基本パターンを示す。ブローカーのサーバー時刻がGMT+3(夏時間)の場合を想定した例だ。
//--- セッション判定関数群
// ブローカーサーバー時刻 GMT+3 を前提とした場合の概算範囲
// (実際はブローカー仕様書で確認すること)
MqlDateTime now;
bool IsTokyoSession()
{
TimeToStruct(TimeCurrent(), now);
// 東京セッション: GMT 00:00-09:00 → サーバー(GMT+3) 03:00-12:00
return (now.hour >= 3 && now.hour < 12);
}
bool IsLondonSession()
{
TimeToStruct(TimeCurrent(), now);
// ロンドンセッション: 夏時間 GMT 07:00-16:00 → サーバー(GMT+3) 10:00-19:00
// 冬時間 GMT 08:00-17:00 → サーバー(GMT+3) 11:00-20:00
return (now.hour >= 10 && now.hour < 19);
}
bool IsNYSession()
{
TimeToStruct(TimeCurrent(), now);
// NYセッション: 冬時間 GMT 13:00-22:00 → サーバー(GMT+3) 16:00-01:00(翌日)
// 夏時間 GMT 12:00-21:00 → サーバー(GMT+3) 15:00-00:00
// 日跨ぎに注意
return (now.hour >= 16 || now.hour < 1);
}
bool IsLondonNYOverlap()
{
TimeToStruct(TimeCurrent(), now);
// 重複時間(冬時間): GMT 13:00-17:00 → サーバー(GMT+3) 16:00-20:00
// 重複時間(夏時間): GMT 12:00-16:00 → サーバー(GMT+3) 15:00-19:00
// ※欧米の夏時間切替タイミングが異なる3月・10月の一部週はさらに変動
return (now.hour >= 16 && now.hour < 20);
}
MqlDateTime構造体のフィールドは以下の通りだ。TimeToStruct()で展開して使う。
struct MqlDateTime
{
int year; // 年
int mon; // 月(1-12)
int day; // 日(1-31)
int hour; // 時(0-23)
int min; // 分(0-59)
int sec; // 秒(0-59)
int day_of_week; // 曜日(0=日曜, 1=月曜, ..., 5=金曜, 6=土曜)
int day_of_year; // 年内通算日(0-365)
};
day_of_weekは0が日曜、6が土曜である点に注意。曜日判定でのオフバイワンは頻出バグで、筆者のコードレビューでも何度か指摘した記憶がある。
OnTick内での使い方
void OnTick()
{
// 取引許可セッションのチェック
if(!IsLondonSession() && !IsNYSession()) return;
// 金曜クローズチェック
if(IsFridayClose()) return;
// 以降はエントリーロジック
// ...
}
Claudeと会話しながらインジケータが作れるhedgrow-fxはこちら
夏時間・冬時間のGMTオフセット対応
多くのブローカーは冬時間GMT+2・夏時間GMT+3でサーバー時刻を自動調整するため、セッション時間をGMT基準でinputパラメータ化しておくと、切替時の対応がinputの1変更で済む。TimeGMT()を直接使う動的取得も有効な選択肢だ。
問題の本質
欧州夏時間(BST)・米国夏時間(EDT)への移行タイミングはほぼ毎年変わらないが、コード内にハードコードした時間条件はオフセット変更に対応できない。多くのブローカーがサーバー時刻を夏冬で自動調整するため、EA側でも追従が必要だ。
典型的なブローカーのオフセット:
- 冬時間(10月末〜3月末): GMT+2
- 夏時間(3月末〜10月末): GMT+3
この1時間の差を放置すると、「ロンドン開始から取引するつもりが、実際は1時間早く、あるいは遅く動き始める」という状態になる。バックテストではどちらの時期のデータを使うかで結果が変わり得る問題でもある。
関連記事: MQL5 EAのブローカー設定チェックリスト——移行時に確認すべき10項目
inputパラメータ化の実装
//--- インプットパラメータ
input int InpGMTOffset = 3; // ブローカーGMTオフセット(夏:3, 冬:2)
input int InpTokyoStart = 0; // 東京開始(GMT基準, hour)
input int InpTokyoEnd = 9; // 東京終了(GMT基準, hour)
input int InpLondonStart = 7; // ロンドン開始(GMT基準, 夏時間: 7, 冬時間: 8)
input int InpLondonEnd = 16; // ロンドン終了(GMT基準, 夏時間: 16, 冬時間: 17)
input int InpNYStart = 13; // NY開始(GMT基準, 冬時間: 13, 夏時間: 12)
input int InpNYEnd = 22; // NY終了(GMT基準, 冬時間: 22, 夏時間: 21)
//--- GMT基準で時間を指定し、サーバー時刻に変換して比較する
bool IsInSession(int gmt_start, int gmt_end)
{
MqlDateTime now;
TimeToStruct(TimeCurrent(), now);
// サーバー時刻をGMTに変換
int gmt_hour = (now.hour - InpGMTOffset + 24) % 24;
if(gmt_start < gmt_end)
{
// 日跨ぎなし(例: 7:00-16:00)
return (gmt_hour >= gmt_start && gmt_hour < gmt_end);
}
else
{
// 日跨ぎあり(例: 22:00-06:00 など)
return (gmt_hour >= gmt_start || gmt_hour < gmt_end);
}
}
InpGMTOffsetをinputにすることで、夏冬の切替時にEAを止めてパラメータを変えるだけで対応できる。ブローカー変更時も同様で、この設計にしてから運用上のトラブルがほぼなくなった。
TimeGMT()による動的取得
完全自動化したい場合はTimeGMT()を直接使う方法がある。
bool IsInSessionDynamic(int gmt_start, int gmt_end)
{
datetime gmt_now = TimeGMT();
MqlDateTime now;
TimeToStruct(gmt_now, now);
int gmt_hour = now.hour;
if(gmt_start < gmt_end)
return (gmt_hour >= gmt_start && gmt_hour < gmt_end);
else
return (gmt_hour >= gmt_start || gmt_hour < gmt_end); // 日跨ぎ対応
}
TimeGMT()はクライアント端末のローカル時刻をDST込みでGMT換算した値を返す。ブローカーが夏冬を自動調整している環境なら、これがシンプルで堅牢な実装だ。ただしTimeGMT()もティックベースの更新であるため、週末の停止処理との組み合わせには別途注意が必要になる。
金曜夜・週末の安全クローズ実装
「新規エントリー停止」と「既存ポジション強制クローズ」の二段階設計が安全策の基本だ。ポジションループは降順で回す必要があり、昇順ループはインデックスシフトによるスキップ漏れが起きる頻出バグとして知られる。
設計の考え方
週末クローズ機能は「新規エントリーの停止」と「既存ポジションのクローズ」の二段階で設計するのが安全策の基本だ。シンプルだが、これを省くと週明けのギャップを丸ごと食らう。
- 新規エントリー停止: 金曜夜以降はフラグで取引を止める
- 強制クローズ: 指定時刻を過ぎたら全ポジションを成行でクローズ
input int InpFridayCloseHour = 21; // 金曜強制クローズ開始時刻(GMT基準)
input bool InpFridayCloseAll = true; // 既存ポジションも強制クローズするか
//--- 金曜夜クローズ判定
bool IsFridayCloseTime()
{
MqlDateTime now;
TimeToStruct(TimeGMT(), now);
// day_of_week: 0=日, 1=月, ..., 5=金, 6=土
if(now.day_of_week == 5 && now.hour >= InpFridayCloseHour)
return true;
if(now.day_of_week == 6) // 土曜(念のため)
return true;
if(now.day_of_week == 0) // 日曜(月曜オープン前)
return true;
return false;
}
//--- 全ポジション強制クローズ
void CloseAllPositions()
{
CTrade trade;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
// 自分のEAが建てたポジションのみ
if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;
if(!trade.PositionClose(ticket))
{
Print("CloseAllPositions failed: ", GetLastError());
}
}
}
OnTick内での組み込み
void OnTick()
{
//--- 週末クローズ処理
if(IsFridayCloseTime())
{
if(InpFridayCloseAll)
CloseAllPositions();
return; // 新規エントリーへ進まない
}
// 通常の時間フィルター
if(!IsInSessionDynamic(InpLondonStart, InpLondonEnd) &&
!IsInSessionDynamic(InpNYStart, InpNYEnd)) return;
// エントリーロジック
}
ループをPositionsTotal() - 1から降順で回す理由は、クローズ操作によって配列インデックスがシフトするためだ。昇順ループでのスキップは頻出バグで、筆者のコードレビューでも繰り返し見かける。必ず降順にする。
経済指標前後の取引停止パターン
経済指標フィルターは時刻を固定できない性質上、手動フラグによるセーフティモードが最も現実的な実装だ。MQL5経済カレンダーAPIを使う自動化は上級者向けであり、本番投入前に十分な動作検証が必要になる。
実装の難しさ
経済指標前後の停止は、セッション時間フィルターと比べて本質的に難しい。理由は三つある。
- 指標発表時刻は曜日・日付ベースで変動する
- MQL5には公式の経済カレンダー連携があるが、利用には制約がある
- 外部APIとの連携は接続断リスクを伴う
現実的なアプローチ:フラグ変数によるセーフティモード
最も実用的なのは「手動でフラグをtrueにするセーフティモード」だ。地味に見えるが、実際の運用ではこれが一番安定する。自動化への誘惑に負けて複雑な実装を入れるより、シンプルで確実な方を選んでいる。
input bool InpNewsFilter = false; // true=経済指標モード(手動ON)
input int InpNewsBefore = 30; // 指標前停止分数
input int InpNewsAfter = 30; // 指標後停止分数
// グローバル変数
datetime NewsEventTime = 0; // 次の指標発表時刻(手動設定)
//--- セーフティモード判定
bool IsNewsFilterActive()
{
if(!InpNewsFilter) return false;
if(NewsEventTime == 0) return false;
datetime now = TimeGMT();
datetime before_start = NewsEventTime - InpNewsBefore * 60;
datetime after_end = NewsEventTime + InpNewsAfter * 60;
return (now >= before_start && now <= after_end);
}
NewsEventTimeはグローバル変数に手動セットするか、EAのinputにdatetime型パラメータを追加して指定する。
// inputに追加する場合
input datetime InpNextNewsTime = 0; // 次の指標時刻(GMTで入力)
MQL5経済カレンダーAPIの活用(上級者向け)
MT5にはビルトインの経済カレンダーAPIが存在する。
//--- 直近指標の自動取得(概念的な実装例)
bool IsUpcomingHighImpactNews(int minutes_before, int minutes_after)
{
MqlCalendarValue values[];
MqlCalendarEvent event;
datetime from = TimeGMT() - minutes_after * 60;
datetime to = TimeGMT() + minutes_before * 60;
// 高インパクト指標を検索(国コード: USD, EUR等を指定)
if(CalendarValueHistory(values, from, to, "USD") > 0)
{
for(int i = 0; i < ArraySize(values); i++)
{
if(CalendarEventById(values[i].event_id, event))
{
if(event.importance == CALENDAR_IMPORTANCE_HIGH)
return true;
}
}
}
return false;
}
このAPIはMT5のデータフィードに依存し、ブローカーのサーバー設定によって精度が変わる。本番環境への適用前に十分なテストが必要だ。筆者の経験では、重要度の高い指標については手動フラグとの併用が安全と判断している。
完全なサンプルコード(inputパラメータ化済み)
本セクションのサンプルコードは、セッション管理・夏冬オフセット・週末クローズ・経済指標フィルターの全機能を統合したEA骨格だ。エントリーロジックを差し込む箇所を明示しているため、自前の戦略をそのまま組み合わせて使える。
上記要素を統合した完全なサンプルEA骨格を示す。エントリーロジック部分は省略しているが、時間管理に関わる機能は全て入っている。自前の戦略をそのまま差し込んで使えるはずだ。
//+------------------------------------------------------------------+
//| TimeFilterEA.mq5 |
//| 時間フィルター機能を統合したEAサンプル |
//+------------------------------------------------------------------+
#property copyright ""
#property version "1.00"
#include <Trade\Trade.mqh>
//--- インプットパラメータ:セッション設定
input group "=== セッション設定 (GMT基準) ==="
input bool InpUseLondon = true; // ロンドンセッション使用
input int InpLondonStart = 7; // ロンドン開始(GMT hour, 夏:7 冬:8)
input int InpLondonEnd = 16; // ロンドン終了(GMT hour, 夏:16 冬:17)
input bool InpUseNY = true; // NYセッション使用
input int InpNYStart = 13; // NY開始(GMT hour, 冬:13 夏:12)
input int InpNYEnd = 22; // NY終了(GMT hour, 冬:22 夏:21)
input bool InpUseTokyo = false; // 東京セッション使用
input int InpTokyoStart = 0; // 東京開始(GMT hour)
input int InpTokyoEnd = 9; // 東京終了(GMT hour)
//--- インプットパラメータ:週末設定
input group "=== 週末クローズ設定 (GMT基準) ==="
input bool InpFridayClose = true; // 金曜クローズ有効
input int InpFridayCloseHour = 21; // 金曜クローズ開始時刻(GMT hour)
input bool InpCloseAllOnFri = true; // 金曜に全ポジクローズ
//--- インプットパラメータ:指標フィルター
input group "=== 経済指標フィルター ==="
input bool InpNewsFilter = false; // 指標フィルター有効
input datetime InpNextNewsTime = 0; // 次の指標時刻(GMT)
input int InpNewsBefore = 30; // 指標前停止(分)
input int InpNewsAfter = 30; // 指標後停止(分)
//--- EA識別
input int MagicNumber = 123456;
CTrade Trade;
//+------------------------------------------------------------------+
//| セッション内かどうかを判定(GMT基準) |
//+------------------------------------------------------------------+
bool IsInSession(int gmt_start, int gmt_end)
{
MqlDateTime now;
TimeToStruct(TimeGMT(), now);
int h = now.hour;
if(gmt_start < gmt_end)
return (h >= gmt_start && h < gmt_end);
else
return (h >= gmt_start || h < gmt_end); // 日跨ぎ対応
}
//+------------------------------------------------------------------+
//| 現在がアクティブセッションかを返す |
//+------------------------------------------------------------------+
bool IsActiveSession()
{
if(InpUseLondon && IsInSession(InpLondonStart, InpLondonEnd)) return true;
if(InpUseNY && IsInSession(InpNYStart, InpNYEnd)) return true;
if(InpUseTokyo && IsInSession(InpTokyoStart, InpTokyoEnd)) return true;
return false;
}
//+------------------------------------------------------------------+
//| 金曜夜・週末クローズ判定 |
//+------------------------------------------------------------------+
bool IsWeekendClose()
{
if(!InpFridayClose) return false;
MqlDateTime now;
TimeToStruct(TimeGMT(), now);
if(now.day_of_week == 5 && now.hour >= InpFridayCloseHour) return true;
if(now.day_of_week == 6) return true;
if(now.day_of_week == 0) return true;
return false;
}
//+------------------------------------------------------------------+
//| 経済指標フィルター判定 |
//+------------------------------------------------------------------+
bool IsNewsTime()
{
if(!InpNewsFilter || InpNextNewsTime == 0) return false;
datetime gmt_now = TimeGMT();
datetime news_start = InpNextNewsTime - InpNewsBefore * 60;
datetime news_end = InpNextNewsTime + InpNewsAfter * 60;
return (gmt_now >= news_start && gmt_now <= news_end);
}
//+------------------------------------------------------------------+
//| 全ポジション強制クローズ |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
Trade.SetExpertMagicNumber(MagicNumber);
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;
bool result = Trade.PositionClose(ticket);
if(!result)
PrintFormat("CloseAllPositions error: ticket=%I64u err=%d",
ticket, GetLastError());
}
}
//+------------------------------------------------------------------+
//| OnTick メイン処理 |
//+------------------------------------------------------------------+
void OnTick()
{
//--- 週末クローズ処理
if(IsWeekendClose())
{
if(InpCloseAllOnFri)
CloseAllPositions();
return;
}
//--- 経済指標フィルター
if(IsNewsTime()) return;
//--- セッションチェック
if(!IsActiveSession()) return;
//--- ここからエントリーロジック
// (各自の戦略を実装する)
}
//+------------------------------------------------------------------+
//| OnInit |
//+------------------------------------------------------------------+
int OnInit()
{
Trade.SetExpertMagicNumber(MagicNumber);
PrintFormat("TimeFilterEA initialized. GMT now: %s",
TimeToString(TimeGMT(), TIME_DATE | TIME_MINUTES));
return INIT_SUCCEEDED;
}
実装上の補足
Magicナンバーの見落としは意外と多い。複数のEAを同じ口座で動かす場合、Magicナンバーが重複すると別のEAのポジションを誤ってクローズする。EAごとに必ず一意の値を使うこと——これは鉄則だ。
PositionClose()が失敗した場合に無限リトライループを組むのは避けた方がいい。エラーをログに残して次のティックで再試行する設計の方が堅牢で、デバッグも格段に楽になる。
ストラテジーテスター上でのTimeGMT()の挙動はブローカーのデータに依存する点も忘れずに。テスト期間の夏冬設定を意識してセッション時間パラメータを合わせること。
関連記事: MQL5 EAのバックテスト設定完全ガイド——ストラテジーテスターの落とし穴と対処法
よくある質問(FAQ)
Q: VPSに移してから時間フィルターがズレるようになった。なぜか?
A: VPSのシステムタイムゾーンがUTC(または日本標準時以外)に設定されており、TimeLocal()に依存した実装がズレている可能性が高い。TimeCurrent()またはTimeGMT()ベースに変更することで解決する。VPS運用ではTimeLocal()への依存を排除するのが鉄則だ。
Q: TimeCurrent()が週末に更新されないとのことだが、週末の処理はどう書くべきか?
A: TimeGMT()を使うか、OnTimer()内でTimeCurrent()の代わりにTimeGMT()を参照する。あるいはWeekend判定をday_of_weekの値(0=日曜, 6=土曜)で行い、ティックに依存しない設計にする。
Q: ブローカーがGMT+0の場合、セッション時間をそのまま使えるか?
A: GMT+0ブローカーならTimeGMT()とTimeCurrent()が一致するので、セッション時間をGMT基準で書いたコードがそのまま機能する。最もシンプルなケースだ。
Q: 夏冬の切替はどのタイミングでinputを変えればよいか?
A: 欧州夏時間(BST)は毎年3月最終日曜、米国夏時間(EDT)は3月第2日曜に始まる。終了は欧州が10月最終日曜、米国が11月第1日曜だ。多くのブローカーはこれに合わせてサーバー時刻を自動変更するため、サーバーのGMTオフセットが変わる日の前後にinputを確認・変更するのが安全だ。
Q: CTradeSessionのような既製クラスを使うべきか自前実装すべきか?
A: 既製クラス(MQL5標準ライブラリのCTradeSession等)はメンテナンスコストが低く、初学者には推奨できる。ただし内部ロジックを理解せずに使うと、エッジケース(日跨ぎ・週末など)での挙動を把握しきれない。本稿のように自前で書いた実装は挙動が完全に把握できる代わりに、テストの責任も自分にある。どちらが良いかはプロジェクトの規模と運用者のスキルによる。
Q: 時間フィルターを追加したらバックテスト成績が改善されたが、これは過学習の可能性があるか?
A: ある。特定期間のヒストリカルデータに最適化した時間帯設定は、アウトオブサンプル期間で劣化しやすい。「なぜその時間帯に優位性があるのか」という構造的な理由(流動性・指標集中・市場参加者の行動パターン)を説明できない場合は、データマイニングバイアスを疑うべきだ。時間フィルターの設定はバックテスト最適化ではなく、市場構造の理解から導くのが筆者の原則だ。
Q: MQL5の時間フィルター実装をAIと対話しながら設計・テストするツールはあるか?
A: Claudeと会話しながらインジケータが作れるhedgrow-fxはこちら。コード設計の相談からバックテスト条件の壁打ちまで、AIを活用したEA開発をサポートしている。
Claudeと会話しながらインジケータが作れるhedgrow-fxはこちら
免責事項: 本記事はMQL5によるEA開発の技術的な情報提供を目的としており、特定の金融商品への投資を推奨するものではありません。FX取引には元本割れリスクを含む様々なリスクが伴います。過去の実績や技術的手法は将来の成果を保証するものではなく、実際の取引判断はご自身の責任において行ってください。
著者: [著者名プレースホルダー] | アルゴリズムファンドでのシステム開発経験を持つクオンツトレーダー。MQL5によるEA設計・バックテスト検証を専門とする。
