本記事で公開しているライブラリーは公開当時のままで、古くなっております。最新版のライブラリーはメルマガ登録いただくことでダウンロードできます。
自動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); }