お役立ち集

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

MQL4 - How To Calculate GMT Offset お役立ち集
この記事は約6分で読めます。
※当サイトは、アフィリエイト広告を利用しています。

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

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

MT4サーバのGMTオフセット計算方法 のMQLサンプルコードです。


GMTオフセットとは?

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

日本のGMTオフセットはいくつ?

タイムゾーンが GMT+9 である日本のGMTオフセットは +9 になります。

どうやればGMTオフセットを取得できるの?

MT4サーバのGMTオフセットを取得する関数はMQL4では用意されていないため、自分で算出する必要があります。

※TimeGMTOffset() という関数がMQLでは用意されていますが、(MT4サーバではなく)「ローカルPCのGMTオフセット」を算出するものなので用途が異なります。

スポンサーリンク

【サンプルコード】

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

/* ------------------------------------------------------------------
 * [Function] MT4サーバのGMTオフセット取得API
 *  - NOTE: OnTick()またはOnCalculate()からのみ呼び出すこと!!
 * ------------------------------------------------------------------ */
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のデフォルトと異なるのは、

こだわりなのでご容赦😂


外部サイトでサンプルコードを見つけましたが、うまく行かないケースを発見~改善して、上記コードに落ち着きました。 →「日付またぎ」は考慮されていましたが「月またぎ」「年またぎ」が考慮されていませんでした。

注意事項

1つだけ大きな注意点があります。

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

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

スポンサーリンク

【解説】

上述した「外部サイトのサンプルコード」は、TimeCurrent(MqlDateTime& dt_struct) 及び TimeGMT(MqlDateTime& dt_struct) と、MqlDateTime構造体を使ってGMTオフセットを計算していました。

一見、楽なようにも思えますが、日付またぎ・月またぎ・年またぎの考慮が必要 になってしまい実は面倒です。 実際、月またぎ及び年またぎの考慮が抜けており、月初や年始にバグ発生が必至なコードとなっていました。

MqlDateTime型を使った日付またぎ計算の例

例えば、「now=”11/05 02:00″、gmt=”11/04 23:00″」のケースは差は3時間なので、GMTオフセットの値は《+3》と計算されるのが正解です。

しかし、このケースは日付またぎなので、MqlDateTime型の時刻の値を使い「now.hour – gmt.hour」で単純に計算すると《-21》になってしまいます。

このため、実際には 24時間をプラスする補正 が必要です。 同様に月またぎ、年またぎについても補正が必要となってきます。



そこでCanaryが考えたのは、諸々の補正が必要になる MqlDateTime型は使わず、

エポック秒で時刻を取得~引き算して1時間で割る

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

さらに

コードの13~14行目で

doubleへキャスト → roundで四捨五入 → intに再キャスト

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


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

GMTオフセット値をdoubleへキャスト → roundで四捨五入 → intに再キャストしている目的は、秒単位誤差の丸め込み です。

経緯

下の例のように、時間差が3時間 (→GMTオフセット値の期待値は+3) なのに「+2」で算出されるケースがありました。

解析

詳細確認すると、時間差はピッタリ3時間にはなっておらず「2時間59分56秒」と数秒の誤差がありました。

  • nowとgmtはタイムゾーンが違うだけなので、分・秒単位は同値になるはず と考えていましたが、実際は異なりました。
  • 「nowはサーバ由来」「gmtはPC由来」と時刻情報の取得元が異なるため、数秒の誤差が発生していました。(下記例では、now=”12:00:00″、gmt=”9:00:04″)

従って、整数型のdatetime型で単純に処理すると 「59分56秒」は削られてGMTオフセット値は「+2」に計算されます。


対策

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

スポンサーリンク

【まとめ】

MT4サーバのGMTオフセットを計算するMQLサンプルコード をご紹介しました。

適宜コピーしていただいて構いませんが、ご自身でもしっかり動作確認されることを推奨いたします。

Canary
Canary

なお、本音としましては

こういった基本的な関数は
プラットフォーム側で用意していただきたく!!

です😂
#MQL5も対応してないっぽいですね・・・

スポンサーリンク
\日額20円から使える高コスパVPS!! Ubuntuも選べます!!/

コメント

  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 より:

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

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

          ではでは!

  3. tomato より:

    月マタギが考慮されてないのは
    ゴゴジャンさんのでしょうか?
    https://www.gogojungle.co.jp/post/1/2278

    改善版、とてもたすかります
    ありがとうございます。

    • Canary より:

      こんにちは~
      お役に立てたようで何よりです🤗

      自分が見たのはゴゴジャンさんのものではありませんでしたが、同じ感じですね。
      月またぎが考慮されてないと思われます。