English Русский 日本語
preview
MQL5-Handelswerkzeuge (Teil 10): Aufbau eines Strategieverfolgungssystems mit visuellen Ebenen und Erfolgsmetriken

MQL5-Handelswerkzeuge (Teil 10): Aufbau eines Strategieverfolgungssystems mit visuellen Ebenen und Erfolgsmetriken

MetaTrader 5Handelssysteme |
21 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In unserem vorherigen Artikel (Teil 9) haben wir einen First Run User Setup Wizard in MetaQuotes Language 5 (MQL5) für Expert Advisors entwickelt, der einen scrollbaren Leitfaden mit interaktiven Dashboard-Elementen, dynamischer Textformatierung und Nutzerkontrollen enthält, um den anfänglichen Konfigurations- und Orientierungsprozess zu optimieren. In Teil 10 entwickeln wir ein Strategie-Trackersystem mit visuellen Ebenen und Erfolgskennzahlen. Dieses System erkennt Kreuzungs-Signale des gleitenden Durchschnitts, die durch einen langfristigen gleitenden Durchschnitt gefiltert werden, verfolgt virtuelle oder Live-Handelsgeschäfte mit mehreren Take-Profit-Levels und Stop-Losses, visualisiert Eingänge, Treffer und Ergebnisse auf dem Chart und bietet ein Dashboard für Echtzeit-Performance-Statistiken, einschließlich Gewinne/Verluste, Gewinnpunkte und Erfolgsquote. Wir werden die folgenden Themen behandeln:

  1. Die Rolle und die Vorteile eines Strategie-Tracker-Systems im Handel
  2. Die Implementation in MQL5
  3. Testen des Strategie-Trackers
  4. Schlussfolgerung

Am Ende haben Sie ein funktionelles, anpassbares MQL5-Tool zur Überwachung der Strategieleistung. Lasst uns beginnen!


Die Rolle und die Vorteile eines Strategie-Tracker-Systems im Handel

Die Rolle und die Vorteile eines Strategieverfolgungssystems beim Handel liegen in seiner Fähigkeit, die Leistung von Signalen in Echtzeit zu überwachen und zu analysieren, was uns dabei hilft, die Wirksamkeit unserer Ansätze zu bewerten, ohne sich ausschließlich auf Backtests oder manuelle Protokolle zu verlassen, die zeitaufwändig und fehleranfällig sein können. Durch die Visualisierung von Einstiegen, Take-Profit-Treffern, Stop-Loss-Auslösern und kumulative Statistiken wie Gewinnraten und Gewinnpunkte auf dem Chart und dem Dashboard bietet es ein unmittelbares Feedback über die Durchführbarkeit der Strategie und ermöglicht schnelle Anpassungen von Parametern wie gleitenden Durchschnittsperioden oder Risikoniveaus, um die Ergebnisse in Live-Märkten zu verbessern. Letztlich verbessert ein solches Instrument die Entscheidungsfindung, schafft Vertrauen durch transparente Nachverfolgung und unterstützt die iterative Verfeinerung.

Unser Ansatz besteht darin, Kreuzungen von schnellen und langsamen gleitenden Durchschnitten bestätigt durch einen Filter gleitender Durchschnitt über/unter dem Preis für Käufe/Verkäufe – das ist nur eine willkürliche Strategie, die wir für einfach gehalten haben, und kann mit jeder Strategie Ihrer Wahl ausgetauscht werden; Wir simulieren virtuelle Positionen oder führen reale Handelsgeschäfte mit konfigurierbaren Take-Profit-Levels und Stop-Loss in Punkten aus, visualisieren Eingänge mit Pfeilen, gepunkteten Linien für Treffer und Icons für Ergebnisse auf dem Chart, während wir ein interaktives Dashboard mit Statistiken zu Signalen, Gewinnen/Verlusten, Alter, Gewinn und Erfolgsquote aktualisieren und so ein umfassendes Tool zur Strategiebewertung und -verfeinerung schaffen. Kurz gesagt, hier ist eine Visualisierung dessen, was wir erreichen wollen.

SYSTEMVISUALISIERUNG


Die Implementation in MQL5

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

//+------------------------------------------------------------------+
//|                                       1. Strategy Tracker EA.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Enums                                                            |
//+------------------------------------------------------------------+
enum TradeMode {                                                  // Define trade mode enum
   Visual_Only,                                                   // Visual Only
   Open_Trades                                                    // Open Trades
};

enum TPLevel {                                                    // Define TP level enum
   Level_1,                                                       // TP1
   Level_2,                                                       // TP2
   Level_3                                                        // TP3
};

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input TradeMode        trade_mode      = Visual_Only;             // Trading Mode
input int              fast_ma_period  = 10;                      // Fast MA Period
input int              slow_ma_period  = 20;                      // Slow MA Period
input int              filter_ma_period = 200;                    // Filter MA Period
input ENUM_MA_METHOD   ma_method       = MODE_SMA;                // MA Method
input ENUM_APPLIED_PRICE ma_price      = PRICE_CLOSE;             // MA Applied Price
input int              tp1_points      = 50;                      // TP1 Points
input int              tp2_points      = 100;                     // TP2 Points
input int              tp3_points      = 150;                     // TP3 Points
input TPLevel          tp_level        = Level_1;                 // Select TP Level
input int              sl_points       = 150;                     // SL Points
input int              dash_x          = 30;                      // Dashboard X Offset
input int              dash_y          = 30;                      // Dashboard Y Offset

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
// Handles for indicators
int h_fast_ma, h_slow_ma, h_filter_ma;                            //--- MA handles
// Active signal structure
struct ActiveSignal {                                             // Define active signal structure
   bool     active;                                               //--- Signal active flag
   int      pos_type;                                             //--- Position type (1 buy, -1 sell)
   datetime entry_time;                                           //--- Entry time
   double   entry_price;                                          //--- Entry price
   double   tp1, tp2, tp3, sl;                                    //--- TP and SL levels
   bool     hit_tp1, hit_tp2, hit_tp3;                            //--- TP hit flags
   bool     hit_sl;                                               //--- SL hit flag
   datetime close_time;                                           //--- Close time
};
ActiveSignal current_signal;                                      //--- Current signal instance
// Stats
long   total_signals      = 0;                                    //--- Total signals count
long   wins               = 0;                                    //--- Wins count
long   losses             = 0;                                    //--- Losses count
double total_profit_points = 0.0;                                 //--- Total profit in points
// Dashboard prefix
string dash_prefix = "ProDashboard_";                             //--- Dashboard object prefix
// Last bar time
datetime last_bar_time = 0;                                       //--- Last processed bar time
// Position ticket for Open_Trades mode
ulong position_ticket = -1;                                       //--- Position ticket

Zunächst definieren wir zwei Enumerationen für Konfigurationsoptionen: „TradeMode“ mit „Visual_Only“ für die Simulation ohne reale Aufträge und „Open_Trades“, um Live-Positionen auszuführen, falls Sie die Strategie handeln möchten, und „TPLevel“ mit „Level_1“, „Level_2“ oder „Level_3“, um das Take-Profit-Ziel auszuwählen.

Als Nächstes richten wir Eingabeparameter für die Nutzeranpassung ein: „trade_mode“ auf „Visual_Only“, Periodenlängen wie „fast_ma_period“ auf 10 für den schnellen gleitenden Durchschnitt, „slow_ma_period“ auf 20 für den langsameren, „filter_ma_period“ auf 200 für den langfristigen Filter, „ma_method“ auf MODE_SMA für die einfache gleitende Durchschnittsberechnung und „ma_price“ auf PRICE auf 20 für den langsameren, „filter_ma_period“ auf 200 für den langfristigen Filter, „ma_method“ als MODE_SMA für die Berechnung des einfachen gleitenden Durchschnitts und „ma_price“ auf PRICE_CLOSE auf der Grundlage der Schlusskurse. Wir setzen die Take-Profit-Abstände „tp1_points“ auf 50, „tp2_points“ auf 100, „tp3_points“ auf 150, wobei „tp_level“ auf „Level_1“, „sl_points“ bei 150 für den Stop-Loss und die Dashboard-Offsets „dash_x“ und „dash_y“ jeweils bei 30 für die Positionierung.

Dann deklarieren wir die globalen Variablen: Handles „h_fast_ma“, „h_slow_ma“, „h_filter_ma“ für die gleitenden Durchschnittsindikatoren, eine Struktur „ActiveSignal“ zur Verfolgung laufender Handelsgeschäfte mit Feldern wie „active“-Flag, „pos_type“ für Kauf (1) oder Verkauf (-1), Einstiegsdetails, Take-Profit- und Stop-Loss-Levels, Hit-Flags und Abschlusszeit, instanziiert als „current_signal“. Zu den statistischen Zählern gehören „total_signals“, „wins“, „losses“ und „total_profit_points“ auf 0.0 für Leistungsmetriken, „dash_prefix“ als „ProDashboard_“ für die Objektbenennung, „last_bar_time“ auf 0, um neue Balken zu erkennen, und „position_ticket“ auf -1 für die Verfolgung offener Handelsgeschäfte im Live-Modus. Anschließend benötigen wir Hilfsfunktionen, um die Visualisierungsobjekte zu erstellen.

//+------------------------------------------------------------------+
//| Function to create rectangle label                               |
//+------------------------------------------------------------------+
bool createRecLabel(string objName, int xD, int yD, int xS, int yS,
                    color clrBg, int widthBorder, color clrBorder = clrNONE,
                    ENUM_BORDER_TYPE borderType = BORDER_FLAT,
                    ENUM_LINE_STYLE borderStyle = STYLE_SOLID,
                    ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER) {
   ResetLastError();                                              //--- Reset last error
   if (!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Create rectangle label
      Print(__FUNCTION__, ": failed to create rec label! Error code = ", _LastError); //--- Log error
      return (false);                                             //--- Return failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD);           //--- Set X distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD);           //--- Set Y distance
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS);               //--- Set X size
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS);               //--- Set Y size
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);          //--- Set corner
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg);          //--- Set background color
   ObjectSetInteger(0, objName, OBJPROP_BORDER_TYPE, borderType); //--- Set border type
   ObjectSetInteger(0, objName, OBJPROP_STYLE, borderStyle);      //--- Set border style
   ObjectSetInteger(0, objName, OBJPROP_WIDTH, widthBorder);      //--- Set border width
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clrBorder);        //--- Set border color
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);             //--- Set to foreground
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);            //--- Disable state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);       //--- Disable selectable
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);         //--- Disable selected
   ChartRedraw(0);                                                //--- Redraw chart
   return (true);                                                 //--- Return success
}

//+------------------------------------------------------------------+
//| Function to create text label                                    |
//+------------------------------------------------------------------+
bool createLabel(string objName, int xD, int yD,
                 string txt, color clrTxt = clrBlack, int fontSize = 12,
                 string font = "Arial Rounded MT Bold",
                 ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER) {
   ResetLastError();                                              //--- Reset last error
   if (!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) {           //--- Create label
      Print(__FUNCTION__, ": failed to create the label! Error code = ", _LastError); //--- Log error
      return (false);                                             //--- Return failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD);           //--- Set X distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD);           //--- Set Y distance
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);          //--- Set corner
   ObjectSetString(0, objName, OBJPROP_TEXT, txt);                //--- Set text
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt);           //--- Set color
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize);      //--- Set font size
   ObjectSetString(0, objName, OBJPROP_FONT, font);               //--- Set font
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);             //--- Set to foreground
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);            //--- Disable state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);       //--- Disable selectable
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);         //--- Disable selected
   ChartRedraw(0);                                                //--- Redraw chart
   return (true);                                                 //--- Return success
}

//+------------------------------------------------------------------+
//| Function to create trend line                                    |
//+------------------------------------------------------------------+
bool createTrendline(string objName, datetime time1, double price1, datetime time2, double price2, color clr, ENUM_LINE_STYLE line_style = STYLE_SOLID, bool isBack = false, bool ray_right = false) {
   ResetLastError();                                              //--- Reset last error
   if (!ObjectCreate(0, objName, OBJ_TREND, 0, time1, price1, time2, price2)) { //--- Create trendline
      Print(__FUNCTION__, ": Failed to create trendline: Error Code: ", GetLastError()); //--- Log error
      return (false);                                             //--- Return failure
   }
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);              //--- Set color
   ObjectSetInteger(0, objName, OBJPROP_STYLE, line_style);       //--- Set style
   ObjectSetInteger(0, objName, OBJPROP_BACK, isBack);            //--- Set back
   ObjectSetInteger(0, objName, OBJPROP_RAY_RIGHT, ray_right);    //--- Set ray right
   ChartRedraw(0);                                                //--- Redraw chart
   return (true);                                                 //--- Return success
}

Wir definieren die Funktion „createRecLabel“, um Rechtecke zur Beschriftungen für Dashboard-Panels zu erzeugen, wobei wir Parameter wie Name, Position („xD“, „yD“), Größe („xS“, „yS“), Hintergrundfarbe „clrBg“, Breite des Rahmens und optionale Rahmenfarbe „clrBorder“, die standardmäßig clrNONE ist, den Typ BORDER_FLAT, den Stil STYLE_SOLID und die Ankerecke CORNER_LEFT_UPPER. Wir setzen den Fehler mit ResetLastError zurück, erstellen das Objekt als OBJ_RECTANGLE_LABEL, setzen Eigenschaften über ObjectSetInteger für Abstände, Größen, Ecken, Farben, Rahmendetails und den nicht auswählbaren Zustand des Vordergrunds, zeichnen mit ChartRedraw neu und protokollieren Fehler, falls vorhanden.

In ähnlicher Weise erstellt „createLabel“ Text-Etiketten mit Name, Position, Text „txt“, Farbe „clrTxt“, Standard-Schwarz, Größe 12, Schriftart „Arial Rounded MT Bold“ und Ecke „CORNER_LEFT_UPPER“, erstellt als OBJ_LABEL, setzt Text- und Schrifteigenschaften, stellt sicher, dass Vordergrund und nicht auswählbar, neu gezeichnet und Fehler behandelt werden. Für „createTrendline“ erstellen wir Trendlinien mit Namen, Zeiten/Preisen für Endpunkte, Farbe „clr“, Stil standardmäßig „STYLE_SOLID“, Rückwärts-Flag falsch und Strahl rechts falsch, erstellen als OBJ_TREND, setzen Farbe/Stil/Rückwärtsstrahl, zeichnen neu und protokollieren Fehler bei der Erstellung. Mit diesen Funktionen können wir nun das erste Dashboard erstellen. Wir werden die Logik auch in einer Funktion modularisieren.

//+------------------------------------------------------------------+
//| Create dashboard                                                 |
//+------------------------------------------------------------------+
void CreateDashboard() {
   int panel_x = dash_x;                                          //--- Panel X
   int panel_y = dash_y;                                          //--- Panel Y
   int panel_w = 250;                                             //--- Panel width
   int panel_h = 350;                                             //--- Panel height
   color bg_color = clrNavy;                                      //--- BG color
   color border_color = clrRoyalBlue;                             //--- Border color
   string space = " ";                                            //--- Space string
   createRecLabel(dash_prefix + "Panel", panel_x, panel_y, panel_w, panel_h, bg_color, 1, border_color, BORDER_FLAT); //--- Create panel
   color header_bg = clrMidnightBlue;                             //--- Header BG
   createRecLabel(dash_prefix + "HeaderPanel", panel_x + 1, panel_y + 1, panel_w - 2, 40, header_bg, 0, clrNONE, BORDER_FLAT); //--- Create header
   int rel_y = 7;                                                 //--- Relative Y
   createLabel(dash_prefix + "Header", panel_x + 15, panel_y + rel_y, "Strategy Tracker Dashboard", clrMediumSpringGreen, 12, "Arial Bold"); //--- Create header label
   rel_y += 30;                                                   //--- Increment Y
   color signal_bg = clrDarkSlateBlue;                            //--- Signal BG
   int signal_height = 160;                                       //--- Signal height
   createRecLabel(dash_prefix + "SignalPanel", panel_x + 1, panel_y + rel_y - 10, panel_w - 2, signal_height, signal_bg, 0, clrNONE, BORDER_FLAT); //--- Create signal panel
   createLabel(dash_prefix + "SignalHeader", panel_x + 10, panel_y + rel_y, "Current Signal", clrLightCyan, 11, "Arial Bold"); //--- Create signal header
   rel_y += 25;                                                   //--- Increment Y
   createLabel(dash_prefix + "SymbolLabel", panel_x + 10, panel_y + rel_y, "Symbol:", clrWhite, 10, "Arial Bold"); //--- Create symbol label
   createLabel(dash_prefix + "SymbolValue", panel_x + 100, panel_y + rel_y, _Symbol+" "+StringSubstr(EnumToString(_Period),7), clrDeepSkyBlue, 10, "Arial Bold"); //--- Create symbol value
   rel_y += 20;                                                   //--- Increment Y
   createLabel(dash_prefix + "DirectionLabel", panel_x + 10, panel_y + rel_y, "Signal:", clrWhite, 10, "Arial Bold"); //--- Create direction label
   createLabel(dash_prefix + "EntryPrice", panel_x + 100, panel_y + rel_y, space, clrWhite, 10, "Arial Bold"); //--- Create entry price
   createLabel(dash_prefix + "DirectionValue", panel_x + 200, panel_y + rel_y, space, clrWhite, 12, "Wingdings"); //--- Create direction value
   rel_y += 20;                                                   //--- Increment Y
   createLabel(dash_prefix + "TP1Label", panel_x + 10, panel_y + rel_y, "TP1:", clrWhite, 10, "Arial Bold"); //--- Create TP1 label
   createLabel(dash_prefix + "TP1Value", panel_x + 100, panel_y + rel_y, space, clrWhite, 10, "Arial"); //--- Create TP1 value
   createLabel(dash_prefix + "TP1Icon", panel_x + 200, panel_y + rel_y, space, clrWhite, 12, "Wingdings"); //--- Create TP1 icon
   rel_y += 20;                                                   //--- Increment Y
   createLabel(dash_prefix + "TP2Label", panel_x + 10, panel_y + rel_y, "TP2:", clrWhite, 10, "Arial Bold"); //--- Create TP2 label
   createLabel(dash_prefix + "TP2Value", panel_x + 100, panel_y + rel_y, space, clrWhite, 10, "Arial"); //--- Create TP2 value
   createLabel(dash_prefix + "TP2Icon", panel_x + 200, panel_y + rel_y, space, clrWhite, 12, "Wingdings"); //--- Create TP2 icon
   rel_y += 20;                                                   //--- Increment Y
   createLabel(dash_prefix + "TP3Label", panel_x + 10, panel_y + rel_y, "TP3:", clrWhite, 10, "Arial Bold"); //--- Create TP3 label
   createLabel(dash_prefix + "TP3Value", panel_x + 100, panel_y + rel_y, space, clrWhite, 10, "Arial"); //--- Create TP3 value
   createLabel(dash_prefix + "TP3Icon", panel_x + 200, panel_y + rel_y, space, clrWhite, 12, "Wingdings"); //--- Create TP3 icon
   rel_y += 20;                                                   //--- Increment Y
   createLabel(dash_prefix + "SLLabel", panel_x + 10, panel_y + rel_y, "SL:", clrWhite, 10, "Arial Bold"); //--- Create SL label
   createLabel(dash_prefix + "SLValue", panel_x + 100, panel_y + rel_y, space, clrWhite, 10, "Arial"); //--- Create SL value
   createLabel(dash_prefix + "SLIcon", panel_x + 200, panel_y + rel_y, space, clrWhite, 12, "Wingdings"); //--- Create SL icon
   rel_y += 20;                                                   //--- Increment Y
   color stats_bg = clrIndigo;                                    //--- Stats BG
   int stats_height = 140;                                        //--- Stats height
   createRecLabel(dash_prefix + "StatsPanel", panel_x + 1, panel_y + rel_y + 3, panel_w - 2, stats_height, stats_bg, 0, clrNONE, BORDER_FLAT); //--- Create stats panel
   createLabel(dash_prefix + "StatsHeader", panel_x + 10, panel_y + rel_y + 10, "Statistics", clrLightCyan, 11, "Arial Bold"); //--- Create stats header
   rel_y += 25;                                                   //--- Increment Y
   createLabel(dash_prefix + "TotalLabel", panel_x + 10, panel_y + rel_y+10, "Total Signals:", clrWhite, 10, "Arial Bold"); //--- Create total label
   createLabel(dash_prefix + "TotalValue", panel_x + 150, panel_y + rel_y+10, space, clrWhite, 10, "Arial"); //--- Create total value
   rel_y += 20;                                                   //--- Increment Y
   createLabel(dash_prefix + "WinLossLabel", panel_x + 10, panel_y + rel_y+10, "Win/Loss:", clrWhite, 10, "Arial Bold"); //--- Create win/loss label
   createLabel(dash_prefix + "WinLossValue", panel_x + 150, panel_y + rel_y+10, space, clrWhite, 10, "Arial"); //--- Create win/loss value
   rel_y += 20;                                                   //--- Increment Y
   createLabel(dash_prefix + "AgeLabel", panel_x + 10, panel_y + rel_y+10, "Last Signal Age:", clrWhite, 10, "Arial Bold"); //--- Create age label
   createLabel(dash_prefix + "AgeValue", panel_x + 150, panel_y + rel_y+10, space, clrWhite, 10, "Arial"); //--- Create age value
   rel_y += 20;                                                   //--- Increment Y
   createLabel(dash_prefix + "ProfitLabel", panel_x + 10, panel_y + rel_y+10, "Profit in Points:", clrWhite, 10, "Arial Bold"); //--- Create profit label
   createLabel(dash_prefix + "ProfitValue", panel_x + 150, panel_y + rel_y+10, space, clrWhite, 10, "Arial"); //--- Create profit value
   rel_y += 20;                                                   //--- Increment Y
   createLabel(dash_prefix + "SuccessLabel", panel_x + 10, panel_y + rel_y+10, "Success Rate:", clrWhite, 10, "Arial Bold"); //--- Create success label
   createLabel(dash_prefix + "SuccessValue", panel_x + 150, panel_y + rel_y+10, space, clrWhite, 10, "Arial"); //--- Create success value
   rel_y += 20;                                                   //--- Increment Y
   color footer_bg = clrMidnightBlue;                             //--- Footer BG
   createRecLabel(dash_prefix + "FooterPanel", panel_x + 1, panel_y + rel_y + 5+10, panel_w - 2, 25, footer_bg, 0, clrNONE, BORDER_FLAT); //--- Create footer
   createLabel(dash_prefix + "Footer", panel_x + 30, panel_y + rel_y + 10+10, "Copyright 2025, Allan Munene Mutiiria.", clrYellow, 8, "Arial"); //--- Create footer label
}

Wir implementieren die Funktion „CreateDashboard“, um das visuelle Panel für Statistiken und Signale einzurichten, beginnend mit den Positionen „panel_x“ von „dash_x“ und „panel_y“ von „dash_y“, Abmessungen 250 breit mal 350 hoch, marineblauer Hintergrund mit königsblauem Rand, den Sie nach Belieben ändern können, mit „createRecLabel“ für den Hauptcontainer. Wir erstellen eine leicht versetzte Kopfzeile mit nachtblauem Hintergrund, ohne Rahmen, und fügen eine Beschriftung „Strategy Tracker Dashboard“ in mittlerem Frühlingsgrün und fetter Arial-Größe 12 hinzu. Indem wir das relative y „rel_y“ um 30 erhöhen, zeichnen wir ein Signal-Sub-Panel in dunklem Schieferblau, Höhe 160, mit der Überschrift „Aktuelles Signal“ in hellem Cyan, Fettdruckgröße 11 Pixel.

Indem wir „rel_y“ um 25 erhöhen, fügen wir Beschriftungen für „Symbol:“ in weißem Fettdruck der Größe 10, den Wert als Symbol plus Zeitrahmen-Teilstring aus EnumToString in tiefem Himmelblau hinzu; dann die Beschriftung „Signal:“, den Platzhalter für den Einstiegspreis in weißem Fettdruck und den Platzhalter für das Richtungssymbol in weißem Wingdings der Größe 12. Für jeden Take-Profit und Stop-Loss fügen wir Bezeichnungen wie „TP1:“, Werte als Leerzeichen in weißer Arial-Größe 10 und Symbole in weißer Wingdings-Größe 12 hinzu, wobei „rel_y“ jedes Mal um 20 erhöht wird. Nach weiteren 20 erstellen wir ein Statistik-Unterpaneel in Indigo, Höhe 140, mit einer Überschrift „Statistik“ in hellcyan bold 11. Indem wir „rel_y“ um 25 erhöhen, platzieren wir Statistikbeschriftungen wie „Total Signals:“ in weißem Fettdruck 10, mit Wertplatzhaltern in weißem Arial 10; ähnlich für „Win/Loss:“, „Last Signal Age:“, „Profit in Points:“, „Success Rate:“, mit einem Abstand von 20. Schließlich, nach 20 weiteren, fügen wir eine Fußzeile in Mitternachtsblau, Höhe 25, mit Copyright-Text in gelb Arial Größe 8 Pixel. Wir können nun das System initialisieren und sehen, was wir erhalten. Wir hätten mit allem anfangen können; dies ist wirklich das einfachste Material, um uns zu integrieren.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   h_fast_ma = iMA(_Symbol, PERIOD_CURRENT, fast_ma_period, 0, ma_method, ma_price); //--- Create fast MA
   h_slow_ma = iMA(_Symbol, PERIOD_CURRENT, slow_ma_period, 0, ma_method, ma_price); //--- Create slow MA
   h_filter_ma = iMA(_Symbol, PERIOD_CURRENT, filter_ma_period, 0, ma_method, ma_price); //--- Create filter MA
   if (h_fast_ma == INVALID_HANDLE || h_slow_ma == INVALID_HANDLE || h_filter_ma == INVALID_HANDLE) { //--- Check handles
      Print("Failed to initialize MA handles");                   //--- Log error
      return(INIT_FAILED);                                        //--- Return failure
   }
   current_signal.active = false;                                 //--- Reset active
   current_signal.hit_sl = false;                                 //--- Reset SL hit
   current_signal.close_time = 0;                                 //--- Reset close time
   CreateDashboard();                                             //--- Create dashboard
   return(INIT_SUCCEEDED);                                        //--- Return success
}

In der Ereignisbehandlung von OnInit erstellen wir Handles für die drei gleitenden Durchschnitte mit iMA mit dem Symbol _Symbol, dem aktuellen Zeitrahmen PERIOD_CURRENT, den jeweiligen Periodenlängen („fast_ma_period“, „slow_ma_period“, „filter_ma_period“), dem „shift“ Null, die Methode „ma_method“ und Preistyp „ma_price“ und speichern sie in „h_fast_ma“, „h_slow_ma“ und „h_filter_ma“. Wir prüfen, ob ein Handle INVALID_HANDLE ist, protokollieren einen Fehler mit Print und geben in diesem Fall INIT_FAILED zurück, um die Einrichtung zu beenden. Wir haben gesagt und wiederholen es noch einmal, dass das Signal, für das Sie sich entscheiden, ausschließlich Ihre Entscheidung ist, da es sich um ein Instrument zur Verfolgung einer Strategie handelt. Es steht Ihnen also frei, zu der von Ihnen gewünschten Strategie zu wechseln. Dann setzen wir die Struktur „current_signal“ zurück, indem wir „active“ auf false, „hit_sl“ auf false und „close_time“ auf 0 setzen, um einen sauberen Start zu ermöglichen. Wir rufen „CreateDashboard“ auf, um das visuelle Panel zu initialisieren, und geben dann INIT_SUCCEEDED zurück, um das erfolgreiche Laden zu bestätigen. Es ist immer eine gute Programmierpraxis, die Codefragmente bei jedem Meilenstein zu testen, und beim Testen erhalten wir das folgende Ergebnis.

INITIALISIERUNG

Wir können sehen, dass wir das Programm erfolgreich initialisiert haben. Wir müssen nun die Signale erkennen und verfolgen, ihre Informationen speichern und sie zur leichteren Verfolgung visualisieren. Um dies zu erreichen, werden wir einige Hilfsfunktionen definieren, die die Visualisierung wie unten beschrieben durchführen.

//+------------------------------------------------------------------+
//| Draw initial TP and SL visuals                                   |
//+------------------------------------------------------------------+
void DrawInitialLevels() {
   datetime entry_tm = current_signal.entry_time;                 //--- Entry time
   datetime bubble_tm = entry_tm;                                 //--- Bubble time
   datetime points_tm = bubble_tm;                                //--- Points time
   // TP1
   string prefix = "Initial_TP1_" + TimeToString(entry_tm) + "_"; //--- TP1 prefix
   color tp_color = clrBlue;                                      //--- TP color
   double hit_pr = current_signal.tp1;                            //--- TP1 price
   int pts = tp1_points;                                          //--- TP1 points
   char bubble_code = (char)140;                                  //--- Bubble code
   string bubble_name = prefix + "Bubble";                        //--- Bubble name
   ObjectCreate(0, bubble_name, OBJ_TEXT, 0, bubble_tm, hit_pr);  //--- Create bubble
   ObjectSetString(0, bubble_name, OBJPROP_TEXT, CharToString(bubble_code)); //--- Set text
   ObjectSetString(0, bubble_name, OBJPROP_FONT, "Wingdings");    //--- Set font
   ObjectSetInteger(0, bubble_name, OBJPROP_COLOR, tp_color);     //--- Set color
   ObjectSetInteger(0, bubble_name, OBJPROP_FONTSIZE, 12);        //--- Set size
   ObjectSetInteger(0, bubble_name, OBJPROP_ANCHOR, ANCHOR_LEFT); //--- Set anchor
   string points_name = prefix + "Points";                        //--- Points name
   ObjectCreate(0, points_name, OBJ_TEXT, 0, points_tm, hit_pr);  //--- Create points
   ObjectSetString(0, points_name, OBJPROP_TEXT, "+" + IntegerToString(pts)); //--- Set text
   ObjectSetInteger(0, points_name, OBJPROP_COLOR, tp_color);     //--- Set color
   ObjectSetInteger(0, points_name, OBJPROP_FONTSIZE, 10);        //--- Set size
   ObjectSetInteger(0, points_name, OBJPROP_ANCHOR, ANCHOR_RIGHT); //--- Set anchor
   // TP2
   prefix = "Initial_TP2_" + TimeToString(entry_tm) + "_";        //--- TP2 prefix
   hit_pr = current_signal.tp2;                                   //--- TP2 price
   pts = tp2_points;                                              //--- TP2 points
   bubble_code = (char)141;                                       //--- Bubble code
   bubble_name = prefix + "Bubble";                               //--- Bubble name
   ObjectCreate(0, bubble_name, OBJ_TEXT, 0, bubble_tm, hit_pr);  //--- Create bubble
   ObjectSetString(0, bubble_name, OBJPROP_TEXT, CharToString(bubble_code)); //--- Set text
   ObjectSetString(0, bubble_name, OBJPROP_FONT, "Wingdings");    //--- Set font
   ObjectSetInteger(0, bubble_name, OBJPROP_COLOR, tp_color);     //--- Set color
   ObjectSetInteger(0, bubble_name, OBJPROP_FONTSIZE, 12);        //--- Set size
   ObjectSetInteger(0, bubble_name, OBJPROP_ANCHOR, ANCHOR_LEFT); //--- Set anchor
   points_name = prefix + "Points";                               //--- Points name
   ObjectCreate(0, points_name, OBJ_TEXT, 0, points_tm, hit_pr);  //--- Create points
   ObjectSetString(0, points_name, OBJPROP_TEXT, "+" + IntegerToString(pts)); //--- Set text
   ObjectSetInteger(0, points_name, OBJPROP_COLOR, tp_color);     //--- Set color
   ObjectSetInteger(0, points_name, OBJPROP_FONTSIZE, 10);        //--- Set size
   ObjectSetInteger(0, points_name, OBJPROP_ANCHOR, ANCHOR_RIGHT); //--- Set anchor
   // TP3
   prefix = "Initial_TP3_" + TimeToString(entry_tm) + "_";        //--- TP3 prefix
   hit_pr = current_signal.tp3;                                   //--- TP3 price
   pts = tp3_points;                                              //--- TP3 points
   bubble_code = (char)142;                                       //--- Bubble code
   bubble_name = prefix + "Bubble";                               //--- Bubble name
   ObjectCreate(0, bubble_name, OBJ_TEXT, 0, bubble_tm, hit_pr);  //--- Create bubble
   ObjectSetString(0, bubble_name, OBJPROP_TEXT, CharToString(bubble_code)); //--- Set text
   ObjectSetString(0, bubble_name, OBJPROP_FONT, "Wingdings");    //--- Set font
   ObjectSetInteger(0, bubble_name, OBJPROP_COLOR, tp_color);     //--- Set color
   ObjectSetInteger(0, bubble_name, OBJPROP_FONTSIZE, 12);        //--- Set size
   ObjectSetInteger(0, bubble_name, OBJPROP_ANCHOR, ANCHOR_LEFT); //--- Set anchor
   points_name = prefix + "Points";                               //--- Points name
   ObjectCreate(0, points_name, OBJ_TEXT, 0, points_tm, hit_pr);  //--- Create points
   ObjectSetString(0, points_name, OBJPROP_TEXT, "+" + IntegerToString(pts)); //--- Set text
   ObjectSetInteger(0, points_name, OBJPROP_COLOR, tp_color);     //--- Set color
   ObjectSetInteger(0, points_name, OBJPROP_FONTSIZE, 10);        //--- Set size
   ObjectSetInteger(0, points_name, OBJPROP_ANCHOR, ANCHOR_RIGHT); //--- Set anchor
   // SL
   prefix = "Initial_SL_" + TimeToString(entry_tm) + "_";         //--- SL prefix
   hit_pr = current_signal.sl;                                    //--- SL price
   color sl_color = clrMagenta;                                   //--- SL color
   pts = sl_points;                                               //--- SL points
   bubble_code = (char)164;                                       //--- Bubble code
   bubble_name = prefix + "Bubble";                               //--- Bubble name
   ObjectCreate(0, bubble_name, OBJ_TEXT, 0, bubble_tm, hit_pr);  //--- Create bubble
   ObjectSetString(0, bubble_name, OBJPROP_TEXT, CharToString(bubble_code)); //--- Set text
   ObjectSetString(0, bubble_name, OBJPROP_FONT, "Wingdings");    //--- Set font
   ObjectSetInteger(0, bubble_name, OBJPROP_COLOR, sl_color);     //--- Set color
   ObjectSetInteger(0, bubble_name, OBJPROP_FONTSIZE, 12);        //--- Set size
   ObjectSetInteger(0, bubble_name, OBJPROP_ANCHOR, ANCHOR_LEFT); //--- Set anchor
   points_name = prefix + "Points";                               //--- Points name
   ObjectCreate(0, points_name, OBJ_TEXT, 0, points_tm, hit_pr);  //--- Create points
   ObjectSetString(0, points_name, OBJPROP_TEXT, "-" + IntegerToString(pts)); //--- Set text
   ObjectSetInteger(0, points_name, OBJPROP_COLOR, sl_color);     //--- Set color
   ObjectSetInteger(0, points_name, OBJPROP_FONTSIZE, 10);        //--- Set size
   ObjectSetInteger(0, points_name, OBJPROP_ANCHOR, ANCHOR_RIGHT); //--- Set anchor
}

//+------------------------------------------------------------------+
//| Draw TP hit visuals                                              |
//+------------------------------------------------------------------+
void DrawTPHit(int tp_num, datetime hit_tm, double hit_pr, int pts) {
   string prefix = "Signal_TP" + IntegerToString(tp_num) + "_" + TimeToString(current_signal.entry_time) + "_"; //--- Prefix
   color tp_color = clrBlue;                                      //--- TP color
   createTrendline(prefix + "DottedLine", current_signal.entry_time, current_signal.entry_price, hit_tm, hit_pr, clrDarkGray, STYLE_DOT, true, false); //--- Draw dotted
   createTrendline(prefix + "Connect", current_signal.entry_time, hit_pr, hit_tm, hit_pr, clrDarkGray, STYLE_SOLID, true, false); //--- Draw connect
   string tick_name = prefix + "Tick";                            //--- Tick name
   ObjectCreate(0, tick_name, OBJ_TEXT, 0, hit_tm, hit_pr);       //--- Create tick
   ObjectSetString(0, tick_name, OBJPROP_TEXT, CharToString((char)254)); //--- Set text
   ObjectSetString(0, tick_name, OBJPROP_FONT, "Wingdings");      //--- Set font
   ObjectSetInteger(0, tick_name, OBJPROP_COLOR, tp_color);       //--- Set color
   ObjectSetInteger(0, tick_name, OBJPROP_FONTSIZE, 12);          //--- Set size
   ObjectSetInteger(0, tick_name, OBJPROP_ANCHOR, ANCHOR_CENTER); //--- Set anchor
}

//+------------------------------------------------------------------+
//| Draw SL hit visuals                                              |
//+------------------------------------------------------------------+
void DrawSLHit(datetime hit_tm, double hit_pr) {
   string prefix = "Signal_SL_" + TimeToString(current_signal.entry_time) + "_"; //--- Prefix
   color sl_color = clrMagenta;                                   //--- SL color
   createTrendline(prefix + "DottedLine", current_signal.entry_time, current_signal.entry_price, hit_tm, hit_pr, clrDarkGray, STYLE_DOT, true, false); //--- Draw dotted
   createTrendline(prefix + "Connect", current_signal.entry_time, hit_pr, hit_tm, hit_pr, clrDarkGray, STYLE_SOLID, true, false); //--- Draw connect
   string tick_name = prefix + "Tick";                            //--- Tick name
   ObjectCreate(0, tick_name, OBJ_TEXT, 0, hit_tm, hit_pr);       //--- Create tick
   ObjectSetString(0, tick_name, OBJPROP_TEXT, CharToString((char)253)); //--- Set text
   ObjectSetString(0, tick_name, OBJPROP_FONT, "Wingdings");      //--- Set font
   ObjectSetInteger(0, tick_name, OBJPROP_COLOR, sl_color);       //--- Set color
   ObjectSetInteger(0, tick_name, OBJPROP_FONTSIZE, 12);          //--- Set size
   ObjectSetInteger(0, tick_name, OBJPROP_ANCHOR, ANCHOR_CENTER); //--- Set anchor
}

//+------------------------------------------------------------------+
//| Draw early close visuals                                         |
//+------------------------------------------------------------------+
void DrawEarlyClose(datetime hit_tm, double hit_pr, double pts) {
   string prefix = "Signal_Close_" + TimeToString(current_signal.entry_time) + "_"; //--- Prefix
   color close_color = (pts > 0) ? clrBlue : clrMagenta;          //--- Close color
   createTrendline(prefix + "DottedLine", current_signal.entry_time, current_signal.entry_price, hit_tm, hit_pr, clrDarkGray, STYLE_DOT, true, false); //--- Draw dotted
   datetime bubble_tm = current_signal.entry_time;                //--- Bubble time
   char bubble_code = (char)214;                                  //--- Bubble code
   string bubble_name = prefix + "Bubble";                        //--- Bubble name
   ObjectCreate(0, bubble_name, OBJ_TEXT, 0, bubble_tm, hit_pr);  //--- Create bubble
   ObjectSetString(0, bubble_name, OBJPROP_TEXT, CharToString(bubble_code)); //--- Set text
   ObjectSetString(0, bubble_name, OBJPROP_FONT, "Wingdings");    //--- Set font
   ObjectSetInteger(0, bubble_name, OBJPROP_COLOR, close_color);  //--- Set color
   ObjectSetInteger(0, bubble_name, OBJPROP_FONTSIZE, 12);        //--- Set size
   ObjectSetInteger(0, bubble_name, OBJPROP_ANCHOR, ANCHOR_LEFT); //--- Set anchor
   datetime points_tm = bubble_tm;                                //--- Points time
   string sign = (pts > 0) ? "+" : "";                            //--- Sign
   string points_text = sign + DoubleToString(pts, 0);            //--- Points text
   string points_name = prefix + "Points";                        //--- Points name
   ObjectCreate(0, points_name, OBJ_TEXT, 0, points_tm, hit_pr);  //--- Create points
   ObjectSetString(0, points_name, OBJPROP_TEXT, points_text);    //--- Set text
   ObjectSetInteger(0, points_name, OBJPROP_COLOR, close_color);  //--- Set color
   ObjectSetInteger(0, points_name, OBJPROP_FONTSIZE, 10);        //--- Set size
   ObjectSetInteger(0, points_name, OBJPROP_ANCHOR, ANCHOR_RIGHT); //--- Set anchor
   createTrendline(prefix + "Connect", bubble_tm, hit_pr, hit_tm, hit_pr, clrDarkGray, STYLE_SOLID, true, false); //--- Draw connect
}

Hier implementieren wir zunächst die Funktion „DrawInitialLevels“, um die Take-Profit- und Stop-Loss-Levels zum Einstiegszeitpunkt des Signals zu visualisieren, indem wir die Zeiten für den Kreis mit Punkt und die Punktbeschriftungen auf den Einstiegszeitstempel aus „current_signal.entry_time“ setzen. Für jedes Take-Profit-Level (1 bis 3) erzeugen wir ein eindeutiges Präfix mit dem Zeitstring, verwenden blaue Farbe, holen den jeweiligen Preis und die Punkte aus Strukturfeldern wie „current_signal.tp1“ und „tp1_points“, erstellen ein Textobjekt als Kreis mit Wingdings-Code (140 für TP1, 141 für TP2, 142 für TP3) über ObjectCreate, setzen die Schriftart auf Wingdings-Größe 12, linker Anker, und fügen ein Punktelabel mit „+“ und string-konvertierten Punkten in blauer Größe 10, rechter Anker, hinzu. Für Stop-Loss, spiegeln wir mit der Farbe Magenta, Code 164 für Kreis mit Punkt , „-“ für den Text. Was den Code betrifft, so stellt MQL5 die Wingdings-Codes zur Verfügung, mit denen Sie interagieren können, um das Gewünschte zu erreichen. Siehe unten.

MQL5 WINGDINGS

Als Nächstes rendert die Funktion „DrawTPHit“ visuelle Darstellungen für Take-Profit-Treffer, wobei sie die Level-Nummer, den Trefferzeitpunkt, den Preis und die Punkte übernimmt; sie erstellt eine gepunktete dunkelgraue Trendlinie vom Einstieg bis zum Treffer mit „createTrendline“ im Hintergrund ohne Strahl, eine durchgehende horizontale Verbindung beim Trefferpreis und einen zentrierten Wingdings-Tick (Code 254) in blauer Größe 12. Jetzt wissen Sie also, wie bei den Ticks passiert. In ähnlicher Weise zeichnet „DrawSLHit“ für Stop-Loss-Treffer eine gepunktete Linie zum Treffer, eine horizontale Verbindung und ein zentriertes Wingdings-Symbol (Code 253) in Magenta, Größe 12.

Für „DrawEarlyClose“, das sich mit vorzeitigen Abschlüssen befasst, zeichnen wir eine gepunktete Linie zum Schlusskurs, bestimmen die Farbe Blau bei positiven Punkten oder Magenta bei negativen Punkten, platzieren einen Kreis (Code 214) zum Einstiegszeitpunkt auf dem Schlusskurs in dieser Farbe, Größe 12, linker Anker, fügen einen Text mit Vorzeichen („+“ oder leer) in derselben Farbe, Größe 10, rechter Anker und eine horizontale Verbindung hinzu. Wir können diese Funktionen in der Logik zum Öffnen und Schließen der Positionen, vor allem der virtuellen Positionen, verwenden. Hier ist die Logik, mit der wir das erreicht haben.

//+------------------------------------------------------------------+
//| Get close price from history for auto-closed position            |
//+------------------------------------------------------------------+
double GetPositionClosePrice(long ticket) {
   HistorySelectByPosition(ticket);                               //--- Select history
   int deals = HistoryDealsTotal();                               //--- Get deals count
   if (deals > 0) {                                               //--- Check deals
      ulong deal_ticket = HistoryDealGetTicket(deals - 1);        //--- Get last deal
      return HistoryDealGetDouble(deal_ticket, DEAL_PRICE);       //--- Return price
   }
   return 0.0;                                                    //--- Return fallback
}

//+------------------------------------------------------------------+
//| Open virtual position                                            |
//+------------------------------------------------------------------+
void OpenVirtualPosition(int type, datetime ent_time, double ent_price) {
   current_signal.active = true;                                  //--- Set active
   current_signal.pos_type = type;                                //--- Set type
   current_signal.entry_time = ent_time;                          //--- Set time
   current_signal.entry_price = ent_price;                        //--- Set price
   current_signal.tp1 = ent_price + (tp1_points * _Point) * type; //--- Set TP1
   current_signal.tp2 = ent_price + (tp2_points * _Point) * type; //--- Set TP2
   current_signal.tp3 = ent_price + (tp3_points * _Point) * type; //--- Set TP3
   current_signal.sl = ent_price - (sl_points * _Point) * type;   //--- Set SL
   current_signal.hit_tp1 = false;                                //--- Reset TP1 hit
   current_signal.hit_tp2 = false;                                //--- Reset TP2 hit
   current_signal.hit_tp3 = false;                                //--- Reset TP3 hit
   current_signal.hit_sl = false;                                 //--- Reset SL hit
   current_signal.close_time = 0;                                 //--- Reset close time
   position_ticket = -1;                                          //--- Reset ticket
   DrawInitialLevels();                                           //--- Draw levels
}

//+------------------------------------------------------------------+
//| Close virtual position                                           |
//+------------------------------------------------------------------+
void CloseVirtualPosition(double close_price, bool is_early) {
   if (!current_signal.active) return;                            //--- Return if not active
   double profit_pts = (close_price - current_signal.entry_price) / _Point * current_signal.pos_type; //--- Calc profit
   current_signal.close_time = TimeCurrent();                     //--- Set close time
   if (is_early) {                                                //--- Check early
      DrawEarlyClose(TimeCurrent(), close_price, profit_pts);     //--- Draw early close
      if (trade_mode == Open_Trades && position_ticket != -1) {   //--- Check open trades
         MqlTradeRequest close_request = {};                      //--- Close request
         MqlTradeResult close_result = {};                        //--- Close result
         close_request.action = TRADE_ACTION_DEAL;                //--- Set action
         close_request.symbol = _Symbol;                          //--- Set symbol
         close_request.volume = 0.1;                              //--- Set volume
         close_request.type = (current_signal.pos_type == 1) ? ORDER_TYPE_SELL : ORDER_TYPE_BUY; //--- Set type
         close_request.price = close_price;                       //--- Set price
         close_request.deviation = 3;                             //--- Set deviation
         close_request.position = position_ticket;                //--- Set position
         if (!OrderSend(close_request, close_result)) {           //--- Send close
            Print("Failed to close trade: ", GetLastError());     //--- Log error
         }
         position_ticket = -1;                                    //--- Reset ticket
      }
   }
   if (trade_mode == Open_Trades) {                               //--- Check open trades
      bool hit_selected_tp = false;                               //--- Init selected TP
      switch(tp_level) {                                          //--- Select TP
         case Level_1: hit_selected_tp = current_signal.hit_tp1; break; //--- TP1
         case Level_2: hit_selected_tp = current_signal.hit_tp2; break; //--- TP2
         case Level_3: hit_selected_tp = current_signal.hit_tp3; break; //--- TP3
      }
      bool count_it = current_signal.hit_sl || hit_selected_tp || !is_early; //--- Check count
      if (count_it) {                                              //--- Count
         total_profit_points += profit_pts;                        //--- Add profit
         total_signals++;                                          //--- Increment signals
         if (profit_pts > 0) wins++;                               //--- Increment wins
         else losses++;                                            //--- Increment losses
      }
   } else {                                                        //--- Visual only
      bool hit_selected = false;                                   //--- Init selected hit
      int selected_points = 0;                                     //--- Init points
      switch(tp_level) {                                           //--- Select TP
         case Level_1: hit_selected = current_signal.hit_tp1; selected_points = tp1_points; break; //--- TP1
         case Level_2: hit_selected = current_signal.hit_tp2; selected_points = tp2_points; break; //--- TP2
         case Level_3: hit_selected = current_signal.hit_tp3; selected_points = tp3_points; break; //--- TP3
      }
      double effective_profit = 0.0;                               //--- Init effective profit
      if (hit_selected) {                                          //--- Check hit selected
         effective_profit = (double)selected_points;               //--- Set to selected points
      } else if (current_signal.hit_sl) {                          //--- Check SL hit
         effective_profit = - (double)sl_points;                   //--- Set to -SL
      } else {                                                     //--- Else
         effective_profit = profit_pts;                            //--- Set to profit pts
      }
      total_profit_points += effective_profit;                     //--- Add effective profit
      total_signals++;                                             //--- Increment signals
      if (hit_selected || effective_profit > 0) wins++;            //--- Increment wins
      else losses++;                                               //--- Increment losses
   }
   current_signal.active = false;                                  //--- Deactivate
}

Hier definieren wir die Funktion „GetPositionClosePrice“, um den Schlusskurs einer Position aus der Geschäftshistorie zu holen, wenn diese automatisch geschlossen wird. Wir verwenden HistorySelectByPosition für das Ticket und überprüfen die Anzahl der Angebote mit der Funktion HistoryDealsTotal. Wenn Angebote verfügbar sind, erhalten wir das Ticket des letzten Angebots über „HistoryDealGetTicket“ und seinen Preis aus HistoryDealGetDouble mit DEAL_PRICE. Wenn es keine Handelsgeschäfte gibt, ist der Standardwert 0,0.

Als Nächstes richtet die Funktion „OpenVirtualPosition“ einen simulierten Handel ein, indem sie das Signal aktiviert, den Typ (1 für Kauf, -1 für Verkauf), die Einstiegszeit und den Preis zuweist, die Take-Profit-Levels als Einstieg berechnet, die um ihre Punktwerte _Point und die Richtung des Typs angepasst werden, den Stop-Loss ebenfalls berechnet, aber subtrahiert, die Hit-Flags und die Schlusszeit löscht, das Ticket zurücksetzt und „DrawInitialLevels“ für die Visualisierung aufruft. Die Funktion „CloseVirtualPosition“ schließt eine Simulation mit einem gegebenen Schlusskurs und einem Flag für vorzeitige Schließung ab, wobei sie bei Inaktivität beendet wird, Gewinnpunkte als vorzeichenbehaftete Differenz zu „_Point“ berechnet und die Schlusszeit auf den aktuellen Wert aktualisiert. Wenn es zu früh ist, zieht es Indikatoren mit „DrawEarlyClose“ und erstellt im Live-Modus mit einem gültigen Ticket eine entgegengesetzte Marktschlussanforderung zum Preis mit Abweichung, sendet mit OrderSend, protokolliert Fehlschläge und löscht das Ticket.

Im Livemodus wertet es aus, ob der gewählte Take-Profit über den Schalter „tp_level“ erreicht wurde, und wenn der Stop-Loss erreicht wurde, der gewählte Take-Profit, oder nicht frühzeitig, akkumuliert es den Gewinn, zählt das Signal und addiert es zu den Gewinnen, wenn es positiv ist, oder zu den Verlusten. Im visuellen Modus prüft es den ausgewählten Take-Profit-Treffer und die Punkte durch den Schalter, leitet den effektiven Gewinn als Punkte ab, wenn er getroffen wurde, als negativen Sl, wenn der Stop-Loss gesetzt wurde, oder als tatsächlichen Gewinn, wenn keines von beiden zutrifft, und addiert ihn zur Gesamtsumme, erhöht die Signale und berechnet den Gewinn, wenn er getroffen wurde oder positiv ist. Anschließend deaktivieren wir das Signal, indem wir „active“ auf false setzen. Wir sind nun vollständig mit den benötigten Hilfsfunktionen ausgestattet und können nun unsere Logik in OnTick zur Ausführung organisieren.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   MqlTick tick;                                                  //--- Tick structure
   if (!SymbolInfoTick(_Symbol, tick)) return;                    //--- Get tick or return
   double bid = tick.bid;                                         //--- Get bid
   double ask = tick.ask;                                         //--- Get ask
   MqlRates rates[2];                                             //--- Rates array
   if (CopyRates(_Symbol, PERIOD_CURRENT, 0, 2, rates) < 2) return; //--- Copy rates or return
   bool new_bar = (rates[0].time > last_bar_time);                //--- Check new bar
   if (new_bar) last_bar_time = rates[0].time;                    //--- Update last time
   double fast_buf[], slow_buf[], filter_buf[];                   //--- Buffers
   ArraySetAsSeries(fast_buf, true);
   ArraySetAsSeries(slow_buf, true);
   ArraySetAsSeries(filter_buf, true);
   if (CopyBuffer(h_fast_ma, 0, 0, 3, fast_buf) < 3) return;      //--- Copy fast or return
   if (CopyBuffer(h_slow_ma, 0, 0, 3, slow_buf) < 3) return;      //--- Copy slow or return
   if (CopyBuffer(h_filter_ma, 0, 0, 2, filter_buf) < 2) return;  //--- Copy filter or return
   double fast_1 = fast_buf[1];                                   //--- Fast MA 1
   double fast_2 = fast_buf[2];                                   //--- Fast MA 2
   double slow_1 = slow_buf[1];                                   //--- Slow MA 1
   double slow_2 = slow_buf[2];                                   //--- Slow MA 2
   double filter_1 = filter_buf[1];                               //--- Filter MA 1
   double close_1 = rates[1].close;                               //--- Close 1
   int signal_type = 0;                                           //--- Init signal type
   if (new_bar) {                                                 //--- Check new bar
      if (fast_2 <= slow_2 && fast_1 > slow_1 && close_1 > filter_1) signal_type = 1; //--- Buy signal
      else if (fast_2 >= slow_2 && fast_1 < slow_1 && close_1 < filter_1) signal_type = -1; //--- Sell signal
   }
   if (signal_type != 0) {                                        //--- Check signal
      if (current_signal.active && current_signal.pos_type != signal_type) { //--- Check active opposite
         double close_price = (current_signal.pos_type == 1) ? bid : ask; //--- Get close price
         CloseVirtualPosition(close_price, true);                  //--- Close early
      }
      if (!current_signal.active) {                                //--- Check not active
         double entry_price = (signal_type == 1) ? ask : bid;      //--- Get entry price
         OpenVirtualPosition(signal_type, rates[1].time, entry_price); //--- Open virtual
         string name = "Signal_Entry_" + TimeToString(rates[1].time); //--- Entry name
         ObjectCreate(0, name, OBJ_ARROW, 0, rates[1].time, signal_type == 1 ? rates[1].low : rates[1].high); //--- Create arrow
         ObjectSetInteger(0, name, OBJPROP_ARROWCODE, (signal_type == 1 ? 236 : 238)); //--- Set code
         ObjectSetString(0, name, OBJPROP_FONT, "Wingdings");      //--- Set font
         ObjectSetInteger(0, name, OBJPROP_COLOR, signal_type == 1 ? clrGreen : clrRed); //--- Set color
         ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 12);          //--- Set size
         ObjectSetInteger(0, name, OBJPROP_ANCHOR, signal_type == 1 ? ANCHOR_LEFT_LOWER : ANCHOR_LEFT_UPPER); //--- Set anchor
         if (trade_mode == Open_Trades) {                          //--- Check open trades
            MqlTradeRequest request = {};                          //--- Request
            MqlTradeResult result = {};                            //--- Result
            request.action = TRADE_ACTION_DEAL;                    //--- Set action
            request.symbol = _Symbol;                              //--- Set symbol
            request.volume = 0.1;                                  //--- Set volume
            request.type = signal_type == 1 ? ORDER_TYPE_BUY : ORDER_TYPE_SELL; //--- Set type
            request.price = signal_type == 1 ? ask : bid;          //--- Set price
            request.deviation = 3;                                 //--- Set deviation
            double selected_tp = 0;                                //--- Init TP
            switch(tp_level) {                                     //--- Select TP
               case Level_1: selected_tp = current_signal.tp1; break; //--- TP1
               case Level_2: selected_tp = current_signal.tp2; break; //--- TP2
               case Level_3: selected_tp = current_signal.tp3; break; //--- TP3
            }
            request.tp = selected_tp;                              //--- Set TP
            request.sl = current_signal.sl;                        //--- Set SL
            if(!OrderSend(request, result)) {                      //--- Send order
               Print("Failed to open trade: ", GetLastError());    //--- Log error
            } else {                                               //--- Success
               position_ticket = result.deal;                      //--- Set ticket
            }
         }
      }
   }
}

In OnTick holen wir den aktuellen Tick mit SymbolInfoTick in eine MqlTick-Struktur, extrahieren „bid“ und „ask“ und kehren bei Fehlschlag vorzeitig zurück. Wir kopieren die letzten beiden Kurse über CopyRates in ein MqlRates-Array und prüfen, ob ein neuer Balken vorliegt, indem wir die letzte Zeit mit „last_bar_time“ vergleichen und sie gegebenenfalls aktualisieren. Wir deklarieren Puffer für gleitende Durchschnitte: „fast_buf[]“, „slow_buf[]“, „filter_buf[]“, setzen sie als Zeitreihen, kopieren von Handles mit CopyBuffer auf Hauptpuffer 0 und kehren bei unzureichenden Daten zurück. Wir weisen Werte wie „fast_1“ aus buffer[1], „fast_2“ aus [2], ähnlich für slow und filter an [1], close aus rates[1].close zu. Wenn ein neuer Balken erscheint, erkennen wir folgende Signale: Kauf (1), wenn der vorherige schnelle Indikator unterhalb oder auf Höhe des langsamen Indikators lag, der aktuelle jedoch darüber liegt und über dem Filter schließt; Verkauf (-1), wenn der vorherige schnelle Indikator oberhalb oder auf Höhe des langsamen Indikators lag, der aktuelle jedoch darunter liegt und unter dem Filter schließt.

Bei einem Signal, wenn aktiv und entgegengesetzt zum aktuellen Typ, berechnen wir den Schlusskurs als Geldkurs für Käufe oder Briefkurs für Verkäufe und rufen von „CloseVirtualPosition“ auf mit early true. Wenn nicht aktiv, rufen wir Ask für Käufe oder Bid für Verkäufe ab, „OpenVirtualPosition“ mit type, rates[1].time und eröffnen. Wir erstellen ein Pfeil-Objekt für die Eröffnung mit dem Namen „Signal_Entry_“ plus Zeitstring als OBJ_ARROW, positioniert bei rates[1].time und Tief für Käufe oder Hoch für Verkäufe, setzen den Pfeilcode 236 für Kauf oder 238 für Verkauf in der Schriftart Wingdings, grün/rot, mit Größe 12, Anker unten für Käufe oder oben für Verkäufe. Im Live-Modus („trade_mode“ als „Open_Trades“) erstellen wir einen MqlTradeRequest für ein Marktgeschäft auf ein Symbol mit Volumen 0.1, Typ buy/sell, Preis ask/bid, Abweichung 3; wählen Take Profit über Schalter auf „tp_level“ aus Signal's tp1/tp2/tp3, setzen Stop Loss aus signal.sl, senden mit OrderSend, protokollieren Fehler, oder speichern Deal Ticket in „position_ticket“ bei Erfolg. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

ANFANGSSIGNALE

Wir können sehen, dass wir bei der Erkennung eines Signals die Positionen schließen und die neuen Signalschemata öffnen. Wir müssen jetzt nur noch die Signale aktualisieren, wenn die Levels erreicht werden. Wir können auch sehen, dass das Dashboard nicht aktualisiert wird, aber das ist das Letzte, was wir tun wollen, nachdem wir alle benötigten Statistiken erhalten haben. Das sollten wir jetzt tun.

if (current_signal.active) {                                   //--- Check active
   // Detect if real position closed automatically (TP/SL)
   if (trade_mode == Open_Trades && position_ticket != -1 && !PositionSelectByTicket(position_ticket)) { //--- Check closed
      double close_price = GetPositionClosePrice(position_ticket); //--- Get close price
      bool hit_sl = MathAbs(close_price - current_signal.sl) < _Point * 5; //--- Check SL hit
      current_signal.hit_sl = hit_sl;                          //--- Set SL hit
      if (hit_sl) DrawSLHit(TimeCurrent(), current_signal.sl); //--- Draw SL
      CloseVirtualPosition(close_price, false);                //--- Close virtual
   } else {                                                    //--- Not auto closed
      // Check for visual hits (TP levels)
      if (!current_signal.hit_tp1) {                           //--- Check TP1
         bool tp1_hit = false;                                 //--- Init hit
         if (current_signal.pos_type == 1 && bid >= current_signal.tp1) tp1_hit = true; //--- Buy hit
         if (current_signal.pos_type == -1 && ask <= current_signal.tp1) tp1_hit = true; //--- Sell hit
         if (tp1_hit) {                                        //--- Hit
            current_signal.hit_tp1 = true;                     //--- Set hit
            DrawTPHit(1, TimeCurrent(), current_signal.tp1, tp1_points); //--- Draw hit
         }
      }
      if (!current_signal.hit_tp2) {                           //--- Check TP2
         bool tp2_hit = false;                                 //--- Init hit
         if (current_signal.pos_type == 1 && bid >= current_signal.tp2) tp2_hit = true; //--- Buy hit
         if (current_signal.pos_type == -1 && ask <= current_signal.tp2) tp2_hit = true; //--- Sell hit
         if (tp2_hit) {                                        //--- Hit
            current_signal.hit_tp2 = true;                     //--- Set hit
            DrawTPHit(2, TimeCurrent(), current_signal.tp2, tp2_points); //--- Draw hit
         }
      }
      if (!current_signal.hit_tp3) {                           //--- Check TP3
         bool tp3_hit = false;                                 //--- Init hit
         if (current_signal.pos_type == 1 && bid >= current_signal.tp3) tp3_hit = true; //--- Buy hit
         if (current_signal.pos_type == -1 && ask <= current_signal.tp3) tp3_hit = true; //--- Sell hit
         if (tp3_hit) {                                        //--- Hit
            current_signal.hit_tp3 = true;                     //--- Set hit
            DrawTPHit(3, TimeCurrent(), current_signal.tp3, tp3_points); //--- Draw hit
            if (trade_mode == Visual_Only) {                   //--- Check visual only
               double close_price = (current_signal.pos_type == 1) ? bid : ask; //--- Get close
               CloseVirtualPosition(close_price, false);       //--- Close virtual
            }
         }
      }
      // SL hit check for Visual_Only or manual if needed
      bool sl_hit = false;                                     //--- Init SL hit
      if (current_signal.pos_type == 1 && bid <= current_signal.sl) sl_hit = true; //--- Buy SL
      if (current_signal.pos_type == -1 && ask >= current_signal.sl) sl_hit = true; //--- Sell SL
      if (sl_hit && !current_signal.hit_sl) {                  //--- Check hit and not set
         bool already_won = false;                             //--- Init won flag
         switch(tp_level) {                                    //--- Check level
            case Level_1: already_won = current_signal.hit_tp1; break; //--- TP1 won
            case Level_2: already_won = current_signal.hit_tp2; break; //--- TP2 won
            case Level_3: already_won = current_signal.hit_tp3; break; //--- TP3 won
         }
         if (!already_won) {                                   //--- Check not won
            current_signal.hit_sl = true;                      //--- Set SL hit
            DrawSLHit(TimeCurrent(), current_signal.sl);       //--- Draw SL
         }
         if (trade_mode == Visual_Only) {                      //--- Check visual
            double close_price = (current_signal.pos_type == 1) ? bid : ask; //--- Get close
            CloseVirtualPosition(close_price, false);          //--- Close virtual
         }
      }
   }
}

Wenn ein Signal in „current_signal“ aktiv ist, erkennen wir zunächst, ob eine echte Position im Live-Modus („trade_mode“ als „Open_Trades“) durch Take Profit oder Stop Loss automatisch geschlossen wurde, indem wir überprüfen, ob das Ticket gültig ist, aber PositionSelectByTicket fehlschlägt; Wenn ja, wird der Schlusskurs mit „GetPositionClosePrice“ ermittelt und geprüft, ob er mit dem Stop Loss innerhalb einer Toleranz von 5 Punkten übereinstimmt. Dann wird das Flag „hit_sl“ gesetzt und mit „DrawSLHit“ zum aktuellen Zeitpunkt und zum Stop-Loss-Kurs gezeichnet, falls dies zutrifft, und dann wird „CloseVirtualPosition“ nicht vorzeitig aufgerufen. Andernfalls prüfen wir bei laufenden Positionen jede Gewinnmitnahme, wenn sie nicht getroffen wird: Für TP1 wird das Flag true gesetzt, wenn der Geldkurs bei Käufen den Wert von TP1 erreicht oder überschreitet oder das Briefkursniveau bei Verkäufen auf oder unter den Wert von TP1 fällt. Dann wird „hit_tp1“ markiert und „DrawTPHit“ mit Level 1, aktueller Zeit, TP1-Preis und „tp1_points“ aufgerufen. Wir wiederholen dies analog für TP2 und TP3; für TP3 im visuellen Modus („trade_mode“ als „Visual_Only“) schließen die Simulation ebenfalls mit dem Schlusskurs als Bid/Ask non-early.

Wir überwachen Stop-Loss-Treffer separat in visuellen oder manuellen Fällen: Flag true, wenn Bid bei oder unter für Käufe oder Ask bei oder über für Verkäufe, und wenn nicht bereits gesetzt, über Schalter auf „tp_level“ prüfen, ob der gewählte Take-Profit erreicht wurde (Setzen von „already_won“ true), nur fortfahren, wenn nicht, um Verluste nach Gewinnen zu vermeiden – dann „hit_sl“ setzen, mit „DrawSLHit“ zum aktuellen Zeitpunkt und Stop-Loss-Preis zeichnen. Im visuellen Modus schließen wir die Simulation mit Geld/Brief nicht vorzeitig. Wir kommen zu folgendem Ergebnis.

MARKIERTE EINSTIEGS- UND TREFFERQUOTEN

Jetzt können wir die Trefferquoten markieren und die Visualisierung des Charts vervollständigen. Da wir nun alle benötigten Statistiken haben, können wir das Dashboard aktualisieren. Wir werden eine Funktion definieren, die die Logik dafür enthält.

//+------------------------------------------------------------------+
//| Update dashboard                                                 |
//+------------------------------------------------------------------+
void UpdateDashboard() {
   string space = " ";                                            //--- Space string
   bool display_signal = current_signal.active || current_signal.hit_tp1 || current_signal.hit_tp2 || current_signal.hit_tp3 || current_signal.hit_sl; //--- Check display
   if (display_signal) {                                          //--- Display signal
      string arrow = (current_signal.pos_type == 1) ? CharToString((char)233) : CharToString((char)234); //--- Arrow char
      color dir_color = (current_signal.pos_type == 1) ? clrLime : clrRed; //--- Direction color
      ObjectSetString(0, dash_prefix + "DirectionValue", OBJPROP_TEXT, arrow); //--- Set direction text
      ObjectSetInteger(0, dash_prefix + "DirectionValue", OBJPROP_COLOR, dir_color); //--- Set color
      color level_color = (current_signal.pos_type == 1) ? clrLime : clrRed; //--- Level color
      string direction = (current_signal.pos_type == 1) ? "BUY " : "SELL "; //--- Direction string
      ObjectSetString(0, dash_prefix + "EntryPrice", OBJPROP_TEXT, direction + DoubleToString(current_signal.entry_price, _Digits)); //--- Set entry text
      ObjectSetInteger(0, dash_prefix + "EntryPrice", OBJPROP_COLOR, level_color); //--- Set color
      ObjectSetString(0, dash_prefix + "TP1Value", OBJPROP_TEXT, DoubleToString(current_signal.tp1, _Digits)); //--- Set TP1 text
      ObjectSetInteger(0, dash_prefix + "TP1Value", OBJPROP_COLOR, clrWhite); //--- Set color
      ObjectSetString(0, dash_prefix + "TP2Value", OBJPROP_TEXT, DoubleToString(current_signal.tp2, _Digits)); //--- Set TP2 text
      ObjectSetInteger(0, dash_prefix + "TP2Value", OBJPROP_COLOR, clrWhite); //--- Set color
      ObjectSetString(0, dash_prefix + "TP3Value", OBJPROP_TEXT, DoubleToString(current_signal.tp3, _Digits)); //--- Set TP3 text
      ObjectSetInteger(0, dash_prefix + "TP3Value", OBJPROP_COLOR, clrWhite); //--- Set color
      ObjectSetString(0, dash_prefix + "SLValue", OBJPROP_TEXT, DoubleToString(current_signal.sl, _Digits)); //--- Set SL text
      ObjectSetInteger(0, dash_prefix + "SLValue", OBJPROP_COLOR, clrWhite); //--- Set color
      int tp1_icon = current_signal.hit_tp1 ? 252 : 183;             //--- TP1 icon
      color tp1_icon_color = current_signal.hit_tp1 ? clrLime : clrWhite; //--- TP1 color
      ObjectSetString(0, dash_prefix + "TP1Icon", OBJPROP_TEXT, CharToString((char)tp1_icon)); //--- Set TP1 icon
      ObjectSetInteger(0, dash_prefix + "TP1Icon", OBJPROP_COLOR, tp1_icon_color); //--- Set color
      int tp2_icon = current_signal.hit_tp2 ? 252 : 183;             //--- TP2 icon
      color tp2_icon_color = current_signal.hit_tp2 ? clrLime : clrWhite; //--- TP2 color
      ObjectSetString(0, dash_prefix + "TP2Icon", OBJPROP_TEXT, CharToString((char)tp2_icon)); //--- Set TP2 icon
      ObjectSetInteger(0, dash_prefix + "TP2Icon", OBJPROP_COLOR, tp2_icon_color); //--- Set color
      int tp3_icon = current_signal.hit_tp3 ? 252 : 183;             //--- TP3 icon
      color tp3_icon_color = current_signal.hit_tp3 ? clrLime : clrWhite; //--- TP3 color
      ObjectSetString(0, dash_prefix + "TP3Icon", OBJPROP_TEXT, CharToString((char)tp3_icon)); //--- Set TP3 icon
      ObjectSetInteger(0, dash_prefix + "TP3Icon", OBJPROP_COLOR, tp3_icon_color); //--- Set color
      int sl_icon = current_signal.hit_sl ? 251 : 183;               //--- SL icon
      color sl_icon_color = current_signal.hit_sl ? clrRed : clrWhite; //--- SL color
      ObjectSetString(0, dash_prefix + "SLIcon", OBJPROP_TEXT, CharToString((char)sl_icon)); //--- Set SL icon
      ObjectSetInteger(0, dash_prefix + "SLIcon", OBJPROP_COLOR, sl_icon_color); //--- Set color
      int entry_shift = iBarShift(_Symbol, PERIOD_CURRENT, current_signal.entry_time, false); //--- Entry shift
      int calc_shift = 0;                                    //--- Init calc shift
      if (!current_signal.active && current_signal.close_time != 0) { //--- Check closed
         calc_shift = iBarShift(_Symbol, PERIOD_CURRENT, current_signal.close_time, false); //--- Close shift
      }
      int age = entry_shift - calc_shift;                    //--- Calc age
      ObjectSetString(0, dash_prefix + "AgeValue", OBJPROP_TEXT, IntegerToString(age) + " bars"); //--- Set age text
   } else {                                                       //--- No signal
      ObjectSetString(0, dash_prefix + "DirectionValue", OBJPROP_TEXT, space); //--- Clear direction
      ObjectSetString(0, dash_prefix + "EntryPrice", OBJPROP_TEXT, space); //--- Clear entry
      ObjectSetString(0, dash_prefix + "TP1Value", OBJPROP_TEXT, space);   //--- Clear TP1
      ObjectSetString(0, dash_prefix + "TP2Value", OBJPROP_TEXT, space);   //--- Clear TP2
      ObjectSetString(0, dash_prefix + "TP3Value", OBJPROP_TEXT, space);   //--- Clear TP3
      ObjectSetString(0, dash_prefix + "SLValue", OBJPROP_TEXT, space);    //--- Clear SL
      ObjectSetString(0, dash_prefix + "AgeValue", OBJPROP_TEXT, space);   //--- Clear age
      ObjectSetString(0, dash_prefix + "TP1Icon", OBJPROP_TEXT, space);    //--- Clear TP1 icon
      ObjectSetString(0, dash_prefix + "TP2Icon", OBJPROP_TEXT, space);    //--- Clear TP2 icon
      ObjectSetString(0, dash_prefix + "TP3Icon", OBJPROP_TEXT, space);    //--- Clear TP3 icon
      ObjectSetString(0, dash_prefix + "SLIcon", OBJPROP_TEXT, space);     //--- Clear SL icon
   }
   ObjectSetString(0, dash_prefix + "TotalValue", OBJPROP_TEXT, (string)total_signals); //--- Set total
   ObjectSetString(0, dash_prefix + "WinLossValue", OBJPROP_TEXT, (string)wins + " / " + (string)losses); //--- Set win/loss
   string profit_str = (total_profit_points > 0 ? "+" : "") + DoubleToString(total_profit_points, 0); //--- Profit string
   color profit_color = total_profit_points > 0 ? clrLime : (total_profit_points < 0 ? clrRed : clrWhite); //--- Profit color
   ObjectSetString(0, dash_prefix + "ProfitValue", OBJPROP_TEXT, profit_str); //--- Set profit text
   ObjectSetInteger(0, dash_prefix + "ProfitValue", OBJPROP_COLOR, profit_color); //--- Set color
   double success = (total_signals > 0) ? (double)wins / total_signals * 100.0 : 0.0; //--- Calc success
   ObjectSetString(0, dash_prefix + "SuccessValue", OBJPROP_TEXT, DoubleToString(success, 2) + "%"); //--- Set success
   ChartRedraw(0);                                                //--- Redraw chart
}

Wir fahren fort mit der Definition der Funktion „UpdateDashboard“, um das Panel mit den aktuellen Signal- und Statistikdaten zu aktualisieren, beginnend mit einer Leerzeichenfolge zum Löschen von Beschriftungen. Wir bestimmen, ob ein Signal angezeigt werden soll, wenn „current_signal“ aktiv ist oder wenn die Treffer-Flags für Take-Profit-/Stop-Loss gesetzt ist. Wenn ja, setzen wir das Richtungssymbol als Wingdings Zeichen 233 für Käufe oder 234 für Verkäufe, die Farbe Limone für Käufe oder Rot für Verkäufe und aktualisieren den Text und die Farbe des Labels mit den Funktionen ObjectSetString und ObjectSetInteger. Wir formatieren die Einstiegsbezeichnung als „KAUFEN“ oder „VERKAUFEN“ plus Einstiegskurs, normalisiert auf _Digits via DoubleToString, in Grün/Rot, und setzen Take Profit 1-3 und Stop Loss-Werte ebenfalls in Weiß.

Bei den Symbolen wird für Take Profit 1 Wingdings 252 (check) verwendet, wenn er in Limone getroffen wird, sonst 183 (dot) in Weiß, wiederholt für 2 und 3; Stop Loss 251 (x), wenn er in Rot getroffen wird, sonst 183 in Weiß. Wir berechnen das Alter des Signals in Balken, indem wir iBarShift von der Einstiegszeit minus der Schlusszeitverschiebung (falls geschlossen) verwenden und die Altersangabe als String plus „Balken“ festlegen. Wenn kein Signal vorhanden ist, löschen wir alle signalbezogenen Beschriftungen in dem Bereich. Unabhängig davon aktualisieren wir die Bezeichnung der Gesamtsignale mit der Zeichenfolge „total_signals“, Gewinn/Verlust als Gewinne/Schrägstrich-Verluste, Gewinn als vorzeichenbehaftete Zeichenfolge, gerundet auf 0 Dezimalstellen mit der Farbe Grün, wenn positiv, bzw.Rot, wenn negativ, und Weiß, wenn null, Erfolg als Prozentsatz auf 2 Dezimalstellen, wenn Signale vorhanden sind, sonst 0,0. Wir zeichnen das Chart mit der Funktion ChartRedraw neu, um die Änderungen anzuwenden. Wir rufen diese Funktion im Tick-Handler auf, um die Aktualisierungen anzuwenden. Wir müssen nun die Objekte löschen, die wir beim Beenden des Systems erstellt haben.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   ObjectsDeleteAll(0, dash_prefix);                              //--- Delete dashboard objects
   ObjectsDeleteAll(0, "Signal_");                                //--- Delete signal objects
   ObjectsDeleteAll(0, "Initial_");                               //--- Delete initial objects
   IndicatorRelease(h_fast_ma);                                   //--- Release fast MA
   IndicatorRelease(h_slow_ma);                                   //--- Release slow MA
   IndicatorRelease(h_filter_ma);                                 //--- Release filter MA
   if (trade_mode == Open_Trades && position_ticket != -1) {      //--- Check open trades mode
      MqlTick tick;                                               //--- Tick structure
      if (SymbolInfoTick(_Symbol, tick)) {                        //--- Get tick
         double close_price = (current_signal.pos_type == 1) ? tick.bid : tick.ask; //--- Get close price
         MqlTradeRequest close_request = {};                      //--- Close request
         MqlTradeResult close_result = {};                        //--- Close result
         close_request.action = TRADE_ACTION_DEAL;                //--- Set action
         close_request.symbol = _Symbol;                          //--- Set symbol
         close_request.volume = 0.1;                              //--- Set volume
         close_request.type = (current_signal.pos_type == 1) ? ORDER_TYPE_SELL : ORDER_TYPE_BUY; //--- Set type
         close_request.price = close_price;                       //--- Set price
         close_request.deviation = 3;                             //--- Set deviation
         close_request.position = position_ticket;                //--- Set position
         if (!OrderSend(close_request, close_result)) {           //--- Send close
            Print("Failed to close trade on deinit: ", GetLastError()); //--- Log error
         }
         position_ticket = -1;                                    //--- Reset ticket
      }
   }
}

In der Ereignisbehandlung von OnDeinit, das die Aufräumarbeiten übernimmt, wenn der Expert Advisor mit einem Code für den Grund entfernt wird, werden zunächst alle Dashboard-Objekte mithilfe von ObjectsDeleteAll mit dem Präfix „dash_“ entfernt, dann die signalbezogenen Visualisierungen mit dem Präfix „Signal_“ und die Anfangspegel mit „Initial_“ in allen Unterfenstern. Wir geben die Ressourcen des gleitenden Durchschnittsindikators über IndicatorRelease für „h_fast_ma“, „h_slow_ma“ und „h_filter_ma“ frei, um Speicherplatz zu schaffen.

Wenn wir uns im Live-Handelsmodus („trade_mode“ als „Open_Trades“) mit einem gültigen „position_ticket“ befinden, holen wir den aktuellen Tick mithilfe von SymbolInfoTick und berechnen bei Erfolg den Schlusskurs als Bid für Käufe oder Ask für Verkäufe auf der Grundlage von „current_signal.pos_type“. Wir konstruieren eine entgegengesetzte Marktanforderung zum Schließen mit der Aktion TRADE_ACTION_DEAL, dem Symbol, dem Volumen 0,1, dem Typ „sell“ für Käufe oder „buy“ für Verkäufe, dem Preis, der Abweichung 3 und dem Positionsticket, senden es mit OrderSend, protokollieren jeden Fehler von GetLastError und setzen das Ticket auf -1 zurück. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

ENDGÜLTIGES ERGEBNIS

Aus dem Bild geht hervor, dass wir das System des Strategietesters korrekt eingerichtet und alle Ziele erreicht haben. Nun bleibt nur noch, die Funktionsfähigkeit des Systems zu testen, was im vorangegangenen Abschnitt behandelt wurde.


Testen des Strategie-Trackers

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

STRATEGIE TRACKER BACKTEST GIF


Schlussfolgerung

Zusammenfassend haben wir ein Strategie-Tracker-System in MQL5 entwickelt, das gleitende Durchschnitts-Crossover-Signale mit einem langfristigen Filter erkennt, der mit einem günstigen Filter ausgetauscht werden kann, die Ergebnisse durch virtuelle oder Live-Positionen mit mehreren Take-Profit-Levels und Stop-Loss verfolgt, Einträge, Treffer und Schließungen auf dem Chart mit Pfeilen, Linien und Symbolen visualisiert und ein Echtzeit-Dashboard zur Überwachung von Statistiken wie Gesamtsignale, Gewinne/Verluste, Gewinnpunkte und Erfolgsquoten bietet. Es ermöglicht Ihnen tiefere Einblicke in Ihre Handelsansätze durch On-Chart-Tracking und Metriken, die Sie für weitere Anpassungen in Ihrem Trading-Toolkit nutzen können. Viel Spaß beim Handeln!

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

Beigefügte Dateien |
Selbstoptimierende Expert Advisors in MQL5 (Teil 17): Ensemble Intelligence Selbstoptimierende Expert Advisors in MQL5 (Teil 17): Ensemble Intelligence
Alle algorithmischen Handelsstrategien sind, unabhängig von ihrer Komplexität, schwierig einzurichten und zu pflegen – eine Herausforderung für Anfänger und Experten gleichermaßen. In diesem Artikel wird ein Ensemble-Rahmenwerk vorgestellt, in dem überwachte Modelle und menschliche Intuition zusammenarbeiten, um ihre gemeinsamen Einschränkungen zu überwinden. Indem wir eine Kanalstrategie mit gleitendem Durchschnitt mit einem Ridge-Regressionsmodell für dieselben Indikatoren abgleichen, erreichen wir eine zentralisierte Kontrolle, eine schnellere Selbstkorrektur und die Rentabilität von ansonsten unrentablen Systemen.
Entwicklung einer Handelsstrategie: Die Triple-Sinus-Mittelwertumkehrmethode Entwicklung einer Handelsstrategie: Die Triple-Sinus-Mittelwertumkehrmethode
In diesem Artikel wird die Methode der Triple Sine Mean Reversion vorgestellt, eine Handelsstrategie, die auf einem neuen mathematischen Indikator basiert – dem Triple Sine Oscillator (TSO). Der TSO ist von der kubischen Sinusfunktion abgeleitet, die zwischen -1 und +1 oszilliert und sich daher zur Erkennung überkaufter und überverkaufter Marktbedingungen eignet. Insgesamt zeigt die Studie, wie mathematische Funktionen in praktische Handelsinstrumente umgewandelt werden können.
Integration von MQL5 mit Datenverarbeitungspaketen (Teil 6): Zusammenführung von Markt-Feedback und Modellanpassung Integration von MQL5 mit Datenverarbeitungspaketen (Teil 6): Zusammenführung von Markt-Feedback und Modellanpassung
In diesem Teil konzentrieren wir uns darauf, wie man Echtzeit-Markt-Feedback – z. B. Live-Handelsergebnisse, Volatilitätsänderungen und Liquiditätsverschiebungen – mit adaptivem Modelllernen zusammenführt, um ein reaktionsfähiges und selbstverbesserndes Handelssystem zu erhalten.
Entwicklung des Price Action Analysis Toolkit (Teil 50): Entwicklung der RVGI, CCI und SMA Confluence Engine in MQL5 Entwicklung des Price Action Analysis Toolkit (Teil 50): Entwicklung der RVGI, CCI und SMA Confluence Engine in MQL5
Vielen Händlern fällt es schwer, echte Umkehrungen zu erkennen. Dieser Artikel stellt einen EA vor, der RVGI, CCI (±100) und einen SMA-Trendfilter kombiniert, um ein einziges klares Umkehrsignal zu erzeugen. Der EA enthält ein On-Chart-Panel, konfigurierbare Warnungen und die vollständige Quelldatei zum sofortigen Herunterladen und Testen.