ブログタイトルを「FX自動売買システム開発部」から「autoFX」に変更しました!

サマータイムを考慮した自動GMT設定関数

本記事で公開しているライブラリーは公開当時のままで、古くなっております。最新版のライブラリーはメルマガ登録いただくことでダウンロードできます。

自動GMT設定のポイントはサーバーとの時差の計算

※2016/8/6 kumaさんのご指摘でcalcTimeDifference()に不具合があることが判明。修正しました。

為替相場では切っても切り離せない問題。その1つが時差です。

EAによる自動売買でも、時間帯によるエントリー制限等でサーバーとの時差を考慮しなければならない時があります。

サーバーとの時差を考慮する際、気をつけなければならないのがサマータイムです。FX業者が採用しているサマータイムには、主に英国式と米国式があります。

そこで、この2種類のサマータイムを考慮した自動GMT設定関数を紹介します。

//+------------------------------------------------------------------+
//|【関数】ローカルタイムのGMTオフセット値を取得する                 |
//|                                                                  |
//|【引数】なし                                                      |
//|                                                                  |
//|【戻値】GMTオフセット値                                           |
//|                                                                  |
//|【備考】MT4を実行するPCのローカルタイムが日本に設定されている場合 |
//|       「9」を返す。                                              |
//+------------------------------------------------------------------+
int getLocalTimeGMT_Offset()
{
  int timeZoneInfo[43];

  // MT4を実行するPCのタイムゾーン取得
  // GetTimeZoneInformation()関数はWindows API
  int tzType = GetTimeZoneInformation(timeZoneInfo);

  // 日本の場合、gTimeZoneInfo[0] = -540 = -9 * 60
  int gmtOffset = timeZoneInfo[0] / 60;

  // 現在サマータイム期間か?
  if(tzType == TIME_ZONE_ID_DAYLIGHT){
    // gTimeZoneInfo[42]はサマータイムで変化する時間(通常=-60分)
    gmtOffset += timeZoneInfo[42] / 60;
  }

  gmtOffset = -gmtOffset;

  return(gmtOffset);
}

//+------------------------------------------------------------------+
//|【関数】ローカルタイムとサーバタイムの時差を計算する              |
//|                                                                  |
//|【引数】 IN OUT  引数名             説明                          |
//|        --------------------------------------------------------- |
//|         ○      aUseAutoGMT_Flg    自動GMT設定有効フラグ         |
//|         ○      aSummerTimeType    サマータイム区分              |
//|         ○      aSummerGMT_Offset  サマータイム時のGMTオフセット値    |
//|         ○      aWinterGMT_Offset  標準時のGMTオフセット値            |
//|         ○      aLocalGMT_Offset   ローカルタイムのGMTオフセット値           |
//|                                                                  |
//|【戻値】ローカルタイムとサーバタイムの時差                        |
//|                                                                  |
//|【備考】なし                                                      |
//+------------------------------------------------------------------+
int calcTimeDifference(bool aUseAutoGMT_Flg, int aSummerTimeType, int aSummerGMT_Offset, int aWinterGMT_Offset, int aLocalGMT_Offset)
{
  int serverGMT_Offset = 0;

  if(aUseAutoGMT_Flg == false || IsTesting() || IsOptimization()){
    bool result = isSummerTime(aSummerTimeType);

    // サマータイムの場合
    if(result){
      int tmp = aSummerGMT_Offset;
    }else{
      tmp = aWinterGMT_Offset;
    }

    serverGMT_Offset = tmp;
  }else{
    // ローカルタイムがサーバタイムより少し遅い場合、1時間不足する不具合が発生。
    // TimeLocal()                 = 2016.08.06 23:25:26
    // TimeCurrent()               = 2016.08.05 10:25:46
    // TimeLocal() - TimeCurrent() = 1970.01.01 12:59:40
    // 時差 = 12h ※本来は13h。TimeHour()で時間hだけを取得するため、59分40秒が切り捨て。結果として、1時間不足する。
    datetime difTime = TimeLocal() - TimeCurrent();

    // それを解消するため、分が30以上なら1時間加算することで暫定対処とする。
    // 美しいロジックではないが、別案が思い浮かばないため。
    if(TimeMinute(difTime) >= 30){
      serverGMT_Offset = aLocalGMT_Offset - (TimeHour(difTime) + 1);
    }else{
      serverGMT_Offset = aLocalGMT_Offset - TimeHour(difTime);
    }
  }

  return(aLocalGMT_Offset - serverGMT_Offset);
}

//+------------------------------------------------------------------+
//|【関数】FX業者のサーバーがサマータイム期間かどうかを判断する      |
//|                                                                  |
//|【引数】 IN OUT  引数名             説明                          |
//|        --------------------------------------------------------- |
//|         ○      aSummerTimeType    サマータイム区分              |
//|                                                                  |
//|【戻値】true :夏時間                                             |
//|        false:冬時間(標準時間)                                 |
//|                                                                  |
//|【備考】英国夏時間:3月最終日曜AM1:00~10月最終日曜AM1:00         |
//|        米国夏時間:3月第2日曜AM2:00~11月第1日曜AM2:00           |
//+------------------------------------------------------------------+
bool isSummerTime(int aSummerTimeType)
{
  int year  = Year();
  int month = Month();
  int day   = Day();
  int w     = DayOfWeek();
  int hour  = Hour();
  int startMonth;
  int startN;
  int startW;
  int startHour;
  int endMonth;
  int endN;
  int endW;
  int endHour;
  int w1;     // month月1日の曜日
  int dstDay; // 夏時間開始または終了日付

  // 英国夏時間
  if(aSummerTimeType == 1){
    startMonth = 3;
    startW     = 0;
    startHour  = 1;

    int dayOfWeek = TimeDayOfWeek(StrToTime(year + "/" + startMonth + "/01"));

    int tmpDay = NthDayOfWeekToDay(5, 0, dayOfWeek);

    if(tmpDay >= 1 && tmpDay <= 31){
      startN = 5;
    }else{
      startN = 4;
    }

    endMonth   = 10;
    endW       = 0;
    endHour    = 1; // 2時になった瞬間に1時に戻るので「1」

    dayOfWeek = TimeDayOfWeek(StrToTime(year + "/" + endMonth + "/01"));

    tmpDay = NthDayOfWeekToDay(5, 0, dayOfWeek);

    if(tmpDay >= 1 && tmpDay <= 31){
      endN = 5;
    }else{
      endN = 4;
    }
  // 米国夏時間
  }else if(aSummerTimeType == 2){
    if(year <= 2006){
      startMonth = 4;
      startN     = 1;
      startW     = 0;
      startHour  = 2;

      endMonth   = 10;
      endW       = 0;
      endHour    = 1; // 2時になった瞬間に1時に戻るので「1」

      dayOfWeek = TimeDayOfWeek(StrToTime(year + "/" + endMonth + "/01"));

      tmpDay = NthDayOfWeekToDay(5, 0, dayOfWeek);

      if(tmpDay >= 1 && tmpDay <= 31){
        endN = 5;
      }else{
        endN = 4;
      }
    }else{
      startMonth = 3;
      startN     = 2;
      startW     = 0;
      startHour  = 2;

      endMonth   = 11;
      endN       = 1;
      endW       = 0;
      endHour    = 1;
    }
  // サマータイムなし
  }else{
    return(false);
  }

  if(month < startMonth || endMonth < month){
    return(false);
  }

  // month月1日の曜日w1を求める.day=1 ならば w1=w で,
  // dayが1日増えるごとにw1は1日前にずれるので,数学的には
  //   w1 = (w - (day - 1)) mod 7
  // しかしC言語の場合は被除数が負になるとまずいので,
  // 負にならないようにするための最小の7の倍数35を足して
  w1 = (w + 36 - day) % 7;

  if(month == startMonth){
    // month月のstartN回目のstartW曜日の日付dstDayを求める.
    dstDay = NthDayOfWeekToDay(startN, startW, w1);

    // (day, hour) が (dstDay, startHour) より前ならば夏時間ではない
    if(day < dstDay || (day == dstDay && hour < startHour)){
      return(false);
    }
  }

  if(month == endMonth){
    // month月のendN回目のendW曜日の日付dstDayを求める
    dstDay = NthDayOfWeekToDay(endN, endW, w1);

    // (day, hour) が (dstDay, startHour) 以後ならば夏時間ではない
    if(day > dstDay || (day == dstDay && hour >= endHour)){
      return(false);
    }
  }

  return(true);
}

//+------------------------------------------------------------------+
//|【関数】ある月のn回目のdow曜日の日付を求める                      |
//|                                                                  |
//|【引数】 IN OUT  引数名             説明                          |
//|        --------------------------------------------------------- |
//|         ○      n                  n週目(1~5)                 |
//|         ○      dow                曜日(0:日曜,…,6:土曜)  |
//|         ○      dow1               その月の1日の曜日             |
//|                                                                  |
//|【戻値】その月のn回目のdow曜日の日にち	                         |
//|                                                                  |
//|【備考】2007/3:1日は木曜(4)で,第3金曜(5)は16日                  |
//|        NthDayOfWeekToDay(3, 5, 4) = 16                           |
//+------------------------------------------------------------------+
int NthDayOfWeekToDay(int n, int dow, int dow1)
{
  int day;

  // day ← (最初の dow 曜日の日付)-1
  if(dow < dow1){
    dow += 7;
  }

  day = dow - dow1;

  // day ← n回目の dow 曜日の日付 (day + 7 * (n - 1) + 1)
  day += 7 * n - 6;

  return(day);
}

上記4つの関数を使って自動GMT設定を実現しています。結構複雑な作りになっていますね。

最初のgetLocalTimeGMT_Offset()関数は、ローカルタイム(MT4を実行するPC)のGMTオフセットを取得する関数です。このブログをご覧の方のPCはほぼ日本にい設定されているでしょうから、「9」になります。

getLocalTimeGMT_Offset()関数内では、Windows APIであるGetTimeZoneInformation()関数を用いています。そのため、実際にgetLocalTimeGMT_Offset()関数を使う際は、EAの冒頭に

// Windows API
#import "kernel32.dll"
  int GetTimeZoneInformation(int& tzinfo[]);
#import

を記述する必要があります。これによって、kernel32.dllに含まれているGetTimeZoneInformation()関数をMQL4内に取り込んだことになり、MQL4内で自由に使えるようになるわけです。

次のcalcTimeDifference()関数は、サーバーとの時差を求める関数になっています。UseAutoGMT_Flgがfalse(自動GMT設定をオフ)か、バックテストか、最適化のいずれかの場合、以下のサマータイム期間判定関数を呼び出してから時差を計算します。そうでない場合(フォワードテストか実運用)は、MQL4の標準関数を使って時差を計算します。

3つ目のisSummerTime()関数は、サマータイム期間判定関数です。英国式と米国式の2つに対応しています。サマータイム期間ならtrue、そうでないならfalseを返します。この関数は人力検索はてなの回答を参考にして作成しています。

最後のNthDayOfWeekToDay()関数は、isSummerTime()関数の中で日にちを計算するのに利用しています。この関数も10.3 ○月のN回目のW曜日は何日?をもとに作成しています。

先人の知恵を借りよう

プログラミングは何も全て自分で考えて作る必要はありません。インターネット上には先人の知恵が無数に転がっています。良いものはどんどん真似させてもらいましょう!「学ぶ」は「真似る」から来ていると言われますし。

ポイントです!
吹き出し

サンプルEA

関数だけではイメージが沸きづらいと思いますので、サンプルEAを載せておきます。

//+------------------------------------------------------------------+
//|                                                       Sample.mq4 |
//|                                     Copyright (c) 2015, りゅーき |
//|                                            https://autofx100.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright (c) 2015, りゅーき"
#property link      "https://autofx100.com/"
#property version   "1.00"

//+------------------------------------------------------------------+
//| ライブラリ                                                       |
//+------------------------------------------------------------------+
#include <stderror.mqh>
#include <stdlib.mqh>
#include <WinUser32.mqh>

//+------------------------------------------------------------------+
//| インポート                                                       |
//+------------------------------------------------------------------+
// Windows API
#import "kernel32.dll"
  int GetTimeZoneInformation(int& tzinfo[]);
#import

//+------------------------------------------------------------------+
//| 定数定義                                                         |
//+------------------------------------------------------------------+
// 自動GMT設定
#define TIME_ZONE_ID_UNKNOWN  0 // 不明
#define TIME_ZONE_ID_STANDARD 1 // 標準
#define TIME_ZONE_ID_DAYLIGHT 2 // サマータイム

//+------------------------------------------------------------------+
//| EAパラメータ設定情報                                             |
//+------------------------------------------------------------------+
extern string Note01           = "--- GMT ------------------------------------------------------";
extern string Note01_1         = "True:Auto  False:Manual";
extern bool   UseAutoGMT_Flg   = true;
extern string Note01_2         = "0:Not Summer Time  1:London  2:N.Y.";
extern int    SummerTimeType   = 2;
extern int    SummerGMT_Offset = 3;
extern int    WinterGMT_Offset = 2;

//+------------------------------------------------------------------+
//| グローバル変数                                                   |
//+------------------------------------------------------------------+
// 自動GMT設定
int gLocalGMT_Offset = 0;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
  // 自動GMT設定
  gLocalGMT_Offset = getLocalTimeGMT_Offset();

  Print("ローカルタイムのGMTオフセット = ", gLocalGMT_Offset);

  return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
  // -----------------------------------------------------------------
  // 自動GMT設定
  // -----------------------------------------------------------------
  int timeDiff = calcTimeDifference(UseAutoGMT_Flg, SummerTimeType, SummerGMT_Offset, WinterGMT_Offset, gLocalGMT_Offset);

  Print("時差 = ", timeDiff, "h");
}

//+------------------------------------------------------------------+
//|【関数】ローカルタイムのGMTオフセット値を取得する                 |
//|                                                                  |
//|【引数】なし                                                      |
//|                                                                  |
//|【戻値】GMTオフセット値                                           |
//|                                                                  |
//|【備考】MT4を実行するPCのローカルタイムが日本に設定されている場合 |
//|       「9」を返す。                                              |
//+------------------------------------------------------------------+
int getLocalTimeGMT_Offset()
{
  int timeZoneInfo[43];

  // MT4を実行するPCのタイムゾーン取得
  // GetTimeZoneInformation()関数はWindows API
  int tzType = GetTimeZoneInformation(timeZoneInfo);

  // 日本の場合、gTimeZoneInfo[0] = -540 = -9 * 60
  int gmtOffset = timeZoneInfo[0] / 60;

  // 現在サマータイム期間か?
  if(tzType == TIME_ZONE_ID_DAYLIGHT){
    // gTimeZoneInfo[42]はサマータイムで変化する時間(通常=-60分)
    gmtOffset += timeZoneInfo[42] / 60;
  }

  gmtOffset = -gmtOffset;

  return(gmtOffset);
}

//+------------------------------------------------------------------+
//|【関数】ローカルタイムとサーバタイムの時差を計算する              |
//|                                                                  |
//|【引数】 IN OUT  引数名             説明                          |
//|        --------------------------------------------------------- |
//|         ○      aUseAutoGMT_Flg    自動GMT設定有効フラグ         |
//|         ○      aSummerTimeType    サマータイム区分              |
//|         ○      aSummerGMT_Offset  サマータイム時のGMTオフセット値    |
//|         ○      aWinterGMT_Offset  標準時のGMTオフセット値            |
//|         ○      aLocalGMT_Offset   ローカルタイムのGMTオフセット値           |
//|                                                                  |
//|【戻値】ローカルタイムとサーバタイムの時差                        |
//|                                                                  |
//|【備考】なし                                                      |
//+------------------------------------------------------------------+
int calcTimeDifference(bool aUseAutoGMT_Flg, int aSummerTimeType, int aSummerGMT_Offset, int aWinterGMT_Offset, int aLocalGMT_Offset)
{
  int serverGMT_Offset = 0;

  if(aUseAutoGMT_Flg == false || IsTesting() || IsOptimization()){
    bool result = isSummerTime(aSummerTimeType);

    // サマータイムの場合
    if(result){
      int tmp = aSummerGMT_Offset;
    }else{
      tmp = aWinterGMT_Offset;
    }

    serverGMT_Offset = tmp;
  }else{
    // ローカルタイムがサーバタイムより少し遅い場合、1時間不足する不具合が発生。
    // TimeLocal()                 = 2016.08.06 23:25:26
    // TimeCurrent()               = 2016.08.05 10:25:46
    // TimeLocal() - TimeCurrent() = 1970.01.01 12:59:40
    // 時差 = 12h ※本来は13h。TimeHour()で時間hだけを取得するため、59分40秒が切り捨て。結果として、1時間不足する。
    datetime difTime = TimeLocal() - TimeCurrent();

    // それを解消するため、分が30以上なら1時間加算することで暫定対処とする。
    // 美しいロジックではないが、別案が思い浮かばないため。
    if(TimeMinute(difTime) >= 30){
      serverGMT_Offset = aLocalGMT_Offset - (TimeHour(difTime) + 1);
    }else{
      serverGMT_Offset = aLocalGMT_Offset - TimeHour(difTime);
    }
  }

  return(aLocalGMT_Offset - serverGMT_Offset);
}

//+------------------------------------------------------------------+
//|【関数】FX業者のサーバーがサマータイム期間かどうかを判断する      |
//|                                                                  |
//|【引数】 IN OUT  引数名             説明                          |
//|        --------------------------------------------------------- |
//|         ○      aSummerTimeType    サマータイム区分              |
//|                                                                  |
//|【戻値】true :夏時間                                             |
//|        false:冬時間(標準時間)                                 |
//|                                                                  |
//|【備考】英国夏時間:3月最終日曜AM1:00~10月最終日曜AM1:00         |
//|        米国夏時間:3月第2日曜AM2:00~11月第1日曜AM2:00           |
//+------------------------------------------------------------------+
bool isSummerTime(int aSummerTimeType)
{
  int year  = Year();
  int month = Month();
  int day   = Day();
  int w     = DayOfWeek();
  int hour  = Hour();
  int startMonth;
  int startN;
  int startW;
  int startHour;
  int endMonth;
  int endN;
  int endW;
  int endHour;
  int w1;     // month月1日の曜日
  int dstDay; // 夏時間開始または終了日付

  // 英国夏時間
  if(aSummerTimeType == 1){
    startMonth = 3;
    startW     = 0;
    startHour  = 1;

    int dayOfWeek = TimeDayOfWeek(StrToTime(year + "/" + startMonth + "/01"));

    int tmpDay = NthDayOfWeekToDay(5, 0, dayOfWeek);

    if(tmpDay >= 1 && tmpDay <= 31){
      startN = 5;
    }else{
      startN = 4;
    }

    endMonth   = 10;
    endW       = 0;
    endHour    = 1; // 2時になった瞬間に1時に戻るので「1」

    dayOfWeek = TimeDayOfWeek(StrToTime(year + "/" + endMonth + "/01"));

    tmpDay = NthDayOfWeekToDay(5, 0, dayOfWeek);

    if(tmpDay >= 1 && tmpDay <= 31){
      endN = 5;
    }else{
      endN = 4;
    }
  // 米国夏時間
  }else if(aSummerTimeType == 2){
    if(year <= 2006){
      startMonth = 4;
      startN     = 1;
      startW     = 0;
      startHour  = 2;

      endMonth   = 10;
      endW       = 0;
      endHour    = 1; // 2時になった瞬間に1時に戻るので「1」

      dayOfWeek = TimeDayOfWeek(StrToTime(year + "/" + endMonth + "/01"));

      tmpDay = NthDayOfWeekToDay(5, 0, dayOfWeek);

      if(tmpDay >= 1 && tmpDay <= 31){
        endN = 5;
      }else{
        endN = 4;
      }
    }else{
      startMonth = 3;
      startN     = 2;
      startW     = 0;
      startHour  = 2;

      endMonth   = 11;
      endN       = 1;
      endW       = 0;
      endHour    = 1;
    }
  // サマータイムなし
  }else{
    return(false);
  }

  if(month < startMonth || endMonth < month){
    return(false);
  }

  // month月1日の曜日w1を求める.day=1 ならば w1=w で,
  // dayが1日増えるごとにw1は1日前にずれるので,数学的には
  //   w1 = (w - (day - 1)) mod 7
  // しかしC言語の場合は被除数が負になるとまずいので,
  // 負にならないようにするための最小の7の倍数35を足して
  w1 = (w + 36 - day) % 7;

  if(month == startMonth){
    // month月のstartN回目のstartW曜日の日付dstDayを求める.
    dstDay = NthDayOfWeekToDay(startN, startW, w1);

    // (day, hour) が (dstDay, startHour) より前ならば夏時間ではない
    if(day < dstDay || (day == dstDay && hour < startHour)){
      return(false);
    }
  }

  if(month == endMonth){
    // month月のendN回目のendW曜日の日付dstDayを求める
    dstDay = NthDayOfWeekToDay(endN, endW, w1);

    // (day, hour) が (dstDay, startHour) 以後ならば夏時間ではない
    if(day > dstDay || (day == dstDay && hour >= endHour)){
      return(false);
    }
  }

  return(true);
}

//+------------------------------------------------------------------+
//|【関数】ある月のn回目のdow曜日の日付を求める                      |
//|                                                                  |
//|【引数】 IN OUT  引数名             説明                          |
//|        --------------------------------------------------------- |
//|         ○      n                  n週目(1~5)                 |
//|         ○      dow                曜日(0:日曜,…,6:土曜)  |
//|         ○      dow1               その月の1日の曜日             |
//|                                                                  |
//|【戻値】その月のn回目のdow曜日の日にち	                         |
//|                                                                  |
//|【備考】2007/3:1日は木曜(4)で,第3金曜(5)は16日                  |
//|        NthDayOfWeekToDay(3, 5, 4) = 16                           |
//+------------------------------------------------------------------+
int NthDayOfWeekToDay(int n, int dow, int dow1)
{
  int day;

  // day ← (最初の dow 曜日の日付)-1
  if(dow < dow1){
    dow += 7;
  }

  day = dow - dow1;

  // day ← n回目の dow 曜日の日付 (day + 7 * (n - 1) + 1)
  day += 7 * n - 6;

  return(day);
}

MQL4プログラミングの最新記事8件

>完全放ったらかしEA 「AutoEndlessCatchRange」

完全放ったらかしEA 「AutoEndlessCatchRange」

「本業が忙しい!」「でも資産運用したい!」そんなあなたに最適なEAです。兼業トレーダーの方はチャートを毎日みて分析してトレードする時間はなかなか確保できないものです。トレードは本EAに任せて、本業やプライベートの時間をもっと増やしませんか?元々は自分自身のために開発したEAですので、手抜き無しのガチものです。

CTR IMG