English
preview
Handel mit dem MQL5 Wirtschaftskalender (Teil 9): Bessere Interaktion mit Nachrichten durch eine dynamische Bildlaufleiste und eine optimierte Anzeige

Handel mit dem MQL5 Wirtschaftskalender (Teil 9): Bessere Interaktion mit Nachrichten durch eine dynamische Bildlaufleiste und eine optimierte Anzeige

MetaTrader 5Handel |
20 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In diesem Artikel entwickeln wir die MQL5-Wirtschaftskalender-Serie weiter, indem wir eine vertikale, dynamische Scrollbar und eine ausgefeilte Anzeige einführen, um die Interaktion von Händlern mit Nachrichtenereignissen zu verbessern und eine intuitive Navigation und zuverlässige Ereignisdarstellung zu gewährleisten. Aufbauend auf den optimierten Backtests und der intelligenten Filterung von Teil 8 konzentrieren wir uns jetzt auf eine reaktionsfähige Nutzeroberfläche (UI) mit einer Bildlaufleiste, die klickbare Zustände visuell signalisiert und den Zugang zu Wirtschaftsnachrichten sowohl für Live- als auch für Backtest-Umgebungen vereinfacht. Wir gliedern den Artikel in die folgenden Themen:

  1. Erstellen einer dynamischen Bildlaufleiste für müheloses Erkunden von Nachrichten
  2. Implementierung in MQL5
  3. Tests und Validierung
  4. Schlussfolgerung

Lassen Sie uns in diese Verbesserungen eintauchen!


Erstellen einer dynamischen Bildlaufleiste für müheloses Erkunden von Nachrichten

Um unsere Interaktion mit dem MQL5-Wirtschaftskalender zu verbessern, stellen wir uns eine dynamische Bildlaufleiste als Eckpfeiler einer intuitiven Nachrichtennavigation vor. Wir planen eine responsive vertikale Bildlaufleiste, die klickbare Zustände visuell signalisiert, gepaart mit einem robusten Ereignisspeichersystem, um einen nahtlosen Zugriff auf alle gefilterten Nachrichten zu gewährleisten und das Dashboard in ein flüssiges, händlerzentriertes Tool zu verwandeln. Wir werden das Minimum an anzeigbaren Ereignissen eliminieren und alle gefilterten Ereignisse auflisten, sodass man bei Bedarf durch alle Nachrichten blättern kann und die Beschränkung auf die Visualisierung nur einer Reihe von priorisierten Ereignissen aufgehoben wird. Wie wir das erreichen wollen, ist folgendermaßen:

  • Dynamisches Scrollbar-Design: Wir werden eine Bildlaufleiste mit Symbolen implementieren, die zwischen schwarz für klickbare Zustände und hellgrau für nicht klickbare Zustände wechseln, um eine sofortige visuelle Rückmeldung zur Navigation durch umfangreiche Ereignislisten zu geben.
  • Zuverlässige Speicherung von Ereignissen: Wir werden ein System entwickeln, das alle gefilterten Ereignisse speichert und garantiert, dass jede Nachricht über die Bildlaufleiste zugänglich ist, sodass keine Anzeigebeschränkungen bestehen und eine umfassende Ansicht gewährleistet ist.
  • Effizienter Update-Mechanismus: Wir optimieren das Dashboard so, dass es nur dann neu gezeichnet wird, wenn sich die Filter ändern, neue Ereignisse erscheinen oder ein Bildlauf stattfindet, um ein reibungsloses und ablenkungsfreies Erlebnis zu gewährleisten.
  • Verfeinerung der Nutzeroberfläche (UI): Wir verbessern die Nutzeroberfläche mit präzisen Layout-Anpassungen und sorgen dafür, dass sich die Bildlaufleiste und die Ereignisanzeige nahtlos in ein professionelles, nutzerfreundliches Design integrieren.

Dieser strategische Fahrplan wird die Grundlage für ein Dashboard bilden, mit dem wir Wirtschaftsnachrichten mühelos erkunden können. Die dynamische Bildlaufleiste ist der Schlüssel zu einem verbesserten Handelserlebnis. Kurz gesagt, hier ist eine Visualisierung dessen, was wir erreichen wollen.

VISUALISIERUNG DES PLANS


Implementierung in MQL5

Um die Verbesserungen in MQL5 vorzunehmen, müssen wir zuerst die zusätzlichen Scrollbar-Objekte mit der Direktive #define zusammen mit ihren Layout-Konstanten definieren, einige Variablen, um den Scroll-Status und die zu verarbeitenden Ereignisse zu verfolgen, und dann Arrays, um die Ereignisdaten zu speichern, die nahtlos wie unten verarbeitet werden.

// Scrollbar UI elements
#define SCROLL_UP_REC "SCROLL_UP_REC"
#define SCROLL_UP_LABEL "SCROLL_UP_LABEL"
#define SCROLL_DOWN_REC "SCROLL_DOWN_REC"
#define SCROLL_DOWN_LABEL "SCROLL_DOWN_LABEL"
#define SCROLL_LEADER "SCROLL_LEADER"
#define SCROLL_SLIDER "SCROLL_SLIDER"

//---- Scrollbar layout constants
#define LIST_X 62
#define LIST_Y 162
#define LIST_WIDTH 716
#define LIST_HEIGHT 286
#define VISIBLE_ITEMS 11
#define ITEM_HEIGHT 26
#define SCROLLBAR_X (LIST_X + LIST_WIDTH + 2) // 780
#define SCROLLBAR_Y LIST_Y
#define SCROLLBAR_WIDTH 20
#define SCROLLBAR_HEIGHT LIST_HEIGHT // 286
#define BUTTON_SIZE 15
#define BUTTON_WIDTH (SCROLLBAR_WIDTH - 2)
#define BUTTON_OFFSET_X 1
#define SCROLL_AREA_HEIGHT (SCROLLBAR_HEIGHT - 2 * BUTTON_SIZE)
#define SLIDER_MIN_HEIGHT 20
#define SLIDER_WIDTH 18
#define SLIDER_OFFSET_X 1

//---- Event name tracking
string current_eventNames_data[];
string previous_eventNames_data[];
string last_dashboard_eventNames[];
string previous_displayable_eventNames[];
string current_displayable_eventNames[];
datetime last_dashboard_update = 0;

//---- Filter flags
bool enableCurrencyFilter = true;
bool enableImportanceFilter = true;
bool enableTimeFilter = true;
bool isDashboardUpdate = true;
bool filters_changed = true;

//---- Scrollbar flags and variables
bool scroll_visible = false;
bool moving_state_slider = false;
int scroll_pos = 0;
int prev_scroll_pos = -1; // Track previous scroll position
int mlb_down_x = 0;
int mlb_down_y = 0;
int mlb_down_yd_slider = 0;
int prev_mouse_state = 0;
int slider_height = SLIDER_MIN_HEIGHT;

//---- Event counters
int totalEvents_Considered = 0;
int totalEvents_Filtered = 0;
int totalEvents_Displayable = 0;

//---- Global arrays for events
EconomicEvent allEvents[];
EconomicEvent filteredEvents[];
EconomicEvent displayableEvents[];

Hier legen wir den Grundstein für die dynamische Bildlaufleiste und die ausgefeilte Ereignisanzeige des MQL5-Wirtschaftskalenders, indem wir wichtige UI-Konstanten, Variablen und Arrays definieren, um eine intuitive Navigation und eine effiziente Ereignisbehandlung zu ermöglichen. Wir legen die Komponenten der Bildlaufleiste mit Konstanten wie „SCROLL_UP_LABEL“, „SCROLL_DOWN_LABEL“, „SCROLL_UP_REC“ und „SCROLL_DOWN_REC“ fest, die die grafischen Elemente für die Auf- und Abwärtsschaltflächen der Bildlaufleiste identifizieren.

Layout-Konstanten wie „LIST_X“ (62), „LIST_Y“ (162), „LIST_WIDTH“ (716) und „LIST_HEIGHT“ (286) definieren den Ereignisanzeigebereich, während „SCROLLBAR_X“ (780), „SCROLLBAR_Y“ (162), „SCROLLBAR_WIDTH“ (20) und „SCROLLBAR_HEIGHT“ (286) (162), „SCROLLBAR_WIDTH“ (20) und „SCROLLBAR_HEIGHT“ (286) die Bildlaufleiste genau positionieren, wobei „VISIBLE_ITEMS“ (11) und „ITEM_HEIGHT“ (26) dafür sorgen, dass 11 Ereignisse angezeigt werden, jedes mit der Größe von 26 Pixels und „BUTTON_SIZE“ (15) und „SLIDER_WIDTH“ (18) gestalten die kompakten Schaltflächen und den Schieberegler.

Zur Verwaltung von Ereignissen und Interaktionen werden Arrays wie „current_displayable_eventNames“ und „previous_displayable_eventNames“ deklariert, um Ereignisnamen für die Erkennung von Änderungen zu verfolgen und stille Aktualisierungen zu unterstützen, sowie „last_dashboard_update“, um Aktualisierungen des Dashboards mit einem Zeitstempel zu versehen. Die Filterflags „enableCurrencyFilter“, „enableImportanceFilter“ und „enableTimeFilter“ (alle true) steuern die Ereignisauswahl, während „isDashboardUpdate“ und „filters_changed“ vorgeben, wann Aktualisierungen stattfinden. Scrollbar-Variablen, einschließlich „scroll_visible“, „scroll_pos“, „prev_scroll_pos“ und „moving_state_slider“, verfolgen die Sichtbarkeit und Position, wobei „mlb_down_x“, „mlb_down_y“ und „slider_height“ das Ziehen von Schiebern ermöglichen.

Wir verwenden die Zähler „totalEvents_Considered“, „totalEvents_Filtered“ und „totalEvents_Displayable“, um die Ereignisverarbeitung zu überwachen, und die Arrays „allEvents“, „filteredEvents“ und „displayableEvents“, um Ereignisdaten zu speichern und sicherzustellen, dass alle gefilterten Nachrichten navigierbar sind. Wir können dann zunächst die Bildlaufleistenelemente mit den vorhandenen Erstellungsfunktionen erstellen und dann die Größen und Positionen der Hauptrechtecke anpassen, um die Bildlaufleiste auf der rechten Seite zu enthalten.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   // Enable mouse move events for scrollbar
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);

   // Create dashboard UI
   createRecLabel(MAIN_REC,50,50,740+13,410,clrSeaGreen,1);
   createRecLabel(SUB_REC1,50+3,50+30,740-3-3+13,410-30-3,clrWhite,1);
   createRecLabel(SUB_REC2,50+3+5,50+30+50+27,740-3-3-5-5,410-30-3-50-27-10+5,clrGreen,1);
   createLabel(HEADER_LABEL,50+3+5,50+5,"MQL5 Economic Calendar",clrWhite,15);

   //---

}

Hier verwenden wir in OnInit die Funktion ChartSetInteger, um das Mausbewegungsereignis auf true zu initialisieren, sodass wir die Bildlaufpriorität des Hauptcharts und unserer nutzerdefinierten Objekte steuern können. Dadurch können wir die vertikale Bildlaufleiste und ihre Elemente verschieben, was eine nahtlose Bewegung und einen nahtlosen Übergang ermöglicht. Dann passen wir die Breite des Hauptrechtecks und des Unterrechtecks 1 an, indem wir 13 Pixel hinzufügen, damit es auch die vertikale Bildlaufleiste aufnehmen kann. Wir erweitern die Höhe des Unterrechtecks 2 um 5 Pixel, um alle 11 Ereignisse effizient unterzubringen und den Überlauf-Effekt zu eliminieren. Nach der Kompilierung erhalten wir folgendes Ergebnis.

ÄNDERUNGEN DER UNTERBRINGUNG

Auf dem Bild sehen wir alle Änderungen, die wir an den Unterkünften vorgenommen haben, gekennzeichnet von 1 bis 3. Änderung 1 zeigt an, dass die Schaltfläche „Abbrechen“ an den Rand des Bedienfelds geschoben wurde, Änderung 2 zeigt die Anpassung der Breite des Hauptrechtecks, um das Abbrechen und die Bildlaufleiste unterzubringen, und Änderung 3 zeigt die Anpassung der Höhe des Dashboard-Rechtecks, um den Überlauf des letzten Elementzeilenhalters unterzubringen. Nun können wir mit der Definition und Erstellung der Bildlaufleiste innerhalb des erstellten Bereichs fortfahren. Da wir den Schieberegler jedoch dynamisch machen und nur bei Bedarf anzeigen wollen, müssen wir einige Funktionen definieren, um dies zu ermöglichen.

//+------------------------------------------------------------------+
//| Calculate slider height                                          |
//+------------------------------------------------------------------+
int calculateSliderHeight() {
   if (totalEvents_Filtered <= VISIBLE_ITEMS)
      return SCROLL_AREA_HEIGHT;
   double visible_ratio = (double)VISIBLE_ITEMS / totalEvents_Filtered;
   int height = (int)::floor(SCROLL_AREA_HEIGHT * visible_ratio);
   return MathMax(SLIDER_MIN_HEIGHT, MathMin(height, SCROLL_AREA_HEIGHT));
}

//+------------------------------------------------------------------+
//| Update slider position                                           |
//+------------------------------------------------------------------+
void updateSliderPosition() {
   int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS);
   if (max_scroll <= 0) return;
   double scroll_ratio = (double)scroll_pos / max_scroll;
   int scroll_area_y_min = SCROLLBAR_Y + BUTTON_SIZE;
   int scroll_area_y_max = scroll_area_y_min + SCROLL_AREA_HEIGHT - slider_height;
   int new_y = scroll_area_y_min + (int)(scroll_ratio * (scroll_area_y_max - scroll_area_y_min));
   ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE, new_y);
   if (debugLogging) Print("Slider moved to y=", new_y);
   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| Update button colors based on scroll position                    |
//+------------------------------------------------------------------+
void updateButtonColors() {
   int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS);
   if (scroll_pos == 0) {
      ObjectSetInteger(0, SCROLL_UP_LABEL, OBJPROP_COLOR, clrLightGray);
   } else {
      ObjectSetInteger(0, SCROLL_UP_LABEL, OBJPROP_COLOR, clrBlack);
   }
   if (scroll_pos >= max_scroll) {
      ObjectSetInteger(0, SCROLL_DOWN_LABEL, OBJPROP_COLOR, clrLightGray);
   } else {
      ObjectSetInteger(0, SCROLL_DOWN_LABEL, OBJPROP_COLOR, clrBlack);
   }
   ChartRedraw(0);
}

Wir verbessern die dynamische Bildlaufleiste des MQL5-Wirtschaftskalenders durch die Implementierung von drei Schlüsselfunktionen - „calculateSliderHeight“, updateSliderPosition“ und „updateButtonColors“ - um eine intuitive Navigation und ein klares visuelles Feedback zu gewährleisten. Mit der Funktion „calculateSliderHeight“ wird die Höhe des Schiebereglers der Bildlaufleiste bestimmt, um den Anteil der sichtbaren Ereignisse im Verhältnis zu den gesamten gefilterten Ereignissen visuell darzustellen.

Wir prüfen, ob „totalEvents_Filtered“ kleiner oder gleich „VISIBLE_ITEMS“ (11) ist und geben „SCROLL_AREA_HEIGHT“ (256 Pixel) zurück, um den Bildlaufbereich zu füllen, wenn alle Ereignisse in eine Ansicht passen. Andernfalls berechnen wir ein „visible_ratio“, indem wir „VISIBLE_ITEMS“ durch „totalEvents_Filtered“ dividieren, mit „SCROLL_AREA_HEIGHT“ multiplizieren und die Funktion Floor verwenden, um eine ganzzahlige „Höhe“ zu erhalten. Wir geben dann das Maximum von „SLIDER_MIN_HEIGHT“ (20 Pixel) und das Minimum von „height“ und „SCROLL_AREA_HEIGHT“ zurück und stellen so sicher, dass der Schieberegler die richtige Größe für die Skala der Ereignisliste hat.

In der Funktion „updateSliderPosition“ wird der Schieberegler der Bildlaufleiste so positioniert, dass er die aktuelle Bildlaufposition innerhalb der Ereignisliste wiedergibt. Wir berechnen „max_scroll“ als Differenz zwischen „ArraySize(displayableEvents)“ und „VISIBLE_ITEMS“, wobei MathMax verwendet wird, um sicherzustellen, dass der Wert nicht negativ ist, und beenden den Vorgang, wenn „max_scroll“ Null ist (kein Scrollen erforderlich). Wir berechnen ein „scroll_ratio“, indem wir „scroll_pos“ durch „max_scroll“ teilen, definieren den vertikalen Bereich des Schiebereglers mit „scroll_area_y_min“ („SCROLLBAR_Y“ + „BUTTON_SIZE“) und „scroll_area_y_max“ („scroll_area_y_min“ + „SCROLL_AREA_HEIGHT“ - „slider_height“), und berechnen Sie eine „new_y“-Position durch Interpolation von „scroll_ratio“ innerhalb dieses Bereichs.

Anschließend setzen wir mit ObjectSetInteger die „OBJPROP_YDISTANCE“ von „SCROLL_SLIDER“ auf „new_y“, protokollieren die Bewegung, wenn „debugLogging“ wahr ist, und rufen ChartRedraw auf, um die Anzeige zu aktualisieren.

Mit der Funktion „updateButtonColors“ werden die Farben der Aufwärts- und Abwärts-Schaltflächen der Bildlaufleiste dynamisch aktualisiert, um ihren klickbaren Zustand anzuzeigen und das Nutzerfeedback zu verbessern. Wir berechnen „max_scroll“ wie in „updateSliderPosition“ und prüfen „scroll_pos“, um den Status von „SCROLL_UP_LABEL“ und „SCROLL_DOWN_LABEL“ zu bestimmen. Wenn „scroll_pos“ gleich 0 ist, setzen wir „SCROLL_UP_LABEL“ und „OBJPROP_COLOR“ auf „clrLightGray“ (nicht klickbar, oben); andernfalls setzen wir sie auf „clrBlack“ (klickbar). Wenn „scroll_pos“ gleich oder größer als „max_scroll“ ist, setzen wir „SCROLL_DOWN_LABEL“ auf „clrLightGray“ (nicht klickbar, unten); andernfalls setzen wir es auf „clrBlack“ (klickbar).

Abschließend rufen wir „ChartRedraw“ auf, um das Chart zu aktualisieren und sicherzustellen, dass die Icons die Navigation visuell leiten. Wir können nun die Bildlaufleiste dynamisch erstellen, wenn wir die Werte des Dashboards aktualisieren, wie unten gezeigt.

// Update TIME_LABEL
string timeText = updateServerTime ? "Server Time: "+TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS) : "Server Time: Static";
updateLabel(TIME_LABEL,timeText+"   |||   Total News: "+IntegerToString(totalEvents_Filtered)+"/"+IntegerToString(totalEvents_Considered));

// Update scrollbar visibility
bool new_scroll_visible = totalEvents_Filtered > VISIBLE_ITEMS;
if (new_scroll_visible != scroll_visible || events_changed || filters_changed) {
   scroll_visible = new_scroll_visible;
   if (debugLogging) Print("Scrollbar visibility: ", scroll_visible ? "Visible" : "Hidden");
   if (scroll_visible) {
      if (ObjectFind(0, SCROLL_LEADER) < 0) {
         createRecLabel(SCROLL_LEADER, SCROLLBAR_X, SCROLLBAR_Y, SCROLLBAR_WIDTH, SCROLLBAR_HEIGHT, clrSilver, 1, clrNONE);
         int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS);
         color up_color = (scroll_pos == 0) ? clrLightGray : clrBlack;
         color down_color = (scroll_pos >= max_scroll) ? clrLightGray : clrBlack;
         createRecLabel(SCROLL_UP_REC, SCROLLBAR_X + BUTTON_OFFSET_X, SCROLLBAR_Y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray);
         createLabel(SCROLL_UP_LABEL, SCROLLBAR_X + BUTTON_OFFSET_X, SCROLLBAR_Y-5, CharToString(0x35), up_color, 15, "Webdings");
         int down_y = SCROLLBAR_Y + SCROLLBAR_HEIGHT - BUTTON_SIZE;
         createRecLabel(SCROLL_DOWN_REC, SCROLLBAR_X + BUTTON_OFFSET_X, down_y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray);
         createLabel(SCROLL_DOWN_LABEL, SCROLLBAR_X + BUTTON_OFFSET_X, down_y-5, CharToString(0x36), down_color, 15, "Webdings");
         slider_height = calculateSliderHeight();
         int slider_y = SCROLLBAR_Y + BUTTON_SIZE;
         createButton(SCROLL_SLIDER, SCROLLBAR_X + SLIDER_OFFSET_X, slider_y, SLIDER_WIDTH, slider_height, "", clrWhite, 12, clrLightSlateGray, clrDarkGray, "Arial Bold");
         ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_WIDTH, 2);
         if (debugLogging) Print("Scrollbar created: totalEvents_Filtered=", totalEvents_Filtered, ", slider_height=", slider_height);
      }
      updateSliderPosition();
      updateButtonColors();
   } else {
      ObjectDelete(0, SCROLL_LEADER);
      ObjectDelete(0, SCROLL_UP_REC);
      ObjectDelete(0, SCROLL_UP_LABEL);
      ObjectDelete(0, SCROLL_DOWN_REC);
      ObjectDelete(0, SCROLL_DOWN_LABEL);
      ObjectDelete(0, SCROLL_SLIDER);
      if (debugLogging) Print("Scrollbar removed: totalEvents_Filtered=", totalEvents_Filtered);
   }
}

Wir verbessern die Nutzeroberfläche, indem wir wichtige Aktualisierungen der Zeitanzeige und der dynamischen Bildlaufleiste des Dashboards implementieren, um sicherzustellen, dass wir nahtlos mit Nachrichtenereignissen interagieren und durch sie navigieren können. Wir aktualisieren das „TIME_LABEL“, um die Serverzeit und die Ereignisstatistiken in Echtzeit wiederzugeben und so ein klares Feedback über den Zustand des Dashboards zu geben. Außerdem verwalten wir die Sichtbarkeit der Bildlaufleiste und initialisieren ihre Komponenten mit dynamischen Symbolfarben, um ein intuitives Navigationssystem zu schaffen, das unsere Erfahrung verbessert.

Zunächst konzentrieren wir uns auf die Aktualisierung des „TIME_LABEL“, um uns über die aktuelle Zeit und den Status der Ereignisverarbeitung zu informieren. Wir erstellen einen „timeText“-String mit Hilfe eines bedingten Ausdrucks: Wenn „updateServerTime“ wahr ist, rufen wir die Funktion TimeToString mit TimeCurrent und den Flags „TIME_DATE|TIME_SECONDS“ auf, um die Serverzeit zu formatieren; andernfalls setzen wir sie auf „Server Time: Static“. Dann verwenden wir die Funktion „updateLabel“, um den Text von „TIME_LABEL“ auf „timeText“ zu setzen, verbunden mit einem Trennzeichen („   |||   “) und den Ereigniszählungen, die über IntegerToString für „totalEvents_Filtered“ und „totalEvents_Considered“ formatiert werden, was zu einer Anzeige wie „Server Time: 2025.03.01 12:00:00   |||   Total News: 1711/3000“, wobei die gefilterten Ereignisse im Vergleich zu den Gesamtereignissen klar dargestellt werden und die anzeigbaren Nachrichten, wie wir sie in den vorherigen Versionen hatten, wegfallen.

Dann implementieren wir die Logik für die Sichtbarkeit der Bildlaufleiste und die Erstellung der Komponenten, um sicherzustellen, dass sie nur bei Bedarf angezeigt wird und ein visuelles Feedback liefert. Wir bestimmen „new_scroll_visible“, indem wir prüfen, ob „totalEvents_Filtered“ den Wert von „VISIBLE_ITEMS“ (11) übersteigt, was bedeutet, dass es mehr Ereignisse gibt, als auf einmal angezeigt werden können. Wenn „new_scroll_visible“ von „scroll_visible“ abweicht, oder wenn „events_changed“ oder „filters_changed“ wahr ist, wird „scroll_visible“ aktualisiert und der Zustand mit Print protokolliert, wenn „debugLogging“ aktiviert ist. Wenn „scroll_visible“ true ist, prüfen wir mit ObjectFind, ob „SCROLL_LEADER“ existiert, und wenn nicht, erstellen wir die Bildlaufleiste: Wir rufen „createRecLabel“ für „SCROLL_LEADER“, „SCROLL_UP_REC“ und „SCROLL_DOWN_REC“ an Positionen auf, die durch „SCROLLBAR_X“, „SCROLLBAR_Y“, „BUTTON_OFFSET_X“ und „BUTTON_SIZE“, unter Verwendung von „clrSilver“ und „clrDarkGray“.

Wir berechnen „max_scroll“ mit MathMax und „ArraySize(displayableEvents)“ minus „VISIBLE_ITEMS“, setzen „up_color“ und „down_color“ auf „clrLightGray“ oder „clrBlack“ basierend auf „scroll_pos“ und „max_scroll“, und erstellen „SCROLL_UP_LABEL“ und „SCROLL_DOWN_LABEL“ mit „createLabel“, unter Verwendung von CharToString für Webdings Pfeile, insbesondere 5 und 6 wie unten gezeigt.

WEB FONTS

Falls Sie sich fragen, warum wir „0x35“ und nicht nur 5 verwendet haben: Das ist das hexadezimale Binärformat zur Basis 16. Sie erhalten die gleichen Ergebnisse, wenn Sie 5 verwenden, aber als Zeichenkette wie „5“. Wenn Sie den ZeichencodeASCII verwenden möchten, können Sie die 53 direkt in eine Zeichenkette umwandeln CharToString(53) und erhalten die gleichen Ergebnisse. Es ist eine breite Palette von Optionen, die Sie verwenden können, so, Ihre Wahl. Hier sind die Informationen des Codes.

HEXADECIMAL „0x35“ AS 5

Wir berechnen „slider_height“ über „calculateSliderHeight“, erstellen „SCROLL_SLIDER“ mit „createButton“ bei „slider_y“, setzen dessen „OBJPROP_WIDTH“ auf 2 und protokollieren Details, wenn „debugLogging“ true ist. Wir rufen „updateSliderPosition“ und „updateButtonColors“ auf, um die Schieberegler- und Symbolfarben zu initialisieren. Wenn „scroll_visible“ false ist, entfernen wir Scrollbar-Objekte mit ObjectDelete für „SCROLL_LEADER“, „SCROLL_UP_REC“, „SCROLL_DOWN_REC““SCROLL_UP_LABEL“, „SCROLL_DOWN_LABEL“ und „SCROLL_SLIDER“, wobei das Entfernen protokolliert wird, um eine saubere Oberfläche zu erhalten, wenn kein Scrollen erforderlich ist. Der Rest der Funktion, in der diese Logik untergebracht ist, sowie die Funktion, die für die Speicherung der Ereignisse verantwortlich ist, ist wie folgt.

//+------------------------------------------------------------------+
//| Update dashboard values                                          |
//+------------------------------------------------------------------+
void update_dashboard_values(string &curr_filter_array[], ENUM_CALENDAR_EVENT_IMPORTANCE &imp_filter_array[]) {
   totalEvents_Considered = 0;
   totalEvents_Filtered = 0;
   totalEvents_Displayable = 0;
   ArrayFree(current_eventNames_data);
   ArrayFree(current_displayable_eventNames);

   datetime timeRange = PeriodSeconds(range_time);
   datetime timeBefore = TimeTradeServer() - timeRange;
   datetime timeAfter = TimeTradeServer() + timeRange;

   // Populate displayableEvents
   if (MQLInfoInteger(MQL_TESTER)) {
      if (filters_changed) {
         FilterEventsForTester();
         ArrayFree(displayableEvents); // Clear displayableEvents on filter change
      }
      int eventIndex = 0;
      for (int i = 0; i < ArraySize(filteredEvents); i++) {
         totalEvents_Considered++;
         datetime eventDateTime = filteredEvents[i].eventDateTime;
         if (eventDateTime < StartDate || eventDateTime > EndDate) {
            if (debugLogging) Print("Event ", filteredEvents[i].event, " skipped due to date range.");
            continue;
         }

         bool timeMatch = !enableTimeFilter;
         if (enableTimeFilter) {
            if (eventDateTime <= TimeTradeServer() && eventDateTime >= timeBefore) timeMatch = true;
            else if (eventDateTime >= TimeTradeServer() && eventDateTime <= timeAfter) timeMatch = true;
         }
         if (!timeMatch) {
            if (debugLogging) Print("Event ", filteredEvents[i].event, " skipped due to time filter.");
            continue;
         }

         bool currencyMatch = !enableCurrencyFilter;
         if (enableCurrencyFilter) {
            for (int j = 0; j < ArraySize(curr_filter_array); j++) {
               if (filteredEvents[i].currency == curr_filter_array[j]) {
                  currencyMatch = true;
                  break;
               }
            }
         }
         if (!currencyMatch) {
            if (debugLogging) Print("Event ", filteredEvents[i].event, " skipped due to currency filter.");
            continue;
         }

         bool importanceMatch = !enableImportanceFilter;
         if (enableImportanceFilter) {
            string imp_str = filteredEvents[i].importance;
            ENUM_CALENDAR_EVENT_IMPORTANCE event_imp = (imp_str == "None") ? CALENDAR_IMPORTANCE_NONE :
                                                      (imp_str == "Low") ? CALENDAR_IMPORTANCE_LOW :
                                                      (imp_str == "Medium") ? CALENDAR_IMPORTANCE_MODERATE :
                                                      CALENDAR_IMPORTANCE_HIGH;
            for (int k = 0; k < ArraySize(imp_filter_array); k++) {
               if (event_imp == imp_filter_array[k]) {
                  importanceMatch = true;
                  break;
               }
            }
         }
         if (!importanceMatch) {
            if (debugLogging) Print("Event ", filteredEvents[i].event, " skipped due to importance filter.");
            continue;
         }

         ArrayResize(displayableEvents, eventIndex + 1);
         displayableEvents[eventIndex] = filteredEvents[i];
         ArrayResize(current_displayable_eventNames, eventIndex + 1);
         current_displayable_eventNames[eventIndex] = filteredEvents[i].event;
         eventIndex++;
      }
      totalEvents_Filtered = ArraySize(displayableEvents);
      if (debugLogging) Print("Tester mode: Stored ", totalEvents_Filtered, " displayable events.");
   } else {
      MqlCalendarValue values[];
      datetime startTime = TimeTradeServer() - PeriodSeconds(start_time);
      datetime endTime = TimeTradeServer() + PeriodSeconds(end_time);
      int allValues = CalendarValueHistory(values,startTime,endTime,NULL,NULL);
      int eventIndex = 0;
      if (filters_changed) ArrayFree(displayableEvents); // Clear displayableEvents on filter change
      for (int i = 0; i < allValues; i++) {
         MqlCalendarEvent event;
         CalendarEventById(values[i].event_id, event);
         MqlCalendarCountry country;
         CalendarCountryById(event.country_id, country);
         MqlCalendarValue value;
         CalendarValueById(values[i].id, value);
         totalEvents_Considered++;

         bool currencyMatch = false;
         if (enableCurrencyFilter) {
            for (int j = 0; j < ArraySize(curr_filter_array); j++) {
               if (country.currency == curr_filter_array[j]) {
                  currencyMatch = true;
                  break;
               }
            }
            if (!currencyMatch) continue;
         }

         bool importanceMatch = false;
         if (enableImportanceFilter) {
            for (int k = 0; k < ArraySize(imp_filter_array); k++) {
               if (event.importance == imp_filter_array[k]) {
                  importanceMatch = true;
                  break;
               }
            }
            if (!importanceMatch) continue;
         }

         bool timeMatch = false;
         if (enableTimeFilter) {
            datetime eventTime = values[i].time;
            if (eventTime <= TimeTradeServer() && eventTime >= timeBefore) timeMatch = true;
            else if (eventTime >= TimeTradeServer() && eventTime <= timeAfter) timeMatch = true;
            if (!timeMatch) continue;
         }

         ArrayResize(displayableEvents, eventIndex + 1);
         displayableEvents[eventIndex].eventDate = TimeToString(values[i].time,TIME_DATE);
         displayableEvents[eventIndex].eventTime = TimeToString(values[i].time,TIME_MINUTES);
         displayableEvents[eventIndex].currency = country.currency;
         displayableEvents[eventIndex].event = event.name;
         displayableEvents[eventIndex].importance = (event.importance == CALENDAR_IMPORTANCE_NONE) ? "None" :
                                                   (event.importance == CALENDAR_IMPORTANCE_LOW) ? "Low" :
                                                   (event.importance == CALENDAR_IMPORTANCE_MODERATE) ? "Medium" : "High";
         displayableEvents[eventIndex].actual = value.GetActualValue();
         displayableEvents[eventIndex].forecast = value.GetForecastValue();
         displayableEvents[eventIndex].previous = value.GetPreviousValue();
         displayableEvents[eventIndex].eventDateTime = values[i].time;
         ArrayResize(current_displayable_eventNames, eventIndex + 1);
         current_displayable_eventNames[eventIndex] = event.name;
         eventIndex++;
      }
      totalEvents_Filtered = ArraySize(displayableEvents);
      if (debugLogging) Print("Live mode: Stored ", totalEvents_Filtered, " displayable events.");
   }

   // Check for changes in displayable events
   bool events_changed = isChangeInStringArrays(previous_displayable_eventNames, current_displayable_eventNames);
   bool scroll_changed = (scroll_pos != prev_scroll_pos);
   if (events_changed || filters_changed || scroll_changed) {
      if (debugLogging) {
         if (events_changed) Print("Changes detected in displayable events.");
         if (filters_changed) Print("Filter changes detected.");
         if (scroll_changed) Print("Scroll position changed: ", prev_scroll_pos, " -> ", scroll_pos);
      }
      ArrayFree(previous_displayable_eventNames);
      ArrayCopy(previous_displayable_eventNames, current_displayable_eventNames);
      prev_scroll_pos = scroll_pos;

      // Clear and redraw UI
      ObjectsDeleteAll(0, DATA_HOLDERS);
      ObjectsDeleteAll(0, ARRAY_NEWS);

      int startY = LIST_Y;
      int start_idx = scroll_visible ? scroll_pos : 0;
      int end_idx = MathMin(start_idx + VISIBLE_ITEMS, ArraySize(displayableEvents));
      for (int i = start_idx; i < end_idx; i++) {
         totalEvents_Displayable++;
         color holder_color = (totalEvents_Displayable % 2 == 0) ? C'213,227,207' : clrWhite;
         createRecLabel(DATA_HOLDERS+string(totalEvents_Displayable),LIST_X,startY-1,LIST_WIDTH,ITEM_HEIGHT+1,holder_color,1,clrNONE);

         int startX = LIST_X + 3;
         string news_data[ArraySize(array_calendar)];
         news_data[0] = displayableEvents[i].eventDate;
         news_data[1] = displayableEvents[i].eventTime;
         news_data[2] = displayableEvents[i].currency;
         color importance_color = clrBlack;
         if (displayableEvents[i].importance == "Low") importance_color = clrYellow;
         else if (displayableEvents[i].importance == "Medium") importance_color = clrOrange;
         else if (displayableEvents[i].importance == "High") importance_color = clrRed;
         news_data[3] = ShortToString(0x25CF);
         news_data[4] = displayableEvents[i].event;
         news_data[5] = DoubleToString(displayableEvents[i].actual, 3);
         news_data[6] = DoubleToString(displayableEvents[i].forecast, 3);
         news_data[7] = DoubleToString(displayableEvents[i].previous, 3);

         for (int k = 0; k < ArraySize(array_calendar); k++) {
            if (k == 3) {
               createLabel(ARRAY_NEWS+IntegerToString(i)+" "+array_calendar[k],startX,startY-(22-12),news_data[k],importance_color,22,"Calibri");
            } else {
               createLabel(ARRAY_NEWS+IntegerToString(i)+" "+array_calendar[k],startX,startY,news_data[k],clrBlack,12,"Calibri");
            }
            startX += buttons[k]+3;
         }

         ArrayResize(current_eventNames_data, ArraySize(current_eventNames_data)+1);
         current_eventNames_data[ArraySize(current_eventNames_data)-1] = displayableEvents[i].event;
         startY += ITEM_HEIGHT;
      }

      if (debugLogging) Print("Displayed ", totalEvents_Displayable, " events, start_idx=", start_idx, ", end_idx=", end_idx);
   } else {
      if (debugLogging) Print("No changes detected. Skipping redraw.");
   }

   // Update TIME_LABEL
   string timeText = updateServerTime ? "Server Time: "+TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS) : "Server Time: Static";
   updateLabel(TIME_LABEL,timeText+"   |||   Total News: "+IntegerToString(totalEvents_Filtered)+"/"+IntegerToString(totalEvents_Considered));

   // Update scrollbar visibility
   bool new_scroll_visible = totalEvents_Filtered > VISIBLE_ITEMS;
   if (new_scroll_visible != scroll_visible || events_changed || filters_changed) {
      scroll_visible = new_scroll_visible;
      if (debugLogging) Print("Scrollbar visibility: ", scroll_visible ? "Visible" : "Hidden");
      if (scroll_visible) {
         if (ObjectFind(0, SCROLL_LEADER) < 0) {
            createRecLabel(SCROLL_LEADER, SCROLLBAR_X, SCROLLBAR_Y, SCROLLBAR_WIDTH, SCROLLBAR_HEIGHT, clrSilver, 1, clrNONE);
            int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS);
            color up_color = (scroll_pos == 0) ? clrLightGray : clrBlack;
            color down_color = (scroll_pos >= max_scroll) ? clrLightGray : clrBlack;
            createRecLabel(SCROLL_UP_REC, SCROLLBAR_X + BUTTON_OFFSET_X, SCROLLBAR_Y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray);
            createLabel(SCROLL_UP_LABEL, SCROLLBAR_X + BUTTON_OFFSET_X, SCROLLBAR_Y-5, CharToString(0x35), up_color, 15, "Webdings");
            int down_y = SCROLLBAR_Y + SCROLLBAR_HEIGHT - BUTTON_SIZE;
            createRecLabel(SCROLL_DOWN_REC, SCROLLBAR_X + BUTTON_OFFSET_X, down_y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray);
            createLabel(SCROLL_DOWN_LABEL, SCROLLBAR_X + BUTTON_OFFSET_X, down_y-5, CharToString(0x36), down_color, 15, "Webdings");
            slider_height = calculateSliderHeight();
            int slider_y = SCROLLBAR_Y + BUTTON_SIZE;
            createButton(SCROLL_SLIDER, SCROLLBAR_X + SLIDER_OFFSET_X, slider_y, SLIDER_WIDTH, slider_height, "", clrWhite, 12, clrLightSlateGray, clrDarkGray, "Arial Bold");
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_WIDTH, 2);
            if (debugLogging) Print("Scrollbar created: totalEvents_Filtered=", totalEvents_Filtered, ", slider_height=", slider_height);
         }
         updateSliderPosition();
         updateButtonColors();
      } else {
         ObjectDelete(0, SCROLL_LEADER);
         ObjectDelete(0, SCROLL_UP_REC);
         ObjectDelete(0, SCROLL_UP_LABEL);
         ObjectDelete(0, SCROLL_DOWN_REC);
         ObjectDelete(0, SCROLL_DOWN_LABEL);
         ObjectDelete(0, SCROLL_SLIDER);
         if (debugLogging) Print("Scrollbar removed: totalEvents_Filtered=", totalEvents_Filtered);
      }
   }

   if (isChangeInStringArrays(previous_eventNames_data, current_eventNames_data)) {
      if (debugLogging) Print("CHANGES IN EVENT NAMES DETECTED.");
      ArrayFree(previous_eventNames_data);
      ArrayCopy(previous_eventNames_data, current_eventNames_data);
   }
}

Hier verfeinern wir das Dashboard in der Funktion „update_dashboard_values“, indem wir eine neue Logik einführen, um eine ausgefeilte Ereignisanzeige und dynamische Bildlaufleistenfunktionalität zu gewährleisten und gleichzeitig stillen Aktualisierungen aus Effizienzgründen Vorrang zu geben. Wir nutzen das Array „displayableEvents“, um alle gefilterten Ereignisse zu speichern, implementieren eine Änderungserkennung, um unnötige Neuaufrollungen zu vermeiden, und integrieren die Bildlaufleiste für eine intuitive Navigation, um frühere Einschränkungen zu beseitigen und die Interaktion mit dem Händler zu verbessern. Zunächst werden die Zähler „totalEvents_Considered“, „totalEvents_Filtered“ und „totalEvents_Displayable“ auf Null zurückgesetzt und die Arrays „current_eventNames_data“ und „current_displayable_eventNames“ mit ArrayFree geleert, um die aktualisierten Ereignisdaten vorzubereiten.

Unter Beibehaltung der bestehenden Filterlogik für den Tester- und den Live-Modus führen wir das Array „displayableEvents“ ein, um alle gefilterten Ereignisse zu speichern und einen umfassenden Zugriff zu gewährleisten (z. B. 1711 Ereignisse), im Gegensatz zu den 5-6 Ereignissen, die im Original angezeigt werden. Im Testermodus rufen wir „FilterEventsForTester“ auf, wenn „filters_changed“ wahr ist, löschen „displayableEvents“ mit „ArrayFree“ und durchlaufen „filteredEvents“ in einer Schleife, wobei wir Filter („enableTimeFilter“, „enableCurrencyFilter“, „enableImportanceFilter“), um „displayableEvents“ und „current_displayable_eventNames“ aufzufüllen und „totalEvents_Filtered“ auf „ArraySize(displayableEvents)“ zu setzen.

Im Live-Modus verwenden wir CalendarValueHistory, um Ereignisse zu holen, „displayableEvents“ zu löschen, wenn „filters_changed“, und gefilterte Ereignisse ähnlich zu speichern, wobei die Anzahl protokolliert wird, wenn „debugLogging“ aktiviert ist.

Wir implementieren stille Aktualisierungen, indem wir „isChangeInStringArrays“ verwenden, um „previous_displayable_eventNames“ mit „current_displayable_eventNames“ zu vergleichen, „events_changed“ zu setzen und zu prüfen, ob „scroll_pos“ sich von „prev_scroll_pos“ für „scroll_changed“ unterscheidet. Wenn „events_changed“, „filters_changed“ oder „scroll_changed“ wahr ist, protokollieren wir Änderungen über „Print“, wenn „debugLogging“ aktiviert ist, aktualisieren „previous_displayable_eventNames“ mit ArrayCopy und setzen „prev_scroll_pos“. Wir löschen UI-Elemente mit ObjectsDeleteAll für „DATA_HOLDERS“ und „ARRAY_NEWS“ und zeichnen dann bis zu „VISIBLE_ITEMS“ (11) Ereignisse aus „displayableEvents“, beginnend bei „start_idx“ (basierend auf „scroll_pos“ bei „scroll_visible“) bis „end_idx“ (begrenzt durch „ArraySize(displayableEvents)“).

Für jedes Ereignis rufen wir „createRecLabel“ auf, um einen Hintergrund mit wechselnden Farben („C'213,227,207'“ oder „clrWhite“) zu erhalten, füllen „news_data“ mit den Ereignisdetails und verwenden „createLabel“, um Felder anzuzeigen, wobei wir „importance_color“ anpassen (z. B., „clrYellow“ für Low) und die Protokollierung der Anzeigezahlen, wenn „debugLogging“ wahr ist. Wenn keine Änderungen auftreten, wird das Neuzeichnen übersprungen und protokolliert, um die Leistung zu erhalten.

Wir integrieren die Bildlaufleiste, indem wir „new_scroll_visible” setzen, wenn „totalEvents_Filtered” „VISIBLE_ITEMS” überschreitet, „scroll_visible” aktualisieren, wenn es sich ändert oder wenn „events_changed” oder „filters_changed” wahr ist, und erstellen die Komponenten („SCROLL_LEADER”, „SCROLL_UP_REC”, „SCROLL_UP_LABEL”, „SCROLL_DOWN_REC”, „SCROLL_DOWN_LABEL”, „SCROLL_SLIDER”) mit „createRecLabel”, „createLabel” und „createButton” und verwenden dabei „clrBlack” oder „clrLightGray” für Symbole basierend auf „scroll_pos” und „max_scroll”. Wir entfernen Komponenten mit ObjectDelete, wenn sie nicht benötigt werden, und aktualisieren „previous_eventNames_data“, wenn „isChangeInStringArrays“ Änderungen in „current_eventNames_data“ feststellt, um ein robustes, navigierbares und effizientes Dashboard zu gewährleisten. Da wir neue Objekte erstellt haben, müssen wir sie als Teil des Haupt-Dashboards löschen.

//+------------------------------------------------------------------+
//| Destroy dashboard                                                |
//+------------------------------------------------------------------+
void destroy_Dashboard() {
   ObjectDelete(0,"MAIN_REC");
   ObjectDelete(0,"SUB_REC1");
   ObjectDelete(0,"SUB_REC2");
   ObjectDelete(0,"HEADER_LABEL");
   ObjectDelete(0,"TIME_LABEL");
   ObjectDelete(0,"IMPACT_LABEL");
   ObjectsDeleteAll(0,"ARRAY_CALENDAR");
   ObjectsDeleteAll(0,"ARRAY_NEWS");
   ObjectsDeleteAll(0,"DATA_HOLDERS");
   ObjectsDeleteAll(0,"IMPACT_LABEL");
   ObjectDelete(0,"FILTER_LABEL");
   ObjectDelete(0,"FILTER_CURR_BTN");
   ObjectDelete(0,"FILTER_IMP_BTN");
   ObjectDelete(0,"FILTER_TIME_BTN");
   ObjectDelete(0,"CANCEL_BTN");
   ObjectsDeleteAll(0,"CURRENCY_BTNS");
   ObjectDelete(0, SCROLL_LEADER);
   ObjectDelete(0, SCROLL_UP_REC);
   ObjectDelete(0, SCROLL_UP_LABEL);
   ObjectDelete(0, SCROLL_DOWN_REC);
   ObjectDelete(0, SCROLL_DOWN_LABEL);
   ObjectDelete(0, SCROLL_SLIDER);
   ArrayFree(displayableEvents);
   ArrayFree(current_displayable_eventNames);
   ArrayFree(previous_displayable_eventNames);
   ChartRedraw(0);
}

Um die neu erstellten Objekte zu löschen, rufen wir einfach die Funktion ObjectDelete auf und geben ihre Namen an, um sicherzustellen, dass sie gelöscht werden, wenn wir das Dashboard löschen, da sie nun Teil des Dashboards sind. Nach der Kompilierung erhalten wir folgendes Ergebnis.

Weniger als oder gleich 11 gefilterte Ereignisse.

PASSENDE EREIGNISSE

Mehr als 11 gefilterte Ereignisse.

UNPASSENDE EREIGNISSE

Umfangreich gefilterter Ereignisbereich.

UMFANGREICHES EREIGNISANGEBOT

Aus den obigen Bildern geht hervor, dass wir die Bildlaufleiste des Kalenders dynamisch anhand der verfügbaren Ereignisse erstellen. Jetzt müssen wir den Scrollbar-Elementen Leben einhauchen, und das können wir mit der Funktion OnChartEvent wie folgt erreichen.

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) {
   int mouse_x = (int)lparam;
   int mouse_y = (int)dparam;
   int mouse_state = (int)sparam;

   if (id == CHARTEVENT_OBJECT_CLICK) {

      // Scrollbar button clicks
      if (scroll_visible && (sparam == SCROLL_UP_REC || sparam == SCROLL_UP_LABEL)) {
         scrollUp();
         updateButtonColors();
         if (debugLogging) Print("Up button clicked (", sparam, "). CurrPos: ", scroll_pos);
         ChartRedraw(0);
      }
      if (scroll_visible && (sparam == SCROLL_DOWN_REC || sparam == SCROLL_DOWN_LABEL)) {
         scrollDown();
         updateButtonColors();
         if (debugLogging) Print("Down button clicked (", sparam, "). CurrPos: ", scroll_pos);
         ChartRedraw(0);
      }
   }
   else if (id == CHARTEVENT_MOUSE_MOVE && scroll_visible) {
      if (prev_mouse_state == 0 && mouse_state == 1) {
         int xd = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XDISTANCE);
         int yd = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE);
         int xs = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XSIZE);
         int ys = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE);
         if (mouse_x >= xd && mouse_x <= xd + xs && mouse_y >= yd && mouse_y <= yd + ys) {
            moving_state_slider = true;
            mlb_down_x = mouse_x;
            mlb_down_y = mouse_y;
            mlb_down_yd_slider = yd;
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, clrDodgerBlue);
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE, slider_height + 2);
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
            if (debugLogging) Print("Slider drag started at y=", mouse_y);
         }
      }
      if (moving_state_slider && mouse_state == 1) {
         int delta_y = mouse_y - mlb_down_y;
         int new_y = mlb_down_yd_slider + delta_y;
         int scroll_area_y_min = SCROLLBAR_Y + BUTTON_SIZE;
         int scroll_area_y_max = scroll_area_y_min + SCROLL_AREA_HEIGHT - slider_height;
         new_y = MathMax(scroll_area_y_min, MathMin(new_y, scroll_area_y_max));
         ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE, new_y);
         int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS);
         double scroll_ratio = (double)(new_y - scroll_area_y_min) / (scroll_area_y_max - scroll_area_y_min);
         int new_scroll_pos = (int)MathRound(scroll_ratio * max_scroll);
         if (new_scroll_pos != scroll_pos) {
            scroll_pos = new_scroll_pos;
            update_dashboard_values(curr_filter_selected, imp_filter_selected);
            updateButtonColors();
            if (debugLogging) Print("Slider dragged. CurrPos: ", scroll_pos, ", Total steps: ", max_scroll, ", Slider y=", new_y);
         }
         ChartRedraw(0);
      }
      if (mouse_state == 0) {
         if (moving_state_slider) {
            moving_state_slider = false;
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, clrLightSlateGray);
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE, slider_height);
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
            if (debugLogging) Print("Slider drag stopped.");
            ChartRedraw(0);
         }
      }
      prev_mouse_state = mouse_state;
   }
}

Hier verbessern wir die Interaktivität, indem wir die neue Logik für die Bildlaufleiste in der Funktion OnChartEvent implementieren, sodass wir durch Klicken und Ziehen nahtlos durch Nachrichtenereignisse navigieren können. Wir konzentrieren uns auf die Verarbeitung von Nutzerinteraktionen mit der dynamischen Bildlaufleiste, insbesondere auf die Verarbeitung von Klicks auf die Aufwärts- und Abwärtsschaltflächen und Mausbewegungen zum Ziehen des Schiebereglers, um reaktionsschnelle Aktualisierungen der Dashboard-Anzeige zu gewährleisten. Wir verarbeiten die Ereignisse von CHARTEVENT_OBJECT_CLICK, um Klicks auf die Schaltflächen der Bildlaufleiste zu verarbeiten, wenn „scroll_visible“ wahr ist. Wenn das angeklickte Objekt („sparam“) „SCROLL_UP_REC“ oder „SCROLL_UP_LABEL“ ist, rufen wir die Funktion „scrollUp“ auf, um „scroll_pos“ zu verringern, gefolgt von „updateButtonColors“ um die Symbolfarben („clrBlack“ oder „clrLightGray“) basierend auf der neuen Position zu aktualisieren, die Aktion mit „Print“ zu protokollieren, wenn „debugLogging“ aktiviert ist, und ChartRedraw aufzurufen, um die Anzeige zu aktualisieren.

Ähnlich verhält es sich bei Klicks auf „SCROLL_DOWN_REC“ oder „SCROLL_DOWN_LABEL“: Wir rufen „scrollDown“ auf, um „scroll_pos“ zu erhöhen, rufen „updateButtonColors“ auf, protokollieren das Ereignis und aktualisieren das Chart mit „ChartRedraw“, um sicherzustellen, dass das Dashboard die Liste der gescrollten Ereignisse wiedergibt. Wir haben die Funktionen definiert und werden die Logik später erklären.

Für die Ereignisse von CHARTEVENT_MOUSE_MOVE, bei denen „scroll_visible“ wahr ist, verwalten wir das Ziehen des Schiebereglers. Wenn „prev_mouse_state“ gleich 0 und „mouse_state“ gleich 1 ist, verwenden wir ObjectGetInteger, um die Position („OBJPROP_XDISTANCE“, „OBJPROP_YDISTANCE“) und Größe („OBJPROP_XSIZE“, „OBJPROP_YSIZE“) als „xd“, „yd“, „xs“ und „ys“. Wenn die Mauskoordinaten („mouse_x“, „mouse_y“) innerhalb der Grenzen des Schiebereglers liegen, setzen wir „moving_state_slider“ auf true, speichern „mlb_down_x“, „mlb_down_y“ und „mlb_down_yd_slider“, ändern die „OBJPROP_BGCOLOR“ von „SCROLL_SLIDER“ auf „clrDodgerBlue“ und erhöhen „OBJPROP_YSIZE“ um 2, deaktivieren das Scrollen des Charts mit ChartSetInteger und protokollieren den Start des Ziehens, wenn „debugLogging“ aktiviert ist.

Während „moving_state_slider“ und „mouse_state“ wahr sind, berechnen wir „delta_y“ als „mouse_y“ minus „mlb_down_y“, berechnen „new_y“ innerhalb der Grenzen „scroll_area_y_min“ („SCROLLBAR_Y“ + „BUTTON_SIZE“) und „scroll_area_y_max“ („scroll_area_y_min“ + „SCROLL_AREA_HEIGHT“ - „slider_height“) unter Verwendung von MathMax und MathMin, und setzen „OBJPROP_YDISTANCE“ von „SCROLL_SLIDER“ auf „new_y“. Wir leiten „new_scroll_pos“ aus dem „scroll_ratio“ von „new_y“ innerhalb des Scrollbereichs ab, und wenn es sich von „scroll_pos“ unterscheidet, aktualisieren wir „scroll_pos“, rufen „update_dashboard_values“ mit „curr_filter_selected“ und „imp_filter_selected“ auf, rufen „updateButtonColors“ auf, protokollieren die Details des Ziehens und rufen ChartRedraw auf.

Wenn „mouse_state“ 0 ist, setzen wir „moving_state_slider“ zurück, stellen „OBJPROP_BGCOLOR“ von „SCROLL_SLIDER“ auf „clrLightSlateGray“ und „OBJPROP_YSIZE“ auf „slider_height“ zurück, aktivieren das Scrollen des Charts wieder, protokollieren den Stopp des Ziehens und rufen „ChartRedraw“ auf, um eine reibungslose Interaktion der Schieberegler zu gewährleisten. Die Funktionen, die für die Bildlauflogik verantwortlich sind, sind wie folgt.

//+------------------------------------------------------------------+
//| Scroll up                                                        |
//+------------------------------------------------------------------+
void scrollUp() {
   if (scroll_pos > 0) {
      scroll_pos--;
      update_dashboard_values(curr_filter_selected, imp_filter_selected);
      updateSliderPosition();
      if (debugLogging) Print("Scrolled up. CurrPos: ", scroll_pos);
   } else {
      if (debugLogging) Print("Cannot scroll up further. Already at top.");
   }
}

//+------------------------------------------------------------------+
//| Scroll down                                                      |
//+------------------------------------------------------------------+
void scrollDown() {
   int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS);
   if (scroll_pos < max_scroll) {
      scroll_pos++;
      update_dashboard_values(curr_filter_selected, imp_filter_selected);
      updateSliderPosition();
      if (debugLogging) Print("Scrolled down. CurrPos: ", scroll_pos);
   } else {
      if (debugLogging) Print("Cannot scroll down further. Max scroll reached: ", max_scroll);
   }
}
//+------------------------------------------------------------------+

Hier, in der Funktion „scrollUp“, erleichtern wir die Aufwärtsnavigation durch die Ereignisliste. Wir prüfen, ob „scroll_pos“ größer als 0 ist, was bedeutet, dass ein Hochscrollen möglich ist. Wenn ja, wird „scroll_pos“ dekrementiert, „update_dashboard_values“ mit „curr_filter_selected“ und „imp_filter_selected“ aufgerufen, um die angezeigten Ereignisse zu aktualisieren, „updateSliderPosition“ auf, um die Position des „SCROLL_SLIDER“ anzupassen, und protokollieren Sie die neue „scroll_pos“ mit Print, wenn „debugLogging“ aktiviert ist. Wenn „scroll_pos“ gleich 0 ist, wird eine Meldung protokolliert, dass der Anfang der Liste erreicht wurde, um unnötige Aktualisierungen zu vermeiden und einen sauberen Interaktionsfluss zu gewährleisten.

Mit der Funktion „scrollDown“ ermöglichen wir die Abwärtsnavigation durch die Ereignisliste. Wir berechnen „max_scroll“ mit MathMax, um einen nicht-negativen Wert zu erhalten, der sich aus „ArraySize(displayableEvents)“ minus „VISIBLE_ITEMS“ (11) ergibt und die maximale Bildlaufposition darstellt.

Wenn „scroll_pos“ kleiner als „max_scroll“ ist, erhöhen wir „scroll_pos“, rufen „update_dashboard_values“ mit „curr_filter_selected“ und „imp_filter_selected“ auf um die angezeigten Ereignisse zu aktualisieren, verwenden „updateSliderPosition“, um den „SCROLL_SLIDER“ neu zu positionieren, und protokollieren die neue „scroll_pos“ mit „Print“, wenn „debugLogging“ aktiviert ist. Wenn „scroll_pos“ gleich oder größer als „max_scroll“ ist, protokollieren wir eine Meldung, die besagt, dass das Ende der Liste erreicht wurde. Dadurch werden redundante Aktualisierungen vermieden und eine intuitive Navigation gewährleistet. Um eine nahtlose Interaktion zu gewährleisten, rufen wir die Funktionen auf, die in OnInit und OnTick definiert sind.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   UpdateFilterInfo();
   CheckForNewsTrade();
   if (isDashboardUpdate) {
      if (MQLInfoInteger(MQL_TESTER)) {
         datetime currentTime = TimeTradeServer();
         datetime timeRange = PeriodSeconds(range_time);
         datetime timeAfter = currentTime + timeRange;
         if (filters_changed || last_dashboard_update < timeAfter) {
            update_dashboard_values(curr_filter_selected, imp_filter_selected);
            ArrayFree(last_dashboard_eventNames);
            ArrayCopy(last_dashboard_eventNames, current_eventNames_data);
            last_dashboard_update = currentTime;
         }
      } else {
         update_dashboard_values(curr_filter_selected, imp_filter_selected);
      }
   }
}

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) {
   int mouse_x = (int)lparam;
   int mouse_y = (int)dparam;
   int mouse_state = (int)sparam;

   if (id == CHARTEVENT_OBJECT_CLICK) {
      UpdateFilterInfo();
      CheckForNewsTrade();
      if (sparam == CANCEL_BTN) {
         isDashboardUpdate = false;
         destroy_Dashboard();
      }
      if (sparam == FILTER_CURR_BTN) {
         bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE);
         enableCurrencyFilter = btn_state;
         if (debugLogging) Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableCurrencyFilter);
         string filter_curr_text = enableCurrencyFilter ? ShortToString(0x2714)+"Currency" : ShortToString(0x274C)+"Currency";
         color filter_curr_txt_color = enableCurrencyFilter ? clrLime : clrRed;
         ObjectSetString(0,FILTER_CURR_BTN,OBJPROP_TEXT,filter_curr_text);
         ObjectSetInteger(0,FILTER_CURR_BTN,OBJPROP_COLOR,filter_curr_txt_color);
         if (MQLInfoInteger(MQL_TESTER)) filters_changed = true;
         update_dashboard_values(curr_filter_selected,imp_filter_selected);
         if (debugLogging) Print("Success. Changes updated! State: "+(string)enableCurrencyFilter);
         ChartRedraw(0);
      }
      if (sparam == FILTER_IMP_BTN) {
         bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE);
         enableImportanceFilter = btn_state;
         if (debugLogging) Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableImportanceFilter);
         string filter_imp_text = enableImportanceFilter ? ShortToString(0x2714)+"Importance" : ShortToString(0x274C)+"Importance";
         color filter_imp_txt_color = enableImportanceFilter ? clrLime : clrRed;
         ObjectSetString(0,FILTER_IMP_BTN,OBJPROP_TEXT,filter_imp_text);
         ObjectSetInteger(0,FILTER_IMP_BTN,OBJPROP_COLOR,filter_imp_txt_color);
         if (MQLInfoInteger(MQL_TESTER)) filters_changed = true;
         update_dashboard_values(curr_filter_selected,imp_filter_selected);
         if (debugLogging) Print("Success. Changes updated! State: "+(string)enableImportanceFilter);
         ChartRedraw(0);
      }
      if (sparam == FILTER_TIME_BTN) {
         bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE);
         enableTimeFilter = btn_state;
         if (debugLogging) Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableTimeFilter);
         string filter_time_text = enableTimeFilter ? ShortToString(0x2714)+"Time" : ShortToString(0x274C)+"Time";
         color filter_time_txt_color = enableTimeFilter ? clrLime : clrRed;
         ObjectSetString(0,FILTER_TIME_BTN,OBJPROP_TEXT,filter_time_text);
         ObjectSetInteger(0,FILTER_TIME_BTN,OBJPROP_COLOR,filter_time_txt_color);
         if (MQLInfoInteger(MQL_TESTER)) filters_changed = true;
         update_dashboard_values(curr_filter_selected,imp_filter_selected);
         if (debugLogging) Print("Success. Changes updated! State: "+(string)enableTimeFilter);
         ChartRedraw(0);
      }
      if (StringFind(sparam,CURRENCY_BTNS) >= 0) {
         string selected_curr = ObjectGetString(0,sparam,OBJPROP_TEXT);
         if (debugLogging) Print("BTN NAME = ",sparam,", CURRENCY = ",selected_curr);
         bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE);
         if (btn_state == false) {
            if (debugLogging) Print("BUTTON IS IN UN-SELECTED MODE.");
            for (int i = 0; i < ArraySize(curr_filter_selected); i++) {
               if (curr_filter_selected[i] == selected_curr) {
                  for (int j = i; j < ArraySize(curr_filter_selected) - 1; j++) {
                     curr_filter_selected[j] = curr_filter_selected[j + 1];
                  }
                  ArrayResize(curr_filter_selected, ArraySize(curr_filter_selected) - 1);
                  if (debugLogging) Print("Removed from selected filters: ", selected_curr);
                  break;
               }
            }
         } else {
            if (debugLogging) Print("BUTTON IS IN SELECTED MODE. TAKE ACTION");
            bool already_selected = false;
            for (int j = 0; j < ArraySize(curr_filter_selected); j++) {
               if (curr_filter_selected[j] == selected_curr) {
                  already_selected = true;
                  break;
               }
            }
            if (!already_selected) {
               ArrayResize(curr_filter_selected, ArraySize(curr_filter_selected) + 1);
               curr_filter_selected[ArraySize(curr_filter_selected) - 1] = selected_curr;
               if (debugLogging) Print("Added to selected filters: ", selected_curr);
            } else {
               if (debugLogging) Print("Currency already selected: ", selected_curr);
            }
         }
         if (debugLogging) Print("SELECTED ARRAY SIZE = ",ArraySize(curr_filter_selected));
         if (debugLogging) ArrayPrint(curr_filter_selected);
         if (MQLInfoInteger(MQL_TESTER)) filters_changed = true;
         update_dashboard_values(curr_filter_selected,imp_filter_selected);
         if (debugLogging) Print("SUCCESS. DASHBOARD UPDATED");
         ChartRedraw(0);
      }
      if (StringFind(sparam, IMPACT_LABEL) >= 0) {
         string selected_imp = ObjectGetString(0, sparam, OBJPROP_TEXT);
         ENUM_CALENDAR_EVENT_IMPORTANCE selected_importance_lvl = get_importance_level(impact_labels,allowed_importance_levels,selected_imp);
         if (debugLogging) Print("BTN NAME = ", sparam, ", IMPORTANCE LEVEL = ", selected_imp,"(",selected_importance_lvl,")");
         bool btn_state = ObjectGetInteger(0, sparam, OBJPROP_STATE);
         color color_border = btn_state ? clrNONE : clrBlack;
         if (btn_state == false) {
            if (debugLogging) Print("BUTTON IS IN UN-SELECTED MODE.");
            for (int i = 0; i < ArraySize(imp_filter_selected); i++) {
               if (impact_filter_selected[i] == selected_imp) {
                  for (int j = i; j < ArraySize(imp_filter_selected) - 1; j++) {
                     imp_filter_selected[j] = imp_filter_selected[j + 1];
                     impact_filter_selected[j] = impact_filter_selected[j + 1];
                  }
                  ArrayResize(imp_filter_selected, ArraySize(imp_filter_selected) - 1);
                  ArrayResize(impact_filter_selected, ArraySize(impact_filter_selected) - 1);
                  if (debugLogging) Print("Removed from selected importance filters: ", selected_imp,"(",selected_importance_lvl,")");
                  break;
               }
            }
         } else {
            if (debugLogging) Print("BUTTON IS IN SELECTED MODE. TAKE ACTION");
            bool already_selected = false;
            for (int j = 0; j < ArraySize(imp_filter_selected); j++) {
               if (impact_filter_selected[j] == selected_imp) {
                  already_selected = true;
                  break;
               }
            }
            if (!already_selected) {
               ArrayResize(imp_filter_selected, ArraySize(imp_filter_selected) + 1);
               imp_filter_selected[ArraySize(imp_filter_selected) - 1] = selected_importance_lvl;
               ArrayResize(impact_filter_selected, ArraySize(impact_filter_selected) + 1);
               impact_filter_selected[ArraySize(impact_filter_selected) - 1] = selected_imp;
               if (debugLogging) Print("Added to selected importance filters: ", selected_imp,"(",selected_importance_lvl,")");
            } else {
               if (debugLogging) Print("Importance level already selected: ", selected_imp,"(",selected_importance_lvl,")");
            }
         }
         if (debugLogging) Print("SELECTED ARRAY SIZE = ", ArraySize(imp_filter_selected)," >< ",ArraySize(impact_filter_selected));
         if (debugLogging) ArrayPrint(imp_filter_selected);
         if (debugLogging) ArrayPrint(impact_filter_selected);
         if (MQLInfoInteger(MQL_TESTER)) filters_changed = true;
         update_dashboard_values(curr_filter_selected,imp_filter_selected);
         ObjectSetInteger(0,sparam,OBJPROP_BORDER_COLOR,color_border);
         if (debugLogging) Print("SUCCESS. DASHBOARD UPDATED");
         ChartRedraw(0);
      }
      // Scrollbar button clicks
      if (scroll_visible && (sparam == SCROLL_UP_REC || sparam == SCROLL_UP_LABEL)) {
         scrollUp();
         updateButtonColors();
         if (debugLogging) Print("Up button clicked (", sparam, "). CurrPos: ", scroll_pos);
         ChartRedraw(0);
      }
      if (scroll_visible && (sparam == SCROLL_DOWN_REC || sparam == SCROLL_DOWN_LABEL)) {
         scrollDown();
         updateButtonColors();
         if (debugLogging) Print("Down button clicked (", sparam, "). CurrPos: ", scroll_pos);
         ChartRedraw(0);
      }
   }
   else if (id == CHARTEVENT_MOUSE_MOVE && scroll_visible) {
      if (prev_mouse_state == 0 && mouse_state == 1) {
         int xd = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XDISTANCE);
         int yd = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE);
         int xs = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XSIZE);
         int ys = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE);
         if (mouse_x >= xd && mouse_x <= xd + xs && mouse_y >= yd && mouse_y <= yd + ys) {
            moving_state_slider = true;
            mlb_down_x = mouse_x;
            mlb_down_y = mouse_y;
            mlb_down_yd_slider = yd;
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, clrDodgerBlue);
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE, slider_height + 2);
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
            if (debugLogging) Print("Slider drag started at y=", mouse_y);
         }
      }
      if (moving_state_slider && mouse_state == 1) {
         int delta_y = mouse_y - mlb_down_y;
         int new_y = mlb_down_yd_slider + delta_y;
         int scroll_area_y_min = SCROLLBAR_Y + BUTTON_SIZE;
         int scroll_area_y_max = scroll_area_y_min + SCROLL_AREA_HEIGHT - slider_height;
         new_y = MathMax(scroll_area_y_min, MathMin(new_y, scroll_area_y_max));
         ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE, new_y);
         int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS);
         double scroll_ratio = (double)(new_y - scroll_area_y_min) / (scroll_area_y_max - scroll_area_y_min);
         int new_scroll_pos = (int)MathRound(scroll_ratio * max_scroll);
         if (new_scroll_pos != scroll_pos) {
            scroll_pos = new_scroll_pos;
            update_dashboard_values(curr_filter_selected, imp_filter_selected);
            updateButtonColors();
            if (debugLogging) Print("Slider dragged. CurrPos: ", scroll_pos, ", Total steps: ", max_scroll, ", Slider y=", new_y);
         }
         ChartRedraw(0);
      }
      if (mouse_state == 0) {
         if (moving_state_slider) {
            moving_state_slider = false;
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, clrLightSlateGray);
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE, slider_height);
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
            if (debugLogging) Print("Slider drag stopped.");
            ChartRedraw(0);
         }
      }
      prev_mouse_state = mouse_state;
   }
}

Hier rufen wir einfach die Funktionen und die Logik auf, die in den Event-Handler-Funktionen implementiert sind, damit die Änderungen in jedem erforderlichen Event-Handler wirksam werden. Nach dem Kompilieren erhalten wir die folgende Ausgabe.

SCROLLBAR-VISUALISIERUNG

Anhand der obigen Visualisierung können wir sehen, dass wir dem Dashboard eine dynamische Bildlaufleiste hinzugefügt haben. Was jetzt noch bleibt, sind gründliche Backtests des Systems, und das wird im nächsten Abschnitt behandelt.


Tests und Validierung

Wir haben das verbesserte Dashboard getestet, um zu bestätigen, dass die dynamische Bildlaufleiste und die Ereignisanzeige wie vorgesehen funktionieren und eine nahtlose Navigation durch die Nachrichtenereignisse ermöglichen. Unser Test konzentrierte sich auf das visuelle Feedback der Bildlaufleiste, die Anzeige aller gefilterten Ereignisse und die Effizienz der stillen Aktualisierungen, die sowohl im Live- als auch im Strategietester-Modus validiert wurden. Wir haben diese Tests in einem prägnanten Graphics Interchange Format (GIF) festgehalten, um die Leistung des Dashboards visuell zu demonstrieren (siehe unten).

PRÜFUNG 1

Anhand der Visualisierung können wir sehen, dass die Bildlaufleiste richtig funktioniert, aber dann gibt es ein Problem, dass die Bildlaufleiste nicht dynamisch ist, wenn wir die Filter durch Anklicken ändern, obwohl sich die Ereignisse richtig ändern. Das liegt an der fehlenden Neuberechnung, die die volle Dynamik ausschaltet. Um das zu erreichen, müssen wir die Bildlaufleiste jedes Mal, wenn wir auf die Schaltflächen klicken, auf rekalibrieren setzen. Wir könnten dasselbe erreichen, indem wir sie einfach aktualisieren, wenn sich die Daten ändern, aber das würde wieder zu redundanten Prozessen führen, die nicht notwendig sind. Hier ist die gesamte Logik, die wir dafür brauchen.

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) {
   int mouse_x = (int)lparam;
   int mouse_y = (int)dparam;
   int mouse_state = (int)sparam;

   if (id == CHARTEVENT_OBJECT_CLICK) {
      UpdateFilterInfo();
      CheckForNewsTrade();
      if (sparam == CANCEL_BTN) {
         isDashboardUpdate = false;
         destroy_Dashboard();
      }
      if (sparam == FILTER_CURR_BTN) {
         bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE);
         enableCurrencyFilter = btn_state;
         if (debugLogging) Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableCurrencyFilter);
         string filter_curr_text = enableCurrencyFilter ? ShortToString(0x2714)+"Currency" : ShortToString(0x274C)+"Currency";
         color filter_curr_txt_color = enableCurrencyFilter ? clrLime : clrRed;
         ObjectSetString(0,FILTER_CURR_BTN,OBJPROP_TEXT,filter_curr_text);
         ObjectSetInteger(0,FILTER_CURR_BTN,OBJPROP_COLOR,filter_curr_txt_color);
         if (MQLInfoInteger(MQL_TESTER)) filters_changed = true;
         update_dashboard_values(curr_filter_selected,imp_filter_selected);
         // Recalculate scrollbar
         ObjectDelete(0, SCROLL_LEADER);
         ObjectDelete(0, SCROLL_UP_REC);
         ObjectDelete(0, SCROLL_UP_LABEL);
         ObjectDelete(0, SCROLL_DOWN_REC);
         ObjectDelete(0, SCROLL_DOWN_LABEL);
         ObjectDelete(0, SCROLL_SLIDER);
         scroll_visible = totalEvents_Filtered > VISIBLE_ITEMS;
         if (debugLogging) Print("Scrollbar visibility: ", scroll_visible ? "Visible" : "Hidden");
         if (scroll_visible) {
            createRecLabel(SCROLL_LEADER, SCROLLBAR_X, SCROLLBAR_Y, SCROLLBAR_WIDTH, SCROLLBAR_HEIGHT, clrSilver, 1, clrNONE);
            int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS);
            color up_color = (scroll_pos == 0) ? clrLightGray : clrBlack;
            color down_color = (scroll_pos >= max_scroll) ? clrLightGray : clrBlack;
            createRecLabel(SCROLL_UP_REC, SCROLLBAR_X + BUTTON_OFFSET_X, SCROLLBAR_Y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray);
            createLabel(SCROLL_UP_LABEL, SCROLLBAR_X + BUTTON_OFFSET_X, SCROLLBAR_Y-5, CharToString(0x35), up_color, 15, "Webdings");
            int down_y = SCROLLBAR_Y + SCROLLBAR_HEIGHT - BUTTON_SIZE;
            createRecLabel(SCROLL_DOWN_REC, SCROLLBAR_X + BUTTON_OFFSET_X, down_y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray);
            createLabel(SCROLL_DOWN_LABEL, SCROLLBAR_X + BUTTON_OFFSET_X, down_y-5, CharToString(0x36), down_color, 15, "Webdings");
            slider_height = calculateSliderHeight();
            int slider_y = SCROLLBAR_Y + BUTTON_SIZE;
            createButton(SCROLL_SLIDER, SCROLLBAR_X + SLIDER_OFFSET_X, slider_y, SLIDER_WIDTH, slider_height, "", clrWhite, 12, clrLightSlateGray, clrDarkGray, "Arial Bold");
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_WIDTH, 2);
            if (debugLogging) Print("Scrollbar created: totalEvents_Filtered=", totalEvents_Filtered, ", slider_height=", slider_height);
            updateSliderPosition();
            updateButtonColors();
         }
         if (debugLogging) Print("Success. Changes updated! State: "+(string)enableCurrencyFilter);
         ChartRedraw(0);
      }
      if (sparam == FILTER_IMP_BTN) {
         bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE);
         enableImportanceFilter = btn_state;
         if (debugLogging) Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableImportanceFilter);
         string filter_imp_text = enableImportanceFilter ? ShortToString(0x2714)+"Importance" : ShortToString(0x274C)+"Importance";
         color filter_imp_txt_color = enableImportanceFilter ? clrLime : clrRed;
         ObjectSetString(0,FILTER_IMP_BTN,OBJPROP_TEXT,filter_imp_text);
         ObjectSetInteger(0,FILTER_IMP_BTN,OBJPROP_COLOR,filter_imp_txt_color);
         if (MQLInfoInteger(MQL_TESTER)) filters_changed = true;
         update_dashboard_values(curr_filter_selected,imp_filter_selected);
         // Recalculate scrollbar
         ObjectDelete(0, SCROLL_LEADER);
         ObjectDelete(0, SCROLL_UP_REC);
         ObjectDelete(0, SCROLL_UP_LABEL);
         ObjectDelete(0, SCROLL_DOWN_REC);
         ObjectDelete(0, SCROLL_DOWN_LABEL);
         ObjectDelete(0, SCROLL_SLIDER);
         scroll_visible = totalEvents_Filtered > VISIBLE_ITEMS;
         if (debugLogging) Print("Scrollbar visibility: ", scroll_visible ? "Visible" : "Hidden");
         if (scroll_visible) {
            createRecLabel(SCROLL_LEADER, SCROLLBAR_X, SCROLLBAR_Y, SCROLLBAR_WIDTH, SCROLLBAR_HEIGHT, clrSilver, 1, clrNONE);
            int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS);
            color up_color = (scroll_pos == 0) ? clrLightGray : clrBlack;
            color down_color = (scroll_pos >= max_scroll) ? clrLightGray : clrBlack;
            createRecLabel(SCROLL_UP_REC, SCROLLBAR_X + BUTTON_OFFSET_X, SCROLLBAR_Y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray);
            createLabel(SCROLL_UP_LABEL, SCROLLBAR_X + BUTTON_OFFSET_X, SCROLLBAR_Y-5, CharToString(0x35), up_color, 15, "Webdings");
            int down_y = SCROLLBAR_Y + SCROLLBAR_HEIGHT - BUTTON_SIZE;
            createRecLabel(SCROLL_DOWN_REC, SCROLLBAR_X + BUTTON_OFFSET_X, down_y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray);
            createLabel(SCROLL_DOWN_LABEL, SCROLLBAR_X + BUTTON_OFFSET_X, down_y-5, CharToString(0x36), down_color, 15, "Webdings");
            slider_height = calculateSliderHeight();
            int slider_y = SCROLLBAR_Y + BUTTON_SIZE;
            createButton(SCROLL_SLIDER, SCROLLBAR_X + SLIDER_OFFSET_X, slider_y, SLIDER_WIDTH, slider_height, "", clrWhite, 12, clrLightSlateGray, clrDarkGray, "Arial Bold");
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_WIDTH, 2);
            if (debugLogging) Print("Scrollbar created: totalEvents_Filtered=", totalEvents_Filtered, ", slider_height=", slider_height);
            updateSliderPosition();
            updateButtonColors();
         }
         if (debugLogging) Print("Success. Changes updated! State: "+(string)enableImportanceFilter);
         ChartRedraw(0);
      }
      if (sparam == FILTER_TIME_BTN) {
         bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE);
         enableTimeFilter = btn_state;
         if (debugLogging) Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableTimeFilter);
         string filter_time_text = enableTimeFilter ? ShortToString(0x2714)+"Time" : ShortToString(0x274C)+"Time";
         color filter_time_txt_color = enableTimeFilter ? clrLime : clrRed;
         ObjectSetString(0,FILTER_TIME_BTN,OBJPROP_TEXT,filter_time_text);
         ObjectSetInteger(0,FILTER_TIME_BTN,OBJPROP_COLOR,filter_time_txt_color);
         if (MQLInfoInteger(MQL_TESTER)) filters_changed = true;
         update_dashboard_values(curr_filter_selected,imp_filter_selected);
         // Recalculate scrollbar
         ObjectDelete(0, SCROLL_LEADER);
         ObjectDelete(0, SCROLL_UP_REC);
         ObjectDelete(0, SCROLL_UP_LABEL);
         ObjectDelete(0, SCROLL_DOWN_REC);
         ObjectDelete(0, SCROLL_DOWN_LABEL);
         ObjectDelete(0, SCROLL_SLIDER);
         scroll_visible = totalEvents_Filtered > VISIBLE_ITEMS;
         if (debugLogging) Print("Scrollbar visibility: ", scroll_visible ? "Visible" : "Hidden");
         if (scroll_visible) {
            createRecLabel(SCROLL_LEADER, SCROLLBAR_X, SCROLLBAR_Y, SCROLLBAR_WIDTH, SCROLLBAR_HEIGHT, clrSilver, 1, clrNONE);
            int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS);
            color up_color = (scroll_pos == 0) ? clrLightGray : clrBlack;
            color down_color = (scroll_pos >= max_scroll) ? clrLightGray : clrBlack;
            createRecLabel(SCROLL_UP_REC, SCROLLBAR_X + BUTTON_OFFSET_X, SCROLLBAR_Y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray);
            createLabel(SCROLL_UP_LABEL, SCROLLBAR_X + BUTTON_OFFSET_X, SCROLLBAR_Y-5, CharToString(0x35), up_color, 15, "Webdings");
            int down_y = SCROLLBAR_Y + SCROLLBAR_HEIGHT - BUTTON_SIZE;
            createRecLabel(SCROLL_DOWN_REC, SCROLLBAR_X + BUTTON_OFFSET_X, down_y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray);
            createLabel(SCROLL_DOWN_LABEL, SCROLLBAR_X + BUTTON_OFFSET_X, down_y-5, CharToString(0x36), down_color, 15, "Webdings");
            slider_height = calculateSliderHeight();
            int slider_y = SCROLLBAR_Y + BUTTON_SIZE;
            createButton(SCROLL_SLIDER, SCROLLBAR_X + SLIDER_OFFSET_X, slider_y, SLIDER_WIDTH, slider_height, "", clrWhite, 12, clrLightSlateGray, clrDarkGray, "Arial Bold");
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_WIDTH, 2);
            if (debugLogging) Print("Scrollbar created: totalEvents_Filtered=", totalEvents_Filtered, ", slider_height=", slider_height);
            updateSliderPosition();
            updateButtonColors();
         }
         if (debugLogging) Print("Success. Changes updated! State: "+(string)enableTimeFilter);
         ChartRedraw(0);
      }
      if (StringFind(sparam,CURRENCY_BTNS) >= 0) {
         string selected_curr = ObjectGetString(0,sparam,OBJPROP_TEXT);
         if (debugLogging) Print("BTN NAME = ",sparam,", CURRENCY = ",selected_curr);
         bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE);
         if (btn_state == false) {
            if (debugLogging) Print("BUTTON IS IN UN-SELECTED MODE.");
            for (int i = 0; i < ArraySize(curr_filter_selected); i++) {
               if (curr_filter_selected[i] == selected_curr) {
                  for (int j = i; j < ArraySize(curr_filter_selected) - 1; j++) {
                     curr_filter_selected[j] = curr_filter_selected[j + 1];
                  }
                  ArrayResize(curr_filter_selected, ArraySize(curr_filter_selected) - 1);
                  if (debugLogging) Print("Removed from selected filters: ", selected_curr);
                  break;
               }
            }
         } else {
            if (debugLogging) Print("BUTTON IS IN SELECTED MODE. TAKE ACTION");
            bool already_selected = false;
            for (int j = 0; j < ArraySize(curr_filter_selected); j++) {
               if (curr_filter_selected[j] == selected_curr) {
                  already_selected = true;
                  break;
               }
            }
            if (!already_selected) {
               ArrayResize(curr_filter_selected, ArraySize(curr_filter_selected) + 1);
               curr_filter_selected[ArraySize(curr_filter_selected) - 1] = selected_curr;
               if (debugLogging) Print("Added to selected filters: ", selected_curr);
            } else {
               if (debugLogging) Print("Currency already selected: ", selected_curr);
            }
         }
         if (debugLogging) Print("SELECTED ARRAY SIZE = ",ArraySize(curr_filter_selected));
         if (debugLogging) ArrayPrint(curr_filter_selected);
         if (MQLInfoInteger(MQL_TESTER)) filters_changed = true;
         update_dashboard_values(curr_filter_selected,imp_filter_selected);
         // Recalculate scrollbar
         ObjectDelete(0, SCROLL_LEADER);
         ObjectDelete(0, SCROLL_UP_REC);
         ObjectDelete(0, SCROLL_UP_LABEL);
         ObjectDelete(0, SCROLL_DOWN_REC);
         ObjectDelete(0, SCROLL_DOWN_LABEL);
         ObjectDelete(0, SCROLL_SLIDER);
         scroll_visible = totalEvents_Filtered > VISIBLE_ITEMS;
         if (debugLogging) Print("Scrollbar visibility: ", scroll_visible ? "Visible" : "Hidden");
         if (scroll_visible) {
            createRecLabel(SCROLL_LEADER, SCROLLBAR_X, SCROLLBAR_Y, SCROLLBAR_WIDTH, SCROLLBAR_HEIGHT, clrSilver, 1, clrNONE);
            int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS);
            color up_color = (scroll_pos == 0) ? clrLightGray : clrBlack;
            color down_color = (scroll_pos >= max_scroll) ? clrLightGray : clrBlack;
            createRecLabel(SCROLL_UP_REC, SCROLLBAR_X + BUTTON_OFFSET_X, SCROLLBAR_Y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray);
            createLabel(SCROLL_UP_LABEL, SCROLLBAR_X + BUTTON_OFFSET_X, SCROLLBAR_Y-5, CharToString(0x35), up_color, 15, "Webdings");
            int down_y = SCROLLBAR_Y + SCROLLBAR_HEIGHT - BUTTON_SIZE;
            createRecLabel(SCROLL_DOWN_REC, SCROLLBAR_X + BUTTON_OFFSET_X, down_y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray);
            createLabel(SCROLL_DOWN_LABEL, SCROLLBAR_X + BUTTON_OFFSET_X, down_y-5, CharToString(0x36), down_color, 15, "Webdings");
            slider_height = calculateSliderHeight();
            int slider_y = SCROLLBAR_Y + BUTTON_SIZE;
            createButton(SCROLL_SLIDER, SCROLLBAR_X + SLIDER_OFFSET_X, slider_y, SLIDER_WIDTH, slider_height, "", clrWhite, 12, clrLightSlateGray, clrDarkGray, "Arial Bold");
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_WIDTH, 2);
            if (debugLogging) Print("Scrollbar created: totalEvents_Filtered=", totalEvents_Filtered, ", slider_height=", slider_height);
            updateSliderPosition();
            updateButtonColors();
         }
         if (debugLogging) Print("SUCCESS. DASHBOARD UPDATED");
         ChartRedraw(0);
      }
      if (StringFind(sparam, IMPACT_LABEL) >= 0) {
         string selected_imp = ObjectGetString(0, sparam, OBJPROP_TEXT);
         ENUM_CALENDAR_EVENT_IMPORTANCE selected_importance_lvl = get_importance_level(impact_labels,allowed_importance_levels,selected_imp);
         if (debugLogging) Print("BTN NAME = ", sparam, ", IMPORTANCE LEVEL = ", selected_imp,"(",selected_importance_lvl,")");
         bool btn_state = ObjectGetInteger(0, sparam, OBJPROP_STATE);
         color color_border = btn_state ? clrNONE : clrBlack;
         if (btn_state == false) {
            if (debugLogging) Print("BUTTON IS IN UN-SELECTED MODE.");
            for (int i = 0; i < ArraySize(imp_filter_selected); i++) {
               if (impact_filter_selected[i] == selected_imp) {
                  for (int j = i; j < ArraySize(imp_filter_selected) - 1; j++) {
                     imp_filter_selected[j] = imp_filter_selected[j + 1];
                     impact_filter_selected[j] = impact_filter_selected[j + 1];
                  }
                  ArrayResize(imp_filter_selected, ArraySize(imp_filter_selected) - 1);
                  ArrayResize(impact_filter_selected, ArraySize(impact_filter_selected) - 1);
                  if (debugLogging) Print("Removed from selected importance filters: ", selected_imp,"(",selected_importance_lvl,")");
                  break;
               }
            }
         } else {
            if (debugLogging) Print("BUTTON IS IN SELECTED MODE. TAKE ACTION");
            bool already_selected = false;
            for (int j = 0; j < ArraySize(imp_filter_selected); j++) {
               if (impact_filter_selected[j] == selected_imp) {
                  already_selected = true;
                  break;
               }
            }
            if (!already_selected) {
               ArrayResize(imp_filter_selected, ArraySize(imp_filter_selected) + 1);
               imp_filter_selected[ArraySize(imp_filter_selected) - 1] = selected_importance_lvl;
               ArrayResize(impact_filter_selected, ArraySize(impact_filter_selected) + 1);
               impact_filter_selected[ArraySize(impact_filter_selected) - 1] = selected_imp;
               if (debugLogging) Print("Added to selected importance filters: ", selected_imp,"(",selected_importance_lvl,")");
            } else {
               if (debugLogging) Print("Importance level already selected: ", selected_imp,"(",selected_importance_lvl,")");
            }
         }
         if (debugLogging) Print("SELECTED ARRAY SIZE = ", ArraySize(imp_filter_selected)," >< ",ArraySize(impact_filter_selected));
         if (debugLogging) ArrayPrint(imp_filter_selected);
         if (debugLogging) ArrayPrint(impact_filter_selected);
         if (MQLInfoInteger(MQL_TESTER)) filters_changed = true;
         update_dashboard_values(curr_filter_selected,imp_filter_selected);
         ObjectSetInteger(0,sparam,OBJPROP_BORDER_COLOR,color_border);
         // Recalculate scrollbar
         ObjectDelete(0, SCROLL_LEADER);
         ObjectDelete(0, SCROLL_UP_REC);
         ObjectDelete(0, SCROLL_UP_LABEL);
         ObjectDelete(0, SCROLL_DOWN_REC);
         ObjectDelete(0, SCROLL_DOWN_LABEL);
         ObjectDelete(0, SCROLL_SLIDER);
         scroll_visible = totalEvents_Filtered > VISIBLE_ITEMS;
         if (debugLogging) Print("Scrollbar visibility: ", scroll_visible ? "Visible" : "Hidden");
         if (scroll_visible) {
            createRecLabel(SCROLL_LEADER, SCROLLBAR_X, SCROLLBAR_Y, SCROLLBAR_WIDTH, SCROLLBAR_HEIGHT, clrSilver, 1, clrNONE);
            int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS);
            color up_color = (scroll_pos == 0) ? clrLightGray : clrBlack;
            color down_color = (scroll_pos >= max_scroll) ? clrLightGray : clrBlack;
            createRecLabel(SCROLL_UP_REC, SCROLLBAR_X + BUTTON_OFFSET_X, SCROLLBAR_Y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray);
            createLabel(SCROLL_UP_LABEL, SCROLLBAR_X + BUTTON_OFFSET_X, SCROLLBAR_Y-5, CharToString(0x35), up_color, 15, "Webdings");
            int down_y = SCROLLBAR_Y + SCROLLBAR_HEIGHT - BUTTON_SIZE;
            createRecLabel(SCROLL_DOWN_REC, SCROLLBAR_X + BUTTON_OFFSET_X, down_y, BUTTON_WIDTH, BUTTON_SIZE, clrDarkGray, 1, clrDarkGray);
            createLabel(SCROLL_DOWN_LABEL, SCROLLBAR_X + BUTTON_OFFSET_X, down_y-5, CharToString(0x36), down_color, 15, "Webdings");
            slider_height = calculateSliderHeight();
            int slider_y = SCROLLBAR_Y + BUTTON_SIZE;
            createButton(SCROLL_SLIDER, SCROLLBAR_X + SLIDER_OFFSET_X, slider_y, SLIDER_WIDTH, slider_height, "", clrWhite, 12, clrLightSlateGray, clrDarkGray, "Arial Bold");
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_WIDTH, 2);
            if (debugLogging) Print("Scrollbar created: totalEvents_Filtered=", totalEvents_Filtered, ", slider_height=", slider_height);
            updateSliderPosition();
            updateButtonColors();
         }
         if (debugLogging) Print("SUCCESS. DASHBOARD UPDATED");
         ChartRedraw(0);
      }
      // Scrollbar button clicks
      if (scroll_visible && (sparam == SCROLL_UP_REC || sparam == SCROLL_UP_LABEL)) {
         scrollUp();
         updateButtonColors();
         if (debugLogging) Print("Up button clicked (", sparam, "). CurrPos: ", scroll_pos);
         ChartRedraw(0);
      }
      if (scroll_visible && (sparam == SCROLL_DOWN_REC || sparam == SCROLL_DOWN_LABEL)) {
         scrollDown();
         updateButtonColors();
         if (debugLogging) Print("Down button clicked (", sparam, "). CurrPos: ", scroll_pos);
         ChartRedraw(0);
      }
   }
   else if (id == CHARTEVENT_MOUSE_MOVE && scroll_visible) {
      if (prev_mouse_state == 0 && mouse_state == 1) {
         int xd = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XDISTANCE);
         int yd = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE);
         int xs = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XSIZE);
         int ys = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE);
         if (mouse_x >= xd && mouse_x <= xd + xs && mouse_y >= yd && mouse_y <= yd + ys) {
            moving_state_slider = true;
            mlb_down_x = mouse_x;
            mlb_down_y = mouse_y;
            mlb_down_yd_slider = yd;
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, clrDodgerBlue);
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE, slider_height + 2);
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
            if (debugLogging) Print("Slider drag started at y=", mouse_y);
         }
      }
      if (moving_state_slider && mouse_state == 1) {
         int delta_y = mouse_y - mlb_down_y;
         int new_y = mlb_down_yd_slider + delta_y;
         int scroll_area_y_min = SCROLLBAR_Y + BUTTON_SIZE;
         int scroll_area_y_max = scroll_area_y_min + SCROLL_AREA_HEIGHT - slider_height;
         new_y = MathMax(scroll_area_y_min, MathMin(new_y, scroll_area_y_max));
         ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE, new_y);
         int max_scroll = MathMax(0, ArraySize(displayableEvents) - VISIBLE_ITEMS);
         double scroll_ratio = (double)(new_y - scroll_area_y_min) / (scroll_area_y_max - scroll_area_y_min);
         int new_scroll_pos = (int)MathRound(scroll_ratio * max_scroll);
         if (new_scroll_pos != scroll_pos) {
            scroll_pos = new_scroll_pos;
            update_dashboard_values(curr_filter_selected, imp_filter_selected);
            updateButtonColors();
            if (debugLogging) Print("Slider dragged. CurrPos: ", scroll_pos, ", Total steps: ", max_scroll, ", Slider y=", new_y);
         }
         ChartRedraw(0);
      }
      if (mouse_state == 0) {
         if (moving_state_slider) {
            moving_state_slider = false;
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, clrLightSlateGray);
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE, slider_height);
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
            if (debugLogging) Print("Slider drag stopped.");
            ChartRedraw(0);
         }
      }
      prev_mouse_state = mouse_state;
   }
}

Hier haben wir einfach die Implementierung der Logik übernommen, die wir für die Klicks auf das Scrollbar-Element hatten, und sie auf die einzelnen Filterschaltflächen ausgedehnt. Wir brauchen nicht viel Zeit darauf zu verwenden. Nach der Kompilierung erhalten wir folgendes Ergebnis.

Anhand der Visualisierung können wir sehen, dass jetzt alles gut funktioniert, mit dynamischen Aktualisierungen und einer ausgefeilten Anzeige der Ereignisse.


Schlussfolgerung

Zusammenfassend lässt sich sagen, dass wir die Serie über den MQL5-Wirtschaftskalender durch die Einführung einer dynamischen Bildlaufleiste und einer ausgefeilten Ereignisanzeige weiterentwickelt haben, was uns eine intuitive Navigation und einen zuverlässigen Zugang zu Nachrichtenereignissen ermöglicht, wie unser umfassendes GIF zeigt. Diese Verbesserungen, die auf den optimierten Backtests von Teil 8 aufbauen, gewährleisten eine nahtlose Interaktion zwischen Live- und Testmodus und bieten eine robuste Plattform für nachrichtengesteuerte Handelsstrategien. Sie können dieses verfeinerte Dashboard als Grundlage nutzen und es an Ihre individuellen Handelsanforderungen anpassen.

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

Beigefügte Dateien |
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.
Die Grenzen des maschinellen Lernens überwinden (Teil 2): Mangelnde Reproduzierbarkeit Die Grenzen des maschinellen Lernens überwinden (Teil 2): Mangelnde Reproduzierbarkeit
Der Artikel geht der Frage nach, warum die Handelsergebnisse bei verschiedenen Brokern selbst bei Verwendung derselben Strategie und desselben Finanzsymbols aufgrund dezentraler Preisfestsetzung und Datenabweichungen erheblich voneinander abweichen können. Der Artikel hilft MQL5-Entwicklern zu verstehen, warum ihre Produkte auf dem MQL5-Marktplatz gemischte Bewertungen erhalten können, und fordert die Entwickler auf, ihre Ansätze auf bestimmte Makler zuzuschneiden, um transparente und reproduzierbare Ergebnisse zu gewährleisten. Dies könnte sich zu einer wichtigen bereichsgebundenen Best Practice entwickeln, die unserer Gemeinschaft gute Dienste leisten würde, wenn sie auf breiter Ebene übernommen würde.
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.
Datenwissenschaft und ML (Teil 39): News + Künstliche Intelligenz, würden Sie darauf wetten? Datenwissenschaft und ML (Teil 39): News + Künstliche Intelligenz, würden Sie darauf wetten?
Nachrichten treiben die Finanzmärkte an, insbesondere wichtige Veröffentlichungen wie die Non-Farm Payrolls (NFP, Beschäftigung außerhalb der Landwirtschaft). Wir alle haben schon erlebt, wie eine einzige Schlagzeile starke Kursbewegungen auslösen kann. In diesem Artikel befassen wir uns mit der leistungsstarken Schnittmenge von Nachrichtendaten und künstlicher Intelligenz.