English Русский
preview
MQL5-Werkzeuge für den Handel (Teil 11): Dashboard einer Korrelationsmatrix (Pearson, Spearman, Kendall) mit Heatmap und Standardmodi

MQL5-Werkzeuge für den Handel (Teil 11): Dashboard einer Korrelationsmatrix (Pearson, Spearman, Kendall) mit Heatmap und Standardmodi

MetaTrader 5Handel |
21 1
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In unserem vorangegangenen Artikel (Teil 10) haben wir ein Strategie-Tracker-System in MetaQuotes Language 5 (MQL5) mit visuellen Ebenen und Erfolgsmetriken entwickelt, das gleitende Durchschnitts-Crossover-Signale erkennt, Trades mit mehreren Take-Profit-Levels und Stop-Losses verfolgt und die Ergebnisse auf dem Chart visualisiert. In Teil 11 entwickeln wir ein Korrelationsmatrix-Dashboard mit den Methoden von Pearson, Spearman, und Kendall mit einer Heatmap und Standardmodi. Dieses Dashboard berechnet die Beziehungen zwischen Vermögenswerten anhand der ausgewählten Methode über einen konfigurierbaren Zeitrahmen und Balken. Es unterstützt den Standardmodus mit Farbschwellen und p-Wert, Signifikanzsternen sowie den Heatmap-Modus mit Farbverlaufsdarstellungen. Ferner verfügt es über eine interaktive Nutzeroberfläche mit Zeitrahmenauswahl, Modusumschaltungen und einer dynamischen Legende. Wir werden die folgenden Themen behandeln:

  1. Verstehen des Dashboards der Korrelationsmatrix
  2. Implementation in MQL5
  3. Backtests
  4. Schlussfolgerung

Am Ende haben Sie ein funktionales MQL5-Korrelationsmatrix-Dashboard für die Analyse von Abhängigkeiten zwischen Assets, das Sie anpassen können – legen Sie los!


Verstehen des Dashboards der Korrelationsmatrix

Das Dashboard der Korrelationsmatrix analysiert Beziehungen zwischen Finanzanlagen durch die Berechnung von Korrelationskoeffizienten und hilft uns dabei, Abhängigkeiten zu identifizieren, die Einfluss auf Portfoliodiversifikation, Hedging oder Multi-Asset-Strategien haben. Es wertet Kursveränderungen bei vom Nutzer ausgewählten Wertpapieren über einen festgelegten Zeitraum und einen bestimmten Zeitrahmen aus und wendet dabei eine von drei statistischen Methoden an – Pearson für lineare Beziehungen, Spearman für rangbasierte monotone Zusammenhänge oder Kendall für die Übereinstimmung in Rangfolgen –, um zu quantifizieren, inwieweit sich Vermögenswerte parallel oder gegenläufig entwickeln. Die Signifikanz wird mit dem p-Wert bewertet, um die Zuverlässigkeit anzuzeigen, wobei visuelle Hinweise wie Farbschwellen oder Farbverläufe starke positive, starke negative, leichte oder neutrale Korrelationen hervorheben und eine schnelle Mustererkennung ohne manuelle Berechnungen ermöglichen.

Im Standardmodus verwendet das Dashboard vordefinierte Schwellenwerte, um Korrelationen zu kategorisieren. Dabei werden unterschiedliche Farben für stark positive oder negative Ergebnisse verwendet und Sterne für die Signifikanzniveaus der p-Werte hinzugefügt, um das statistische Vertrauen anzuzeigen. Der Heatmap-Modus verwendet einen kontinuierlichen Farbverlauf für eine feinere Visualisierung der Korrelationsintensitäten von negativ zu positiv, wodurch subtile Variationen besser sichtbar werden. Die Nutzeroberfläche enthält interaktive Elemente wie Zeitrahmenselektoren für den Wechsel von Analysezeiträumen, Schaltflächen für Modi oder Themen und eine dynamische Legende zur Interpretation von Farben und Werten, die alle in einem Raster mit Symbolen auf Achsen und Zellen zur Darstellung paarweiser Korrelationen angeordnet sind.

Unser Plan ist es, eine Liste von Symbolen zu analysieren, Korrelationen und p-Werte mit der gewählten Methode auf Preisdeltas zu berechnen, eine Nutzeroberfläche mit Panels für Überschriften, Zeitrahmen, Symbole, Zellen und Legenden zu erstellen und die Grafiken dynamisch auf der Grundlage von Modi und Schwellenwerten zu aktualisieren. Wir werden die Behandlung von Ereignissen für Interaktionen wie das Umschalten des Modus oder die Änderung des Zeitrahmens einbeziehen und sicherstellen, dass das Dashboard bei neuen Daten aktualisiert wird, um Einblicke in Echtzeit zu ermöglichen. Kurz gesagt: Hier ist eine visuelle Darstellung unserer Ziele.

DASHBOARD DER KORRELATIONSMATRIX


Implementation in MQL5

Um das Programm in MQL5 zu erstellen, öffnen wir den MetaEditor, gehen zum Navigator, suchen den Ordner „Experts“, klicken auf die Registerkarte „Neu“ und folgen den Anweisungen, um die Datei zu erstellen. Sobald sie erstellt ist, müssen wir in der Programmierumgebung einige Eingabeparameter und globale Variablen deklarieren, die wir im gesamten Programm verwenden werden.

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

Wir beginnen die Implementierung, indem wir die Math-Bibliothek mit „#include <Math\Stat\Math.mqh>“ einbinden, die statistische Funktionen bereitstellt, die für Korrelationsberechnungen wichtig sind. Als Nächstes definieren wir die Eingabeparameter, mit denen wir das Dashboard anpassen können. Dazu gehören die „SymbolsList“ als kommagetrennte Zeichenfolge für bis zu dreißig Symbole, „CorrelationTimeframe“ unter Verwendung der Enumeration ENUM_TIMEFRAMES für den Berechnungszeitraum, „CorrelationBars“ als Ganzzahl, die die Anzahl der Balken mit einem Minimum von zwanzig angibt, Schwellenwerte wie „StrongPositiveThresholdPct“ und „StrongNegativeThresholdPct“ in Prozent zur Kategorisierung von Korrelationen, p-Wert-Schwellenwerte wie „PValueThreshold1“ für Signifikanzniveaus sowie Farbeinstellungen wie „ColorStrongPositiveBg“, das für stark positive Hintergründe auf Limettengrün gesetzt ist, zusammen mit weiteren Einstellungen für negative, neutrale, diagonale und Textfarben.

Anschließend erstellen wir eine Enumeration für Konfigurationsoptionen. Die Enumeration „DisplayMode“ bietet „MODE_STANDARD“ für schwellenwertbasierte Anzeigen und „MODE_HEATMAP“ für Farbverlaufsdarstellungen, wobei „DashboardMode“ als Eingabe auf Standard eingestellt ist. In ähnlicher Weise bietet die Enumeration „CorrelationMethod“ „PEARSON“ für lineare Korrelationen, „SPEARMAN“ für rangbasierte Korrelationen und „KENDALL“ für Tau, wobei „CorrMethod“ als Eingabe standardmäßig auf Pearson eingestellt ist. Anschließend verwenden wir Define-Direktiven, um mit der #define Konstanten für Elemente der Nutzeroberfläche und das Layout festzulegen. Dazu gehören die Bezeichner wie „MAIN_PANEL“ für das Hauptrechteck, „HEADER_PANEL“ für die Kopfzeile und Präfixe wie „TF_CELL_RECT“ für Zeitrahmenzellen, zusammen mit Abmessungen wie „WIDTH_SYMBOL“ mit achtzig Pixeln, „HEIGHT_RECTANGLE“ mit dreißig Pixeln, Lücken wie „GAP_HEIGHT“ mit acht Pixeln und Farbdefinitionen wie „COLOR_WHITE“ mit dem Alias Weiß.

Wir deklarieren globale Variablen, um Zustand und Daten zu verwalten. Dazu gehören „panel_x“ und „panel_y“, die für die Position auf zwanzig oder vierzig initialisiert sind, „symbols_array“ als Zeichenfolgenarray mit der maximalen Symbolanzahl, „num_symbols“ beginnend bei Null sowie die zweidimensionalen Double-Arrays „correlation_matrix“ und „pvalue_matrix“ zum Speichern von Werten, „global_display_mode“ aus der Enumeration „display“, „global_correlation_tf“ aus „timeframes“, „current_tf_index“ bei minus eins sowie Ganzzahlen wie „num_tf_visible“ und „num_legend_visible“ sowie ein Double-Array „visible_corr_vals“ für Legendenwerte. Schließlich initialisieren wir Arrays für Zeitrahmen und Farben: „tf_list“ als Array für „ENUM_TIMEFRAMES“ mit Werten von einer Minute bis wöchentlich, „tf_strings“ als entsprechende String-Labels und „heatmap_colors“ als Farbarray für Farbverläufe von Rot bis Grün. Als Nächstes werden wir einige Hilfsfunktionen zur Durchführung der statistischen Analyse definieren.

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

Zunächst definieren wir die Funktion „NormalCDF“, um die kumulative Verteilungsfunktion (CDF) für eine Normalverteilung zu approximieren, die bei p-Wert-Berechnungen verwendet wird. Sie benötigt Parameter für Mittelwert, Standardabweichung, den Wert x und einen Verweis zum Speichern des CDF-Ergebnisses. Zunächst wird geprüft, ob die Standardabweichung ungültig ist, und wenn ja, wird false zurückgegeben. Anschließend wird der z-Score durch Standardisierung von x berechnet. Bei extremen z-Werten unter minus zehn oder über zehn setzen wir die CDF auf null bzw. eins und geben true zurück. Andernfalls berechnen wir eine Annäherung unter Verwendung einer Polynom-Erweiterung auf der Grundlage von t und der Dichte d und passen die CDF für positive z an, bevor wir true zurückgeben. Ein normales CDF-Diagramm würde wie folgt aussehen.

DIAGRAMM DER KUMULATIVEN NORMALVERTEILUNGSFUNKTION (CDF)

Als Nächstes implementieren wir die Funktion „StudentCDF“, um die kumulative Verteilungsfunktion (CDF) für die Student'sche t-Verteilung zu approximieren, die für die p-Werte in den Pearson- und Spearman-Methoden wichtig ist, die wir später definieren werden. Sie akzeptiert die Freiheitsgrade df, den Wert x und eine Referenz für die CDF. Wir validieren df und geben false zurück, wenn es ungültig ist. Anschließend berechnen wir die Parameter a und b, gefolgt von xt für die unvollständige Betafunktion. Unter Verwendung von „MathBetaIncomplete“, um ib zu erhalten und das vollständige Beta mit Gamma-Logs zu berechnen, leiten wir das regularisierte unvollständige Beta ab. Je nachdem, ob x nicht-negativ oder negativ ist, wird die CDF entsprechend angepasst und true zurückgegeben. Ein Student-CDF-Diagramm würde wie folgt aussehen:

STUDENT KUMULATIVE VERTEILUNGSFUNKTION (CDF)

Anschließend erstellen wir die Funktion „calculate_pvalue“, um den p-Wert auf der Grundlage des Korrelationskoeffizienten, der Methode und des Stichprobenumfangs n zu berechnen. Wenn n kleiner als drei ist, geben wir eine Eins zurück, um die Ungültigkeit anzuzeigen. Wir initialisieren eine Variable mit kumulativer Verteilungsfunktion und behandeln Kendall separat, indem wir sigma berechnen, auf Null prüfen, z ableiten und „NormalCDF“ verwenden, um den zweiseitigen p-Wert zu erhalten. Für Pearson oder Spearman berechnen wir die quadrierte Korrelation r², behandeln perfekte Korrelationen, indem wir Null zurückgeben, berechnen den Nenner und die t-Statistik und verwenden dann „StudentCDF“ mit angepasstem df, um den zweiseitigen p-Wert zu erhalten. Als Nächstes müssen wir eine Funktion erstellen, die die Liste der Symbole in Arrays zerlegt, die wir verwalten können. Hier ist die Logik, mit der wir das erreicht haben.

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

Hier implementieren wir die Funktion „parse_symbols“, um die vom Nutzer bereitgestellte Liste von Symbolen in ein brauchbares Array für das Dashboard zu verarbeiten. Zunächst wird die Eingabe „SymbolsList“ in eine temporäre String-Variable kopiert und der Zähler „num_symbols“ auf Null zurückgesetzt. Anschließend wird eine Schleife eingeleitet, die so lange fortgesetzt wird, wie ein Komma in der temporären Zeichenfolge gefunden wird und die Anzahl der Symbole unter dem Grenzwert „MAX_SYMBOLS“ liegt. Innerhalb der Schleife suchen wir mit StringFind die Position des nächsten Kommas, extrahieren mit StringSubstr den Teilstring des Symbols vom Anfang bis zu dieser Position und versuchen, das Symbol in der Marktbeobachtung mit SymbolSelect mit true auszuwählen, um es gegebenenfalls hinzuzufügen. Bei Erfolg wird das Symbol im „symbols_array“ am aktuellen Index gespeichert und „num_symbols“ erhöht; andernfalls wird eine Warnmeldung für nicht verfügbare Symbole ausgegeben. Wir aktualisieren die temporäre Zeichenfolge auf den Rest nach dem Komma mit einem weiteren „StringSubstr“.

Nach der Schleife wird geprüft, ob noch Text in der temporären Zeichenfolge vorhanden ist – was auf das letzte Symbol hinweist – und ob die Anzahl noch unter dem Grenzwert liegt. Wir versuchen, dieses letzte Symbol auf ähnliche Weise auszuwählen und zu speichern, und geben eine Warnung aus, wenn es nicht verfügbar ist. Wenn schließlich weniger als zwei gültige Symbole gefunden werden, wird eine Fehlermeldung ausgegeben und das Programm mit ExpertRemove entfernt, um sicherzustellen, dass die Matrix mindestens zwei Aktiva für Korrelationen benötigt. Das Ausdrucken des Arrays führt zu folgendem Ergebnis.

PARSED SYMBOL-ARRAY

Nun können wir unsere statistischen Funktionen für die Verwendung in Berechnungen definieren.

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

Zunächst implementieren wir die Funktion „rank_data“, um einem Array von Datenwerten Ränge zuzuweisen, was für die Korrelationsmethode entscheidend ist, um nicht-parametrische Ränge zu verarbeiten. Sie benötigt einen konstanten Verweis auf das Daten-Array und einen Verweis auf das Array des Rankings. Wir bestimmen zunächst die Größe mit ArraySize und deklarieren ein Indizes-Array, passen die Größe an und initialisieren es mit aufeinanderfolgenden Werten von Null bis Größe minus Eins. Anschließend werden die Indizes anhand der entsprechenden Datenwerte sortiert, wobei die Indizes vertauscht werden, wenn die Daten am aktuellen Index größer sind als die am nächsten. Nach der Sortierung werden die sortierten Indizes in einer Schleife durchlaufen, um Ränge zuzuweisen. Dabei werden Gleichstände behandelt, indem Gruppen mit demselben Wert identifiziert werden, ein durchschnittlicher Rang für die Gruppe mit der Formel (Anfang + Ende – 1) / 2,0 + 1,0 berechnet und dieser Rang über die Indizes auf die ursprünglichen Positionen zurückgeführt wird.

Als Nächstes definieren wir die Funktion „pearson_correlation“, um den Pearsonschen Korrelationskoeffizienten zwischen zwei Arrays von Preisänderungen über einen bestimmten Zeitraum zu berechnen. Sie initialisiert die Mittelwerte für beide Arrays auf Null, führt dann eine Schleife durch, um die Deltas zu summieren, und berechnet die Mittelwerte, indem sie durch die Größe dividiert. Anschließend werden die Varianzen und die Kovarianz mit Null initialisiert und eine weitere Schleife zur Berechnung der Abweichungen von den Mittelwerten durchgeführt, wobei die quadrierten Abweichungen für die Varianzen und das Produkt für die Kovarianz akkumuliert werden. Ist eine der beiden Varianzen gleich Null, geben wir 0,0 zurück, um eine Division durch null zu vermeiden; andernfalls geben wir die Kovarianz geteilt durch die Quadratwurzel aus dem Produkt der Varianzen mithilfe der Funktion MathSqrt zurück. Anschließend erstellen wir die Funktion „spearman_correlation“ zur Berechnung der Spearmanschen Rangkorrelation. Es deklariert zwei Rang-Arrays und passt deren Größe an die Eingabegröße an, ruft „rank_data“ auf jedem Deltas-Array auf, um die Ränge zu füllen, und gibt das Ergebnis von „pearson_correlation“ zurück, das auf diese Rang-Arrays anstelle der ursprünglichen Deltas angewendet wurde.

Schließlich implementieren wir die Funktion „kendall_correlation“ für Kendalls Tau-Koeffizienten. Die Zähler für übereinstimmende und nicht übereinstimmende Paare werden auf Null initialisiert. Wir verwenden verschachtelte Schleifen über die Datengröße, um jedes Elementpaar zu vergleichen und die Vorzeichen als Unterschiede in jedem Feld zu berechnen. Wenn das Produkt der Vorzeichen positiv ist, wird die Konkordanz erhöht; wenn es negativ ist, wird die Ungleichheit der Vorzeichen aufgehoben. Die Gesamtzahl der Paare wird berechnet als Größe mal (Größe – 1) / 2. Wir geben 0,0 zurück, wenn es keine Paare gibt, oder die Differenz zwischen übereinstimmend und nicht übereinstimmend geteilt durch die Gesamtzahl der Paare. Bei einem normalen bivariaten Bevölkerungsvergleich sehen sie wie folgt aus.

PEARSON, SPEARMAN & KENDALL BIVARIATER VERGLEICH

Mithilfe der Analysefunktionen können wir nun Hilfsfunktionen erstellen, um die standardisierten Korrelationsberechnungen wie folgt durchzuführen.

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

Wir erstellen die Funktion „calculate_correlation“, um den Korrelationskoeffizienten zwischen zwei Symbolen zu berechnen und einen Doppelwert zurückzugeben. Sie benötigt String-Parameter für sym1 und sym2. Wenn sie identisch sind, wird sofort 1,0 zurückgegeben, um eine perfekte Selbstkorrelation darzustellen. Wir deklarieren zwei Double-Arrays „prices1“ und „prices2“ und passen deren Größe an die Größe von „CorrelationBars“ an. Wir verwenden CopyClose, um die Schlusskurse für jedes Symbol aus dem Zeitrahmen „global_correlation_tf“ zu holen, beginnend mit dem aktuellen Takt Null. Wenn eine der beiden Kopien nicht den vollen Betrag abrufen kann, geben wir 0,0 zurück, um anzuzeigen, dass die Daten nicht ausreichen.

Um sich auf die Preisänderungen zu konzentrieren, werden die Delta-Arrays „deltas1“ und „deltas2“ deklariert und in der Größe auf einen Wert kleiner als „CorrelationBars“ angepasst. Wir berechnen jedes Delta als Differenz zwischen aufeinanderfolgenden Kursen. Die effektive Größe wird auf „CorrelationBars“ minus eins gesetzt. Eine switch-Anweisung für die Enumeration „CorrMethod“ ruft die entsprechende Korrelationsfunktion auf. Für „PEARSON“ rufen wir „pearson_correlation“ mit den Deltas und der Größe auf. Für „SPEARMAN“ rufen wir „spearman_correlation“ auf. Für „KENDALL“ rufen wir „kendall_correlation“ auf. Der Standardfall gibt 0,0 zurück.

Als Nächstes implementieren wir die Funktion „update_correlations“, um die Korrelations- und p-Wert-Matrizen mit neuen Berechnungen zu füllen. Es berechnet die Stichprobengröße n als „CorrelationBars“ minus eins und prüft, ob sie kleiner als zwei ist. Ist dies der Fall, wird eine Fehlermeldung ausgegeben, da für aussagekräftige Korrelationen mindestens drei Balken erforderlich sind, und das Programm wird vorzeitig beendet. Wir verwenden verschachtelte Schleifen über „num_symbols“ für die Zeilen i und Spalten j, rufen „calculate_correlation“ mit den entsprechenden Symbolen aus „symbols_array“ auf, um corr zu erhalten, und speichern es in „correlation_matrix“ bei [i][j]. Für p-Werte, wenn i gleich j für die Diagonale ist, setzen wir „pvalue_matrix“ bei [i][j] auf 0,0; wenn corr gleich 0,0 und n kleiner als drei ist, setzen wir sie auf 1,0; andernfalls berechnen wir sie mit „calculate_pvalue“ mit corr, „CorrMethod“ und n und speichern das Ergebnis. Als Nächstes benötigen wir Helfer für die Heatmap.

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

Wir definieren die Funktion „get_significance_stars“, um die Anzahl der Asterisk-Symbole zu bestimmen, die die statistische Signifikanz auf der Grundlage eines gegebenen p-Wertes darstellen, und geben eine Zeichenkette zurück. Die Funktion nimmt einen double-Parameter „pval“ entgegen und führt bedingte Prüfungen durch: Ist „pval“ kleiner als „PValueThreshold1“, wird „***“ für die höchste Signifikanz zurückgegeben; ist es kleiner als „PValueThreshold2“, wird „**“ zurückgegeben; ist es kleiner als „PValueThreshold3“, wird „*“ zurückgegeben; andernfalls wird eine leere Zeichenkette für keine Signifikanz zurückgegeben.

Als Nächstes implementieren wir die Funktion „interpolate_heatmap_color“, um eine Farbe für den Heatmap-Modus zu erzeugen, indem wir innerhalb des Gradienten-Arrays auf der Grundlage eines Korrelationswertes zwischen negativ eins und eins interpolieren und einen Farbtyp zurückgeben. Sie behandelt Null, indem sie direkt „ColorNeutralBg“ zurückgibt. Wir berechnen den absoluten Wert „abs_val“, bestimmen die Anzahl der Stopps pro Seite, indem wir die Größe des Arrays „heatmap_colors“ durch zwei teilen, und berechnen die Schrittweite als eins geteilt durch Stopps minus eins. Bei positiven Werten ermitteln wir den Index mit MathFloor für „abs_val“ über „step“, begrenzen ihn, falls er am oder jenseits des letzten Intervalls liegt, berechnen einen Faktor als Rest über „step“ und rufen „interpolate_color“ mit Farben aus der positiven Hälfte des Arrays sowie dem Faktor auf. Bei negativen Werten gehen wir ähnlich vor, verwenden aber die negative Hälfte des Feldes.

Dann erstellen wir die Funktion „interpolate_color“, um zwei Farben linear auf der Grundlage eines Faktors von null bis eins zu mischen und die resultierende Farbe zurückzugeben. Es extrahiert rote, grüne und blaue Komponenten als Zeichen ohne Vorzeichen aus „c1“ und „c2“ unter Verwendung bitweiser Operationen: Maskierung mit 0xFF für Rot, Verschiebung nach rechts um acht und Maskierung für Grün, und Verschiebung um sechzehn für Blau. Wir interpolieren jeden Kanal, indem wir vom Wert der ersten Farbe ausgehen, den Faktor mal die Differenz zur zweiten Farbe addieren, 0,5 für die Rundung hinzufügen und die Werte mit den Funktionen MathMax und MathMin zwischen Null und 255 beschränken. Schließlich werden die interpolierten Komponenten mithilfe von Bitverschiebungen zu einem einzigen Farbwert kombiniert: Blau links um 16, Grün um 8 und Rot, dann werden sie bitweise ODER-verknüpft. Wir können nun mit der Erstellung des Dashboards beginnen, da wir die meisten Hilfsfunktionen haben, die wir für die Berechnungen benötigen.

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

Hier definieren wir die Funktion „create_rectangle“, um ein rechteckiges grafisches Objekt für die Nutzeroberfläche zu erzeugen, das bei Erfolg einen booleschen Wert zurückgibt. Die Parameter umfassen den Objektnamen als Zeichenkette, ganze Zahlen für die x- und y-Abstände, die Größen, die Hintergrundfarbe und eine optionale Randfarbe, die standardmäßig auf „keine“ eingestellt ist. Wir versuchen, das Objekt mithilfe von ObjectCreate mit dem Teilfenster Null, dem Namen OBJ_RECTANGLE_LABEL und den Standardkoordinaten zu erstellen, wobei wir eine Fehlermeldung mit dem Funktionsnamen und dem letzten Fehlercode ausgeben, wenn dies nicht gelingt, und false zurückgeben. Bei Erfolg konfigurieren wir die Eigenschaften mit ObjectSetInteger: x- und y-Abstände, Größen, Ecke nach links oben, Hintergrundfarbe, Rahmenfarbe, Rahmentyp nach flach und zurück nach falsch und geben dann true zurück.

Als Nächstes implementieren wir die Funktion „create_label“, um ein Text-Label-Objekt für die Nutzeroberfläche zu erzeugen, das ebenfalls einen booleschen Erfolg zurückgibt. Sie akzeptiert den Objektnamen und den Text als Zeichenketten, die x- und y-Abstände, die optionale Schriftgröße mit dem Standardwert zehn, die Textfarbe mit dem Standardwert Weiß und die Schriftart mit dem Standardwert Arial Rounded MT Bold. Wir verwenden „ObjectCreate“ mit dem Typ OBJ_LABEL und geben einen Fehler aus, wenn der Vorgang fehlschlägt. Bei Erfolg werden die x- und y-Abstände, die Ecke links oben, der Text, die Schriftart, die Schriftgröße, die Textfarbe und der Anker mit den entsprechenden Aufrufen von „ObjectSetInteger“- und ObjectSetString auf die Mitte gesetzt und true zurückgegeben.

Anschließend erstellen wir die Funktion „create_full_dashboard“, um die gesamte Struktur der Nutzeroberfläche unter Verwendung der oben genannten Hilfsmittel aufzubauen. Es setzt lokale Farben wie den Haupthintergrund auf ein dunkles Grau, den Kopfzeilenhintergrund auf ein mittleres Grau, die Textfarbe auf Weiß, den neutralen Hintergrund von input, den Schaltflächentext auf Gold, die Themen- und Schließsymbole auf Weiß und das Kopfzeilensymbol auf Aqua. Wir berechnen die Breite des Panels auf der Grundlage der Symbolbreite plus der um die Anzahl der Symbole bereinigten Zellen und die Höhe unter Einbeziehung der Kopfzeile, der Zeitrahmenzeile, der Lücke und der Matrixzeilen.

Wir rufen „create_rectangle“ für das Haupt- und das Header-Panel auf, anschließend „create_label“ für Elemente wie das Kopfzeilensymbol mit dem Zeichen 181 aus den Wingdings, den Titel „Correlation Matrix“, die Schaltfläche „Schließen“ mit dem Zeichen „r“ aus Webdings, die Schaltfläche zum Umschalten auf ähnliche Weise, die Schaltfläche der Heatmap mit einem dynamischen Symbol basierend auf dem Anzeigemodus unter Verwendung von CharToString und uchar-Umwandlungen, die P-Wert-Schaltfläche mit einem „X“ aus Wingdings, die Sortier-Schaltfläche mit einem „N“ aus Wingdings 3 und die Themen-Schaltfläche mit einem „[“ aus Wingdings. Sie können eine beliebige Schriftart aus der folgenden Liste verwenden.

SCHRIFTFONT DER SYMBOLE

Für die Zeile des Zeitrahmens werden die y-Position unter der Kopfzeile und das Start-X berechnet. Dann wird eine Schleife über die sichtbaren Zeitrahmen gezogen, um Rechtecke und Beschriftungen zu erstellen: Berechnung von Offsets, Bildung von Namen mit dem Präfix der Zelle des Zeitrahmens und der Indexzeichenfolge, bedingte Einstellung des Hintergrunds auf eine stark positive oder Kopfzeilenfarbe, basierend auf dem aktuellen Index, und Beschriftung mit Zeitrahmenzeichenfolgen in Arial Bold. Wir setzen die y-Position der Matrix unter die Zeitrahmenzeile plus Lücke, erstellen dann das Zeilenkopf-Rechteck und beschriften es mit „Symbols“. Für die Zeilensymbole auf der linken Seite werden in einer Schleife die y-Offsets berechnet, Rechtecke mit Zeilenpräfix und Index sowie Beschriftungen mit Symbolnamen aus dem Array in Arial Bold erstellt. Für die Spaltensymbole oben berechnen wir in einer Schleife die x-Offsets, erstellen Rechtecke mit Spaltenpräfixen und Beschriftungen mit Symbolnamen.

Für Korrelationszellen werden Schleifen über Zeilen und Spalten geschachtelt, Offsets berechnet, Zellnamen mit Rechteckpräfix und Indizes, getrennt durch Unterstrich, und Textnamen in ähnlicher Weise gebildet, dann „create_rectangle“ mit neutralem Hintergrund und „create_label“ initialisiert auf 0,00 in Arial aufgerufen. Zum Schluss wird das Chart mit ChartRedraw im Teilfenster Null neu gezeichnet. Wir können diese Funktion nun im „OnInit“-Ereignishandler aufrufen, um die schwere Arbeit zu erledigen.

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

In der Ereignisbehandlung von OnInit werden die Laufzeiteinstellungen und die Nutzeroberfläche des Programms initialisiert. Wir setzen „global_display_mode“ auf den Eingabewert „DashboardMode“ und bestimmen „global_correlation_tf“, indem wir prüfen, ob „CorrelationTimeframe“ den Wert PERIOD_CURRENT hat; in diesem Fall wandeln wir den aktuellen Chart-Zeitraum _Period in ENUM_TIMEFRAMES um; andernfalls verwenden wir den Eingabewert direkt. Anschließend wird das Array „tf_list“ in einer Schleife durchlaufen, um den Index zu finden, der mit „global_correlation_tf“ übereinstimmt, und dieser Index wird „current_tf_index“ zugewiesen, wobei der Vorgang bei einer Übereinstimmung vorzeitig beendet wird. Wird keine Übereinstimmung gefunden, sodass der „current_tf_index“ negativ ist, wird er auf drei gesetzt, was dem stündlichen Zeitrahmen entspricht. Wir aktualisieren „global_correlation_tf“ auf den Wert an diesem Index in „tf_list“, um Konsistenz zu gewährleisten.

Als Nächstes rufen wir „parse_symbols“ auf, um die Symbolliste zu verarbeiten. Wir berechnen die „panel_width“ auf der Grundlage von „WIDTH_SYMBOL“ plus einer Anpassung für die Anzahl der Symbole mal „WIDTH_CELL“. Die „num_tf_visible“ wird auf das Minimum aus „NUM_TF“ und dem verfügbaren Platz im Panel geteilt durch „WIDTH_TF_CELL“ gesetzt. Wenn „current_tf_index“ diese sichtbare Anzahl überschreitet, wird er auf den letzten sichtbaren Index minus eins beschränkt und „global_correlation_tf“ wieder entsprechend aktualisiert. Das Array „pvalue_matrix“ wird vor den Berechnungen mit ArrayInitialize als Standardzustand auf 1,0 initialisiert. Schließlich rufen wir „create_full_dashboard“ auf, um die Nutzeroberfläche zu erstellen, und geben INIT_SUCCEEDED zurück, um die erfolgreiche Initialisierung anzuzeigen. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

ERSTES DASHBOARD

Aus dem Bild ist ersichtlich, dass das Dashboard erfolgreich erstellt wurde. Wir müssen es um das Legendenfeld erweitern, damit es in Verbindung mit dem Dashboard erstellt wird.

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

Wir implementieren die Funktion „recreate_legend“, um die Legendenobjekte auf der Grundlage des aktuellen Anzeigemodus dynamisch neu zu erstellen und sicherzustellen, dass sie in das Panel passen und geeignete Korrelationswerte wiedergeben. Wir führen eine Schleife durch, um vorhandene Legendenrechtecke und Textbeschriftungen mit ObjectDelete zu löschen, wobei die Namen aus Präfixen und Indexstrings gebildet werden.

Wir deklarieren ein vollständiges Korrelationswerte-Array und füllen es bedingt auf: für den Heatmap-Modus unter Verwendung eines lokalen Arrays mit Werten von negativ 1,0 bis 1,0 in 0,2-Schritten, die kopiert werden; für den Standardmodus unter Verwendung von Schwellenwerten, die in Dezimalzahlen umgewandelt werden, und festen Punkten wie negativ 0,75. Um den Raum einzupassen, kopieren wir in das sichtbare Feld, berechnen die Breite des Paneels, die verfügbare Breite, die Kosten des Elements einschließlich der Abstände und die maximale Passung mit einer Bodeneinteilung, wobei mindestens eine gewährleistet sein muss. In einer übergroßen while-Schleife entfernen wir die Null, falls vorhanden, oder wir finden und entfernen alle Instanzen des kleinsten absoluten Wertes ungleich Null, indem wir sie durchsuchen und ArrayRemove rückwärts verwenden.

Wir setzen die sichtbare Anzahl auf die aktualisierte Array-Größe. Für die Positionierung berechnen wir die Panelhöhe aus Konstanten und Zeilen, die Legende y unterhalb des Panels mit einer Lücke, die Gesamtbreite der Legende mit Zellen und Abständen und das Start-X für die Zentrierung. Wir legen einen neutralen Hintergrund und eine neutrale Textfarbe fest und erstellen dann in einer Schleife Rechtecke und Beschriftungen: Wir berechnen Versätze, bilden Namen, verwenden „create_rectangle“ mit einem neutralen Hintergrund und „create_label“, initialisiert auf 0% in Arial. In der Funktion „create_full_dashboard“ werden nach anderer Schnittstellenlogik die Legende y berechnet und das Rechteck des Legendenfeldes mit dem Haupthintergrund erstellt, „recreate_legend“ aufgerufen, um es zu füllen, und das Chart neu gezeichnet. Bei der Kompilierung ergibt sich folgendes Ergebnis:

TAFEL MIT LEGENDE

Nachdem wir die Legende hinzugefügt haben, müssen wir jetzt nur noch das Dashboard und die Legende aktualisieren, damit die Berechnungen wirksam werden.

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

Hier implementieren wir die Funktion „update_tf_highlights“, um die ausgewählte Zelle des Zeitrahmens im Dashboard visuell hervorzuheben. Wir setzen eine inaktive Hintergrundfarbe auf ein mittleres Grau und ziehen eine Schleife über die Anzahl der sichtbaren Zeitrahmen. Für jedes Rechteck wird der Name mit dem Präfix „TF_CELL_RECT“ und dem Indexstring gebildet, dann wird die Hintergrundfarbe bedingt auf „ColorStrongPositiveBg“ gesetzt, wenn sie mit dem „current_tf_index“ übereinstimmt, ansonsten auf die inaktive Farbe. Wir aktualisieren die Hintergrundeigenschaft des Objekts mit ObjectSetInteger unter Verwendung von „OBJPROP_BGCOLOR“ und zeichnen das Chart neu.

Als Nächstes definieren wir die Funktion „update_legend“, um die Farben und Texte der Legende je nach Modus zu aktualisieren. Wir setzen eine Standard-Textfarbe aus „ColorTextStrong“ und ziehen eine Schleife über die sichtbaren Legendenelemente. Für jede dieser Funktionen werden die Namen für Rechteck und Text mithilfe von Präfixen und Index abgerufen, der Korrelationswert aus „visible_corr_vals“ ermittelt, Dezimalstellen anhand von Absolutwertprüfungen für eine saubere Formatierung bestimmt und die Textzeichenfolge mit der Funktion DoubleToString als Prozentwert formatiert.

Wir initialisieren die Hintergrund- und Textfarben und behandeln dann Sonderfälle: Für 1,0 wird ein diagonaler Hintergrund verwendet, für 0,0 ein neutraler Hintergrund – beide mit Standardtext. Für andere, im Standardmodus, berechnen wir starke Schwellenwerte aus Prozentsätzen und setzen Hintergründe und Textfarben für stark positiv, stark negativ oder mild auf der Grundlage von Vergleichen, wobei positiver oder negativer milder Text für Nicht-Null verwendet wird. Im Heatmap-Modus legen wir den Standardtext fest und interpolieren den Hintergrund mit „interpolate_heatmap_color“. Wir aktualisieren die „OBJPROP_BGCOLOR“ des Rechtecks, die „OBJPROP_TEXT“ des Textes und die „OBJPROP_COLOR“ mithilfe von Objekt-Settern und zeichnen dann das Chart neu.

Anschließend erstellen wir die Funktion „update_dashboard“, um alle Zellen mit den aktuellen Korrelationen und Grafiken zu aktualisieren. Wir rufen zuerst „update_correlations“ auf, berechnen starke Schwellenwerte aus Prozentsätzen und setzen eine Basistextfarbe aus „ColorTextStrong“.

In verschachtelten Schleifen über Symbole für Zeilen und Spalten werden Korrelation und p-Wert aus den Matrizen abgerufen, der Zellentext als Prozentsatz plus Signifikanzsterne aus „get_significance_stars“ formatiert und die Farben initialisiert. Für diagonale Zellen, bei denen die Indizes übereinstimmen, verwenden wir einen diagonalen Hintergrund und einen Basistext. Für die Off-Diagonale werden im Standardmodus Hintergrund und Text für stark positiv, stark negativ oder mild mit bedingtem positiv/negativ/Null-Text festgelegt; im Heatmap-Modus werden Basistext und interpolierter Hintergrund aus „interpolate_heatmap_color“ verwendet. Wir bilden Zell- und Textnamen mit Präfixen und Indizes, aktualisieren den Hintergrund mit OBJPROP_BGCOLOR, den Textinhalt mit „OBJPROP_TEXT“ und die Textfarbe mit „OBJPROP_COLOR“ über Setter.

Schließlich rufen wir „update_legend“ auf und zeichnen das Chart neu. Wir rufen diese Funktionen in der Initialisierung als Tick-Ereignisse auf, um die volle Wirkung zu erzielen (siehe unten).

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

In OnInit rufen wir „update_tf_highlights“ für die Zeitrahmenvisualisierung und „update_dashboard“ für die anfängliche Datenpopulation auf und geben INIT_SUCCEEDED zurück. In OnTick rufen wir einfach „update_dashboard“ auf, um Korrelationen und Grafiken bei jedem neuen Tick zu aktualisieren. Wenn Sie die Berechnungen bei jedem Balken durchführen möchten, können Sie eine Kontrolllogik hinzufügen, aber im Moment lassen wir es so, wie es ist, da wir nur wenige Symbolsätze haben. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

DASHBOARD MIT AUSGEFÜLLTER KORRELATIONSMATRIX

Aus dem Bild ist ersichtlich, dass wir das Korrelationsmatrix-Dashboard mit allen Populationen korrekt eingerichtet und damit unsere Ziele erreicht haben. Nun bleibt nur noch, die Funktionsfähigkeit des Systems zu testen, was im vorangegangenen Abschnitt behandelt wurde.


Backtests

Wir haben die Tests durchgeführt, und unten sehen Sie die kompilierte Visualisierung in einem einzigen Bitmap-Bildformat. (Graphics Interchange Format, GIF).

KORRELATIONSMATRIX GIF


Schlussfolgerung

Zusammenfassend haben wir ein Dashboard für eine Korrelationsmatrix in MQL5 entwickelt, das Beziehungen zwischen nutzerspezifischen Symbolen mit Pearson-, Spearman- oder Kendall-Methoden über konfigurierbare Zeitrahmen und Balken berechnet und p-Wert-Signifikanztests zur Überprüfung der Zuverlässigkeit durchführt. Das System unterstützt den Standardmodus mit schwellenwertbasierter Einfärbung und Sternen für Signifikanzniveaus sowie den Modus einer Heatmap mit Gradienteninterpolation für die visuelle Darstellung von Korrelationsstärken und bietet eine interaktive Nutzeroberfläche mit Zeitrahmenauswahl, Modusumschaltung und einer dynamischen Legende für die Analyse von Vermögenswertinterdependenzen. In den nächsten Abschnitten werden wir dem Dashboard Leben einhauchen, um es schwebend und verschiebbar zu machen und die anklickbaren Symbole durch die Ausführung ihrer jeweiligen Funktionen ansprechbar zu machen. Bleiben Sie dran!

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/20945

Letzte Kommentare | Zur Diskussion im Händlerforum (1)
Cedric Olivier Kusiele Some
Cedric Olivier Kusiele Some | 18 Jan. 2026 in 17:39

Hallo Allan Munene Mutiiria,

Danke, dass Sie mit Ihrem Artikel eine brillante Idee teilen. Im Gegenzug habe ich deinen Quellcode für die Produktion zur Verfügung gestellt

.ex5 Datei von Moderator entfernt - nur Quellcode ist im Forum erlaubt

Entwicklung eines Toolkits zur Preisaktionsanalyse (Teil 54): Filtern von Trends mit EMA und geglätteter Kursbewegung Entwicklung eines Toolkits zur Preisaktionsanalyse (Teil 54): Filtern von Trends mit EMA und geglätteter Kursbewegung
In diesem Artikel wird eine Methode untersucht, die Heikin-Ashi-Glättung mit EMA20-Hoch- und -Tiefgrenzen und einem EMA50-Trendfilter kombiniert, um die Klarheit und das Timing des Handels zu verbessern. Es wird aufgezeigt, wie diese Tools Händlern dabei helfen können, echte Impulse zu erkennen, Rauschen herauszufiltern und sich besser auf volatilen Märkten oder in Trends zurechtzufinden.
Einführung in MQL5 (Teil 35): Beherrschen der API- und WebRequest-Funktion in MQL5 (IX) Einführung in MQL5 (Teil 35): Beherrschen der API- und WebRequest-Funktion in MQL5 (IX)
Erfahren Sie, wie Sie Nutzeraktionen in MetaTrader 5 erkennen, Anfragen an eine KI-API senden, Antworten extrahieren und als Lauftext in Ihrem Panel implementieren.
Einführung in MQL5 (Teil 36): Beherrschen der API und der Funktion WebRequest in MQL5 (X) Einführung in MQL5 (Teil 36): Beherrschen der API und der Funktion WebRequest in MQL5 (X)
Dieser Artikel stellt die grundlegenden Konzepte hinter HMAC-SHA256 und API-Signaturen in MQL5 vor und erklärt, wie Nachrichten und geheime Schlüssel kombiniert werden, um Anfragen sicher zu authentifizieren. Sie bildet die Grundlage für das Signieren von API-Aufrufen, ohne sensible Daten preiszugeben.
Optimieren der Trendstärke: Handel in Richtung von Trend und Stärke Optimieren der Trendstärke: Handel in Richtung von Trend und Stärke
Dies ist ein spezieller Trendfolge-EA, der sowohl kurz- als auch langfristige Analysen, Handelsentscheidungen und Ausführungen auf der Grundlage des Gesamttrends und seiner Stärke vornimmt. In diesem Artikel wird ein EA ausführlich vorgestellt, der speziell für Trader entwickelt wurde, die geduldig, diszipliniert und zielstrebig genug sind, um Trades nur dann auszuführen und ihre Positionen nur dann zu halten, wenn sie mit starker Marktdynamik und in Trendrichtung handeln, ohne ihre Ausrichtung häufig zu ändern – insbesondere nicht gegen den Trend –, bis die Gewinnziele erreicht sind.