【MT5/MT4】見やすい日本時間表示インジケーターを無料配布!コードも全て公開

日本時間表示インジケーターを無料配布&コードを公開 FX・バイナリー攻略

MT4・MT5のチャート時間は海外時間で分かりにくい…そんな悩みを解決する、とにかく見やすい日本時間表示インジケーター「Japantime-big」を無料配布します。

メイン画面を邪魔しないサブウィンドウ表示に加え、マウス連動の十字線機能で特定の日時もピンポイントで把握可能です。

さらに、MQL学習用にソースコードを完全公開しています。トレード環境の改善やプログラミング学習に、ぜひご活用ください。

japantime-bigの取り扱い説明書

japantime-bigの画面

通常、チャートには海外のサーバー時間が表示されているため、日本時間でいつなのかが直感的に分かりにくいという課題がありますが、このツールを導入することで解決できます。

具体的には、メインチャートの下にあるサブウィンドウへ日本時間の日付と時刻を表示させることが可能になります。

さらに、マウスカーソルの動きに合わせて縦横の十字線を表示する機能も備わっているため、チャート上の特定の位置が日本時間でいつなのかをピンポイントでスムーズに把握できるようになります。

「LINE」ボタン

「LINE」ボタンがONの時、マウスを動かすと、黄色い点線(縦線)がついてきます。 同時に、その場所の詳しい日時(曜日つき)が表示されます。

表示例: 2025/12/15(Mon) 10:00

「LINE」ボタンがOFFの時、縦線とラベルが消えます。 チャート分析の邪魔になるときは、OFFにしておくとスッキリします。

設定の変更(パラメーター設定)

japantime-bigのインプット画面

インジケーターの設定画面を開くと、以下の日本語の項目名が表示されます。ここから自分好みにカスタマイズが可能です。

=== サマータイム設定 ===

利用しているFX業者のサーバー時間に合わせて、サマータイムの切り替え方式を選択します。

サマータイム方式

夏時間の計算方式を選択します。(初期値:DST_US)

DST_US(米国方式) 一般的な海外FX業者で、冬時間がGMT+2・夏時間がGMT+3の業者の場合はこちらを選択します。 (切り替え:3月の第2日曜日 ~ 11月の第1日曜日)

DST_EU(欧州方式) ヨーロッパ時間を基準にしている業者の場合はこちらを選択します。 (切り替え:3月の最終日曜日 ~ 10月の最終日曜日)

=== 文字と見た目の設定 ===

日本時間の文字サイズや色を変更したい場合は、この項目を調整します。

文字の大きさ(フォントサイズ)

文字の大きさです。(初期値:12)

文字の色

文字の色です。(初期値:White / 白)

文字同士の最低間隔(ピクセル単位)

文字同士が重ならないようにする「最低限の隙間」の設定です。 数字を減らすと文字が詰まって多く表示され、増やすと間引かれてスッキリ表示されます。(初期値:90)

=== カーソル(十字線)の設定 ===

チャート左上の「LINE」ボタンをONにした際に表示される、縦線と時刻ラベルの見た目を設定します。

縦線の色

マウスに追従する縦線の色です。(初期値:Yellow / 黄色)

時刻表示の文字色

カーソル部分に表示される時刻ラベルの文字色です。

時刻表示の背景色

時刻ラベルの背景色です。

=== 時間足ごとの表示間隔(分) ===

ここが一番重要な設定です! 「1分足を見ているときは30分ごとに時間を表示したい」「1時間足のときは1日ごとに表示したい」といった調整ができます。

※数字はすべて「分(Minutes)」で指定します。

1分足の時は30分間隔 1分足チャートを表示している時の間隔です。 初期値 30 → 1分足では「30分おき」に時間が表示されます。

5分足の時は60分間隔 5分足チャートを表示している時の間隔です。 初期値 60 → 5分足では「1時間(60分)おき」に時間が表示されます。

Kankaku_H1_Fun など その他の時間足の設定です。(例:H1=1時間足) 初期値 480 → 「8時間(480分)おき」に表示されます。 (ここを 1440 に変更すると、1日おきの表示になります)

※注意点 設定した間隔が狭すぎて文字同士が重なってしまう場合は、自動的に文字が間引かれて(消えて)表示されます。その場合は「文字同士の最低間隔(ピクセル単位)」の数字を小さくするか、ここの表示間隔(分)を広く設定してください。

japantime-bigをダウンロード

オリジナルインジケーター「japantime-big」はMT4・MT5専用のインジケーターになります。

MT4・MT5のダウンロードからインジケーターの導入方法に関しては関連記事をご確認下さい。

【簡単】MT4・MT5の導入~インジケーター導入方法を動画で紹介!
MT4・MT5の導入方法~インジケーターの導入方法を動画付きで分かりやすく紹介しています。また、導入方以外にも、MT4・MT5の注意点やおすすめの業者も紹介しています。

Japantime-big【MT5】のコードを公開

コードはコピペでそのまま使えます。


//+------------------------------------------------------------------+
//|      japantime-big_Ver.mq5                                   |
//|      初心者向け:全行コメント解説付き                                     |
//+------------------------------------------------------------------+
#property copyright "ayase tomoya"      // 著作権者の表示
#property version   "1.0"               // バージョン番号

//--- インジケータの基本設定 ---
#property indicator_separate_window     // メインチャートではなく、サブウィンドウに表示する
#property indicator_buffers 0           // 計算用のバッファ(メモリ領域)、今回未使用
#property indicator_plots   0           // 折れ線グラフなどは描画、今回未使用

#property indicator_height 35           // サブウィンドウの高さを「35ピクセル」に固定

// 「文字を常に画面の『真ん中』に表示させるために、画面の上下の範囲を固定する設定
#property indicator_minimum 0.0         // 縦軸の最小値を0.0に固定
#property indicator_maximum 2.0         // 縦軸の最大値を2.0に固定(これで高さが固定される)

enum ENUM_DST_TYPE
  {
   DST_US, // 米国方式 (2週目日曜~11月1週目日曜)
   DST_EU  // 欧州方式 (最終日曜~10月最終日曜)
  };

input group "=== サマータイム設定 ==="          // ★追加
input ENUM_DST_TYPE Settei_DST_Type = DST_US; // サマータイム方式(デフォルト米国)

//====================================================================
// ユーザーが設定画面で変更できるパラメータ (input変数)
//====================================================================
input group "=== 文字と見た目の設定 ==="         // 設定画面でのグループ分け表示
input int    Settei_Moji_Size     = 12;         // 文字の大きさ(フォントサイズ)
input color  Settei_Moji_Color       = clrWhite;   // 文字の色
input int    Settei_Moji_Kankaku  = 90;         // 文字同士の最低間隔(ピクセル単位)

input group "=== カーソル(十字線)の設定 ==="     // カーソル関連のグループ
input color  Settei_Cursor_Sen_Color  = clrYellow; // 縦線の色
input color  Settei_Cursor_Moji_Color = clrYellow; // 時刻表示の文字色
input color  Settei_Cursor_Haikei   = clrBlack;   // 時刻表示の背景色

//--- 時間足ごとのグリッド間隔設定 ---
input group "=== 時間足ごとの表示間隔(分) ===" // 各時間足で何分ごとに表示するか
input int Kankaku_M1_Fun  = 30;               // 1分足の時は30分間隔
input int Kankaku_M2_Fun  = 30;               // 2分足の時は30分間隔
input int Kankaku_M3_Fun  = 30;               // 3分足...
input int Kankaku_M4_Fun  = 60;               // 4分足...
input int Kankaku_M5_Fun  = 60;               // 5分足の時は60分間隔
input int Kankaku_M6_Fun  = 60;               // ...以下同様の設定
input int Kankaku_M10_Fun = 60;
input int Kankaku_M12_Fun = 120;
input int Kankaku_M15_Fun = 120;
input int Kankaku_M20_Fun = 240;
input int Kankaku_M30_Fun = 240;
input int Kankaku_H1_Fun  = 480;
input int Kankaku_H2_Fun  = 720;
input int Kankaku_H3_Fun  = 1440;             // 1日(1440分)間隔
input int Kankaku_H4_Fun  = 1440;
input int Kankaku_H6_Fun  = 1440;
input int Kankaku_H8_Fun  = 1440;
input int Kankaku_H12_Fun = 1440;
input int Kankaku_D1_Fun  = 14400;            // 日足用
input int Kankaku_W1_Fun  = 72000;            // 週足用
input int Kankaku_MN1_Fun = 259200;           // 月足用



//====================================================================
// グローバル変数(プログラム全体で共通して使う変数)
//====================================================================
string Obj_Namae_No_Atama   = "JST_Moji_";      // 作成する文字オブジェクトの名前の先頭部分

// カーソル(縦線とラベル)の名前を決めておく
string Cursor_Tatesen_Namae = "JST_Cursor_Tatesen"; // 縦線の名前
string Cursor_Label_Namae   = "JST_Cursor_Label";   // 時刻ラベルの名前

// ON/OFFボタンの名前と状態
string Button_Waku_Namae    = "JST_Button_Waku";    // ボタンの「枠」の名前
string Button_Moji_Namae    = "JST_Button_Moji";    // ボタンの「文字」の名前
bool   Button_Switch_ON     = true;                 // ボタンの状態(trueならON、falseならOFF)
string Week_Names[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; //曜日用の配列



//+------------------------------------------------------------------+
//| 【関数】今の時間足に合わせて「何分間隔」にするか決める場所
//+------------------------------------------------------------------+

// ENUM_TIMEFRAMES = MT5で使える「すべての時間足(M1やH1など)」があらかじめ登録されている「専用のリスト」のこと
int Kankaku_Get_Fun(ENUM_TIMEFRAMES Jikan_Ashi)
{
   int kankaku = -1; // とりあえず初期値として無効な値を入れておく
   
   // 今のチャートの時間足(Jikan_Ashi)がどれかによって分岐
   switch(Jikan_Ashi)
   {
      case PERIOD_M1:  kankaku=Kankaku_M1_Fun;  break; // 1分足なら設定値を代入してswitchを抜ける
      case PERIOD_M2:  kankaku=Kankaku_M2_Fun;  break; // 2分足なら...
      case PERIOD_M3:  kankaku=Kankaku_M3_Fun;  break;
      case PERIOD_M4:  kankaku=Kankaku_M4_Fun;  break;
      case PERIOD_M5:  kankaku=Kankaku_M5_Fun;  break;
      case PERIOD_M6:  kankaku=Kankaku_M6_Fun;  break;
      case PERIOD_M10: kankaku=Kankaku_M10_Fun; break;
      case PERIOD_M12: kankaku=Kankaku_M12_Fun; break;
      case PERIOD_M15: kankaku=Kankaku_M15_Fun; break;
      case PERIOD_M20: kankaku=Kankaku_M20_Fun; break;
      case PERIOD_M30: kankaku=Kankaku_M30_Fun; break;
      case PERIOD_H1:  kankaku=Kankaku_H1_Fun;  break;
      case PERIOD_H2:  kankaku=Kankaku_H2_Fun;  break;
      case PERIOD_H3:  kankaku=Kankaku_H3_Fun;  break;
      case PERIOD_H4:  kankaku=Kankaku_H4_Fun;  break;
      case PERIOD_H6:  kankaku=Kankaku_H6_Fun;  break;
      case PERIOD_H8:  kankaku=Kankaku_H8_Fun;  break;
      case PERIOD_H12: kankaku=Kankaku_H12_Fun; break;
      case PERIOD_D1:  kankaku=Kankaku_D1_Fun;  break;
      case PERIOD_W1:  kankaku=Kankaku_W1_Fun;  break;
      case PERIOD_MN1: kankaku=Kankaku_MN1_Fun; break;
      default:          kankaku=60; break;              // 想定外の時間足なら60分にする
   }
   
   if(kankaku <= 0) kankaku = 60; // もし0以下になっていたら60に直す(安全策)
   
   // チャートの時間足自体が何分か計算(例: H1なら60)
   // PeriodSeconds=その時間足が「何秒」なのかを教えてくれる関数
   int Ashi_no_Funsu = (int)(PeriodSeconds(Jikan_Ashi)/60);
   
   // エラー防止
   if(Ashi_no_Funsu <= 0)
   {
      Ashi_no_Funsu = 1; // 0なら1にする(割り算エラー防止)
   }

   // 設定した間隔が、足の分数より短かったら意味がないので合わせる
   if(kankaku < Ashi_no_Funsu)
   {
      kankaku = Ashi_no_Funsu;
   }
   else
   {
      // キリよく割り切れるように調整(余りが出ないようにする)
      int amari = kankaku % Ashi_no_Funsu;
      if(amari != 0)
      {
         kankaku += (Ashi_no_Funsu - amari);
      }
   }
   return kankaku; // 最終的に決定した「間隔(分)」を呼び出し元に返す
}

//+------------------------------------------------------------------+
//| 【関数】指定した時間がサマータイム(夏時間)かどうかを判定して
//|  日本時間(JST)との時差(秒数)を計算して返す場所
//+------------------------------------------------------------------+
long Get_JST_Offset(datetime Hantei_Time)
{
   // 日付や曜日を調べやすくするために、時間のデータを分解できる「構造体」に入れます
   MqlDateTime Jikan_Data;
   TimeToStruct(Hantei_Time, Jikan_Data);

   bool Natsu_Jikan_Desuka = false; // 「今は夏時間ですか?」というフラグ(最初は「いいえ(false)」にしておく)

   //----------------------------------------------------------------
   // 米国方式 (DST_US) の場合
   // 夏時間:3月の第2日曜日 ~ 11月の第1日曜日
   //----------------------------------------------------------------
   if(Settei_DST_Type == DST_US)
   {
      // 3月よりあと、かつ、11月より前なら、間違いなく夏時間
      if(Jikan_Data.mon > 3 && Jikan_Data.mon < 11) Natsu_Jikan_Desuka = true;
      
      // 3月より前、または、11月よりあとなら、間違いなく冬時間
      else if(Jikan_Data.mon < 3 || Jikan_Data.mon > 11) Natsu_Jikan_Desuka = false;
      
      // 際どい「3月」と「11月」の判定
      else
      {
         // 3月の場合 (第2日曜日を過ぎているかチェック)
         if(Jikan_Data.mon == 3)
         {
            // まず「3月1日」が何曜日だったか逆算して調べる(0:日曜 ~ 6:土曜)
            int Tsuitachi_No_Youbi = (Jikan_Data.day_of_week - (Jikan_Data.day - 1) % 7 + 7) % 7;
            
            // 「第2日曜日」が何日になるか計算
            // 式の意味:1日 + (次の日曜までの日数) + 7日(1週間分)
            int Daini_Nichiyoubi_Hizuke = 1 + (7 - Tsuitachi_No_Youbi) % 7 + 7; 
            
            // 今日の日付が、第2日曜より後なら夏時間
            if(Jikan_Data.day > Daini_Nichiyoubi_Hizuke) Natsu_Jikan_Desuka = true;
            
            // もし今日がドンピシャで「第2日曜」なら、朝2時を過ぎているかチェック
            else if(Jikan_Data.day == Daini_Nichiyoubi_Hizuke && Jikan_Data.hour >= 2) Natsu_Jikan_Desuka = true; 
         }
         
         // 11月の場合 (第1日曜日までかどうかチェック)
         if(Jikan_Data.mon == 11)
         {
            // 「11月1日」が何曜日か調べる
            int Tsuitachi_No_Youbi = (Jikan_Data.day_of_week - (Jikan_Data.day - 1) % 7 + 7) % 7;
            
            // 「第1日曜日」が何日になるか計算
            int Daiichi_Nichiyoubi_Hizuke = 1 + (7 - Tsuitachi_No_Youbi) % 7; 
            
            // 今日の日付が、第1日曜より前ならまだ夏時間
            if(Jikan_Data.day < Daiichi_Nichiyoubi_Hizuke) Natsu_Jikan_Desuka = true;
            
            // もし今日がドンピシャで「第1日曜」なら、朝2時より前なら夏時間
            else if(Jikan_Data.day == Daiichi_Nichiyoubi_Hizuke && Jikan_Data.hour < 2) Natsu_Jikan_Desuka = true;
         }
      }
   }
   
   //----------------------------------------------------------------
   // 欧州方式 (DST_EU) の場合
   // 夏時間:3月の最終日曜日 ~ 10月の最終日曜日
   //----------------------------------------------------------------
   if(Settei_DST_Type == DST_EU)
   {
      // 3月~10月の間なら夏時間の可能性が高い
      if(Jikan_Data.mon > 3 && Jikan_Data.mon < 10) Natsu_Jikan_Desuka = true;
      else if(Jikan_Data.mon < 3 || Jikan_Data.mon > 10) Natsu_Jikan_Desuka = false;
      else
      {
         // 3月の場合 (最終日曜日を過ぎているかチェック)
         if(Jikan_Data.mon == 3)
         {
            // 月末(31日)から逆算して、最終日曜日が何日か求める
            // 計算式:31日 - (31日の曜日) ※31日の曜日は現在日から推測
            int Matsubi_No_Youbi = (Jikan_Data.day_of_week + (31 - Jikan_Data.day)) % 7; 
            int Saishu_Nichiyoubi_Hizuke = 31 - Matsubi_No_Youbi;
            
            // 今日の日付が、最終日曜より後なら夏時間
            if(Jikan_Data.day > Saishu_Nichiyoubi_Hizuke) Natsu_Jikan_Desuka = true;
            
            // 当日の場合、欧州はGMT1時(日本時間だとサーバー時間にもよるが早い時間)で切り替わる
            else if(Jikan_Data.day == Saishu_Nichiyoubi_Hizuke && Jikan_Data.hour >= 1) Natsu_Jikan_Desuka = true;
         }
         
         // 10月の場合 (最終日曜日までかどうかチェック)
         if(Jikan_Data.mon == 10)
         {
             // 10月も31日まであるので計算は同じ
             int Matsubi_No_Youbi = (Jikan_Data.day_of_week + (31 - Jikan_Data.day)) % 7;
             int Saishu_Nichiyoubi_Hizuke = 31 - Matsubi_No_Youbi;
             
             // 今日の日付が、最終日曜より前なら夏時間
             if(Jikan_Data.day < Saishu_Nichiyoubi_Hizuke) Natsu_Jikan_Desuka = true;
             
             // 当日の場合、切り替え時間より前なら夏時間
             else if(Jikan_Data.day == Saishu_Nichiyoubi_Hizuke && Jikan_Data.hour < 1) Natsu_Jikan_Desuka = true;
         }
      }
   }

   // --- 結果を秒数で返す ---
   // 夏時間なら: サーバー(GMT+3) → 日本(GMT+9) なので、差は「6時間」
   // 冬時間なら: サーバー(GMT+2) → 日本(GMT+9) なので、差は「7時間」
   
   if(Natsu_Jikan_Desuka == true)
   {
      return 6 * 3600; // 6時間 × 3600秒
   }
   else
   {
      return 7 * 3600; // 7時間 × 3600秒
   }
}


//+------------------------------------------------------------------+
// 【関数】パソコンが重くならないように、画面から見えなくなった不要な文字(日時)を削除する関数
// 日本時間を全ての範囲で表示すると処理が重くなる最悪フリーズします。今見えている範囲のみで時間を表示する事が重要になります
//+------------------------------------------------------------------+

void Fuyou_Moji_Sakujyo(long chart_id, int window_num, int start_index, int end_index)
{

   // ObjectsTotal = チャート上に、文字や線などのオブジェクトが「全部で何個あるか」を数えて教えてくれる関数
   int Sousuu = ObjectsTotal(chart_id, window_num, OBJ_TEXT); // 今あるテキストオブジェクトの総数
   
   // 最新のオブジェクトから順番にチェック(削除時は後ろからループするのが鉄則)
   for(int i = Sousuu - 1; i >= 0; i--)
   {
      // 削除すべきかどうかを判断するために、i番目のテキストオブジェクトの『名前』を読み取って、いったん変数にメモ
      string obj_name = ObjectName(chart_id, i, window_num, OBJ_TEXT); // オブジェクトの名前を取得
      
      // 名前がこのインジケータのもの(JST_Moji_で始まる)かどうか確認
      if(StringFind(obj_name, Obj_Namae_No_Atama) == 0)
      {
         
         // 名前の後ろの番号部分を取り出す
         string str_num = StringSubstr(obj_name, StringLen(Obj_Namae_No_Atama));
         int obj_index = (int)StringToInteger(str_num); // 文字列を数字に変換
         
         // 表示すべき範囲(start〜end)の外なら削除する
         if(obj_index < start_index || obj_index > end_index)
         {
            ObjectDelete(chart_id, obj_name); // 削除実行
         }
      }
   }
}




//+------------------------------------------------------------------+
// ボタンは枠と文字で作成しています。ボタンを使うのも良いのですが、見た目のカスタムの自由度が下がるので枠と文字を採用しています
// 使い方としては枠と文字を作成して、枠もしくは文字をクリックした際にイベントが発生するようにします
//+------------------------------------------------------------------+
void Button_LINE()
{
   long chart_id = ChartID();          // 今のチャートID
   int sub_window = ChartWindowFind(); // このインジケータがいるサブウィンドウ番号

   // --- 1. ボタンの「枠」を作る(まだ存在しない場合のみ) ---
   if(ObjectFind(chart_id, Button_Waku_Namae) < 0)
   {
      ObjectCreate(chart_id, Button_Waku_Namae, OBJ_RECTANGLE_LABEL, sub_window, 0, 0); // 枠作成
      ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_CORNER, CORNER_LEFT_UPPER); // 左上基準
      ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_XDISTANCE, 5);  // 左から5pxの位置
      ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_YDISTANCE, 5);  // 上から5pxの位置
      ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_XSIZE, 60);     // 幅50px
      ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_YSIZE, 24);     // 高さ24px
      ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_COLOR, clrBlack); // 枠線の色
      ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_BACK, false);     // 背景として描画しない
      ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_SELECTABLE, true); // クリック選択可能にする
      ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_ZORDER, 255);      // 最前面に表示
   }

   // --- 2. ボタンの「文字」を作る(まだ存在しない場合のみ) ---
   if(ObjectFind(chart_id, Button_Moji_Namae) < 0)
   {
      ObjectCreate(chart_id, Button_Moji_Namae, OBJ_LABEL, sub_window, 0, 0); // ラベル作成
      ObjectSetInteger(chart_id, Button_Moji_Namae, OBJPROP_CORNER, CORNER_LEFT_UPPER);
      ObjectSetInteger(chart_id, Button_Moji_Namae, OBJPROP_XDISTANCE, 19); // 文字の位置調整X
      ObjectSetInteger(chart_id, Button_Moji_Namae, OBJPROP_YDISTANCE, 10); // 文字の位置調整Y
      ObjectSetInteger(chart_id, Button_Moji_Namae, OBJPROP_FONTSIZE, 10);  // 文字サイズ
      ObjectSetString(chart_id, Button_Moji_Namae, OBJPROP_FONT, "Arial");  // フォント
      ObjectSetInteger(chart_id, Button_Moji_Namae, OBJPROP_COLOR, clrBlack); // 文字色
      ObjectSetInteger(chart_id, Button_Moji_Namae, OBJPROP_BACK, false);
      ObjectSetInteger(chart_id, Button_Moji_Namae, OBJPROP_SELECTABLE, true); // クリック選択可能
      ObjectSetInteger(chart_id, Button_Moji_Namae, OBJPROP_ZORDER, 255);      // 最前面
   }

   // --- 3. ON/OFFの状態に合わせて色と文字を更新する ---
   // ONならライム色(緑)、OFFなら白にする(条件演算子 ? : を使用)
   ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_BGCOLOR, Button_Switch_ON ? clrLime : clrWhite);
   
   // ONなら「LINE」、OFFなら「LINE」と表示する、今回は同じ文字を使っていますが「LINE[ON]」「LINE[OFF]」などにすると分かりやすい
   ObjectSetString(chart_id, Button_Moji_Namae, OBJPROP_TEXT, Button_Switch_ON ? "LINE" : "LINE");
   
   // クリックした後に選択枠(ポチポチ)が残らないように選択状態を解除する
   ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_SELECTED, false);
   ObjectSetInteger(chart_id, Button_Moji_Namae, OBJPROP_SELECTED, false);
}

//+------------------------------------------------------------------+
//| 【関数】日本時間を計算して画面に並べる(メイン処理)
//+------------------------------------------------------------------+
void Jikan_Hyouji_Koushin()
{
   long chart_id = ChartID();              // チャートID
   int sub_window = ChartWindowFind();     // サブウィンドウ番号
   int bar_Sousuu = Bars(_Symbol, _Period); // チャートにあるローソク足の総数

   // --- 今、画面に見えている範囲(左端の場所と、表示されている本数)を取得 ---
   // 画面の「一番左端」に見えているローソク足が、最新(右端)から数えて「何本目」かを取得します
   long hidari_hashi_index = ChartGetInteger(chart_id, CHART_FIRST_VISIBLE_BAR); 

   // 画面の横幅の中に、ローソク足が「全部で何本」入っているか(表示本数)を取得します
   long mieteru_honsuu     = ChartGetInteger(chart_id, CHART_VISIBLE_BARS);    

   int yohaku = 50; // 計算範囲に少し余裕を持たせる(50本分)
   
   // --- 配列(データの入れ物)のどこからどこまでを計算するか決める ---
   // 【開始位置(画面の左端)】
   int start_i = bar_Sousuu - 1 - (int)(hidari_hashi_index + yohaku); 

   // 【終了位置(画面の右端)】
   int end_i   = bar_Sousuu - 1 - (int)(hidari_hashi_index - mieteru_honsuu - yohaku);

   // --- 配列の範囲外エラー(Array out of range)を防ぐための補正処理 ---
   if(start_i < 0)
   { 
      start_i = 0;
   }
   
   if(end_i >= bar_Sousuu)
   {
      end_i = bar_Sousuu - 1;
   }
   
   if(start_i > end_i)
   {
      return; // 範囲がおかしい場合は何もしない
   }

   // 画面外にある古いオブジェクトを削除して軽くする
   Fuyou_Moji_Sakujyo(chart_id, sub_window, start_i, end_i);

   // --- 表示間隔の準備 ---

   // 関数(Kankaku_Get_Fun)で決めた「何分間隔」を、計算用に「秒」に直します(分 × 60秒)
   long kankaku_byou = Kankaku_Get_Fun(_Period) * 60;
   
   // 最後に文字を表示した画面上のX座標(横位置)を記憶する変数です
   // 最初は比較対象がないので、絶対に被らないようなマイナスの値を適当に入れておきます
   int last_drawn_x = -999999; 

   // --- 時間データの取得 ---
   datetime time_array[]; // すべてのローソク足の時間データを入れるための配列(箱)を用意します
   
   // 配列の並び順を「古い順(左から右)」に設定します
   ArraySetAsSeries(time_array, false); 
   
   // チャート上の時間データ(_Symbol, _Period)を、用意した配列(time_array)にコピーします
   if(CopyTime(_Symbol, _Period, 0, bar_Sousuu, time_array) <= 0)
   {
      return;
   }    

   // 計算した範囲(画面に見えている部分+α)のバーを1本ずつチェックするループ
   for(int i = start_i; i <= end_i; i++)
   {
      string obj_name = Obj_Namae_No_Atama + IntegerToString(i); // バーごとのオブジェクト名を生成(例:JST_Moji_123)
      
      bool is_grid_point = false; // 「ここに時間を表示すべきか?」を判定するフラグ(最初はfalse)
      
      // ★修正:判定関数(Get_JST_Offset)を使って、その瞬間のサマータイム考慮済み時差を取得
      long offset_sec = Get_JST_Offset(time_array[i]);
      long current_jst = (long)time_array[i] + offset_sec; // そのバーの日本時間を算出
      
      // 1つ前のバーと比較して、設定した時間区切り(例:1時間)を跨いだかチェック
      if(i > 0)
      {
         // 1つ前のバーも、その時点での夏冬判定を行う
         long prev_offset_sec = Get_JST_Offset(time_array[i-1]);
         long prev_jst = (long)time_array[i-1] + prev_offset_sec;
         
         // 時間を間隔秒数で割ることで「区切り番号」を算出(例:10:59と11:00で番号が変わる)
         long current_block = current_jst / kankaku_byou; // 現在の区切り番号
         long prev_block    = prev_jst / kankaku_byou;    // 1つ前の区切り番号
         
         // 区切り番号が変わった=ここが時間の変わり目なので表示ポイントとする
         if(current_block != prev_block) is_grid_point = true;
      }
      else is_grid_point = true; // 配列の最初(一番左端)のバーは比較対象がないので無条件で表示候補にする

      if(is_grid_point) // ここが表示ポイントだと判定された場合の処理
      {
         int x, y;
         // その時間(time_array[i])が、画面上のどの座標(X,Y)にあるかを取得
         if(ChartTimePriceToXY(chart_id, sub_window, time_array[i], 0.0, x, y))
         {
            // 左上の「LINE」ボタンと文字が被らないように、X座標が120未満(左端付近)なら表示しない
            if(x < 120)
            {
               // もし既に文字が表示されていたら削除して、次のループへ進む
               if(ObjectFind(chart_id, obj_name) >= 0) ObjectDelete(chart_id, obj_name);
               continue; // 次のループへ(以下の処理はスキップ)
            }

            int gap = x - last_drawn_x; // 「前回文字を書いた場所」と「今回の場所」の隙間を計算
            
            // 隙間が設定値(Settei_Moji_Kankaku)以上空いていれば、重ならないので表示する
            if(gap >= Settei_Moji_Kankaku)
            {
               // 表示する時間を区切りのいい数字に丸める(秒単位のズレを補正して00分や30分ぴったりにする)
               long rounded_time = (current_jst / kankaku_byou) * kankaku_byou;
               
               MqlDateTime dt;
               TimeToStruct((datetime)rounded_time, dt); // 計算しやすいように時間構造体へ変換
               
               // 画面に表示するテキストを作成(例: 12/15 10:00)
               string text = StringFormat("%02d/%02d %02d:%02d", dt.mon, dt.day, dt.hour, dt.min);
               
               // まだその場所にオブジェクトが存在しなければ新規作成する
               if(ObjectFind(chart_id, obj_name) < 0)
               {
                  // テキストオブジェクトを作成(作成成功したら中身を設定)
                  if(ObjectCreate(chart_id, obj_name, OBJ_TEXT, sub_window, time_array[i], 1.0))
                  {
                     // プロパティ(文字の内容、フォント、色、サイズなど)を設定
                     ObjectSetString(chart_id, obj_name, OBJPROP_TEXT, text);
                     ObjectSetString(chart_id, obj_name, OBJPROP_FONT, "Arial Bold");
                     ObjectSetInteger(chart_id, obj_name, OBJPROP_COLOR, Settei_Moji_Color);
                     ObjectSetInteger(chart_id, obj_name, OBJPROP_FONTSIZE, Settei_Moji_Size);
                     ObjectSetInteger(chart_id, obj_name, OBJPROP_ANCHOR, ANCHOR_CENTER); // 文字の中心を基準点に合わせる
                     ObjectSetInteger(chart_id, obj_name, OBJPROP_BACK, false); 
                     ObjectSetInteger(chart_id, obj_name, OBJPROP_SELECTABLE, false); // マウスで選択できないようにする(誤操作防止)
                     ObjectSetInteger(chart_id, obj_name, OBJPROP_ZORDER, 0); 
                  }
               }
               else
               {
                  // 既にオブジェクトがあるなら、作り直さずにテキスト内容だけ更新(PCの負荷軽減)
                  ObjectSetString(chart_id, obj_name, OBJPROP_TEXT, text); 
               }
               
               last_drawn_x = x; // 「ここに文字を書いた」というX座標を記録(次のループのgap計算で使う)
            }
            else
            {
               // 表示タイミングだけど、隙間が狭すぎて重なってしまう場合は削除
               if(ObjectFind(chart_id, obj_name) >= 0) ObjectDelete(chart_id, obj_name);
            }
         }
      }
      else
      {
         // 表示ポイントではない場所(例:10:00と11:00の間の半端な時間)にオブジェクトが残っていたら削除
         if(ObjectFind(chart_id, obj_name) >= 0) ObjectDelete(chart_id, obj_name);
      }
   }
   
   // ボタンの表示も更新しておく
   Button_LINE();
   
   ChartRedraw(); // 画面を再描画して変更を反映
}

//+------------------------------------------------------------------+
//| 【関数】マウスに合わせてカーソルと時間を表示する場所
//+------------------------------------------------------------------+
void Mouse_Cursor_Koushin(long chart_id, int mouse_x, int mouse_y, int sub_window)
{
   // --- 機能OFFの場合の処理 ---
   // ボタンが「OFF」の状態なら、カーソル(縦線とラベル)が表示されていたら邪魔なので消去して終わる
   if(!Button_Switch_ON)
   {
      // 縦線が残っていたら削除
      if(ObjectFind(chart_id, Cursor_Tatesen_Namae) >= 0) ObjectDelete(chart_id, Cursor_Tatesen_Namae);
      // ラベルが残っていたら削除
      if(ObjectFind(chart_id, Cursor_Label_Namae) >= 0) ObjectDelete(chart_id, Cursor_Label_Namae);
      
      ChartRedraw(); // 画面を更新して削除を反映させる
      return;        // ここで関数を強制終了(以下の処理は行わない)
   }

   datetime mouse_time; // マウス位置の時間が入る変数
   double mouse_price;  // マウス位置の価格が入る変数(今回は使わないが関数に必要)
   
   // --- 座標変換 ---
   // マウスの画面上の座標(X,Y)から、その場所の「チャート時間」と「価格」を逆算して取得
   // 取得に成功したら if の中身を実行する
   if(ChartXYToTimePrice(chart_id, mouse_x, mouse_y, sub_window, mouse_time, mouse_price))
   {
      // --- 日本時間(JST)の計算 ---
      int bar_index = iBarShift(_Symbol, _Period, mouse_time); // マウス位置の足を特定
      datetime bar_start_time = iTime(_Symbol, _Period, bar_index); // その足の開始時間を取得

      // ★修正:判定関数(Get_JST_Offset)を使って、マウス位置の足の時間のサマータイム時差を取得
      long offset_sec = Get_JST_Offset(bar_start_time);
      long current_jst = (long)bar_start_time + offset_sec; // 開始時間を日本時間に変換
      
      MqlDateTime dt;
      TimeToStruct((datetime)current_jst, dt); // 年月日を取り出せるように構造体に変換
      
      // カーソル用の表示テキスト作成(例: 2025/12/15 10:00 )
      // 左右にスペースを入れて見た目の余白を作っています
      string text = StringFormat(" %04d/%02d/%02d(%s) %02d:%02d ", dt.year, dt.mon, dt.day, Week_Names[dt.day_of_week], dt.hour, dt.min);

      // --- 1. 縦線 (VLINE) の描画処理 ---
      // まだ縦線オブジェクトが存在しない場合(初回表示など)
      if(ObjectFind(chart_id, Cursor_Tatesen_Namae) < 0) 
      {
         // 縦線を作成
         ObjectCreate(chart_id, Cursor_Tatesen_Namae, OBJ_VLINE, 0, mouse_time, 0); 
         ObjectSetInteger(chart_id, Cursor_Tatesen_Namae, OBJPROP_COLOR, Settei_Cursor_Sen_Color);
         ObjectSetInteger(chart_id, Cursor_Tatesen_Namae, OBJPROP_STYLE, STYLE_DOT); // 点線スタイル
         ObjectSetInteger(chart_id, Cursor_Tatesen_Namae, OBJPROP_WIDTH, 2);
         ObjectSetInteger(chart_id, Cursor_Tatesen_Namae, OBJPROP_BACK, true); // ローソク足の「裏側」に表示
         ObjectSetInteger(chart_id, Cursor_Tatesen_Namae, OBJPROP_SELECTABLE, false); // クリック選択不可
         
         // ★重要テクニック:ツールチップ(マウスを乗せた時に出るポップアップ)を無効化
         // "\n"(改行コードだけ)を入れると、ツールチップが出なくなります
         ObjectSetString(chart_id, Cursor_Tatesen_Namae, OBJPROP_TOOLTIP, "\n");
      }
      else
      {
         // 位置をマウスの細かい時間(mouse_time)ではなく、足の開始時間(bar_start_time)にする
         ObjectMove(chart_id, Cursor_Tatesen_Namae, 0, bar_start_time, 0);
         ObjectSetString(chart_id, Cursor_Tatesen_Namae, OBJPROP_TOOLTIP, "\n");
      }

      // --- 2. 日本時間ラベル (BUTTON) の描画処理 ---
      // ※通常のラベル(OBJ_LABEL)ではなくボタン(OBJ_BUTTON)を使う理由は、背景色を綺麗につけるためです
      int label_width = 180; 
      int label_height = 24; 
      
      // ラベルが常にマウスの「真上」かつ「中心」に来るようにX座標を計算
      // マウスX位置から、ラベル幅の半分を引くと中心が合います
      int label_x = mouse_x - (label_width / 2);
      int label_y = 5; // Y座標はサブウィンドウの上部(5px)に固定

      // まだラベルが存在しない場合
      if(ObjectFind(chart_id, Cursor_Label_Namae) < 0) 
      {
         // ボタンオブジェクトを作成
         ObjectCreate(chart_id, Cursor_Label_Namae, OBJ_BUTTON, 0, 0, 0); 
         ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_CORNER, CORNER_LEFT_UPPER); // 左上基準
         ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_XSIZE, label_width); // 幅
         ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_YSIZE, label_height); // 高さ
         ObjectSetString(chart_id, Cursor_Label_Namae, OBJPROP_FONT, "Arial Bold");
         ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_FONTSIZE, Settei_Moji_Size);
         ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_COLOR, Settei_Cursor_Moji_Color); // 文字色
         ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_BGCOLOR, Settei_Cursor_Haikei);    // 背景色
         ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_BORDER_COLOR, Settei_Cursor_Haikei); // 枠線の色 
         ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_STATE, false); // ボタンが押し込まれていない状態
         ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_SELECTABLE, false);
         ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_ZORDER, 100); // 他の文字より手前に表示
         
         ObjectSetString(chart_id, Cursor_Label_Namae, OBJPROP_TOOLTIP, "\n"); // ツールチップ消去
      }
      
      // 内容や位置を更新(マウスに合わせて移動)
      ObjectSetString(chart_id, Cursor_Label_Namae, OBJPROP_TEXT, text);
      ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_XDISTANCE, label_x);
      ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_YDISTANCE, label_y);
      ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_BGCOLOR, Settei_Cursor_Haikei); 
      
      ChartRedraw(); // 再描画して、画面上の位置ズレを即座に直す
   }
}

//+------------------------------------------------------------------+
//| 【関数】後片付け(作ったオブジェクトを全部消す)
//+------------------------------------------------------------------+
void Zen_Obj_Sakujo()
{
   int Sousuu = ObjectsTotal(0, -1, OBJ_TEXT); // 全テキストオブジェクト数
   for(int i = Sousuu - 1; i >= 0; i--) {      // 後ろからループ
      string obj_name = ObjectName(0, i, -1, OBJ_TEXT);
      // 名前が一致するなら削除
      if(StringFind(obj_name, Obj_Namae_No_Atama) == 0) ObjectDelete(0, obj_name);
   }
   // カーソルとラベルも削除
   ObjectDelete(0, Cursor_Tatesen_Namae);
   ObjectDelete(0, Cursor_Label_Namae);
   
   // ボタンも削除
   ObjectDelete(0, Button_Waku_Namae);
   ObjectDelete(0, Button_Moji_Namae);
}

//+------------------------------------------------------------------+
//| 初期化イベント(インジケータをチャートに入れた瞬間に1回動く)
//+------------------------------------------------------------------+
int OnInit()
{
   Zen_Obj_Sakujo(); // まず前の残骸があれば綺麗にする
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); // マウスの動きを検知できるように設定ON
   Jikan_Hyouji_Koushin(); // 最初の描画を実行
   return(INIT_SUCCEEDED); // 「初期化成功」をMT5に伝える
}

//+------------------------------------------------------------------+
//| 終了イベント(インジケータを削除した時に1回動く)
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); // マウス検知設定をOFFに戻す
   Zen_Obj_Sakujo(); // 全部消して綺麗にする
   ChartRedraw();    // 最後に画面更新
}

//+------------------------------------------------------------------+
//| 計算イベント(価格が動くたびに何度も動く)
//+------------------------------------------------------------------+
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[])
{
   Jikan_Hyouji_Koushin(); // メインの描画処理を呼び出す
   return(rates_total);    // 処理した数をMT5に返す
}

//+------------------------------------------------------------------+
//| チャートイベント関数(クリック、マウス移動、サイズ変更などを検知して動く場所)
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // どんなイベントが起きたかのID(クリック?移動?変更?)
                  const long &lparam,   // パラメータ1 (マウスのX座標などが入る整数)
                  const double &dparam, // パラメータ2 (マウスのY座標などが入る小数)
                  const string &sparam) // パラメータ3 (クリックしたオブジェクトの名前などが入る文字)
{
   // --- パターン1:チャートの変更イベント ---
   // チャートのサイズが変わった、スクロールされた、ズームされた時など
   if(id == CHARTEVENT_CHART_CHANGE)
   {
      // 表示範囲が変わっている可能性が高いので、日本時間の再計算と再描画を行う
      Jikan_Hyouji_Koushin(); 
   }
   
   // --- パターン2:オブジェクトのクリックイベント ---
   // 画面上の何らかのオブジェクトがクリックされた時
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      // クリックされたオブジェクトの名前(sparam)が、このツールの「ボタン枠」か「ボタン文字」だったら
      if(sparam == Button_Waku_Namae || sparam == Button_Moji_Namae)
      {
         // ★スイッチ切り替え処理
         // trueならfalseに、falseならtrueに反転させる(!マークは「否定/逆」の意味)
         Button_Switch_ON = !Button_Switch_ON; 
         
         // ボタンの見た目(緑色か白色かなど)を新しい状態に合わせて更新する
         Button_LINE(); 
         
         // もしOFFになったのなら、画面に残っているカーソル(縦線・ラベル)は邪魔なので即座に消す
         if(!Button_Switch_ON)
         {
            ObjectDelete(0, Cursor_Tatesen_Namae);
            ObjectDelete(0, Cursor_Label_Namae);
         }
         
         // 変更を即座に画面に反映させる
         ChartRedraw(); 
      }
   }
   
   // --- パターン3:マウスの移動イベント ---
   // マウスカーソルが動いた時(頻繁に発生します)
   if(id == CHARTEVENT_MOUSE_MOVE)
   {
      // このインジケータが表示されているサブウィンドウの番号を取得
      int sub_window = ChartWindowFind();
      
      // カーソル更新関数を呼び出す
      // lparam にはマウスのX座標、dparam にはY座標が入っているので、それをキャスト(int)して渡す
      Mouse_Cursor_Koushin(0, (int)lparam, (int)dparam, sub_window);
   }
}

コードの解説

japantime-bigのコードを分かりやすく解説しています。解説はMT5のJapantime-bigのみになります。MT4に関してははコードのコメントをご確認下さい。

japantime-bigは初心者向けに作っています。変数をあえて日本語のアルファベットにして、位置関係を覚えやすくしていますが、本来NG行為です(分かりやすい英語を使うのが一般的)。

また、位置関係分かりやすくするためにifやforに{}を付けています。これは省略可能ですが、どこで区切りか判断しやすくする為に全てにつけています。

その点だけご了承の上ご確認下さい。

ウィンドウの準備とユーザー設定

まず最初に行うのは、このインジケータが「どこに」「どのように」表示されるかの基本ルール作りと、使う人が設定画面で変更できる項目の準備です。


#property indicator_separate_window     // メインチャートではなく、サブウィンドウに表示する
#property indicator_height 35           // サブウィンドウの高さを「35ピクセル」に固定
#property indicator_minimum 0.0         // 縦軸の最小値を0.0に固定
#property indicator_maximum 2.0         // 縦軸の最大値を2.0に固定

input group "=== 文字と見た目の設定 ==="
input int    Settei_Moji_Size     = 12;        // 文字の大きさ
input color  Settei_Moji_Color    = clrWhite;  // 文字の色
input int    Settei_Moji_Kankaku  = 90;        // 文字同士の最低間隔

ここでは #propertyという命令を使って、このツールを「サブウィンドウ(チャートの下の枠)」に表示するように指定しています。

また、ウィンドウの高さが勝手に伸び縮みすると見栄えが悪いので、高さを「35ピクセル」に固定します。

そして、ここで少し不思議な「0から2」という設定が出てきます。これは、「文字を真ん中に置くための『目盛り』」を作っていると考えてください。

床の高さを「0」

天井の高さを「2」

と決めておけば。真ん中の高さは自動的に「1」になります。 この様に設定しておくと、あとでプログラムから「高さ1.0(真ん中)に文字を置いて!」と命令するだけで、ズレることなく綺麗に真ん中に表示できるようになります。

input から始まる行は、ユーザーがパラメーター設定画面で自由に変更できる変数です。「文字が小さい」と感じたらプログラムを書き換えなくても設定画面で数字を変えるだけで済むように、ここで受け皿を作っています。


enum ENUM_DST_TYPE
  {
   DST_US, // 米国方式 (2週目日曜~11月1週目日曜)
   DST_EU  // 欧州方式 (最終日曜~10月最終日曜)
  };

input group "=== サマータイム設定 ==="
input ENUM_DST_TYPE Settei_DST_Type = DST_US; // サマータイム方式(デフォルト米国)

今回はサマータイム(夏時間)に対応するため、enum という機能を使ってリストを作っています。 これにより、設定画面で「米国方式」「欧州方式」をリストから選べるようになります。

input から始まる行は、ユーザーがパラメーター設定画面で自由に変更できる変数です。

間隔の計算(自動調整機能)

チャートには1分足や1時間足など様々な種類があります。1分足なら「30分ごと」に時間を表示したいですが、1時間足で「30分ごと」に表示しようとすると、ローソク足1本の中に文字を詰め込むことになり無理が生じます。そこで、時間足に合わせて表示間隔を自動で切り替える機能を作ります。


int Kankaku_Get_Fun(ENUM_TIMEFRAMES Jikan_Ashi)
{
   int kankaku = -1;
   
   switch(Jikan_Ashi)
   {
      case PERIOD_M1:  kankaku=Kankaku_M1_Fun;  break; // 1分足なら設定値(30分)
      case PERIOD_M5:  kankaku=Kankaku_M5_Fun;  break; // 5分足なら設定値(60分)
      // ... (他の時間足も同様) ...
      default:         kankaku=60; break;
   }
   return kankaku;
}

ここでは switch という分岐処理を使っています。

「もし今のチャートが1分足(PERIOD_M1)なら、設定しておいた『30』という数字を使う」「もし5分足なら『60』を使う」というように、カタログから最適な数字を選び出してくる役割を果たします。

これにより、どの時間足に切り替えても、常に見やすい間隔で時間が表示されるようになります。

メインの描画処理(日本時間の計算と表示)

ここがこのプログラムの心臓部です。チャート上のローソク足の時間を読み取り、時差を修正して日本時間にし、それを画面に文字として書き込みます。


void Jikan_Hyouji_Koushin()
{
   // ... (画面の表示範囲を取得する処理) ...

   // 計算した範囲のバーを1本ずつチェックするループ
   for(int i = start_i; i <= end_i; i++)
   {
      // 1. 時差の計算(サーバー時間 - GMT + 日本時間の9時間)
     long offset_sec = Get_JST_Offset(time_array[i]);
   long current_jst = (long)time_array[i] + offset_sec; // そのバーの日本時間を算出
      
      // 2. 表示タイミングの判定
      // 「今回の時間の区切り番号」と「前回の区切り番号」が違えば、ここが境目
      long current_block = current_jst / kankaku_byou;
      long prev_block    = prev_jst / kankaku_byou;
      
      if(current_block != prev_block) // 時間の変わり目なら
      {
          // 3. 重なり防止チェック
          // 「前回文字を書いた場所(last_drawn_x)」と「今回の場所(x)」の隙間を測る
          int gap = x - last_drawn_x;
          
          if(gap >= Settei_Moji_Kankaku) // 隙間が十分空いていれば
          {
             // 4. 文字(オブジェクト)の作成
             ObjectCreate(chart_id, obj_name, OBJ_TEXT, sub_window, time_array[i], 1.0);
             ObjectSetString(chart_id, obj_name, OBJPROP_TEXT, text); // 日時をセット
             
             last_drawn_x = x; // 「ここに書いた」と場所を記録しておく
          }
      }
   }
}

この長い処理の中で特に重要なのは「重なり防止チェック」です。単に時間が来たからといって文字を表示すると、チャートをズームアウトした時に文字が重なって真っ黒になってしまいます。

そこで、文字を表示しようとするたびに「さっき文字を書いた場所から、十分な距離(ピクセル)が離れているか」を確認しています。

もし距離が近すぎる場合は、キリの良い時間であってもあえて表示をスキップします。これによって、常にスッキリとした見た目が保たれます。

パソコンを重くしないためのお掃除機能

チャートをスクロールすると、これまで表示されていた時間は画面の外へ消えていきます。画面外にある文字データをそのまま放置しておくと、パソコンのメモリが無駄に使われて動作が重くなります。それを防ぐための処理です。


void Fuyou_Moji_Sakujyo(long chart_id, int window_num, int start_index, int end_index)
{
   int Sousuu = ObjectsTotal(chart_id, window_num, OBJ_TEXT); // 文字の総数

   // 全ての文字オブジェクトをチェック
   for(int i = Sousuu - 1; i >= 0; i--)
   {
      // ... (オブジェクトの名前と位置を確認) ...
      
      // 表示すべき範囲(start〜end)の外なら削除する
      if(obj_index < start_index || obj_index > end_index)
      {
         ObjectDelete(chart_id, obj_name); // 削除実行
      }
   }
}

ここでは、画面上に存在するすべての文字オブジェクトを監視しています。「この文字は、今見えている範囲(startからendまで)に含まれているか」を一つずつ確認し、範囲外にはみ出している文字があれば ObjectDelete 命令で即座に削除します。

新しい文字を書くことと同じくらい、この「捨てる」作業が快適な動作には不可欠です。

夏時間・冬時間の自動判定

ここが今回のコードの大きな特徴です。チャートの日付ひとつひとつに対して「この日は夏時間か?冬時間か?」を判定し、正しい時差(6時間または7時間)を計算する専用の関数を作っています。


   //----------------------------------------------------------------
   // 欧州方式 (DST_EU) の場合
   // 夏時間:3月の最終日曜日 ~ 10月の最終日曜日
   //----------------------------------------------------------------
   if(Settei_DST_Type == DST_EU)
   {
      // 3月~10月の間なら夏時間の可能性が高い
      if(Jikan_Data.mon > 3 && Jikan_Data.mon < 10) Natsu_Jikan_Desuka = true;
      else if(Jikan_Data.mon < 3 || Jikan_Data.mon > 10) Natsu_Jikan_Desuka = false;
      else
      {
         // 3月の場合 (最終日曜日を過ぎているかチェック)
         if(Jikan_Data.mon == 3)
         {
            // 月末(31日)から逆算して、最終日曜日が何日か求める
            // 計算式:31日 - (31日の曜日) ※31日の曜日は現在日から推測
            int Matsubi_No_Youbi = (Jikan_Data.day_of_week + (31 - Jikan_Data.day)) % 7; 
            int Saishu_Nichiyoubi_Hizuke = 31 - Matsubi_No_Youbi;
            
            // 今日の日付が、最終日曜より後なら夏時間
            if(Jikan_Data.day > Saishu_Nichiyoubi_Hizuke) Natsu_Jikan_Desuka = true;
            
            // 当日の場合、欧州はGMT1時(日本時間だとサーバー時間にもよるが早い時間)で切り替わる
            else if(Jikan_Data.day == Saishu_Nichiyoubi_Hizuke && Jikan_Data.hour >= 1) Natsu_Jikan_Desuka = true;
         }
         
         // 10月の場合 (最終日曜日までかどうかチェック)
         if(Jikan_Data.mon == 10)
         {
             // 10月も31日まであるので計算は同じ
             int Matsubi_No_Youbi = (Jikan_Data.day_of_week + (31 - Jikan_Data.day)) % 7;
             int Saishu_Nichiyoubi_Hizuke = 31 - Matsubi_No_Youbi;
             
             // 今日の日付が、最終日曜より前なら夏時間
             if(Jikan_Data.day < Saishu_Nichiyoubi_Hizuke) Natsu_Jikan_Desuka = true;
             
             // 当日の場合、切り替え時間より前なら夏時間
             else if(Jikan_Data.day == Saishu_Nichiyoubi_Hizuke && Jikan_Data.hour < 1) Natsu_Jikan_Desuka = true;
         }
      }
   }

   // --- 結果を秒数で返す ---
   // 夏時間なら: サーバー(GMT+3) → 日本(GMT+9) なので、差は「6時間」
   // 冬時間なら: サーバー(GMT+2) → 日本(GMT+9) なので、差は「7時間」
   
   if(Natsu_Jikan_Desuka == true)
   {
      return 6 * 3600; // 6時間 × 3600秒
   }
   else
   {
      return 7 * 3600; // 7時間 × 3600秒
   }
}


この Get_JST_Offset 関数に「判定したい時間」を渡すと、その日が夏時間なら「6時間分の秒数」、冬時間なら「7時間分の秒数」を返してくれます。 「3月の第2日曜日」のような複雑な切り替えルールも、この中で計算しています。

マウスに追従するカーソル

マウスを動かした時に、縦線とラベルが吸い付くように動く機能です。これはマウスが動くたびに何度も呼び出されます。


void Mouse_Cursor_Koushin(long chart_id, int mouse_x, int mouse_y, int sub_window)
{
   // 1. マウスの座標(ピクセル)を、時間と価格に変換する
   ChartXYToTimePrice(chart_id, mouse_x, mouse_y, sub_window, mouse_time, mouse_price);

   // 2. その時間を日本時間に計算し直す
   long offset_sec = Get_JST_Offset(mouse_time);
  long current_jst = (long)mouse_time + offset_sec;

   // 3. 縦線(VLINE)をその時間の場所に「移動」させる
   if(ObjectFind(chart_id, Cursor_Tatesen_Namae) >= 0)
   {
       ObjectMove(chart_id, Cursor_Tatesen_Namae, 0, mouse_time, 0);
   }
   
   // 4. ラベル(BUTTON)の位置と文字を更新する
   ObjectSetString(chart_id, Cursor_Label_Namae, OBJPROP_TEXT, text);
   ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_XDISTANCE, label_x);
}

ここでのポイントは ChartXYToTimePrice という便利な関数です。

これは「画面の左から何ピクセル目」というマウスの情報を、「チャート上の何月何日何時」という時間の情報に変換してくれます。

そして、縦線やラベルを表示する際、毎回削除して作り直すのではなく ObjectMove や ObjectSetInteger を使って「位置だけを変更」しています。

作り直すと画面が一瞬消えてチカチカしてしまいますが、移動させるだけなら非常に滑らかにアニメーションするように見えます。

イベントの監視

最後に、これまで作った機能を「いつ動かすのか」を決める指令塔の部分です。


void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   // パターンA:オブジェクトがクリックされた時
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      if(sparam == Button_Waku_Namae) // ボタンが押されたら
      {
         Button_Switch_ON = !Button_Switch_ON; // スイッチを反転(ON⇔OFF)
         Button_LINE(); // ボタンの色を変える
         ChartRedraw(); // 画面更新
      }
   }
   
   // パターンB:マウスが動いた時
   if(id == CHARTEVENT_MOUSE_MOVE)
   {
      // マウス位置に合わせてカーソルを動かす関数を呼ぶ
      Mouse_Cursor_Koushin(0, (int)lparam, (int)dparam, sub_window);
   }
}

この OnChartEvent は、ユーザーの操作を常に待ち構えています。 マウスが動けば「マウスが動いた!」と検知してカーソル移動の命令を出し、クリックされれば「クリックされた!」と検知してボタンのON/OFFを切り替えます。

この部分があるおかげで、ただ情報を表示するだけでなく、ユーザーが操作できる「ツール」として機能するようになっています。

Japantime-big【MT4】のコードを公開

コードはコピペでそのまま使えます。


//+------------------------------------------------------------------+
//|      japatime-big_Ver_MQL4.mq4                              |
//|      初心者向け:全行コメント解説付き (MQL4版)                   |
//+------------------------------------------------------------------+
#property copyright "ayase tomoya"      // 著作権者の表示
#property version   "1.0"               // バージョン番号
#property strict                        // MQL4の厳密なコンパイルモード

//--- インジケータの基本設定 ---
#property indicator_separate_window     // メインチャートではなく、サブウィンドウに表示する
#property indicator_buffers 0           // 計算用のバッファ(メモリ領域)、今回未使用
#property indicator_plots   0           // 折れ線グラフなどは描画、今回未使用

#property indicator_height 35           // サブウィンドウの高さを「35ピクセル」に固定

// 「文字を常に画面の『真ん中』に表示させるために、画面の上下の範囲を固定する設定
#property indicator_minimum 0.0         // 縦軸の最小値を0.0に固定
#property indicator_maximum 2.0         // 縦軸の最大値を2.0に固定(これで高さが固定される)

string Week_Names[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; // 曜日用の配列

// 追加:サマータイム方式の選択リスト
enum ENUM_DST_TYPE
  {
   DST_US, // 米国方式 (2週目日曜~11月1週目日曜)
   DST_EU  // 欧州方式 (最終日曜~10月最終日曜)
  };
  
// === サマータイム設定 ===
input ENUM_DST_TYPE Settei_DST_Type = DST_US; // サマータイム方式(デフォルト米国)  

//====================================================================
// ユーザーが設定画面で変更できるパラメータ (input変数)
// ※MQL4にはinput groupがないため、コメントで区切っています
//====================================================================
// === 文字と見た目の設定 ===
input int    Settei_Moji_Size     = 12;         // 文字の大きさ(フォントサイズ)
input color  Settei_Moji_Color        = clrWhite;   // 文字の色
input int    Settei_Moji_Kankaku  = 90;         // 文字同士の最低間隔(ピクセル単位)

// === カーソル(十字線)の設定 ===
input color  Settei_Cursor_Sen_Color  = clrYellow; // 縦線の色
input color  Settei_Cursor_Moji_Color = clrYellow; // 時刻表示の文字色
input color  Settei_Cursor_Haikei     = clrBlack;    // 時刻表示の背景色

//--- 時間足ごとのグリッド間隔設定 ---
// === 時間足ごとの表示間隔(分) ===
// ※MT4に存在しない時間足(2分足など)の設定は削除しました
input int Kankaku_M1_Fun  = 30;                // 1分足の時は30分間隔
input int Kankaku_M5_Fun  = 60;                // 5分足の時は60分間隔
input int Kankaku_M15_Fun = 120;               // 15分足...
input int Kankaku_M30_Fun = 240;               // 30分足...
input int Kankaku_H1_Fun  = 480;               // 1時間足...
input int Kankaku_H4_Fun  = 1440;              // 4時間足...
input int Kankaku_D1_Fun  = 14400;             // 日足用
input int Kankaku_W1_Fun  = 72000;             // 週足用
input int Kankaku_MN1_Fun = 259200;            // 月足用

//====================================================================
// グローバル変数(プログラム全体で共通して使う変数)
//====================================================================
string Obj_Namae_No_Atama   = "JST_Moji_";      // 作成する文字オブジェクトの名前の先頭部分

// カーソル(縦線とラベル)の名前を決めておく
string Cursor_Tatesen_Namae = "JST_Cursor_Tatesen"; // 縦線の名前
string Cursor_Label_Namae   = "JST_Cursor_Label";   // 時刻ラベルの名前

// ON/OFFボタンの名前と状態
string Button_Waku_Namae    = "JST_Button_Waku";    // ボタンの「枠」の名前
string Button_Moji_Namae    = "JST_Button_Moji";    // ボタンの「文字」の名前
bool   Button_Switch_ON     = true;                  // ボタンの状態(trueならON、falseならOFF)

//+------------------------------------------------------------------+
//| 【関数】今の時間足に合わせて「何分間隔」にするか決める場所
//+------------------------------------------------------------------+

// MQL4では時間足はint型(分)で扱われることが多いですが、定数(PERIOD_M1等)を使用します
int Kankaku_Get_Fun(int Jikan_Ashi)
{
   int kankaku = -1; // とりあえず初期値として無効な値を入れておく
   
   // 今のチャートの時間足(Jikan_Ashi)がどれかによって分岐
   // ※MT4にある標準的な時間足のみ残しています
   switch(Jikan_Ashi)
   {
      case PERIOD_M1:  kankaku=Kankaku_M1_Fun;  break;
      case PERIOD_M5:  kankaku=Kankaku_M5_Fun;  break;
      case PERIOD_M15: kankaku=Kankaku_M15_Fun; break;
      case PERIOD_M30: kankaku=Kankaku_M30_Fun; break;
      case PERIOD_H1:  kankaku=Kankaku_H1_Fun;  break;
      case PERIOD_H4:  kankaku=Kankaku_H4_Fun;  break;
      case PERIOD_D1:  kankaku=Kankaku_D1_Fun;  break;
      case PERIOD_W1:  kankaku=Kankaku_W1_Fun;  break;
      case PERIOD_MN1: kankaku=Kankaku_MN1_Fun; break;
      default:           kankaku=60; break;               // 想定外の時間足なら60分にする
   }
   
   if(kankaku <= 0) kankaku = 60; // もし0以下になっていたら60に直す(安全策)
   
   // チャートの時間足自体が何分か計算
   // MQL4ではJikan_Ashi自体が「分数」を表す整数なのでそのまま使えます
   int Ashi_no_Funsu = Jikan_Ashi;
   
   // エラー防止
   if(Ashi_no_Funsu <= 0)
   {
      Ashi_no_Funsu = 1; // 0なら1にする(割り算エラー防止)
   }

   // 設定した間隔が、足の分数より短かったら意味がないので合わせる
   if(kankaku < Ashi_no_Funsu)
   {
      kankaku = Ashi_no_Funsu;
   }
   else
   {
      // キリよく割り切れるように調整(余りが出ないようにする)
      int amari = kankaku % Ashi_no_Funsu;
      if(amari != 0)
      {
         kankaku += (Ashi_no_Funsu - amari);
      }
   }
   return kankaku; // 最終的に決定した「間隔(分)」を呼び出し元に返す
}

//+------------------------------------------------------------------+
// 【関数】パソコンが重くならないように、画面から見えなくなった不要な文字(日時)を削除する関数
// 日本時間を全ての範囲で表示すると処理が重くなる最悪フリーズします。今見えている範囲のみで時間を表示する事が重要になります
//+------------------------------------------------------------------+

void Fuyou_Moji_Sakujyo(long chart_id, int window_num, int start_index, int end_index)
{

   // ObjectsTotal = チャート上に、文字や線などのオブジェクトが「全部で何個あるか」を数えて教えてくれる関数
   // MQL4のObjectsTotalは引数が異なりますが、最近のMT4(build 600+)ならこの書き方でも動く場合があります。
   // ここではMQL4標準的な書き方に合わせています。
   int Sousuu = ObjectsTotal(chart_id, window_num, OBJ_TEXT); // 今あるテキストオブジェクトの総数
   
   // 最新のオブジェクトから順番にチェック(削除時は後ろからループするのが鉄則)
   for(int i = Sousuu - 1; i >= 0; i--)
   {
      // 削除すべきかどうかを判断するために、i番目のテキストオブジェクトの『名前』を読み取って、いったん変数にメモ
      string obj_name = ObjectName(chart_id, i, window_num, OBJ_TEXT); // オブジェクトの名前を取得
      
      // 名前がこのインジケータのもの(JST_Moji_で始まる)かどうか確認
      if(StringFind(obj_name, Obj_Namae_No_Atama) == 0)
      {
         
         // 名前の後ろの番号部分を取り出す
         string str_num = StringSubstr(obj_name, StringLen(Obj_Namae_No_Atama));
         int obj_index = (int)StringToInteger(str_num); // 文字列を数字に変換
         
         // 表示すべき範囲(start〜end)の外なら削除する
         if(obj_index < start_index || obj_index > end_index)
         {
            ObjectDelete(chart_id, obj_name); // 削除実行
         }
      }
   }
}


//+------------------------------------------------------------------+
//| 【関数】指定した時間がサマータイム(夏時間)かどうかを判定して
//|  日本時間(JST)との時差(秒数)を計算して返す場所
//|  ※MT4対応版
//+------------------------------------------------------------------+
long Get_JST_Offset(datetime Hantei_Time)
{
   // 日付や曜日を調べやすくするために、時間のデータを分解できる「構造体」に入れます
   // (MT4でもこの書き方で問題なく動きます)
   MqlDateTime Jikan_Data;
   TimeToStruct(Hantei_Time, Jikan_Data);

   bool Natsu_Jikan_Desuka = false; // 「今は夏時間ですか?」というフラグ(最初は「いいえ(false)」にしておく)

   //----------------------------------------------------------------
   // 米国方式 (DST_US) の場合
   // 夏時間:3月の第2日曜日 ~ 11月の第1日曜日
   //----------------------------------------------------------------
   if(Settei_DST_Type == DST_US)
   {
      // 3月よりあと、かつ、11月より前なら、間違いなく夏時間
      if(Jikan_Data.mon > 3 && Jikan_Data.mon < 11) Natsu_Jikan_Desuka = true;
      
      // 3月より前、または、11月よりあとなら、間違いなく冬時間
      else if(Jikan_Data.mon < 3 || Jikan_Data.mon > 11) Natsu_Jikan_Desuka = false;
      
      // 際どい「3月」と「11月」の判定
      else
      {
         // 3月の場合 (第2日曜日を過ぎているかチェック)
         if(Jikan_Data.mon == 3)
         {
            // まず「3月1日」が何曜日だったか逆算して調べる(0:日曜 ~ 6:土曜)
            int Tsuitachi_No_Youbi = (Jikan_Data.day_of_week - (Jikan_Data.day - 1) % 7 + 7) % 7;
            
            // 「第2日曜日」が何日になるか計算
            // 式の意味:1日 + (次の日曜までの日数) + 7日(1週間分)
            int Daini_Nichiyoubi_Hizuke = 1 + (7 - Tsuitachi_No_Youbi) % 7 + 7; 
            
            // 今日の日付が、第2日曜より後なら夏時間
            if(Jikan_Data.day > Daini_Nichiyoubi_Hizuke) Natsu_Jikan_Desuka = true;
            
            // もし今日がドンピシャで「第2日曜」なら、MT4サーバー時間で「2時」を過ぎているかチェック
            else if(Jikan_Data.day == Daini_Nichiyoubi_Hizuke && Jikan_Data.hour >= 2) Natsu_Jikan_Desuka = true; 
         }
         
         // 11月の場合 (第1日曜日までかどうかチェック)
         if(Jikan_Data.mon == 11)
         {
            // 「11月1日」が何曜日か調べる
            int Tsuitachi_No_Youbi = (Jikan_Data.day_of_week - (Jikan_Data.day - 1) % 7 + 7) % 7;
            
            // 「第1日曜日」が何日になるか計算
            int Daiichi_Nichiyoubi_Hizuke = 1 + (7 - Tsuitachi_No_Youbi) % 7; 
            
            // 今日の日付が、第1日曜より前ならまだ夏時間
            if(Jikan_Data.day < Daiichi_Nichiyoubi_Hizuke) Natsu_Jikan_Desuka = true;
            
            // もし今日がドンピシャで「第1日曜」なら、朝2時より前なら夏時間
            else if(Jikan_Data.day == Daiichi_Nichiyoubi_Hizuke && Jikan_Data.hour < 2) Natsu_Jikan_Desuka = true;
         }
      }
   }
   
   //----------------------------------------------------------------
   // 欧州方式 (DST_EU) の場合
   // 夏時間:3月の最終日曜日 ~ 10月の最終日曜日
   //----------------------------------------------------------------
   if(Settei_DST_Type == DST_EU)
   {
      // 3月~10月の間なら夏時間の可能性が高い
      if(Jikan_Data.mon > 3 && Jikan_Data.mon < 10) Natsu_Jikan_Desuka = true;
      else if(Jikan_Data.mon < 3 || Jikan_Data.mon > 10) Natsu_Jikan_Desuka = false;
      else
      {
         // 3月の場合 (最終日曜日を過ぎているかチェック)
         if(Jikan_Data.mon == 3)
         {
            // 月末(31日)から逆算して、最終日曜日が何日か求める
            // 計算式:31日 - (31日の曜日) ※31日の曜日は現在日から推測
            int Matsubi_No_Youbi = (Jikan_Data.day_of_week + (31 - Jikan_Data.day)) % 7; 
            int Saishu_Nichiyoubi_Hizuke = 31 - Matsubi_No_Youbi;
            
            // 今日の日付が、最終日曜より後なら夏時間
            if(Jikan_Data.day > Saishu_Nichiyoubi_Hizuke) Natsu_Jikan_Desuka = true;
            
            // 当日の場合、欧州はGMT1時(MT4時間だと早い時間)で切り替わる
            else if(Jikan_Data.day == Saishu_Nichiyoubi_Hizuke && Jikan_Data.hour >= 1) Natsu_Jikan_Desuka = true;
         }
         
         // 10月の場合 (最終日曜日までかどうかチェック)
         if(Jikan_Data.mon == 10)
         {
             // 10月も31日まであるので計算は同じ
             int Matsubi_No_Youbi = (Jikan_Data.day_of_week + (31 - Jikan_Data.day)) % 7;
             int Saishu_Nichiyoubi_Hizuke = 31 - Matsubi_No_Youbi;
             
             // 今日の日付が、最終日曜より前なら夏時間
             if(Jikan_Data.day < Saishu_Nichiyoubi_Hizuke) Natsu_Jikan_Desuka = true;
             
             // 当日の場合、切り替え時間より前なら夏時間
             else if(Jikan_Data.day == Saishu_Nichiyoubi_Hizuke && Jikan_Data.hour < 1) Natsu_Jikan_Desuka = true;
         }
      }
   }

   // --- 結果を秒数で返す ---
   // 夏時間なら: サーバー(GMT+3) → 日本(GMT+9) なので、差は「6時間」
   // 冬時間なら: サーバー(GMT+2) → 日本(GMT+9) なので、差は「7時間」
   
   if(Natsu_Jikan_Desuka == true)
   {
      return 6 * 3600; // 6時間 × 3600秒
   }
   else
   {
      return 7 * 3600; // 7時間 × 3600秒
   }
}

//+------------------------------------------------------------------+
// ボタンは枠と文字で作成しています。
//+------------------------------------------------------------------+
void Button_LINE()
{
   long chart_id = ChartID();           // 今のチャートID
   int sub_window = ChartWindowFind(); // このインジケータがいるサブウィンドウ番号

   // --- 1. ボタンの「枠」を作る(まだ存在しない場合のみ) ---
   if(ObjectFind(chart_id, Button_Waku_Namae) < 0)
   {
      ObjectCreate(chart_id, Button_Waku_Namae, OBJ_RECTANGLE_LABEL, sub_window, 0, 0); // 枠作成
      ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_CORNER, CORNER_LEFT_UPPER); // 左上基準
      ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_XDISTANCE, 5);  // 左から5pxの位置
      ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_YDISTANCE, 5);  // 上から5pxの位置
      ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_XSIZE, 60);     // 幅50px
      ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_YSIZE, 24);     // 高さ24px
      ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_COLOR, clrBlack); // 枠線の色
      ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_BACK, false);     // 背景として描画しない
      ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_SELECTABLE, true); // クリック選択可能にする
      ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_ZORDER, 255);       // 最前面に表示
   }

   // --- 2. ボタンの「文字」を作る(まだ存在しない場合のみ) ---
   if(ObjectFind(chart_id, Button_Moji_Namae) < 0)
   {
      ObjectCreate(chart_id, Button_Moji_Namae, OBJ_LABEL, sub_window, 0, 0); // ラベル作成
      ObjectSetInteger(chart_id, Button_Moji_Namae, OBJPROP_CORNER, CORNER_LEFT_UPPER);
      ObjectSetInteger(chart_id, Button_Moji_Namae, OBJPROP_XDISTANCE, 19); // 文字の位置調整X
      ObjectSetInteger(chart_id, Button_Moji_Namae, OBJPROP_YDISTANCE, 10); // 文字の位置調整Y
      ObjectSetInteger(chart_id, Button_Moji_Namae, OBJPROP_FONTSIZE, 10);  // 文字サイズ
      ObjectSetString(chart_id, Button_Moji_Namae, OBJPROP_FONT, "Arial");  // フォント
      ObjectSetInteger(chart_id, Button_Moji_Namae, OBJPROP_COLOR, clrBlack); // 文字色
      ObjectSetInteger(chart_id, Button_Moji_Namae, OBJPROP_BACK, false);
      ObjectSetInteger(chart_id, Button_Moji_Namae, OBJPROP_SELECTABLE, true); // クリック選択可能
      ObjectSetInteger(chart_id, Button_Moji_Namae, OBJPROP_ZORDER, 255);       // 最前面
   }

   // --- 3. ON/OFFの状態に合わせて色と文字を更新する ---
   // ONならライム色(緑)、OFFなら白にする(条件演算子 ? : を使用)
   ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_BGCOLOR, Button_Switch_ON ? clrLime : clrWhite);
   
   // ONなら「LINE」、OFFなら「LINE」と表示する
   ObjectSetString(chart_id, Button_Moji_Namae, OBJPROP_TEXT, Button_Switch_ON ? "LINE" : "LINE");
   
   // クリックした後に選択枠(ポチポチ)が残らないように選択状態を解除する
   ObjectSetInteger(chart_id, Button_Waku_Namae, OBJPROP_SELECTED, false);
   ObjectSetInteger(chart_id, Button_Moji_Namae, OBJPROP_SELECTED, false);
}

//+------------------------------------------------------------------+
//| 【関数】日本時間を計算して画面に並べる(メイン処理)
//+------------------------------------------------------------------+
void Jikan_Hyouji_Koushin()
{
   long chart_id = ChartID();              // チャートID
   int sub_window = ChartWindowFind();     // サブウィンドウ番号
   int bar_Sousuu = Bars;                  // チャートにあるローソク足の総数 (MQL4ではBars)

   // --- 今、画面に見えている範囲(左端の場所と、表示されている本数)を取得 ---
   long hidari_hashi_index = ChartGetInteger(chart_id, CHART_FIRST_VISIBLE_BAR); 

   // 画面の横幅の中に、ローソク足が「全部で何本」入っているか(表示本数)を取得します
   long mieteru_honsuu     = ChartGetInteger(chart_id, CHART_VISIBLE_BARS);    

   int yohaku = 50; // 計算範囲に少し余裕を持たせる(50本分)
   
   // --- 配列(データの入れ物)のどこからどこまでを計算するか決める ---
   int start_i = bar_Sousuu - 1 - (int)(hidari_hashi_index + yohaku); 
   int end_i   = bar_Sousuu - 1 - (int)(hidari_hashi_index - mieteru_honsuu - yohaku);

   // --- 配列の範囲外エラーを防ぐための補正処理 ---
   if(start_i < 0) start_i = 0;
   if(end_i >= bar_Sousuu) end_i = bar_Sousuu - 1;
   if(start_i > end_i) return; 

   // 画面外にある古いオブジェクトを削除して軽くする
   Fuyou_Moji_Sakujyo(chart_id, sub_window, start_i, end_i);

   // --- 表示間隔の準備 ---
   // 関数(Kankaku_Get_Fun)で決めた「何分間隔」を、計算用に「秒」に直します(分 × 60秒)
   long kankaku_byou = Kankaku_Get_Fun(_Period) * 60;
   
   // 最後に文字を表示した画面上のX座標(横位置)を記憶する変数です
   int last_drawn_x = -999999; 

   // --- 時間データの取得 ---
   datetime time_array[]; // すべてのローソク足の時間データを入れるための配列(箱)を用意します
   
   // 配列の並び順を「古い順(左から右)」に設定します
   ArraySetAsSeries(time_array, false); 
   
   // チャート上の時間データ(_Symbol, _Period)を、用意した配列(time_array)にコピーします
   if(CopyTime(_Symbol, _Period, 0, bar_Sousuu, time_array) <= 0)
   {
      return;
   }    

   // 計算した範囲(画面に見えている部分+α)のバーを1本ずつチェックするループ
   for(int i = start_i; i <= end_i; i++)
   {
      string obj_name = Obj_Namae_No_Atama + IntegerToString(i); // バーごとのオブジェクト名を生成
      
      bool is_grid_point = false; // 「ここに時間を表示すべきか?」を判定するフラグ(最初はfalse)
      
      // ★修正:夏時間・冬時間を考慮した時差を計算
      long offset_sec = Get_JST_Offset(time_array[i]);
      long current_jst = (long)time_array[i] + offset_sec;
      
      // 1つ前のバーと比較して、設定した時間区切り(例:1時間)を跨いだかチェック
      if(i > 0)
      {
         // 1つ前のバーも、その時点での夏冬判定を行う
         long prev_offset_sec = Get_JST_Offset(time_array[i-1]);
         long prev_jst = (long)time_array[i-1] + prev_offset_sec;
         
         // 時間を間隔秒数で割ることで「区切り番号」を算出
         long current_block = current_jst / kankaku_byou; // 現在の区切り番号
         long prev_block    = prev_jst / kankaku_byou;    // 1つ前の区切り番号
         
         // 区切り番号が変わった=ここが時間の変わり目なので表示ポイントとする
         if(current_block != prev_block) is_grid_point = true;
      }
      else is_grid_point = true; // 配列の最初(一番左端)のバーは比較対象がないので無条件で表示候補にする

      if(is_grid_point) // ここが表示ポイントだと判定された場合の処理
      {
         int x, y;
         // その時間(time_array[i])が、画面上のどの座標(X,Y)にあるかを取得
         if(ChartTimePriceToXY(chart_id, sub_window, time_array[i], 0.0, x, y))
         {
            // 左上の「LINE」ボタンと文字が被らないように、X座標が110未満(左端付近)なら表示しない
            if(x < 110)
            {
               // もし既に文字が表示されていたら削除して、次のループへ進む
               if(ObjectFind(chart_id, obj_name) >= 0) ObjectDelete(chart_id, obj_name);
               continue; // 次のループへ(以下の処理はスキップ)
            }

            int gap = x - last_drawn_x; // 「前回文字を書いた場所」と「今回の場所」の隙間を計算
            
            // 隙間が設定値(Settei_Moji_Kankaku)以上空いていれば、重ならないので表示する
            if(gap >= Settei_Moji_Kankaku)
            {
               // 表示する時間を区切りのいい数字に丸める
               long rounded_time = (current_jst / kankaku_byou) * kankaku_byou;
               
               MqlDateTime dt;
               TimeToStruct((datetime)rounded_time, dt); // 計算しやすいように時間構造体へ変換
               
               // 画面に表示するテキストを作成(例: 12/15 10:00)
               string text = StringFormat("%02d/%02d %02d:%02d", dt.mon, dt.day, dt.hour, dt.min);
               
               // まだその場所にオブジェクトが存在しなければ新規作成する
               if(ObjectFind(chart_id, obj_name) < 0)
               {
                  // テキストオブジェクトを作成(作成成功したら中身を設定)
                  if(ObjectCreate(chart_id, obj_name, OBJ_TEXT, sub_window, time_array[i], 1.0))
                  {
                     // プロパティ(文字の内容、フォント、色、サイズなど)を設定
                     ObjectSetString(chart_id, obj_name, OBJPROP_TEXT, text);
                     ObjectSetString(chart_id, obj_name, OBJPROP_FONT, "Arial Bold");
                     ObjectSetInteger(chart_id, obj_name, OBJPROP_COLOR, Settei_Moji_Color);
                     ObjectSetInteger(chart_id, obj_name, OBJPROP_FONTSIZE, Settei_Moji_Size);
                     ObjectSetInteger(chart_id, obj_name, OBJPROP_ANCHOR, ANCHOR_CENTER); // 文字の中心を基準点に合わせる
                     ObjectSetInteger(chart_id, obj_name, OBJPROP_BACK, false); 
                     ObjectSetInteger(chart_id, obj_name, OBJPROP_SELECTABLE, false); // マウスで選択できないようにする
                     ObjectSetInteger(chart_id, obj_name, OBJPROP_ZORDER, 0); 
                  }
               }
               else
               {
                  // 既にオブジェクトがあるなら、作り直さずにテキスト内容だけ更新(PCの負荷軽減)
                  ObjectSetString(chart_id, obj_name, OBJPROP_TEXT, text); 
               }
               
               last_drawn_x = x; // 「ここに文字を書いた」というX座標を記録
            }
            else
            {
               // 表示タイミングだけど、隙間が狭すぎて重なってしまう場合は削除
               if(ObjectFind(chart_id, obj_name) >= 0) ObjectDelete(chart_id, obj_name);
            }
         }
      }
      else
      {
         // 表示ポイントではない場所(例:10:00と11:00の間の半端な時間)にオブジェクトが残っていたら削除
         if(ObjectFind(chart_id, obj_name) >= 0) ObjectDelete(chart_id, obj_name);
      }
   }
   
   // ボタンの表示も更新しておく
   Button_LINE();
   
   ChartRedraw(); // 画面を再描画して変更を反映
}

//+------------------------------------------------------------------+
//| 【関数】マウスに合わせてカーソルと時間を表示する場所
//+------------------------------------------------------------------+
void Mouse_Cursor_Koushin(long chart_id, int mouse_x, int mouse_y, int sub_window)
{
   // --- 機能OFFの場合の処理 ---
   // ボタンが「OFF」の状態なら、カーソル(縦線とラベル)が表示されていたら邪魔なので消去して終わる
   if(!Button_Switch_ON)
   {
      // 縦線が残っていたら削除
      if(ObjectFind(chart_id, Cursor_Tatesen_Namae) >= 0) ObjectDelete(chart_id, Cursor_Tatesen_Namae);
      // ラベルが残っていたら削除
      if(ObjectFind(chart_id, Cursor_Label_Namae) >= 0) ObjectDelete(chart_id, Cursor_Label_Namae);
      
      ChartRedraw(); // 画面を更新して削除を反映させる
      return;        // ここで関数を強制終了(以下の処理は行わない)
   }

   datetime mouse_time; // マウス位置の時間が入る変数
   double mouse_price;  // マウス位置の価格が入る変数(今回は使わないが関数に必要)
   
   // --- 座標変換 ---
   // マウスの画面上の座標(X,Y)から、その場所の「チャート時間」と「価格」を逆算して取得
   // 取得に成功したら if の中身を実行する
   if(ChartXYToTimePrice(chart_id, mouse_x, mouse_y, sub_window, mouse_time, mouse_price))
   {
      // --- 日本時間(JST)の計算 ---
      // ★修正:カーソル位置の時刻から夏時間・冬時間を判定
      int bar_idx = iBarShift(NULL, 0, mouse_time); // マウス位置に対応するバーの番号を取得
      datetime bar_time = iTime(NULL, 0, bar_idx);  // そのバーの時間を取得
      
      long offset_sec = Get_JST_Offset(bar_time);   // 判定関数でオフセットを取得
      long current_jst = (long)bar_time + offset_sec; // JST計算
      
      MqlDateTime dt;
      TimeToStruct((datetime)current_jst, dt); // 年月日を取り出せるように構造体に変換
      
      // カーソル用の表示テキスト作成(例: 2025/12/15 10:00 )
      // 左右にスペースを入れて見た目の余白を作っています
      string text = StringFormat(" %04d/%02d/%02d(%s) %02d:%02d ", dt.year, dt.mon, dt.day, Week_Names[dt.day_of_week], dt.hour, dt.min);

      // --- 1. 縦線 (VLINE) の描画処理 ---
      // まだ縦線オブジェクトが存在しない場合(初回表示など)
      if(ObjectFind(chart_id, Cursor_Tatesen_Namae) < 0) 
      {
         // 縦線を作成
         ObjectCreate(chart_id, Cursor_Tatesen_Namae, OBJ_VLINE, 0, mouse_time, 0); 
         ObjectSetInteger(chart_id, Cursor_Tatesen_Namae, OBJPROP_COLOR, Settei_Cursor_Sen_Color);
         ObjectSetInteger(chart_id, Cursor_Tatesen_Namae, OBJPROP_STYLE, STYLE_DOT); // 点線スタイル
         ObjectSetInteger(chart_id, Cursor_Tatesen_Namae, OBJPROP_WIDTH, 1);
         ObjectSetInteger(chart_id, Cursor_Tatesen_Namae, OBJPROP_BACK, true); // ローソク足の「裏側」に表示
         ObjectSetInteger(chart_id, Cursor_Tatesen_Namae, OBJPROP_SELECTABLE, false); // クリック選択不可
         
         // ★重要テクニック:ツールチップ(マウスを乗せた時に出るポップアップ)を無効化
         // "\n"(改行コードだけ)を入れると、ツールチップが出なくなります
         ObjectSetString(chart_id, Cursor_Tatesen_Namae, OBJPROP_TOOLTIP, "\n");
      }
      else
      {
         // 既に縦線がある場合は、作り直さずに「位置だけ移動」させる(この方が処理が軽くスムーズ)
         ObjectMove(chart_id, Cursor_Tatesen_Namae, 0, mouse_time, 0);
         ObjectSetString(chart_id, Cursor_Tatesen_Namae, OBJPROP_TOOLTIP, "\n");
      }

      // --- 2. 日本時間ラベル (BUTTON) の描画処理 ---
      // ※通常のラベル(OBJ_LABEL)ではなくボタン(OBJ_BUTTON)を使う理由は、背景色を綺麗につけるためです
      int label_width = 180; 
      int label_height = 24; 
      
      // ラベルが常にマウスの「真上」かつ「中心」に来るようにX座標を計算
      // マウスX位置から、ラベル幅の半分を引くと中心が合います
      int label_x = mouse_x - (label_width / 2);
      int label_y = 5; // Y座標はサブウィンドウの上部(5px)に固定

      // まだラベルが存在しない場合
      if(ObjectFind(chart_id, Cursor_Label_Namae) < 0) 
      {
         // ボタンオブジェクトを作成
         ObjectCreate(chart_id, Cursor_Label_Namae, OBJ_BUTTON, 0, 0, 0); 
         ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_CORNER, CORNER_LEFT_UPPER); // 左上基準
         ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_XSIZE, label_width); // 幅
         ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_YSIZE, label_height); // 高さ
         ObjectSetString(chart_id, Cursor_Label_Namae, OBJPROP_FONT, "Arial Bold");
         ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_FONTSIZE, Settei_Moji_Size);
         ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_COLOR, Settei_Cursor_Moji_Color); // 文字色
         ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_BGCOLOR, Settei_Cursor_Haikei);    // 背景色
         ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_BORDER_COLOR, Settei_Cursor_Haikei); // 枠線の色 
         ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_STATE, false); // ボタンが押し込まれていない状態
         ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_SELECTABLE, false);
         ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_ZORDER, 100); // 他の文字より手前に表示
         
         ObjectSetString(chart_id, Cursor_Label_Namae, OBJPROP_TOOLTIP, "\n"); // ツールチップ消去
      }
      
      // 内容や位置を更新(マウスに合わせて移動)
      ObjectSetString(chart_id, Cursor_Label_Namae, OBJPROP_TEXT, text);
      ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_XDISTANCE, label_x);
      ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_YDISTANCE, label_y);
      ObjectSetInteger(chart_id, Cursor_Label_Namae, OBJPROP_BGCOLOR, Settei_Cursor_Haikei); 
      
      ChartRedraw(); // 再描画して、画面上の位置ズレを即座に直す
   }
}

//+------------------------------------------------------------------+
//| 【関数】後片付け(作ったオブジェクトを全部消す)
//+------------------------------------------------------------------+
void Zen_Obj_Sakujo()
{
   // MQL4ではObjectsTotalに引数がないバージョンもありますが、
   // 最近のコンパイラではMQL5互換の引数付きが通ることが多いです。
   // ここではMQL4標準的なChartIDを指定する方法で記述します。
   int Sousuu = ObjectsTotal(0, -1, OBJ_TEXT); 
   for(int i = Sousuu - 1; i >= 0; i--) {      // 後ろからループ
      string obj_name = ObjectName(0, i, -1, OBJ_TEXT);
      // 名前が一致するなら削除
      if(StringFind(obj_name, Obj_Namae_No_Atama) == 0) ObjectDelete(0, obj_name);
   }
   // カーソルとラベルも削除
   ObjectDelete(0, Cursor_Tatesen_Namae);
   ObjectDelete(0, Cursor_Label_Namae);
   
   // ボタンも削除
   ObjectDelete(0, Button_Waku_Namae);
   ObjectDelete(0, Button_Moji_Namae);
}

//+------------------------------------------------------------------+
//| 初期化イベント(インジケータをチャートに入れた瞬間に1回動く)
//+------------------------------------------------------------------+
int OnInit()
{
   Zen_Obj_Sakujo(); // まず前の残骸があれば綺麗にする
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); // マウスの動きを検知できるように設定ON
   Jikan_Hyouji_Koushin(); // 最初の描画を実行
   return(INIT_SUCCEEDED); // 「初期化成功」をMT4に伝える
}

//+------------------------------------------------------------------+
//| 終了イベント(インジケータを削除した時に1回動く)
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); // マウス検知設定をOFFに戻す
   Zen_Obj_Sakujo(); // 全部消して綺麗にする
   ChartRedraw();    // 最後に画面更新
}

//+------------------------------------------------------------------+
//| 計算イベント(価格が動くたびに何度も動く)
//+------------------------------------------------------------------+
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[])
{
   Jikan_Hyouji_Koushin(); // メインの描画処理を呼び出す
   return(rates_total);    // 処理した数をMT4に返す
}

//+------------------------------------------------------------------+
//| チャートイベント関数(クリック、マウス移動、サイズ変更などを検知して動く場所)
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // どんなイベントが起きたかのID(クリック?移動?変更?)
                  const long &lparam,   // パラメータ1 (マウスのX座標などが入る整数)
                  const double &dparam, // パラメータ2 (マウスのY座標などが入る小数)
                  const string &sparam) // パラメータ3 (クリックしたオブジェクトの名前などが入る文字)
{
   // --- パターン1:チャートの変更イベント ---
   // チャートのサイズが変わった、スクロールされた、ズームされた時など
   if(id == CHARTEVENT_CHART_CHANGE)
   {
      // 表示範囲が変わっている可能性が高いので、日本時間の再計算と再描画を行う
      Jikan_Hyouji_Koushin(); 
   }
   
   // --- パターン2:オブジェクトのクリックイベント ---
   // 画面上の何らかのオブジェクトがクリックされた時
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      // クリックされたオブジェクトの名前(sparam)が、このツールの「ボタン枠」か「ボタン文字」だったら
      if(sparam == Button_Waku_Namae || sparam == Button_Moji_Namae)
      {
         // ★スイッチ切り替え処理
         // trueならfalseに、falseならtrueに反転させる(!マークは「否定/逆」の意味)
         Button_Switch_ON = !Button_Switch_ON; 
         
         // ボタンの見た目(緑色か白色かなど)を新しい状態に合わせて更新する
         Button_LINE(); 
         
         // もしOFFになったのなら、画面に残っているカーソル(縦線・ラベル)は邪魔なので即座に消す
         if(!Button_Switch_ON)
         {
            ObjectDelete(0, Cursor_Tatesen_Namae);
            ObjectDelete(0, Cursor_Label_Namae);
         }
         
         // 変更を即座に画面に反映させる
         ChartRedraw(); 
      }
   }
   
   // --- パターン3:マウスの移動イベント ---
   // マウスカーソルが動いた時(頻繁に発生します)
   if(id == CHARTEVENT_MOUSE_MOVE)
   {
      // このインジケータが表示されているサブウィンドウの番号を取得
      int sub_window = ChartWindowFind();
      
      // カーソル更新関数を呼び出す
      // lparam にはマウスのX座標、dparam にはY座標が入っているので、それをキャスト(int)して渡す
      Mouse_Cursor_Koushin(0, (int)lparam, (int)dparam, sub_window);
   }
}

japantime-bigをダウンロード

オリジナルインジケーター「japantime-big」はMT4・MT5専用のインジケーターになります。

MT4・MT5のダウンロードからインジケーターの導入方法に関しては関連記事をご確認下さい。

【簡単】MT4・MT5の導入~インジケーター導入方法を動画で紹介!
MT4・MT5の導入方法~インジケーターの導入方法を動画付きで分かりやすく紹介しています。また、導入方以外にも、MT4・MT5の注意点やおすすめの業者も紹介しています。

よくある質問・注意点

Q. パソコンが重くなりませんか?

A. 大丈夫です!
このツールは、「今、画面に見えている範囲だけ」 を計算して文字を表示する仕組みになっています。画面外の不要な文字は自動的に削除されるため、長時間チャートを開いていてもパソコンが重くならないように設計されています。

Q. 夏時間・冬時間は設定が必要ですか?

A. 基本的には必要ありません(自動判定されます)。
面倒な「3月の切り替え」や「11月の切り替え」をプログラムがカレンダーから自動計算するため、手動でサマータイム設定を変更する必要はありません。 ※お使いの業者が「欧州時間(冬GMT+2/夏GMT+3)」を採用している場合のみ、設定画面で「DST_EU」を選択してください(多くの業者は初期値の「DST_US」のままで大丈夫です)。

Q. 文字が被って見にくいです。

A. 設定で間隔を広げてください。
設定の「文字同士の最低間隔(ピクセル単位)」の数字を大きくするか、「時間足ごとの表示間隔(分)」にある各項目の数字を大きくしてみてください。

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