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

続いて、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を加えることで丸め処理をおこない、MathMaxとMathMinで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の場合は現在のチャート周期_PeriodをENUM_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)ビットマップ画像形式で示したものです。

結論
MQL5で相関行列ダッシュボードを開発しました。このシステムは、ユーザーが指定した銘柄間の関係をピアソン法、スピアマン法、ケンドール法の各手法を用いて計算し、設定可能な時間足およびバー数に基づいて処理し、信頼性のためにp値による有意性検定を組み込んでいます。このシステムは、閾値ベースの色分けと有意性レベルを示す星表示を備えた標準モード、および相関強度を視覚的に表現するためのグラデーション補間を用いたヒートマップモードをサポートしています。また、時間足選択ツール、モード切替ボタン、および動的レジェンドを備えたインタラクティブなUIを持ち、資産間の相互依存関係の分析を可能にします。次回からは、このダッシュボードをホバー可能およびドラッグ可能にし、クリック可能なアイコンがそれぞれの機能を実行するようにして、システムにインタラクティブ性を追加していきます。どうぞお楽しみに。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20945
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
Python-MetaTrader 5ストラテジーテスター(第4回):テスター入門
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
MQL5入門(第35回):MQL5のAPIとWebRequest関数の習得(IX)
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
こんにちは、アラン・ムネネ・ムティリア、
記事を通して素晴らしいアイデアを共有してくれてありがとう。お返しに、あなたのソースコードを 本番で使えるようにしました。
.ex5ファイルはモデレーターによって削除されました。