English Русский
preview
Einführung in MQL5 (Teil 42): Leitfaden für Einsteiger zur Dateiverarbeitung in MQL5 (IV)

Einführung in MQL5 (Teil 42): Leitfaden für Einsteiger zur Dateiverarbeitung in MQL5 (IV)

MetaTrader 5Indikatoren |
17 2
ALGOYIN LTD
Israel Pelumi Abioye

Einführung

Willkommen zurück zu Teil 42 der Reihe „Einführung in MQL5“. Viele Trader speichern ihre Handelshistorie in einer CSV-Datei, die die Gewinn- und Verlustwerte abgeschlossener Trades enthält, analysieren diese jedoch lediglich als Tabelle. Unabhängig davon, ob sie in Excel oder als reines CSV-Dokument angezeigt werden, bleiben die Daten statisch und fragmentiert. Sie können zwar einzelne Ergebnisse einsehen, aber die Entwicklung des Kontostands, die Drawdown-Dynamik oder die kumulativen Auswirkungen der einzelnen Trades lassen sich im MetaTrader-5-Chart nicht auf einen Blick beurteilen. Die Informationen sind zwar vorhanden, werden jedoch nicht visuell in die Handelsumgebung integriert, in der die Analyse normalerweise stattfindet.

Der Strategietester in MetaTrader 5 liefert zwar Saldenkurven und Leistungsstatistiken, doch diese Ergebnisse basieren ausschließlich auf dem getesteten Expert Advisor. Sie spiegeln nicht die tatsächlich in einem echten Handelskonto ausgeführten Trades wider, insbesondere wenn es sich um manuelle Trades handelt. Im vorherigen Artikel haben wir erläutert, wie Sie Ihre gesamte Handelshistorie aus MetaTrader 5 in eine CSV-Datei exportieren und wie Sie diese Datei in MQL5 auslesen können. Wir haben wichtige Felder wie die Spalte „Profit($)“ und die Gesamtzahl der abgeschlossenen Trades extrahiert, sie in dynamischen Arrays gespeichert und die Daten für die strukturierte Verarbeitung aufbereitet. Auf dieser Grundlage können wir nun historische Rohdaten innerhalb des Terminals in ein praktisches Analysewerkzeug umwandeln.

Am Ende dieses Artikels verfügen Sie über einen voll funktionsfähigen MQL5-Indikator für MetaTrader 5, der eine CSV-Datei mit Ihrer Handelshistorie ausliest – konkret die im vorherigen Artikel erstellte Beispieldatei, die diesem Leitfaden ebenfalls beigefügt ist. Sie erfahren, wo Sie die CSV-Datei ablegen müssen, damit MetaTrader 5 darauf zugreifen kann. Der Indikator extrahiert die Werte von „Profit($)“ aus dieser CSV-Datei und berechnet eine kumulative Saldenkurve. Die Kurve wird dann in einem separaten Fenster dargestellt, wobei die vertikale Achse automatisch entsprechend den Minimal- und Maximalwerten der Progression skaliert wird.

Es werden sowohl die horizontale als auch die vertikale Achse eingezeichnet, um die Kurve an die Daten auszurichten, und über eine Einstellungsoption können Sie numerische Gewinn-/Verlustangaben für jeden einzelnen Trade direkt im Chart aktivieren oder deaktivieren. Mithilfe der Ereignisbehandler „OnInit“ und „OnTimer“ richten Sie den Indikator so ein, dass er dynamisch aktualisiert wird, wobei sichergestellt wird, dass die Kurve nur dann neu gezeichnet wird, wenn neue Trades in der CSV-Datei erscheinen. Dies bietet eine übersichtliche, überprüfbare visuelle Darstellung der Kontoentwicklung und verwandelt statische CSV-Daten in ein praxisorientiertes Handelsinstrument.

 

Das Projekt verstehen

Bevor wir mit der Umsetzung der Lösung beginnen, ist es entscheidend, genau zu verstehen, was wir schaffen wollen und mit welcher Art von Daten wir es zu tun haben. Ziel dieses Projekts ist es, anhand von Handelsdaten aus einer CSV-Datei eine Saldenkurve zu visualisieren. Um das sauber umzusetzen, müssen wir zunächst das Projekt und die Struktur der Datei verstehen (die Beispieldatei ist diesem Beitrag beigefügt).

Die Saldenkurve verstehen

Bevor wir mit dem Indikator arbeiten, ist es unerlässlich, die Definition einer Saldenkurve und ihre Bedeutung für den Handel zu verstehen. Die Wertentwicklung eines Handelskontos im Zeitverlauf wird grafisch durch eine Saldenkurve dargestellt. So lassen sich der Gesamtgewinn und der Gesamtverlust aus abgeschlossenen Trades darstellen. Die Saldenkurve zeigt den allgemeinen Leistungstrend des Kontos, indem sie die Ergebnisse der einzelnen Trades zusammenfasst, anstatt sie separat zu betrachten. Einfach ausgedrückt: Der Gewinn oder Verlust aus jedem einzelnen Trade wird zur bisherigen kumulierten Summe der bisherigen Trades addiert. Die Kurve steigt, wenn ein Trade Gewinn abwirft. Die Kurve verläuft nach unten, wenn ein Trade mit einem Verlust endet. Wir erstellen eine Kurve, die die Entwicklung des Kontos veranschaulicht, indem wir jedes Handelsergebnis fortlaufend zum aktuellen Kontostand hinzurechnen.

Mithilfe der Saldenkurve können Trader wichtige Fragen beantworten. Ist dieser Ansatz durchweg erfolgreich? Gibt es lang anhaltende Drawdowns? Ist die Performance wirklich schwankend oder konstant? Während eine Kurve mit plötzlichen Einbrüchen auf eine ungleichmäßige Entwicklung oder Probleme beim Risikomanagement hindeuten kann, zeugt eine gleichmäßig ansteigende Kurve oft von stabilen Fortschritten. Wichtig ist außerdem die Unterscheidung zwischen Equity und Kontosaldo (Balance). Während das Equity die schwankenden Gewinne und Verluste aus offenen Positionen umfasst, bezieht sich der Kontostand oder Saldo ausschließlich auf den Kontowert nach abgeschlossenen Trades. Die Saldenkurve, die wir in diesem Fall erstellen, basiert auf den realisierten Gewinn- und Verlustwerten, da wir historische Daten aus einer Datei extrahieren, die abgeschlossene Trades enthält.

Wir wandeln Rohdaten in eine verständliche Darstellung der Wertentwicklung um, indem wir die Saldenkurve aus der in der Datei gespeicherten Gewinn- und Verlustentwicklung visualisieren. Wir können auf einen Blick erkennen, ob das Konto im Laufe der Zeit wächst, schrumpft oder stagniert, anstatt uns durch ganze Zeilen voller Zahlen arbeiten zu müssen. Bevor man diesen Indikator praktisch einsetzt, ist es unerlässlich, diesen Gedanken zu verstehen, da ihr Zweck darin besteht, die Kontoentwicklung aussagekräftig und analytisch darzustellen und nicht lediglich Statistiken anzuzeigen.

Abbildung 1. Spalte „Profit($)“

In diesem Projekt können Sie selbst festlegen, wie die Daten im Chart dargestellt werden sollen. Sowohl die Saldenkurve als auch die spezifischen Gewinn- und Verlustzahlen für jeden Trade können sofort im Chart angezeigt werden. So können Sie leicht erkennen, wie sich jeder abgeschlossene Trade auf die Gesamtperformance Ihres Kontos auswirkt.

Um beispielsweise besser zu verstehen, wie sich bestimmte Trades auf das Konto ausgewirkt haben, möchte man den genauen Gewinn- oder Verlustbetrag in jeder Phase entlang der Kurve einsehen. Bei der Analyse des strategischen Verhaltens oder der Untersuchung von Drawdowns und Erholungsphasen kann dies äußerst hilfreich sein.

Abbildung 2. Saldenkurve

Sie könnten sich jedoch für eine einfachere und übersichtlichere grafische Darstellung entscheiden. In diesem Fall haben Sie die Möglichkeit, statt der einzelnen Gewinn- und Verlustzahlen nur die Saldenkurve anzuzeigen. Dank dieser Option bleibt das Chart übersichtlicher, sodass Sie sich leichter auf den allgemeinen Aufwärts- oder Abwärtstrend konzentrieren können.

Abbildung 3. Indikator für die Saldenkurve

Die Saldenkurve interpretieren

Es ist entscheidend, zu verstehen, was die Saldenkurve im Indikatorfenster in Bezug auf Ihr Handelskonto aussagt, nachdem Sie sie eingezeichnet haben. Jeder Punkt auf der Kurve, die den kumulierten Kontostand darstellt, entspricht genau einem abgeschlossenen Trade aus Ihrer CSV-Datei. Der Gewinn oder Verlust des Trades bestimmt, ob die Kurve steigt oder fällt. Die Kurve steigt bei einem erfolgreichen Trade und fällt bei einem verlorenen Trade. Der Verlauf und die Steigung der Kurve geben einen sofortigen Einblick in die Leistungsmuster. Einbrüche deuten auf Verluste hin, während eine kontinuierlich ansteigende Kurve eine Reihe positiver Trades und anhaltende Fortschritte anzeigt. Sie können die Höhe der Gewinne oder Verluste ermitteln, indem Sie untersuchen, wie stark diese Schwankungen ausfallen.

Der vertikale Abstand zwischen einem vorherigen Höchststand und einem nachfolgenden Tiefststand ist ein Maß für Drawdowns. Der maximale Drawdown ist der größte Rückgang vom Höchststand zum Tiefststand über den gesamten Handelszeitraum hinweg. Es zeigt den größten Verlust, den das Konto erlitten hat, bevor es sich wieder erholt hat. Dies ist eine wichtige Risikokennzahl, da sie Aufschluss darüber gibt, um wie viel das Konto in ungünstigen Phasen an Wert verlieren könnte, und dabei hilft, das Risiko Ihrer Handelsstrategie einzuschätzen. Da jeder Punkt mit einem einzelnen Trade verknüpft ist, können Sie auch genau erkennen, welche Trades zu den größten Drawdowns beigetragen haben.

Abbildung 4. Drawdown

Anhand der Kurve können Sie die maximale und minimale Anzahl aufeinanderfolgender Gewinne und Verluste ermitteln. Trades, die durchweg mit einem Verlust abgeschlossen werden, werden als aufeinanderfolgende Verluste bezeichnet und können Ihnen helfen, Situationen zu erkennen, in denen Ihre Strategie unter Druck geraten könnte. Eine Reihe gewinnbringender Trades, die Phasen hoher Leistungsfähigkeit und Dynamik verdeutlichen, wird als „Gewinnserien“ bezeichnet. Indem Sie beide Faktoren im Auge behalten, können Sie mehr über die Zuverlässigkeit und Volatilität Ihrer Handelsstrategie erfahren. Die numerischen Gewinn- und Verlustangaben, die der Kurve hinzugefügt wurden, verdeutlichen dies noch besser. Indem Sie den tatsächlichen Gewinn bzw. Verlust jedes einzelnen Trades anzeigen, können Sie schnell erkennen, welche Trades den größten Einfluss auf Ihren kumulierten Kontostand hatten, und nachvollziehen, warum die Kurve an bestimmten Punkten steigt oder fällt.

Anhand dieser Kurve können Sie die Gesamtrentabilität bewerten, risikobehaftete Phasen erkennen und feststellen, wie gut Ihre Handelsstrategie Chancen in Kontowachstum umsetzt. Der maximale Drawdown sowie aufeinanderfolgende Gewinne oder Verluste liefern präzise Indikatoren für Risiko und Beständigkeit, während die Kurve die rohen CSV-Daten in eine anschauliche und aussagekräftige Darstellung Ihrer Handelsbilanz umwandelt. Dies wird durch die Hinzufügung von numerischen Gewinn- und Verlustangaben zur Kurve noch deutlicher. Indem Sie den tatsächlichen Gewinn bzw. Verlust jedes einzelnen Trades anzeigen lassen, können Sie leicht feststellen, welche Trades den größten Einfluss auf Ihren kumulierten Kontostand hatten, und nachvollziehen, warum die Kurve an bestimmten Stellen ansteigt oder abfällt. Anhand dieser Kurve können Sie die Gesamtrentabilität bewerten, risikobehaftete Phasen erkennen und feststellen, wie gut Ihre Handelsstrategie Chancen in Kontowachstum umsetzt. Die Kurve wandelt CSV-Rohdaten in eine visuelle und aussagekräftige Darstellung Ihrer Handelsperformance um.

Dateistruktur

Der erste Schritt bei der Arbeit mit externen Daten in MQL5 besteht darin, die Struktur der Datei zu verstehen. Die Logik Ihres Programms hängt vollständig vom Format der jeweiligen CSV-Datei ab, die eine festgelegte Anzahl von Spalten und Kopfzeilen aufweist.

In der Beispieldatei, die wir in diesem Artikel verwenden werden, gibt es zwölf Spalten und zwölf entsprechende Überschriften. Jede Spalte steht für eine bestimmte Handelsinformation, wie beispielsweise Zeitpunkt, Symbol, Ordertyp, Lotgröße, Eröffnungszeit, Gewinn und Ergebnisse.

Abbildung 5. Dateistruktur

Im vorherigen Artikel haben wir bereits erläutert, wie die Indizierung funktioniert, wenn CSV-Daten in Arrays eingelesen werden. Wir nutzen den Index, um auf jeden Wert zuzugreifen, der entsprechend seiner Position in der Zeile gespeichert ist. Daher muss die Reihenfolge der Spalten unverändert bleiben.

Für die Erstellung der Saldenkurve benötigen wir lediglich die Spalte „Profit($)“. Alle erforderlichen Daten stammen ausschließlich aus den unter dieser Überschrift aufgeführten Werten. Indem wir die Gewinnwerte an ihren indexierten Positionen extrahieren und kumulativ summieren, können wir die Saldenkurve berechnen. Wenn sich die Reihenfolge der Spalten ändert oder die Spalte „Profit($)“ verschoben wird, verweisen die Indizes nicht mehr auf die richtigen Daten, und das Programm funktioniert nicht mehr wie vorgesehen.

So greifen Sie aus Ihrem MQL5-Programm auf Dateien zu

In diesem Abschnitt wird gezeigt, wie man die erforderliche Datei im entsprechenden Ordner ablegt, und es wird erläutert, warum dies wichtig ist. In diesem Artikel arbeiten wir mit einer CSV-Datei. Nehmen wir zur Veranschaulichung einmal an, wir hätten eine CSV-Datei (die Datei ist diesem Artikel beigefügt). Diese Datei muss in einem von zwei unterschiedlichen Verzeichnissen abgelegt werden, die jeweils einen eigenen Pfad und eine eigene Funktion haben, damit Ihr MQL5-Programm darauf zugreifen kann.

Der terminalspezifische Ordner „MQL5\Files“ ist der erste Ordner. In diesem Ordner, der mit dem von Ihnen derzeit verwendeten MetaTrader 5-Terminal verknüpft ist, sollten programmspezifische Daten gespeichert werden. Auf den meisten Systemen sieht der Pfad wie folgt aus:

C:\Users\Dell\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Files

Hier steht D0E8209F77C8CF37AD8BF550E51FF075 für eine bestimmte Installation des MetaTrader-5-Terminals auf meinem PC. Nur dieses bestimmte Terminal kann auf alle in diesem Ordner gespeicherten Dateien zugreifen. Dadurch werden Probleme mit anderen Terminals vermieden und es wird sichergestellt, dass Ihr Programm die Datei stets finden kann. Wenn Ihr Programm regelmäßig auf seine Dateien zugreifen muss, ist dies die sicherste Wahl.

Der zweite Ordner ist der gemeinsame Terminalordner, der von allen auf demselben Computer installierten MetaTrader 5-Terminals gemeinsam genutzt wird. Der Pfad lautet in der Regel:

C:\Users\Dell\AppData\Roaming\MetaQuotes\Terminal\Common\Files

Auf die hier gespeicherten Dateien kann von jedem Terminal auf diesem Rechner aus zugegriffen werden. Auch wenn dies für den Datenaustausch zwischen verschiedenen Terminals auf demselben Rechner nützlich sein kann, kann es zu Problemen kommen, wenn viele Programme gleichzeitig versuchen, dieselbe Datei zu lesen oder zu schreiben. Bei der Arbeit an einem einzelnen Programm wird im Allgemeinen empfohlen, den terminalspezifischen Ordner „MQL5\Files“ zu verwenden. Wenn Sie Ihre CSV-Datei in diesem Ordner ablegen, stellen Sie sicher, dass Ihr Programm stets darauf zugreifen kann, und sorgen dafür, dass Ihre Dateien übersichtlich organisiert und von anderen Terminals getrennt bleiben.

Stellen Sie für die Zwecke dieses Beitrags sicher, dass Ihr MQL5-Programm ohne zusätzliche Optionen oder Einstellungen auf die CSV-Datei zugreifen kann, indem Sie sie im terminalspezifischen Ordner „MQL5\Files“ ablegen. Dadurch ist die Einrichtung unkompliziert und es werden unnötige Missverständnisse beim Nacharbeiten der Beispiele vermieden. Je nachdem, wie Sie auf diese Dateien in all Ihren MetaTrader 5-Installationen zugreifen und diese verwalten möchten, können Sie entscheiden, ob Sie Ihre eigenen Dateien im gemeinsamen Ordner oder im terminalspezifischen Ordner ablegen möchten.

Objekte im Indikatorfenster darstellen

Die Überschrift „Profit($)“ wird dann mithilfe einer Schleife in der Datei gesucht. Wir können die Gewinnwerte zuverlässig extrahieren, indem wir den Index um zwölf erhöhen, nachdem wir die Startposition der Gewinnspalte ermittelt haben, da wir die Dateistruktur bereits untersucht und festgestellt haben, dass jeder Handelsdatensatz zwölf Spalten umfasst. Zudem wird der Eintrag „Total Trades:“, der in der Datei gespeichert ist, mithilfe einer anderen Schleife ermittelt. Dadurch wird sichergestellt, dass wir ausschließlich gültige Handelsdaten abrufen, und wir können die Gesamtzahl der ausgeführten Trades ermitteln. Sobald alle Gewinnwerte aus der Spalte „Profit($)“ erfasst wurden, wird ein Array erstellt. Anhand dieser Werte wird dann das kumulierte Ergebnis berechnet, das die Entwicklung des Saldos im Zeitverlauf zeigt. Die Saldenkurve basiert auf der kumulativen Berechnung.

Die Funktion berechnet nach der Berechnung der kumulierten Werte die Minimal- und Maximalwerte des Indikatorfensters. Dadurch wird sichergestellt, dass die Saldenkurve im Unterfenster vollständig sichtbar ist und die richtige Größe hat. Wenn der Indikator gestartet wird, ruft der Ereignis-Handler „OnInit()“ diese Datenverarbeitungsprozedur auf, um die Daten zu laden. Zudem wird sie innerhalb des OnTimer()-Ereignishandlers aufgerufen, wodurch der Indikator in bestimmten Intervallen automatisch aktualisiert wird.

Zuletzt zeichnet die Funktion „OnCalculate()“ die grafischen Elemente (wie Trendlinien und Textbeschriftungen) ein. Dies ist erforderlich, um die Objekte korrekt zu platzieren, da OnCalculate() Zugriff auf die Daten der Bars im Chart bietet. Um die Leistung zu steigern, wird eine Bedingung eingeführt, sodass OnCalculate() die Zeichenlogik nur dann ausführt, wenn die Gesamtzahl der Trades von dem zuvor ermittelten Wert abweicht. Dies steigert die Leistung und vermeidet unnötiges Neuzeichnen.

 

Öffnen der CSV-Datei in MQL5

In diesem Abschnitt beschreiben wir, wie man eine CSV-Datei in MQL5 öffnet. Dieses Mal werden wir einen methodischeren und effektiveren Ansatz verfolgen als im letzten Beitrag, in dem die Datei direkt innerhalb der Ereignisbehandlung geöffnet wurde. Das Öffnen der Datei wird von einer eigens dafür vorgesehenen Funktion übernommen und nicht innerhalb von OnCalculate(). Durch diese Struktur wird die Datei nicht bei jedem Tick neu geöffnet, was die Leistung beeinträchtigen und mehr Ressourcen als nötig beanspruchen würde.

Die Funktion übernimmt alle dateibezogenen Aufgaben, wie beispielsweise das Öffnen der CSV-Datei, die Überprüfung des Datei-Handles und die Vorbereitung der Daten für die Verarbeitung. Indem man die Logik in eine Funktion einbindet, wird der Code übersichtlicher, modularer und leichter zu warten. Um die Datei bei der ersten Initialisierung des Indikators zu laden, wird diese Funktion innerhalb des Ereignis-Handlers „OnInit()“ aufgerufen. Auch der OnTimer()-Ereignishandler ruft ihn auf. Indem der Timer das erneute Laden der Datei in einem vorgegebenen Intervall ermöglicht, stellt er sicher, dass Änderungen regelmäßig und nicht bei jedem Markt-Tick verarbeitet werden.

In der Zwischenzeit wird lediglich das Zeichnen der grafischen Objekte vom Ereignis-Handler „OnCalculate()“ übernommen. Es wird eine Bedingung hinzugefügt, um die Effizienz weiter zu steigern. Dadurch wird sichergestellt, dass OnCalculate() seine Zeichenlogik nur dann ausführt, wenn sich die Gesamtzahl der Trades im Vergleich zum zuvor berechneten Wert geändert hat. Dadurch werden unnötige Neuberechnungen und das erneute Zeichnen von Objekten vermieden, da sichergestellt wird, dass der Indikator nur dann aktualisiert wird, wenn neue Handelsdaten erkannt werden.

Beispiel:
#property indicator_separate_window

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {

//--- create timer
   EventSetTimer(60);

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {

//--- destroy timer
   EventKillTimer();
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int32_t rates_total,
                const int32_t prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int32_t &spread[])
  {
//---
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {

  }
//+-----------------------------------------------------------------+
//| Balance curve function                                           |
//+------------------------------------------------------------------+
void BalanceCurve(string Filename)
  {

   int file_handle = FileOpen(Filename, FILE_READ | FILE_CSV | FILE_ANSI, ',');

   if(file_handle == INVALID_HANDLE)
     {
      Print("Failed to open file. Error: ", GetLastError());
     }

   else
      if(file_handle != INVALID_HANDLE)
        {

         FileClose(file_handle);

        }
  }

Erläuterung:

„Indicator_separate_window“ ist die erste wichtige Einstellung unseres Indikators. Mit dieser Einstellung wird MetaTrader angewiesen, den Indikator in einem separaten Fenster unterhalb des Hauptkurscharts anzuzeigen. Es ist sinnvoller, die Visualisierung vom Kerzenchart zu trennen, da wir mit Daten zur Kontoperformance und nicht mit Kursdaten arbeiten. Im Gegensatz zur Marktpreisentwicklung zeigt die Saldenkurve die kumulierten Gewinne und Verluste im Zeitverlauf. Das Chart bleibt übersichtlich, und die Leistungsanalyse ist leichter zu verstehen, wenn sie in einem separaten Fenster angezeigt wird. Würde diese Funktion nicht genutzt, würde das Kurschart die Saldenkurve direkt anzeigen. Das könnte das Verständnis beider Arten von Informationen erschweren, da sie dadurch miteinander vermischt würden. Die Verwendung eines separaten Fensters sorgt für Übersichtlichkeit und eine bessere Organisation.

Eine Funktion namens „BalanceCurve“, die keinen Rückgabewert liefert, wird mit der Funktionsdeklaration „void BalanceCurve(string Filename)“ definiert. Die Funktion führt intern Operationen aus, gibt jedoch keine Ergebnisse an den Aufrufer zurück, wie das Schlüsselwort „void“ andeutet. Der Name der CSV-Datei, die wir öffnen möchten, wird durch den einzigen Parameter der Funktion, den String „Filename“, dargestellt. Die Funktion wird flexibler und wiederverwendbarer, indem der Dateiname als Parameter übergeben wird, anstatt ihn fest zu programmieren. Das bedeutet, dass wir mehrere CSV-Dateien verwenden können, um dieselbe Funktion aufzurufen, ohne die Kernlogik zu verändern.

Ein Parameter innerhalb der Funktion versucht, die angegebene Datei im Lesemodus mit ANSI-Kodierung und im CSV-Format zu öffnen, und speichert das Ergebnis anschließend in einer Variablen namens „file_handle“. Bei erfolgreicher Dateiöffnung gibt das System in MQL5 eine Ganzzahl zurück, die als Dateihandle bezeichnet wird. Das Programm kann dank dieses Handles, das als eindeutige Kennung dient, mit dieser bestimmten Datei interagieren, indem es Daten aus ihr ausliest oder andere Dateioperationen durchführt. Die Funktion signalisiert einen Fehler, indem sie ein ungültiges Handle zurückgibt, falls die Datei aus irgendeinem Grund nicht geöffnet werden kann.

Dem Programm wird über das erste Argument „Filename“ mitgeteilt, welche Datei geöffnet werden soll. MQL5 durchsucht standardmäßig den terminalspezifischen Ordner „MQL5\Files“ nach Dateien. Dieser Ordner ist Teil der Installation des derzeit laufenden MetaTrader 5-Terminals. Ohne weitere Konfiguration kann jede in diesem Verzeichnis gespeicherte Datei sofort abgerufen werden. Wenn Sie mit indikatorspezifischen Daten arbeiten, ist dies der empfohlene Ort.

Die Dateizugriffsflags sind im zweiten Argument enthalten. Durch diese Flags werden die Methoden zum Öffnen und zur Verarbeitung der Datei festgelegt. Die Datei wird im schreibgeschützten Modus geöffnet, wenn das Flag FILE_READ gesetzt ist. Dieser Parameter ist erforderlich, da das Ziel darin besteht, Daten aus der CSV-Datei zu extrahieren, anstatt sie zu ändern. MQL5 wird angewiesen, die Datei mithilfe des Flags FILE_CSV als CSV-Datei zu behandeln. Wenn dieses Flag gesetzt ist, teilt das System die Daten automatisch anhand des angegebenen Trennzeichens in Felder auf.

Dadurch wird das separate Auslesen der einzelnen Komponenten vereinfacht. Die Kodierung der Datei wird durch das Flag FILE_ANSI festgelegt. MQL5 wird darüber informiert, dass die Datei mit ANSI-Kodierung gespeichert wird. Das FILE_UNICODE-Flag müsste verwendet werden, wenn die Datei stattdessen im Unicode-Format gespeichert würde. Damit Zeichen korrekt angezeigt werden, muss die Kodierung mit dem Dateiformat übereinstimmen.

Das Komma, das als letztes Argument angegeben wird, bezeichnet das Trennzeichen der CSV-Datei. Da Werte in gewöhnlichen CSV-Dateien durch Kommas getrennt sind, gibt dieses Argument MQL5 vor, wie die einzelnen Zeilen in einzelne Felder unterteilt werden sollen. Ein Trennzeichen, beispielsweise ein Semikolon, müsste angegeben werden, falls die Datei eines verwendet. Ein zusätzliches Flag namens FILE_COMMON muss der Funktion „FileOpen“ hinzugefügt werden, wenn sich die Datei im allgemeinen Terminalverzeichnis und nicht im terminalspezifischen Ordner „MQL5\Files“ befindet. Alle auf dem Computer installierten MetaTrader 5-Terminals können auf den Ordner „Terminal\Common\Files“ zugreifen. Dort sucht MQL5 nach der Datei, wenn dieses Flag gesetzt ist. Wenn Sie eine Datei über mehrere Terminals hinweg gemeinsam nutzen möchten, ist diese Funktion hilfreich.

Beim Versuch, die Datei zu öffnen, prüft die Funktion sofort, ob das zurückgegebene Handle mit INVALID_HANDLE übereinstimmt. Um die Diagnose von Problemen wie fehlenden Dateien oder falschen Verzeichnissen zu erleichtern, gibt das Programm in solchen Fällen eine Fehlermeldung aus, zusammen mit dem genauen Fehlercode, der über die Funktion GetLastError() abgerufen wurde. Die Datei wird mit „FileClose“ ordnungsgemäß geschlossen, sofern sie erfolgreich geöffnet wurde. Das ordnungsgemäße Schließen der Datei verhindert nicht nur mögliche Konflikte beim Dateizugriff, sondern stellt auch sicher, dass die Systemressourcen freigegeben werden. In einer vollständigen Implementierung würde die Logik zum Einlesen der Datei normalerweise vor dem Beenden des Prozesses ausgeführt werden.

 

Speichern von Dateielementen in einem dynamischen Array

Der gesamte Inhalt der Datei muss nach dem Öffnen in einem dynamischen Array gespeichert werden. Um dies sicher durchzuführen, muss das Programm zunächst ermitteln, wie viele Elemente in der Datei vorhanden sind. Dynamische Arrays müssen dieselbe Größe haben wie die Anzahl der zu speichernden Elemente, da sie vor ihrer Verwendung angepasst werden müssen. Wenn das Array zu klein ist, treten beim Versuch, auf weitere Elemente zuzugreifen, Fehler wegen Bereichsüberschreitung auf.

Indem die Datei nacheinander gelesen und ein Zähler nach der Verarbeitung jedes Elements erhöht wird, werden die Elemente gezählt. Der Zähler zeigt die Gesamtzahl der Datensätze in der Datei an. Um den Inhalt noch einmal von Anfang an zu lesen, wird die Dateipositionsanzeige anschließend auf den Anfang zurückgesetzt. Sobald die Gesamtanzahl bekannt ist, wird die Größe des dynamischen Arrays so angepasst, dass jedes Element Platz findet. Jeder Datensatz wird dann gelesen und dem Array hinzugefügt, während die Datei erneut durchlaufen wird.

Beispiel:
//+------------------------------------------------------------------+
//| Balance curve function                                           |
//+------------------------------------------------------------------+
void BalanceCurve(string Filename)
  {
   int file_handle = FileOpen(Filename, FILE_READ | FILE_CSV | FILE_ANSI, ',');

   if(file_handle == INVALID_HANDLE)
     {
      Print("Failed to open file. Error: ", GetLastError());
     }

   else
      if(file_handle != INVALID_HANDLE)
        {
         //counting total elements
         int   total_elements_count = 0;
         while(!FileIsEnding(file_handle))
           {
            FileReadString(file_handle);
            total_elements_count++;
           }

         FileSeek(file_handle, 0, SEEK_SET);

         //storing in array
         string TotalElements[];
         ArrayResize(TotalElements,total_elements_count);

         for(int i = 0; i < total_elements_count; i++)
           {

            TotalElements[i] = FileReadString(file_handle);

           }

         FileClose(file_handle);
        }
  }

Erläuterung:

Wir sind bereit, jedes Element in einem dynamischen Array zu speichern, sobald wir die Gesamtzahl der Elemente in der Datei ermittelt haben. Zunächst deklarieren wir „TotalElements“, ein dynamisches String-Array. Da sich die Größe eines dynamischen Arrays genau an die Anzahl der Elemente anpassen lässt, die darin gespeichert werden sollen, ist es flexibel. Nachdem die Gesamtanzahl berechnet wurde, wird die Größe des Arrays entsprechend dieser Zahl angepasst. Dadurch wird sichergestellt, dass jede Komponente der Datei einen eigenen Speicherplatz im Arbeitsspeicher belegt. Sollte das Array zudem kleiner sein als die Anzahl der zu speichernden Elemente, könnte es zu Problemen aufgrund von Werten außerhalb des zulässigen Bereichs kommen. Dies lässt sich vermeiden, indem man das Array entsprechend vergrößert.

Nachdem das Array korrekt dimensioniert wurde, wird die Datei schrittweise eingelesen. Um dies zu erreichen, wird eine Schleife verwendet, die beim ersten Index beginnt und so lange fortgesetzt wird, bis jedes Element verarbeitet wurde. In jedem Zyklus wird das nächste Dateielement gelesen und dem entsprechenden Array-Index zugewiesen. Die Daten werden in einer geordneten und fortlaufenden Reihenfolge geliefert, da der Dateizeiger nach jedem Lesevorgang automatisch weitergeschaltet wird.

Bis zum Ende der Schleife wird jedes Element aus der Datei nach und nach im Array abgelegt. Dank dieser Struktur lässt sich jedes einzelne Element später ganz einfach über seinen Index abrufen. Darüber hinaus bietet es eine Grundlage für komplexere Verarbeitungsschritte, darunter die Anordnung der Daten in Spalten oder die Durchführung von Berechnungen wie beispielsweise des kumulierten Gewinns bei der Erstellung einer Saldenkurve. Zunächst wird eine Zählvariable deklariert und auf Null gesetzt, um die Anzahl der Elemente in der Datei zu ermitteln. Da bisher noch keine Elemente gezählt wurden, ist es sinnvoll, bei Null anzufangen. Solange die Datei noch nicht zu Ende ist, läuft eine Schleife weiter. Das Programm wird aufgrund dieser Bedingung so lange weiterlesen, bis keine Daten mehr verfügbar sind.

Das Programm liest die Datei innerhalb der Schleife Element für Element ein. Der Zähler wird erhöht, um anzuzeigen, dass jedes Mal, wenn ein Wert korrekt erfasst wird, ein weiteres Element gezählt wurde. Anschließend wird die Datei sequenziell verarbeitet, da der Dateizeiger bei jedem Lesevorgang automatisch weitergeschaltet wird. Der Zähler enthält am Ende der Schleife die Gesamtzahl aller Elemente in der Datei. Als Nächstes: der Zeiger befindet sich nun an der letzten Stelle, da die gesamte Datei bereits gelesen wurde. Der Zeiger muss an den Anfang der Datei zurückgesetzt werden, falls eine weitere Verarbeitung erforderlich ist. Auf diese Weise können Sie sicherstellen, dass nachfolgende Vorgänge auf die Daten vom Anfang aus zugreifen und nicht vom Ende, wo keine Daten mehr zum Lesen vorhanden sind.

                          

Die Spalte „Profit($)“ in der CSV-Datei finden

In diesem Abschnitt werden wir uns damit befassen, wie man den Index der Datei für eine bestimmte Information oder ein bestimmtes Element ermittelt. Im vorherigen Artikel habe ich beschrieben, wie man mithilfe der Indizierung in einer CSV-Datei die Position jedes Elements ermitteln kann. Die Überschrift „Profit($)“ befindet sich an Index 22, wenn Sie sich die Beispieldatei ansehen. Ich werde diesen Index in dieser Erklärung jedoch nicht fest codieren. Diese Methode stellt sicher, dass Sie bei der Arbeit mit anderen Dateien dieselbe Vorgehensweise anwenden können, sodass Ihr Code die erforderliche Spalte dynamisch ermitteln kann, anstatt auf festgelegte Positionen angewiesen zu sein.

Abbildung 6. Spalte „Profit“

Beispiel:

#property indicator_separate_window


string File_name = "Trading_Journal.csv";
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {

   BalanceCurve(File_name);

//--- create timer
   EventSetTimer(60);

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {

//--- destroy timer
   EventKillTimer();
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int32_t rates_total,
                const int32_t prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int32_t &spread[])
  {
//---

   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {

  }
//+-----------------------------------------------------------------+
//| Balance curve function                                           |
//+------------------------------------------------------------------+
void BalanceCurve(string Filename)
  {

   int file_handle = FileOpen(Filename, FILE_READ | FILE_CSV | FILE_ANSI, ',');

   if(file_handle == INVALID_HANDLE)
     {
      Print("Failed to open file. Error: ", GetLastError());
     }

   else
      if(file_handle != INVALID_HANDLE)
        {

         //counting total elements
         int   total_elements_count = 0;
         while(!FileIsEnding(file_handle))
           {
            FileReadString(file_handle);
            total_elements_count++;
           }

         FileSeek(file_handle, 0, SEEK_SET);

         //storing in array
         string TotalElements[];
         ArrayResize(TotalElements,total_elements_count);

         for(int i = 0; i < total_elements_count; i++)
           {

            TotalElements[i] = FileReadString(file_handle);

           }

         //Serching Profit($) header
         int profit_header_index;

         for(int i = 0; i < total_elements_count; i++)
           {

            if(TotalElements[i] == "Profit($)")
              {

               profit_header_index = i;

               break;
              }
           }

         Comment(profit_header_index);

         FileClose(file_handle);

        }
  }

Erläuterung:

Im ersten Abschnitt des Programms geht es darum, den Index der Spalte „Profit($)“ der CSV-Datei zu ermitteln. Das Programm weiß, wo er mit dem Auslesen der Gewinnzahlen beginnen muss, da dieser Index in einer Variablen für die spätere Verwendung gespeichert ist. Der Code durchsucht die Datei nacheinander nach jeder einzelnen Komponente, und sobald er den Header findet, bricht er die Suche sofort ab und protokolliert die Position. Das Programm zeigt kurz den gefundenen Index an, um zu überprüfen, ob die richtige Spalte gefunden wurde, und so die Genauigkeit sicherzustellen. Anschließend wird die CSV-Datei ausgewählt, ihr Inhalt eingelesen und in Arrays organisiert, woraufhin die Gewinn-Daten für die Darstellung der Saldenkurve bereitstehen.


Die Gesamtzahl der Trades in der CSV-Datei ermitteln

Das Programm kann nun in der Datei nach „Total Trades:“ suchen, genau wie es dies bei der Überschrift „Profit($)“ tut. Die Anzahl der Trades steht im Wert rechts neben dem Eintrag „Total Trades:“. Es ist zuverlässiger, nach dem Text zu suchen als nach der Zahl, da der Text immer gleich bleibt, während sich die Zahl ändern kann. Das Programm muss nur einen Schritt weitergehen, um den tatsächlichen Gesamtwert der Trades zu ermitteln, nachdem die Position von „Total Trades:“ ermittelt wurde, wobei sichergestellt werden muss, dass es stets den richtigen Wert zurückgibt, unabhängig vom aktuellen Wert.

Abbildung 7. Gesamtzahl der Trades

Beispiel:

//+------------------------------------------------------------------+
//| Balance curve function                                           |
//+------------------------------------------------------------------+
void BalanceCurve(string Filename, int &total_deals)
  {
   int file_handle = FileOpen(Filename, FILE_READ | FILE_CSV | FILE_ANSI, ',');

   if(file_handle == INVALID_HANDLE)
     {
      Print("Failed to open file. Error: ", GetLastError());
     }

   else
      if(file_handle != INVALID_HANDLE)
        {

         //counting total elements
         int   total_elements_count = 0;
         while(!FileIsEnding(file_handle))
           {
            FileReadString(file_handle);
            total_elements_count++;
           }

         FileSeek(file_handle, 0, SEEK_SET);

         //storing in array
         string TotalElements[];
         ArrayResize(TotalElements,total_elements_count);

         for(int i = 0; i < total_elements_count; i++)
           {

            TotalElements[i] = FileReadString(file_handle);

           }

         //Serching Profit($) header
         int profit_header_index;

         for(int i = 0; i < total_elements_count; i++)
           {

            if(TotalElements[i] == "Profit($)")
              {

               profit_header_index = i;

               break;
              }
           }

         //Comment(profit_header_index);

         //Getting total deals
         int total_deal_index;

         for(int i = 0; i < total_elements_count; i++)
           {

            if(TotalElements[i] == "Total Trades:")
              {

               total_deal_index = i + 1;

               break;
              }
           }

         total_deals = (int)StringToInteger(TotalElements[total_deal_index]);

         FileClose(file_handle);
        }
  }
string File_name = "Trading_Journal.csv";
int Total_deals;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {

   BalanceCurve(File_name,Total_deals);
   Comment("Total Deals: ", Total_deals);

//--- create timer
   EventSetTimer(60);

//---
   return(INIT_SUCCEEDED);
  }

Erläuterung:

Die Funktion „BalanceCurve“ dient dazu, die CSV-Datei zu verarbeiten und die Gesamtzahl der Trades zu überwachen. Die Verwendung einer Referenz deutet darauf hin, dass eine Referenzvariable für die Gesamtzahl der Trades akzeptiert wird. Dadurch kann die Funktion praktisch einen Wert „zurückgeben“, ohne dass eine herkömmliche return-Anweisung erforderlich ist, da alle Änderungen an der Gesamtzahl der Trades innerhalb der Funktion automatisch die Variable außerhalb der Funktion aktualisieren.

Um zu steuern, wann die Funktion „OnCalculate“ Objekte im Chart darstellt, wird eine Referenzvariable verwendet. Insbesondere können Chart-Elemente nur dann gezeichnet werden, wenn sich die Gesamtzahl der Deals geändert hat. Das Programm kann feststellen, ob seit der letzten Berechnung eine neue Transaktion hinzugefügt oder entfernt wurde, indem es die aktuelle Anzahl der Trades in der Referenzvariablen verfolgt. Dadurch werden unnötige Zeichnungsvorgänge vermieden und die Leistung gesteigert. Das Programm berechnet innerhalb der Funktion, wie viele Trades die CSV-Datei insgesamt enthält. Anschließend wird die Bezeichnung „Total Trades:“ durch Durchsuchen des Arrays der Dateielemente gefunden. Sobald das Programm die Beschriftung findet, erkennt es das Element, das die tatsächliche Anzahl der Trades enthält. Sobald das Ziel gefunden wurde, wird die Suche beendet, um unnötige Verarbeitungsschritte zu vermeiden.

Die Referenzvariable wird dann verwendet, um die Anzahl der Trades zu speichern, nachdem sie von einer Zeichenkette in eine Ganzzahl umgewandelt wurde. Dieser Wert steht außerhalb der Funktion automatisch zur Verfügung, sodass andere Anzeigekomponenten ihn bei Bedarf nutzen können. Schließlich wird die externe Variable mit der aktuellen Anzahl der Trades aktualisiert, wenn „BalanceCurve“ mit der CSV-Datei und der Variable „total trades“ aufgerufen wird. Zur Bestätigung kann das Programm diese Zahl dann im Chart anzeigen. Durch die Anwendung dieser Methode werden Effizienz und Genauigkeit gesteigert, da der Indikator stets über die aktuelle Anzahl der Trades informiert ist und die Darstellung in „OnCalculate“ entsprechend anpassen kann, je nachdem, ob sich die Anzahl der Trades geändert hat.

 

Berechnung der Saldenentwicklung

In diesem Abschnitt werden wir die Saldenentwicklung Schritt für Schritt mithilfe einer for-Schleife berechnen. Das Ziel besteht darin, die Gewinn- und Verlustzahlen der einzelnen Trades in eine kumulative Reihe umzuwandeln, die veranschaulicht, wie sich die Kontoperformance im Laufe der Zeit verändert. Beginnen wir damit, den Begriff „Gewinnentwicklung“ genau zu definieren. Die laufende Summe der Gewinne und Verluste, während die Trades nacheinander hinzugefügt werden, wird als fortlaufende Gewinn- und Verlustentwicklung bezeichnet. Wir addieren jeden neuen Gewinn oder Verlust fortlaufend zur ursprünglichen Gesamtsumme, anstatt das Ergebnis jeder einzelnen Transaktion separat zu betrachten. Der kumulierte Wert steigt, wenn ein Trade mit Gewinn abgeschlossen wird. Der Gesamtwert sinkt, wenn der Handel mit einem Verlust abgeschlossen wird. Die Saldenkurve wird auf der Grundlage dieser laufenden Summe erstellt.

Die Gewinn- und Verlustzahlen in der Spalte „Profit($)“ werden dann zu einem einzigen dynamischen Array zusammengefasst. Dadurch wird sichergestellt, dass wir nur die Werte herausfiltern, die zur Berechnung der Progression benötigt werden. Jedes Element in diesem Array steht für das Ergebnis eines einzelnen abgeschlossenen Trades.

Mithilfe einer for-Schleife wird der kumulierte Fortschritt ermittelt, nachdem alle Gewinn- und Verlustzahlen in einem einzigen Array gespeichert wurden. Der Gewinn oder Verlust aus dem aktuellen Trade wird bei jeder Schleifeniteration zu einer fortlaufenden Summenvariablen addiert. Dieser modifizierte kumulierte Wert wird gleichzeitig in einem anderen dynamischen Array gespeichert. Die gesamte Gewinnentwicklungssequenz, in der jeder Index die Kontoperformance nach einem bestimmten Trade darstellt, wird in diesem zweiten Array enthalten sein. Anhand dieses Arrays wird die Saldenkurve im Chart dargestellt. Wir sorgen für eine übersichtliche Datenstruktur und vereinfachen und strukturieren den Prozess der grafischen Darstellung, indem wir die Werte der kumulierten Entwicklung von den Rohgewinnzahlen trennen.

Beispiel:
//+------------------------------------------------------------------+
//| Balance curve function                                           |
//+------------------------------------------------------------------+
void BalanceCurve(string Filename, int &total_deals, double &cumulative[])
  {

   int file_handle = FileOpen(Filename, FILE_READ | FILE_CSV | FILE_ANSI, ',');

   if(file_handle == INVALID_HANDLE)
     {
      Print("Failed to open file. Error: ", GetLastError());
     }

   else
      if(file_handle != INVALID_HANDLE)
        {

         //counting total elements
         int   total_elements_count = 0;
         while(!FileIsEnding(file_handle))
           {
            FileReadString(file_handle);
            total_elements_count++;
           }

         FileSeek(file_handle, 0, SEEK_SET);

         //storing in array
         string TotalElements[];
         ArrayResize(TotalElements,total_elements_count);

         for(int i = 0; i < total_elements_count; i++)
           {

            TotalElements[i] = FileReadString(file_handle);

           }

         //Serching Profit($) header
         int profit_header_index;

         for(int i = 0; i < total_elements_count; i++)
           {

            if(TotalElements[i] == "Profit($)")
              {

               profit_header_index = i;

               break;
              }
           }

         //Comment(profit_header_index);

         //Getting total deals
         int total_deal_index;

         for(int i = 0; i < total_elements_count; i++)
           {

            if(TotalElements[i] == "Total Trades:")
              {

               total_deal_index = i + 1;

               break;
              }
           }

         total_deals = (int)StringToInteger(TotalElements[total_deal_index]);

         // saving all profit/loss in one dynamic array
         double pro_loss[];
         ArrayResize(pro_loss,total_deals);
         int profit_count = 0;

         for(int i = profit_header_index + 12; i < total_elements_count; i += 12)
           {

            pro_loss[profit_count] = StringToDouble(TotalElements[i]);
            profit_count++;
           }

         Print("Profit/Loss:");
         ArrayPrint(pro_loss);

         //saving all progit progression in one dynamic array
         ArrayResize(cumulative,total_deals);
         double running_total = 0.0;

         for(int i = 0; i < total_deals; i++)
           {

            running_total += pro_loss[i];
            cumulative[i] = running_total;

           }

         FileClose(file_handle);

        }
  }
double balance_progression[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {

   BalanceCurve(File_name,Total_deals,balance_progression);
   Print("\nBalance Progression: ");
   ArrayPrint(balance_progression);

//--- create timer
   EventSetTimer(60);

//---
   return(INIT_SUCCEEDED);
  }

Erläuterung:

Alle innerhalb der Funktion „BalanceCurve“ vorgenommenen Änderungen wirken sich sofort auf das außerhalb definierte ursprüngliche Array aus, in diesem Fall „balance_progression[]“, da das Argument für das kumulative Array per Referenz übergeben wird. Da dadurch andere Indikator-Komponenten – wie beispielsweise die Datenüberprüfung oder die Darstellung der Saldenkurve – auf den berechneten kumulativen Gewinnverlauf zugreifen können, ist es entscheidend, das Array per Referenz zu übergeben. Ohne Übergabe per Referenz würde die Funktion lediglich eine lokale Kopie verändern; die Ergebnisse wären außerhalb der Funktion nicht verfügbar.

Die Funktion beginnt damit, die einzelnen Gewinn- und Verlustzahlen in jeweils ein eigenes Array zusammenzufassen. Jeder Wert wird aus der CSV-Datei übernommen, wobei alle Kopfzeileninformationen weggelassen werden, und dieses Array wird an die Gesamtzahl der Trades angepasst. Das Programm sorgt dafür, dass die Werte in der richtigen Reihenfolge bleiben, nachdem jeder Textwert in ein numerisches Format umgewandelt wurde, das für Berechnungen verwendet werden kann. Alle Handelsergebnisse werden nach und nach am Ende des Arrays gespeichert, von wo aus sie zur Fehlersuche oder Überprüfung in das Protokoll ausgegeben werden können.

Nach der Aufbereitung der einzelnen Ergebnisse wird die kumulative Saldenentwicklung berechnet. Dies ist eine kumulierte Übersicht über die Wertentwicklung des Kontos im Zeitverlauf. Es initialisiert eine laufende Summe und passt die Größe des kumulativen Arrays an die Anzahl der Trades an. Die Funktion addiert dann das Ergebnis jedes Trades zur laufenden Summe hinzu und speichert die aktualisierte Summe an der entsprechenden Stelle im kumulativen Array, während sie die Gewinn- und Verlustwerte durchläuft. Dadurch wird die Reihe der Ergebnisse einzelner Trades in eine kumulative Reihe umgewandelt, in der sich der Kontostand nach jedem Trade in jedem Index widerspiegelt.

Das dynamische Array „balance_progression[]“, das am Anfang des Indikators angegeben ist, enthält den kumulativen Saldenverlauf. Das Array, der Name der CSV-Datei und die Variable „total trades“ werden per Referenz übergeben, wenn „OnInit“ die Funktion „BalanceCurve“ aufruft. Dadurch kann die Funktion die laufenden Summen direkt in das Array eintragen, sodass andere Programmkomponenten sofort auf die kumulierten Daten zugreifen können. Um die Richtigkeit der Berechnung zu überprüfen, kann der kumulierte Fortschritt nach Abschluss der Funktion im Protokoll vermerkt werden. Diese Methode schließt die Lücke zwischen Berechnung und Darstellung, indem sie die Rohdaten und die kumulierten Daten voneinander trennt und so eine klare Struktur beibehält. Schließlich können Sie die Kontoentwicklung im Zeitverlauf anzeigen, indem Sie das kumulative Array als Grundlage für die Darstellung der Saldenkurve im Indikatorfenster verwenden.

Ausgabe:

Abbildung 8. Entwicklung des Saldos


Festlegen der Grenzen des Indikatorfensters

Bei der Arbeit mit einem separaten Indikatorfenster in MQL5 ist es unerlässlich, die Grenzen des Indikatorfensters festzulegen. Der Indikator erkennt nicht automatisch, welche Werteskala bei der Darstellung von Daten, wie beispielsweise einer Saldenkurve, angezeigt werden soll. Es könnte schwierig sein, die Ergebnisse visuell zu erfassen, wenn im Chart die Minimal- und Maximalwerte nicht angegeben sind, da die Daten dadurch möglicherweise übermäßig komprimiert oder gestreckt werden. Durch die Festlegung dieser Grenzen können Sie sicherstellen, dass die dargestellte Linie vollständig innerhalb des Indikatorfensters liegt und die Schwankungen in Ihren Daten angemessen wiedergibt.

Der erste Schritt bei der Festlegung der Grenzen besteht darin, den Wertebereich zu ermitteln, den Ihre Daten annehmen können. In diesem Fall befassen wir uns mit der Entwicklung des kumulierten Saldos des Arrays. Der höchste Wert dieses Arrays legt den Höchstwert des Indikatorfensters fest, und sein niedrigster Wert bestimmt dessen Tiefstwert. Spitzen und Tiefpunkte werden präzise dargestellt, wodurch die gesamte Saldenkurve sichtbar wird. Die Minimal- und Maximalwerte lassen sich direkt auf die Merkmale des Indikators anwenden. Dadurch wird dem eigenständigen Fenster mitgeteilt, wie die Y-Achse so skaliert werden soll, dass die dargestellte Linie den verfügbaren Platz vollständig ausfüllt. Durch die Festlegung der Grenzen des Indikatorfensters stellen wir ein übersichtliches, unverzerrtes Chart bereit, das es Händlern erleichtert, die Entwicklung der Performance im Zeitverlauf zu erkennen.

Beispiel:

         //saving all progit progression in one dynamic array
         ArrayResize(cumulative,total_deals);
         double running_total = 0.0;

         for(int i = 0; i < total_deals; i++)
           {

            running_total += pro_loss[i];
            cumulative[i] = running_total;
           }

         //MAX AND MIN Indicator Window
         double max_cumulative  = cumulative[ArrayMaximum(cumulative,0,WHOLE_ARRAY)];
         double min_cumulative  = cumulative[ArrayMinimum(cumulative,0,WHOLE_ARRAY)];

         IndicatorSetDouble(INDICATOR_MAXIMUM, max_cumulative);  // maximum value
         IndicatorSetDouble(INDICATOR_MINIMUM,min_cumulative);  // minimum value

         FileClose(file_handle);

        }
  }

Erläuterung:

Das Programm ermittelt in diesem Abschnitt die Maximal- und Minimalwerte der kumulativen Saldenentwicklung, um den Bereich des Indikatorfensters festzulegen. Dies ist entscheidend, da für die korrekte Darstellung der Saldenkurve ein anderes Indikatorfenster den Datenbereich kennen muss. Der Höchstwert des kumulativen Arrays markiert den Scheitelpunkt der Kurve, während ihr Tiefstwert den Tiefpunkt der Kurve markiert. Der vertikale Bereich, der zur Darstellung der Saldenkurve verwendet wird, wird durch diese Zahlen in ihrer Gesamtheit definiert.

Der berechnete Maximal- und Minimalwert wird dann mithilfe der Kommentarfunktion vorübergehend im Chart angezeigt, wodurch sich deren Richtigkeit vor der Umsetzung überprüfen lässt. Die Funktion „IndicatorSetDouble“ dient dazu, die Maximal- und Minimalwerte festzulegen, um die tatsächlichen Anzeigegrenzen des Indikators zu definieren. Dadurch passt die gesamte Saldenkurve gut in das Fenster, da MetaTrader die Y-Achse entsprechend skalieren kann. Der Indikator stellt die Höchst- und Tiefststände des Kontos durch die Festlegung dieser Grenzen anschaulich dar, was die Visualisierung von Trends, das Erkennen von Mustern und die Bewertung des Handelserfolgs erleichtert.

Ausgabe:

Abbildung 9. Fenstergrenzen


Zeichnen der horizontalen und vertikalen Achsen

Die X- und Y-Achsen müssen sorgfältig festgelegt werden, bevor die Saldenkurve des Indikators aufgezeichnet wird. Dazu müssen sowohl die Gesamtzahl der Trades in der CSV-Datei als auch die typische Anzahl der Kerzen im Kerzenchart berücksichtigt werden. Die Gewinnniveaus entlang der Saldenkurve werden auf der Y-Achse dargestellt, während die X-Achse die Entwicklung der Trades im Zeitverlauf zeigt.

Zunächst ermitteln wir, wie weit wir in der Grafik zurückgehen müssen, um den ersten Trade in der Datei abzubilden und die Saldenkurve korrekt mit den historischen Daten abzugleichen. Um dies zu erreichen, zählen wir von der aktuellen Bar rückwärts, bis wir die Bar erreichen, die den ersten Trade in der Datei repräsentiert. Diese Bar markiert den Schnittpunkt der X- und Y-Achse und damit den Ursprung des Diagramms. Von dort aus wird die X-Achse horizontal gezogen, von der Bar des ersten Trades bis zur aktuellen Bar, und die Y-Achse verläuft vertikal, um die Gewinnentwicklung darzustellen. In vielen realen Situationen kann der Zeitraum zwischen den Trades sehr lang sein, manchmal bis zu zwei Monaten. Daher verwenden wir die Anzahl der Kerzen auf dem Chart anstelle der in der CSV-Datei enthaltenen Handelszeiten. Die dargestellte Kurve könnte falsch komprimiert oder gestreckt sein, wenn wir uns ausschließlich auf die Transaktionszeiten stützen würden.

Das Indikatorfenster ist stets mit dem Hauptchartfenster kompatibel, was ein weiterer Anreiz ist, die Bars im Chart zu verwenden. Das bedeutet, dass zwar beide Fenster gemeinsam scrollen, das Indikatorfenster jedoch nicht über die letzte Bar im Hauptchart hinausscrollen kann. Es muss lediglich sichergestellt sein, dass die Anzahl der Bars im Chart mit der Gesamtzahl der Deals in der CSV-Datei übereinstimmt. Die Verwendung von Kerzencharts anstelle der tatsächlichen Handelszeiten stellt zudem sicher, dass der Indikator in allen Zeitrahmen gut funktioniert. Würden wir die X-Achse anhand der Handelszeiten festlegen, würde ein Wechsel zu einem höheren Zeitrahmen dazu führen, dass die Kurve schmaler wird, wodurch sie schwerer zu lesen und zu interpretieren wäre. Durch die Verwendung der Bars selbst behält die Saldenkurve unabhängig vom Zeitrahmen des Charts ihre korrekten Proportionen und Abstände bei.

Beispiel:
#property indicator_separate_window

string File_name = "Trading_Journal.csv";
int Total_deals;
double balance_progression[];

int last_total_deals = 0;
string y = "Y Axis";
string x = "X Axis";
double max_cumulative;
double min_cumulative;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {

   BalanceCurve(File_name,Total_deals,balance_progression);
// Print("\nBalance Progression: ");
// ArrayPrint(balance_progression);

//--- create timer
   EventSetTimer(60);

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {

//--- destroy timer
   EventKillTimer();
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int32_t rates_total,
                const int32_t prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int32_t &spread[])
  {
//---

   if(rates_total >= Total_deals && Total_deals != last_total_deals)
     {

      //MAX AND MIN Indicator Window
      max_cumulative  = balance_progression[ArrayMaximum(balance_progression,0,WHOLE_ARRAY)];
      min_cumulative  = balance_progression[ArrayMinimum(balance_progression,0,WHOLE_ARRAY)];

      // Creating y and x axis
      ObjectCreate(0,y,OBJ_TREND,1,time[rates_total - Total_deals],0,time[rates_total - Total_deals],max_cumulative);
      ObjectCreate(0,x,OBJ_TREND,1,time[rates_total - Total_deals],0,time[rates_total - 1],0);

      IndicatorSetDouble(INDICATOR_MAXIMUM, max_cumulative);  // maximum value
      IndicatorSetDouble(INDICATOR_MINIMUM,min_cumulative);  // minimum value

      last_total_deals = Total_deals;
     }

   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {

   BalanceCurve(File_name,Total_deals,balance_progression);

  }

Erläuterung:

Nach dem Aufruf der Funktion „BalanceCurve“ mit der CSV-Datei, der Variablen „total trades“ und dem Array „cumulative balance“ werden vom Programm zwei wichtige Informationen abgerufen. Während die kumulierte Gewinnentwicklung berechnet und in dem Array gespeichert wird, das die laufenden Summen enthält, wird die Gesamtzahl der Trades in einer Variablen gespeichert, die die Gesamtzahl der Trades verfolgt. Um die Saldenkurve im Indikatorfenster darzustellen, wird dieses kumulative Array benötigt, das die laufenden Gewinne und Verluste des Kontos für jeden einzelnen Trade anzeigt.

Durch die Ausführung des Codes im OnInit-Handler wird der Indikator sofort nach dem Einfügen in das Chart aktiviert. Dadurch wird sichergestellt, dass die Ausgangskonfiguration korrekt eingerichtet ist, einschließlich der Gesamtzahl der Trades und der kumulierten Gewinne. Anschließend wird OnTimer so eingestellt, dass er alle 60 Sekunden ausgeführt wird, indem mit „EventSetTimer“ ein Timer festgelegt wird. BalanceCurve in OnTimer aktualisiert die Gesamtzahl der Trades und den kumulierten Fortschritt. Der Indikator wird ordnungsgemäß initialisiert und automatisch in vorgegebenen Intervallen aktualisiert, sofern die Funktion sowohl in „OnInit“ als auch in „OnTimer“ aufgerufen wird. EventKillTimer verhindert unnötige Aktualisierungen und schont die Systemressourcen, indem der Timer gestoppt wird, sobald die Anzeige entfernt oder das Terminal geschlossen wird.

Eine Variable im globalen Namensraum speichert die zuletzt bekannte Gesamtzahl der Trades. Eine Bedingung innerhalb des OnCalculate-Handlers stellt sicher, dass der Charting-Code nur dann ausgeführt wird, wenn genügend Bars vorhanden sind, um jeden Trade abzubilden, und wenn sich die Gesamtzahl der Trades seit der letzten Berechnung geändert hat. Um sicherzustellen, dass der Indikator nur bei Bedarf neu berechnet wird, wird die Variable, die die Anzahl der zuletzt getätigten Trades erfasst, nach der Aktualisierung aktualisiert. Diese Methode vermeidet unnötige Berechnungen und steigert die Effizienz.

Der Höchst- und der Tiefstwert des Indikators werden in der Funktion „OnCalculate“ festgelegt, da die Achsenwerte für die Darstellung im Chart benötigt werden. Das Programm ermittelt die größten und kleinsten Werte im kumulativen Array, um den oberen und unteren Rand der Y-Achse zu bestimmen. Von der Kerze des ersten Trades bis zur letzten Kerze im Chart erstreckt sich die X-Achse horizontal, während die Y-Achse bei der Kerze des ersten Trades beginnt und sich bis zum höchsten kumulierten Wert erstreckt. Durch die Verwendung dieser Einschränkungen wird sichergestellt, dass die Saldenkurve vollständig innerhalb des Indikatorfensters liegt und korrekt an den Achsen ausgerichtet ist.

Mit dieser Einstellung wird sichergestellt, dass die Saldenkurve genau in das Chart passt, mit den Kerzen übereinstimmt und nur dann aktualisiert wird, wenn neue Trades getätigt werden. Der Indikator ist in allen Zeitrahmen zuverlässig und gleicht unregelmäßige Handelsintervalle problemlos aus, da er sich an den Positionen die Bar und nicht an Zeitstempeln orientiert. Im Gegensatz zu einem zeitbasierten Ansatz, bei dem sich die Kurve bei einer Änderung des Zeitrahmens verkleinern oder vergrößern würde, gewährleisten Referenzbalken, dass die Kurve ihre korrekten Proportionen und ihre Ausrichtung beibehält.

Ausgabe:

Abbildung 10. X- und Y-Achsen


Einzeichnen der Saldenkurve im Indikator-Chart

Der letzte Schritt im Vorbereitungsprozess besteht darin, die Saldenkurve in das Indikator-Chart einzutragen. Bisher haben wir die X- und Y-Achsen zur Darstellung des Charts festgelegt, die Maximal- und Minimalwerte für das Indikatorfenster bestimmt, die Gewinn- und Verlustdaten aus der Datei eingelesen und die Entwicklung des kumulierten Saldos berechnet. Die Saldenkurve selbst, die zeigt, wie sich der Kontostand im Laufe der Zeit auf der Grundlage vergangener Trades verändert, ist nun zur Darstellung bereit. Wir durchlaufen jeden Wert im Array „kumulativer Gewinn“, um die Saldenkurve zu zeichnen. Wir ordnen jede Zahl einer Bar im Chart zu, die den Kontostand zu einem bestimmten Zeitpunkt widerspiegelt. Anhand der in der Datei angegebenen Gesamtzahl der Trades wird die Position entlang der X-Achse berechnet, indem von der aktuellen Bar aus rückwärtsgezählt wird.

Dadurch wird sichergestellt, dass der jüngste Trade der aktuellen Bar im Chart entspricht und der erste Trade in der Datei dem Startpunkt der Kurve entspricht. Der kumulierte Gewinn zu diesem Zeitpunkt, anhand dessen der Punkt vertikal auf der Grundlage des Kontostands positioniert wird, ist der Wert auf der Y-Achse. Das Programm erstellt die durchgehende Saldenkurve, indem es jeden Punkt im Chart anhand seiner X- und Y-Koordinaten miteinander verbindet.

//+------------------------------------------------------------------+
//| Balance curve function                                           |
//+------------------------------------------------------------------+
void BalanceCurve(string Filename, int &total_deals, double &cumulative[], double &pro_loss[])
  {
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {

   BalanceCurve(File_name,Total_deals,balance_progression,profit_loss);
// Print("\nBalance Progression: ");
// ArrayPrint(balance_progression);

//--- create timer
   EventSetTimer(60);

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {

//--- destroy timer
   EventKillTimer();
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int32_t rates_total,
                const int32_t prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int32_t &spread[])
  {
//---

   if(rates_total >= Total_deals && Total_deals != last_total_deals)
     {
     
    IndicatorSetString(INDICATOR_SHORTNAME, "Balance Curve");
    int indicator_window = ChartWindowFind(ChartID(),"Balance Curve");
    
   // Comment(indicator_window);
    
      ObjectsDeleteAll(ChartID(),indicator_window,OBJ_TREND);

      //MAX AND MIN Indicator Window
      max_cumulative  = balance_progression[ArrayMaximum(balance_progression,0,WHOLE_ARRAY)];
      min_cumulative  = balance_progression[ArrayMinimum(balance_progression,0,WHOLE_ARRAY)];

      // Creating y and x axis
      ObjectCreate(0,y,OBJ_TREND,indicator_window,time[rates_total - Total_deals],0,time[rates_total - Total_deals],max_cumulative);
      ObjectCreate(0,x,OBJ_TREND,indicator_window,time[rates_total - Total_deals],0,time[rates_total - 1],0);

      IndicatorSetDouble(INDICATOR_MAXIMUM, max_cumulative);  // maximum value
      IndicatorSetDouble(INDICATOR_MINIMUM,min_cumulative);  // minimum value

      int c = 0;

      for(int i = rates_total - Total_deals; i <= rates_total - 2; i++)
        {

         chart_lines = StringFormat("Lines %d", c);

         if(ObjectFind(0, chart_lines) == -1)
           {

            ObjectCreate(0,chart_lines,OBJ_TREND,indicator_window,time[i],balance_progression[c],time[i+1],balance_progression[c+1]);

           }
         else
           {
            ObjectMove(0, chart_lines, 0, time[i], balance_progression[c]);
            ObjectMove(0, chart_lines, 1, time[i+1], balance_progression[c+1]);
           }

         p_l = StringFormat("Profit-Loss %d", c);
         if(show_pro == true)
           {
            if(ObjectFind(0, p_l) == -1)
              {
               ObjectCreate(0,p_l,OBJ_TEXT,1,time[i],balance_progression[c]);
              }
            else
              {
               ObjectMove(0, p_l, 0, time[i], balance_progression[c]);

              }

           }
         else
           {
            ObjectDelete(0,p_l);
           }

         if(profit_loss[c] > 0)
           {

            d_sign = "$";

           }

         if(profit_loss[c] <= 0)
           {

            d_sign = "-$";

           }

         ObjectSetString(0,p_l,OBJPROP_TEXT, d_sign + DoubleToString(MathAbs(profit_loss[c]),2));

         c++;

        }

      last_total_deals = Total_deals;
     }

   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {

   BalanceCurve(File_name,Total_deals,balance_progression,profit_loss);
  }

Erläuterung:

Zur Verbesserung der Funktion „BalanceCurve“ wurde ein zusätzlicher Parameter für das Gewinn-Verlust-Array hinzugefügt, der per Referenz übergeben wird. Anstatt eine lokale Kopie zu erstellen, kann die Funktion durch die Übergabe per Referenz direkt auf das ursprünglich außerhalb erzeugte Array zugreifen. Dadurch stehen die Ergebnisse im gesamten Programm zur Verfügung, und die Prozedur kann das Array mit den genauen Gewinn- oder Verlustwerten füllen, die aus der Spalte „Profit($)“ der CSV-Datei extrahiert wurden. Die Funktion bietet nun auch Zugriff auf das jeweilige Ergebnis jedes einzelnen Trades, während sie zuvor lediglich die kumulierte Saldenentwicklung berechnete. Um dies zu ermöglichen, wurde das im globalen Namensraum definierte Gewinn-Verlust-Array in die Funktionsaufrufe in „OnInit“ und „OnTimer“ integriert, wodurch sichergestellt wird, dass sowohl die kumulierten Summen als auch die Ergebnisse der einzelnen Trades im gesamten Programm verfügbar sind.

Die Schleifenvariable steht für die Bars im Chart, und es wird ein Zähler initialisiert, um den Index innerhalb des Arrays für den kumulierten Saldo zu verfolgen. Obwohl sie sich parallel entwickeln, haben diese beiden Indizes unterschiedliche Funktionen: Der eine verfolgt die Trade-Nummer in der Datei, der andere die Kerzenpositionen im Chart.

Bisher sind wir davon ausgegangen, dass der Indikator immer mit dem zweiten Chartfenster verknüpft ist, und haben den festen Unterfenster-Indexwert 1 für die Erstellung grafischer Objekte wie Trendlinien verwendet. Diese Methode ist nur unter dieser bestimmten Voraussetzung wirksam; wird der Indikator verschoben oder werden weitere Indikatoren hinzugefügt, verliert sie an Zuverlässigkeit. Zudem würde das Löschen von Objekten anhand dieses fest codierten Fensterindexes alle Trendlinien aus diesem Unterfenster löschen, falls sich die Anzahl der Trades ändern sollte, was unbeabsichtigt andere Objekte beeinträchtigen könnte.

Wir verwenden nun die Funktion „IndicatorSetString()“, um den Kurznamen des Indikators festzulegen, und die Funktion „ChartWindowFind()“, um den tatsächlichen Index des Unterfensters zur Laufzeit abzurufen, damit die Lösung dynamisch und zuverlässig ist. Wir können diesen Wert beim Hinzufügen oder Entfernen von Objekten konsistent verwenden, indem wir ihn in einer Variablen wie „indicator_window“ speichern. Dadurch wird sichergestellt, dass die Objektverwaltung unabhängig von der Chart-Konfiguration im entsprechenden Fenster erfolgt, und es muss nicht mehr davon ausgegangen werden, dass sich der Indikator immer in Fenster 1 befindet.

Um jeden Trade innerhalb des Chartbereichs darzustellen, verbindet die Zeichnungsschleife jedes Paares aufeinanderfolgender Bars – von der Bar des ersten Handels bis zur vorletzten Bar. Der Handelsindex dient dazu, jedem Abschnitt der Saldenkurve eine eigene Bezeichnung zu geben. Die Existenz der Trendlinie wird mithilfe von ObjectFind bestätigt. ObjectCreate zeichnet den Bereich zwischen zwei aufeinanderfolgenden kumulativen Saldenpunkten, falls dies noch nicht geschehen ist. Um die Leistung zu verbessern und eine dynamische Aktualisierung der Saldenkurve mit neuen Daten oder bei der Aktualisierung von Charts zu ermöglichen, verschiebt ObjectMove die Linie, sofern vorhanden.

Ebenso wird für das Textobjekt, das den Gewinn oder Verlust jedes Trades anzeigt, ein eigener Name erstellt. Ob diese Textbeschriftungen angezeigt werden, hängt von der Variablen „show_pro“ ab, die die Sichtbarkeit steuert. Wenn diese Option aktiviert ist, überprüft das Programm „ “ ( ), ob das Objekt vorhanden ist, und verschiebt entweder das aktuelle Objekt an den aktualisierten Speicherort oder generiert ein neues OBJ_TEXT-Objekt auf der entsprechenden Bar und auf dem entsprechenden kumulierten Saldenniveau. Um ein übersichtliches Chart mit intakter Saldenkurve zu gewährleisten, entfernt ObjectDelete die Textobjekte, wenn show_pro auf „false“ gesetzt ist.

Jeder Gewinn- oder Verlustbetrag wird vom Computer so aufbereitet, dass das entsprechende Vorzeichen ermittelt und etwaige Minuszeichen mithilfe von MathAbs aus dem Zahlenwert entfernt werden. Der formatierte Text wird dem Objekt anschließend mithilfe von ObjectSetString zugewiesen, wodurch eine übersichtliche Darstellung wie „$25,50“ für einen Gewinn oder „-$12,30“ für einen Verlust entsteht. Um sicherzustellen, dass die nachfolgende Iteration den nächsten Trade verarbeitet, wird der Handelsindexzähler abschließend erhöht. Dank dieser Logik kann der Indikator korrekt formatierte Gewinn- und Verlustbeschriftungen dynamisch ein- oder ausblenden, die genau auf jeden Punkt der Saldenkurve abgestimmt sind.

Ausgabe:

Abbildung 11. Saldenkurve


Schlussfolgerung

In diesem Artikel haben Sie gelernt, wie man eine CSV-Datei mit dem Handelsverlauf in MQL5 einliest und wichtige Daten wie „Profit($)“ und die Gesamtzahl der Trades extrahiert. Außerdem haben Sie gelernt, wie Sie die benötigte Datei im entsprechenden Ordner ablegen. Mittlerweile wissen Sie, wie man die kumulative Saldenentwicklung berechnet und damit die Maximal- und Minimalwerte für das Indikatorfenster ermittelt. Das Zeichnen einer kontinuierlichen Saldenkurve, das Erstellen und Ausrichten der horizontalen und vertikalen Achsen sowie die optionale Anzeige von Gewinn- und Verlustangaben für jeden Trade gehörten zu den Fähigkeiten, die Sie erworben haben. Außerdem haben Sie gelernt, wie Sie sicherstellen können, dass die Kurve nur dann neu gezeichnet wird, wenn neue Trades eingehen, und wie Sie den Indikator mithilfe der Ereignisbehandler „OnInit“ und „OnTimer“ dynamisch aktualisieren können. Alles in allem haben Sie gelernt, wie Sie mit MetaTrader 5 rohe CSV-Handelsdaten in ein aussagekräftiges, visuelles Chart umwandeln können, mit dem Sie die Kontoperformance, Drawdowns und die Beiträge einzelner Transaktionen direkt im Programm analysieren können.

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

Beigefügte Dateien |
Letzte Kommentare | Zur Diskussion im Händlerforum (2)
Oluwatosin Mary Babalola
Oluwatosin Mary Babalola | 1 März 2026 in 07:40
Wow, du bist ein toller Lehrer. Ich werde nie aufhören, deine Artikel zu lesen.
ALGOYIN LTD
Israel Pelumi Abioye | 1 März 2026 in 07:43
Oluwatosin Mary Babalola #:
Wow, du bist ein toller Lehrer. Ich werde nie aufhören, deine Artikel zu lesen
Hallo Oluwatosin.

Vielen Dank für deine freundlichen Worte.
Entwicklung eines Toolkits zur Price-Action-Analyse (Teil 62): Entwicklung eines adaptiven Systems in MQL5 zur Erkennung paralleler Kanäle und den Ausbrüchen. Entwicklung eines Toolkits zur Price-Action-Analyse (Teil 62): Entwicklung eines adaptiven Systems in MQL5 zur Erkennung paralleler Kanäle und den Ausbrüchen.
Dieser Artikel stellt ein adaptives System zur Erkennung paralleler Kanäle und den Ausbrüchen daraus in MQL5 vor. Darin wird erläutert, wie Swing-Punkte ermittelt, Kanäle konstruiert und dynamisch neu berechnet werden und wie Ausbrüche durch persistente Signale bestätigt und visualisiert werden. Das Framework vereint Trendliniengeometrie, ATR-basierte Filterung und Retest-Validierung, um eine zuverlässige Kursanalyse in Echtzeit für professionelle Chartanalysen und Handelsentscheidungen zu ermöglichen.
Trading-Disziplin in Code umsetzen (Teil 2): Entwicklung eines MQL5-Systems zur Durchsetzung eines täglichen Handelslimits für alle Trades in MQL5 Trading-Disziplin in Code umsetzen (Teil 2): Entwicklung eines MQL5-Systems zur Durchsetzung eines täglichen Handelslimits für alle Trades in MQL5
Wir haben ein System entwickelt, das ein tägliches Handelslimit durchsetzt, damit Sie Ihre Handelsregeln einhalten können. Es überwacht alle über das Konto ausgeführten Trades und greift automatisch ein, sobald das festgelegte Limit erreicht ist, wodurch weitere Aktivitäten verhindert werden. Durch die direkte Integration der Kontrollmechanismen in die Plattform stellt das System sicher, dass die Disziplin auch bei steigendem Marktdruck gewahrt bleibt.
Larry Williams’ Marktgeheimnisse (Teil 13): Automatisierung von Hidden-Smash-Day-Umkehrmustern Larry Williams’ Marktgeheimnisse (Teil 13): Automatisierung von Hidden-Smash-Day-Umkehrmustern
Der Artikel beschreibt die Erstellung eines transparenten MQL5-Expert-Advisors für die Hidden-Smash-Day-Reversals von Larry Williams. Signale werden nur bei neuen Bars generiert: Eine Setup-Bar wird validiert und anschließend bestätigt, wenn die nächste Sitzung jenseits des Extremums schließt. Das Risiko wird über ATR oder strukturelle Stopps mit einem festgelegten Risiko-Ertrags-Verhältnis gesteuert; die Positionsgröße kann fest oder vom Kontostand abhängig sein, und Richtungsfilter sowie eine Ein-Positions-Regel gewährleisten reproduzierbare Tests.
Einführung in MQL5 (Teil 41): Leitfaden für Anfänger zur Dateiverwaltung in MQL5 (III) Einführung in MQL5 (Teil 41): Leitfaden für Anfänger zur Dateiverwaltung in MQL5 (III)
Erfahren Sie, wie Sie eine CSV-Datei in MQL5 einlesen und deren Handelsdaten in dynamischen Arrays organisieren. Dieser Artikel zeigt Schritt für Schritt, wie man Dateielemente zählt, alle Daten in einem einzigen Array speichert und jede Spalte in eigene Arrays aufteilt, wodurch die Grundlage für fortgeschrittene Analysen und die Visualisierung der Handelsperformanz geschaffen wird.