English Русский Deutsch
preview
MQL5取引ツール(第11回):ヒートマップおよび標準モード対応相関行列ダッシュボード(ピアソン、スピアマン、ケンドール)

MQL5取引ツール(第11回):ヒートマップおよび標準モード対応相関行列ダッシュボード(ピアソン、スピアマン、ケンドール)

MetaTrader 5トレーディング |
19 1
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

前回の記事(第10回)では、MetaQuotes Language 5 (MQL5)でストラテジートラッカーシステムを開発しました。このシステムは、移動平均クロスオーバーシグナルを検出し、複数のテイクプロフィットレベルやストップロスを用いて取引を追跡し、その結果をチャート上に視覚的に表示するものでした。第11回では、ピアソン、スピアマン、ケンドールの各手法に対応した相関行列ダッシュボードを開発し、ヒートマップモードと標準モードの両方を実装します。このダッシュボードは、選択した手法に基づき、設定可能な時間足およびバー数を用いて資産間の関係性を算出します。標準モードでは色の閾値とp値の有意性を示す星印を表示し、ヒートマップモードではグラデーションによる視覚表現を提供します。さらに、時間足選択ツール、モード切り替え、動的な凡例を備えたインタラクティブなユーザーインターフェース(UI)も実装します。本記事では以下のトピックを扱います。

  1. 相関行列ダッシュボードフレームワークの理解
  2. MQL5での実装
  3. バックテスト
  4. 結論

最終的には、資産間の相互依存関係を分析するための、カスタマイズ可能で実用的なMQL5相関行列ダッシュボードを完成させます。それでは始めましょう。


相関行列ダッシュボードフレームワークの理解

相関行列ダッシュボードのフレームワークは、相関係数を計算することで金融資産間の関係性を分析し、ポートフォリオの分散、ヘッジ、マルチアセット戦略に影響を与える相互依存関係を把握するのに役立ちます。ユーザーが選択した銘柄について、指定された期間および時間足における価格変化を計算し、線形関係を測定するピアソン、順位に基づく単調関係を捉えるスピアマン、順位の一致度を評価するケンドールの3つの統計手法のいずれかを適用します。これにより、資産同士が同じ方向に動くのか、逆方向に動くのかを定量化します。さらに、p値によって統計的な有意性(信頼性)を評価し、強い正相関、強い負相関、弱い相関、無相関といった状態を色の閾値やグラデーションで視覚的に示すことで、手計算なしでパターンを素早く把握できるようにします。

標準モードでは、あらかじめ定義された閾値に基づいて相関を分類し、強い正相関や負相関には異なる色を適用し、さらにp値の有意水準に応じて星印を付与して統計的信頼性を示します。一方、ヒートマップモードでは、負から正までの相関の強さを連続的なカラ―グラデーションで表現し、微妙な差異も把握しやすくなります。インターフェースには、分析期間を切り替える時間足選択ツール、モードやテーマを切り替えるトグルボタン、色や値の意味を説明する動的な凡例などのインタラクティブ要素が含まれており、銘柄を行列に配置したグリッド上でで各セルにペアごとの相関が表示されます。

実装方針としては、まず銘柄のリストを解析し、価格変化(デルタ)に基づいて選択した手法で相関係数とp値を計算します。その後、ヘッダー、時間足、銘柄、セル、凡例などを含むパネル構成のUIを描画し、モードや閾値に応じて表示を動的に更新します。さらに、モード切り替えや時間足変更といったユーザー操作に対応するイベント処理を組み込み、新しいデータに応じてダッシュボードを更新して、リアルタイムに近い分析を可能にします。以下に想定されるビジュアル表示の例を示します。

相関行列ダッシュボードフレームワーク


MQL5での実装

MQL5でプログラムを作成するには、まずMetaEditorを開き、ナビゲーターで[Experts]フォルダを探します。[新規]タブをクリックして指示に従い、ファイルを作成します。ファイルが作成されたら、コーディング環境で、まずプログラム全体で使用する入力パラメータグローバル変数をいくつか宣言する必要があります。

//+------------------------------------------------------------------+
//|                           Correlation Matrix Dashboard PART1.mq5 |
//|                           Copyright 2026, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Allan Munene Mutiiria."
#property link "https://t.me/Forex_Algo_Trader"
#property version "1.00"

#include <Math\Stat\Math.mqh>

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input string SymbolsList                   = "EURUSDm,GBPUSDm,USDJPYm,AUDUSDm,BTCUSDm,NZDUSDm,US500m,XAUUSDm"; // Comma-separated symbols (up to 30)
input ENUM_TIMEFRAMES CorrelationTimeframe = PERIOD_CURRENT;                                                   // Timeframe for correlation calculation
input int CorrelationBars                  = 100;                                                              // Number of bars for correlation calculation (min 20)
input double StrongPositiveThresholdPct    = 70.0;                                                             // Strong positive threshold in % (e.g., 70.0 for >=0.70)
input double StrongNegativeThresholdPct    = -70.0;                                                            // Strong negative threshold in % (e.g., -70.0 for <=-0.70)
input double PValueThreshold1              = 0.01;                                                             // P-value for *** significance
input double PValueThreshold2              = 0.05;                                                             // P-value for ** significance
input double PValueThreshold3              = 0.10;                                                             // P-value for * significance
input color ColorStrongPositiveBg          = clrLimeGreen;                                                     // Background for strong positive
input color ColorStrongNegativeBg          = clrOrangeRed;                                                     // Background for strong negative
input color ColorNeutralBg                 = C'70,70,70';                                                      // Background for neutral/mild/zero/diagonal cells
input color ColorDiagonalBg                = C'40,40,40';                                                      // Background for diagonal cells
input color ColorTextStrong                = clrWhite;                                                         // Text color for strong correlations
input color ColorTextPositive              = clrDeepSkyBlue;                                                   // Text color for mild positive
input color ColorTextNegative              = clrRed;                                                           // Text color for mild negative
input color ColorTextZero                  = clrWhite;                                                         // Text color for zero or diagonal

//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+
enum DisplayMode
{
   MODE_STANDARD, // Standard Thresholds
   MODE_HEATMAP   // Heatmap Gradient
};
input DisplayMode DashboardMode = MODE_STANDARD; // Dashboard Display Mode

enum CorrelationMethod
{
   PEARSON,   // Pearson correlation
   SPEARMAN,  // Spearman rank correlation
   KENDALL    // Kendall tau correlation
};
input CorrelationMethod CorrMethod = PEARSON; // Correlation calculation method

//+------------------------------------------------------------------+
//| Defines                                                          |
//+------------------------------------------------------------------+
#define MAIN_PANEL              "PANEL_MAIN"                     // Define main panel rectangle identifier
#define HEADER_PANEL            "PANEL_HEADER"                   // Define header panel rectangle identifier
#define LEGEND_PANEL            "PANEL_LEGEND"                   // Define legend panel rectangle identifier
#define HEADER_PANEL_ICON       "PANEL_HEADER_ICON"              // Define header icon label identifier
#define HEADER_PANEL_TEXT       "PANEL_HEADER_TEXT"              // Define header title label identifier
#define CLOSE_BUTTON            "BUTTON_CLOSE"                   // Define close button identifier
#define TOGGLE_BUTTON           "BUTTON_TOGGLE"                  // Define toggle (minimize/maximize) button identifier
#define HEATMAP_BUTTON          "BUTTON_HEATMAP"                 // Define heatmap toggle button identifier
#define PVAL_BUTTON             "BUTTON_PVAL"                    // Define P-value toggle button identifier
#define SORT_BUTTON             "BUTTON_SORT"                    // Define sort button identifier
#define THEME_BUTTON            "BUTTON_THEME"                   // Define theme toggle button identifier
#define TF_CELL_RECT            "TF_CELL_RECT_"                  // Define timeframe cell rectangle prefix
#define TF_CELL_TEXT            "TF_CELL_TEXT_"                  // Define timeframe cell text prefix
#define SYMBOL_ROW_RECTANGLE    "SYMBOL_ROW_"                    // Define row symbol rectangle prefix
#define SYMBOL_ROW_TEXT         "SYMBOL_ROW_TEXT_"               // Define row symbol text label prefix
#define SYMBOL_COL_RECTANGLE    "SYMBOL_COL_"                    // Define column symbol rectangle prefix
#define SYMBOL_COL_TEXT         "SYMBOL_COL_TEXT_"               // Define column symbol text label prefix
#define CELL_RECTANGLE          "CELL_"                          // Define correlation cell rectangle prefix
#define CELL_TEXT               "CELL_TEXT_"                     // Define correlation cell text label prefix
#define LEGEND_CELL_RECTANGLE   "LEGEND_CELL_"                   // Define legend cell rectangle prefix
#define LEGEND_CELL_TEXT        "LEGEND_CELL_TEXT_"              // Define legend cell text prefix
#define WIDTH_SYMBOL            80                               // Define width of symbol rectangles
#define WIDTH_CELL              80                               // Define width of correlation cells
#define WIDTH_TF_CELL           45                               // Define width of TF cells
#define WIDTH_LEGEND_CELL       45                               // Define width of legend cells
#define HEIGHT_RECTANGLE        30                               // Define height of all rectangles
#define HEIGHT_HEADER           27                               // Define height of header
#define HEIGHT_TF_CELL          25                               // Define height of TF cells
#define HEIGHT_LEGEND           30                               // Define height of legend cells
#define HEIGHT_LEGEND_PANEL     34                               // Define height of legend panel (with padding)
#define LEGEND_SPACING          5                                // Define spacing between legend cells
#define NUM_LEGEND_ITEMS        15                               // Define increased to cover max possible (e.g., 11 in heatmap)
#define GAP_HEIGHT              8                                // Define gap between sections
#define GAP_MAIN_LEGEND         2                                // Define vertical gap between main panel and legend
#define COLOR_WHITE             clrWhite                         // Define white color for text and backgrounds
#define COLOR_BLACK             clrBlack                         // Define black color for borders and text
#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 MAX_SYMBOLS             30                               // Define maximum symbols supported
#define NUM_TF                  8                                // Define number of timeframes

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
int panel_x = 20, panel_y = 40;                                  //--- Initialize panel position coordinates
string symbols_array[MAX_SYMBOLS];                               //--- Declare array to store symbols
int num_symbols = 0;                                             //--- Initialize number of valid symbols
double correlation_matrix[MAX_SYMBOLS][MAX_SYMBOLS];             //--- Declare matrix to store correlations
double pvalue_matrix[MAX_SYMBOLS][MAX_SYMBOLS];                  //--- Declare matrix to store p-values
DisplayMode global_display_mode;                                 //--- Declare runtime display mode
ENUM_TIMEFRAMES global_correlation_tf;                           //--- Declare runtime timeframe
int current_tf_index = -1;                                       //--- Initialize index of current TF
int num_tf_visible;                                              //--- Declare dynamic number of visible TF cells
int num_legend_visible;                                          //--- Declare dynamic number of visible legend items
double visible_corr_vals[];                                      //--- Declare array of visible correlation values for legend

ENUM_TIMEFRAMES tf_list[NUM_TF] = {PERIOD_M1, PERIOD_M5, PERIOD_M15, PERIOD_M30, PERIOD_H1, PERIOD_H4, PERIOD_D1, PERIOD_W1}; //--- Initialize timeframe list
string tf_strings[NUM_TF] = {"M1", "M5", "M15", "M30", "H1", "H4", "D1", "W1"};                                            //--- Initialize timeframe strings

// Enhanced gradient colors for heatmap
color heatmap_colors[] = {clrRed, clrOrangeRed, clrOrange, clrYellow, clrLightGray, clrLime, clrLimeGreen, clrGreen}; //--- Initialize heatmap color gradient array

まず、相関計算に不可欠な統計関数を提供する数学ライブラリを「#include <Math\Stat\Math.mqh>」でインクルードすることから実装を開始します。次に、ダッシュボードをカスタマイズするための入力パラメータを定義します。最大30銘柄まで指定可能なカンマ区切り文字列のSymbolsList、計算に使用する時間足を指定するENUM_TIMEFRAMES型のCorrelationTimeframe、最小20本以上のバー数を指定する整数CorrelationBarsが含まれます。また、相関の分類に使うパーセンテージ閾値としてStrongPositiveThresholdPctやStrongNegativeThresholdPct、統計的有意性を示すためのPValueThreshold1などのp値閾値、さらにColorStrongPositiveBg(強い正相関の背景色:ライムグリーン)をはじめ、負相関、中立、対角、テキスト用の色設定も定義します。

続いて、設定オプション用の列挙型を定義します。DisplayMode列挙型では、閾値ベースの表示をおこなうMODE_STANDARDと、グラデーション表示をおこなうMODE_HEATMAPを定義し、入力パラメータDashboardModeのデフォルトは標準モードに設定します。同様に、CorrelationMethod列挙型では、線形相関のPEARSON、順位ベースのSPEARMAN、順位の一致度を測るKENDALLを用意し、入力パラメータCorrMethodはデフォルトでピアソン法とします。その後、#defineディレクティブを使ってUI要素やレイアウトの定数を定義します。たとえば、メインパネル用のMAIN_PANEL、ヘッダーパネル用のHEADER_PANEL、時間足セル用の接頭辞「TF_CELL_RECT」といった識別子に加え、WIDTH_SYMBOL(80ピクセル)、HEIGHT_RECTANGLE(30ピクセル)、GAP_HEIGHT(8ピクセル)などの寸法、さらにCOLOR_WHITEをwhiteにエイリアスとして定義した色も含まれます。

次に、状態管理とデータ保持のためのグローバル変数を宣言します。パネル位置を表すpanel_xとpanel_y(それぞれ20と40で初期化)、最大銘柄数サイズの文字列配列symbols_array、銘柄数を保持するnum_symbols(初期値0)、相関値とp値を格納する2次元配列correlation_matrixとpvalue_matrixを用意します。また、表示モードを保持するglobal_display_mode、時間足を保持するglobal_correlation_tf、現在の時間足インデックスcurrent_tf_index(初期値-1)、表示する時間足数や凡例数を表すnum_tf_visibleとnum_legend_visible、さらに凡例用の相関値配列visible_corr_valsも定義します。最後に、時間足と色に関する配列を初期化します。tf_listは1分足から週足までの値を含むENUM_TIMEFRAMES配列、tf_stringsはそれに対応する文字列ラベル、heatmap_colorsは赤から緑へと変化するグラデーション用のカラー配列です。次に、統計分析を実行するためのいくつかのヘルパー関数を定義します。

//+------------------------------------------------------------------+
//| Approximate Normal CDF                                           |
//+------------------------------------------------------------------+
bool NormalCDF(double mean, double stddev, double x, double &cdf) {
   if (stddev <= 0.0) return false;                             //--- Check for invalid standard deviation
   double z = (x - mean) / stddev;                              //--- Compute z-score
   if (z < -10) {                                               //--- Handle extreme low z-value
      cdf = 0.0;                                                //--- Set CDF to 0
      return true;                                              //--- Return success
   }
   if (z > 10) {                                                //--- Handle extreme high z-value
      cdf = 1.0;                                                //--- Set CDF to 1
      return true;                                              //--- Return success
   }
   double t = 1 / (1 + 0.2316419 * MathAbs(z));                 //--- Compute t for approximation
   double d = 0.3989423 * MathExp(-z * z / 2);                  //--- Compute density d
   cdf = d * t * (0.3193815 + t * (-0.3565638 + t * (1.7814779 + t * (-1.821256 + t * 1.3302744)))); //--- Calculate CDF approximation
   if (z > 0) cdf = 1 - cdf;                                    //--- Adjust for positive z
   return true;                                                 //--- Return success
}

//+------------------------------------------------------------------+
//| Approximate Student t CDF                                        |
//+------------------------------------------------------------------+
bool StudentCDF(int df, double x, double &cdf) {
   if (df <= 0) return false;                                   //--- Check for invalid degrees of freedom
   double a = df / 2.0;                                         //--- Compute alpha parameter
   double b = 0.5;                                              //--- Set beta parameter
   double xt = df / (df + x * x);                               //--- Compute xt for incomplete beta
   double ib = MathBetaIncomplete(xt, a, b);                    //--- Compute incomplete beta
   double beta = MathExp(MathGammaLog(a) + MathGammaLog(b) - MathGammaLog(a + b)); //--- Compute beta function
   double regularized = ib / beta;                              //--- Compute regularized incomplete beta
   if (x >= 0) {                                                //--- Handle non-negative x
      cdf = 1 - 0.5 * regularized;                              //--- Set CDF for positive side
   } else {                                                     //--- Handle negative x
      cdf = 0.5 * regularized;                                  //--- Set CDF for negative side
   }
   return true;                                                 //--- Return success
}

//+------------------------------------------------------------------+
//| Calculate p-value based on method and correlation                |
//+------------------------------------------------------------------+
double calculate_pvalue(double corr, CorrelationMethod method, int n) {
   if (n < 3) return 1.0;                                       //--- Return invalid if insufficient samples

   double cdf = 0.0;                                            //--- Initialize CDF variable
   if (method == KENDALL) {                                     //--- Handle Kendall method
      double sigma = MathSqrt((4.0 * n + 10.0) / (9.0 * n * (n - 1.0))); //--- Compute sigma
      if (sigma == 0) return 1.0;                               //--- Return invalid if sigma zero
      double z = corr / sigma;                                  //--- Compute z-score
      if (!NormalCDF(0, 1, MathAbs(z), cdf)) return 1.0;        //--- Compute Normal CDF or return invalid
      return 2 * (1 - cdf);                                     //--- Return two-tailed p-value
   } else {                                                     //--- Handle PEARSON or SPEARMAN
      double r2 = corr * corr;                                  //--- Compute squared correlation
      if (r2 >= 1.0) return 0.0;                                //--- Return zero for perfect correlation
      double denom = 1.0 - r2;                                  //--- Compute denominator
      if (denom <= 0.0) return 0.0;                             //--- Avoid division by zero
      double t = corr * MathSqrt((n - 2.0) / denom);            //--- Compute t-statistic
      if (!StudentCDF(n - 2, MathAbs(t), cdf)) return 1.0;      //--- Compute Student CDF or return invalid
      return 2 * (1 - cdf);                                     //--- Return two-tailed p-value
   }
}

まず、NormalCDF関数を定義します。これは正規分布の累積分布関数(CDF)を近似するためのもので、p値の計算に使用されます。平均、標準偏差、値x、そしてCDF結果を格納する参照を引数として受け取ります。最初に標準偏差が不正かどうかをチェックし、不正であればfalseを返します。その後、xを標準化してzスコアを計算します。zがマイナス10未満または10より大きい極端な値の場合、それぞれCDFを0または1に設定してtrueを返します。それ以外の場合は、tと確率密度dに基づく多項式展開を用いて近似計算をおこない、zが正の場合に応じてCDFを調整したうえでtrueを返します。正規CDFグラフは次のようになります。

正規累積分布関数グラフ

次に、StudentCDF関数を実装します。これはt分布のCDFを近似するもので、後で定義するピアソン法やスピアマン法におけるp値計算に不可欠です。自由度df、値x、CDF結果を格納する参照を引数として受け取ります。まずdfの妥当性を確認し、不正であればfalseを返します。その後、パラメータaとbを計算し、不完全ベータ関数用のxtを求めます。MathBetaIncompleteを使ってibを取得し、ガンマ関数の対数を用いて完全ベータを計算し、正規化された不完全ベータ関数を導出します。xが0以上か負かに応じてCDFを調整し、最終的にtrueを返します。t分布のCDFグラフは次のようになります。

t分布累積分布関数(CDF)グラフ

続いて、calculate_pvalue関数を定義します。この関数は、相関係数、手法、サンプルサイズnに基づいてp値を計算します。nが3未満の場合は無効とみなし1を返します。CDF用の変数を初期化し、まずケンドール法を個別に処理します。sigmaを計算してゼロチェックをおこない、z値を導出し、NormalCDFを使って両側検定のp値を求めます。一方、ピアソン法またはスピアマン法の場合は、相関係数の二乗r2を計算し、完全相関であれば0を返します。その後、分母とt統計量を求め、StudentCDFを自由度dfで適用して両側検定のp値を取得します。次に必要なのは、銘柄のリストを配列にパースして扱えるようにする関数です。以下がそのロジックです。

//+------------------------------------------------------------------+
//| 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
         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
         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
   }
}

ここでは、ユーザーが指定した銘柄一覧をダッシュボードで扱える配列に変換するためにparse_symbols関数を実装します。まず、入力であるSymbolsListを一時的な文字列変数にコピーし、num_symbolsカウンタを0にリセットします。その後、一時文字列内にカンマが存在し、かつ銘柄数がMAX_SYMBOLSの上限未満である間ループを実行します。ループ内では、StringFindを使って次のカンマの位置を取得し、StringSubstrで先頭からその位置までの部分文字列を取り出して銘柄名を取得します。次に、SymbolSelectをtrue指定で呼び出して気配値表示に追加しつつ選択を試みます。成功した場合はsymbols_arrayの現在インデックスに格納し、num_symbolsをインクリメントします。失敗した場合は、その銘柄が利用できないことを警告メッセージとして出力します。その後、StringSubstrを再度使用してカンマ以降の残りの文字列を一時変数に更新します。

ループ終了後、一時文字列にまだテキストが残っている場合は、それが最後の銘柄であることを意味します。この場合も同様に、上限未満であればSymbolSelectで選択を試み、成功すれば配列に格納し、失敗すれば警告を出力します。最後に、有効な銘柄が2つ未満しか取得できなかった場合、相関行列の計算には最低2銘柄が必要であるため、エラーメッセージを表示してExpertRemoveでプログラムを終了します。これにより、不正な入力状態で処理が続行されるのを防ぎます。配列を出力すると、次の結果が得られます。

解析済み銘柄配列

それでは、計算に使用する統計関数を定義する作業に移りましょう。

//+------------------------------------------------------------------+
//| Rank data for Spearman correlation                               |
//+------------------------------------------------------------------+
void rank_data(const double &data[], double &ranks[]) {
   int size = ArraySize(data);                                  //--- Get data size
   int indices[];                                               //--- Declare indices array
   ArrayResize(indices, size);                                  //--- Resize indices
   for (int i = 0; i < size; i++) indices[i] = i;               //--- Initialize indices

   // Sort indices based on data values
   for (int i = 0; i < size - 1; i++) {                         //--- Loop outer for sorting
      for (int j = i + 1; j < size; j++) {                      //--- Loop inner for comparison
         if (data[indices[i]] > data[indices[j]]) {             //--- Check if swap needed
            int temp = indices[i];                              //--- Store temporary
            indices[i] = indices[j];                            //--- Swap indices
            indices[j] = temp;                                  //--- Complete swap
         }
      }
   }

   // Assign ranks, handling ties
   for (int i = 0; i < size; ) {                                //--- Loop through sorted indices
      int start = i;                                            //--- Set start of tie group
      double value = data[indices[i]];                          //--- Get current value
      while (i < size && data[indices[i]] == value) i++;        //--- Skip ties
      double rank = (start + i - 1) / 2.0 + 1.0;                //--- Compute average rank
      for (int k = start; k < i; k++) {                         //--- Assign rank to group
         ranks[indices[k]] = rank;                              //--- Set rank
      }
   }
}

//+------------------------------------------------------------------+
//| Calculate Pearson correlation                                    |
//+------------------------------------------------------------------+
double pearson_correlation(const double &deltas1[], const double &deltas2[], int size) {
   double mean1 = 0, mean2 = 0;                                 //--- Initialize means
   for (int i = 0; i < size; i++) {                             //--- Loop to compute sums
      mean1 += deltas1[i];                                      //--- Accumulate first deltas
      mean2 += deltas2[i];                                      //--- Accumulate second deltas
   }
   mean1 /= size;                                               //--- Compute first mean
   mean2 /= size;                                               //--- Compute second mean

   double var1 = 0, var2 = 0, cov = 0;                          //--- Initialize variances and covariance
   for (int i = 0; i < size; i++) {                             //--- Loop to compute deviations
      double dev1 = deltas1[i] - mean1;                         //--- Compute first deviation
      double dev2 = deltas2[i] - mean2;                         //--- Compute second deviation
      var1 += dev1 * dev1;                                      //--- Accumulate first variance
      var2 += dev2 * dev2;                                      //--- Accumulate second variance
      cov += dev1 * dev2;                                       //--- Accumulate covariance
   }
   if (var1 == 0 || var2 == 0) return 0.0;                      //--- Return zero if no variance
   return cov / MathSqrt(var1 * var2);                          //--- Return correlation
}

//+------------------------------------------------------------------+
//| Calculate Spearman correlation                                   |
//+------------------------------------------------------------------+
double spearman_correlation(const double &deltas1[], const double &deltas2[], int size) {
   double ranks1[], ranks2[];                                   //--- Declare rank arrays
   ArrayResize(ranks1, size);                                   //--- Resize first ranks
   ArrayResize(ranks2, size);                                   //--- Resize second ranks
   rank_data(deltas1, ranks1);                                  //--- Rank first deltas
   rank_data(deltas2, ranks2);                                  //--- Rank second deltas
   return pearson_correlation(ranks1, ranks2, size);            //--- Return Pearson on ranks
}

//+------------------------------------------------------------------+
//| Calculate Kendall correlation                                    |
//+------------------------------------------------------------------+
double kendall_correlation(const double &deltas1[], const double &deltas2[], int size) {
   int concordant = 0, discordant = 0;                          //--- Initialize pair counts
   for (int i = 0; i < size - 1; i++) {                         //--- Loop outer pairs
      for (int j = i + 1; j < size; j++) {                      //--- Loop inner pairs
         double sign1 = deltas1[i] - deltas1[j];                //--- Compute first sign
         double sign2 = deltas2[i] - deltas2[j];                //--- Compute second sign
         if (sign1 * sign2 > 0) concordant++;                   //--- Increment concordant
         else if (sign1 * sign2 < 0) discordant++;              //--- Increment discordant
         // Ties are ignored in basic Kendall tau
      }
   }
   int total_pairs = size * (size - 1) / 2;                     //--- Compute total pairs
   if (total_pairs == 0) return 0.0;                            //--- Return zero if no pairs
   return (concordant - discordant) / (double)total_pairs;      //--- Return tau
}

まず、rank_data関数を実装します。この関数はデータ配列の各値に順位を付けるものであり、スピアマンの順位相関において非パラメトリックな順位処理をおこなうために重要です。引数としてデータ配列への定数参照と、順位配列への参照を受け取ります。まずArraySizeでサイズを取得し、indices配列を宣言して同じサイズにリサイズし、0からsize-1までの連番で初期化します。その後、バブルソートによってデータ値に基づきindicesを並び替えます。現在のインデックスのデータ値が次のインデックスの値より大きい場合にスワップします。ソート後、並び替えられたインデックスによって順位を付与します。同値のグループが存在する場合はその範囲を特定し、(start + end - 1) / 2.0 + 1.0の式で平均順位を計算し、その順位をインデックスを通じて元の位置へ反映します。

次に、pearson_correlation関数を定義します。この関数は、指定されたサイズの価格デルタ配列2つの間でピアソン相関係数を計算します。まず両配列の平均値を0で初期化し、ループで合計を計算した後、sizeで割って平均を求めます。その後、分散と共分散を0で初期化し、再度ループ処理で平均からの偏差を計算します。分散は偏差の二乗和、共分散は偏差の積として累積されます。いずれかの分散が0の場合はゼロ除算を防ぐため0.0を返します。それ以外の場合は、共分散を分散の積の平方根(MathSqrtを使用)で割って結果を返します。続いて、スピアマンの順位相関係数を計算するためのspearman_correlation関数を定義します。ここでは2つのrank配列を宣言して入力サイズにリサイズし、それぞれのdeltas配列に対してrank_dataを呼び出して順位を生成します。その後、元のデータではなく順位配列を用いてpearson_correlationを実行し、その結果を返します。

最後に、ケンドールの順位相関係数のためのkendall_correlation関数を実装します。concordant(順一致)とdiscordant(逆一致)のカウンタを0で初期化します。二重ループを用いてすべてのデータペアを比較し、それぞれの差分の符号を計算します。両配列で符号の積が正ならconcordantを増加、負ならdiscordantを増加し、同値は無視します。総ペア数はsize × (size - 1) / 2で計算します。ペアが存在しない場合は0.0を返し、それ以外では(concordant - discordant) / total_pairsを返します。通常の二変量分布の比較では、これらは以下のように表されます。

ピアソン、スピアマン、ケンドールの二変量比較

これらの分析関数を用いることで、標準化された相関計算のためのヘルパー関数を次のように構築できます。

//+------------------------------------------------------------------+
//| Calculate correlation based on method                            |
//+------------------------------------------------------------------+
double calculate_correlation(string sym1, string sym2) {
   if (sym1 == sym2) return 1.0;                                //--- Return self-correlation

   double prices1[], prices2[];                                 //--- Declare price arrays
   ArrayResize(prices1, CorrelationBars);                       //--- Resize first prices
   ArrayResize(prices2, CorrelationBars);                       //--- Resize second prices
   if (CopyClose(sym1, global_correlation_tf, 0, CorrelationBars, prices1) < CorrelationBars || //--- Copy first closes or check failure
       CopyClose(sym2, global_correlation_tf, 0, CorrelationBars, prices2) < CorrelationBars) {
      return 0.0;                                               //--- Return insufficient data
   }

   // Compute price changes (deltas)
   double deltas1[], deltas2[];                                 //--- Declare delta arrays
   ArrayResize(deltas1, CorrelationBars - 1);                   //--- Resize first deltas
   ArrayResize(deltas2, CorrelationBars - 1);                   //--- Resize second deltas
   for (int i = 0; i < CorrelationBars - 1; i++) {              //--- Loop to compute deltas
      deltas1[i] = prices1[i + 1] - prices1[i];                 //--- Set first delta
      deltas2[i] = prices2[i + 1] - prices2[i];                 //--- Set second delta
   }

   int size = CorrelationBars - 1;                              //--- Set effective size
   switch (CorrMethod) {                                        //--- Switch on method
      case PEARSON:                                             //--- Handle Pearson
         return pearson_correlation(deltas1, deltas2, size);    //--- Return Pearson result
      case SPEARMAN:                                            //--- Handle Spearman
         return spearman_correlation(deltas1, deltas2, size);   //--- Return Spearman result
      case KENDALL:                                             //--- Handle Kendall
         return kendall_correlation(deltas1, deltas2, size);    //--- Return Kendall result
      default:                                                  //--- Handle default
         return 0.0;                                            //--- Return zero
   }
}

//+------------------------------------------------------------------+
//| Update correlation matrix values                                 |
//+------------------------------------------------------------------+
void update_correlations() {
   int n = CorrelationBars - 1;                                 //--- Set sample size
   if (n < 2) {                                                 //--- Check minimum bars
      Print("Error: Insufficient bars for correlation (need at least 3)."); //--- Print error
      return;                                                   //--- Exit function
   }
   for (int i = 0; i < num_symbols; i++) {                      //--- Loop rows
      for (int j = 0; j < num_symbols; j++) {                   //--- Loop columns
         double corr = calculate_correlation(symbols_array[i], symbols_array[j]); //--- Compute correlation
         correlation_matrix[i][j] = corr;                       //--- Store correlation
         if (i == j) {                                          //--- Handle diagonal
            pvalue_matrix[i][j] = 0.0;                          //--- Set p-value to zero
         } else if (corr == 0.0 && n < 3) {                     //--- Handle insufficient data
            pvalue_matrix[i][j] = 1.0;                          //--- Set p-value to one
         } else {                                               //--- Handle normal case
            pvalue_matrix[i][j] = calculate_pvalue(corr, CorrMethod, n); //--- Compute and store p-value
         }
      }
   }
}

calculate_correlation関数を定義します。この関数は2つの銘柄間の相関係数を計算し、double型の値を返します。引数としてsym1とsym2の文字列を受け取ります。両者が同一であれば、それは完全な自己相関を意味するため、即座に1.0を返します。次に、CorrelationBarsのサイズに合わせてprices1とprices2の2つのdouble配列を宣言しリサイズします。CopyCloseを使用して、それぞれの銘柄の終値をglobal_correlation_tf時間足から取得し、開始位置は現在のバー0とします。どちらかの取得が必要な本数を満たさない場合は、データ不足として0.0を返します。

価格変化に着目するために、deltas1とdeltas2のデルタ配列をCorrelationBars-1のサイズで宣言しリサイズします。その後、ループを用いて連続する価格差を計算し、各デルタを格納します。実効サイズはCorrelationBars - 1とします。続いてCorrMethod列挙型に基づくswitch文を使用し、適切な相関関数を呼び出します。PEARSONの場合はpearson_correlationにデルタ配列とサイズを渡して計算します。SPEARMANの場合はspearman_correlationを呼び出します。KENDALLについては、kendall_correlationを呼び出します。どれにも該当しない場合は0.0を返します。

次に、update_correlations関数を実装します。この関数は相関行列とp値行列を最新の計算結果で更新します。サンプルサイズnをCorrelationBars - 1として計算し、これが2未満の場合は相関計算には最低3本のバーが必要であるためエラーメッセージを出力し、処理を終了します。その後、num_symbolsを用いた二重ループ処理で、行iと列jの組み合わせごとに処理します。calculate_correlationを呼び出してsymbols_arrayから対応する銘柄の相関値corrを取得し、それをcorrelation_matrix[i][j]に格納します。p値の計算では、iとjが同じ場合は対角要素としてpvalue_matrix[i][j]に0.0を設定します。corrが0.0かつnが3未満の場合は1.0を設定します。それ以外の場合はcalculate_pvalueを使用し、corr、CorrMethod、nを渡して計算し、その結果をpvalue_matrix[i][j]に格納します。次に必要なのは、ヒートマップ表示のためのヘルパー関数です。

//+------------------------------------------------------------------+
//| Get significance stars based on p-value                          |
//+------------------------------------------------------------------+
string get_significance_stars(double pval) {
   if (pval < PValueThreshold1) return "***";                   //--- Return three stars
   if (pval < PValueThreshold2) return "**";                    //--- Return two stars
   if (pval < PValueThreshold3) return "*";                     //--- Return one star
   return "";                                                   //--- Return empty
}

//+------------------------------------------------------------------+
//| Interpolate between multiple colors based on value (-1 to 1)     |
//+------------------------------------------------------------------+
color interpolate_heatmap_color(double value) {
   if (value == 0.0) return ColorNeutralBg;                     //--- Return neutral for zero
   double abs_val = MathAbs(value);                             //--- Compute absolute value
   int num_stops = ArraySize(heatmap_colors) / 2;               //--- Compute stops per side
   double step = 1.0 / (num_stops - 1);                         //--- Compute step size

   if (value > 0.0) {                                           //--- Handle positive
      int idx = (int)MathFloor(abs_val / step);                 //--- Compute index
      if (idx >= num_stops - 1) idx = num_stops - 2;            //--- Clamp index
      double factor = (abs_val - idx * step) / step;            //--- Compute factor
      return interpolate_color(heatmap_colors[idx + num_stops], heatmap_colors[idx + num_stops + 1], factor); //--- Interpolate positive
   } else {                                                     //--- Handle negative
      int idx = (int)MathFloor(abs_val / step);                 //--- Compute index
      if (idx >= num_stops - 1) idx = num_stops - 2;            //--- Clamp index
      double factor = (abs_val - idx * step) / step;            //--- Compute factor
      return interpolate_color(heatmap_colors[idx], heatmap_colors[idx + 1], factor); //--- Interpolate negative
   }
}

//+------------------------------------------------------------------+
//| Interpolate between two colors based on factor (0 to 1)          |
//+------------------------------------------------------------------+
color interpolate_color(color c1, color c2, double factor) {
   uchar r1 = (uchar)(c1 & 0xFF), g1 = (uchar)((c1 >> 8) & 0xFF), b1 = (uchar)((c1 >> 16) & 0xFF); //--- Extract RGB from first color
   uchar r2 = (uchar)(c2 & 0xFF), g2 = (uchar)((c2 >> 8) & 0xFF), b2 = (uchar)((c2 >> 16) & 0xFF); //--- Extract RGB from second color
   uchar r = (uchar)MathMax(0, MathMin(255, r1 + factor * (r2 - r1) + 0.5)); //--- Interpolate red
   uchar g = (uchar)MathMax(0, MathMin(255, g1 + factor * (g2 - g1) + 0.5)); //--- Interpolate green
   uchar b = (uchar)MathMax(0, MathMin(255, b1 + factor * (b2 - b1) + 0.5)); //--- Interpolate blue
   return (color)((b << 16) | (g << 8) | r);                    //--- Return interpolated color
}

get_significance_stars関数を定義します。この関数は、与えられたp値に基づいて統計的有意性を示すアスタリスク記号の数を判定し、string型として返します。引数としてdouble型のpvalを受け取ります。条件分岐は以下の通りです。まずpvalがPValueThreshold1より小さい場合、最も高い有意性を示す「***」を返します。次にpvalがPValueThreshold2より小さい場合は「**」を返します。さらにPValueThreshold3より小さい場合は「*」を返します。それ以外の場合は、有意性がないため空文字列を返します。

次に、interpolate_heatmap_color関数を実装します。この関数は、相関値(-1から1の範囲)に基づいてヒートマップ用の色をグラデーション配列から補間し、color型として返します。値が0の場合は特別扱いとしてColorNeutralBgをそのまま返します。まずabs_valとして絶対値を計算し、heatmap_colors配列のサイズを2で割ることで正負それぞれのカラーストップ数を求めます。さらにstepサイズは1を(stops - 1)で割って算出します。正の値の場合、MathFloorを使ってabs_val/stepからインデックスを求め、最終区間を超えないようにクランプします。その後、stepに対する余りから補間係数factorを算出し、カラー配列の正の半分から隣接する2色を選んでinterpolate_color関数に渡し、補間結果を得ます。負の値の場合も同様の処理をおこないますが、カラースケールの負の半分を使用します。

続いてinterpolate_color関数を定義し、0から1のfactorに基づいて2色を線形補間します。まずc1とc2からビット演算(0xFFマスク、8ビット右シフト、16ビット右シフト)をによってRGB成分をunsigned charとして抽出します。各チャンネルはc1から開始し、(c2-c1)*factorを加算して補間し、0.5を加えることで丸め処理をおこない、MathMaxMathMinで0〜255の範囲に制限します。最後に、補間されたRGB値をビットシフトで再構成し、blueを16ビット左シフト、greenを8ビット左シフト、redをそのまま保持してビットOR結合し、最終的なcolor値として返します。計算系の主要なヘルパー関数は揃ったので、ダッシュボード本体の構築に進むことができます。

//+------------------------------------------------------------------+
//| Create rectangle for UI                                          |
//+------------------------------------------------------------------+
bool create_rectangle(string object_name, int x_distance, int y_distance, int x_size, int y_size, 
                      color background_color, color border_color = clrNONE) {
   if (!ObjectCreate(0, object_name, OBJ_RECTANGLE_LABEL, 0, 0, 0)) {        //--- Create rectangle or check failure
      Print(__FUNCTION__, ": failed to create Rectangle: ", GetLastError()); //--- Print error
      return false;                                                          //--- Return failure
   }
   ObjectSetInteger(0, object_name, OBJPROP_XDISTANCE, x_distance);          //--- Set x distance
   ObjectSetInteger(0, object_name, OBJPROP_YDISTANCE, y_distance);          //--- Set y distance
   ObjectSetInteger(0, object_name, OBJPROP_XSIZE, x_size);                  //--- Set x size
   ObjectSetInteger(0, object_name, OBJPROP_YSIZE, y_size);                  //--- Set y size
   ObjectSetInteger(0, object_name, OBJPROP_CORNER, CORNER_LEFT_UPPER);      //--- Set corner
   ObjectSetInteger(0, object_name, OBJPROP_BGCOLOR, background_color);      //--- Set background
   ObjectSetInteger(0, object_name, OBJPROP_COLOR, border_color);            //--- Set border color
   ObjectSetInteger(0, object_name, OBJPROP_BORDER_TYPE, BORDER_FLAT);       //--- Set border type
   ObjectSetInteger(0, object_name, OBJPROP_BACK, false);                    //--- Set back property
   return true;                                                              //--- Return success
}

//+------------------------------------------------------------------+
//| Create text label for UI                                         |
//+------------------------------------------------------------------+
bool create_label(string object_name, string text, int x_distance, int y_distance, int font_size = 10, 
                  color text_color = COLOR_WHITE, string font = "Arial Rounded MT Bold") {
   if (!ObjectCreate(0, object_name, OBJ_LABEL, 0, 0, 0)) {                 //--- Create label or check failure
      Print(__FUNCTION__, ": failed to create Label: ", GetLastError());    //--- Print error
      return false;                                                         //--- Return failure
   }
   ObjectSetInteger(0, object_name, OBJPROP_XDISTANCE, x_distance);         //--- Set x distance
   ObjectSetInteger(0, object_name, OBJPROP_YDISTANCE, y_distance);         //--- Set y distance
   ObjectSetInteger(0, object_name, OBJPROP_CORNER, CORNER_LEFT_UPPER);     //--- Set corner
   ObjectSetString(0, object_name, OBJPROP_TEXT, text);                     //--- Set text
   ObjectSetString(0, object_name, OBJPROP_FONT, font);                     //--- Set font
   ObjectSetInteger(0, object_name, OBJPROP_FONTSIZE, font_size);           //--- Set font size
   ObjectSetInteger(0, object_name, OBJPROP_COLOR, text_color);             //--- Set text color
   ObjectSetInteger(0, object_name, OBJPROP_ANCHOR, ANCHOR_CENTER);         //--- Set anchor
   return true;                                                             //--- Return success
}


//+------------------------------------------------------------------+
//| Create full dashboard UI                                         |
//+------------------------------------------------------------------+
void create_full_dashboard() { 
   color main_bg = C'30,30,30';                                 //--- Set main background
   color header_bg = C'60,60,60';                               //--- Set header background
   color text_color = COLOR_WHITE;                              //--- Set text color
   color neutral_bg = ColorNeutralBg;                           //--- Set neutral background
   color button_text = clrGold;                                 //--- Set button text color
   color theme_icon_color = clrWhite;                           //--- Set theme icon color
   color close_text = clrWhite;                                 //--- Set close text color
   color header_icon_color = 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
   create_label(TOGGLE_BUTTON, CharToString('r'), panel_x + (panel_width - 47), panel_y + 14, 18, button_text, "Wingdings"); //--- Create toggle button
   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
   create_label(PVAL_BUTTON, CharToString('X'), panel_x + (panel_width - 107), panel_y + 14, 18, button_text, "Wingdings"); //--- Create PVAL button
   string sort_icon = CharToString('N');                        //--- Set sort icon
   create_label(SORT_BUTTON, sort_icon, panel_x + (panel_width - 137), panel_y + 14, 18, button_text, "Wingdings 3"); //--- Create sort button
   create_label(THEME_BUTTON, CharToString('['), panel_x + (panel_width - 167), panel_y + 14, 18, theme_icon_color, "Wingdings"); //--- Create theme button

   // Timeframe cells row
   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
   }

   // Create row symbols (left column), pushed down
   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
   }

   // Create column symbols (top row), pushed down
   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
   }

   // Create correlation cells, pushed down
   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
      }
   }

   ChartRedraw(0);                                              //--- Redraw chart
}

ここでは、UI用の矩形グラフィカルオブジェクトを生成するcreate_rectangle関数を定義し、成功可否を示すbooleanを返します。この関数は、オブジェクト名(文字列)、x座標とy座標(整数)、サイズ、背景色、そしてデフォルトで未設定となる任意の枠線色を引数として受け取ります。ObjectCreateを使用してオブジェクトの生成を試みます。サブウィンドウ0にOBJ_RECTANGLE_LABELタイプとして作成し、デフォルト座標を指定します。失敗した場合は、関数名とエラーコードを含むエラーメッセージを出力し、falseを返します。成功した場合はObjectSetIntegerをによってプロパティを設定します。x座標とy座標、サイズ、左上コーナー(CORNER_LEFT_UPPER)、背景色、枠線色、枠線タイプ(フラット)、backをfalseに設定し、最後にtrueを返します。

次に、UI用のテキストラベルを生成するcreate_label関数を実装します。この関数も成功可否を示すbooleanを返します。引数としてオブジェクト名とテキスト(文字列)、x座標とy座標、任意のフォントサイズ(デフォルト10)、文字色(デフォルト白)、フォント名(デフォルトArial Rounded MT Bold)を受け取ります。ObjectCreateでOBJ_LABELタイプのオブジェクトを生成し、失敗した場合はエラーを出力します。成功した場合はx座標とy座標、コーナー(CORNER_LEFT_UPPER)、テキスト内容、フォント、フォントサイズ、文字色、アラインメント(中央)をObjectSetIntegerおよびObjectSetStringで設定し、trueを返します。

次に、create_full_dashboard関数を定義し、上記のヘルパー関数でUI全体を構築します。まずローカルカラーを定義します。メイン背景はダークグレー、ヘッダー背景はミディアムグレー、テキスト色は白、ニュートラル背景は入力値から取得、ボタンテキストはゴールド、テーマおよびクローズアイコンは白、ヘッダーアイコンはアクアとします。次にパネルサイズを計算します。幅は銘柄セルの幅と銘柄数から決定し、高さはヘッダー、時間足行、ギャップ、行列部分を合計して算出します。

create_rectangleでメインパネルとヘッダーパネルを作成し、その後create_labelで各要素を生成します。たとえばヘッダーアイコンはWingdingsの文字181、タイトルは相関行列、クローズボタンはWebdingsの'r'、トグルボタンも同様、ヒートマップボタンは表示モードに応じてCharToStringとucharキャストを用いた動的アイコン、p値ボタンはWingdingsの'X'、ソートボタンはWingdings 3の'N'、テーマボタンはWingdingsの'['を使用します。記号フォントのリストから任意に選択できます。

記号フォント

時間足行ではヘッダーパネルの下のy位置と開始x位置を計算し、表示される時間足数に応じてループ処理をおこない、各要素ごとに矩形とラベルを生成します。オフセットを算出し、時間足セル用の接頭辞とインデックス文字列を結合してオブジェクト名を形成します。現在のインデックスに基づき、強い正の相関色またはヘッダーカラーを条件的に背景色として設定し、Arial Boldで時間足文字列をラベル表示します。次に行列領域のy位置を時間足行の下にギャップ分を加えて設定します。行ヘッダー用の矩形を作成し、ラベルとして銘柄を設定します。左側の銘柄行ではループにより各行のyオフセットを計算し、行接頭辞とインデックスを用いて矩形を生成し、銘柄名配列から取得した値をArial Boldでラベル表示します。同様に銘柄列では上部でxオフセットを計算し、列接頭辞とインデックスを用いて矩形を生成し、対応する銘柄名をラベル表示します。

相関セルの生成では行と列の二重ループを用い、各セルのxおよびyオフセットを計算します。矩形接頭辞と行列インデックスをアンダースコアで結合してセル名を生成し、テキスト名も同様に作成します。その後create_rectangleでニュートラル背景のセル矩形を生成し、create_labelで初期値0.00をArialフォントで表示します。最後に、ChartRedrawをサブウィンドウ0で呼び出し、チャート全体を再描画します。この関数はOnInitイベントハンドラ内で呼び出すことで、ダッシュボード全体の初期構築処理を実行できます。

//+------------------------------------------------------------------+
//| Initialize expert                                                |
//+------------------------------------------------------------------+
int OnInit() {
   global_display_mode = DashboardMode;                         //--- Set display mode
   global_correlation_tf = (CorrelationTimeframe == PERIOD_CURRENT ? (ENUM_TIMEFRAMES)_Period : CorrelationTimeframe); //--- Set timeframe
   for (int i = 0; i < NUM_TF; i++) {                           //--- Loop to find index
      if (tf_list[i] == global_correlation_tf) {                //--- Check match
         current_tf_index = i;                                  //--- Set index
         break;                                                 //--- Exit loop
      }
   }
   if (current_tf_index == -1) current_tf_index = 3;            //--- Default to H1
   global_correlation_tf = tf_list[current_tf_index];           //--- Update timeframe
   parse_symbols();                                             //--- Parse symbols
   int panel_width = WIDTH_SYMBOL + num_symbols * (WIDTH_CELL - 1) + 4; //--- Compute width
   num_tf_visible = MathMin(NUM_TF, (panel_width - 2) / WIDTH_TF_CELL); //--- Set visible TFs
   if (current_tf_index >= num_tf_visible) current_tf_index = num_tf_visible - 1; //--- Clamp index
   global_correlation_tf = tf_list[current_tf_index];           //--- Update timeframe
   ArrayInitialize(pvalue_matrix, 1.0);                         //--- Initialize p-values
   create_full_dashboard();                                     //--- Create dashboard
   return(INIT_SUCCEEDED);                                      //--- Return success
}

OnInitイベントハンドラ内では、プログラムのランタイム設定とUIの初期化をおこないます。まずglobal_display_modeに入力値DashboardModeを設定し、global_correlation_tfを決定します。CorrelationTimeframeがPERIOD_CURRENTの場合は現在のチャート周期_PeriodENUM_TIMEFRAMESにキャストし、それ以外の場合は入力値をそのまま使用します。次にtf_list配列をループし、global_correlation_tfと一致するインデックスを探索してcurrent_tf_indexに格納し、一致した時点でループを終了します。一致が見つからずcurrent_tf_indexが-1のままの場合は、デフォルトとして3を設定し、これは時間足の1時間に対応します。その後、一貫性を保つためglobal_correlation_tfをtf_list内の該当インデックス値で更新します。

続いてparse_symbols関数を呼び出して銘柄リストを処理します。panel_widthはWIDTH_SYMBOLに加えて銘柄数とWIDTH_CELLの積による調整値で計算します。num_tf_visibleはNUM_TFと、パネル幅をWIDTH_TF_CELLで割った値のうち小さい方を採用します。current_tf_indexが表示可能範囲を超えている場合は、最後の表示可能インデックスにクランプし、その後global_correlation_tfも再更新します。その後pvalue_matrix配列をArrayInitializeで1.0に初期化し、計算前のデフォルト状態を設定します。最後にcreate_full_dashboardを呼び出してUI全体を構築し、INIT_SUCCEEDEDを返して初期化成功を示します。コンパイルすると、次の結果が得られます。

初期ダッシュボード

画像から確認できる通り、ダッシュボードは正常に生成されています。次におこなうべき拡張は、レジェンドパネルを追加し、ダッシュボードと同時に生成されるように統合することです。

//+------------------------------------------------------------------+
//| Recreate legend objects based on current mode                    |
//+------------------------------------------------------------------+
void recreate_legend() {
   // Delete existing legend objects
   for (int i = 0; i < NUM_LEGEND_ITEMS; i++) {                 //--- Loop legend items
      ObjectDelete(0, LEGEND_CELL_RECTANGLE + IntegerToString(i)); //--- Delete rectangle
      ObjectDelete(0, LEGEND_CELL_TEXT + IntegerToString(i));   //--- Delete text
   }

   // Define full correlation values based on mode (more points for finer legend)
   double full_corr_vals[];                                     //--- Declare full values array
   if (global_display_mode == MODE_HEATMAP) {                   //--- Handle heatmap mode
      double heatmap_vals[] = {-1.0, -0.8, -0.6, -0.4, -0.2, 0.0, 0.2, 0.4, 0.6, 0.8, 1.0}; //--- Set heatmap values
      ArrayCopy(full_corr_vals, heatmap_vals);                  //--- Copy to full
   } else {                                                     //--- Handle standard mode
      double standard_vals[] = {StrongNegativeThresholdPct / 100.0, -0.75, -0.5, -0.25, 0.0, 0.25, 0.5, 0.75, StrongPositiveThresholdPct / 100.0, 1.0}; //--- Set standard values
      ArrayCopy(full_corr_vals, standard_vals);                 //--- Copy to full
   }

   // Copy to visible and reduce dynamically if needed
   ArrayCopy(visible_corr_vals, full_corr_vals);                //--- Copy to visible
   int panel_width = WIDTH_SYMBOL + num_symbols * (WIDTH_CELL - 1) + 4; //--- Compute panel width
   int available_width = panel_width - 4;                       //--- Compute available width
   double item_cost = WIDTH_LEGEND_CELL + LEGEND_SPACING;       //--- Compute item cost
   int max_fit = (int)MathFloor((available_width + LEGEND_SPACING) / item_cost); //--- Compute max fit
   if (max_fit < 1) max_fit = 1;                                //--- Ensure minimum fit

   while (ArraySize(visible_corr_vals) > max_fit) {             //--- Loop to reduce
      int sz = ArraySize(visible_corr_vals);                    //--- Get current size
      int zero_pos = -1;                                        //--- Initialize zero position
      for (int p = 0; p < sz; p++) {                            //--- Loop to find zero
         if (visible_corr_vals[p] == 0.0) {                     //--- Check for zero
            zero_pos = p;                                       //--- Set position
            break;                                              //--- Exit loop
         }
      }
      if (zero_pos >= 0) {                                      //--- Handle found zero
         ArrayRemove(visible_corr_vals, zero_pos, 1);           //--- Remove zero
         continue;                                              //--- Continue reduction
      }

      // Find min abs > 0
      double min_abs = DBL_MAX;                                 //--- Initialize min abs
      for (int p = 0; p < sz; p++) {                            //--- Loop to find min
         double av = MathAbs(visible_corr_vals[p]);             //--- Get absolute
         if (av > 0 && av < min_abs) min_abs = av;              //--- Update min
      }
      if (min_abs == DBL_MAX) break;                            //--- Exit if no more

      // Remove all == ±min_abs
      for (int p = sz - 1; p >= 0; p--) {                       //--- Loop backward to remove
         if (MathAbs(visible_corr_vals[p]) == min_abs) ArrayRemove(visible_corr_vals, p, 1); //--- Remove match
      }
   }

   num_legend_visible = ArraySize(visible_corr_vals);           //--- Set visible count

   // Calculate positions
   int panel_height = HEIGHT_HEADER + HEIGHT_TF_CELL + GAP_HEIGHT + HEIGHT_RECTANGLE * (num_symbols + 1) - num_symbols + 2; //--- Compute panel height
   int legend_y = panel_y + panel_height + GAP_MAIN_LEGEND;     //--- Compute legend y
   int total_legend_width = num_legend_visible * WIDTH_LEGEND_CELL + (num_legend_visible - 1) * LEGEND_SPACING; //--- Compute total width
   int x_start = panel_x + (panel_width - total_legend_width) / 2; //--- Compute start x

   color neutral_bg = ColorNeutralBg;                           //--- Set neutral background
   color text_color = COLOR_WHITE;                              //--- Set text color

   // Create new legend objects
   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
   }
}

//+------------------------------------------------------------------+
//| Create full dashboard UI                                         |
//+------------------------------------------------------------------+
void create_full_dashboard() { 

   //--- Other dashboard creation logic

   // Create separate legend panel
   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
}

recreate_legend関数では、現在の表示モードに基づいて凡例を動的に再構築し、パネルに収まるようにしつつ適切な相関値を反映させます。まず既存の凡例用の矩形およびテキストラベルを削除するため、接頭辞とインデックス文字列から構成される名前を用いてObjectDeleteをループで実行します。

次に完全な相関値配列を宣言し、条件に応じて初期化します。ヒートマップモードでは-1.0から1.0まで0.2刻みのローカル配列を作成し、それをコピーして使用します。標準モードではしきい値を小数に変換した固定値セットを使用し、たとえば-0.75などを含めます。表示領域に収めるため可視配列へコピーし、パネル幅を計算し、利用可能幅を算出します。さらにアイテムコスト(間隔を含む)から最大表示数を切り捨て除算で求め、少なくとも1を保証します。要素が多すぎる場合はwhileループで調整し、まず0が存在する場合はそれを削除し、それ以外の場合は絶対値が最小の非ゼロ値を走査して特定し、後ろからArrayRemoveを実行してすべて削除します。

その後可視数を更新後の配列サイズに設定します。配置処理ではパネル高さを定数と行数から算出し、凡例のy位置をパネルの下にギャップを加えて設定します。さらに凡例全体の幅をセル幅と間隔から算出し、中央配置となるよう開始x位置を決定します。色はニュートラル背景とテキスト色を使用します。ループ内では各凡例項目についてオフセットを計算し、名前を生成したうえでcreate_rectangleによってニュートラル背景の矩形を作成し、create_labelによってArialフォントで0%を初期値として表示します。create_full_dashboard関数では、他のインターフェースロジックの後にlegend_yを計算し、メイン背景で凡例パネル矩形を作成し、recreate_legendを呼び出してそれを生成し、チャートを再描画します。コンパイルすると、次の結果が得られます。

凡例付きパネル

凡例を追加したので、あとはダッシュボードと凡例を更新して、計算結果を反映させるだけです。

//+------------------------------------------------------------------+
//| Update TF highlights                                             |
//+------------------------------------------------------------------+
void update_tf_highlights() {
   color inactive_bg = 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
}

//+------------------------------------------------------------------+
//| Update legend colors and texts based on mode                     |
//+------------------------------------------------------------------+
void update_legend() {
   color default_txt = ColorTextStrong;                         //--- Set default text color
   for (int i = 0; i < num_legend_visible; i++) {               //--- Loop visible legends
      string rect_name = LEGEND_CELL_RECTANGLE + IntegerToString(i); //--- Get rectangle name
      string text_name = LEGEND_CELL_TEXT + IntegerToString(i); //--- Get text name
      double corr = visible_corr_vals[i];                       //--- Get correlation value
      int decimals = (MathAbs(corr) == 0.5 || corr == 0.0 || MathAbs(corr) == 1.0) ? 0 : 1; //--- Set decimals
      string txt_str = DoubleToString(corr * 100, decimals) + "%"; //--- Format text
      color bg_color = ColorNeutralBg;                          //--- Initialize background
      color txt_color = default_txt;                            //--- Initialize text color

      if (corr == 1.0) {                                        //--- Handle perfect positive
         bg_color = ColorDiagonalBg;                            //--- Set diagonal background
         txt_color = default_txt;                               //--- Set text color
      } else if (corr == 0.0) {                                 //--- Handle zero
         bg_color = ColorNeutralBg;                             //--- Set neutral background
         txt_color = default_txt;                               //--- Set text color
      } else {                                                  //--- Handle other values
         if (global_display_mode == MODE_STANDARD) {            //--- Handle standard mode
            double strong_pos = StrongPositiveThresholdPct / 100.0; //--- Set positive threshold
            double strong_neg = StrongNegativeThresholdPct / 100.0; //--- Set negative threshold
            if (corr >= strong_pos) {                           //--- Check strong positive
               bg_color = ColorStrongPositiveBg;                //--- Set positive background
               txt_color = default_txt;                         //--- Set text color
            } else if (corr <= strong_neg) {                    //--- Check strong negative
               bg_color = ColorStrongNegativeBg;                //--- Set negative background
               txt_color = default_txt;                         //--- Set text color
            } else {                                            //--- Handle mild
               bg_color = ColorNeutralBg;                       //--- Set neutral background
               txt_color = (corr > 0.0) ? ColorTextPositive : ColorTextNegative; //--- Set mild text color
            }
         } else {                                               //--- Handle heatmap mode
            txt_color = default_txt;                            //--- Set text color
            bg_color = interpolate_heatmap_color(corr);         //--- Interpolate background
         }
      }

      ObjectSetInteger(0, rect_name, OBJPROP_BGCOLOR, bg_color); //--- Update background
      ObjectSetString(0, text_name, OBJPROP_TEXT, txt_str);     //--- Update text
      ObjectSetInteger(0, text_name, OBJPROP_COLOR, txt_color); //--- Update text color
   }
   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 = 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 = DoubleToString(corr * 100, 1) + "%" + get_significance_stars(pval); //--- Format text
         color bg_color = ColorNeutralBg;                       //--- Initialize background
         color txt_color = ColorTextZero;                       //--- Initialize text color

         if (i == j) {                                          //--- Handle diagonal
            bg_color = 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 = ColorNeutralBg;                    //--- Set neutral background
                  if (corr > 0.0) {                             //--- Check positive mild
                     txt_color = ColorTextPositive;             //--- Set positive text
                  } else if (corr < 0.0) {                      //--- Check negative mild
                     txt_color = 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
      }
   }
   update_legend();                                              //--- Update legend
   ChartRedraw(0);                                               //--- Redraw chart
}

ここでは、update_tf_highlights関数を実装して、ダッシュボードで選択された時間足のセルを強調表示します。まず非アクティブ背景色として中間グレーを設定し、表示されている時間足数だけループ処理します。各要素について、TF_CELL_RECT接頭辞とインデックス文字列を用いて矩形名を生成し、current_tf_indexと一致する場合はColorStrongPositiveBgを背景色として設定し、一致しない場合は非アクティブ色を設定します。ObjectSetIntegerを用いてOBJPROP_BGCOLORプロパティを更新し、チャートを再描画します。

次にupdate_legend関数を定義し、モードに応じて凡例の色とテキストを更新します。デフォルトのテキスト色としてColorTextStrongを設定し、表示されている凡例項目数だけループします。各要素について、接頭辞とインデックスから矩形名とテキスト名を取得し、visible_corr_valsから相関値を取得します。絶対値チェックに基づいて表示桁数を決定し、DoubleToString関数を用いてパーセンテージ形式の文字列に整形します。

背景色とテキスト色を初期化した後、特殊ケースを処理します。値が1.0の場合は対角用背景を使用し、0.0の場合はニュートラル背景を使用し、いずれもデフォルトテキストを適用します。それ以外の場合、標準モードではパーセンテージから強い閾値を計算し、強い正、強い負、または中程度に応じて背景色とテキスト色を設定し、ゼロ以外では正、負、中立のテキストを使用します。ヒートマップモードではデフォルトテキストを使用し、interpolate_heatmap_color関数で背景色を補間します。オブジェクトセッターでOBJPROP_BGCOLOR、ObjectSetStringでOBJPROP_TEXT、OBJPROP_COLORを更新し、チャートを再描画します。

続いてupdate_dashboard関数を作成し、すべてのセルの相関と表示を更新します。まずupdate_correlationsを呼び出し、パーセンテージから強い閾値を計算し、ColorTextStrongからベーステキスト色を設定します。

銘柄の行列に対する二重ループ内で、相関値とp値を行列から取得し、get_significance_stars関数で有意性スターを取得してパーセンテージ文字列と結合します。色を初期化し、インデックスが一致する対角セルでは対角背景とベーステキストを使用します。非対角セルでは標準モードの場合、強い正、強い負、中程度に応じて背景色とテキスト色を設定し、正、負、ゼロに応じたテキストを使用します。ヒートマップモードではベーステキストを使用し、interpolate_heatmap_color関数による背景補間をおこないます。セッターを使用して、セル名とテキスト名を接頭辞とインデックスで作成し、OBJPROP_BGCOLORで背景を更新し、OBJPROP_TEXTでテキストコンテンツを更新し、OBJPROP_COLORでテキストの色を更新します。

最後にupdate_legendを呼び出し、チャートを再描画します。これらの関数は初期化およびティックイベント内で呼び出され、以下のように全体の効果を適用します。

//+------------------------------------------------------------------+
//| Initialize expert                                                |
//+------------------------------------------------------------------+
int OnInit() {
   global_display_mode = DashboardMode;                         //--- Set display mode
   global_correlation_tf = (CorrelationTimeframe == PERIOD_CURRENT ? (ENUM_TIMEFRAMES)_Period : CorrelationTimeframe); //--- Set timeframe
   for (int i = 0; i < NUM_TF; i++) {                           //--- Loop to find index
      if (tf_list[i] == global_correlation_tf) {                //--- Check match
         current_tf_index = i;                                  //--- Set index
         break;                                                 //--- Exit loop
      }
   }
   if (current_tf_index == -1) current_tf_index = 3;            //--- Default to H1
   global_correlation_tf = tf_list[current_tf_index];           //--- Update timeframe
   parse_symbols();                                             //--- Parse symbols
   int panel_width = WIDTH_SYMBOL + num_symbols * (WIDTH_CELL - 1) + 4; //--- Compute width
   num_tf_visible = MathMin(NUM_TF, (panel_width - 2) / WIDTH_TF_CELL); //--- Set visible TFs
   if (current_tf_index >= num_tf_visible) current_tf_index = num_tf_visible - 1; //--- Clamp index
   global_correlation_tf = tf_list[current_tf_index];           //--- Update timeframe
   ArrayInitialize(pvalue_matrix, 1.0);                         //--- Initialize p-values
   create_full_dashboard();                                     //--- Create dashboard
   update_tf_highlights();                                      //--- Update highlights
   update_dashboard();                                          //--- Update initial
   return(INIT_SUCCEEDED);                                      //--- Return success
}

//+------------------------------------------------------------------+
//| Handle tick event                                                |
//+------------------------------------------------------------------+
void OnTick() {
   update_dashboard();                                          //--- Update on tick
}

OnInitイベントハンドラ内では、時間足の可視化のためにupdate_tf_highlightsを呼び出し、初期データの生成のためにupdate_dashboardを呼び出し、INIT_SUCCEEDEDを返します。OnTickイベントハンドラでは、各新規ティックごとにcorrelationsとvisualsを更新するためにupdate_dashboardのみを呼び出します。すべてのバーごとに計算を処理したい場合は制御ロジックを追加できますが、現時点ではsymbolセットが少数であるため、そのままにしています。コンパイルすると、次の結果が得られます。

相関行列ダッシュボード(データ入力済み)済み)

画像から確認できるように、相関行列ダッシュボードは正しく構築されており、すべてのデータが適切に生成されています。これにより目的は達成されています。残るは、システムの動作確認、つまり前のセクションでおこなったテストです。


バックテスト

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

相関行列GIF


結論

MQL5相関行列ダッシュボードを開発しました。このシステムは、ユーザーが指定した銘柄間の関係をピアソン法、スピアマン法、ケンドール法の各手法を用いて計算し、設定可能な時間足およびバー数に基づいて処理し、信頼性のためにp値による有意性検定を組み込んでいます。このシステムは、閾値ベースの色分けと有意性レベルを示す星表示を備えた標準モード、および相関強度を視覚的に表現するためのグラデーション補間を用いたヒートマップモードをサポートしています。また、時間足選択ツール、モード切替ボタン、および動的レジェンドを備えたインタラクティブなUIを持ち、資産間の相互依存関係の分析を可能にします。次回からは、このダッシュボードをホバー可能およびドラッグ可能にし、クリック可能なアイコンがそれぞれの機能を実行するようにして、システムにインタラクティブ性を追加していきます。どうぞお楽しみに。

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

添付されたファイル |
最後のコメント | ディスカッションに移動 (1)
Cedric Olivier Kusiele Some
Cedric Olivier Kusiele Some | 18 1月 2026 において 17:39

こんにちは、アラン・ムネネ・ムティリア、

記事を通して素晴らしいアイデアを共有してくれてありがとう。お返しに、あなたのソースコードを 本番で使えるようにしました。

.ex5ファイルはモデレーターによって削除されました。

EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
Python-MetaTrader 5ストラテジーテスター(第4回):テスター入門 Python-MetaTrader 5ストラテジーテスター(第4回):テスター入門
シミュレーター上で初めての自動売買ロボットを構築し、MetaTrader 5のストラテジーテスター風にストラテジーテスト処理を実行します。その上で、カスタムシミュレーションで生成された結果を、普段使用しているターミナルの結果と比較します。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
MQL5入門(第35回):MQL5のAPIとWebRequest関数の習得(IX) MQL5入門(第35回):MQL5のAPIとWebRequest関数の習得(IX)
MetaTrader 5でユーザー操作を検出する方法、AI APIへリクエストを送信する方法、応答を抽出する方法を学び、パネルにスクロールテキストを実装します。