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

この可視化から、相関行列ダッシュボードに対してすべてのインタラクティブ機能を実装し、目的が達成されたことが確認できます。残るは、システムの動作確認、つまり前のセクションでおこなったテストです。
バックテスト
テストを実施しました。以下はコンパイル後の可視化を単一のGraphics Interchange Format (GIF)ビットマップ画像形式で示したものです。

結論
本記事では、MQL5における相関行列ダッシュボードを拡張し、パネルのドラッグおよび最小化といったマウスイベントによる操作、ボタンのホバーエフェクトによる視覚的フィードバック、相関強度に基づく昇順および降順ソート、相関表示とp値表示の切り替え、ライトテーマとダークテーマの切り替えおよび動的色更新、セルツールチップによる詳細情報表示を実装しました。このシステムは、使いやすさを向上させるためにイベント駆動型の応答をサポートするようになりました。具体的には、ホバー検出、ドラッグ中にチャートの範囲内に収まるようにするクランプ機能、そしてパフォーマまた、ホバー検出、ドラッグ時のチャート境界内制御、効率的な更新処理により、イベント駆動型の応答性とパフォーマンスの両立を実現しています。このインタラクティブな相関行列ダッシュボードにより、資産間の依存関係をより動的に分析できる環境が整い、今後のさらなる最適化にも対応可能となっています。取引をお楽しみください。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20962
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
プライスアクション分析ツールキットの開発(第55回):CPIミニローソク足オーバーレイによるバー内圧力の可視化
ラリー・ウィリアムズの『市場の秘密』(第8回):ボラティリティ、ストラクチャー、時間フィルターの組み合わせ
古典的な戦略を再構築する(第21回):ボリンジャーバンドとRSIのアンサンブル戦略の発見
プライスアクション分析ツールキットの開発(第56回):CPIを用いたセッションの受容と拒否の解読
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
ボタンのシンボルが表示されないのはなぜですか?
ボタンのシンボルが表示されず、小さな四角形だけが表示されるのはなぜかわかりますか?