English Русский Deutsch
preview
MQL5取引ツール(第12回):相関行列ダッシュボードのインタラクティブ機能の強化

MQL5取引ツール(第12回):相関行列ダッシュボードのインタラクティブ機能の強化

MetaTrader 5トレーディング |
30 4
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

前回の記事(第11回)では、MetaQuotes Language 5 (MQL5)を用いて相関行列ダッシュボードを構築し、ピアソン、スピアマン、ケンドールの各手法を用いて、設定可能な時間足およびバー数に基づき資産間の関係性を計算しました。第12回では、このダッシュボードにインタラクティブ性を追加します。この拡張では、マウスイベントによるパネルのドラッグおよび最小化、視覚的フィードバックのためのボタンホバー効果、相関強度に基づくシンボルの昇順・降順ソート、相関表示とp値表示の切り替え、動的なカラー更新を伴うライトテーマとダークテーマの切り替え、ならびに詳細情報を表示するセルツールチップといった機能が導入されます。本記事では以下のトピックを扱います。

  1. インタラクティブ相関行列拡張の理解
  2. MQL5での実装
  3. バックテスト
  4. 結論

最終的には、カスタマイズ可能で使いやすさが向上したインタラクティブなMQL5相関ダッシュボードを利用できるようになります。それでは始めましょう。


インタラクティブ相関行列拡張の理解

この拡張では、既存のダッシュボードを基にして、ユーザーがより直感的に操作できる機能を追加し、リアルタイムで資産間の関係性を分析できるようにすることを目的としています。これにより、作業フローを妨げることなく、柔軟な操作とカスタマイズが可能になります。主な追加要素として、チャート上でダッシュボードの位置を変更できるマウスイベントによるパネルドラッグ、コンパクト表示(ヘッダーのみ表示)とフル表示を切り替える最小化・最大化機能、ボタンや時間足セルに対するホバー効果による色変化などの視覚的フィードバック、クリック操作による表示モードやビューの切り替えが含まれます。

また、平均絶対相関の強さに基づいて銘柄を並び替える機能も導入され、これにより強い相関を持つ資産を昇順、降順または元の順序でグループ化できます。さらに、相関表示とp値表示の切り替えにより統計的な理解を深めることができ、ユーザーの好みに応じてライトテーマとダークテーマを切り替え、色を動的に更新する機能も追加されます。加えて、セルにはツールチップが付与され、銘柄、時間足、手法、相関値、p値、使用バー数などの詳細情報を確認できます。

新しい表示モードに対応するため入力パラメータおよび列挙型を拡張し、インタラクション状態を管理するグローバル変数を追加します。また、元の銘柄情報を保持するために解析処理を修正し、平均相関強度に基づくソートロジックを実装してインデックスを再配置します。さらに、最小化およびドラッグ操作に対応するための生成処理および位置更新処理を拡張し、テーマの切り替えに応じてカラーを動的に更新します。加えて、p値表示およびツールチップ機能に対応したダッシュボード更新を行い、すべてのインタラクションをイベントハンドラで統合することで、シームレスな操作性を実現します。以下に想定されるビジュアル表示の例を示します。

機能強化フレームワーク


MQL5での実装

MQL5においてプログラムを拡張するためには、マルチダッシュボード制御機構を管理するための新しい入力パラメータと、追加のグローバル変数を定義する必要があります。

input color ColorLightThemeBg           = clrWhite;                                                         // Light theme background
input color ColorLightThemeText         = clrBlack;                                                         // Light theme text

enum ViewMode
{
   VIEW_CORR, // Show correlations
   VIEW_PVAL  // Show p-values
};

#define SORT_BUTTON             "BUTTON_SORT"                    // Define sort button identifier
#define THEME_BUTTON            "BUTTON_THEME"                   // Define theme toggle button identifier
#define COLOR_LIGHT_GRAY        C'230,230,230'                   // Define light gray for neutral cells
#define COLOR_DARK_GRAY         C'105,105,105'                   // Define dark gray for headers
#define BUTTON_HOVER_SIZE       24                               // Define hover size for buttons (centered around anchor)
#define NUM_LEGEND_ITEMS        15                               // Define increased to cover max possible (e.g., 11 in heatmap)

bool panel_is_visible = true;                                    //--- Set panel visibility flag
bool panel_minimized = false;                                    //--- Set minimized state flag
bool panel_dragging = false;                                     //--- Set dragging flag
int panel_drag_x = 0, panel_drag_y = 0;                          //--- Initialize drag start mouse coordinates
int panel_start_x = 0, panel_start_y = 0;                        //--- Initialize drag start panel coordinates
int prev_mouse_state = 0;                                        //--- Initialize previous mouse state
bool prev_header_hovered = false;                                //--- Set previous header hover state
bool prev_toggle_hovered = false;                                //--- Set previous toggle hover state
bool prev_close_hovered = false;                                 //--- Set previous close hover state
bool prev_heatmap_hovered = false;                               //--- Set previous heatmap hover state
bool prev_pval_hovered = false;                                  //--- Set previous pval hover state
bool prev_sort_hovered = false;                                  //--- Set previous sort hover state
bool prev_theme_hovered = false;                                 //--- Set previous theme hover state
bool prev_tf_hovered[NUM_TF];                                    //--- Declare previous TF hover states array
int last_mouse_x = 0, last_mouse_y = 0;                          //--- Initialize last mouse position
bool is_light_theme = false;                                     //--- Set theme flag (dark by default)
int sort_mode = 0;                                               //--- Initialize sort mode (0=original)

string original_symbols[MAX_SYMBOLS];                            //--- Declare array to store original order

ViewMode global_view_mode = VIEW_CORR;

まず、ライトテーマのカスタマイズ用の入力パラメータを追加します。背景色として白を設定するColorLightThemeBgと、テキスト色として黒を設定するColorLightThemeTextを用意します。次にViewMode列挙型を定義し、相関を表示するVIEW_CORRとp値を表示するVIEW_PVALを切り替え可能なモードとして追加します。続いて、ユーザーインターフェース要素に関する新しい定義を導入します。ソートボタンおよびテーマボタンの識別子としてSORT_BUTTONとTHEME_BUTTONを追加し、さらにライトモードにおけるニュートラルセル用の色としてCOLOR_LIGHT_GRAYを薄いグレー、ヘッダー用の色としてCOLOR_DARK_GRAYを濃いグレーとして定義します。またボタンのホバー判定領域としてBUTTON_HOVER_SIZEを二十四ピクセルに設定し、ヒートマップなどのモードに対応するため凡例数をNUM_LEGEND_ITEMSとして15に拡張します。

次にインタラクティブ状態を管理するためのグローバル変数を宣言します。ダッシュボードの表示状態としてpanel_is_visibleをtrueで初期化し、最小化されていない状態としてpanel_minimizedをfalseに設定します。ドラッグ操作管理用としてpanel_draggingをfalseとし、ドラッグ位置管理のためにpanel_drag_xおよびpanel_start_xをゼロで初期化します。マウス状態追跡のためprev_mouse_stateをゼロとし、ホバー状態の管理としてprev_header_hoveredをfalseに設定します。さらにトグルやクローズ、ヒートマップ、p値表示、ソート、テーマの切り替えなど各ボタンのホバー状態を管理するためのフラグを用意します。時間足セルのホバー状態としてprev_tf_hovered配列を使用し、カーソル位置追跡としてlast_mouse_xおよびlast_mouse_yをゼロで初期化します。テーマ状態はis_light_themeをfalseとしてダークモードから開始し、ソート状態はsort_modeをゼロとして初期状態を保持します。さらに元の銘柄順序を保持するためにoriginal_symbols文字列配列を定義し、初期銘柄列を保存します。また表示モードのデフォルトとしてglobal_view_modeをVIEW_CORRに設定します。

その後、銘柄データの処理において、元の銘柄順序を保存する処理を追加します。これにより、後でオリジナル状態へ戻す際に正しい並びを復元できます。以下に示すように、定義済みの配列に追加します。追加部分は分かりやすいように強調して記述します。

//+------------------------------------------------------------------+
//| Parse symbols list into array                                    |
//+------------------------------------------------------------------+
void parse_symbols() {
   string temp = SymbolsList;                                   //--- Copy input symbols list
   num_symbols = 0;                                             //--- Reset symbol count
   while (StringFind(temp, ",") >= 0 && num_symbols < MAX_SYMBOLS) { //--- Loop through comma-separated symbols
      int pos = StringFind(temp, ",");                          //--- Find comma position
      string sym = StringSubstr(temp, 0, pos);                  //--- Extract symbol
      if (SymbolSelect(sym, true)) {                            //--- Select symbol if available
         symbols_array[num_symbols] = sym;                      //--- Store valid symbol
         original_symbols[num_symbols] = sym;                   //--- Store in original order
         num_symbols++;                                         //--- Increment count
      } else {                                                  //--- Handle unavailable symbol
         Print("Warning: Symbol ", sym, " not available.");     //--- Print warning
      }
      temp = StringSubstr(temp, pos + 1);                       //--- Update remaining string
   }
   if (StringLen(temp) > 0 && num_symbols < MAX_SYMBOLS) {      //--- Handle last symbol
      if (SymbolSelect(temp, true)) {                           //--- Select last symbol if available
         symbols_array[num_symbols] = temp;                     //--- Store last valid symbol
         original_symbols[num_symbols] = temp;                  //--- Store in original order
         num_symbols++;                                         //--- Increment count
      } else {                                                  //--- Handle unavailable last symbol
         Print("Warning: Symbol ", temp, " not available.");    //--- Print warning
      }
   }
   if (num_symbols < 2) {                                       //--- Check minimum symbols
      Print("Error: At least 2 valid symbols required. Found: ", num_symbols); //--- Print error
      ExpertRemove();                                           //--- Remove expert
   }
}

分析値を整理するために、元の順序から降順、さらに昇順へと切り替える相関強度によるソート機能を導入します。このためには、それぞれの動作に対応する関数を実装する必要があります。以下にそのロジックを示します。

//+------------------------------------------------------------------+
//| Sort symbols by average correlation strength                     |
//+------------------------------------------------------------------+
void sort_symbols_by_strength() {
   if (sort_mode == 0) {                                        //--- Check original mode
      ArrayCopy(symbols_array, original_symbols);               //--- Restore original symbols
      update_correlations();                                    //--- Recompute correlations
      return;                                                   //--- Exit function
   }

   double avg_corr[MAX_SYMBOLS];                                //--- Declare average correlation array
   ArrayInitialize(avg_corr, 0.0);                              //--- Initialize averages
   for (int i = 0; i < num_symbols; i++) {                      //--- Loop symbols
      for (int j = 0; j < num_symbols; j++) {                   //--- Loop pairs
         if (i != j) avg_corr[i] += MathAbs(correlation_matrix[i][j]); //--- Accumulate absolute correlations
      }
      avg_corr[i] /= (num_symbols - 1);                         //--- Compute average
   }

   int indices[MAX_SYMBOLS];                                    //--- Declare indices array
   for (int i = 0; i < num_symbols; i++) indices[i] = i;        //--- Initialize indices
   for (int i = 0; i < num_symbols - 1; i++) {                  //--- Loop outer for sorting
      for (int j = i + 1; j < num_symbols; j++) {               //--- Loop inner for comparison
         bool swap = (sort_mode == 1) ? (avg_corr[indices[i]] < avg_corr[indices[j]]) : (avg_corr[indices[i]] > avg_corr[indices[j]]); //--- Determine swap
         if (swap) {                                            //--- Check if swap needed
            int temp = indices[i];                              //--- Store temporary
            indices[i] = indices[j];                            //--- Swap indices
            indices[j] = temp;                                  //--- Complete swap
         }
      }
   }

   string new_symbols[MAX_SYMBOLS];                             //--- Declare new symbols array
   double new_corr[MAX_SYMBOLS][MAX_SYMBOLS];                   //--- Declare new correlation matrix
   double new_pval[MAX_SYMBOLS][MAX_SYMBOLS];                   //--- Declare new p-value matrix
   for (int i = 0; i < num_symbols; i++) {                      //--- Loop to reorder
      int old_i = indices[i];                                   //--- Get old index
      new_symbols[i] = symbols_array[old_i];                    //--- Set new symbol
      for (int j = 0; j < num_symbols; j++) {                   //--- Loop columns
         int old_j = indices[j];                                //--- Get old column index
         new_corr[i][j] = correlation_matrix[old_i][old_j];     //--- Copy correlation
         new_pval[i][j] = pvalue_matrix[old_i][old_j];          //--- Copy p-value
      }
   }
   ArrayCopy(symbols_array, new_symbols);                       //--- Update symbols
   ArrayCopy(correlation_matrix, new_corr);                     //--- Update correlations
   ArrayCopy(pvalue_matrix, new_pval);                          //--- Update p-values
}

//+------------------------------------------------------------------+
//| Cycle sort mode                                                  |
//+------------------------------------------------------------------+
void cycle_sort_mode() {
   sort_mode = (sort_mode + 1) % 3;                             //--- Cycle mode
   string direction;                                            //--- Declare direction string
   if (sort_mode == 0) direction = "original";                  //--- Set original
   else if (sort_mode == 1) direction = "descending";           //--- Set descending
   else direction = "ascending";                                //--- Set ascending
   Print("Sort mode cycled to ", direction);                    //--- Print new mode
   sort_symbols_by_strength();                                  //--- Sort symbols
}

まずsort_symbols_by_strength関数を実装し、現在のソートモードに応じて、平均絶対相関の強さに基づき銘柄の並び替えをおこないます。ソートモードがゼロ、つまり元の順序の場合は、original_symbols配列をsymbols_arrayへArrayCopyで復元し、その後update_correlationsを呼び出して相関行列を再計算し、処理を終了します。それ以外の場合は、まずavg_corrというdouble型配列を銘柄数の最大サイズで宣言し、ArrayInitializeでゼロ初期化します。その後、各銘柄ペアをループ処理して、自己ペアを除いたcorrelation_matrixの絶対値を累積し、num_symbolsから1を引いた値で割ることで平均値を算出します。次にindices配列を作成し、初期状態では順序通りに初期化します。このインデックス配列に対してバブルソートを適用します。ネストされたループの中で、スワップの必要性を判定し、ソートモードが1の場合は昇順、つまり平均値が小さいものを前に、ソートモードが2の場合は降順、つまり平均値が大きいものを前に並べ替えます。条件を満たす場合はインデックスを交換します。

その後、新しい銘柄配列、相関行列、p値行列用の配列を宣言し、再配置処理を行います。各新しい位置について旧インデックスを取得し、その位置に対応する銘柄を設定し、相関行列およびp値行列についても旧位置から新位置へ行と列を対応させてコピーします。最後にArrayCopyを用いて、銘柄配列、correlation_matrix、pvalue_matrixをグローバル変数へ反映します。次にcycle_sort_mode関数を作成し、sort_modeを三で剰余を取る形でインクリメントし、オリジナル、降順、昇順の順に循環させます。新しいモードに応じて方向を示す文字列を設定し、その内容を出力した上でsort_symbols_by_strengthを呼び出し、ソート処理を適用します。さらにテーマ切り替え時の一貫性を確保するため、ライトテーマとダークテーマの視覚的整合性を改善します。そのために、ハイライト処理を更新し、非アクティブ時の背景色が適用中のテーマに依存するように変更します。具体的には、以下のように実装します。

//+------------------------------------------------------------------+
//| Update TF highlights                                             |
//+------------------------------------------------------------------+
void update_tf_highlights() {
   color inactive_bg = is_light_theme ? clrSilver : C'60,60,60'; //--- Set inactive background
   for (int i = 0; i < num_tf_visible; i++) {                   //--- Loop visible TFs
      string rect_name = TF_CELL_RECT + IntegerToString(i);     //--- Get rectangle name
      color bg = (i == current_tf_index) ? ColorStrongPositiveBg : inactive_bg; //--- Set background
      ObjectSetInteger(0, rect_name, OBJPROP_BGCOLOR, bg);      //--- Update background
   }
   ChartRedraw(0);                                              //--- Redraw chart
}

ここでは三項演算子を用い、ライトモードの場合は非アクティブ背景色をシルバーに設定し、それ以外の場合は従来の色を維持するようにしています。凡例コンポーネントも同様に処理し、背景色およびテキストラベルが選択されたテーマに応じて変化するようにすることで、視認性を統一します。

//+------------------------------------------------------------------+
//| Recreate legend objects based on current mode                    |
//+------------------------------------------------------------------+
void recreate_legend() {
   
   //--- Existing logic
   
   color neutral_bg = is_light_theme ? COLOR_LIGHT_GRAY : ColorNeutralBg; //--- Set neutral background
   color text_color = is_light_theme ? ColorLightThemeText : COLOR_WHITE; //--- Set text color

   for (int i = 0; i < num_legend_visible; i++) {                         //--- Loop to create
      int x_offset = x_start + i * (WIDTH_LEGEND_CELL + LEGEND_SPACING);  //--- Compute offset
      string rect_name = LEGEND_CELL_RECTANGLE + IntegerToString(i);      //--- Get rectangle name
      string text_name = LEGEND_CELL_TEXT + IntegerToString(i);           //--- Get text name
      create_rectangle(rect_name, x_offset, legend_y + 2, WIDTH_LEGEND_CELL, HEIGHT_LEGEND, neutral_bg); //--- Create rectangle
      create_label(text_name, "0%", x_offset + WIDTH_LEGEND_CELL / 2, legend_y + 2 + HEIGHT_LEGEND / 2 - 1, 10, text_color, "Arial"); //--- Create label
   }
}

//+------------------------------------------------------------------+
//| Update legend colors and texts based on mode                     |
//+------------------------------------------------------------------+
void update_legend() {
   color default_txt = is_light_theme ? ColorLightThemeText : ColorTextStrong; //--- Set default text color
   
   //--- Rest of the logic
   
}

//+------------------------------------------------------------------+
//| Update borders for main panel and legend                         |
//+------------------------------------------------------------------+
void update_borders() {
   color border_col = is_light_theme ? COLOR_BLACK : COLOR_WHITE; //--- Set border color
   
   //--- Rest of the logic
   
}

recreate_legend関数を修正し、テーマ変更に対応できるようにします。まずニュートラル背景色については条件分岐をおこない、is_light_themeがtrueの場合はCOLOR_LIGHT_GRAYを使用し、それ以外の場合はColorNeutralBgを使用します。同様にテキスト色も調整し、ライトモードではColorLightThemeText、ダークモードでは白を設定します。生成ループ内では、各凡例のcreate_rectangle呼び出し時に計算済みの背景色を適用し、create_labelでは更新されたテキスト色を使用することで、現在のテーマに一致した凡例表示を実現します。

次にupdate_legend関数を更新し、テーマに応じたデフォルトテキスト色を設定します。ライトモードではColorLightThemeText、ダークモードではColorTextStrongを使用し、この値をループ内での相関ケースごとのテキスト描画に適用します。続いてupdate_borders関数を実装し、主要パネルの枠線色を更新します。枠線色はis_light_themeに基づき、ライトモードでは黒、ダークモードでは白を設定し、ヘッダー、メイン、凡例パネルに対してObjectSetIntegerを用いてOBJPROP_COLORを更新します。ここからはダッシュボードのテーマ制御、最小化、ドラッグ操作、ホバー処理を扱う新しい関数群を追加します。トグルクリック処理から実装を開始します。

//+------------------------------------------------------------------+
//| Toggle theme (dark/light)                                        |
//+------------------------------------------------------------------+
void toggle_theme() {
   is_light_theme = !is_light_theme;                                      //--- Switch theme
   color main_bg = is_light_theme ? ColorLightThemeBg : C'30,30,30';      //--- Set main background
   color header_bg = is_light_theme ? clrSilver : C'60,60,60';            //--- Set header background
   color text_color = is_light_theme ? ColorLightThemeText : COLOR_WHITE; //--- Set text color
   color neutral_bg = is_light_theme ? COLOR_LIGHT_GRAY : ColorNeutralBg; //--- Set neutral background
   color diagonal_bg = is_light_theme ? clrGray : ColorDiagonalBg;        //--- Set diagonal background
   color theme_icon_color = is_light_theme ? clrBlack : clrWhite;         //--- Set theme icon color
   color button_text = is_light_theme ? clrNavy : clrGold;                //--- Set button text color
   color close_text = is_light_theme ? clrBlack : clrWhite;               //--- Set close text color
   color header_icon_color = is_light_theme ? clrDodgerBlue : clrAqua;    //--- Set header icon color

   ObjectSetInteger(0, MAIN_PANEL, OBJPROP_BGCOLOR, main_bg);             //--- Update main background
   ObjectSetInteger(0, HEADER_PANEL, OBJPROP_BGCOLOR, header_bg);         //--- Update header background
   ObjectSetInteger(0, LEGEND_PANEL, OBJPROP_BGCOLOR, main_bg);           //--- Update legend background
   ObjectSetInteger(0, HEADER_PANEL_TEXT, OBJPROP_COLOR, text_color);     //--- Update header text color
   ObjectSetInteger(0, HEADER_PANEL_ICON, OBJPROP_COLOR, header_icon_color); //--- Update header icon color
   ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_COLOR, close_text);          //--- Update close color
   ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_COLOR, button_text);        //--- Update toggle color
   ObjectSetInteger(0, HEATMAP_BUTTON, OBJPROP_COLOR, button_text);       //--- Update heatmap color
   ObjectSetInteger(0, PVAL_BUTTON, OBJPROP_COLOR, button_text);          //--- Update pval color
   ObjectSetInteger(0, SORT_BUTTON, OBJPROP_COLOR, button_text);          //--- Update sort color
   ObjectSetInteger(0, THEME_BUTTON, OBJPROP_COLOR, theme_icon_color);    //--- Update theme color

   for (int i = 0; i < num_symbols; i++) {                                //--- Loop symbols
      ObjectSetInteger(0, SYMBOL_ROW_RECTANGLE + IntegerToString(i), OBJPROP_BGCOLOR, header_bg); //--- Update row background
      ObjectSetInteger(0, SYMBOL_ROW_TEXT + IntegerToString(i), OBJPROP_COLOR, text_color);       //--- Update row text color
      ObjectSetInteger(0, SYMBOL_COL_RECTANGLE + IntegerToString(i), OBJPROP_BGCOLOR, header_bg); //--- Update column background
      ObjectSetInteger(0, SYMBOL_COL_TEXT + IntegerToString(i), OBJPROP_COLOR, text_color);       //--- Update column text color
      for (int j = 0; j < num_symbols; j++) {                             //--- Loop cells
         string cell_name = CELL_RECTANGLE + IntegerToString(i) + "_" + IntegerToString(j); //--- Get cell name
         color bg = (i == j) ? diagonal_bg : neutral_bg;                  //--- Set base background
         ObjectSetInteger(0, cell_name, OBJPROP_BGCOLOR, bg);             //--- Update cell background
      }
   }
   update_dashboard();                                                    //--- Reapply colors
   update_borders();                                                      //--- Update borders
   ChartRedraw(0);                                                        //--- Redraw chart
}

ライトテーマとダークテーマを切り替えるために、toggle_theme関数を実装します。この関数ではis_light_themeの真偽値を反転させ、その状態に応じてローカルカラーを設定します。メイン背景色はライトテーマの場合ColorLightThemeBg、それ以外ではダークグレーを使用します。ヘッダーはライトテーマでシルバー、ダークテーマでミディアムグレーに変更されます。テキスト色はライトテーマでColorLightThemeText、ダークテーマで白に設定します。ニュートラルカラーはライトテーマでライトグレー、ダークテーマでColorNeutralBgを使用します。対角セルの色はグレーまたはColorDiagonalBgに切り替えます。テーマアイコンはライトテーマで黒、ダークテーマで白に変更します。

ボタン関連の色も更新し、ボタンテキストはネイビーまたはゴールド、クローズボタンのテキストは黒または白、ヘッダーアイコンはライトテーマでDodger Blue、ダークテーマでアクアに設定します。これらの変更はObjectSetIntegerを用いて各オブジェクトプロパティに適用します。メインパネル、ヘッダー、凡例の背景にはそれぞれ更新されたメインカラーまたはヘッダー色を適用し、ヘッダーテキストおよびアイコンも対応する新しい色に更新します。さらに、クローズ、トグル、ヒートマップ、p値、ソート、テーマの各ボタンについても、それぞれの新しい配色を適用します。

その後、銘柄ループ内で各行および列の矩形を更新し、ヘッダー背景色を適用するとともにテキスト色も新しいテーマに合わせて変更します。さらにネストされたセル処理では、セル名を生成し、対角要素であれば対角色、それ以外はニュートラル色を判定して設定します。最後にupdate_dashboardを呼び出してセル色を再適用し、update_bordersでパネル枠線を更新し、ChartRedrawにより表示を更新します。最小化モードの場合は、以下のようにヘッダー要素のみを生成します。

//+------------------------------------------------------------------+
//| Create minimized dashboard UI                                    |
//+------------------------------------------------------------------+
void create_minimized_dashboard() {
   color header_bg = is_light_theme ? clrSilver : C'60,60,60';            //--- Set header background
   color text_color = is_light_theme ? ColorLightThemeText : COLOR_WHITE; //--- Set text color
   color button_text = is_light_theme ? clrNavy : clrGold;                //--- Set button text color
   color close_text = is_light_theme ? clrBlack : clrWhite;               //--- Set close text color
   color header_icon_color = is_light_theme ? clrDodgerBlue : clrAqua;    //--- Set header icon color
   int panel_width = WIDTH_SYMBOL + num_symbols * (WIDTH_CELL - 1) + 4;   //--- Compute panel width
   create_rectangle(HEADER_PANEL, panel_x, panel_y, panel_width, HEIGHT_HEADER, header_bg);                                  //--- Create header panel
   create_label(HEADER_PANEL_ICON, CharToString(181), panel_x + 12, panel_y + 14, 18, header_icon_color, "Wingdings");       //--- Create header icon
   create_label(HEADER_PANEL_TEXT, "Correlation Matrix", panel_x + 90, panel_y + 12, 13, text_color);                        //--- Create header text
   create_label(CLOSE_BUTTON, CharToString('r'), panel_x + (panel_width - 17), panel_y + 14, 18, close_text, "Webdings");    //--- Create close button
   create_label(TOGGLE_BUTTON, CharToString('o'), panel_x + (panel_width - 47), panel_y + 14, 18, button_text, "Wingdings"); //--- Create toggle button
   update_borders();                                                      //--- Update borders
   ChartRedraw(0);                                                        //--- Redraw chart
}

ここではcreate_minimized_dashboard関数を実装し、パネルが最小化された状態において、スペース効率の良いコンパクトなユーザーインターフェースを生成します。この状態ではヘッダーのみを表示します。まずテーマに応じてローカルカラーを設定します。ヘッダー背景色はライトモードではシルバー、ダークモードではミディアムグレーを使用します。テキスト色はライトモードではColorLightThemeText、ダークモードでは白に設定します。ボタンテキストはネイビーまたはゴールド、クローズテキストは黒または白、ヘッダーアイコンはライトモードでDodger Blue、ダークモードでアクアを使用します。

次に銘柄数に応じて調整された定数からパネル幅を算出します。その後create_rectangleを用いてHEADER_PANELを作成し、現在の位置とヘッダー高さに基づいて、テーマに応じた背景色を適用します。続いてヘッダー要素としてラベルを追加します。ヘッダーアイコンにはWingdingsの文字181を使用し、タイトルは「Correlation Matrix」と表示します。クローズボタンにはWebdingsの文字「r」を使用し、トグルボタンにはWingdingsの文字「o」を使用します。これらはすべてパネル位置を基準に相対配置され、テーマに応じた色設定が適用されます。

最後にupdate_bordersを呼び出して枠線を更新し、ChartRedrawにより表示を反映させます。次に、クローズ操作および時間足切り替えクリックを処理する関数を実装します。

//+------------------------------------------------------------------+
//| Delete all dashboard objects                                     |
//+------------------------------------------------------------------+
void delete_all_objects() {
   ObjectDelete(0, MAIN_PANEL);                                 //--- Delete main panel
   ObjectDelete(0, HEADER_PANEL);                               //--- Delete header panel
   ObjectDelete(0, HEADER_PANEL_ICON);                          //--- Delete header icon
   ObjectDelete(0, HEADER_PANEL_TEXT);                          //--- Delete header text
   ObjectDelete(0, CLOSE_BUTTON);                               //--- Delete close button
   ObjectDelete(0, TOGGLE_BUTTON);                              //--- Delete toggle button
   ObjectDelete(0, HEATMAP_BUTTON);                             //--- Delete heatmap button
   ObjectDelete(0, PVAL_BUTTON);                                //--- Delete pval button
   ObjectDelete(0, SORT_BUTTON);                                //--- Delete sort button
   ObjectDelete(0, THEME_BUTTON);                               //--- Delete theme button
   for (int i = 0; i < NUM_TF; i++) {                           //--- Loop TFs
      ObjectDelete(0, TF_CELL_RECT + IntegerToString(i));       //--- Delete TF rectangle
      ObjectDelete(0, TF_CELL_TEXT + IntegerToString(i));       //--- Delete TF text
   }
   ObjectDelete(0, "SYMBOL_ROW_HEADER");                        //--- Delete row header rectangle
   ObjectDelete(0, "SYMBOL_ROW_HEADER_TEXT");                   //--- Delete row header text
   for (int i = 0; i < num_symbols; i++) {                      //--- Loop symbols
      ObjectDelete(0, SYMBOL_ROW_RECTANGLE + IntegerToString(i)); //--- Delete row rectangle
      ObjectDelete(0, SYMBOL_ROW_TEXT + IntegerToString(i));    //--- Delete row text
      ObjectDelete(0, SYMBOL_COL_RECTANGLE + IntegerToString(i)); //--- Delete column rectangle
      ObjectDelete(0, SYMBOL_COL_TEXT + IntegerToString(i));    //--- Delete column text
      for (int j = 0; j < num_symbols; j++) {                   //--- Loop cells
         ObjectDelete(0, CELL_RECTANGLE + IntegerToString(i) + "_" + IntegerToString(j)); //--- Delete cell rectangle
         ObjectDelete(0, CELL_TEXT + IntegerToString(i) + "_" + IntegerToString(j)); //--- Delete cell text
      }
   }
   ObjectDelete(0, LEGEND_PANEL);                               //--- Delete legend panel
   for (int i = 0; i < NUM_LEGEND_ITEMS; i++) {                 //--- Loop legend items
      ObjectDelete(0, LEGEND_CELL_RECTANGLE + IntegerToString(i)); //--- Delete legend rectangle
      ObjectDelete(0, LEGEND_CELL_TEXT + IntegerToString(i));   //--- Delete legend text
   }
}

//+------------------------------------------------------------------+
//| Switch to specific timeframe                                     |
//+------------------------------------------------------------------+
void switch_timeframe(int index) {
   if (index < 0 || index >= num_tf_visible) return;            //--- Check valid index
   global_correlation_tf = tf_list[index];                      //--- Set new timeframe
   current_tf_index = index;                                    //--- Update index
   Print("Switched timeframe to ", tf_strings[index]);          //--- Print switch
   update_tf_highlights();                                      //--- Update highlights
   update_dashboard();                                          //--- Update dashboard
}

クローズ操作に対しては、ダッシュボードに関連するすべてのグラフィカルオブジェクトを削除するためのdelete_all_objects関数を実装します。この関数ではObjectDeleteを使用し、サブウィンドウ0に存在するメインパネル、ヘッダーパネル、ヘッダーアイコンおよびテキスト、クローズボタン、トグルボタン、ヒートマップボタン、p値ボタン、ソートボタン、テーマボタンを削除します。さらに時間足の数だけループして、各時間足セルに対応する矩形およびテキストを、接頭辞とインデックス文字列を組み合わせて削除します。続いて銘柄行ヘッダーの矩形およびテキストを削除し、銘柄数のループ内で行および列の矩形とテキストを削除します。その内部ではセルごとにループを行い、相関セルの矩形およびテキストを名前結合により特定して削除します。最後に凡例パネルを削除し、凡例アイテムについてもループ処理で矩形とテキストを順に削除します。

次にswitch_timeframe関数を作成し、指定されたインデックスに基づいて分析時間足を切り替えます。まずインデックスが有効範囲、すなわち0からnum_tf_visibleから1を引いた範囲内にあるかを確認し、範囲外の場合は処理を終了します。次にglobal_correlation_tfをtf_listの該当インデックスの値に更新し、current_tf_indexも同時に更新します。その後、対応するtf_stringsの値を用いて時間足切り替えメッセージを出力し、update_tf_highlightsを呼び出して表示を更新し、さらにupdate_dashboardを実行して新しいデータの再計算と表示をおこないます。次のステップとして、ダッシュボード全体をテーマ対応に更新し、さらにテーマ切り替えおよびソート機能に対応する新しいボタンを追加します。

//+------------------------------------------------------------------+
//| Create full dashboard UI                                         |
//+------------------------------------------------------------------+
void create_full_dashboard() { 
   color main_bg = is_light_theme ? ColorLightThemeBg : C'30,30,30';      //--- Set main background
   color header_bg = is_light_theme ? clrSilver : C'60,60,60';            //--- Set header background
   color text_color = is_light_theme ? ColorLightThemeText : COLOR_WHITE; //--- Set text color
   color neutral_bg = is_light_theme ? COLOR_LIGHT_GRAY : ColorNeutralBg; //--- Set neutral background
   color button_text = is_light_theme ? clrNavy : clrGold;                //--- Set button text color
   color theme_icon_color = is_light_theme ? clrBlack : clrWhite;         //--- Set theme icon color
   color close_text = is_light_theme ? clrBlack : clrWhite;               //--- Set close text color
   color header_icon_color = is_light_theme ? clrDodgerBlue : clrAqua;    //--- Set header icon color
   int panel_width = WIDTH_SYMBOL + num_symbols * (WIDTH_CELL - 1) + 4;   //--- Compute panel width
   int panel_height = HEIGHT_HEADER + HEIGHT_TF_CELL + GAP_HEIGHT + HEIGHT_RECTANGLE * (num_symbols + 1) - num_symbols + 2; //--- Compute panel height
   create_rectangle(MAIN_PANEL, panel_x, panel_y, panel_width, panel_height, main_bg);                //--- Create main panel
   create_rectangle(HEADER_PANEL, panel_x, panel_y, panel_width, HEIGHT_HEADER, header_bg);           //--- Create header panel
   create_label(HEADER_PANEL_ICON, CharToString(181), panel_x + 12, panel_y + 14, 18, header_icon_color, "Wingdings"); //--- Create header icon
   create_label(HEADER_PANEL_TEXT, "Correlation Matrix", panel_x + 90, panel_y + 12, 13, text_color); //--- Create header text
   create_label(CLOSE_BUTTON, CharToString('r'), panel_x + (panel_width - 17), panel_y + 14, 18, close_text, "Webdings"); //--- Create close button
   ObjectSetString(0, CLOSE_BUTTON, OBJPROP_TOOLTIP, "Close Panel");       //--- Set close tooltip
   create_label(TOGGLE_BUTTON, CharToString('r'), panel_x + (panel_width - 47), panel_y + 14, 18, button_text, "Wingdings"); //--- Create toggle button
   ObjectSetString(0, TOGGLE_BUTTON, OBJPROP_TOOLTIP, "Toggle Minimize/Maximize");                    //--- Set toggle tooltip
   string heatmap_icon = CharToString(global_display_mode == MODE_STANDARD ? (uchar)82 : (uchar)110); //--- Set heatmap icon
   create_label(HEATMAP_BUTTON, heatmap_icon, panel_x + (panel_width - 77), panel_y + 14, 18, button_text, "Wingdings"); //--- Create heatmap button
   ObjectSetString(0, HEATMAP_BUTTON, OBJPROP_TOOLTIP, "Toggle Heatmap/Standard Mode");               //--- Set heatmap tooltip
   create_label(PVAL_BUTTON, CharToString('X'), panel_x + (panel_width - 107), panel_y + 14, 18, button_text, "Wingdings"); //--- Create pval button
   ObjectSetString(0, PVAL_BUTTON, OBJPROP_TOOLTIP, "Toggle Correlation/P-Value View");               //--- Set pval tooltip
   string sort_icon;                                                      //--- Declare sort icon
   if (sort_mode == 0) sort_icon = CharToString('N');                     //--- Set neutral for original
   else if (sort_mode == 1) sort_icon = CharToString('K');                //--- Set descending
   else sort_icon = CharToString('J');                                    //--- Set ascending
   create_label(SORT_BUTTON, sort_icon, panel_x + (panel_width - 137), panel_y + 14, 18, button_text, "Wingdings 3");             //--- Create sort button
   ObjectSetString(0, SORT_BUTTON, OBJPROP_TOOLTIP, "Sort Symbols by Strength (Cycle Original/Desc/Asc)");                        //--- Set sort tooltip
   create_label(THEME_BUTTON, CharToString('['), panel_x + (panel_width - 167), panel_y + 14, 18, theme_icon_color, "Wingdings"); //--- Create theme button
   ObjectSetString(0, THEME_BUTTON, OBJPROP_TOOLTIP, "Toggle Dark/Light Theme");                                                  //--- Set theme tooltip

   int tf_y = panel_y + HEIGHT_HEADER;                                    //--- Compute TF y position
   int tf_x_start = panel_x + 2;                                          //--- Set TF start x
   for (int i = 0; i < num_tf_visible; i++) {                             //--- Loop visible TFs
      int x_offset = tf_x_start + i * WIDTH_TF_CELL;                      //--- Compute offset
      string rect_name = TF_CELL_RECT + IntegerToString(i);               //--- Get rectangle name
      string text_name = TF_CELL_TEXT + IntegerToString(i);               //--- Get text name
      color bg = (i == current_tf_index) ? ColorStrongPositiveBg : header_bg; //--- Set background
      create_rectangle(rect_name, x_offset, tf_y, WIDTH_TF_CELL, HEIGHT_TF_CELL, bg); //--- Create TF rectangle
      create_label(text_name, tf_strings[i], x_offset + (WIDTH_TF_CELL / 2), tf_y + (HEIGHT_TF_CELL / 2), 10, text_color, "Arial Bold"); //--- Create TF text
   }

   int matrix_y = tf_y + HEIGHT_TF_CELL + GAP_HEIGHT;                     //--- Compute matrix y
   create_rectangle("SYMBOL_ROW_HEADER", panel_x + 2, matrix_y, WIDTH_SYMBOL, HEIGHT_RECTANGLE, header_bg); //--- Create row header rectangle
   create_label("SYMBOL_ROW_HEADER_TEXT", "Symbols", panel_x + (WIDTH_SYMBOL / 2 + 2), matrix_y + (HEIGHT_RECTANGLE / 2), 10, text_color, "Arial Bold"); //--- Create row header text
   for (int i = 0; i < num_symbols; i++) {                                //--- Loop row symbols
      int y_offset = matrix_y + HEIGHT_RECTANGLE * (i + 1) - (1 + i);     //--- Compute y offset
      create_rectangle(SYMBOL_ROW_RECTANGLE + IntegerToString(i), panel_x + 2, y_offset, WIDTH_SYMBOL, HEIGHT_RECTANGLE, header_bg); //--- Create row rectangle
      create_label(SYMBOL_ROW_TEXT + IntegerToString(i), symbols_array[i], panel_x + (WIDTH_SYMBOL / 2 + 2), y_offset + (HEIGHT_RECTANGLE / 2 - 1), 10, text_color, "Arial Bold"); //--- Create row text
   }

   for (int j = 0; j < num_symbols; j++) {                               //--- Loop column symbols
      int x_offset = panel_x + WIDTH_SYMBOL + j * WIDTH_CELL - j + 1;    //--- Compute x offset
      create_rectangle(SYMBOL_COL_RECTANGLE + IntegerToString(j), x_offset, matrix_y, WIDTH_CELL, HEIGHT_RECTANGLE, header_bg); //--- Create column rectangle
      create_label(SYMBOL_COL_TEXT + IntegerToString(j), symbols_array[j], x_offset + (WIDTH_CELL / 2), matrix_y + (HEIGHT_RECTANGLE / 2), 10, text_color, "Arial Bold"); //--- Create column text
   }

   for (int i = 0; i < num_symbols; i++) {                               //--- Loop rows for cells
      int y_offset = matrix_y + HEIGHT_RECTANGLE * (i + 1) - (1 + i);    //--- Compute y offset
      for (int j = 0; j < num_symbols; j++) {                            //--- Loop columns for cells
         string cell_name = CELL_RECTANGLE + IntegerToString(i) + "_" + IntegerToString(j); //--- Get cell name
         string text_name = CELL_TEXT + IntegerToString(i) + "_" + IntegerToString(j); //--- Get text name
         int x_offset = panel_x + WIDTH_SYMBOL + j * WIDTH_CELL - j + 1; //--- Compute x offset
         create_rectangle(cell_name, x_offset, y_offset, WIDTH_CELL, HEIGHT_RECTANGLE, neutral_bg); //--- Create cell rectangle
         create_label(text_name, "0.00", x_offset + (WIDTH_CELL / 2), y_offset + (HEIGHT_RECTANGLE / 2 - 1), 10, text_color, "Arial"); //--- Create cell text
      }
   }

   int legend_y = panel_y + panel_height + GAP_MAIN_LEGEND;              //--- Compute legend y
   create_rectangle(LEGEND_PANEL, panel_x, legend_y, panel_width, HEIGHT_LEGEND_PANEL, main_bg); //--- Create legend panel
   recreate_legend();                                                    //--- Recreate legend
   ChartRedraw(0);                                                       //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Update dashboard cells with correlation values and colors        |
//+------------------------------------------------------------------+
void update_dashboard() {
   update_correlations();                                                //--- Update correlations
   double strong_pos = StrongPositiveThresholdPct / 100.0;               //--- Set positive threshold
   double strong_neg = StrongNegativeThresholdPct / 100.0;               //--- Set negative threshold
   color text_base = is_light_theme ? ColorLightThemeText : ColorTextStrong; //--- Set base text color
   for (int i = 0; i < num_symbols; i++) {                               //--- Loop rows
      for (int j = 0; j < num_symbols; j++) {                            //--- Loop columns
         double corr = correlation_matrix[i][j];                         //--- Get correlation
         double pval = pvalue_matrix[i][j];                              //--- Get p-value
         string text = "";                                               //--- Initialize text
         if (global_view_mode == VIEW_CORR) {                            //--- Handle correlation view
            double corr_pct = corr * 100.0;                              //--- Compute percentage
            text = DoubleToString(corr_pct, 1) + "%" + get_significance_stars(pval); //--- Format correlation text
         } else {                                                        //--- Handle p-value view
            text = DoubleToString(pval, 4) + get_significance_stars(pval); //--- Format p-value text
         }
         color bg_color = is_light_theme ? clrLightGray : ColorNeutralBg; //--- Initialize background
         color txt_color = is_light_theme ? clrBlack : ColorTextZero;    //--- Initialize text color

         if (i == j) {                                                   //--- Handle diagonal
            bg_color = is_light_theme ? clrGray : ColorDiagonalBg;       //--- Set diagonal background
            txt_color = text_base;                                       //--- Set text color
         } else {                                                        //--- Handle off-diagonal
            if (global_display_mode == MODE_STANDARD) {                  //--- Handle standard mode
               if (corr >= strong_pos) {                                 //--- Check strong positive
                  bg_color = ColorStrongPositiveBg;                      //--- Set positive background
                  txt_color = text_base;                                 //--- Set text color
               } else if (corr <= strong_neg) {                          //--- Check strong negative
                  bg_color = ColorStrongNegativeBg;                      //--- Set negative background
                  txt_color = text_base;                                 //--- Set text color
               } else {                                                  //--- Handle mild
                  bg_color = is_light_theme ? clrLightGray : ColorNeutralBg; //--- Set neutral background
                  if (corr > 0.0) {                                      //--- Check positive mild
                     txt_color = is_light_theme ? clrBlue : ColorTextPositive; //--- Set positive text
                  } else if (corr < 0.0) {                               //--- Check negative mild
                     txt_color = is_light_theme ? clrRed : ColorTextNegative; //--- Set negative text
                  } else {                                               //--- Handle zero
                     txt_color = text_base;                              //--- Set base text
                  }
               }
            } else {                                                     //--- Handle heatmap mode
               txt_color = text_base;                                    //--- Set text color
               bg_color = interpolate_heatmap_color(corr);               //--- Set interpolated background
            }
         }
         string cell_name = CELL_RECTANGLE + IntegerToString(i) + "_" + IntegerToString(j); //--- Get cell name
         string text_name = CELL_TEXT + IntegerToString(i) + "_" + IntegerToString(j);      //--- Get text name
         ObjectSetInteger(0, cell_name, OBJPROP_BGCOLOR, bg_color);      //--- Update background
         ObjectSetString(0, text_name, OBJPROP_TEXT, text);              //--- Update text
         ObjectSetInteger(0, text_name, OBJPROP_COLOR, txt_color);       //--- Update text color

         string sym1 = symbols_array[i];                                 //--- Get first symbol
         string sym2 = symbols_array[j];                                 //--- Get second symbol
         string tf_str = EnumToString(global_correlation_tf);            //--- Get timeframe string
         string method_str = EnumToString(CorrMethod);                   //--- Get method string
         string tooltip = StringFormat("Symbols: %s vs %s\nTimeframe: %s\nMethod: %s\nCorrelation: %.4f\nP-value: %.4f\nBars: %d", sym1, sym2, tf_str, method_str, corr, pval, CorrelationBars); //--- Format tooltip
         ObjectSetString(0, text_name, OBJPROP_TOOLTIP, tooltip);        //--- Set text tooltip
         ObjectSetString(0, cell_name, OBJPROP_TOOLTIP, tooltip);        //--- Set cell tooltip
      }
   }
   update_legend();                                                      //--- Update legend
   ChartRedraw(0);                                                       //--- Redraw chart
}

まずcreate_full_dashboard関数を修正し、テーマに依存した色設定と追加のインタラクティブ要素を組み込みます。ローカルカラーはis_light_themeに応じて条件分岐し、メイン背景はライトモードではColorLightThemeBg、ダークモードではダークグレーを使用します。ヘッダーはシルバーまたはミディアムグレー、テキストはColorLightThemeTextまたは白、ニュートラルはCOLOR_LIGHT_GRAYまたはColorNeutralBg、ボタンテキストはネイビーまたはゴールド、テーマアイコンは黒または白、クローズテキストは黒または白、ヘッダーアイコンはDodger Blueまたはアクアに設定します。

パネルサイズは従来通り計算し、メインパネルおよびヘッダーパネルをテーマ背景で生成します。ヘッダー要素として、Wingdingsの文字181を用いたヘッダーアイコン、「Correlation Matrix」というタイトルを追加します。クローズボタンにはWebdingsの「r」を使用し、ObjectSetStringOBJPROP_TOOLTIPによりツールチップを「Close Panel」に設定します。トグルボタンにはWingdingsの「r」を使用し、最小化および最大化切り替えのツールチップを設定します。ヒートマップボタンは表示モードに応じてアイコンを切り替え、標準表示とヒートマップ切り替えのツールチップを付与します。p値ボタンにはWingdingsの「X」を使用し、相関表示とp値表示の切り替えツールチップを設定します。ソートボタンにはソートモードに応じたアイコンを設定し、オリジナルは「N」、降順は「K」、昇順は「J」とし、Wingdings 3を使用します。ツールチップには強度ソートの循環切り替えを示します。テーマボタンにはWingdingsの「[」を使用し、ダークモードとライトモード切り替えのツールチップを設定します。これらすべてはテーマに応じた色で描画されます。

時間足行では位置を計算し、ループ内で現在インデックスに応じた背景色を持つ矩形を作成し、Arial Boldで時間足文字列を表示します。シンボル行ヘッダーも作成し、タイトルSymbolsをテーマ色で表示します。行シンボルはヘッダー背景で矩形を作成し、シンボル名をラベル表示します。同様に列シンボルも矩形とラベルを生成します。同様に列シンボルも矩形とラベルを生成します。セル部分では二重ループを用い、ニュートラル背景の矩形と初期値0.00のラベルをArialで作成します。その後凡例パネルをメイン背景で生成し、recreate_legendを呼び出し、最後に描画を更新します。さらにupdate_dashboard関数を更新し、ビュー切り替えとテーマの両方に対応できるようにします。ロジックは同様に維持されます。これらの関数を用いてテストをおこなうと、ライトテーマおよびダークテーマの両方で以下のような表示結果が得られます。

ライトテーマとダークテーマ

ここまででダッシュボード生成関数にテーマモードが組み込まれたため、次はボタンのホバー状態およびクリック処理を実装する必要があります。そのためのヘルパー関数を以下のように定義します。

//+------------------------------------------------------------------+
//| Check if cursor is inside header or buttons                      |
//+------------------------------------------------------------------+
bool is_cursor_in_header_or_buttons(int mouse_x, int mouse_y) {
   int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);         //--- Get chart width
   int header_x = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE); //--- Get header x
   int header_y = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE); //--- Get header y
   int header_width = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XSIZE); //--- Get header width
   int header_height = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YSIZE);//--- Get header height
   int header_left = header_x;                                               //--- Set left edge
   int header_right = header_left + header_width;                            //--- Set right edge
   bool in_header = (mouse_x >= header_left && mouse_x <= header_right && mouse_y >= header_y && mouse_y <= header_y + header_height); //--- Check in header

   int half_size = BUTTON_HOVER_SIZE / 2;                                    //--- Compute half hover size
   int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE);  //--- Get close x
   int close_y = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE);  //--- Get close y
   bool in_close = (mouse_x >= close_x - half_size && mouse_x <= close_x + half_size && mouse_y >= close_y - half_size && mouse_y <= close_y + half_size); //--- Check in close

   int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE); //--- Get toggle x
   int toggle_y = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE); //--- Get toggle y
   bool in_toggle = (mouse_x >= toggle_x - half_size && mouse_x <= toggle_x + half_size && mouse_y >= toggle_y - half_size && mouse_y <= toggle_y + half_size); //--- Check in toggle

   int heatmap_x = (int)ObjectGetInteger(0, HEATMAP_BUTTON, OBJPROP_XDISTANCE); //--- Get heatmap x
   int heatmap_y = (int)ObjectGetInteger(0, HEATMAP_BUTTON, OBJPROP_YDISTANCE); //--- Get heatmap y
   bool in_heatmap = (mouse_x >= heatmap_x - half_size && mouse_x <= heatmap_x + half_size && mouse_y >= heatmap_y - half_size && mouse_y <= heatmap_y + half_size); //--- Check in heatmap

   int pval_x = (int)ObjectGetInteger(0, PVAL_BUTTON, OBJPROP_XDISTANCE);     //--- Get pval x
   int pval_y = (int)ObjectGetInteger(0, PVAL_BUTTON, OBJPROP_YDISTANCE);     //--- Get pval y
   bool in_pval = (mouse_x >= pval_x - half_size && mouse_x <= pval_x + half_size && mouse_y >= pval_y - half_size && mouse_y <= pval_y + half_size); //--- Check in pval

   int sort_x = (int)ObjectGetInteger(0, SORT_BUTTON, OBJPROP_XDISTANCE);     //--- Get sort x
   int sort_y = (int)ObjectGetInteger(0, SORT_BUTTON, OBJPROP_YDISTANCE);     //--- Get sort y
   bool in_sort = (mouse_x >= sort_x - half_size && mouse_x <= sort_x + half_size && mouse_y >= sort_y - half_size && mouse_y <= sort_y + half_size); //--- Check in sort

   int theme_x = (int)ObjectGetInteger(0, THEME_BUTTON, OBJPROP_XDISTANCE);   //--- Get theme x
   int theme_y = (int)ObjectGetInteger(0, THEME_BUTTON, OBJPROP_YDISTANCE);   //--- Get theme y
   bool in_theme = (mouse_x >= theme_x - half_size && mouse_x <= theme_x + half_size && mouse_y >= theme_y - half_size && mouse_y <= theme_y + half_size); //--- Check in theme

   bool in_tf = false;                                                        //--- Initialize TF check
   for (int i = 0; i < num_tf_visible; i++) {                                 //--- Loop TFs
      string rect_name = TF_CELL_RECT + IntegerToString(i);                   //--- Get TF name
      int tf_x = (int)ObjectGetInteger(0, rect_name, OBJPROP_XDISTANCE);      //--- Get TF x
      int tf_y = (int)ObjectGetInteger(0, rect_name, OBJPROP_YDISTANCE);      //--- Get TF y
      if (mouse_x >= tf_x && mouse_x <= tf_x + WIDTH_TF_CELL && mouse_y >= tf_y && mouse_y <= tf_y + HEIGHT_TF_CELL) { //--- Check in TF
         in_tf = true;                                                        //--- Set in TF
         break;                                                               //--- Exit loop
      }
   }

   return in_header || in_close || in_toggle || in_heatmap || in_pval || in_sort || in_theme || in_tf; //--- Return if in any area
}

//+------------------------------------------------------------------+
//| Update button hover states                                       |
//+------------------------------------------------------------------+
void update_button_hover_states(int mouse_x, int mouse_y) {
   int half_size = BUTTON_HOVER_SIZE / 2;                       //--- Compute half hover size
   color hover_bg = clrDodgerBlue;                              //--- Set hover background
   color button_normal = is_light_theme ? clrNavy : clrGold;    //--- Set normal button color
   color theme_normal = is_light_theme ? clrBlack : clrWhite;   //--- Set normal theme color
   color close_normal = is_light_theme ? clrBlack : clrWhite;   //--- Set normal close color
   color hover_text = is_light_theme ? clrWhite : clrYellow;    //--- Set hover text color
   color theme_hover = is_light_theme ? clrWhite : clrYellow;   //--- Set hover theme color
   color close_hover = clrRed;                                  //--- Set hover close color

   int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE); //--- Get close x
   int close_y = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE); //--- Get close y
   bool is_close_hovered = (mouse_x >= close_x - half_size && mouse_x <= close_x + half_size && mouse_y >= close_y - half_size && mouse_y <= close_y + half_size); //--- Check close hover
   if (is_close_hovered != prev_close_hovered) {                //--- Check state change
      ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_COLOR, is_close_hovered ? close_hover : close_normal); //--- Update close color
      ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_BGCOLOR, is_close_hovered ? hover_bg : clrNONE); //--- Update close background
      prev_close_hovered = is_close_hovered;                    //--- Update previous state
      ChartRedraw(0);                                           //--- Redraw chart
   }

   int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE); //--- Get toggle x
   int toggle_y = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE); //--- Get toggle y
   bool is_toggle_hovered = (mouse_x >= toggle_x - half_size && mouse_x <= toggle_x + half_size && mouse_y >= toggle_y - half_size && mouse_y <= toggle_y + half_size); //--- Check toggle hover
   if (is_toggle_hovered != prev_toggle_hovered) {              //--- Check state change
      ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_COLOR, is_toggle_hovered ? hover_text : button_normal); //--- Update toggle color
      ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_BGCOLOR, is_toggle_hovered ? hover_bg : clrNONE); //--- Update toggle background
      prev_toggle_hovered = is_toggle_hovered;                  //--- Update previous state
      ChartRedraw(0);                                           //--- Redraw chart
   }

   int heatmap_x = (int)ObjectGetInteger(0, HEATMAP_BUTTON, OBJPROP_XDISTANCE); //--- Get heatmap x
   int heatmap_y = (int)ObjectGetInteger(0, HEATMAP_BUTTON, OBJPROP_YDISTANCE); //--- Get heatmap y
   bool is_heatmap_hovered = (mouse_x >= heatmap_x - half_size && mouse_x <= heatmap_x + half_size && mouse_y >= heatmap_y - half_size && mouse_y <= heatmap_y + half_size); //--- Check heatmap hover
   if (is_heatmap_hovered != prev_heatmap_hovered) {            //--- Check state change
      ObjectSetInteger(0, HEATMAP_BUTTON, OBJPROP_COLOR, is_heatmap_hovered ? hover_text : button_normal); //--- Update heatmap color
      ObjectSetInteger(0, HEATMAP_BUTTON, OBJPROP_BGCOLOR, is_heatmap_hovered ? hover_bg : clrNONE); //--- Update heatmap background
      prev_heatmap_hovered = is_heatmap_hovered;                //--- Update previous state
      ChartRedraw(0);                                           //--- Redraw chart
   }

   int pval_x = (int)ObjectGetInteger(0, PVAL_BUTTON, OBJPROP_XDISTANCE); //--- Get pval x
   int pval_y = (int)ObjectGetInteger(0, PVAL_BUTTON, OBJPROP_YDISTANCE); //--- Get pval y
   bool is_pval_hovered = (mouse_x >= pval_x - half_size && mouse_x <= pval_x + half_size && mouse_y >= pval_y - half_size && mouse_y <= pval_y + half_size); //--- Check pval hover
   if (is_pval_hovered != prev_pval_hovered) {                  //--- Check state change
      ObjectSetInteger(0, PVAL_BUTTON, OBJPROP_COLOR, is_pval_hovered ? hover_text : button_normal); //--- Update pval color
      ObjectSetInteger(0, PVAL_BUTTON, OBJPROP_BGCOLOR, is_pval_hovered ? hover_bg : clrNONE); //--- Update pval background
      prev_pval_hovered = is_pval_hovered;                     //--- Update previous state
      ChartRedraw(0);                                          //--- Redraw chart
   }

   int sort_x = (int)ObjectGetInteger(0, SORT_BUTTON, OBJPROP_XDISTANCE); //--- Get sort x
   int sort_y = (int)ObjectGetInteger(0, SORT_BUTTON, OBJPROP_YDISTANCE); //--- Get sort y
   bool is_sort_hovered = (mouse_x >= sort_x - half_size && mouse_x <= sort_x + half_size && mouse_y >= sort_y - half_size && mouse_y <= sort_y + half_size); //--- Check sort hover
   if (is_sort_hovered != prev_sort_hovered) {                            //--- Check state change
      ObjectSetInteger(0, SORT_BUTTON, OBJPROP_COLOR, is_sort_hovered ? hover_text : button_normal); //--- Update sort color
      ObjectSetInteger(0, SORT_BUTTON, OBJPROP_BGCOLOR, is_sort_hovered ? hover_bg : clrNONE); //--- Update sort background
      prev_sort_hovered = is_sort_hovered;                                //--- Update previous state
      ChartRedraw(0);                                                     //--- Redraw chart
   }

   int theme_x = (int)ObjectGetInteger(0, THEME_BUTTON, OBJPROP_XDISTANCE); //--- Get theme x
   int theme_y = (int)ObjectGetInteger(0, THEME_BUTTON, OBJPROP_YDISTANCE); //--- Get theme y
   bool is_theme_hovered = (mouse_x >= theme_x - half_size && mouse_x <= theme_x + half_size && mouse_y >= theme_y - half_size && mouse_y <= theme_y + half_size); //--- Check theme hover
   if (is_theme_hovered != prev_theme_hovered) {                          //--- Check state change
      ObjectSetInteger(0, THEME_BUTTON, OBJPROP_COLOR, is_theme_hovered ? theme_hover : theme_normal); //--- Update theme color
      ObjectSetInteger(0, THEME_BUTTON, OBJPROP_BGCOLOR, is_theme_hovered ? hover_bg : clrNONE); //--- Update theme background
      prev_theme_hovered = is_theme_hovered;                              //--- Update previous state
      ChartRedraw(0);                                                     //--- Redraw chart
   }

   for (int i = 0; i < num_tf_visible; i++) {                             //--- Loop TFs
      string rect_name = TF_CELL_RECT + IntegerToString(i);               //--- Get TF name
      int tf_x = (int)ObjectGetInteger(0, rect_name, OBJPROP_XDISTANCE);  //--- Get TF x
      int tf_y = (int)ObjectGetInteger(0, rect_name, OBJPROP_YDISTANCE);  //--- Get TF y
      bool is_hovered = (mouse_x >= tf_x && mouse_x <= tf_x + WIDTH_TF_CELL && mouse_y >= tf_y && mouse_y <= tf_y + HEIGHT_TF_CELL); //--- Check hover
      if (is_hovered != prev_tf_hovered[i]) {                             //--- Check state change
         color bg = is_hovered ? clrDodgerBlue : (i == current_tf_index ? ColorStrongPositiveBg : (is_light_theme ? clrSilver : C'60,60,60')); //--- Set background
         ObjectSetInteger(0, rect_name, OBJPROP_BGCOLOR, bg);             //--- Update background
         prev_tf_hovered[i] = is_hovered;                                 //--- Update previous state
         ChartRedraw(0);                                                  //--- Redraw chart
      }
   }
}

//+------------------------------------------------------------------+
//| Update header hover state                                        |
//+------------------------------------------------------------------+
void update_header_hover_state(int mouse_x, int mouse_y) {
   int header_x = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE);  //--- Get header x
   int header_y = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE);  //--- Get header y
   int header_width = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XSIZE);  //--- Get header width
   int header_height = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YSIZE); //--- Get header height
   int header_left = header_x;                                                //--- Set left edge
   int header_right = header_left + header_width;                             //--- Set right edge

   int half_size = BUTTON_HOVER_SIZE / 2;                                   //--- Compute half hover size
   int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE); //--- Get close x
   int close_y = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE); //--- Get close y
   bool in_close_area = (mouse_x >= close_x - half_size && mouse_x <= close_x + half_size && mouse_y >= close_y - half_size && mouse_y <= close_y + half_size); //--- Check close area

   int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE); //--- Get toggle x
   int toggle_y = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE); //--- Get toggle y
   bool in_toggle_area = (mouse_x >= toggle_x - half_size && mouse_x <= toggle_x + half_size && mouse_y >= toggle_y - half_size && mouse_y <= toggle_y + half_size); //--- Check toggle area

   int heatmap_x = (int)ObjectGetInteger(0, HEATMAP_BUTTON, OBJPROP_XDISTANCE); //--- Get heatmap x
   int heatmap_y = (int)ObjectGetInteger(0, HEATMAP_BUTTON, OBJPROP_YDISTANCE); //--- Get heatmap y
   bool in_heatmap_area = (mouse_x >= heatmap_x - half_size && mouse_x <= heatmap_x + half_size && mouse_y >= heatmap_y - half_size && mouse_y <= heatmap_y + half_size); //--- Check heatmap area

   int pval_x = (int)ObjectGetInteger(0, PVAL_BUTTON, OBJPROP_XDISTANCE);     //--- Get pval x
   int pval_y = (int)ObjectGetInteger(0, PVAL_BUTTON, OBJPROP_YDISTANCE);     //--- Get pval y
   bool in_pval_area = (mouse_x >= pval_x - half_size && mouse_x <= pval_x + half_size && mouse_y >= pval_y - half_size && mouse_y <= pval_y + half_size); //--- Check pval area

   int sort_x = (int)ObjectGetInteger(0, SORT_BUTTON, OBJPROP_XDISTANCE);     //--- Get sort x
   int sort_y = (int)ObjectGetInteger(0, SORT_BUTTON, OBJPROP_YDISTANCE);     //--- Get sort y
   bool in_sort_area = (mouse_x >= sort_x - half_size && mouse_x <= sort_x + half_size && mouse_y >= sort_y - half_size && mouse_y <= sort_y + half_size); //--- Check sort area

   int theme_x = (int)ObjectGetInteger(0, THEME_BUTTON, OBJPROP_XDISTANCE);   //--- Get theme x
   int theme_y = (int)ObjectGetInteger(0, THEME_BUTTON, OBJPROP_YDISTANCE);   //--- Get theme y
   bool in_theme_area = (mouse_x >= theme_x - half_size && mouse_x <= theme_x + half_size && mouse_y >= theme_y - half_size && mouse_y <= theme_y + half_size); //--- Check theme area

   bool is_header_hovered = (mouse_x >= header_left && mouse_x <= header_right && mouse_y >= header_y && mouse_y <= header_y + header_height &&
                             !in_close_area && !in_toggle_area && !in_heatmap_area && !in_pval_area && !in_sort_area && !in_theme_area); //--- Check header hover

   color header_bg = is_light_theme ? clrSilver : C'60,60,60';                //--- Set header background
   if (is_header_hovered != prev_header_hovered && !panel_dragging) {         //--- Check state change
      ObjectSetInteger(0, HEADER_PANEL, OBJPROP_BGCOLOR, is_header_hovered ? clrRed : header_bg); //--- Update header color
      prev_header_hovered = is_header_hovered;                                //--- Update previous state
      ChartRedraw(0);                                                         //--- Redraw chart
   }

   update_button_hover_states(mouse_x, mouse_y);                              //--- Update button hovers
}

ここではis_cursor_in_header_or_buttons関数を実装し、マウスカーソルがヘッダーパネル上またはボタンや時間足セルといったインタラクティブ要素上にあるかを判定し、真偽値を返します。まずChartGetIntegerを用いてチャート幅をCHART_WIDTH_IN_PIXELSから取得し、続いてObjectGetIntegerを用いてヘッダーのOBJPROP_XDISTANCE、OBJPROP_YDISTANCE、OBJPROP_XSIZE、OBJPROP_YSIZEからヘッダーのx座標、y座標、幅、高さを取得します。これらを基にヘッダーの左右境界を計算し、マウス座標がヘッダー範囲内にあるかを判定します。

ボタンについては、ホバー判定用の半サイズとしてBUTTON_HOVER_SIZEの半分を算出し、各ボタンのx座標およびy座標を同様に取得します。その後、マウス座標がクローズ、トグル、ヒートマップ、p値、ソート、テーマ各ボタンのホバー領域内にあるかを個別に判定します。時間足セルについてはフラグをfalseで初期化し、num_tf_visible回ループを行います。各セルについてTF_CELL_RECTプレフィックスとインデックスを組み合わせた名前から矩形オブジェクトを取得し、その位置情報を用いてマウスが矩形の幅と高さの範囲内にあるかを確認します。該当する場合はフラグをtrueに設定し、ループを終了します。最終的にヘッダー上、ボタン上、または時間足領域内のいずれかであればtrueを返します。

次にupdate_button_hover_states関数を作成し、マウス位置に基づいてボタンおよび時間足のホバー状態を更新します。まずホバーサイズの半分を算出し、ホバー時の背景色としてDodger Blue、通常時の色として各テーマに応じた色を設定します。各ボタンについて位置を取得し、前述と同様の方法でホバー判定を行います。現在の状態がprev_close_hoveredなどの前回状態と異なる場合のみ、ObjectSetIntegerを用いてOBJPROP_COLORおよびOBJPROP_BGCOLORを更新し、ホバー時または通常時の色を適用します。その後、前回状態を更新し再描画します。時間足についても同様に、ループ内でセル名を生成し位置を取得、ホバー判定を行います。状態がprev_tf_hovered配列の値と異なる場合、ホバー時はDodger Blue、選択中の場合は強調色、それ以外はテーマに応じた通常色を設定し、ObjectSetIntegerで更新します。その後前回状態を更新し再描画します。

続いてupdate_header_hover_state関数を定義し、ボタン領域を除いたヘッダーホバー処理を管理します。ヘッダー位置とサイズは先ほどと同様に取得し、ホバーサイズを算出します。その上でクローズ、トグル、ヒートマップ、p値、ソート、テーマ各ボタン領域を除外したうえで、ヘッダー領域内にカーソルがあるかを判定します。状態がprev_header_hoveredと異なり、かつドラッグ中でない場合のみ、ヘッダー背景をホバー時は赤、通常時はテーマに応じた色へと変更し、ObjectSetIntegerでOBJPROP_BGCOLORを更新します。その後前回状態を更新し再描画を行います。最後にupdate_button_hover_statesを呼び出し、ボタンのホバー処理も同時に実行します。これらの関数はチャートイベントハンドラ内で使用しますが、マウス移動時のドラッグ操作を正しく処理するためには、マウス位置に応じてダッシュボード全体の要素位置を動的に更新するヘルパー関数が別途必要になります。

//+------------------------------------------------------------------+
//| Update panel object positions                                    |
//+------------------------------------------------------------------+
void update_panel_positions() {
   ObjectSetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE, panel_x);           //--- Update header x
   ObjectSetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE, panel_y);           //--- Update header y
   int panel_width = WIDTH_SYMBOL + num_symbols * (WIDTH_CELL - 1) + 4;     //--- Compute width
   ObjectSetInteger(0, HEADER_PANEL, OBJPROP_XSIZE, panel_width); //--- Update header size
   ObjectSetInteger(0, HEADER_PANEL_ICON, OBJPROP_XDISTANCE, panel_x + 12); //--- Update icon x
   ObjectSetInteger(0, HEADER_PANEL_ICON, OBJPROP_YDISTANCE, panel_y + 14); //--- Update icon y
   ObjectSetInteger(0, HEADER_PANEL_TEXT, OBJPROP_XDISTANCE, panel_x + 105); //--- Update text x
   ObjectSetInteger(0, HEADER_PANEL_TEXT, OBJPROP_YDISTANCE, panel_y + 12); //--- Update text y
   ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE, panel_x + (panel_width - 17)); //--- Update close x
   ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE, panel_y + 14);      //--- Update close y
   ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE, panel_x + (panel_width - 47)); //--- Update toggle x
   ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE, panel_y + 14);     //--- Update toggle y
   ObjectSetInteger(0, HEATMAP_BUTTON, OBJPROP_XDISTANCE, panel_x + (panel_width - 77)); //--- Update heatmap x
   ObjectSetInteger(0, HEATMAP_BUTTON, OBJPROP_YDISTANCE, panel_y + 14);    //--- Update heatmap y
   ObjectSetInteger(0, PVAL_BUTTON, OBJPROP_XDISTANCE, panel_x + (panel_width - 107)); //--- Update pval x
   ObjectSetInteger(0, PVAL_BUTTON, OBJPROP_YDISTANCE, panel_y + 14);       //--- Update pval y
   ObjectSetInteger(0, SORT_BUTTON, OBJPROP_XDISTANCE, panel_x + (panel_width - 137)); //--- Update sort x
   ObjectSetInteger(0, SORT_BUTTON, OBJPROP_YDISTANCE, panel_y + 14);       //--- Update sort y
   ObjectSetInteger(0, THEME_BUTTON, OBJPROP_XDISTANCE, panel_x + (panel_width - 167)); //--- Update theme x
   ObjectSetInteger(0, THEME_BUTTON, OBJPROP_YDISTANCE, panel_y + 14);      //--- Update theme y

   if (!panel_minimized) {                                                  //--- Check if maximized
      int panel_height = HEIGHT_HEADER + HEIGHT_TF_CELL + GAP_HEIGHT + HEIGHT_RECTANGLE * (num_symbols + 1) - num_symbols + 2; //--- Compute height
      
      ObjectSetInteger(0, MAIN_PANEL, OBJPROP_XDISTANCE, panel_x);          //--- Update main x
      ObjectSetInteger(0, MAIN_PANEL, OBJPROP_YDISTANCE, panel_y);          //--- Update main y
      ObjectSetInteger(0, MAIN_PANEL, OBJPROP_XSIZE, panel_width);          //--- Update main width
      ObjectSetInteger(0, MAIN_PANEL, OBJPROP_YSIZE, panel_height);         //--- Update main height

      int tf_y = panel_y + HEIGHT_HEADER;                                  //--- Compute TF y
      int tf_x_start = panel_x + 2;                                        //--- Set TF start x
      for (int i = 0; i < num_tf_visible; i++) {                           //--- Loop TFs
         int x_offset = tf_x_start + i * WIDTH_TF_CELL;                    //--- Compute offset
         string rect_name = TF_CELL_RECT + IntegerToString(i);             //--- Get rectangle name
         string text_name = TF_CELL_TEXT + IntegerToString(i);             //--- Get text name
         ObjectSetInteger(0, rect_name, OBJPROP_XDISTANCE, x_offset);      //--- Update TF rect x
         ObjectSetInteger(0, rect_name, OBJPROP_YDISTANCE, tf_y);          //--- Update TF rect y
         ObjectSetInteger(0, text_name, OBJPROP_XDISTANCE, x_offset + (WIDTH_TF_CELL / 2)); //--- Update TF text x
         ObjectSetInteger(0, text_name, OBJPROP_YDISTANCE, tf_y + (HEIGHT_TF_CELL / 2));    //--- Update TF text y
      }

      int matrix_y = tf_y + HEIGHT_TF_CELL + GAP_HEIGHT;                   //--- Compute matrix y
      ObjectSetInteger(0, "SYMBOL_ROW_HEADER", OBJPROP_XDISTANCE, panel_x + 2); //--- Update row header x
      ObjectSetInteger(0, "SYMBOL_ROW_HEADER", OBJPROP_YDISTANCE, matrix_y); //--- Update row header y
      ObjectSetInteger(0, "SYMBOL_ROW_HEADER_TEXT", OBJPROP_XDISTANCE, panel_x + (WIDTH_SYMBOL / 2 + 2)); //--- Update row header text x
      ObjectSetInteger(0, "SYMBOL_ROW_HEADER_TEXT", OBJPROP_YDISTANCE, matrix_y + (HEIGHT_RECTANGLE / 2)); //--- Update row header text y

      for (int i = 0; i < num_symbols; i++) {                              //--- Loop rows
         int y_offset = matrix_y + HEIGHT_RECTANGLE * (i + 1) - (1 + i);   //--- Compute y offset
         ObjectSetInteger(0, SYMBOL_ROW_RECTANGLE + IntegerToString(i), OBJPROP_XDISTANCE, panel_x + 2); //--- Update row rect x
         ObjectSetInteger(0, SYMBOL_ROW_RECTANGLE + IntegerToString(i), OBJPROP_YDISTANCE, y_offset); //--- Update row rect y
         ObjectSetInteger(0, SYMBOL_ROW_TEXT + IntegerToString(i), OBJPROP_XDISTANCE, panel_x + (WIDTH_SYMBOL / 2 + 2)); //--- Update row text x
         ObjectSetInteger(0, SYMBOL_ROW_TEXT + IntegerToString(i), OBJPROP_YDISTANCE, y_offset + (HEIGHT_RECTANGLE / 2 - 1)); //--- Update row text y

         int x_offset_col = panel_x + WIDTH_SYMBOL + i * WIDTH_CELL - i + 1; //--- Compute column x
         ObjectSetInteger(0, SYMBOL_COL_RECTANGLE + IntegerToString(i), OBJPROP_XDISTANCE, x_offset_col); //--- Update column rect x
         ObjectSetInteger(0, SYMBOL_COL_RECTANGLE + IntegerToString(i), OBJPROP_YDISTANCE, matrix_y); //--- Update column rect y
         ObjectSetInteger(0, SYMBOL_COL_TEXT + IntegerToString(i), OBJPROP_XDISTANCE, x_offset_col + (WIDTH_CELL / 2)); //--- Update column text x
         ObjectSetInteger(0, SYMBOL_COL_TEXT + IntegerToString(i), OBJPROP_YDISTANCE, matrix_y + (HEIGHT_RECTANGLE / 2)); //--- Update column text y

         for (int j = 0; j < num_symbols; j++) {                            //--- Loop columns
            string cell_name = CELL_RECTANGLE + IntegerToString(i) + "_" + IntegerToString(j); //--- Get cell name
            string text_name = CELL_TEXT + IntegerToString(i) + "_" + IntegerToString(j);      //--- Get text name
            int x_offset = panel_x + WIDTH_SYMBOL + j * WIDTH_CELL - j + 1; //--- Compute cell x
            ObjectSetInteger(0, cell_name, OBJPROP_XDISTANCE, x_offset);    //--- Update cell rect x
            ObjectSetInteger(0, cell_name, OBJPROP_YDISTANCE, y_offset);    //--- Update cell rect y
            ObjectSetInteger(0, text_name, OBJPROP_XDISTANCE, x_offset + (WIDTH_CELL / 2)); //--- Update cell text x
            ObjectSetInteger(0, text_name, OBJPROP_YDISTANCE, y_offset + (HEIGHT_RECTANGLE / 2 - 1)); //--- Update cell text y
         }
      }

      int legend_y = panel_y + panel_height + GAP_MAIN_LEGEND;               //--- Compute legend y
      ObjectSetInteger(0, LEGEND_PANEL, OBJPROP_XDISTANCE, panel_x);         //--- Update legend x
      ObjectSetInteger(0, LEGEND_PANEL, OBJPROP_YDISTANCE, legend_y);        //--- Update legend y
      ObjectSetInteger(0, LEGEND_PANEL, OBJPROP_XSIZE, panel_width);         //--- Update legend width
      ObjectSetInteger(0, LEGEND_PANEL, OBJPROP_YSIZE, HEIGHT_LEGEND_PANEL); //--- Update legend height

      int total_legend_width = num_legend_visible * WIDTH_LEGEND_CELL + (num_legend_visible - 1) * LEGEND_SPACING; //--- Compute legend width
      int x_start = panel_x + (panel_width - total_legend_width) / 2;        //--- Compute start x
      for (int i = 0; i < num_legend_visible; i++) {                         //--- Loop legends
         int x_offset = x_start + i * (WIDTH_LEGEND_CELL + LEGEND_SPACING);  //--- Compute offset
         string rect_name = LEGEND_CELL_RECTANGLE + IntegerToString(i);      //--- Get rectangle name
         string text_name = LEGEND_CELL_TEXT + IntegerToString(i);           //--- Get text name
         ObjectSetInteger(0, rect_name, OBJPROP_XDISTANCE, x_offset);        //--- Update legend rect x
         ObjectSetInteger(0, rect_name, OBJPROP_YDISTANCE, legend_y + 2);    //--- Update legend rect y
         ObjectSetInteger(0, text_name, OBJPROP_XDISTANCE, x_offset + WIDTH_LEGEND_CELL / 2); //--- Update legend text x
         ObjectSetInteger(0, text_name, OBJPROP_YDISTANCE, legend_y + 2 + HEIGHT_LEGEND / 2 - 1); //--- Update legend text y
      }
   }
   ChartRedraw(0);                                                           //--- Redraw chart
}

ここではupdate_panel_positions関数を実装し、パネルのドラッグやリサイズに応じてダッシュボード内のすべてのオブジェクト位置を調整し、現在のpanel_xおよびpanel_yに整合するようにします。まずヘッダーパネルのx座標およびy座標を更新し、銘柄配置およびセルレイアウトに合わせて幅を再計算し、OBJPROP_XSIZEを設定します。その後ヘッダーアイコン、タイトルテキスト、クローズ、トグル、ヒートマップ、p値、ソート、テーマといった各ボタンについて、パネル左端からの相対オフセットを用いてx位置を算出し、ObjectSetIntegerでOBJPROP_XDISTANCEおよびOBJPROP_YDISTANCEを更新します。

最小化状態でない場合は、フルパネルの高さを定数と行数から算出し、メインパネルの位置、幅、高さを同様に更新します。時間足行についてはy位置と開始x位置を計算し、表示対象の時間足数だけループし、各矩形およびテキストラベルのxおよびy位置を更新します。続いて行列領域のy位置を更新し、銘柄行ヘッダーの矩形およびテキスト位置を調整します。行方向のシンボルについてはyオフセットを計算し、各行の矩形およびテキストのxおよびy位置を更新します。列方向のシンボルについてはxオフセットを計算し、固定y位置のまま矩形とテキストを更新します。

セル領域では二重ループを用い、各セル名を生成しオフセットを算出したうえで、矩形およびテキストの位置を更新します。凡例についてはパネル下部にギャップを設けてy位置を計算し、凡例パネルのx、y、幅、高さを更新します。さらに凡例全体の幅を計算し中央揃えのための開始x位置を求め、表示対象アイテムごとにループし、それぞれの矩形およびテキストのxおよびy位置を更新します。最後にChartRedrawを呼び出して表示を更新します。これでダッシュボードの位置制御が完成するため、次はマウスイベントを有効化し、イベント駆動で操作できるようにします。そのためにOnInitイベントハンドラに以下のコードを追加します。

//--- Add this as the last line in ontick

ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);            //--- Enable mouse events

ここではChartSetIntegerを使用し、CHART_EVENT_MOUSE_MOVEディレクティブを有効化することでマウスイベントの取得を可能にします。これにより、OnChartEventイベントハンドラ内でチャート上のインタラクションを処理できるようになります。実装は以下のロジックに基づいておこないます。

//+------------------------------------------------------------------+
//| Handle chart event                                               |
//+------------------------------------------------------------------+
void OnChartEvent(const int event_id, const long& long_param, const double& double_param, const string& string_param) {
   if (event_id == CHARTEVENT_OBJECT_CLICK) {                   //--- Handle click event
      if (string_param == CLOSE_BUTTON) {                       //--- Check close click
         Print("Closing the panel now");                        //--- Print closing
         PlaySound("alert.wav");                                //--- Play sound
         panel_is_visible = false;                              //--- Hide panel
         delete_all_objects();                                  //--- Delete objects
         ChartRedraw(0);                                        //--- Redraw chart
      } else if (string_param == TOGGLE_BUTTON) {               //--- Check toggle click
         delete_all_objects();                                  //--- Delete objects
         panel_minimized = !panel_minimized;                    //--- Toggle minimized
         if (panel_minimized) {                                 //--- Handle minimize
            Print("Minimizing the panel");                      //--- Print minimizing
            create_minimized_dashboard();                       //--- Create minimized
            update_borders();                                   //--- Update borders
         } else {                                               //--- Handle maximize
            Print("Maximizing the panel");                      //--- Print maximizing
            create_full_dashboard();                            //--- Create full
            update_borders();                                   //--- Update borders
            update_tf_highlights();                             //--- Update highlights
            update_dashboard();                                 //--- Update dashboard
         }
         prev_header_hovered = false;                           //--- Reset header hover
         prev_close_hovered = false;                            //--- Reset close hover
         prev_toggle_hovered = false;                           //--- Reset toggle hover
         prev_heatmap_hovered = false;                          //--- Reset heatmap hover
         prev_pval_hovered = false;                             //--- Reset pval hover
         prev_sort_hovered = false;                             //--- Reset sort hover
         prev_theme_hovered = false;                            //--- Reset theme hover
         ArrayInitialize(prev_tf_hovered, false);               //--- Reset TF hovers
         color header_bg = is_light_theme ? clrSilver : C'60,60,60';               //--- Set header background
         ObjectSetInteger(0, HEADER_PANEL, OBJPROP_BGCOLOR, header_bg);            //--- Update header color
         color button_text = is_light_theme ? clrNavy : clrGold;                   //--- Set button color
         color theme_icon_color = is_light_theme ? clrBlack : clrWhite;            //--- Set theme color
         color close_text = is_light_theme ? clrBlack : clrWhite;                  //--- Set close color
         color header_icon_color = is_light_theme ? clrDodgerBlue : clrAqua;       //--- Set icon color
         ObjectSetInteger(0, HEADER_PANEL_ICON, OBJPROP_COLOR, header_icon_color); //--- Update icon color
         ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_COLOR, close_text);             //--- Update close color
         ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_BGCOLOR, clrNONE);              //--- Reset close background
         ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_COLOR, button_text);           //--- Update toggle color
         ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_BGCOLOR, clrNONE);             //--- Reset toggle background
         if (!panel_minimized) {                                                   //--- Check if maximized
            ObjectSetInteger(0, HEATMAP_BUTTON, OBJPROP_COLOR, button_text);       //--- Update heatmap color
            ObjectSetInteger(0, HEATMAP_BUTTON, OBJPROP_BGCOLOR, clrNONE);         //--- Reset heatmap background
            ObjectSetInteger(0, PVAL_BUTTON, OBJPROP_COLOR, button_text);          //--- Update pval color
            ObjectSetInteger(0, PVAL_BUTTON, OBJPROP_BGCOLOR, clrNONE);            //--- Reset pval background
            ObjectSetInteger(0, SORT_BUTTON, OBJPROP_COLOR, button_text);          //--- Update sort color
            ObjectSetInteger(0, SORT_BUTTON, OBJPROP_BGCOLOR, clrNONE);            //--- Reset sort background
            ObjectSetInteger(0, THEME_BUTTON, OBJPROP_COLOR, theme_icon_color);    //--- Update theme color
            ObjectSetInteger(0, THEME_BUTTON, OBJPROP_BGCOLOR, clrNONE);           //--- Reset theme background
         }
         ChartRedraw(0);                                        //--- Redraw chart
      } else if (string_param == HEATMAP_BUTTON) {              //--- Check heatmap click
         global_display_mode = (global_display_mode == MODE_STANDARD ? MODE_HEATMAP : MODE_STANDARD); //--- Toggle mode
         string new_icon = CharToString(global_display_mode == MODE_STANDARD ? (uchar)82 : (uchar)110); //--- Set new icon
         ObjectSetString(0, HEATMAP_BUTTON, OBJPROP_TEXT, new_icon); //--- Update icon
         Print("Switching to ", (global_display_mode == MODE_HEATMAP ? "Heatmap" : "Standard"), " mode"); //--- Print switch
         recreate_legend();                                     //--- Recreate legend
         update_dashboard();                                    //--- Update dashboard
         ChartRedraw(0);                                        //--- Redraw chart
      } else if (string_param == PVAL_BUTTON) {                 //--- Check pval click
         global_view_mode = (global_view_mode == VIEW_CORR ? VIEW_PVAL : VIEW_CORR); //--- Toggle view
         Print("Switching to ", (global_view_mode == VIEW_PVAL ? "P-Value" : "Correlation"), " view"); //--- Print switch
         update_dashboard();                                    //--- Update dashboard
         ChartRedraw(0);                                        //--- Redraw chart
      } else if (string_param == SORT_BUTTON) {                 //--- Check sort click
         cycle_sort_mode();                                     //--- Cycle mode
         string new_sort_icon;                                  //--- Declare new icon
         if (sort_mode == 0) new_sort_icon = CharToString('N'); //--- Set neutral
         else if (sort_mode == 1) new_sort_icon = CharToString('K'); //--- Set descending
         else new_sort_icon = CharToString('J');                //--- Set ascending
         ObjectSetString(0, SORT_BUTTON, OBJPROP_TEXT, new_sort_icon); //--- Update icon
         delete_all_objects();                                  //--- Delete objects
         create_full_dashboard();                               //--- Create full
         update_borders();                                      //--- Update borders
         update_dashboard();                                    //--- Update dashboard
         ChartRedraw(0);                                        //--- Redraw chart
      } else if (string_param == THEME_BUTTON) {                //--- Check theme click
         toggle_theme();                                        //--- Toggle theme
         ChartRedraw(0);                                        //--- Redraw chart
      } else {                                                  //--- Handle other clicks
         for (int i = 0; i < num_tf_visible; i++) {             //--- Loop TFs
            if (string_param == TF_CELL_TEXT + IntegerToString(i)) { //--- Check TF click
               switch_timeframe(i);                             //--- Switch timeframe
               ChartRedraw(0);                                  //--- Redraw chart
               break;                                           //--- Exit loop
            }
         }
      }
   } else if (event_id == CHARTEVENT_MOUSE_MOVE && panel_is_visible) { //--- Handle mouse move
      int mouse_x = (int)long_param;                            //--- Get mouse x
      int mouse_y = (int)double_param;                          //--- Get mouse y
      int mouse_state = (int)string_param;                      //--- Get mouse state

      if (mouse_x == last_mouse_x && mouse_y == last_mouse_y && !panel_dragging) return; //--- Skip if unchanged
      last_mouse_x = mouse_x;                                   //--- Update last x
      last_mouse_y = mouse_y;                                   //--- Update last y

      update_header_hover_state(mouse_x, mouse_y);              //--- Update header hover

      int header_x = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE);  //--- Get header x
      int header_y = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE);  //--- Get header y
      int header_width = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XSIZE);  //--- Get header width
      int header_height = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YSIZE); //--- Get header height
      int header_left = header_x;                               //--- Set left edge
      int header_right = header_left + header_width;            //--- Set right edge

      int half_size = BUTTON_HOVER_SIZE / 2;                    //--- Compute half size
      int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE); //--- Get close x
      int close_y = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE); //--- Get close y
      bool in_close_area = (mouse_x >= close_x - half_size && mouse_x <= close_x + half_size && mouse_y >= close_y - half_size && mouse_y <= close_y + half_size); //--- Check close area

      int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE); //--- Get toggle x
      int toggle_y = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE); //--- Get toggle y
      bool in_toggle_area = (mouse_x >= toggle_x - half_size && mouse_x <= toggle_x + half_size && mouse_y >= toggle_y - half_size && mouse_y <= toggle_y + half_size); //--- Check toggle area

      int heatmap_x = (int)ObjectGetInteger(0, HEATMAP_BUTTON, OBJPROP_XDISTANCE); //--- Get heatmap x
      int heatmap_y = (int)ObjectGetInteger(0, HEATMAP_BUTTON, OBJPROP_YDISTANCE); //--- Get heatmap y
      bool in_heatmap_area = (mouse_x >= heatmap_x - half_size && mouse_x <= heatmap_x + half_size && mouse_y >= heatmap_y - half_size && mouse_y <= heatmap_y + half_size); //--- Check heatmap area

      int pval_x = (int)ObjectGetInteger(0, PVAL_BUTTON, OBJPROP_XDISTANCE); //--- Get pval x
      int pval_y = (int)ObjectGetInteger(0, PVAL_BUTTON, OBJPROP_YDISTANCE); //--- Get pval y
      bool in_pval_area = (mouse_x >= pval_x - half_size && mouse_x <= pval_x + half_size && mouse_y >= pval_y - half_size && mouse_y <= pval_y + half_size); //--- Check pval area

      int sort_x = (int)ObjectGetInteger(0, SORT_BUTTON, OBJPROP_XDISTANCE); //--- Get sort x
      int sort_y = (int)ObjectGetInteger(0, SORT_BUTTON, OBJPROP_YDISTANCE); //--- Get sort y
      bool in_sort_area = (mouse_x >= sort_x - half_size && mouse_x <= sort_x + half_size && mouse_y >= sort_y - half_size && mouse_y <= sort_y + half_size); //--- Check sort area

      int theme_x = (int)ObjectGetInteger(0, THEME_BUTTON, OBJPROP_XDISTANCE); //--- Get theme x
      int theme_y = (int)ObjectGetInteger(0, THEME_BUTTON, OBJPROP_YDISTANCE); //--- Get theme y
      bool in_theme_area = (mouse_x >= theme_x - half_size && mouse_x <= theme_x + half_size && mouse_y >= theme_y - half_size && mouse_y <= theme_y + half_size); //--- Check theme area

      if (prev_mouse_state == 0 && mouse_state == 1) {          //--- Check drag start
         if (mouse_x >= header_left && mouse_x <= header_right && mouse_y >= header_y && mouse_y <= header_y + header_height &&
             !in_close_area && !in_toggle_area && !in_heatmap_area && !in_pval_area && !in_sort_area && !in_theme_area) { //--- Check draggable area
            panel_dragging = true;                              //--- Start dragging
            panel_drag_x = mouse_x;                             //--- Set drag x
            panel_drag_y = mouse_y;                             //--- Set drag y
            panel_start_x = header_x;                           //--- Set start x
            panel_start_y = header_y;                           //--- Set start y
            ObjectSetInteger(0, HEADER_PANEL, OBJPROP_BGCOLOR, clrMediumBlue); //--- Set drag color
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);      //--- Disable scroll
         }
      }

      if (panel_dragging && mouse_state == 1) {                 //--- Handle dragging
         int dx = mouse_x - panel_drag_x;                       //--- Compute x delta
         int dy = mouse_y - panel_drag_y;                       //--- Compute y delta
         panel_x = panel_start_x + dx;                          //--- Update panel x
         panel_y = panel_start_y + dy;                          //--- Update panel y

         int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);    //--- Get chart width
         int chart_height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);  //--- Get chart height
         int panel_width = WIDTH_SYMBOL + num_symbols * (WIDTH_CELL - 1) + 4; //--- Compute panel width
         int panel_height = HEIGHT_HEADER + HEIGHT_TF_CELL + GAP_HEIGHT + HEIGHT_RECTANGLE * (num_symbols + 1) - num_symbols + 2; //--- Compute height
         int total_height = panel_height + GAP_MAIN_LEGEND + HEIGHT_LEGEND_PANEL; //--- Compute total height
         
         panel_x = MathMax(0, MathMin(chart_width - panel_width, panel_x)); //--- Clamp x
         panel_y = MathMax(0, MathMin(chart_height - (panel_minimized ? HEIGHT_HEADER : total_height), panel_y)); //--- Clamp y

         update_panel_positions();                              //--- Update positions
         ChartRedraw(0);                                        //--- Redraw chart
      }

      if (mouse_state == 0 && prev_mouse_state == 1) {          //--- Check drag end
         if (panel_dragging) {                                  //--- Check was dragging
            panel_dragging = false;                             //--- Stop dragging
            update_header_hover_state(mouse_x, mouse_y);        //--- Update hover
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true);       //--- Enable scroll
            ChartRedraw(0);                                     //--- Redraw chart
         }
      }

      prev_mouse_state = mouse_state;                           //--- Update previous state
   }
}

OnChartEventイベントハンドラ内では、まずCHARTEVENT_OBJECT_CLICKを処理し、string_paramをボタン名と照合します。CLOSE_BUTTONの場合はメッセージを出力し、PlaySoundでアラート音を再生し、panel_is_visibleをfalseに設定した上でdelete_all_objectsを呼び出し、ChartRedrawで画面を更新します。TOGGLE_BUTTONの場合はオブジェクトを削除し、panel_minimizedの状態を切り替えます。最小化に移行する場合はメッセージを出力し、create_minimized_dashboardを呼び出して最小化表示を生成し、update_bordersを実行します。最大化に戻る場合はメッセージを出力し、create_full_dashboardを呼び出してフル表示を再生成し、update_bordersに加えてハイライトとダッシュボード更新します。その後、すべてのホバー状態をリセットし、時間足の状態配列はArrayInitializeで初期化します。テーマに応じたヘッダー背景色を再設定し、ヘッダーパネルへ適用します。アイコン、クローズ、トグルの各要素もテーマに応じて更新します。フル表示状態であれば、ヒートマップ、p値、ソート、テーマボタンも含めて背景をリセットし、最後にChartRedrawを呼び出します。

次にCHARTEVENT_MOUSE_MOVEを処理します。panel_is_visibleがtrueの場合のみ処理をおこない、long_paramをマウスx座標、double_paramをy座標、string_paramを状態として取得します。前回のマウス位置と一致し、かつドラッグ中でない場合は早期終了します。それ以外の場合は座標を更新し、update_header_hover_stateを呼び出してホバー状態を更新します。ヘッダーの境界および各ボタン位置を取得し、ホバー半径を計算した上で、クローズ、トグル、ヒートマップ、p値、ソート、テーマ各領域の判定をおこないます。前回状態がゼロで現在が1であり、マウスがヘッダー内かつボタン領域外にある場合はドラッグ開始と判断し、panel_draggingをtrueに設定します。ドラッグ開始位置と基準位置を保存し、ヘッダー背景をミディアムブルーに変更し、チャートのスクロール操作を無効化します。

ドラッグ中かつ状態が1の場合は、差分を計算しpanel_xおよびpanel_yを更新します。チャート幅および高さをChartGetIntegerで取得し、パネルサイズおよび凡例を含む全体高さを計算したうえで、パネルがチャート外に出ないようMathMaxおよびMathMinで制限します。その後update_panel_positionsを呼び出し、ChartRedrawで更新します。状態が0で前回が1の場合はドラッグ終了と判定し、panel_draggingをfalseに戻します。再度update_header_hover_stateを呼び出し、スクロールを有効化し、再描画します。最後にprev_mouse_stateを現在状態で更新します。ここまででインタラクション全体が完成していますが、重要な点として、ダッシュボードが非表示または最小化状態のときには更新処理を継続する必要はありません。そのためOnTickイベントハンドラ側では、次のロジックを使用します。

//+------------------------------------------------------------------+
//| Handle tick event                                                |
//+------------------------------------------------------------------+
void OnTick() {
   if (panel_is_visible && !panel_minimized) {                  //--- Check if update needed
      update_dashboard();                                       //--- Update on tick
   }
}

OnTickイベントハンドラを修正し、効率性の観点からダッシュボードの更新を必要な場合にのみ実行するようにします。現在はpanel_is_visibleがtrueであり、かつpanel_minimizedがfalseの場合にのみupdate_dashboardを呼び出し、各ティックごとに相関計算、可視化、およびその他の要素更新をおこないます。最後にプログラム終了時には、チャート上のダッシュボード要素を削除し、チャートイベントを無効化するために、delete_all_objectsを呼び出してクリーンアップします。

//+------------------------------------------------------------------+
//| Deinitialize expert                                              |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   delete_all_objects();                                        //--- Delete objects
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);           //--- Disable mouse events
   ChartRedraw(0);                                              //--- Redraw chart
}

OnDeinitイベントハンドラでは、プログラムが削除または初期化解除された際のリソースクリーンアップ処理を追加します。まずdelete_all_objectsを呼び出し、チャート上のすべてのグラフィカル要素を削除します。次にChartSetInteger を用いてCHART_EVENT_MOUSE_MOVEをfalseに設定し、チャートのマウスムーブイベントを無効化します。その後ChartRedrawを呼び出し、変更内容をチャートに反映させます。コンパイルすると、次の結果が得られます。

完全なテストGIF

この可視化から、相関行列ダッシュボードに対してすべてのインタラクティブ機能を実装し、目的が達成されたことが確認できます。残るは、システムの動作確認、つまり前のセクションでおこなったテストです。


バックテスト

テストを実施しました。以下はコンパイル後の可視化を単一のGraphics Interchange Format (GIF)ビットマップ画像形式で示したものです。

相関行列バックテストGIF


結論

本記事では、MQL5における相関行列ダッシュボードを拡張し、パネルのドラッグおよび最小化といったマウスイベントによる操作、ボタンのホバーエフェクトによる視覚的フィードバック、相関強度に基づく昇順および降順ソート、相関表示とp値表示の切り替え、ライトテーマとダークテーマの切り替えおよび動的色更新、セルツールチップによる詳細情報表示を実装しました。このシステムは、使いやすさを向上させるためにイベント駆動型の応答をサポートするようになりました。具体的には、ホバー検出、ドラッグ中にチャートの範囲内に収まるようにするクランプ機能、そしてパフォーマまた、ホバー検出、ドラッグ時のチャート境界内制御、効率的な更新処理により、イベント駆動型の応答性とパフォーマンスの両立を実現しています。このインタラクティブな相関行列ダッシュボードにより、資産間の依存関係をより動的に分析できる環境が整い、今後のさらなる最適化にも対応可能となっています。取引をお楽しみください。

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20962

添付されたファイル |
最後のコメント | ディスカッションに移動 (4)
Criistian Moore'
Criistian Moore' | 25 1月 2026 において 03:01
なぜボタンのシンボルが表示されないのか、理由がわかりますか?
Allan Munene Mutiiria
Allan Munene Mutiiria | 25 1月 2026 において 14:44
Criistian Moore' #:
ボタンのシンボルが表示されないのはなぜですか?
あなたのPCではまだフォントが使用できません。
Vitaly Muzichenko
Vitaly Muzichenko | 25 3月 2026 において 13:21
Criistian Moore' #:
ボタンのシンボルが表示されず、小さな四角形だけが表示されるのはなぜかわかりますか?
ターミナルを最新バージョンにアップデートしてください。
Юрий Елагин
Юрий Елагин | 27 3月 2026 において 22:41
私のバージョンでは、いくつかのツールの上にカーソルを置き、追加ウィンドウでエンターキーを押すと、最後のN本のローソク足の相関グラフが表示され、ユーザーが自分で設定することができました。しかし、私はあなたのバージョンの表のニュアンスの方が好きだ。チャートの描画を追加して、履歴上のペアの相関関係を分析すれば、大砲の出来上がりだ。
プライスアクション分析ツールキットの開発(第55回):CPIミニローソク足オーバーレイによるバー内圧力の可視化 プライスアクション分析ツールキットの開発(第55回):CPIミニローソク足オーバーレイによるバー内圧力の可視化
価格チャート上にバー内の買い圧力と売り圧力を可視化するCLVベースのオーバーレイであるCandle Pressure Index(CPI、ローソク足圧力指数)の設計とMetaTrader 5への実装について解説します。本記事では、ローソク足の構造、圧力分類および可視化の仕組み、そして時間足や銘柄に依存せず一貫した動作を維持する、リペイントなしの遷移ベースアラートシステムに焦点を当てます。
ラリー・ウィリアムズの『市場の秘密』(第8回):ボラティリティ、ストラクチャー、時間フィルターの組み合わせ ラリー・ウィリアムズの『市場の秘密』(第8回):ボラティリティ、ストラクチャー、時間フィルターの組み合わせ
MQL5における、ラリー・ウィリアムズに着想を得たボラティリティブレイクアウト型エキスパートアドバイザーの構築についての詳細な解説です。本手法は、スイング構造、ボラティリティベースのエントリー、曜日フィルター、時間フィルター、柔軟なリスク管理を組み合わせ、完全な実装と再現性のあるテスト環境を備えています。
古典的な戦略を再構築する(第21回):ボリンジャーバンドとRSIのアンサンブル戦略の発見 古典的な戦略を再構築する(第21回):ボリンジャーバンドとRSIのアンサンブル戦略の発見
EURUSD市場を対象としたアンサンブル型アルゴリズム取引戦略の開発について説明します。この戦略は、ボリンジャーバンドとRSI(相対力指数)を組み合わせたものです。初期のルールベース戦略は高品質なシグナルを生成した一方で、取引頻度が低く、収益性にも限界がありました。その後、複数の戦略バリエーションを反復的に評価した結果、市場に対する理解の誤り、ノイズの増加、パフォーマンスの劣化といった問題が明らかになりました。これらの課題に対し、統計的学習アルゴリズムを適切に活用し、モデリング対象をテクニカル指標へと再定義し、適切なスケーリングを適用したうえで、機械学習による予測と従来の取引ルールを組み合わせることで、最終的には許容可能なシグナル品質を維持しながら、収益性と取引頻度の大幅な改善を達成しました。
プライスアクション分析ツールキットの開発(第56回):CPIを用いたセッションの受容と拒否の解読 プライスアクション分析ツールキットの開発(第56回):CPIを用いたセッションの受容と拒否の解読
時間で区切られた市場セッションとCandle Pressure Index (CPI)を組み合わせ、確定足データと明確に定義されたルールに基づき、セッション境界での受容と拒否の挙動を分類するセッションに基づいた分析手法を提示します。