English 日本語
preview
MQL5-Handelswerkzeuge (Teil 4): Verbesserung des Dashboards des Multi-Timeframe-Scanners mit dynamischer Positionierung und Umschaltfunktionen

MQL5-Handelswerkzeuge (Teil 4): Verbesserung des Dashboards des Multi-Timeframe-Scanners mit dynamischer Positionierung und Umschaltfunktionen

MetaTrader 5Handel |
95 2
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In unserem vorherigen Artikel (Teil 3) haben wir ein Multi-Timeframe Scanner Dashboard in MetaQuotes Language 5 (MQL5) entwickelt, das die Indikatoren Relative Strength Index (RSI), Stochastic, Commodity Channel Index (CCI), Average Directional Index (ADX) und den Awesome Oszillator (AO) über mehrere Zeitrahmen anzeigt, um Handelssignale für das aktuelle Symbol zu identifizieren. In Teil 4 verbessern wir dieses Dashboard, indem wir eine dynamische Positionierung hinzufügen, um das Ziehen über das Chart zu ermöglichen, und eine Umschaltfunktion, um die Anzeige zu minimieren oder zu maximieren, was die Nutzerfreundlichkeit und die Bildschirmverwaltung verbessert. Wir werden die folgenden Themen behandeln:

  1. Verstehen der Architektur der dynamischen Positionierung und des Umschaltens
  2. Implementation in MQL5
  3. Backtests
  4. Schlussfolgerung

Am Ende haben Sie ein fortschrittliches MQL5-Dashboard mit flexibler Positionierung und Toggle-Funktionalität, bereit für Tests und weitere Anpassungen - legen wir los!


Verstehen der Architektur der dynamischen Positionierung und des Umschaltens

Wir verbessern unser Multi-Timeframe-Scanner-Dashboard, indem wir eine dynamische Positionierung hinzufügen, sodass es über das Chart gezogen werden kann, und einen Umschalter, um es zu minimieren oder zu maximieren, was die Nutzerfreundlichkeit verbessert. Diese Funktionen sind wichtig, um ein unübersichtliches Chart zu vermeiden und den Bildschirmplatz für eine effiziente Handelsanalyse zu optimieren. Wir werden ein mausgesteuertes Ziehen zur Neupositionierung des Dashboards und eine Umschalttaste zum Wechseln zwischen Kompakt- und Vollansicht implementieren, um eine nahtlose Aktualisierung der Indikatoren für bessere Handelsentscheidungen zu gewährleisten. Kurz gesagt, hier ist eine Visualisierung dessen, was wir erreichen wollen.

POSITIONIERUNG UND ZIEHOPTIONEN


Implementation in MQL5

Um die Verbesserungen in MQL5 zu implementieren, werden wir ein zusätzliches Tasten-Objekt definieren, das wir später zum Umschalten zwischen maximiertem und minimiertem Zustand verwenden werden, da das Hovern bzw. Schweben und Ziehen auf dem Header-Objekt erfolgen wird, das wir bereits in der Implementierung haben.

// Define identifiers and properties for UI elements
#define MAIN_PANEL              "PANEL_MAIN"                     //--- Main panel rectangle identifier

//--- THE REST OF THE EXISTING OBJECTS

#define TOGGLE_BUTTON           "BUTTON_TOGGLE"                  //--- Toggle (minimize/maximize) button identifier

//--- THE REST OF THE EXISTING OBJECTS

#define COLOR_DARK_GRAY         C'105,105,105'                   //--- Dark gray color for indicator backgrounds

Wir beginnen die Verbesserung unseres Multi-Timeframe-Scanner-Dashboards mit der Aktualisierung der Benutzeroberfläche (UI)-Elementdefinitionen, um einen neuen Bezeichner für die Umschalttaste einzufügen, der unser Ziel unterstützt, die Funktionen zum Minimieren/Maximieren hinzuzufügen. Wir behalten die bestehenden Definitionen bei, um die Kernstruktur des Dashboards zu erhalten. Die wichtigste Änderung ist die Hinzufügung des Bezeichners „TOGGLE_BUTTON“, der als „BUTTON_TOGGLE“ definiert ist und es uns ermöglicht, eine Schaltfläche zum Umschalten des Dashboards zwischen minimiertem und maximiertem Zustand zu erstellen.

Diese Ergänzung ist von entscheidender Bedeutung, da sie die neue Umschaltfunktion ermöglicht, die es uns erlaubt, das Dashboard zu verkleinern, um Platz auf dem Bildschirm zu sparen, oder zu vergrößern, um es vollständig sichtbar zu machen. Dann brauchen wir etwas mehr Kontrolle über die globalen Variablen, die wir zum Speichern der Dashboard-Zustände verwenden werden.

bool panel_minimized = false;                                    //--- Flag to control minimized state
int panel_x = 632, panel_y = 40;                                 //--- Panel position coordinates
bool panel_dragging = false;                                     //--- Flag to track if panel is being dragged
int panel_drag_x = 0, panel_drag_y = 0;                          //--- Mouse coordinates when drag starts
int panel_start_x = 0, panel_start_y = 0;                        //--- Panel coordinates when drag starts
int prev_mouse_state = 0;                                        //--- Variable to track previous mouse state
bool header_hovered = false;                                     //--- Header hover state
bool toggle_hovered = false;                                     //--- Toggle button hover state
bool close_hovered = false;                                      //--- Close button hover state
int last_mouse_x = 0, last_mouse_y = 0;                          //--- Track last mouse position for optimization
bool prev_header_hovered = false;                                //--- Track previous header hover state
bool prev_toggle_hovered = false;                                //--- Track previous toggle hover state
bool prev_close_hovered = false;                                 //--- Track previous close button hover state

Um die Zustände des Dashboards zu verfolgen, fügen wir einige globale Variablen hinzu. Wir führen „panel_minimized“ ein, um den minimierten Zustand zu verfolgen, „panel_x“ und „panel_y“ für die Position des Dashboards, und „panel_dragging“, „panel_drag_x“, „panel_drag_y“, „panel_start_x“, und „panel_start_y“ für das Ziehen. Wir fügen auch „prev_mouse_state“, „header_hovered“, „toggle_hovered“, „close_hovered“, „last_mouse_x“, „last_mouse_y“, „prev_header_hovered“, „prev_toggle_hovered“ und „prev_close_hovered“, um Mausereignisse und Schwebezustände zu verwalten. Diese ermöglichen ein verschiebbares, interaktives und umschaltbares Dashboard.

Anschließend können wir die Umschalttaste in das Dashboard einfügen. Um die Schaltzustände zu verwalten, müssen wir die Erstellungslogik in separaten Funktionen für die maximierten und minimierten Panels haben. Beginnen wir mit dem maximierten Dashboard.

//+------------------------------------------------------------------+
//| Create full dashboard UI                                         |
//+------------------------------------------------------------------+
void create_full_dashboard() {
   create_rectangle(MAIN_PANEL, panel_x, panel_y, 617, 374, C'30,30,30', BORDER_FLAT); //--- Create main panel background
   create_rectangle(HEADER_PANEL, panel_x, panel_y, 617, 27, C'60,60,60', BORDER_FLAT); //--- Create header panel background
   create_label(HEADER_PANEL_ICON, CharToString(91), panel_x - 12, panel_y + 14, 18, clrAqua, "Wingdings"); //--- Create header icon
   create_label(HEADER_PANEL_TEXT, "TimeframeScanner", panel_x - 105, panel_y + 12, 13, COLOR_WHITE); //--- Create header title
   create_label(CLOSE_BUTTON, CharToString('r'), panel_x - 600, panel_y + 14, 18, clrYellow, "Webdings"); //--- Create close button
   create_label(TOGGLE_BUTTON, CharToString('r'), panel_x - 570, panel_y + 14, 18, clrYellow, "Wingdings"); //--- Create minimize button (-)

   // Create header rectangle and label
   create_rectangle(SYMBOL_RECTANGLE, panel_x - 2, panel_y + 35, WIDTH_TIMEFRAME, HEIGHT_RECTANGLE, clrGray); //--- Create symbol rectangle
   create_label(SYMBOL_TEXT, _Symbol, panel_x - 47, panel_y + 45, 11, COLOR_WHITE); //--- Create symbol label
   
   // Create summary and indicator headers (rectangles and labels)
   string header_names[] = {"BUY", "SELL", "RSI", "STOCH", "CCI", "ADX", "AO"}; //--- Define header titles
   for(int header_index = 0; header_index < ArraySize(header_names); header_index++) { //--- Loop through headers
      int x_offset = panel_x - WIDTH_TIMEFRAME - (header_index < 2 ? header_index * WIDTH_SIGNAL : 2 * WIDTH_SIGNAL + (header_index - 2) * WIDTH_INDICATOR) + (1 + header_index); //--- Calculate x position
      int width = (header_index < 2 ? WIDTH_SIGNAL : WIDTH_INDICATOR); //--- Set width based on header type
      create_rectangle(HEADER_RECTANGLE + IntegerToString(header_index), x_offset, panel_y + 35, width, HEIGHT_RECTANGLE, clrGray); //--- Create header rectangle
      create_label(HEADER_TEXT + IntegerToString(header_index), header_names[header_index], x_offset - width/2, panel_y + 45, 11, COLOR_WHITE); //--- Create header label
   }
   
   // Create timeframe rectangles and labels, and summary/indicator cells
   for(int timeframe_index = 0; timeframe_index < ArraySize(timeframes_array); timeframe_index++) { //--- Loop through timeframes
      // Highlight current timeframe
      color timeframe_background = (timeframes_array[timeframe_index] == _Period) ? clrLimeGreen : clrGray; //--- Set background color for current timeframe
      color timeframe_text_color = (timeframes_array[timeframe_index] == _Period) ? COLOR_BLACK : COLOR_WHITE; //--- Set text color for current timeframe
      
      create_rectangle(TIMEFRAME_RECTANGLE + IntegerToString(timeframe_index), panel_x - 2, (panel_y + 35 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index), WIDTH_TIMEFRAME, HEIGHT_RECTANGLE, timeframe_background); //--- Create timeframe rectangle
      create_label(TIMEFRAME_TEXT + IntegerToString(timeframe_index), truncate_timeframe_name(timeframe_index), panel_x - 47, (panel_y + 45 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index), 11, timeframe_text_color); //--- Create timeframe label
                  
      // Create summary and indicator cells
      for(int header_index = 0; header_index < ArraySize(header_names); header_index++) { //--- Loop through headers for cells
         string cell_rectangle_name, cell_text_name;              //--- Declare cell name and label variables
         color cell_background = (header_index < 2) ? COLOR_LIGHT_GRAY : COLOR_BLACK; //--- Set cell background color
         switch(header_index) {                                   //--- Select cell type
            case 0: cell_rectangle_name = BUY_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = BUY_TEXT + IntegerToString(timeframe_index); break; //--- Buy cell
            case 1: cell_rectangle_name = SELL_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = SELL_TEXT + IntegerToString(timeframe_index); break; //--- Sell cell
            case 2: cell_rectangle_name = RSI_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = RSI_TEXT + IntegerToString(timeframe_index); break; //--- RSI cell
            case 3: cell_rectangle_name = STOCH_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = STOCH_TEXT + IntegerToString(timeframe_index); break; //--- Stochastic cell
            case 4: cell_rectangle_name = CCI_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = CCI_TEXT + IntegerToString(timeframe_index); break; //--- CCI cell
            case 5: cell_rectangle_name = ADX_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = ADX_TEXT + IntegerToString(timeframe_index); break; //--- ADX cell
            case 6: cell_rectangle_name = AO_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = AO_TEXT + IntegerToString(timeframe_index); break; //--- AO cell
         }
         int x_offset = panel_x - WIDTH_TIMEFRAME - (header_index < 2 ? header_index * WIDTH_SIGNAL : 2 * WIDTH_SIGNAL + (header_index - 2) * WIDTH_INDICATOR) + (1 + header_index); //--- Calculate x position
         int width = (header_index < 2 ? WIDTH_SIGNAL : WIDTH_INDICATOR); //--- Set width based on cell type
         create_rectangle(cell_rectangle_name, x_offset, (panel_y + 35 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index), width, HEIGHT_RECTANGLE, cell_background); //--- Create cell rectangle
         create_label(cell_text_name, "-/-", x_offset - width/2, (panel_y + 45 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index), 10, COLOR_WHITE); //--- Create cell label
      }
   }
   ChartRedraw(0);
}

Wir implementieren die Dashboard-Zustände, indem wir die Funktion „create_full_dashboard“ erstellen, die statische Erstellungslogik dorthin verlagern und sie so aktualisieren, dass sie dynamische Positionierungs- und Umschaltfunktionen unterstützt. Die wichtigste Änderung ist die Integration der Variablen „panel_x“ und „panel_y“ in die Positionierung aller Dashboard-Elemente, sodass das Dashboard an beliebiger Stelle im Chart platziert werden kann. Wir erstellen das Hauptpanel mit „create_rectangle“ unter Verwendung von „MAIN_PANEL“, „panel_x“ und „panel_y“ für die Position und behalten die Größe von 617x374 bei. In ähnlicher Weise positionieren wir den Kopfbereich, das Symbol und den Titel mit „create_rectangle“ und „create_label“ für „HEADER_PANEL“, „HEADER_PANEL_ICON“ und „HEADER_PANEL_TEXT“, wobei wir ihre x- und y-Koordinaten relativ zu „panel_x“ und „panel_y“ anpassen.

Wir fügen den neuen „TOGGLE_BUTTON“ mit „create_label“ hinzu, platzieren ihn bei „panel_x - 570“ und „panel_y + 14“ und zeigen ein Minimierungssymbol („r“ in Webdings) an. Das Symbolrechteck und die Beschriftung („SYMBOL_RECTANGLE“ und „SYMBOL_TEXT“) und die Kopfzeilenrechtecke und -beschriftungen („HEADER_RECTANGLE“ und „HEADER_TEXT“) verwenden „panel_x“ und „panel_y“ für ihre x- und y-Versätze, um sicherzustellen, dass sie sich mit dem Panel bewegen. Für jeden Zeitrahmen in „timeframes_array“ werden Zeitrahmenrechtecke und -beschriftungen („TIMEFRAME_RECTANGLE“ und „TIMEFRAME_TEXT“) sowie Indikatorzellen („RSI_RECTANGLE“, „STOCH_RECTANGLE“, etc.) mit relativ zu „panel_x“ und „panel_y“ berechneten Positionen, wobei das Layout beibehalten wird, es aber verschiebbar ist. Wir rufen die Funktion ChartRedraw auf, um die Anzeige zu aktualisieren. Im Gegensatz zu den festen Koordinaten der Vorgängerversion (z. B. 632, 40) verwendet diese Funktion dynamische Koordinaten, die es ermöglichen, das Dashboard zu verschieben, und fügt eine Umschalttaste zum Minimieren/Maximieren hinzu. Wir werden etwas erhalten, das dem unten stehenden Beispiel ähnelt.

MAXIMIERTER ZUSTAND

Wir können dann eine Funktion haben, um das minimierte Panel zu erstellen.

//+------------------------------------------------------------------+
//| Create minimized dashboard UI                                    |
//+------------------------------------------------------------------+
void create_minimized_dashboard() {
   create_rectangle(HEADER_PANEL, panel_x, panel_y, 617, 27, C'60,60,60', BORDER_FLAT); //--- Create header panel background
   create_label(HEADER_PANEL_ICON, CharToString(91), panel_x - 12, panel_y + 14, 18, clrAqua, "Wingdings"); //--- Create header icon
   create_label(HEADER_PANEL_TEXT, "TimeframeScanner", panel_x - 105, panel_y + 12, 13, COLOR_WHITE); //--- Create header title
   create_label(CLOSE_BUTTON, CharToString('r'), panel_x - 600, panel_y + 14, 18, clrYellow, "Webdings"); //--- Create close button
   create_label(TOGGLE_BUTTON, CharToString('o'), panel_x - 570, panel_y + 14, 18, clrYellow, "Wingdings"); //--- Create maximize button (+)
   ChartRedraw(0);
}

Um das Umschalten auf eine kompakte Ansicht zu unterstützen, definieren wir die Funktion „create_minimized_dashboard“. Wir erstellen das Header-Panel mit „create_rectangle“ für „HEADER_PANEL“ bei „panel_x“ und „panel_y“, fügen „HEADER_PANEL_ICON“ und „HEADER_PANEL_TEXT“ mit „create_label“ für das Symbol und den Titel, fügen „CLOSE_BUTTON“ und „TOGGLE_BUTTON“ mit einem Maximierungssymbol („o“ in Webdings) an „panel_x - 570“ und „panel_y + 14“ hinzu. Wir rufen „ChartRedraw“ auf, um die Anzeige zu aktualisieren und einen beweglichen, minimierten Zustand des Dashboards zu ermöglichen. Wie Sie vielleicht bemerkt haben, haben wir aus Gründen der Konsistenz den Schriftfont Wingdings sowohl für die Maximieren- als auch für die Minimieren-Schaltfläche gewählt. Sie können wählen, was Ihnen gefällt. In unserem Fall sind die Symbolzeichen „r“ bzw. „o“. Hier finden Sie eine zentrale Übersicht über sie.

WINGDINGS SCHRIFTZEICHEN

Wenn Sie den minimierten Panelzustand ausführen, erhalten Sie folgendes Ergebnis.

MINIMIERTER ZUSTAND

Mit den Funktionen ausgestattet, können wir je nach Nutzerbefehl auswählen, welche wir verwenden wollen. Da wir nun mehrere Objekte erstellt haben, können wir das Löschen aller Objekte in einer Funktion zentralisieren, da wir die Objekte löschen und automatisch neu erstellen müssen.

//+------------------------------------------------------------------+
//| 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 title
   ObjectDelete(0, CLOSE_BUTTON);                                //--- Delete close button
   ObjectDelete(0, TOGGLE_BUTTON);                               //--- Delete toggle button

   ObjectsDeleteAll(0, SYMBOL_RECTANGLE);                        //--- Delete all symbol rectangles
   ObjectsDeleteAll(0, SYMBOL_TEXT);                             //--- Delete all symbol labels
   ObjectsDeleteAll(0, TIMEFRAME_RECTANGLE);                     //--- Delete all timeframe rectangles
   ObjectsDeleteAll(0, TIMEFRAME_TEXT);                          //--- Delete all timeframe labels
   ObjectsDeleteAll(0, HEADER_RECTANGLE);                        //--- Delete all header rectangles
   ObjectsDeleteAll(0, HEADER_TEXT);                             //--- Delete all header labels
   ObjectsDeleteAll(0, RSI_RECTANGLE);                           //--- Delete all RSI rectangles
   ObjectsDeleteAll(0, RSI_TEXT);                                //--- Delete all RSI labels
   ObjectsDeleteAll(0, STOCH_RECTANGLE);                         //--- Delete all Stochastic rectangles
   ObjectsDeleteAll(0, STOCH_TEXT);                              //--- Delete all Stochastic labels
   ObjectsDeleteAll(0, CCI_RECTANGLE);                           //--- Delete all CCI rectangles
   ObjectsDeleteAll(0, CCI_TEXT);                                //--- Delete all CCI labels
   ObjectsDeleteAll(0, ADX_RECTANGLE);                           //--- Delete all ADX rectangles
   ObjectsDeleteAll(0, ADX_TEXT);                                //--- Delete all ADX labels
   ObjectsDeleteAll(0, AO_RECTANGLE);                            //--- Delete all AO rectangles
   ObjectsDeleteAll(0, AO_TEXT);                                 //--- Delete all AO labels
   ObjectsDeleteAll(0, BUY_RECTANGLE);                           //--- Delete all buy rectangles
   ObjectsDeleteAll(0, BUY_TEXT);                                //--- Delete all buy labels
   ObjectsDeleteAll(0, SELL_RECTANGLE);                          //--- Delete all sell rectangles
   ObjectsDeleteAll(0, SELL_TEXT);                               //--- Delete all sell labels
}

Um mehr Kontrolle über den Zeitpunkt des Löschens unserer Objekte zu haben, erstellen und aktualisieren wir die Funktion „delete_all_objects“, um die Entfernung des neuen „TOGGLE_BUTTON“ einzuschließen, was unsere Verbesserung der Toggle-Funktionalität unterstützt. Wir fügen die Funktion ObjectDelete für „TOGGLE_BUTTON“ zur Liste der zu entfernenden Objekte hinzu, um sicherzustellen, dass die Umschalttaste ordnungsgemäß gelöscht wird, wenn das Dashboard geschlossen oder umgeschaltet wird. Alle anderen Objekte wie „MAIN_PANEL“, „HEADER_PANEL“, „HEADER_PANEL_ICON“, „HEADER_PANEL_TEXT“, „CLOSE_BUTTON“ sowie alle Symbol-, Zeitrahmen-, Kopfzeilen-, Indikator- und Signalrechtecke und -kennzeichnungen werden mit der Funktion ObjectsDeleteAll weiterhin gelöscht. Diese Änderung stellt sicher, dass unser bewegliches und minimierbares Dashboard alle Komponenten, einschließlich der neuen Umschalttaste, bereinigt und ein ordentliches Chart beibehält, wenn das Dashboard ausgeblendet oder neu initialisiert wird.

Um die dynamische Positionierung zu verbessern, müssen wir eine Funktion erstellen, die die Dashboard-Elemente auf der Grundlage der Cursorposition erstellt und aktualisiert. Hier ist die Logik, mit der wir das erreichen.

//+------------------------------------------------------------------+
//| Update panel object positions                                    |
//+------------------------------------------------------------------+
void update_panel_positions() {
   // Update header and buttons
   ObjectSetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE, panel_x); //--- Set header panel x position
   ObjectSetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE, panel_y); //--- Set header panel y position
   ObjectSetInteger(0, HEADER_PANEL_ICON, OBJPROP_XDISTANCE, panel_x - 12); //--- Set header icon x position
   ObjectSetInteger(0, HEADER_PANEL_ICON, OBJPROP_YDISTANCE, panel_y + 14); //--- Set header icon y position
   ObjectSetInteger(0, HEADER_PANEL_TEXT, OBJPROP_XDISTANCE, panel_x - 105); //--- Set header title x position
   ObjectSetInteger(0, HEADER_PANEL_TEXT, OBJPROP_YDISTANCE, panel_y + 12); //--- Set header title y position
   ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE, panel_x - 600); //--- Set close button x position
   ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE, panel_y + 14); //--- Set close button y position
   ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE, panel_x - 570); //--- Set toggle button x position
   ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE, panel_y + 14); //--- Set toggle button y position

   if (!panel_minimized) {
      // Update main panel
      ObjectSetInteger(0, MAIN_PANEL, OBJPROP_XDISTANCE, panel_x); //--- Set main panel x position
      ObjectSetInteger(0, MAIN_PANEL, OBJPROP_YDISTANCE, panel_y); //--- Set main panel y position

      // Update symbol rectangle and label
      ObjectSetInteger(0, SYMBOL_RECTANGLE, OBJPROP_XDISTANCE, panel_x - 2); //--- Set symbol rectangle x position
      ObjectSetInteger(0, SYMBOL_RECTANGLE, OBJPROP_YDISTANCE, panel_y + 35); //--- Set symbol rectangle y position
      ObjectSetInteger(0, SYMBOL_TEXT, OBJPROP_XDISTANCE, panel_x - 47); //--- Set symbol text x position
      ObjectSetInteger(0, SYMBOL_TEXT, OBJPROP_YDISTANCE, panel_y + 45); //--- Set symbol text y position

      // Update header rectangles and labels
      string header_names[] = {"BUY", "SELL", "RSI", "STOCH", "CCI", "ADX", "AO"};
      for(int header_index = 0; header_index < ArraySize(header_names); header_index++) { //--- Loop through headers
         int x_offset = panel_x - WIDTH_TIMEFRAME - (header_index < 2 ? header_index * WIDTH_SIGNAL : 2 * WIDTH_SIGNAL + (header_index - 2) * WIDTH_INDICATOR) + (1 + header_index); //--- Calculate x position
         ObjectSetInteger(0, HEADER_RECTANGLE + IntegerToString(header_index), OBJPROP_XDISTANCE, x_offset); //--- Set header rectangle x position
         ObjectSetInteger(0, HEADER_RECTANGLE + IntegerToString(header_index), OBJPROP_YDISTANCE, panel_y + 35); //--- Set header rectangle y position
         ObjectSetInteger(0, HEADER_TEXT + IntegerToString(header_index), OBJPROP_XDISTANCE, x_offset - (header_index < 2 ? WIDTH_SIGNAL/2 : WIDTH_INDICATOR/2)); //--- Set header text x position
         ObjectSetInteger(0, HEADER_TEXT + IntegerToString(header_index), OBJPROP_YDISTANCE, panel_y + 45); //--- Set header text y position
      }

      // Update timeframe rectangles, labels, and cells
      for(int timeframe_index = 0; timeframe_index < ArraySize(timeframes_array); timeframe_index++) { //--- Loop through timeframes
         int y_offset = (panel_y + 35 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index); //--- Calculate y position
         ObjectSetInteger(0, TIMEFRAME_RECTANGLE + IntegerToString(timeframe_index), OBJPROP_XDISTANCE, panel_x - 2); //--- Set timeframe rectangle x position
         ObjectSetInteger(0, TIMEFRAME_RECTANGLE + IntegerToString(timeframe_index), OBJPROP_YDISTANCE, y_offset); //--- Set timeframe rectangle y position
         ObjectSetInteger(0, TIMEFRAME_TEXT + IntegerToString(timeframe_index), OBJPROP_XDISTANCE, panel_x - 47); //--- Set timeframe text x position
         ObjectSetInteger(0, TIMEFRAME_TEXT + IntegerToString(timeframe_index), OBJPROP_YDISTANCE, y_offset + 10); //--- Set timeframe text y position

         for(int header_index = 0; header_index < ArraySize(header_names); header_index++) { //--- Loop through cells
            string cell_rectangle_name, cell_text_name;
            switch(header_index) { //--- Select cell type
               case 0: cell_rectangle_name = BUY_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = BUY_TEXT + IntegerToString(timeframe_index); break; //--- Buy cell
               case 1: cell_rectangle_name = SELL_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = SELL_TEXT + IntegerToString(timeframe_index); break; //--- Sell cell
               case 2: cell_rectangle_name = RSI_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = RSI_TEXT + IntegerToString(timeframe_index); break; //--- RSI cell
               case 3: cell_rectangle_name = STOCH_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = STOCH_TEXT + IntegerToString(timeframe_index); break; //--- Stochastic cell
               case 4: cell_rectangle_name = CCI_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = CCI_TEXT + IntegerToString(timeframe_index); break; //--- CCI cell
               case 5: cell_rectangle_name = ADX_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = ADX_TEXT + IntegerToString(timeframe_index); break; //--- ADX cell
               case 6: cell_rectangle_name = AO_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = AO_TEXT + IntegerToString(timeframe_index); break; //--- AO cell
            }
            int x_offset = panel_x - WIDTH_TIMEFRAME - (header_index < 2 ? header_index * WIDTH_SIGNAL : 2 * WIDTH_SIGNAL + (header_index - 2) * WIDTH_INDICATOR) + (1 + header_index); //--- Calculate x position
            int width = (header_index < 2 ? WIDTH_SIGNAL : WIDTH_INDICATOR); //--- Set cell width
            ObjectSetInteger(0, cell_rectangle_name, OBJPROP_XDISTANCE, x_offset); //--- Set cell rectangle x position
            ObjectSetInteger(0, cell_rectangle_name, OBJPROP_YDISTANCE, y_offset); //--- Set cell rectangle y position
            ObjectSetInteger(0, cell_text_name, OBJPROP_XDISTANCE, x_offset - width/2); //--- Set cell text x position
            ObjectSetInteger(0, cell_text_name, OBJPROP_YDISTANCE, y_offset + 10); //--- Set cell text y position
         }
      }
   }
   ChartRedraw(0);                                               //--- Redraw chart
}

Um die dynamische Positionierung des Dashboards zu unterstützen, führen wir eine neue Funktion „update_panel_positions“ ein. Die Funktion passt die Positionen aller Dashboard-Elemente anhand der aktuellen „panel_x“- und „panel_y“-Koordinaten an, sodass das Dashboard über das Chart gezogen werden kann. Wir aktualisieren die Kopfzeile, das Symbol, den Titel, die Schaltfläche zum Schließen und die Umschalttaste („HEADER_PANEL“, „HEADER_PANEL_ICON“, „HEADER_PANEL_TEXT“, „CLOSE_BUTTON“, „TOGGLE_BUTTON“) mit der Funktion ObjectSetInteger mit OBJPROP_XDISTANCE und „OBJPROP_YDISTANCE“, wobei ihre Positionen relativ zu „panel_x“ und „panel_y“ festgelegt werden.

Wenn „panel_minimized“ falsch ist, werden das Hauptfenster („MAIN_PANEL“), das Symbolrechteck und die Kennzeichnung („SYMBOL_RECTANGLE“, „SYMBOL_TEXT“), die Kopfzeilenrechtecke und -kennzeichnung („HEADER_RECTANGLE“, „HEADER_TEXT“) und Zeitrahmen-Rechtecke, -Kennzeichnung und -Indikatorzellen („TIMEFRAME_RECTANGLE“, „TIMEFRAME_TEXT“, „BUY_RECTANGLE“, „RSI_RECTANGLE“, usw.)) unter Verwendung berechneter Offsets von „panel_x“ und „panel_y“. Wir rufen die Funktion ChartRedraw auf, um die Anzeige zu aktualisieren. Mit dieser Funktion wird sichergestellt, dass sich alle Elemente zusammen bewegen, wenn das Dashboard gezogen wird – eine wichtige Funktion für unsere Erweiterung des beweglichen Dashboards. Wir können dann versuchen, das Dashboard zu testen. Dazu rufen wir die Funktion zum Erstellen des vollständigen Dashboards in OnInit auf und rufen die Zerstörungsfunktion in OnDeinit auf.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()                                                     //--- Initialize EA
{
   create_full_dashboard();                                      //--- Create full dashboard
   ArraySetAsSeries(rsi_values, true);                           //--- Set RSI array as timeseries
   ArraySetAsSeries(stochastic_values, true);                    //--- Set Stochastic array as timeseries
   ArraySetAsSeries(cci_values, true);                           //--- Set CCI array as timeseries
   ArraySetAsSeries(adx_values, true);                           //--- Set ADX array as timeseries
   ArraySetAsSeries(ao_values, true);                            //--- Set AO array as timeseries
    
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);             //--- Enable mouse move events
   return(INIT_SUCCEEDED);                                       //--- Return initialization success
}

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

Hier rufen wir nur die entsprechenden Funktionen während der Initialisierung und Deinitialisierung des Programms auf, um die grundlegenden Antworten zu testen, bevor wir zu den nächsten Teilen übergehen. Es ist immer besser, Ihr Programm schrittweise zu kompilieren, um sicherzustellen, dass es funktioniert. Nach der Zusammenstellung ergibt sich folgendes Ergebnis.

INIT UND DEINIT GIF

Anhand der Visualisierung können wir sehen, dass wir das Panel erfolgreich initialisieren und entfernen können. Lassen Sie uns nun dazu übergehen, das Dashboard reaktionsfähig zu machen. Zunächst müssen wir die Position des Cursors ermitteln, um festzustellen, ob er sich in der Kopfzeile oder in den Schaltflächen befindet, sodass wir genau wissen, was der Nutzer möglicherweise tun möchte. Wir werden auch den Bewegungspfad für die Schaltflächen erhalten, sodass wir den Schwebezustand und den Klickzustand durch Ändern der Farbe erkennen und visualisieren können, aber zunächst müssen wir eine Funktion zur Bestimmung der Cursorposition relativ zu den Dashboard-Elementen definieren.

//+------------------------------------------------------------------+
//| 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);
   
   // Header panel bounds
   int header_x = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE);
   int header_y = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE);
   int header_width = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XSIZE);
   int header_height = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YSIZE);
   int header_left = chart_width - header_x;
   int header_right = header_left + header_width;
   bool in_header = (mouse_x >= header_left && mouse_x <= header_right && 
                     mouse_y >= header_y && mouse_y <= header_y + header_height);

   // Close button bounds
   int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE);
   int close_y = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE);
   int close_width = 20;
   int close_height = 20;
   int close_left = chart_width - close_x;
   int close_right = close_left + close_width;
   bool in_close = (mouse_x >= close_left && mouse_x <= close_right && 
                    mouse_y >= close_y && mouse_y <= close_y + close_height);

   // Toggle button bounds
   int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE);
   int toggle_y = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE);
   int toggle_width = 20;
   int toggle_height = 20;
   int toggle_left = chart_width - toggle_x;
   int toggle_right = toggle_left + toggle_width;
   bool in_toggle = (mouse_x >= toggle_left && mouse_x <= toggle_right && 
                     mouse_y >= toggle_y && mouse_y <= toggle_y + toggle_height);

   return in_header || in_close || in_toggle;
}

Zunächst führen wir eine neue Funktion „is_cursor_in_header_or_buttons“ ein, um eine dynamische Positionierung und Umschaltfunktion zu unterstützen. Die Funktion prüft, ob sich der Mauszeiger über der Kopfleiste, der Schließtaste oder der Umschalttaste befindet und ermöglicht so interaktives Ziehen und Schaltflächenaktionen. Wir beginnen mit der Abfrage der Chart-Breite mit ChartGetInteger mit CHART_WIDTH_IN_PIXELS. Für die Kopfzeile erhalten wir „header_x“, „header_y“, „header_width“ und „header_height“ mit ObjectGetInteger mit „OBJPROP_XDISTANCE“, „OBJPROP_YDISTANCE“, „OBJPROP_XSIZE“ und OBJPROP_YSIZE für „HEADER_PANEL“, wobei „header_left“ und „header_right“ relativ zur Chart-Breite berechnet werden. Wir prüfen, ob „mouse_x“ und „mouse_y“ innerhalb dieser Grenzen liegen und setzen „in_header“ auf true, wenn dies der Fall ist.

Für die Schließtaste holen wir „close_x“ und „close_y“ für „CLOSE_BUTTON“, definieren einen Bereich von 20x20 Pixeln, berechnen „close_left“ und „close_right“ und setzen „in_close“ auf true, wenn sich der Cursor innerhalb dieses Bereichs befindet. In ähnlicher Weise erhalten wir für die Umschalttaste „toggle_x“ und „toggle_y“ für „TOGGLE_BUTTON“, definieren einen 20x20-Bereich und setzen „in_toggle“ auf true, wenn sich der Cursor darin befindet. Wir geben true zurück, wenn sich der Cursor in einem dieser Bereiche befindet („in_header“, „in_close“, oder „in_toggle“). Diese Funktion ist entscheidend für die Erkennung von Mausinteraktionen mit der verschiebbaren Kopfzeile und den Schaltflächen des Dashboards und ermöglicht unsere erweiterten beweglichen und interaktiven Funktionen. Wir können dann die Schwebezustände aktualisieren, indem wir die Farben zur leichteren Erkennung visualisieren.

//+------------------------------------------------------------------+
//| Update button hover states                                       |
//+------------------------------------------------------------------+
void update_button_hover_states(int mouse_x, int mouse_y) {
   int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   
   // Close button hover
   int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE);
   int close_y = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE);
   int close_width = 20;
   int close_height = 20;
   int close_left = chart_width - close_x;
   int close_right = close_left + close_width;
   bool is_close_hovered = (mouse_x >= close_left && mouse_x <= close_right && 
                            mouse_y >= close_y && mouse_y <= close_y + close_height);

   if (is_close_hovered != prev_close_hovered) {
      ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_COLOR, is_close_hovered ? clrWhite : clrYellow);
      ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_BGCOLOR, is_close_hovered ? clrDodgerBlue : clrNONE);
      prev_close_hovered = is_close_hovered;
      ChartRedraw(0);
   }

   // Toggle button hover
   int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE);
   int toggle_y = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE);
   int toggle_width = 20;
   int toggle_height = 20;
   int toggle_left = chart_width - toggle_x;
   int toggle_right = toggle_left + toggle_width;
   bool is_toggle_hovered = (mouse_x >= toggle_left && mouse_x <= toggle_right && 
                             mouse_y >= toggle_y && mouse_y <= toggle_y + toggle_height);

   if (is_toggle_hovered != prev_toggle_hovered) {
      ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_COLOR, is_toggle_hovered ? clrWhite : clrYellow);
      ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_BGCOLOR, is_toggle_hovered ? clrDodgerBlue : clrNONE);
      prev_toggle_hovered = is_toggle_hovered;
      ChartRedraw(0);
   }
}

//+------------------------------------------------------------------+
//| Update header hover state                                        |
//+------------------------------------------------------------------+
void update_header_hover_state(int mouse_x, int mouse_y) {
   int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   int header_x = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE);
   int header_y = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE);
   int header_width = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XSIZE);
   int header_height = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YSIZE);
   int header_left = chart_width - header_x;
   int header_right = header_left + header_width;

   // Exclude button areas from header hover
   int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE);
   int close_y = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE);
   int close_width = 20;
   int close_height = 20;
   int close_left = chart_width - close_x;
   int close_right = close_left + close_width;

   int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE);
   int toggle_y = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE);
   int toggle_width = 20;
   int toggle_height = 20;
   int toggle_left = chart_width - toggle_x;
   int toggle_right = toggle_left + toggle_width;

   bool is_header_hovered = (mouse_x >= header_left && mouse_x <= header_right && 
                             mouse_y >= header_y && mouse_y <= header_y + header_height &&
                             !(mouse_x >= close_left && mouse_x <= close_right && 
                               mouse_y >= close_y && mouse_y <= close_y + close_height) &&
                             !(mouse_x >= toggle_left && mouse_x <= toggle_right && 
                               mouse_y >= toggle_y && mouse_y <= toggle_y + toggle_height));

   if (is_header_hovered != prev_header_hovered && !panel_dragging) {
      ObjectSetInteger(0, HEADER_PANEL, OBJPROP_BGCOLOR, is_header_hovered ? clrRed : C'60,60,60');
      prev_header_hovered = is_header_hovered;
      ChartRedraw(0);
   }

   update_button_hover_states(mouse_x, mouse_y);
}

Hier führen wir zwei neue Funktionen ein, „update_button_hover_states“ und „update_header_hover_state“, um visuelles Feedback für Nutzerinteraktionen hinzuzufügen und die Nutzerfreundlichkeit des Dashboards zu verbessern. Wir beginnen mit der Funktion „update_button_hover_states“, die „mouse_x“ und „mouse_y“ benötigt, um den Schwebezustand über den Schaltflächen „Schließen“ und „Umschalten“ zu erkennen. Für den „CLOSE_BUTTON“ holen wir uns „close_x“ und „close_y“ mit ObjectGetInteger mit „OBJPROP_XDISTANCE“ und „OBJPROP_YDISTANCE“, berechnen einen Bereich von 20x20 Pixel relativ zur Chart-Breite aus ChartGetInteger mit „CHART_WIDTH_IN_PIXELS“ und setzen „is_close_hovered“, wenn sich der Cursor innerhalb dieses Bereichs befindet.

Wenn „is_close_hovered“ von „prev_close_hovered“ abweicht, aktualisieren wir „CLOSE_BUTTON“ mit „ObjectSetInteger“ und setzen „OBJPROP_COLOR“ auf „clrWhite“ und OBJPROP_BGCOLOR auf „clrDodgerBlue“, wenn der Mauszeiger in der Schwebe ist, oder auf „clrYellow“ und „clrNONE“, wenn er nicht in der Schwebe ist, aktualisieren „prev_close_hovered“ und rufen die Funktion ChartRedraw auf. In ähnlicher Weise holen wir für „TOGGLE_BUTTON“ die Werte „toggle_x“ und „toggle_y“, überprüfen den 20x20-Bereich und aktualisieren die Farben und „prev_toggle_hovered“, wenn sich „is_toggle_hovered“ ändert, um ein reaktionsschnelles Feedback der Schaltfläche zu gewährleisten.

Als Nächstes erstellen wir die Funktion „update_header_hover_state“, die ebenfalls „mouse_x“ und „mouse_y“ benötigt. Wir ermitteln „header_x“, „header_y“, „header_width“ und „header_height“ für „HEADER_PANEL“ mit „ObjectGetInteger“, berechnen die Grenzen der Kopfzeile und schließen die Bereiche von „CLOSE_BUTTON“ und „TOGGLE_BUTTON“ (jeweils 20x20 Pixel) aus, um Überlappungen zu vermeiden. Wenn „is_header_hovered“ sich von „prev_header_hovered“ unterscheidet und „panel_dragging“ falsch ist, aktualisieren wir „OBJPROP_BGCOLOR“ von „HEADER_PANEL“ auf „clrRed“, wenn der Mauszeiger in der Schwebe gehalten wird, oder auf „C'60,60,60“, wenn dies nicht der Fall ist, aktualisieren „prev_header_hovered“ und rufen „ChartRedraw“ auf. Anschließend rufen wir „update_button_hover_states“ auf, um sicherzustellen, dass die Schaltflächenzustände aktualisiert werden. Diese Funktionen bieten visuelle Anhaltspunkte für das Ziehen und die Interaktion mit Schaltflächen, wodurch die Interaktivität des Dashboards verbessert wird. Wir können dann die Funktionen in der Ereignisbehandlung von OnChartEvent für die vollständige Implementierung verwenden. Hier ist die Logik, die wir anwenden.

//+------------------------------------------------------------------+
//| Expert chart event handler                                       |
//+------------------------------------------------------------------+
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 object click event
      if (string_param == CLOSE_BUTTON) {                        //--- Check if close button clicked
         Print("Closing the panel now");                         //--- Log panel closure
         PlaySound("alert.wav");                                 //--- Play alert sound
         panel_is_visible = false;                               //--- Hide panel
         delete_all_objects();                                   //--- Delete all objects
         ChartRedraw(0);                                         //--- Redraw chart
      } else if (string_param == TOGGLE_BUTTON) {                //--- Toggle button clicked
         delete_all_objects();                                   //--- Delete current UI
         panel_minimized = !panel_minimized;                     //--- Toggle minimized state
         if (panel_minimized) {
            Print("Minimizing the panel");                       //--- Log minimization
            create_minimized_dashboard();                        //--- Create minimized UI
         } else {
            Print("Maximizing the panel");                       //--- Log maximization
            create_full_dashboard();                             //--- Create full UI
         }
         // Reset hover states after toggle
         prev_header_hovered = false;
         prev_close_hovered = false;
         prev_toggle_hovered = false;
         ObjectSetInteger(0, HEADER_PANEL, OBJPROP_BGCOLOR, C'60,60,60');
         ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_COLOR, clrYellow);
         ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_BGCOLOR, clrNONE);
         ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_COLOR, clrYellow);
         ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_BGCOLOR, clrNONE);
         ChartRedraw(0);
      }
   }
   else if (event_id == CHARTEVENT_MOUSE_MOVE && panel_is_visible) { //--- Handle mouse move events
      int mouse_x = (int)long_param;                             //--- Get mouse x-coordinate
      int mouse_y = (int)double_param;                           //--- Get mouse y-coordinate
      int mouse_state = (int)string_param;                       //--- Get mouse state

      if (mouse_x == last_mouse_x && mouse_y == last_mouse_y && !panel_dragging) { //--- Skip redundant updates
         return;
      }
      last_mouse_x = mouse_x;                                    //--- Update last mouse x position
      last_mouse_y = mouse_y;                                    //--- Update last mouse y position

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

      int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
      int header_x = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE);
      int header_y = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE);
      int header_width = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XSIZE);
      int header_height = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YSIZE);
      int header_left = chart_width - header_x;
      int header_right = header_left + header_width;

      int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE);
      int close_width = 20;
      int close_left = chart_width - close_x;
      int close_right = close_left + close_width;

      int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE);
      int toggle_width = 20;
      int toggle_left = chart_width - toggle_x;
      int toggle_right = toggle_left + toggle_width;

      if (prev_mouse_state == 0 && mouse_state == 1) {           //--- Detect mouse button down
         if (mouse_x >= header_left && mouse_x <= header_right && 
             mouse_y >= header_y && mouse_y <= header_y + header_height &&
             !(mouse_x >= close_left && mouse_x <= close_right) &&
             !(mouse_x >= toggle_left && mouse_x <= toggle_right)) { //--- Exclude button areas
            panel_dragging = true;                              //--- Start dragging
            panel_drag_x = mouse_x;                             //--- Store mouse x-coordinate
            panel_drag_y = mouse_y;                             //--- Store mouse y-coordinate
            panel_start_x = header_x;                           //--- Store panel x-coordinate
            panel_start_y = header_y;                           //--- Store panel y-coordinate
            ObjectSetInteger(0, HEADER_PANEL, OBJPROP_BGCOLOR, clrMediumBlue); //--- Set header to blue on drag start
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);      //--- Disable chart scrolling
         }
      }

      if (panel_dragging && mouse_state == 1) {                  //--- Handle dragging
         int dx = mouse_x - panel_drag_x;                        //--- Calculate x displacement
         int dy = mouse_y - panel_drag_y;                        //--- Calculate y displacement
         panel_x = panel_start_x - dx;                           //--- Update panel x-position (inverted for CORNER_RIGHT_UPPER)
         panel_y = panel_start_y + dy;                           //--- Update panel y-position

         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
         panel_x = MathMax(617, MathMin(chart_width, panel_x));  //--- Keep panel within right edge
         panel_y = MathMax(0, MathMin(chart_height - (panel_minimized ? 27 : 374), panel_y)); //--- Adjust height based on state

         update_panel_positions();                               //--- Update all panel object positions
         ChartRedraw(0);                                         //--- Redraw chart during dragging
      }

      if (mouse_state == 0 && prev_mouse_state == 1) {           //--- Detect mouse button release
         if (panel_dragging) {
            panel_dragging = false;                              //--- Stop dragging
            update_header_hover_state(mouse_x, mouse_y);          //--- Update hover state immediately after drag
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true);        //--- Re-enable chart scrolling
            ChartRedraw(0);                                      //--- Redraw chart
         }
      }

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

Hier erweitern wir unser Programm, indem wir mit OnChartEvent die Ereignisse aktualisieren, um die dynamische Positionierung und die Umschaltfunktion zu unterstützen, was eine deutliche Verbesserung gegenüber der vorherigen Version darstellt. Wir behalten die Behandlung von CHARTEVENT_OBJECT_CLICK für den „CLOSE_BUTTON“ bei, der das Schließen mit Print protokolliert, einen Sound mit PlaySound abspielt, „panel_is_visible“ auf false setzt, die Funktion „delete_all_objects“ aufruft und das Chart neu zeichnet.

Die neue Ergänzung ist die Behandlung von Klicks auf den „TOGGLE_BUTTON“, wo wir die Funktion „delete_all_objects“ aufrufen, „panel_minimized“ umschalten und entweder ein minimiertes Dashboard mit „create_minimized_dashboard“ (Protokollierung „Minimierung des Panels“) oder ein vollständiges Dashboard mit „create_full_dashboard“ (Protokollierung „Maximierung des Panels“). Wir setzen die Schwebezustände („prev_header_hovered“, „prev_close_hovered“, „prev_toggle_hovered“) auf false zurück, stellen die Standardfarben für „HEADER_PANEL“, „CLOSE_BUTTON“ und „TOGGLE_BUTTON“ mit der Funktion ObjectSetInteger wieder her und zeichnen das Chart neu.

Für die dynamische Positionierung fügen wir CHARTEVENT_MOUSE_MOVE hinzu, wenn „panel_is_visible“ wahr ist. Wir erhalten „mouse_x“, „mouse_y“ und „mouse_state“ aus den Ereignisparametern, überspringen redundante Aktualisierungen, wenn die Koordinaten mit „last_mouse_x“ und „last_mouse_y“ übereinstimmen und „panel_dragging“ falsch ist, und aktualisieren diese Koordinaten. Wir rufen „update_header_hover_state“ auf, um Hover-Effekte zu verwalten. Wenn „prev_mouse_state“ gleich 0 und „mouse_state“ gleich 1 ist, prüfen wir mit „ObjectGetInteger“ und ChartGetInteger, ob sich der Cursor über „HEADER_PANEL“ befindet (mit Ausnahme der Bereiche „CLOSE_BUTTON“ und „TOGGLE_BUTTON“), setzen dann „panel_dragging“ auf true, speichern die Koordinaten in „panel_drag_x“, „panel_drag_y“, „panel_start_x“ und „panel_start_y“, setzen die Farbe „HEADER_PANEL“ auf „clrMediumBlue“ und deaktivieren das Scrollen des Charts mit „ChartSetInteger“.

Während „panel_dragging“ und „mouse_state“ 1 sind, berechnen wir die Verschiebung, aktualisieren „panel_x“ und „panel_y“ innerhalb der Chart-Grenzen, rufen „update_panel_positions“ auf und zeichnen neu. Wenn die Maus losgelassen wird, wird das Ziehen gestoppt, der Schwebe-Status aktualisiert, der Bildlauf wieder aktiviert und das Bild neu gezeichnet. Wir aktualisieren „prev_mouse_state“. Diese Änderungen ermöglichen nun das Ziehen und Umschalten, im Gegensatz zu der bisherigen statischen, nur klickbaren Handhabung. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

ZIEHEN, SCHWEBEN UND SICHTBARKEITSSTATUS

Anhand der Visualisierung können wir sehen, dass das Dashboard zum Leben erwacht, aber wir müssen uns um die Konflikte zwischen den Ereignissen kümmern. Wir müssen einer Ereignisbehandlung den Vortritt lassen, damit sie zur Steigerung der Produktivität priorisiert werden kann. Wenn wir z. B. den Mauszeiger bewegen oder ziehen, oder sogar im minimierten Modus, müssen wir den Chart-Ereignissen Vorrang einräumen. Wenn wir uns im Ruhezustand oder im maximierten Zustand befinden, müssen wir den Aktualisierungen des Dashboards Vorrang einräumen. Wir erreichen dies, indem wir das Tick-Verarbeitungsereignis ändern, da wir dort das Dashboard aktualisieren.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()                                                     //--- Handle tick events
{
   if (panel_is_visible && !panel_minimized && !is_cursor_in_header_or_buttons(last_mouse_x, last_mouse_y)) { //--- Update indicators only if panel is visible, not minimized, and cursor is not in header/buttons
      updateIndicators();                                        //--- Update indicators
   }
}

Wir optimieren die Aktualisierung der Indikatoren in OnTick. Im Gegensatz zur vorherigen Version, in der die Funktion „updateIndicators“ ausschließlich auf der Grundlage von „panel_is_visible“ aufgerufen wurde, werden jetzt Bedingungen hinzugefügt, um die Indikatoren nur zu aktualisieren, wenn „panel_is_visible“ wahr ist, „panel_minimized“ falsch ist und der Cursor sich nicht über der Kopfzeile oder den Schaltflächen befindet, was mit „is_cursor_in_header_or_buttons“ mit „last_mouse_x“ und „last_mouse_y“ überprüft wird. Durch diese Änderung wird sichergestellt, dass die Aktualisierung der Indikatoren angehalten wird, wenn das Dashboard minimiert wird oder der Nutzer mit der Kopfzeile, der Schaltfläche zum Schließen oder der Schaltfläche zum Umschalten interagiert, wodurch unnötige Verarbeitungsvorgänge vermieden und die Leistung beim Ziehen oder Umschalten verbessert wird. Nach der Kompilierung erhalten wir folgendes Ergebnis.

VERBESSERTE LEISTUNG GIF

Aus der Visualisierung geht hervor, dass sich die Leistung des Dashboards verbessert hat und alle Ziele erreicht worden sind. Nun bleibt nur noch die Prüfung der Durchführbarkeit des Projekts, die im vorangegangenen Abschnitt behandelt wurde.


Backtests

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

DASHBOARD-BACKTEST


Schlussfolgerung

Abschließend haben wir unser Multi-Timeframe-Scanner-Dashboard in MQL5 verbessert, indem wir dynamische Positionierungs- und Umschaltfunktionen hinzugefügt haben, die auf Teil 3 mit einer beweglichen Oberfläche, einem Minimieren/Maximieren-Umschalter und interaktiven Hover-Effekten für eine bessere Nutzersteuerung aufbauen. Wir haben gezeigt, wie diese Verbesserungen mithilfe von Funktionen wie „create_minimized_dashboard“ und „update_header_hover_state“ implementiert werden können, um eine nahtlose Integration mit dem bestehenden Indikatorraster für Echtzeit-Handelseinblicke zu gewährleisten. Sie können dieses Dashboard weiter an Ihre Handelsbedürfnisse anpassen und so Ihre Fähigkeit zur effizienten Überwachung von Marktsignalen über mehrere Zeitrahmen hinweg verbessern. 

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

Letzte Kommentare | Zur Diskussion im Händlerforum (2)
linfo2
linfo2 | 17 Juli 2025 in 17:38
Danke, Allan, das ist ziemlich cool, gut dokumentiert und deckt Funktionen ab, die ich nicht kannte.
Allan Munene Mutiiria
Allan Munene Mutiiria | 17 Juli 2025 in 18:58
linfo2 #:
Danke Allan, das ist ziemlich cool, gut dokumentiert und deckt Funktionen ab, die ich nicht kannte, sehr geschätzt
@linfo2 so sehr begrüßt. Ich danke Ihnen.
Vom Neuling zum Experten: Animierte Nachrichtenschlagzeilen mit MQL5 (VI) – Strategie von schwebenden Aufträgen für den Nachrichtenhandel Vom Neuling zum Experten: Animierte Nachrichtenschlagzeilen mit MQL5 (VI) – Strategie von schwebenden Aufträgen für den Nachrichtenhandel
In diesem Artikel verlagern wir den Schwerpunkt auf die Integration einer nachrichtengesteuerten Auftragsausführungslogik, die den EA in die Lage versetzt, zu handeln und nicht nur zu informieren. Begleiten Sie uns, wenn wir erforschen, wie man die automatisierte Handelsausführung in MQL5 implementiert und den News Headline EA zu einem vollständig reaktionsfähigen Handelssystem erweitert. Expert Advisors bieten den Entwicklern von Algorithmen erhebliche Vorteile, da sie eine Vielzahl von Funktionen unterstützen. Bislang haben wir uns auf die Entwicklung eines Tools zur Präsentation von Nachrichten und Kalenderereignissen konzentriert, das mit integrierten KI-Einsichten und technischen Indikatoren ausgestattet ist.
Selbstoptimierende Expert Advisors in MQL5 (Teil 9): Kreuzen zweier gleitender Durchschnitte Selbstoptimierende Expert Advisors in MQL5 (Teil 9): Kreuzen zweier gleitender Durchschnitte
Dieser Artikel beschreibt den Aufbau einer Strategie des Kreuzens zweier gleitender Durchschnitte, die Signale aus einem höheren Zeitrahmen (D1) verwendet, um Einstiege auf einem niedrigeren Zeitrahmen (M15) zu steuern, wobei die Stop-Loss-Niveaus aus einem Zeitrahmen mit mittlerem Risiko (H4) berechnet werden. Es werden Systemkonstanten, nutzerdefinierte Enumerationen und Logik für trendfolgende und zum Mittelwert rückkehrende Modi eingeführt, wobei der Schwerpunkt auf Modularität und künftige Optimierung mithilfe eines genetischen Algorithmus liegt. Der Ansatz ermöglicht flexible Einstiegs- und Ausstiegsbedingungen und zielt darauf ab, die Signalverzögerung zu verringern und das Handels-Timing zu verbessern, indem Einstiegsmöglichkeiten im unteren Zeitrahmen mit Trends im oberen Zeitrahmen abgestimmt werden.
MQL5-Assistenz-Techniken, die Sie kennen sollten (Teil 73): Verwendung von Ichimoku-Mustern und ADX-Wilder MQL5-Assistenz-Techniken, die Sie kennen sollten (Teil 73): Verwendung von Ichimoku-Mustern und ADX-Wilder
Der Ichimoku-Kinko-Hyo-Indikator und der Oszillator ADX-Wilder sind ein Paar, das ergänzend in einem MQL5 Expert Advisor verwendet werden kann. Das Ichimoku hat viele Facetten, aber in diesem Artikel verlassen wir uns hauptsächlich auf seine Fähigkeit, Unterstützungs- und Widerstandsniveaus zu definieren. Inzwischen verwenden wir auch den ADX, um unseren Trend zu definieren. Wie üblich verwenden wir den MQL5-Assistenten, um das Potenzial dieser beiden zu erstellen und zu testen.
Vom Neuling zum Experten: Animierte Nachrichtenschlagzeilen mit MQL5 (V) – Ereignis-Erinnerungssystem Vom Neuling zum Experten: Animierte Nachrichtenschlagzeilen mit MQL5 (V) – Ereignis-Erinnerungssystem
In dieser Diskussion werden wir weitere Fortschritte bei der Integration einer verfeinerten Logik zur Ereigniswarnung für die vom „News Headline EA“ angezeigten wirtschaftlichen Kalenderereignisse untersuchen. Diese Verbesserung ist von entscheidender Bedeutung, da sie sicherstellt, dass die Nutzer rechtzeitig vor wichtigen Ereignissen benachrichtigt werden. Nehmen Sie an dieser Diskussion teil und erfahren Sie mehr.