English Русский
preview
MQL5-Werkzeuge für den Handel (Teil 12): Erweiterung des Korrelationsmatrix-Dashboards um interaktive Funktionen

MQL5-Werkzeuge für den Handel (Teil 12): Erweiterung des Korrelationsmatrix-Dashboards um interaktive Funktionen

MetaTrader 5Handel |
22 4
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In unserem vorherigen Artikel (Teil 11) haben wir ein Dashboard für eine Korrelationsmatrix in MetaQuotes Language 5 (MQL5) entwickelt, das die Beziehungen zwischen den Vermögenswerten mithilfe der Methoden Pearson, Spearman und Kendall über einen konfigurierbaren Zeitrahmen und eine konfigurierbare Anzahl von Balken berechnet. In Teil 12 erweitern wir das Dashboard der Korrelationsmatrix um Interaktivität. Mit dieser Erweiterung werden Funktionen wie das Ziehen und Minimieren von Panels über Mausereignisse, Hover-Effekte für visuelles Feedback, die Sortierung von Symbolen nach Korrelationsstärke in auf- und absteigenden Modi, das Umschalten zwischen Korrelation und dem p-Wert, der Wechsel zwischen hellem und dunklem Farbschema mit dynamischen Farbaktualisierungen und Tooltips zu den Zellen für detaillierte Informationen eingeführt. Wir werden die folgenden Themen behandeln:

  1. Verstehen der Erweiterungen der interaktiven Korrelationsmatrix
  2. Implementation in MQL5
  3. Backtests
  4. Schlussfolgerung

Am Ende werden Sie ein interaktives MQL5-Korrelationsmatrix-Dashboard mit verbesserter Nutzerfreundlichkeit verwenden, das Sie sofort anpassen können – legen wir los!


Verstehen der Erweiterungen der interaktiven Korrelationsmatrix

Die interaktive Korrelationsmatrix baut auf dem Basis-Dashboard auf, indem sie nutzerfreundliche Funktionen einführt, die eine dynamische Manipulation und Anpassung ermöglichen, sodass wir die Beziehungen zwischen Vermögenswerten in Echtzeit analysieren können, ohne den Arbeitsablauf zu unterbrechen. Zu den wichtigsten Neuerungen gehören die Verarbeitung von Mausereignissen für das Ziehen von Feldern, um das Dashboard auf dem Chart neu zu positionieren, das Minimieren/Maximieren, um zwischen einer kompakten Kopfansicht und der Vollanzeige umzuschalten, Schwebeeffekte auf Schaltflächen und Zeitrahmenzellen für visuelles Feedback, wie Farbänderungen, und Klickreaktionen, um zwischen Modi oder Ansichten zu wechseln.

Wir haben auch die Sortierung von Symbolen nach der durchschnittlichen absoluten Korrelationsstärke in ursprünglicher, absteigender oder aufsteigender Reihenfolge integriert, um hoch korrelierte Vermögenswerte zu gruppieren, ebenso wie das Umschalten zwischen der Korrelation und dem p-Wert für tiefere statistische Einblicke, den Wechsel zwischen hellem und dunklem Farbschema, um sich den Nutzerpräferenzen mit entsprechenden Farben anzupassen, und Tooltips zu den Zellen, die Details wie Symbole, Zeitrahmen, Methode, Korrelation, p-Wert und verwendete Balken liefern.

Wir planen, die Eingaben und Enumerationen für neue Modi wie das Umschalten der Ansicht zu erweitern, Globals für Interaktionszustände hinzuzufügen, das Parsing zu modifizieren, um Originalsymbole zu speichern, eine Sortierlogik auf der Grundlage von Durchschnittsstärken mit Indexumordnung zu implementieren, die Erstellungs- und Positionsfunktionen zu aktualisieren, um die Minimierung und das Ziehen zu unterstützen, die Farben in Farbschemenumschaltungen dynamisch anzupassen, die Dashboard-Aktualisierungen der Darstellung der p-Werte und Tooltips zu verbessern und alle Interaktionen in einem Event-Handler für nahtlose Nutzerfreundlichkeit zu behandeln. Kurz gesagt: Hier ist eine visuelle Darstellung unserer Ziele.

ERWEITERTETES SYSTEM


Implementation in MQL5

Um das Programm in MQL5 zu verbessern, müssen wir einige neue Eingabeparameter zur Steuerung des Multi-Dashboard-Kontrollmechanismus und einige zusätzliche Globals definieren.

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

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

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

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

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

ViewMode global_view_mode = VIEW_CORR;

Wir beginnen die Implementierung mit dem Hinzufügen von Eingabeparametern für die Anpassung des hellen Farbschemas, einschließlich „ColorLightThemeBg", das für Hintergründe auf Weiß und „ColorLightThemeText", das für Text auf Schwarz gesetzt ist. Anschließend definieren wir die Enumeration „ViewMode" mit den Optionen „VIEW_CORR" für die Anzeige von Korrelationen und „VIEW_PVAL" für die Anzeige von p-Werten, sodass zwischen den Ansichten umgeschaltet werden kann. Als nächstes führen wir Definitionen für neue Elemente der Nutzeroberfläche wie „SORT_BUTTON" und „THEME_BUTTON" ein, zusammen mit Farben wie „COLOR_LIGHT_GRAY" als helles Grau für neutrale Zellen im hellen Modus, „COLOR_DARK_GRAY" als Dunkelgrau für Kopfzeilen, „BUTTON_HOVER_SIZE" mit vierundzwanzig Pixeln für Bereiche zur Erkennung des Schwebens von Schaltflächen und „NUM_LEGEND_ITEMS" erweitert auf fünfzehn, um feinere Legenden in Modi wie Heatmap.

Wir deklarieren globale Variablen, um Interaktionszustände zu verwalten: „panel_is_visible“ wird auf „true“ gesetzt, um die Sichtbarkeit des Dashboards anzuzeigen, „panel_minimized“ auf „false“ für die Vollbildansicht, „panel_dragging“ auf „false“ mit Koordinaten wie „panel_drag_x“ und „panel_start_x“ auf Null für die Verarbeitung von Drag-Aktionen, „prev_mouse_state“ auf Null, um Mausknopfzustände zu verfolgen, boolesche Flags wie „prev_header_hovered“, gesetzt auf „false“ zur Überwachung von Hover-Zuständen über Elementen wie Header, Toggle, Schließen, Heatmap, p-Wert, Sortieren und Schaltflächen der Farbschemen, ein Array „prev_tf_hovered“ für Hover-Zustände über Zeitrahmen-Zellen, „last_mouse_x“ und „last_mouse_y“ auf Null für die letzte Cursorposition, „is_light_theme“ auf „false“ bei Start im Dunkelmodus und „sort_mode“ auf Null für die ursprüngliche Symbolreihenfolge. Außerdem fügen wir das String-Array „original_symbols" mit der maximalen Symbolgröße hinzu, um die anfängliche Symbolreihenfolge für Sortierrückstellungen zu erhalten, und setzen „global_view_mode" auf „VIEW_CORR" als Standardansicht.

Wenn wir nun die Symbole analysieren, müssen wir die Klassifizierung der ursprünglichen Symbole speichern, damit wir sie abrufen können, wenn wir zum ursprünglichen Zustand wechseln. Wir fügen sie einfach dem definierten Array hinzu, wie unten gezeigt. Zur besseren Übersichtlichkeit haben wir die Ergänzung hervorgehoben.

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

Um die Sortierung nach Stärke von ursprünglich über absteigend bis hin zu aufsteigend für die Clusterung analytischer Werte einzuführen, müssen wir die entsprechenden Funktionen einführen. Hier ist die Logik, die wir dafür verwendet haben.

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

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

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

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

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

Zunächst implementieren wir die Funktion „sort_symbols_by_strength", um die Symbole auf der Grundlage ihrer durchschnittlichen absoluten Korrelationsstärke gemäß dem aktuellen Sortiermodus neu zu ordnen. Wenn „sort_mode" für die ursprüngliche Reihenfolge Null ist, kopieren wir das Array „original_symbols" mit ArrayCopy zurück nach „symbols_array" und berechnen die Matrizen mit „update_correlations" neu, bevor wir die Funktion beenden. Andernfalls deklarieren und initialisieren wir mit ArrayInitialize ein Double-Array „avg_corr" mit einer maximalen Symbolgröße von null, durchlaufen dann eine Schleife über Symbole und Paare, akkumulieren die absoluten Werte aus der „correlation_matrix" ohne Selbstpaare und dividieren durch „num_symbols" minus eins, um Durchschnittswerte zu erhalten. Wir richten ein Array „indices" ein, das sequenziell initialisiert wird, und sortieren es mit dem Bubblesort: In den verschachtelten Schleifen bestimmen wir, ob ein Platztausch erforderlich ist, indem wir den „sort_mode" auf eins für aufsteigend (niedrigerer Durchschnitt zuerst) oder zwei für absteigend (höherer zuerst) einstellen und die Indizes ggf. die Plätze tauschen.

Dann deklarieren wir neue Arrays für Symbole, Korrelationen und p-Werte und führen eine Schleife zur Neuordnung durch: Für jede neue Position erhalten wir den alten Index, setzen das Symbol und kopieren Matrixzeilen und -spalten entsprechend von der alten zur neuen Position. Schließlich aktualisieren wir die globalen Arrays mit „ArrayCopy" für Symbole, „correlation_matrix" und „pvalue_matrix". Als Nächstes erstellen wir die Funktion „cycle_sort_mode“, um den Wert von „sort_mode“ modulo drei zu erhöhen und so zwischen „original“, „absteigend“ und „aufsteigend“ zu wechseln. Wir setzen einen Richtungsstring basierend auf dem neuen Modus und drucken ihn aus. Dann rufen wir „sort_symbols_by_strength" auf, um die Sortierung anzuwenden. Als Nächstes müssen wir die Konsistenz zwischen hellen und dunklen Farbschemen sicherstellen und die visuelle Kohärenz verbessern, wenn die Farbschemen für die Zeiträume umgeschaltet werden. Um dies zu erreichen, müssen wir die Hervorhebungen wie folgt aktualisieren, damit der inaktive Hintergrund vom verwendeten Farbschema abhängt.

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

Hier haben wir einen ternären Operator verwendet, um die inaktive Hintergrundfarbe im hellen Modus auf Silber zu setzen; andernfalls bleibt die ursprüngliche Farbe erhalten. Wir werden dasselbe mit den Legendenelementen vornehmen, damit sich deren Hintergrund und Textbeschriftungen nun an das ausgewählte Design anpassen und so für eine einheitliche Darstellung sorgen.

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

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

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

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

Wir ändern die Funktion „recreate_legend", um Änderungen des Farbschemas zu unterstützen, indem wir die neutrale Hintergrundfarbe bedingt einstellen: Wenn „is_light_theme" wahr ist, verwenden wir „COLOR_LIGHT_GRAY", andernfalls „ColorNeutralBg". In ähnlicher Weise stellen wir die Textfarbe auf „ColorLightThemeText" im hellen Modus oder Weiß im dunklen Modus ein. In der Erstellungsschleife werden diese Farben beim Aufruf von „create_rectangle" für jedes Legendenelement mit dem berechneten neutralen Hintergrund und bei „create_label" mit der aktualisierten Textfarbe angewendet, um sicherzustellen, dass die Legende optisch an das aktuelle Farbschema angepasst ist.

Als Nächstes aktualisieren wir die Funktion „update_legend", um die Standard-Textfarbe auf der Grundlage des Farbschemas festzulegen: „ColorLightThemeText" für hell oder „ColorTextStrong" für dunkel, die dann in der Schleife für die Behandlung von Textfarben in verschiedenen Korrelationsfällen verwendet wird. Anschließend implementieren wir die Funktion „update_borders", um die Rahmenfarben für die wichtigsten Felder zu aktualisieren. Mit „is_light_theme" legen wir die Rahmenfarbe als Schwarz im hellen Farbschema oder Weiß im dunklen Farbschema fest und wenden sie auf die Kopf-, Haupt- und Legendenfelder an, indem wir OBJPROP_COLOR mit der Funktion ObjectSetInteger setzen. Wir benötigen nun neue Funktionen, um Themen, Minimierung, Ziehen und Schweben der Dashboard-Komponenten zu handhaben. Wir beginnen mit der Behandlung von Toggle-Klicks.

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

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

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

Wir implementieren die Funktion „toggle_theme", um zwischen dem hellen und dem dunklen Dashboard-Farbschema zu wechseln. Die Funktion kehrt das boolesche Flag „is_light_theme" um und setzt die lokalen Farben entsprechend. Der Haupthintergrund ist entweder „ColorLightThemeBg" oder dunkelgrau. Die Kopfzeile wechselt zu Silber oder Mittelgrau. Der Text wird zu „ColorLightThemeText" oder weiß. Die neutrale Farbe ist hellgrau oder „ColorNeutralBg". Die diagonale Farbe ist grau oder „ColorDiagonalBg". Das Symbol für das Farbschema wird schwarz oder weiß.

Der Text auf der Schaltfläche ist marineblau oder goldfarben. Der Text zum Schließen ist schwarz oder weiß. Das Kopfzeilensymbol wechselt zu Dodger Blue oder Aqua. Wir verwenden ObjectSetInteger, um Objekteigenschaften zu aktualisieren. Die Hintergründe des Hauptfensters, der Kopfzeile und der Legende erhalten die neuen Haupt- oder Kopffarben. Kopfzeilentext und -symbol ändern sich in ihre aktualisierten Text- und Symbolfarben. Die Schaltflächen zum Schließen, Umschalten, der Heatmap, dem p-Wert, dem Sortieren und dem Farbschema erhalten ihre neuen Farben.

In einer Schleife über die Symbole aktualisieren wir die Zeilen- und Spaltenrechtecke auf den neuen Kopfzeilenhintergrund und setzen ihre Texte auf die neue Textfarbe. Innerhalb der Schleife bilden wir für jede Zelle den Namen, bestimmen den Basishintergrund – diagonal, wenn er auf der Diagonalen liegt, ansonsten neutral – und legen ihn fest. Schließlich rufen wir „update_dashboard" auf, um die Zellenfarben neu zuzuweisen, „update_borders" für die Panel-Ränder und ChartRedraw, um die Anzeige zu aktualisieren. Für den minimierten Modus erstellen wir nur die Kopfkomponenten, wie unten beschrieben.

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

Hier implementieren wir die Funktion „create_minimized_dashboard", um eine kompakte Version der Nutzeroberfläche zu erzeugen, wenn das Panel minimiert ist und aus Platzgründen nur die Kopfzeile angezeigt wird. Es setzt lokale Farben auf der Grundlage des Farbschemas: Kopfzeilenhintergrund auf Silber im hellen Modus oder Mittelgrau im dunklen Modus, Textfarbe auf „ColorLightThemeText" oder Weiß, Schaltflächentext auf Marineblau oder Gold, Schließtext auf Schwarz oder Weiß und Kopfzeilensymbol auf Dodgerblau oder Aqua.

Wir berechnen die Breite des Panels aus Konstanten, die an die Anzahl der Symbole angepasst sind, und rufen dann „create_rectangle" für das „HEADER_PANEL" mit der aktuellen Position und der Kopfhöhe auf, wobei der Hintergrund des Farbschemas verwendet wird. Wir fügen Beschriftungen für das Kopfzeilensymbol mit dem Wingdings-Zeichen 181, den Titel als Korrelationsmatrix, die Schließen-Schaltfläche mit dem Webdings-Zeichen „r" und die Umschalt-Schaltfläche mit dem Wingdings-Zeichen „o" hinzu, die alle relativ zum Panel positioniert sind und die Farben des Farbschemas verwenden.

Schließlich rufen wir „update_borders" auf, um die Ränder, und „ChartRedraw“, um die Anzeige zu aktualisieren. Um die Klicks für den Abschluss und den Wechsel des Zeitrahmens zu verarbeiten, implementieren wir die folgenden Funktionen.

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

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

Für das Schließen von Klicks implementieren wir die Funktion „delete_all_objects", um alle mit dem Dashboard verbundenen grafischen Objekte beim Schließen oder Zurücksetzen zu löschen. Es verwendet ObjectDelete mit dem Subfenster „zero“, um das Hauptfenster, das Kopfzeilenfenster, das Kopfzeilensymbol sowie die Schaltflächen zum Schließen, Umschalten, für die Heatmap, den p-Wert, die Sortierung und das Design zu entfernen. Wir durchlaufen in einer Schleife die Anzahl der Zeitrahmen, um jedes Rechteck und jeden Text der Zeitrahmenzellen mithilfe von Präfixen und Indexzeichenfolgen zu löschen. Anschließend löschen wir das Rechteck und den Text der Symbolzeilenüberschrift und entfernen in einer Schleife über alle Symbole hinweg die Zeilen- und Spaltenrechtecke sowie die Texte. Darin werden Schleifen über die Zellen gelegt, um jedes Korrelationszellenrechteck und jeden Text mit verketteten Namen zu löschen. Schließlich löschen wir das Legendenfeld und ziehen eine Schleife über die Legendenelemente, um ihre Rechtecke und Texte zu entfernen.

Als Nächstes erstellen wir die Funktion „switch_timeframe", um den Analysezeitrahmen auf der Grundlage eines bestimmten Index zu ändern. Sie prüft, ob der Index innerhalb von Null bis „num_tf_visible" minus Eins gültig ist, und bricht andernfalls vorzeitig ab. Wir setzen „global_correlation_tf" auf den Wert an diesem Index in „tf_list", aktualisieren „current_tf_index", drucken die Umschaltmeldung mit der entsprechenden Zeichenkette aus „tf_strings" aus, rufen „update_tf_highlights" auf, um das Bildmaterial zu aktualisieren, und „update_dashboard", um die neuen Daten neu zu berechnen und anzuzeigen. Als Nächstes müssen wir das gesamte Dashboard aktualisieren, damit es auf das Farbschema reagiert, und die neuen Schaltflächen hinzufügen, die wir für Farbschema und Sortierung benötigen.

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

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

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

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

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

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

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

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

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

Zunächst modifizieren wir die Funktion „create_full_dashboard", um die Farben des jeweiligen Farbschemas und zusätzliche interaktive Elemente einzubauen. Wir setzen lokale Farben abhängig von „is_light_theme": den Haupthintergrund auf „ColorLightThemeBg" oder Dunkelgrau, die Kopfzeile auf Silber oder Mittelgrau, den Text auf „ColorLightThemeText" oder Weiß, Neutral auf „COLOR_LIGHT_GRAY" oder „ColorNeutralBg", den Schaltflächentext auf Marineblau oder Gold, das Farbschemasymbol auf Schwarz oder Weiß, den Text zum Schließen auf Schwarz oder Weiß und das Kopfzeilensymbol auf Dodgerblau oder Aqua.

Wir berechnen die Abmessungen der Panels wie zuvor und erstellen die Haupt- und Header-Panels mit diesen Hintergründen der Farbschemen. Wir fügen Beschriftungen für das Symbol in der Kopfzeile mit dem Zeichen 181 aus Wingdings hinzu, den Titel „Korrelationsmatrix“, die Schaltfläche zum Schließen mit dem Buchstaben „r“ aus Webdings, wobei der Tooltip über ObjectSetString mit OBJPROP_TOOLTIP auf „Fenster schließen“ gesetzt wird, der Umschalt-Schaltfläche mit „r“ aus Wingdings und Tooltip für Umschalten zwischen Minimieren/Maximieren, der Heatmap-Schaltfläche mit dynamischem Symbol je nach Anzeigemodus und Tooltip für Umschalten zwischen Heatmap/Standard, der p-Wert-Schaltfläche mit „X“ aus Wingdings und Tooltip für das Umschalten zwischen der Darstellung von Korrelationen und p-Werten, die Sortier-Schaltfläche mit Symbol abhängig von „sort_mode“ („N“ für Original, „K“ für absteigend, ‚J‘ für aufsteigend) aus Wingdings 3 und Tooltip zum Sortieren nach Stärkezyklusmodi sowie Farbschemen-Schaltfläche mit ‚[‘ aus Wingdings und Tooltip zum Umschalten zwischen dunklem und hellem Farbschema – alles unter Verwendung der Farben des jeweiligen Farbschemas.

Für die Zeile mit dem Zeitrahmen berechnen wir die Positionen und erstellen in einer Schleife Rechtecke mit einem vom aktuellen Index abhängigen Hintergrund und einer Kopffarbe sowie Beschriftungen mit Zeitrahmen-Strings in fett mit Arial Bold. Wir erstellen das Kopfzeilenrechteck der Symbole und den Text als Symbole mit den Farben des Farbschemas. Für Zeilensymbole erstellen wir in einer Schleife Rechtecke mit einem Kopfhintergrund und Beschriftungen mit Symbolnamen. In ähnlicher Weise erstellen wir Rechtecke und Beschriftungen für Spaltensymbole. Für die Zellen werden Schleifen verschachtelt, um Rechtecke mit einem neutralen Hintergrund und anfänglichen Textbeschriftungen als 0,00 in Arial zu erstellen. Anschließend wird das Legendenfeld mit dem Haupthintergrund erstellt, „recreate_legend" aufgerufen und neu gezeichnet. Dann aktualisieren wir die Funktion „update_dashboard", um Ansichtsmodi und Farbschemen zu unterstützen. Eine ähnliche Logik wird auch verwendet. Wenn wir diese Funktionen zu Testzwecken aufrufen, erhalten wir folgende Ergebnisse für das dunkle und das helle Farbschema.

HELLES UND DUNKLES FARBSCHEMA

Nachdem wir nun die Modi der Farbschemen in den Funktionen zur Erstellung des Dashboards gehandhabt haben, müssen wir den Schwebezustand von Schaltflächen und Klicks behandeln. Um dies zu erreichen, benötigen wir die folgenden Hilfsfunktionen.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Hier implementieren wir die Funktion „is_cursor_in_header_or_buttons", um festzustellen, ob sich der Mauszeiger über der Kopfzeile oder über interaktiven Elementen wie Schaltflächen oder Zeitrahmenzellen befindet, und geben einen booleschen Wert zurück. Die Chartbreite wird mit ChartGetInteger unter Verwendung von CHART_WIDTH_IN_PIXELS abgerufen, und Header-Eigenschaften wie x, y, Breite und Höhe über ObjectGetInteger mit „OBJPROP_XDISTANCE", „OBJPROP_YDISTANCE", „OBJPROP_XSIZE" und „OBJPROP_YSIZE". Wir berechnen den linken und rechten Rand der Kopfzeile und prüfen, ob die Mauskoordinaten innerhalb der Kopfzeilengrenzen liegen.

Für Schaltflächen berechnen wir die Hälfte der „BUTTON_HOVER_SIZE", holen die x- und y-Positionen jeder Schaltfläche auf die gleiche Weise und überprüfen, ob sich die Maus innerhalb der Hover-Bereiche für Schließen, Umschalten, die Heatmap, den p-Wert, Sortieren und Farbschema befindet. Für Zeitrahmenzellen initialisieren wir ein Flag auf false und ziehen eine Schleife über „num_tf_visible", wobei wir die Position jedes Rechtecks mit dem Namen aus dem Präfix „TF_CELL_RECT" und dem Index ermitteln, prüfen, ob sich die Maus innerhalb der Breite und Höhe befindet, und das Flag auf true setzen. Wir geben „true“ zurück, wenn sich das Element im Kopfbereich, auf einer Schaltfläche oder im Zeitraumbereich befindet.

Als Nächstes erstellen wir die Funktion „update_button_hover_states", um das visuelle Feedback für die Schaltflächen- und Zeitrahmen-Hover auf der Grundlage der Mausposition zu aktualisieren. Wir berechnen die Hälfte der Hover-Größe, setzen den Hover-Hintergrund auf Dodger-Blau und die normalen Farben für Schaltflächen, Farbschema und Schließen entsprechend des Farbschemas. Für jede Schaltfläche werden die Positionen ermittelt, der Schwebezustand wie oben geprüft, und wenn der Zustand von „prev_close_hovered" oder ähnlichem abweicht, wird die Farbe des Objekts mit ObjectSetInteger unter Verwendung von „OBJPROP_COLOR" und OBJPROP_BGCOLOR auf „schwebend" oder „normal" und der Hintergrund auf „schwebend" oder „keine" aktualisiert, der vorherige Zustand eingestellt und neu gezeichnet. Was die Zeitrahmen betrifft: Wir durchlaufen die sichtbaren Zellen, bilden Namen, ermitteln Positionen, prüfen, ob der Mauszeiger innerhalb der Grenzen schwebt, und wenn sich der Wert im Array „prev_tf_hovered“ geändert hat, setzen wir den Hintergrund beim Schweben auf „Dodger Blue“ oder bei Auswahl auf „Strong Positive“, andernfalls auf den normalen Themenhintergrund, aktualisieren mit „ObjectSetInteger“, setzen den vorherigen Zustand und zeichnen neu.

Anschließend definieren wir die Funktion „update_header_hover_state" zur Verwaltung der visuellen Kopfzeilen-Hover, mit Ausnahme der Schaltflächenbereiche. Wir rufen wie zuvor die Positionen und Größen der Kopfzeilen ab, berechnen die halbe Hover-Größe und prüfen die einzelnen Schaltflächenbereiche für Schließen, Umschalten, Heatmap, p-Wert, Sortieren und Farbschema. Wir stellen fest, ob der Mauszeiger in der Kopfzeile schwebt, aber nicht in einem Schaltflächenbereich, und wenn der Zustand von „prev_header_hovered" abweicht und nicht gezogen wird, aktualisieren wir den Kopfzeilenhintergrund mit „ObjectSetInteger" und „OBJPROP_BGCOLOR" auf Rot beim Schweben oder normal, setzen den vorherigen Zustand und zeichnen neu. Schließlich rufen wir „update_button_hover_states" auf, um das Überfahren von Schaltflächen gleichzeitig zu behandeln. Wir werden diese Funktionen nun im Ereignis-Handler der Chart-Interaktion verwenden. Um jedoch das Ziehen bei Mausbewegungen zu handhaben, benötigen wir eine weitere Hilfsfunktion, um die Positionen der Dashboard-Elemente zusammen mit der Maus dynamisch zu aktualisieren.

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

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

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

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

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

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

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

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

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

Hier implementieren wir die Funktion „update_panel_positions", um die Positionen aller Dashboard-Objekte anzupassen, wenn das Panel gezogen oder in der Größe verändert wird, um sicherzustellen, dass alles mit den aktuellen Werten „panel_x" und „panel_y" übereinstimmt. Wir aktualisieren die x- und y-Abstände der Kopfleiste und berechnen ihre Breite neu, um sie an das Symbol- und Zellenlayout anzupassen, indem wir „OBJPROP_XSIZE" entsprechend einstellen. Wir positionieren dann das Kopfzeilensymbol, den Text und die Schaltflächen wie Schließen, Umschalten, Heatmap, p-Wert, Sortieren und Farbschema neu, indem wir ihren relativen x-Versatz vom Panelrand berechnen und OBJPROP_XDISTANCE und OBJPROP_YDISTANCE" mit ObjectSetInteger" einstellen.

Wenn nicht minimiert, berechnen wir die gesamte Panelhöhe aus Konstanten und Zeilen und aktualisieren die Position, Größe und Abmessungen des Hauptpanels auf ähnliche Weise. Für die Zeile mit dem Zeitrahmen werden y und das Start-X berechnet, dann wird eine Schleife über die sichtbaren Zeitrahmen gezogen, um die X- und Y-Werte der einzelnen Rechtecke und Textbeschriftungen zu aktualisieren. Wir setzen die Matrix y, aktualisieren das Kopfzeilenrechteck der Symbole und die Textpositionen. Für Zeilensymbole berechnen wir in einer Schleife den y-Versatz und aktualisieren die x- und y-Werte der Rechtecke und Texte. Für die Spaltensymbole gilt das Gleiche für die x-Offsets und die festen y-Werte.

Für verschachtelte Zellen bilden wir Namen, berechnen Versätze und aktualisieren die Positionen von Rechtecken und Texten. Für die Legende berechnen wir ihr y unterhalb des Feldes mit einer Lücke und aktualisieren das x, y, die Breite und die Höhe des Legendenfeldes. Wir berechnen die Gesamtbreite der Legende und das Start-X für die Zentrierung und führen dann eine Schleife über die sichtbaren Elemente durch, um die X- und Y-Abstände der einzelnen Rechtecke und Texte zu aktualisieren. Schließlich wird mit ChartRedraw neu gezeichnet. Nun müssen wir die Erkennung von Mausereignissen aktivieren, damit wir sie nutzen können. Wir aktivieren dies in der Ereignisbehandlung von OnInit, indem wir den folgenden Codeschnipsel hinzufügen.

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

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

Wir verwenden einfach ChartSetInteger, um die Registrierung der Mausereignisse mit der Direktive CHART_EVENT_MOUSE_MOVE zu aktivieren. Wir können nun die Ereignislogik unseres Charts in OnChartEvent erstellen. Um dies zu erreichen, haben wir die folgende Logik angewandt.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

In der Ereignisbehandlung von OnChartEvent für CHARTEVENT_OBJECT_CLICK prüfen wir den Schaltflächennamen in „string_param": Lautet er „CLOSE_BUTTON", drucken wir eine Meldung, spielen einen Warnton mit PlaySound, setzen „panel_is_visible" auf false, rufen „delete_all_objects" auf und zeichnen mit „ChartRedraw" neu; ist er „TOGGLE_BUTTON" werden Objekte gelöscht, „panel_minimized" umgeschaltet und, falls minimiert, gedruckt, ein minimiertes Dashboard erstellt und die Ränder aktualisiert; andernfalls, falls maximiert, gedruckt, ein vollständiges Dashboard erstellt und die Ränder, Hervorhebungen und das Dashboard aktualisiert. Wir setzen alle vorherigen Hover-Zustände auf false zurück, einschließlich des Arrays mit ArrayInitialize für Timeframes, setzen den thematischen Kopfzeilen-Hintergrund und wenden ihn auf das Kopfzeilen-Panel an, aktualisieren die Farben für Icon, Close, Toggle und, falls nicht minimiert, für Heatmap, p-Wert, Sortierung und die Schaltfläche für das Farbschema, setzen deren Hintergründe auf none zurück und zeichnen dann neu.

Für CHARTEVENT_MOUSE_MOVE, wenn „panel_is_visible" wahr ist, setzen wir „long_param" auf Maus x, „double_param" auf y und „string_param" auf state. Wenn die Koordinaten mit „last_mouse_x" und „last_mouse_y" übereinstimmen und nicht gezogen werden, verlassen wir das Programm vorzeitig; andernfalls aktualisieren wir die letzten Positionen und rufen „update_header_hover_state" mit x und y auf. Wir rufen Header-Eigenschaften für Bounds und Button-Positionen ab, berechnen die halbe Hover-Größe, und prüfen Bereiche für Close, Toggle, Heatmap, p-Wert, Sortierung und Farbschema. Wenn der vorherige Zustand null war und der aktuelle eins ist und sich die Maus in der Kopfzeile, aber nicht in den Schaltflächenbereichen befindet, beginnen Sie mit dem Ziehen, indem Sie „panel_dragging" auf true setzen, die Zieh- und Startkoordinaten speichern, den Hintergrund der Kopfzeile auf mittelblau setzen und den Mauslauf im Chart deaktivieren.

Wenn das Ziehen aktiv ist und der Status „1“ ist, berechnen wir die Deltas, aktualisieren „panel_x“ und „panel_y“ ausgehend vom Startwert plus Deltas, rufen die Chart-Abmessungen mit ChartGetInteger für Breite und Höhe in Pixeln ab, berechnen die Panel-Abmessungen sowie die Gesamthöhe einschließlich Legende und begrenzen „panel_x“ und „panel_y“ mithilfe von MathMax und MathMin, um innerhalb der Chartgröße abzüglich der Panelgröße oder der Kopfzeile zu bleiben, falls minimiert, rufen dann „update_panel_positions“ auf und zeichnen alles neu. Wenn der Status null ist und zuvor eins war und gerade ein Ziehen stattfand, dann wird „panel_dragging“ beendet, „update_header_hover_state“ aufgerufen, das Scrollen aktiviert und die Anzeige neu gezeichnet. Schließlich wird „prev_mouse_state" auf den aktuellen Zustand aktualisiert. Wenn das erledigt ist, müssen wir das Dashboard konsequent aktualisieren, da wir keine Aktualisierungen aufrufen müssen, wenn das Dashboard geschlossen oder minimiert ist. Das wäre sinnlos. In der Ereignisbehandlung von Tick-Ereignissen verwenden wir also die folgende Logik.

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

Wir ändern die Ereignisbehandlung in OnTick, um das Dashboard nur dann zu aktualisieren, wenn dies aus Effizienzgründen erforderlich ist. Es wird nun geprüft, ob „panel_is_visible" wahr und nicht „panel_minimized" ist, und wenn ja, wird „update_dashboard" aufgerufen, um Korrelationen, Grafiken und andere Elemente bei jedem neuen Tick neu zu berechnen und zu aktualisieren. Zum Schluss, wenn wir das Programm schließen, wollen wir unsere Dashboard-Komponenten löschen, um das Chart zu bereinigen und die Chart-Ereignisse zu deaktivieren.

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

In der Ereignisbehandlung von OnDeinit fügen wir eine Logik zum Aufräumen der Ressourcen hinzu, wenn das Programm entfernt oder deinitialisiert wird. Sie ruft „delete_all_objects" auf, um alle grafischen Elemente aus dem Chart zu entfernen, deaktiviert Mausbewegungsereignisse im Chart mit ChartSetInteger unter Verwendung von CHART_EVENT_MOUSE_MOVE, das auf false gesetzt ist, und zeichnet das Chart über ChartRedraw neu, um die Änderungen wiederzugeben. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

VOLLSTÄNDIGER TEST GIF

Anhand der Visualisierung können wir sehen, dass wir das Dashboard der Korrelationsmatrix mit allen durchgeführten Interaktionen verbessert und somit 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 Bild im Bitmap-Format Graphics Interchange Format (GIF).

KORRELATIONSMATRIX BACKTEST GIF


Schlussfolgerung

Abschließend haben wir das Dashboard der Korrelationsmatrix in MQL5 um interaktive Funktionen erweitert, darunter das Ziehen und Minimieren des Panels über Mausereignisse, Hover-Effekte für visuelles Feedback, die Sortierung von Symbolen nach Korrelationsstärke in auf- und absteigenden Modi, das Umschalten zwischen den Darstellungen der Korrelationen und der p-Werte, das Umschalten zwischen hellem und dunklem Farbschema mit dynamischen Farbaktualisierungen und Tooltips über den Zellen für detaillierte Einblicke. Das System unterstützt jetzt ereignisgesteuerte Reaktionen zur Verbesserung der Nutzerfreundlichkeit, mit Erkennung von Schwebezuständen, Begrenzung während des Ziehens, um innerhalb der Chartgrenzen zu bleiben, und effizienten Aktualisierungen zur Aufrechterhaltung der Leistung. Mit diesem interaktiven Korrelationsmatrix-Dashboard sind Sie in der Lage, die Interdependenzen von Vermögenswerten dynamischer zu analysieren und Ihre Handelsaktivitäten weiter zu optimieren. Viel Spaß beim Handeln!

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

Letzte Kommentare | Zur Diskussion im Händlerforum (4)
Criistian Moore'
Criistian Moore' | 25 Jan. 2026 in 03:01
Haben Sie eine Idee, warum die Symbole der Schaltflächen nicht angezeigt werden, es werden nur kleine Rechtecke angezeigt.
Allan Munene Mutiiria
Allan Munene Mutiiria | 25 Jan. 2026 in 14:44
Criistian Moore' #:
Haben Sie eine Idee, warum die Symbole der Schaltflächen nicht angezeigt werden, es werden nur kleine Rechtecke angezeigt.
Die Schriftarten sind auf Ihrem PC noch nicht verfügbar
Vitaly Muzichenko
Vitaly Muzichenko | 25 März 2026 in 13:21
Criistian Moore' #:
Wissen Sie, warum die Schaltflächensymbole nicht angezeigt werden, sondern nur kleine Rechtecke?
Aktualisieren Sie das Terminal auf die neueste Version, dann sollte es funktionieren
Юрий Елагин
Юрий Елагин | 27 März 2026 in 22:41
Ich beschloss, nur zu schreiben, weil ich eine sehr ähnliche Tabelle mit Claude AI generiert, in meiner Version, wenn Sie den Cursor über ein paar Werkzeuge und drücken Sie die Eingabetaste in einem zusätzlichen Fenster, ein Korrelationsdiagramm für die letzten N Candlesticks erschien, die der Benutzer selbst einstellen konnte. Aber einige Nuancen der Tabelle in Ihrer Version gefallen mir besser. Wenn man die Zeichnung des Charts hinzufügt, um die Korrelation der Paare in der Historie zu analysieren, hat man eine Kanone.
Die Übertragung der Trading-Signale in einem universalen Expert Advisor. Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
In diesem Artikel wurden die verschiedenen Möglichkeiten beschrieben, um die Trading-Signale von einem Signalmodul des universalen EAs zum Steuermodul der Positionen und Orders zu übertragen. Es wurden die seriellen und parallelen Interfaces betrachtet.
Klassische Strategien neu interpretieren (Teil 21): Entdeckung einer Ensemble-Strategie aus Bollinger-Bändern und RSI Klassische Strategien neu interpretieren (Teil 21): Entdeckung einer Ensemble-Strategie aus Bollinger-Bändern und RSI
Dieser Artikel befasst sich mit der Entwicklung einer algorithmischen Handelsstrategie für den EURUSD-Markt, die die Bollinger-Bänder und den Relative Strength Indicator (RSI) kombiniert. Die ersten regelbasierten Strategien lieferten zwar hochwertige Signale, litten aber unter einer geringen Handelsfrequenz und begrenzter Rentabilität. Mehrere Iterationen der Strategie wurden evaluiert, wobei sich herausstellte, dass unser Verständnis des Marktes unzureichend war, das Rauschen zunahm und die Leistung sich verschlechterte. Durch den angemessenen Einsatz statistischer Lernalgorithmen, die Verlagerung des Modellierungsziels auf technische Indikatoren, die Anwendung einer angemessenen Skalierung und die Kombination von maschinellen Lernprognosen mit klassischen Handelsregeln erzielte die endgültige Strategie eine deutlich verbesserte Rentabilität und Handelshäufigkeit bei gleichzeitig akzeptabler Signalqualität.
Eine alternative Log-datei mit der Verwendung der HTML und CSS Eine alternative Log-datei mit der Verwendung der HTML und CSS
In diesem Artikel werden wir eine sehr einfache, aber leistungsfähige Bibliothek zur Erstellung der HTML-Dateien schreiben, dabei lernen wir auch, wie man eine ihre Darstellung einstellen kann (nach seinem Geschmack) und sehen wir, wie man es leicht in seinem Expert Advisor oder Skript hinzufügen oder verwenden kann.
Entwicklung eines Toolkits zur Preisaktionsanalyse (Teil 55): Entwurf eines Overlays der CPI-Minikerzen für Intra-Bar-Druck Entwicklung eines Toolkits zur Preisaktionsanalyse (Teil 55): Entwurf eines Overlays der CPI-Minikerzen für Intra-Bar-Druck
Dieser Artikel stellt das Design und die MetaTrader 5-Implementierung des Candle Pressure Index (CPI) vor – ein CLV-basiertes Overlay, das den Kauf- und Verkaufsdruck innerhalb einer Kerze direkt auf dem Preischart anzeigt. Die Diskussion konzentriert sich auf die Kerzenstruktur, die Druckklassifizierung, die Visualisierungsmechanismen und ein übergangsloses, übergangsorientiertes Warnsystem, das für ein konsistentes Verhalten über Zeitrahmen und Instrumente hinweg konzipiert ist.