【MQL4リファレンス】MqlDateTime構造体の使い方!時間帯フィルターEAを作る方法

構造体

MqlDateTime構造体とは

MqlDateTime構造体は、日時情報を年・月・日・時・分・秒・曜日・年内通算日といった個別の要素に分解して格納するための構造体です。MQL4では、時間は通常datetime型(1970年1月1日からの経過秒数)で扱われますが、「何時台か?」「何曜日か?」といった判定を行うには、MqlDateTime構造体に変換するのが便利です。

特にEA(エキスパートアドバイザー)開発では、特定の時間帯だけエントリーする金曜日は取引しない月末にポジションを閉じるなど、時間条件のフィルタリングに頻繁に使用されます。

MqlDateTime構造体の定義

MqlDateTime構造体は以下のメンバーで構成されています。

struct MqlDateTime
{
   int year;         // 年(例: 2024)
   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=月曜, ..., 6=土曜)
   int day_of_year;  // 年内通算日(1月1日=0)
};

各メンバーの詳細

メンバー名 説明 値の範囲
year int 西暦年 1970〜
mon int 1〜12
day int 1〜31
hour int 0〜23
min int 0〜59
sec int 0〜59
day_of_week int 曜日(0=日, 1=月, 2=火, 3=水, 4=木, 5=金, 6=土) 0〜6
day_of_year int 年の初日からの通算日数(1月1日=0) 0〜365

datetime型との変換方法

datetime型とMqlDateTime構造体の相互変換には、以下の2つの関数を使用します。

TimeToStruct() ─ datetime → MqlDateTime

// datetime型をMqlDateTime構造体に変換
bool TimeToStruct(
   datetime      dt,       // 変換元のdatetime値
   MqlDateTime&  dt_struct  // 変換先の構造体(参照渡し)
);

戻り値はbool型で、変換に成功すればtrue、失敗すればfalseを返します。

StructToTime() ─ MqlDateTime → datetime

// MqlDateTime構造体をdatetime型に変換
datetime StructToTime(
   MqlDateTime&  dt_struct  // 変換元の構造体(参照渡し)
);

戻り値は変換されたdatetime型の値です。

プログラム例1:基本的な使い方 ─ 現在時刻の各要素を表示する

まずは最もシンプルな使い方です。現在のサーバー時刻をMqlDateTime構造体に変換し、各メンバーの値をチャートに表示します。

//+------------------------------------------------------------------+
//| プログラム例1:現在時刻の分解表示                                    |
//+------------------------------------------------------------------+
#property indicator_chart_window

//+------------------------------------------------------------------+
//| 曜日番号を日本語文字列に変換する関数                                  |
//+------------------------------------------------------------------+
string DayOfWeekToString(int dow)
{
   string days[] = {"日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日"};
   if(dow >= 0 && dow <= 6)
      return days[dow];
   return "不明";
}

//+------------------------------------------------------------------+
//| カスタムインジケーターの初期化関数                                    |
//+------------------------------------------------------------------+
int OnInit()
{
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| メインの計算関数(ティック毎に呼ばれる)                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int 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 int &spread[])
{
   // MqlDateTime構造体の変数を宣言
   MqlDateTime dt;
   
   // 現在のサーバー時刻を取得して構造体に変換
   datetime currentTime = TimeCurrent();
   TimeToStruct(currentTime, dt);
   
   // 各メンバーの値を文字列にまとめる
   string info = "";
   info += "━━━ 現在のサーバー時刻 ━━━\n";
   info += "datetime値: " + TimeToString(currentTime, TIME_DATE|TIME_SECONDS) + "\n";
   info += "年: " + IntegerToString(dt.year) + "\n";
   info += "月: " + IntegerToString(dt.mon) + "\n";
   info += "日: " + IntegerToString(dt.day) + "\n";
   info += "時: " + IntegerToString(dt.hour) + "\n";
   info += "分: " + IntegerToString(dt.min) + "\n";
   info += "秒: " + IntegerToString(dt.sec) + "\n";
   info += "曜日: " + DayOfWeekToString(dt.day_of_week) + " (" + IntegerToString(dt.day_of_week) + ")\n";
   info += "年内通算日: " + IntegerToString(dt.day_of_year) + "\n";
   
   // チャート左上に表示
   Comment(info);
   
   return(rates_total);
}

//+------------------------------------------------------------------+
//| インジケーター削除時にコメントをクリア                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   Comment("");
}

このプログラムをインジケーターとしてチャートに適用すると、サーバー時刻の各要素がリアルタイムで表示されます。TimeToStruct()datetime値と構造体変数を渡すだけで、自動的に年・月・日・時・分・秒・曜日が分解されることが分かります。

プログラム例2:時間帯フィルターの実装 ─ 東京・ロンドン・NY時間を判定する

実際のEA開発で最も多い用途が「特定の時間帯だけ取引する」というフィルター処理です。以下の例では、東京時間・ロンドン時間・ニューヨーク時間を判定する関数を作成します。

//+------------------------------------------------------------------+
//| プログラム例2:主要市場の時間帯判定                                   |
//| ※サーバー時刻がGMT+2(冬)/ GMT+3(夏)前提の例                     |
//+------------------------------------------------------------------+

// 取引を許可する時間帯の設定(サーバー時刻基準)
input int TokyoStartHour    = 2;   // 東京時間 開始(サーバー時刻)
input int TokyoEndHour      = 9;   // 東京時間 終了(サーバー時刻)
input int LondonStartHour   = 10;  // ロンドン時間 開始
input int LondonEndHour     = 18;  // ロンドン時間 終了
input int NewYorkStartHour  = 15;  // ニューヨーク時間 開始
input int NewYorkEndHour    = 23;  // ニューヨーク時間 終了

//+------------------------------------------------------------------+
//| 指定した時間帯内かどうかを判定する汎用関数                            |
//| startHour〜endHourの範囲内であればtrueを返す                        |
//+------------------------------------------------------------------+
bool IsWithinTimeRange(int startHour, int endHour)
{
   MqlDateTime dt;
   TimeToStruct(TimeCurrent(), dt);
   
   // 日付をまたがない通常のケース(例: 10時〜18時)
   if(startHour < endHour)
   {
      return (dt.hour >= startHour && dt.hour < endHour);
   }
   // 日付をまたぐケース(例: 22時〜6時)
   else if(startHour > endHour)
   {
      return (dt.hour >= startHour || dt.hour < endHour);
   }
   
   // startHour == endHour の場合は常にfalse
   return false;
}

//+------------------------------------------------------------------+
//| 現在が東京時間かどうか                                              |
//+------------------------------------------------------------------+
bool IsTokyoSession()
{
   return IsWithinTimeRange(TokyoStartHour, TokyoEndHour);
}

//+------------------------------------------------------------------+
//| 現在がロンドン時間かどうか                                           |
//+------------------------------------------------------------------+
bool IsLondonSession()
{
   return IsWithinTimeRange(LondonStartHour, LondonEndHour);
}

//+------------------------------------------------------------------+
//| 現在がニューヨーク時間かどうか                                       |
//+------------------------------------------------------------------+
bool IsNewYorkSession()
{
   return IsWithinTimeRange(NewYorkStartHour, NewYorkEndHour);
}

//+------------------------------------------------------------------+
//| EAのメインティック処理                                              |
//+------------------------------------------------------------------+
void OnTick()
{
   // 各セッションの状態を取得
   bool tokyo   = IsTokyoSession();
   bool london  = IsLondonSession();
   bool newyork = IsNewYorkSession();
   
   // セッション情報を表示
   string sessionInfo = "【セッション状態】\n";
   sessionInfo += "東京:      " + (tokyo   ? "●アクティブ" : "○非アクティブ") + "\n";
   sessionInfo += "ロンドン:   " + (london  ? "●アクティブ" : "○非アクティブ") + "\n";
   sessionInfo += "ニューヨーク: " + (newyork ? "●アクティブ" : "○非アクティブ") + "\n";
   Comment(sessionInfo);
   
   // 例:ロンドン時間のみエントリーを許可
   if(!london)
   {
      // ロンドン時間外なので何もしない
      return;
   }
   
   // ここにロンドン時間中の取引ロジックを記述
   // ...
}

IsWithinTimeRange()関数は、日付をまたぐ時間帯(例:22時〜翌6時)にも対応しています。MqlDateTimehourメンバーを利用して、サーバー時刻が指定範囲内かどうかを効率的に判定しています。

プログラム例3:曜日フィルターと月末決済の実装

次に、day_of_weekdayメンバーを活用した、曜日フィルターと月末自動決済のサンプルです。

//+------------------------------------------------------------------+
//| プログラム例3:曜日フィルターと月末自動決済                            |
//+------------------------------------------------------------------+

input bool TradeOnMonday    = true;   // 月曜日に取引するか
input bool TradeOnTuesday   = true;   // 火曜日に取引するか
input bool TradeOnWednesday = true;   // 水曜日に取引するか
input bool TradeOnThursday  = true;   // 木曜日に取引するか
input bool TradeOnFriday    = false;  // 金曜日に取引するか(デフォルトOFF)
input int  FridayCloseHour  = 20;    // 金曜日の強制決済時刻

//+------------------------------------------------------------------+
//| 今日が取引許可曜日かどうかを判定                                      |
//+------------------------------------------------------------------+
bool IsTradingDay()
{
   MqlDateTime dt;
   TimeToStruct(TimeCurrent(), dt);
   
   // day_of_week: 0=日, 1=月, 2=火, 3=水, 4=木, 5=金, 6=土
   switch(dt.day_of_week)
   {
      case 1: return TradeOnMonday;
      case 2: return TradeOnTuesday;
      case 3: return TradeOnWednesday;
      case 4: return TradeOnThursday;
      case 5: return TradeOnFriday;
      default: return false;  // 土日は常に取引しない
   }
}

//+------------------------------------------------------------------+
//| 金曜日の指定時刻以降かどうかを判定(週末リスク回避用)                  |
//+------------------------------------------------------------------+
bool IsFridayCloseTime()
{
   MqlDateTime dt;
   TimeToStruct(TimeCurrent(), dt);
   
   // 金曜日(day_of_week == 5)かつ指定時刻以降
   if(dt.day_of_week == 5 && dt.hour >= FridayCloseHour)
      return true;
   
   return false;
}

//+------------------------------------------------------------------+
//| 今月の最終営業日かどうかを判定                                       |
//+------------------------------------------------------------------+
bool IsLastBusinessDayOfMonth()
{
   MqlDateTime dt;
   TimeToStruct(TimeCurrent(), dt);
   
   // 各月の最終日を定義(配列インデックス0は未使用)
   int daysInMonth[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
   
   // うるう年の判定(2月の日数を修正)
   if(dt.mon == 2)
   {
      if((dt.year % 4 == 0 && dt.year % 100 != 0) || (dt.year % 400 == 0))
         daysInMonth[2] = 29;
   }
   
   int lastDay = daysInMonth[dt.mon];
   
   // 月末の最終営業日を逆算(土日を除く)
   // lastDayの曜日を計算するため、一時的にdatetimeを生成
   MqlDateTime tempDt;
   tempDt.year = dt.year;
   tempDt.mon  = dt.mon;
   tempDt.day  = lastDay;
   tempDt.hour = 12;
   tempDt.min  = 0;
   tempDt.sec  = 0;
   
   datetime lastDayTime = StructToTime(tempDt);
   TimeToStruct(lastDayTime, tempDt);  // 曜日を再計算
   
   // 最終日が土曜なら金曜(-1日)、日曜なら金曜(-2日)
   int lastBusinessDay = lastDay;
   if(tempDt.day_of_week == 6)       // 土曜
      lastBusinessDay = lastDay - 1;
   else if(tempDt.day_of_week == 0)  // 日曜
      lastBusinessDay = lastDay - 2;
   
   return (dt.day == lastBusinessDay);
}

//+------------------------------------------------------------------+
//| 全ポジションを決済する関数                                           |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if(OrderSymbol() == Symbol() && OrderMagicNumber() == 12345)
         {
            if(OrderType() == OP_BUY)
               OrderClose(OrderTicket(), OrderLots(), Bid, 3, clrRed);
            else if(OrderType() == OP_SELL)
               OrderClose(OrderTicket(), OrderLots(), Ask, 3, clrBlue);
         }
      }
   }
}

//+------------------------------------------------------------------+
//| EAのメインティック処理                                              |
//+------------------------------------------------------------------+
void OnTick()
{
   // 金曜日の指定時刻を超えたら全ポジション決済
   if(IsFridayCloseTime())
   {
      CloseAllPositions();
      Comment("金曜日 " + IntegerToString(FridayCloseHour) + "時以降のため全決済済み");
      return;
   }
   
   // 月末最終営業日も全ポジション決済
   if(IsLastBusinessDayOfMonth())
   {
      CloseAllPositions();
      Comment("月末最終営業日のため全決済済み");
      return;
   }
   
   // 取引許可曜日でなければ新規エントリーしない
   if(!IsTradingDay())
   {
      Comment("本日は取引対象外の曜日です");
      return;
   }
   
   // ここに通常の取引ロジックを記述
   Comment("取引許可中");
   // ...
}

この例では3つの重要なテクニックを示しています。day_of_weekswitch文で分岐させる曜日フィルター、hourday_of_weekを組み合わせた金曜決済、そしてStructToTime()を使って任意の日付のdatetime値を生成し曜日を逆算する月末判定です。

プログラム例4:複合時間フィルターEA ─ 時間帯・曜日・重要イベント前を統合管理

最後に、これまでの要素を統合した実践的な複合フィルターEAのテンプレートです。

//+------------------------------------------------------------------+
//| プログラム例4:複合時間フィルターEA                                   |
//| 時間帯・曜日・特定日を統合的にフィルタリングする                       |
//+------------------------------------------------------------------+
#property strict

// === 時間帯フィルター設定 ===
input string   _TimeFilter_     = "=== 時間帯フィルター ===";   // セパレータ
input int      TradeStartHour   = 10;    // エントリー開始時刻(時)
input int      TradeStartMin    = 30;    // エントリー開始時刻(分)
input int      TradeEndHour     = 20;    // エントリー終了時刻(時)
input int      TradeEndMin      = 0;     // エントリー終了時刻(分)

// === 曜日フィルター設定 ===
input string   _DayFilter_      = "=== 曜日フィルター ===";     // セパレータ
input bool     AllowMonday      = true;  // 月曜の取引を許可
input bool     AllowTuesday     = true;  // 火曜の取引を許可
input bool     AllowWednesday   = true;  // 水曜の取引を許可
input bool     AllowThursday    = true;  // 木曜の取引を許可
input bool     AllowFriday      = true;  // 金曜の取引を許可

// === 特殊ルール設定 ===
input string   _SpecialRules_   = "=== 特殊ルール ===";         // セパレータ
input bool     AvoidMonthEnd    = true;  // 月末(最終3日間)は取引しない
input bool     AvoidYearEnd     = true;  // 年末年始(12/25〜1/3)は取引しない
input int      MagicNumber      = 20240101; // マジックナンバー

//+------------------------------------------------------------------+
//| フィルター結果を格納する構造体                                       |
//+------------------------------------------------------------------+
struct FilterResult
{
   bool  isAllowed;     // 取引許可されているか
   string reason;       // 不許可の理由(許可の場合は空文字列)
};

//+------------------------------------------------------------------+
//| 時刻を「時×60+分」の数値に変換する補助関数                           |
//+------------------------------------------------------------------+
int TimeToMinutes(int hour, int minute)
{
   return hour * 60 + minute;
}

//+------------------------------------------------------------------+
//| 時間帯フィルターのチェック                                           |
//+------------------------------------------------------------------+
bool CheckTimeFilter(MqlDateTime &dt, string &reason)
{
   int currentMinutes = TimeToMinutes(dt.hour, dt.min);
   int startMinutes   = TimeToMinutes(TradeStartHour, TradeStartMin);
   int endMinutes     = TimeToMinutes(TradeEndHour, TradeEndMin);
   
   // 日付をまたがないケース
   if(startMinutes < endMinutes)
   {
      if(currentMinutes < startMinutes || currentMinutes >= endMinutes)
      {
         reason = StringFormat("時間外(許可: %02d:%02d〜%02d:%02d)",
                               TradeStartHour, TradeStartMin,
                               TradeEndHour, TradeEndMin);
         return false;
      }
   }
   // 日付をまたぐケース
   else if(startMinutes > endMinutes)
   {
      if(currentMinutes < startMinutes && currentMinutes >= endMinutes)
      {
         reason = StringFormat("時間外(許可: %02d:%02d〜%02d:%02d)",
                               TradeStartHour, TradeStartMin,
                               TradeEndHour, TradeEndMin);
         return false;
      }
   }
   
   return true;
}

//+------------------------------------------------------------------+
//| 曜日フィルターのチェック                                             |
//+------------------------------------------------------------------+
bool CheckDayFilter(MqlDateTime &dt, string &reason)
{
   // 曜日ごとの許可フラグを配列にまとめる
   bool dayAllowed[7];
   dayAllowed[0] = false;          // 日曜(常に不許可)
   dayAllowed[1] = AllowMonday;
   dayAllowed[2] = AllowTuesday;
   dayAllowed[3] = AllowWednesday;
   dayAllowed[4] = AllowThursday;
   dayAllowed[5] = AllowFriday;
   dayAllowed[6] = false;          // 土曜(常に不許可)
   
   string dayNames[] = {"日","月","火","水","木","金","土"};
   
   if(!dayAllowed[dt.day_of_week])
   {
      reason = dayNames[dt.day_of_week] + "曜日は取引不許可";
      return false;
   }
   
   return true;
}

//+------------------------------------------------------------------+
//| 特殊日フィルターのチェック(月末・年末年始)                           |
//+------------------------------------------------------------------+
bool CheckSpecialDateFilter(MqlDateTime &dt, string &reason)
{
   // 月末チェック(最終3日間)
   if(AvoidMonthEnd)
   {
      int daysInMonth[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
      
      // うるう年判定
      if(dt.mon == 2 && ((dt.year % 4 == 0 && dt.year % 100 != 0) || (dt.year % 400 == 0)))
         daysInMonth[2] = 29;
      
      if(dt.day > daysInMonth[dt.mon] - 3)
      {
         reason = "月末期間(最終3日間)のため取引停止";
         return false;
      }
   }
   
   // 年末年始チェック(12/25〜1/3)
   if(AvoidYearEnd)
   {
      if((dt.mon == 12 && dt.day >= 25) || (dt.mon == 1 && dt.day <= 3))
      {
         reason = "年末年始期間のため取引停止";
         return false;
      }
   }
   
   return true;
}

//+------------------------------------------------------------------+
//| 全フィルターを統合チェック                                           |
//+------------------------------------------------------------------+
FilterResult CheckAllFilters()
{
   FilterResult result;
   result.isAllowed = true;
   result.reason    = "";
   
   MqlDateTime dt;
   TimeToStruct(TimeCurrent(), dt);
   
   string reason = "";
   
   // 曜日チェック(最優先)
   if(!CheckDayFilter(dt, reason))
   {
      result.isAllowed = false;
      result.reason    = reason;
      return result;
   }
   
   // 特殊日チェック
   if(!CheckSpecialDateFilter(dt, reason))
   {
      result.isAllowed = false;
      result.reason    = reason;
      return result;
   }
   
   // 時間帯チェック
   if(!CheckTimeFilter(dt, reason))
   {
      result.isAllowed = false;
      result.reason    = reason;
      return result;
   }
   
   return result;
}

//+------------------------------------------------------------------+
//| EAの初期化関数                                                     |
//+------------------------------------------------------------------+
int OnInit()
{
   Print("複合時間フィルターEA 初期化完了");
   Print("取引時間帯: ", StringFormat("%02d:%02d〜%02d:%02d",
         TradeStartHour, TradeStartMin, TradeEndHour, TradeEndMin));
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| EAのメインティック処理                                              |
//+------------------------------------------------------------------+
void OnTick()
{
   // 全フィルターの統合チェック
   FilterResult filter = CheckAllFilters();
   
   // 現在時刻情報を取得して表示用に整形
   MqlDateTime dt;
   TimeToStruct(TimeCurrent(), dt);
   string dayNames[] = {"日","月","火","水","木","金","土"};
   
   string display = "";
   display += "━━━ 複合時間フィルターEA ━━━\n";
   display += StringFormat("サーバー時刻: %04d/%02d/%02d (%s) %02d:%02d:%02d\n",
                           dt.year, dt.mon, dt.day,
                           dayNames[dt.day_of_week],
                           dt.hour, dt.min, dt.sec);
   
   if(filter.isAllowed)
   {
      display += "状態: ✔ 取引許可中\n";
      
      // ここに取引ロジックを記述
      // 例: シグナル判定、エントリー処理など
   }
   else
   {
      display += "状態: ✖ 取引停止中\n";
      display += "理由: " + filter.reason + "\n";
   }
   
   Comment(display);
}

//+------------------------------------------------------------------+
//| EA削除時の処理                                                     |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   Comment("");
   Print("複合時間フィルターEA 削除完了");
}

このプログラム例4では、フィルター結果を専用のFilterResult構造体に格納することで、取引の可否だけでなく不許可の理由も管理できるようにしています。各フィルター関数は独立しているため、必要に応じて個別に有効・無効を切り替えたり、新しいフィルター条件を追加したりすることが容易です。

MqlDateTime構造体を使う際の注意点

サーバー時刻とローカル時刻の違い

TimeCurrent()はブローカーのサーバー時刻を返します。日本時間とは異なる場合がほとんどです。時間帯フィルターを設定する際は、必ずサーバー時刻のタイムゾーンを確認してください。ローカル時刻が必要な場合はTimeLocal()を使用します。

day_of_yearの開始値

day_of_yearは1月1日が0から始まります。1月2日は1、12月31日は364(うるう年は365)となります。直感的に「1月1日=1日目」と考えがちなので注意が必要です。

StructToTime()使用時の曜日自動計算

StructToTime()datetimeに変換する際、day_of_weekday_of_yearの値は無視されます。逆に、TimeToStruct()で変換した場合は自動的に正しい値が設定されます。プログラム例3の月末判定のように、StructToTime()TimeToStruct()と変換することで、任意の日付の曜日を取得できます。

まとめ

MqlDateTime構造体は、datetime型の時刻データを人間が理解しやすい個別の要素に分解するための構造体です。TimeToStruct()StructToTime()の2つの変換関数を使うことで、時間帯フィルター・曜日フィルター・月末判定など、EA開発で頻出する時間条件のロジックを効率的に実装できます。