【MQL4】GMTオフセット計算方法 (改善版)

MQL4 - How To Calculate GMT Offset お役立ち集
この記事は約5分で読めます。

MT4 EA開発で得られた ノウハウやお役立ち情報を時折書いていこうと思っています。

手始めに簡単なところから。

MQL4による「MT4サーバのGMTオフセット」の計算方法のサンプルコードです。

GMTオフセットとは?

「GMTオフセット」もしくは「UTCオフセット」とは、タイムゾーンと協定世界時 (UTC) との時間差です。

  • 例えば、GMT+9である日本のGMTオフセットは「+9」になります。
  • MT4サーバのGMTオフセットを取得する関数はMQL4では用意されていないため、自分で算出する必要があります。
    • 「TimeGMTOffset」という関数がMQLでは用意されていますが、ローカルPCのGMTオフセットを算出するものなので用途が異なります。
スポンサーリンク

【サンプルコード】

結論のソースコードから載せてしまいますが、Canaryは以下でMT4サーバのGMTオフセットを取得しています。

/* ------------------------------------------------------------------
 * [Function] MT4サーバのGMTオフセット取得API
 * ------------------------------------------------------------------ */
int GetGmtOffset() {
  //--- バックテスト時は固定値"3"を返却
  if (IsTesting()) { return 3; }

  //--- 「サーバ時刻(now)」と「GMT時刻(gmt)」を取得
  const datetime now = TimeCurrent();
  const datetime gmt = TimeGMT();

  //--- MT4サーバのGMTオフセット値の計算
  const double offsetSeconds = double(now - gmt);
  return int(round(offsetSeconds / 3600.0));
}


Canary
Canary

括弧の位置やインデントがMT4のデフォルトと異なるのは、こだわりなのでご容赦w


注意点

本コードは、OnTick() 内の呼び出しでのみ使用してください
※ Init() や OnTimer() はダメです。
※インジケータならOnCalculate() 内のみ。

TimeCurrent() は週末にティックが止まった後は最終ティックの時刻を返し続けるため、上記のGetGmtOffset() も正しい値を返さなくなります。

他サイトでサンプルコードも見つけましたが、うまく行かないケースを発見~改善して、上記コードに落ち着きました。

  • 「日付またぎ は考慮されているが 月またぎ が考慮されていない」ようでした。

スポンサーリンク

【解説】

基本的に、TimeCurrent関数で取得したサーバ時刻(now) – TimeGMT関数で取得したGMT時刻(gmt) でオフセットを取得するだけなのですが、

MqlDateTime型をそのまま使って now.hour - gmt.hour のように時刻の数字だけ取り出して計算してしまうと、日付またぎ・月またぎも考慮が必要になってしまい面倒 です。

例えば、「now=”11/05 02:00″、gmt=”11/04 23:00″」のケースは差は3時間なので、GMTオフセットの期待値は「+3」です。

  • しかし、このケースは日付またぎなので、単純に now.hour - gmt.hour で計算すると「-21」になってしまい、+24する補正 が必要です。

さらに、日付またぎの判定は now.day - gmt.day だけでは月またぎ・年またぎのケースで問題が出るのでそれらの考慮も必要になりますが、上述した「他サイトのサンプルコード」ではこの考慮が抜けてました。

POINT

Canaryは、この now.hour - gmt.hour による補正は行わず、

エポック秒で時刻を引き算し「1時間」で割る

ことでオフセット値を算出しました。※コードの14行目の「3600.0」が1時間に相当します。

また、13~14行目で

オフセット値をdoubleへキャスト→roundで四捨五入→intに再キャスト

しているのにも、ちゃんとした理由があります。(→下のコラム参照。)

「オフセット値をdoubleへキャスト→roundで四捨五入→intに再キャスト」している理由

当初は整数型 (int / long) で計算していましたが、そのままだと問題があったため、doubleを経由させるようにしました。目的は 秒単位誤差の丸め込み です。

【経緯】

  • nowとgmtはタイムゾーンが違うだけなので、分・秒単位は同値になるはず と考えていたが、実際は異なった
  • 「nowはサーバ由来」、「gmtはPC由来」と取得元が異なるためか、数秒の誤差が発生するケースがあった。(例:now=”12:00:00″、gmt=”9:00:04″)
  • 上記例で時間差は「2時間59分56秒」なのでGMTオフセットの期待値は「+3」だが、整数型で単純に処理すると (「59分56秒」は削られて)「+2」に計算されてしまった

【対策】

  • 他にも色々やり方はありますが、外部ライブラリを使わずに2行で完結できるのを重視し、 今回の「doubleへキャスト→roundで四捨五入→intに再キャストでの誤差丸め込み」を採用しました。
スポンサーリンク

【まとめ】

MQL4による「MT4サーバのGMTオフセット」の計算方法 をご紹介しました。

適宜コピー等して頂いて構いませんが、ご自分でも動作確認されることを推奨致します。

なお、本音としてはこう言った関数はMQL側で用意して頂けると!!ですw
#MQL5もまだ対応してなさそう・・・

Canary
Canary

ちなみに、Canaryがこれを計算するようになった経緯は、「約定通知Eメールに(GMTオフセットを含む)正確な時刻を載せるため」でした。ではでは~。

コメント

  1. db より:

    こちらのコードを使わせていただいています。ありがとうございます。
    日本時間表示で GMT+3 の普通の口座と、Exness など GMT 0 の口座の両方に対応させたいと
    思っています。

    一点悩みがありまして相談させていただきたくコメントさせていただきます。

    TimeCurrent の更新が止まる休日でも TimeGMT は更新されてしまう

    ことと格闘しています。
    「休日」であることが判定できれば解決すると思うのですが、僕の力量では難しく困り果てています。

    GetGmtOffset を OnCalculate のみで呼び出し、
    前回呼び出し時の TimeCurrent と今回の TimeCurrent を比較して
    変わっていなければ「休日」と判定しようとしましたが、平日のティックでも同じ TimeCurrent を
    返すタイミングがあるらしく断念です。

    day_of_week で休日を判定しようにも仮想通貨のように土日でも TimeCurrent が更新される口座があるのでそれもできず。
    そもそも、ストラテジーテスター用に GMT を入れさせる(+3 or 0)なら自動で判定は必要ないのかもと諦めかけています。

    休日を判定する方法をお持ちではないでしょうか・・・

    不躾なコメントで申し訳ありませんが、お力をお貸しいただけると助かります。

    よろしくお願い致します。

    • Canary より:

      こんにちは!

      なるほど~。休日にこの関数はちゃんと動かないということですね…。
      #自分のユースケースでは休日には呼ばないので気づきませんでした💦

      TimeCurrent×TimeGMTの組み合わせでダメなのであれば、TimeLocal×TimeGMTOffsetの組み合わせでは如何でしょうか?

      👉特に試してみた訳ではないのですが、以下のサイトの記述からもしかしたらと思いまして。(既に試されてたらご容赦ください…)
      👉MQL4リファレンス¥日付と時間

      • db より:

        お返事ありがとうございます。
        こんなくだらない相談に乗って頂けて本当に感謝です。ありがとうございます。

        TimeLocal + TimeGMT ですと、ローカル×ローカルなので口座(サーバ)の時間が加味されず
        口座の休日は判定できないと思うのですが、何かトリック的なこと(計算で、など)を示唆されているんでしょうか・・・

        相談しておいて不躾な返答になってしまい申し訳ありませんが
        もう少し詳細を教えていただけないでしょうか。

        よろしくお願い致します。

        • db より:

          度々失礼します。

          教えて頂いたリファレンス内の TimeGMTOffset() で何かできそうな気がしてきました。

          TimeGMTOffset()
          「アカウントのサマータイムスイッチサインを考慮したGMT時間と、ローカルコンピュータの時間の差を返します。」

          ということは、サーバ時間の GMT とローカル時間の差分を返す、という意味に見えるので
          例え PC の時間が間違っていようともサーバが動いている間は同じ秒数を返すはずですよね?

          その差が広がったらサーバ時間が止まったと判定できないでしょうか?

          試してみます!(週末まで待つ必要がありますが^^;)

          また週末にお邪魔します!本当にありがとうございます!

          • db より:

            本当に何度も申し訳ありません。

            最初からそう仰っていたんですね。。。
            TimeGMTOffset と TimeGMT を読み違えておりました。

            大変失礼致しました。

          • Canary より:

            (同じ秒数を返すのかどうか)実際に動かしてみないと何とも言えませんが、トライしてみる価値はありそうですね。
            結果気になりますので、分かりましたらご報告いただけると嬉しいです!

            ちなみに、元々の目的は「日本時間表示」という事で良かったでしょうか。
            (その目的が果たせれば)休日判定ができるかどうかは不問なのですよね?
            ちょっと気になりましたので確認です。

  2. db より:

    必ず報告させて頂きます!

    今のところ認識できている GMT+3 のブローカーと GMT 0 のブローカーで、
    ユーザーに GMT を選ばせずにそれぞれの GMT を適用して日本時間を表示したいのが目的です。
    ユーザーにどちらのブローカーですか?と聞いたところで認識されている方は希有かと思うからです。

    GMT +3 と決め打ちして GMT 0 をスコープに入れない、またはユーザーに input から選ばせるのであれば休日判定は不要です。Canary さんの関数を使わせていただく必要もありません。

    そんなに重要なことか?と聞かれたらそうでもないんですが、
    事実として GMT 0 のブローカーもありますし、前述の「GMT の認識」から
    そのくらいはやってあげてもいいのかな、が本質です。

    文章が下手で申し訳ありません。
    僕が日本時間を表示したいと思うと休日判定が必要なのです。

    何かロジックが変か、どこか勘違いしていますでしょうか?

    • db より:

      今日はサーバが稼働しているので同じ秒数をずっと返し続けています。
      以下を仕掛けてありますが、まだ一度もプリントされていません。

      int GetGMTOffset(int testerGMT)
      {
      //— バックテスト時は固定値を返却
      #ifdef __MQL5__
      if(MQLInfoInteger(MQL_TESTER))
      #else
      if(IsTesting())
      #endif
      return testerGMT;
      //—
      //— 「サーバ時刻(now)」と「GMT時刻(gmt)」を取得
      const datetime server_time = TimeCurrent();
      const datetime gmt_time = TimeGMT();
      //— サーバ停止判定
      const int timeGMTOffset = TimeGMTOffset();
      static int last_timeGMTOffset;
      last_timeGMTOffset = !last_timeGMTOffset ? timeGMTOffset : last_timeGMTOffset;
      if(last_timeGMTOffset != timeGMTOffset)
      {
      Print(“return testerGMT = ” + testerGMT);
      return testerGMT;
      }
      last_timeGMTOffset = timeGMTOffset;
      //— GMTオフセット値の計算
      const double offsetSeconds = double(server_time – gmt_time);
      return int(MathRound(offsetSeconds / 3600.0));
      }

    • Canary より:

      PCが日本のタイムゾーンに設定されている前提にはなりますが、「日本時間を表示したい」という目的であれば、単純にTimeLocalを使ってそのまま表示すればよいのかなと思いましたが、それでは何か問題があったのでしょうか?
      (何かこちらも勘違いしてるかも知れません。。)

      確かに、MT4サーバーのタイムゾーンなんて大多数は認識ないでしょうから、ユーザーに選択させずに自動で設定したいところですね。

      • db より:

        TimeCurrent を変換して日本時間を表示したいので TimeLocal ではターゲットが違ってしまいます。

        また、docs-mql4-com/dateandtime/timegmtoffset で確認したところ

        TimeGMTOffset() = TimeGMT() – TimeLocal()

        とのことですので、同じ秒数を返すのは当然でした。振り出しです。。。

        ユーザーに入力してもらうようにします。
        お騒がせして申し訳ありませんでした。ありがとうございました。
        感謝します。

        • Canary より:

          ご返信ありがとうございます。

          そうでしたか…。もし何か良い方法見つかったら是非ご報告いただければ。
          自分も何か浮かんだら書き込みますね。

          ではでは!

タイトルとURLをコピーしました