English 日本語
preview
MQL5 Handels-Toolkit (Teil 4): Entwicklung einer EX5-Bibliothek zur Verwaltung der Handelsgeschichte

MQL5 Handels-Toolkit (Teil 4): Entwicklung einer EX5-Bibliothek zur Verwaltung der Handelsgeschichte

MetaTrader 5Beispiele | 15 April 2025, 07:29
80 0
Wanateki Solutions LTD
Kelvin Muturi Muigua

Einführung

In dieser spannenden Artikelserie haben wir zwei umfassende EX5-Bibliotheken entwickelt: PositionsManager.ex5 für die Verarbeitung und Verwaltung von Positionen und PendingOrdersManager.ex5 zur Verwaltung von schwebenden Aufträgen. Daneben haben wir praktische Beispiele erstellt, darunter einige mit grafischen Nutzeroberflächen, um die Implementierung dieser Bibliotheken effektiv zu demonstrieren.

In diesem Artikel stellen wir Ihnen eine weitere wichtige EX5-Bibliothek vor, die dazu dient, die Historie von geschlossenen Aufträgen, Deals, den sogenannten Deals, und Positionstransaktionen abzurufen und zu verarbeiten. Darüber hinaus werden wir Analysemodule entwickeln, um Handelsberichte zu erstellen, die die Leistung von Handelssystemen, Expert Advisors oder bestimmten Symbolen anhand verschiedener flexibler Kriterien bewerten.

Dieser Artikel dient als praktischer Leitfaden für MQL5-Anfänger, für die die Arbeit mit Positionen, Aufträgen und Deals eine Herausforderung darstellen kann. Es ist auch eine wertvolle Ressource für jeden MQL5-Programmierer, der eine Bibliothek zur Rationalisierung und Verbesserung der Effizienz bei der Handhabung der Handelsgeschichte sucht.

Zu Beginn werden wir einige kritische Fragen ansprechen, die viele MQL5-Programmierer, insbesondere diejenigen, die neu in der Verarbeitung von Handelsverläufen in MetaTrader 5 sind, oft nur schwer verstehen können.


Was ist der Lebenszyklus einer Handelstransaktion in MQL5?

In MQL5 beginnt der Lebenszyklus eines Handelsgeschäfts mit der Ausführung eines Auftrags. Dieser Auftrag kann in zwei Haupttypen unterteilt werden: ein direkter Marktauftrag oder eine schwebende Order.

Direkte Eingabe von Marktaufträgen

Ein direkter Marktauftrag ist eine Echtzeitanfrage zum Kauf oder Verkauf eines Vermögenswerts zum aktuellen Marktpreis (Ask oder Bid). Wie man diese Aufträge bearbeitet, haben wir bereits im ersten und zweiten Artikel bei der Entwicklung der Positionsmanager-Bibliothek behandelt.

Auftrag für einen direkten Markteintritt

Ein direkter Marktauftrag wird sofort ausgeführt und ist daher ideal für manuelle und automatisierte Handelsstrategien. Nach der Ausführung wird der Auftrag in eine aktive offene Position umgewandelt und erhält eine eindeutige Ticket-Nummer und eine separate Positionskennung, POSITION_ID, zugewiesen, die eine zuverlässigere Verfolgung und Verwaltung der verschiedenen Phasen der Position während ihres Lebenszyklus ermöglicht.

Platzieren eines schwebenden Auftrags

Ein schwebender Auftrag (BUY STOP, BUY LIMIT, SELL STOP, SELL LIMIT, BUY STOP LIMIT und SELL STOP LIMIT) ist dagegen ein verzögerter Auftrag, der bei Erreichen eines bestimmten Kursniveaus ausgelöst wird. Eine ausführliche Anleitung zur Bearbeitung dieser Arten von Aufträgen finden Sie im dritten Artikel dieser Reihe, in dem wir die Bibliothek „PendingOrdersManager.ex5“ entwickelt haben.

Erteilen eines schwebende Auftrags

Solange sich der Marktpreis nicht an den vordefinierten Auslösepreis für den schwebende Auftrag berührt, bleibt der schwebende Auftrag inaktiv. Sobald er ausgelöst wurde, wird er in einen Marktauftrag umgewandelt und ausgeführt, wobei er eine eindeutige Ticketnummer und Positionskennung (POSITION_ID ) erhält, ähnlich wie ein direkter Marktauftrag.


Wie kann sich der Status einer Position während ihrer Lebensdauer ändern?

Im Laufe der Lebensdauer einer Stelle kann sich ihr Status aufgrund verschiedener Faktoren ändern:

  • Partielles Schließen: Wenn ein Teil der Position geschlossen wird, wird ein entsprechendes Exit-Deal (Out) in der Handelshistorie aufgezeichnet.
  • Umkehr der Position: Eine Positionsumkehr, durch z. B. eine „Close by“-Transaktion, wird ebenfalls als Exit-Deal erfasst.
  • Vollständiges Schließen: Wenn die gesamte Position geschlossen wird, entweder manuell oder automatisch durch Take-Profit, Stop-Loss oder ein Stop-Out aufgrund eines Margin-Calls, wird ein endgültiges Exit-Deal in der Handelshistorie aufgezeichnet.

Das Verständnis des Lebenszyklus einer Handelsoperation in MQL5 ist entscheidend. Jeder Handel beginnt mit einem an den Handelsserver gesendeten Auftrag, sei es eine Anfrage zur Eröffnung eines schwebenden Auftrags, zur Ausführung eines direkten Marktauftrags zum Kauf oder Verkauf oder zur teilweisen Schließung einer bestehenden Position. Unabhängig von der Art werden alle Handelsvorgänge zunächst als Aufträge erfasst.

Wenn der Auftrag erfolgreich ausgeführt wurde, geht er in die nächste Phase über und wird in der Datenbank als Deal gespeichert. Mit Hilfe der verschiedenen Eigenschaften und Funktionen, die für Aufträge und Deals zur Verfügung stehen, können Sie jeden Deal zu seinem Ursprungsauftrag zurückverfolgen und mit der entsprechenden Position verknüpfen. Auf diese Weise lässt sich der Lebenszyklus eines Handelsgeschäfts klar und systematisch verfolgen.

Dieser „Brotkrumen“-Ansatz ermöglicht es Ihnen, den Ursprung, den Verlauf und das Ergebnis jedes Handels oder jeder Transaktion in der MQL5-Umgebung zu verfolgen. Er bietet einen detaillierten Prüfpfad, einschließlich des Auftrags, der die Transaktion ausgelöst hat, des genauen Ausführungszeitpunkts, aller während des Vorgangs vorgenommenen Änderungen und des Endergebnisses des Handels (Position). Ein solches Tracking erhöht nicht nur die Transparenz, sondern ermöglicht Ihnen als MQL5-Programmierer auch die Entwicklung von Algorithmen zur Analyse von Handelsstrategien, zur Ermittlung von Verbesserungsmöglichkeiten und zur Optimierung der Leistung.


Was ist der Unterschied zwischen einer Position und einem Handelsgeschäft in MQL5?

In MQL5 stellt eine Position einen aktiven laufenden Handel (Position) dar, den Sie derzeit auf dem Markt halten. Er befindet sich in einem offenen Zustand, der entweder eine Kauf- oder Verkaufsposition für ein bestimmtes Symbol widerspiegelt. Ein Handelsgeschäft hingegen bezieht sich auf eine abgeschlossene Transaktion - wenn eine Position vollständig geschlossen wurde. Aktive offene Positionen und schwebende Aufträge werden im Fenster Werkzeuge des MetaTrader 5 unter der Registerkarte „Handel“ (engl. Trade) angezeigt.

MetaTrader 5 Toolbox Registerkarte „Trades“

Geschlossene Positionen (Handelsgeschäfte) werden zusammen mit Aufträgen und Deals in der „Kontohistorie“ des Werkzeug-Fensters angezeigt.

MetaTrader 5 Werkzeug Registerkarte Historie

Um auf die vollständige Historie der Positionen zuzugreifen, können Sie die Menüoptionen der Plattform nutzen und die Option „Positionen“ auswählen. Über die gleichen Menüoptionen können Sie auch auf die Historie der Orders und Deals zugreifen.

MetaTrader 5 Toolbox Registerkarte „Historie“ - Auswahl der Positionshistorie

Die Unterscheidung zwischen Positionen und Handelsgeschäfte (Trades) kann für Anfänger in der MQL5-Programmierung verwirrend sein, insbesondere bei der Verwendung der Standard-Historienfunktionen der Plattform. Dieser Artikel und der detaillierte Code in der Bibliothek, die wir gleich erstellen werden, vermitteln Ihnen ein klares Verständnis dafür, wie Positionen und Handelsgeschäfte in MQL5 kategorisiert und verfolgt werden. Wenn Sie wenig Zeit haben und eine fertige History-Bibliothek benötigen, können Sie einfach der ausführlichen Dokumentation im nächsten Artikel folgen, wie Sie sie direkt in Ihr Projekt implementieren können.


Erstellen der Quellcodedatei der History Manager Library (.mq5)

Um zu beginnen, öffnen Sie Ihre MetaEditor-IDE und rufen Sie den MQL-Assistenten auf, indem Sie „New“ aus dem Menü wählen. Wählen Sie im Assistenten die Option zum Erstellen einer neuen Bibliothek, die wir HistoryManager.mq5 nennen werden. Diese Datei bildet die Grundlage für unsere Kernfunktionen, die sich mit der Verwaltung und Analyse der Handelshistorie der Konten befassen. Wenn Sie die neue Bibliothek HistoryManager.mq5 erstellen, speichern Sie sie in dem Ordner Libraries\Toolkit, den wir im ersten Artikel erstellt haben. Indem wir diese neue Datei im gleichen Verzeichnis wie die EX5-Bibliotheken Positionsmanager und Manager für schwebende Aufträge speichern, erhalten wir eine klare und konsistente Organisationsstruktur für unser Projekt. Dieser Ansatz wird es einfacher machen, jede Komponente zu finden und zu verwalten, wenn unser Toolkit erweitert wird.

MetaEditor Navigator Bibliotheks-Toolkit-Verzeichnis

So sieht unsere neu erstellte Quelldatei HistoryManager.mq5 aus. Beginnen Sie mit dem Löschen der „My function“-Kommentare, die sich unter den Eigenschaftsanweisungen befinden. Die Copyright- und Link-Eigenschaftsanweisungen in Ihrer Datei können sich unterscheiden, aber das hat keinen Einfluss auf das Verhalten oder die Leistung des Codes. Sie können die Copyright- und Link-Direktiven mit den von Ihnen gewünschten Informationen anpassen, aber stellen Sie sicher, dass die Bibliothekseigenschafts-Direktive unverändert bleibt.

//+------------------------------------------------------------------+
//|                                               HistoryManager.mq5 |
//|                          Copyright 2024, Wanateki Solutions Ltd. |
//|                                         https://www.wanateki.com |
//+------------------------------------------------------------------+
#property library
#property copyright "Copyright 2024, Wanateki Solutions Ltd."
#property link      "https://www.wanateki.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| My function                                                      |
//+------------------------------------------------------------------+
// int MyCalculator(int value,int value2) export
//   {
//    return(value+value2);
//   }
//+------------------------------------------------------------------+


Datenstrukturen, Präprozessoranweisungen und globale Variablen

In der neu erstellten Quelldatei der Bibliothek HistoryManager.mq5 werden wir zunächst die folgenden Komponenten definieren:

  • Präprozessor-Direktiven: Diese helfen bei der Sortierung und Abfrage verschiedener Arten von Handelsdaten.
  • Daten-Strukturen: Hier werden Verlaufsdaten für Aufträge, Deals, Positionen und schwebende Aufträge gespeichert.
  • Globale dynamische Strukturmatrizen: Diese enthalten alle relevanten historischen Daten in der Bibliothek.

Durch die Definition dieser Komponenten im globalen Bereich wird sichergestellt, dass sie in der gesamten Bibliothek zugänglich sind und von allen verschiedenen Modulen oder Funktionen innerhalb der Bibliothek genutzt werden können.

Präprozessor-Direktiven

Da unsere Bibliothek zur Verwaltung des Verlaufs verschiedene Arten von Anfragen bearbeiten wird, ist es wichtig, sie so zu gestalten, dass nur die spezifischen Verlaufsdaten abgerufen werden, die für jede Anfrage benötigt werden. Dieser modulare und gezielte Ansatz wird die Leistung unserer Bibliothek verbessern und gleichzeitig die Flexibilität für verschiedene Anwendungsfälle erhalten.

Um dies zu erreichen, werden wir ganzzahlige Konstanten definieren, die als Bezeichner für bestimmte Arten von historischen Daten dienen. Diese Konstanten ermöglichen es der Bibliothek, nur die benötigten Daten zu erfassen, was einen minimalen Ressourcenverbrauch und eine schnellere Verarbeitung gewährleistet.

Wir werden die historischen Daten in fünf Hauptkategorien einteilen:

  1. Orders History.
  2. Deals History.
  3. Positions History.
  4. Pending Orders History.
  5. All History Data.

Durch die Verwendung dieser Konstanten können die Funktionen innerhalb der Bibliothek die Art der Historie angeben, die sie verarbeiten wollen. Die Hauptfunktion zum Abrufen der Historie fragt nur die angeforderten Daten ab und gibt sie zurück, was Zeit und Rechenressourcen spart. Beginnen wir mit der Definition dieser Integer-Konstanten, indem wir sie direkt unter die letzte #Eigenschaft Direktiven in unserem Code platzieren.

#define GET_ORDERS_HISTORY_DATA 1001
#define GET_DEALS_HISTORY_DATA 1002
#define GET_POSITIONS_HISTORY_DATA 1003
#define GET_PENDING_ORDERS_HISTORY_DATA 1004
#define GET_ALL_HISTORY_DATA 1005

Daten-Strukturen

Unsere EX5-Bibliothek speichert verschiedene Verlaufsdaten in global deklarierten Datenstrukturen. In diesen Strukturen werden die Deals, Aufträge, Positionen und die Historie der schwebenden Aufträge effizient gespeichert, wenn sie abgefragt werden.

//- Data structure to store deal properties
struct DealData
  {
   ulong             ticket;
   ulong             magic;
   ENUM_DEAL_ENTRY   entry;
   ENUM_DEAL_TYPE    type;
   ENUM_DEAL_REASON  reason;
   ulong             positionId;
   ulong             order;
   string            symbol;
   string            comment;
   double            volume;
   double            price;
   datetime          time;
   double            tpPrice;
   double            slPrice;
   double            commission;
   double            swap;
   double            profit;
  };

//- Data structure to store order properties
struct OrderData
  {
   datetime                timeSetup;
   datetime                timeDone;
   datetime                expirationTime;
   ulong                   ticket;
   ulong                   magic;
   ENUM_ORDER_REASON       reason;
   ENUM_ORDER_TYPE         type;
   ENUM_ORDER_TYPE_FILLING typeFilling;
   ENUM_ORDER_STATE        state;
   ENUM_ORDER_TYPE_TIME    typeTime;
   ulong                   positionId;
   ulong                   positionById;
   string                  symbol;
   string                  comment;
   double                  volumeInitial;
   double                  priceOpen;
   double                  priceStopLimit;
   double                  tpPrice;
   double                  slPrice;
  };

//- Data structure to store closed position/trade properties
struct PositionData
  {
   ENUM_POSITION_TYPE type;
   ulong              ticket;
   ENUM_ORDER_TYPE    initiatingOrderType;
   ulong              positionId;
   bool               initiatedByPendingOrder;
   ulong              openingOrderTicket;
   ulong              openingDealTicket;
   ulong              closingDealTicket;
   string             symbol;
   double             volume;
   double             openPrice;
   double             closePrice;
   datetime           openTime;
   datetime           closeTime;
   long               duration;
   double             commission;
   double             swap;
   double             profit;
   double             tpPrice;
   double             slPrice;
   int                tpPips;
   int                slPips;
   int                pipProfit;
   double             netProfit;
   ulong              magic;
   string             comment;
  };

//- Data structure to store executed or canceled pending order properties
struct PendingOrderData
  {
   string                  symbol;
   ENUM_ORDER_TYPE         type;
   ENUM_ORDER_STATE        state;
   double                  priceOpen;
   double                  tpPrice;
   double                  slPrice;
   int                     tpPips;
   int                     slPips;
   ulong                   positionId;
   ulong                   ticket;
   datetime                timeSetup;
   datetime                expirationTime;
   datetime                timeDone;
   ENUM_ORDER_TYPE_TIME    typeTime;
   ulong                   magic;
   ENUM_ORDER_REASON       reason;
   ENUM_ORDER_TYPE_FILLING typeFilling;
   string                  comment;
   double                  volumeInitial;
   double                  priceStopLimit;
  };

Globale dynamische Struktur-Arrays

Die endgültigen Deklarationen im globalen Bereich bestehen aus den dynamischen Datenstruktur-Arrays der zuvor definierten Strukturen. Diese Arrays dienen als Hauptspeicher für alle von unserer Bibliothek verwalteten Kerndaten.

OrderData orderInfo[];
DealData dealInfo[];
PositionData positionInfo[];
PendingOrderData pendingOrderInfo[];


Die Funktion GetHistoryDataFunction

Die Funktion GetHistoryDataFunction() ist das Herzstück unserer EX5-Bibliothek und bildet das Rückgrat ihrer Funktionsweise. Die meisten anderen Funktionen in der Bibliothek hängen von ihr ab, um die Handelshistorie auf der Grundlage bestimmter Zeiträume und Historientypen abzurufen. Da diese Funktion nur für den internen Gebrauch gedacht ist, wird sie nicht als exportierbar definiert.

Diese Funktion dient dazu, die angeforderten Verlaufsdaten für einen bestimmten Zeitraum und eine bestimmte Art von Verlauf zu holen. Es handelt sich um eine Funktion vom Typ bool , d. h. sie gibt true zurück, wenn der Verlauf erfolgreich abgerufen wurde, und false , wenn der Vorgang fehlgeschlagen ist.

Die Funktion GetHistoryDataFunction() übernimmt drei Eingabeparameter:

  1. Zwei Datetime-Variablen, fromDateTime und toDateTime, die den Beginn und das Ende des gewünschten Zeitraums angeben.
  2. Eine Ganzzahl ohne Vorzeichen, dataToGet, die einer der vordefinierten Konstanten am Anfang der Datei entspricht.

Durch die Kombination dieser Eingaben kann die Funktion die erforderlichen Verlaufsdaten effizient abfragen und verarbeiten. Wir beginnen mit der Definition der Funktion.

bool GetHistoryData(datetime fromDateTime, datetime toDateTime, uint dataToGet)
  {
   return(true);

//-- Our function's code will go here

  }

Die erste Aufgabe unserer Funktion besteht darin, zu überprüfen, ob der angegebene Datumsbereich gültig ist. Da der Datentyp datetime in MQL5 im Wesentlichen eine Ganzzahl vom Typ long ist, die die Zeit im Unix-Epochenformat darstellt (d. h. die Anzahl der seit dem 1. Januar 1970, 00:00:00 UTC, verstrichenen Sekunden), können wir diese Werte direkt vergleichen, um ihre Korrektheit sicherzustellen. Beachten Sie auch, dass bei der Abfrage von Verlaufsdaten in MQL5 die Zeit auf der Zeit des Handelsservers basiert, nicht auf der Ihres lokalen Rechners.

Um den Datumsbereich zu validieren, prüfen wir, ob der Wert fromDateTime kleiner ist als der von toDateTime. Wenn fromDateTime größer oder gleich toDateTime ist, weist dies auf einen ungültigen Zeitraum hin, da das Startdatum nicht später oder gleich dem Enddatum sein kann. Wenn der angegebene Zeitraum nicht validiert werden kann, geben wir false zurück und beenden die Funktion.

if(fromDateTime >= toDateTime)
     {
      //- Invalid time period selected
      Print("Invalid time period provided. Can't load history!");
      return(false);
     }

Sobald die Daten und der Zeitraum verifiziert sind, setzen wir den Fehler-Cache von MQL5 zurück, um genaue Fehlercodes sicherzustellen, falls Probleme auftreten. Als Nächstes rufen wir die Funktion HistorySelect() innerhalb eines if-else auf und übergeben die validierten Datumswerte, um die Historie der Deals und Aufträge für den angegebenen Zeitraum abzurufen. Da HistorySelect() einen booleschen Wert zurückgibt, wird true zurückgegeben, wenn die zu verarbeitende Historie gefunden wurde, oder false , wenn ein Fehler auftritt oder die Daten nicht abgerufen werden können.

ResetLastError();
if(HistorySelect(fromDateTime, toDateTime)) //- History selected ok
  {
//-- Code to process the history data will go here
  }
else //- History selecting failed
  {
   Print("Selecting the history failed. Error code = ", GetLastError());
   return(false);
  }

Im else-Teil von if-else haben wir Code hinzugefügt, um eine Meldung zu protokollieren, die angibt, dass die Verlaufsauswahl fehlgeschlagen ist, zusammen mit dem Fehlercode, bevor die Funktion beendet wird und der boolesche Wert false zurückgegeben wird. Im if-Teil werden wir eine switch-Anweisung verwenden, um die entsprechenden Funktionen für die Verarbeitung der geladenen Handelsverlaufsdaten auf der Grundlage des Werts von dataToGet aufzurufen.

switch(dataToGet)
  {
   case GET_DEALS_HISTORY_DATA: //- Get and save only the deals history data
      SaveDealsData();
      break;

   case GET_ORDERS_HISTORY_DATA: //- Get and save only the orders history data
      SaveOrdersData();
      break;

   case GET_POSITIONS_HISTORY_DATA: //- Get and save only the positions history data
      SaveDealsData();  //- Needed to generate the positions history data
      SaveOrdersData(); //- Needed to generate the positions history data
      SavePositionsData();
      break;

   case GET_PENDING_ORDERS_HISTORY_DATA: //- Get and save only the pending orders history data
      SaveOrdersData(); //- Needed to generate the pending orders history data
      SavePendingOrdersData();
      break;

   case GET_ALL_HISTORY_DATA: //- Get and save all the history data
      SaveDealsData();
      SaveOrdersData();
      SavePositionsData();
      SavePendingOrdersData();
      break;

   default: //-- Unknown entry
      Print("-----------------------------------------------------------------------------------------");
      Print(__FUNCTION__, ": Can't fetch the historical data you need.");
      Print("*** Please specify the historical data you need in the (dataToGet) parameter.");
      break;
  }

Hier ist die vollständige GetHistoryDataFunction() mit allen enthaltenen Codesegmenten.

bool GetHistoryData(datetime fromDateTime, datetime toDateTime, uint dataToGet)
  {
//- Check if the provided period of dates are valid
   if(fromDateTime >= toDateTime)
     {
      //- Invalid time period selected
      Print("Invalid time period provided. Can't load history!");
      return(false);
     }

//- Reset last error and get the history
   ResetLastError();
   if(HistorySelect(fromDateTime, toDateTime)) //- History selected ok
     {
      //- Get the history data
      switch(dataToGet)
        {
         case GET_DEALS_HISTORY_DATA: //- Get and save only the deals history data
            SaveDealsData();
            break;

         case GET_ORDERS_HISTORY_DATA: //- Get and save only the orders history data
            SaveOrdersData();
            break;

         case GET_POSITIONS_HISTORY_DATA: //- Get and save only the positions history data
            SaveDealsData();  //- Needed to generate the positions history data
            SaveOrdersData(); //- Needed to generate the positions history data
            SavePositionsData();
            break;

         case GET_PENDING_ORDERS_HISTORY_DATA: //- Get and save only the pending orders history data
            SaveOrdersData(); //- Needed to generate the pending orders history data
            SavePendingOrdersData();
            break;

         case GET_ALL_HISTORY_DATA: //- Get and save all the history data
            SaveDealsData();
            SaveOrdersData();
            SavePositionsData();
            SavePendingOrdersData();
            break;

         default: //-- Unknown entry
            Print("-----------------------------------------------------------------------------------------");
            Print(__FUNCTION__, ": Can't fetch the historical data you need.");
            Print("*** Please specify the historical data you need in the (dataToGet) parameter.");
            break;
        }
     }
   else
     {
      Print(__FUNCTION__, ": Selecting the history failed. Error code = ", GetLastError());
      return(false);
     }
   return(true);
  }

Wenn Sie unsere Quellcodedatei in diesem Stadium speichern und versuchen zu kompilieren, werden Sie zahlreiche Kompilierfehler und Warnungen erhalten. Der Grund dafür ist, dass viele der Funktionen, auf die im Code verwiesen wird, noch nicht erstellt worden sind. Da wir uns noch im Anfangsstadium der Entwicklung unserer EX5-Bibliothek befinden, wird die EX5-Bibliotheksdatei, sobald alle fehlenden Funktionen implementiert sind, ohne Fehler oder Warnungen kompiliert.


Funktion zum Speichern von Abrechnungsdaten

Die Funktion SaveDealsData() ist für das Abrufen und Speichern aller derzeit im Cache der Handelshistorie verfügbaren Deals für die verschiedenen Zeiträume zuständig, die von verschiedenen Funktionen in der Bibliothek angefordert werden. Sie gibt keine Daten zurück und ist nicht als exportierbar definiert, da sie intern in der Bibliothek aufgerufen wird, insbesondere von der Funktion GetHistoryData(). Diese Funktion verwendet die MQL5-Standardfunktionen HistoryDealGet..., um die verschiedenen Deal-Eigenschaften abzurufen und sie im Array der dynamischen Datenstruktur dealInfo zu speichern.

Beginnen wir damit, die Funktionsdefinition oder Signatur zu erstellen.

void SaveDealsData()
  {

//-- Our function's code will go here

  }

Da SaveDealsData() innerhalb der Funktion GetHistoryData() aufgerufen wird, ist es nicht erforderlich, HistorySelect() erneut aufzurufen, bevor die Handelshistorie verarbeitet wird. Der erste Schritt in der Funktion SaveDealsData() besteht darin, zu prüfen, ob eine Deal-Historie vorhanden ist, die verarbeitet werden kann. Dies geschieht mit Hilfe der Funktion HistoryDealsTotal(), die die Gesamtzahl der im History-Cache verfügbaren Angebote zurückgibt. Aus Gründen der Effizienz erstellen wir eine Ganzzahl mit dem Namen totalDeals, um die Gesamtzahl der historischen Deals zu speichern, und eine unsigned long mit dem Namen dealTicket, um die Bezeichner der Dealtickets zu speichern.

int totalDeals = HistoryDealsTotal();
ulong dealTicket;

Wenn keine Angebote verfügbar sind oder gefunden werden (totalDeals ist kleiner gleich 0), wird eine entsprechende Meldung protokolliert, und die Funktion wird vorzeitig beendet, um unnötige Verarbeitung zu vermeiden.

if(totalDeals > 0)
  {
//-- Code to process deal goes here
  }
else
  {
   Print(__FUNCTION__, ": No deals available to be processed, totalDeals = ", totalDeals);
  }

Wenn es einen Deal-Historie gibt, besteht der nächste Schritt darin, ein Array zum Speichern der abgerufenen Daten vorzubereiten. Wir verwenden für diese Aufgabe das dynamische Array dealInfo und passen zunächst dessen Größe mit der Funktion ArrayResize() an die Gesamtzahl der Deals an, um sicherzustellen, dass es über genügend Kapazität verfügt, um alle relevanten Dealseigenschaften zu speichern.

ArrayResize(dealInfo, totalDeals);

Anschließend werden die Deals in umgekehrter Reihenfolge, beginnend mit dem jüngsten, in einer for-Schleife durchlaufen. Für jedes Deal wird die Funktion HistoryDealGetTicket() verwendet, um das mit dem Deal verbundene Ticket abzurufen. Wenn der Abruf des Tickets erfolgreich war, werden wir die verschiedenen Eigenschaften des Deals abrufen und speichern. Wir speichern jede Eigenschaft in dem entsprechenden Feld im dealInfo-Array mit dem Index, der der aktuellen Schleifeniteration entspricht.

Wenn die Funktion HistoryDealGetTicket() kein gültiges Ticket für ein Deal abrufen kann, wird zu Debugging-Zwecken eine Fehlermeldung einschließlich des Fehlercodes aufgezeichnet. Dies gewährleistet Transparenz für den Fall, dass während der Rückgabe unerwartete Probleme auftreten.

for(int x = totalDeals - 1; x >= 0; x--)
  {
   ResetLastError();
   dealTicket = HistoryDealGetTicket(x);
   if(dealTicket > 0)
     {
      //- Deal ticket selected ok, we can now save the deals properties
      dealInfo[x].ticket = dealTicket;
      dealInfo[x].entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
      dealInfo[x].type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(dealTicket, DEAL_TYPE);
      dealInfo[x].magic = HistoryDealGetInteger(dealTicket, DEAL_MAGIC);
      dealInfo[x].positionId = HistoryDealGetInteger(dealTicket, DEAL_POSITION_ID);
      dealInfo[x].order = HistoryDealGetInteger(dealTicket, DEAL_ORDER);
      dealInfo[x].symbol = HistoryDealGetString(dealTicket, DEAL_SYMBOL);
      dealInfo[x].comment = HistoryDealGetString(dealTicket, DEAL_COMMENT);
      dealInfo[x].volume = HistoryDealGetDouble(dealTicket, DEAL_VOLUME);
      dealInfo[x].price = HistoryDealGetDouble(dealTicket, DEAL_PRICE);
      dealInfo[x].time = (datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME);
      dealInfo[x].tpPrice = HistoryDealGetDouble(dealTicket, DEAL_TP);
      dealInfo[x].slPrice = HistoryDealGetDouble(dealTicket, DEAL_SL);
      dealInfo[x].commission = HistoryDealGetDouble(dealTicket, DEAL_COMMISSION);
      dealInfo[x].swap = HistoryDealGetDouble(dealTicket, DEAL_SWAP);
      dealInfo[x].reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(dealTicket, DEAL_REASON);
      dealInfo[x].profit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT);
     }
   else
     {
      Print(
         __FUNCTION__, " HistoryDealGetTicket(", x, ") failed. (dealTicket = ", dealTicket,
         ") *** Error Code: ", GetLastError()
      );
     }
  }

Hier ist die vollständige Funktion SaveDealsData() mit allen Codesegmenten in voller Länge.

void SaveDealsData()
  {
//- Get the number of loaded history deals
   int totalDeals = HistoryDealsTotal();
   ulong dealTicket;
//-
//- Check if we have any deals to be worked on
   if(totalDeals > 0)
     {
      //- Resize the dynamic array that stores the deals
      ArrayResize(dealInfo, totalDeals);

      //- Let us loop through the deals and save them one by one
      for(int x = totalDeals - 1; x >= 0; x--)
        {
         ResetLastError();
         dealTicket = HistoryDealGetTicket(x);
         if(dealTicket > 0)
           {
            //- Deal ticket selected ok, we can now save the deals properties
            dealInfo[x].ticket = dealTicket;
            dealInfo[x].entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
            dealInfo[x].type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(dealTicket, DEAL_TYPE);
            dealInfo[x].magic = HistoryDealGetInteger(dealTicket, DEAL_MAGIC);
            dealInfo[x].positionId = HistoryDealGetInteger(dealTicket, DEAL_POSITION_ID);
            dealInfo[x].order = HistoryDealGetInteger(dealTicket, DEAL_ORDER);
            dealInfo[x].symbol = HistoryDealGetString(dealTicket, DEAL_SYMBOL);
            dealInfo[x].comment = HistoryDealGetString(dealTicket, DEAL_COMMENT);
            dealInfo[x].volume = HistoryDealGetDouble(dealTicket, DEAL_VOLUME);
            dealInfo[x].price = HistoryDealGetDouble(dealTicket, DEAL_PRICE);
            dealInfo[x].time = (datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME);
            dealInfo[x].tpPrice = HistoryDealGetDouble(dealTicket, DEAL_TP);
            dealInfo[x].slPrice = HistoryDealGetDouble(dealTicket, DEAL_SL);
            dealInfo[x].commission = HistoryDealGetDouble(dealTicket, DEAL_COMMISSION);
            dealInfo[x].swap = HistoryDealGetDouble(dealTicket, DEAL_SWAP);
            dealInfo[x].reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(dealTicket, DEAL_REASON);
            dealInfo[x].profit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT);
           }
         else
           {
            Print(
               __FUNCTION__, " HistoryDealGetTicket(", x, ") failed. (dealTicket = ", dealTicket,
               ") *** Error Code: ", GetLastError()
            );
           }
        }
     }
   else
     {
      Print(__FUNCTION__, ": No deals available to be processed, totalDeals = ", totalDeals);
     }
  }


Funktion Historie der Abschlüsse drucken

Die Funktion PrintDealsHistory() dient dazu, die historischen Daten der Deals für einen bestimmten Zeitraum abzurufen und anzuzeigen. Diese Funktion ist in Situationen nützlich, in denen Sie eine Reihe von Handelsdaten innerhalb eines bestimmten Zeitrahmens untersuchen müssen. Es gibt keine Daten zurück, sondern gibt die Deal-Informationen zur Überprüfung in das Protokoll von MetaTrader 5 aus. Diese Funktion kann extern aufgerufen werden, um den Nutzern Einblicke in vergangene Geschäfte zu geben, indem die Funktion GetHistoryData() verwendet wird, um die relevanten Daten abzurufen.

Wir beginnen mit der Definition der Funktion PrintDealsHistory(). Die Funktion benötigt zwei Parameter, fromDateTime und toDateTime, die die Anfangs- und Endzeit des zu durchsuchenden Zeitraums angeben. Die Funktion ruft die Deals ab, die innerhalb dieses Zeitrahmens abgeschlossen wurden. Beachten Sie, dass die Funktion als Export gekennzeichnet ist, d. h. sie kann von anderen Programmen oder Bibliotheken aus aufgerufen werden, sodass sie leicht für die externe Verwendung verfügbar ist.

void PrintDealsHistory(datetime fromDateTime, datetime toDateTime) export
  {
//-- Our function's code will go here
  }

Als Nächstes rufen wir die Funktion GetHistoryData() auf und übergeben die Werte fromDateTime, toDateTime und eine zusätzliche Konstante GET_DEALS_HISTORY_DATA. Damit wird die Funktion angewiesen, die relevanten Handelsdaten zwischen dem angegebenen Start- und Endzeitpunkt abzurufen. Dieser Funktionsaufruf stellt sicher, dass die Deal-Informationen für den gewünschten Zeitraum abgerufen und im Array dealInfo gespeichert werden.

GetHistoryData(fromDateTime, toDateTime, GET_DEALS_HISTORY_DATA);

Sobald die Daten der Deals abgerufen sind, müssen wir prüfen, ob Daten vorhanden sind. Wir verwenden die Funktion ArraySize(), um die Gesamtzahl der im Array dealInfo gespeicherten Deals zu ermitteln. Wenn keine Deals gefunden werden (d.h. totalDeals ist 0), wird eine Meldung protokolliert, um den Nutzer zu informieren und die Funktion zu beenden. Wenn keine Deals angezeigt werden können, bricht die Funktion vorzeitig ab, um Zeit zu sparen und unnötige Operationen zu vermeiden.

int totalDeals = ArraySize(dealInfo);
if(totalDeals <= 0)
  {
   Print("");
   Print(__FUNCTION__, ": No deals history found for the specified period.");
   return; //-- Exit the function
  }

Wenn Daten der Deals gefunden werden, drucken wir die Details aus. Der erste Schritt besteht darin, eine zusammenfassende Meldung zu drucken, die die Gesamtzahl der gefundenen Deals und den Datumsbereich, für den sie ausgeführt wurden, angibt.

Print("");
Print(__FUNCTION__, "-------------------------------------------------------------------------------");
Print(
   "Found a total of ", totalDeals,
   " deals executed between (", fromDateTime, ") and (", toDateTime, ")."
);

Als Nächstes verwenden wir eine for-Schleife, um alle Deals im Array dealInfo zu durchlaufen. Für jedes Deal drucken wir die relevanten Details aus, wie z.B. das Symbol des Deals, die Ticketnummer, die Positions-ID, den Einstiegstyp, den Preis, die Stop-Loss- (SL) und Take-Profit-Level (TP), den Swap, die Kommission, den Gewinn und mehr. Die Details zu jedem Deal werden mit einer beschreibenden Kennzeichnung ausgedruckt, sodass der Nutzer den Transaktionsverlauf leicht nachvollziehen kann.

for(int r = 0; r < totalDeals; r++)
  {
   Print("---------------------------------------------------------------------------------------------------");
   Print("Deal #", (r + 1));
   Print("Symbol: ", dealInfo[r].symbol);
   Print("Time Executed: ", dealInfo[r].time);
   Print("Ticket: ", dealInfo[r].ticket);
   Print("Position ID: ", dealInfo[r].positionId);
   Print("Order Ticket: ", dealInfo[r].order);
   Print("Type: ", EnumToString(dealInfo[r].type));
   Print("Entry: ", EnumToString(dealInfo[r].entry));
   Print("Reason: ", EnumToString(dealInfo[r].reason));
   Print("Volume: ", dealInfo[r].volume);
   Print("Price: ", dealInfo[r].price);
   Print("SL Price: ", dealInfo[r].slPrice);
   Print("TP Price: ", dealInfo[r].tpPrice);
   Print("Swap: ", dealInfo[r].swap, " ", AccountInfoString(ACCOUNT_CURRENCY));
   Print("Commission: ", dealInfo[r].commission, " ", AccountInfoString(ACCOUNT_CURRENCY));
   Print("Profit: ", dealInfo[r].profit, " ", AccountInfoString(ACCOUNT_CURRENCY));
   Print("Comment: ", dealInfo[r].comment);
   Print("Magic: ", dealInfo[r].magic);
   Print("");
  }

Hier ist die vollständige Funktion PrintDealsHistory() mit allen integrierten Codesegmenten.

void PrintDealsHistory(datetime fromDateTime, datetime toDateTime) export
  {
//- Get and save the deals history for the specified period
   GetHistoryData(fromDateTime, toDateTime, GET_DEALS_HISTORY_DATA);
   int totalDeals = ArraySize(dealInfo);
   if(totalDeals <= 0)
     {
      Print("");
      Print(__FUNCTION__, ": No deals history found for the specified period.");
      return; //-- Exit the function
     }

   Print("");
   Print(__FUNCTION__, "-------------------------------------------------------------------------------");
   Print(
      "Found a total of ", totalDeals,
      " deals executed between (", fromDateTime, ") and (", toDateTime, ")."
   );

   for(int r = 0; r < totalDeals; r++)
     {
      Print("---------------------------------------------------------------------------------------------------");
      Print("Deal #", (r + 1));
      Print("Symbol: ", dealInfo[r].symbol);
      Print("Time Executed: ", dealInfo[r].time);
      Print("Ticket: ", dealInfo[r].ticket);
      Print("Position ID: ", dealInfo[r].positionId);
      Print("Order Ticket: ", dealInfo[r].order);
      Print("Type: ", EnumToString(dealInfo[r].type));
      Print("Entry: ", EnumToString(dealInfo[r].entry));
      Print("Reason: ", EnumToString(dealInfo[r].reason));
      Print("Volume: ", dealInfo[r].volume);
      Print("Price: ", dealInfo[r].price);
      Print("SL Price: ", dealInfo[r].slPrice);
      Print("TP Price: ", dealInfo[r].tpPrice);
      Print("Swap: ", dealInfo[r].swap, " ", AccountInfoString(ACCOUNT_CURRENCY));
      Print("Commission: ", dealInfo[r].commission, " ", AccountInfoString(ACCOUNT_CURRENCY));
      Print("Profit: ", dealInfo[r].profit, " ", AccountInfoString(ACCOUNT_CURRENCY));
      Print("Comment: ", dealInfo[r].comment);
      Print("Magic: ", dealInfo[r].magic);
      Print("");
     }
  }


Die Funktion SaveOrdersData

Die Funktion SaveOrdersData() ist für das Abrufen und Speichern der historischen Auftragsdaten verantwortlich, die im Cache für die Handelsgeschichte verfügbar sind. Diese Funktion verarbeitet einen Auftrag nach dem anderen, extrahiert ihre Schlüsseleigenschaften mit Hilfe der Funktionen HistoryOrderGet... von MQL5 und speichert sie in einem dynamischen Array namens orderInfo. Dieses Array wird dann von anderen Teilen der Bibliothek verwendet, um die Daten nach Bedarf zu analysieren und zu manipulieren. Diese Funktion gibt keine Daten zurück, wird nicht als exportierbar definiert, da sie intern in der Bibliothek verwendet wird, behandelt Fehler korrekt und protokolliert alle Probleme zu Debugging-Zwecken.

Beginnen wir mit der Definition der Funktionssignatur.

void SaveOrdersData()
  {
//-- Our function's code will go here
  }

Als Nächstes ermitteln wir, wie viele historische Aufträge vorhanden sind. Dies wird mit der Funktion HistoryOrdersTotal() erreicht, die die Gesamtzahl der historischen Aufträge im Cache zurückgibt. Das Ergebnis wird in einer Variablen namens totalOrdersHistory gespeichert. Außerdem deklarieren wir eine vorzeichenlose Variable vom Typ long, orderTicket, um das Ticket jedes Auftrags zu speichern, während wir sie verarbeiten.

int totalOrdersHistory = HistoryOrdersTotal();
ulong orderTicket;

Wenn es keine historischen Aufträge gibt (totalOrdersHistory <= 0), protokolliert die Funktion eine entsprechende Meldung und wird vorzeitig beendet, um unnötige Verarbeitung zu vermeiden.

if(totalOrdersHistory > 0)
  {
   //-- Code to process orders goes here
  }
else
  {
   Print(__FUNCTION__, ": No order history available to be processed, totalOrdersHistory = ", totalOrdersHistory);
   return;
  }

Wenn historische Aufträge verfügbar sind, bereiten wir das ArrayorderInfo vor, um die abgerufenen Daten zu speichern. Dazu wird die Größe des Arrays mit der Funktion ArrayResize() an die Gesamtzahl der historischen Aufträge angepasst.

ArrayResize(orderInfo, totalOrdersHistory);

In einer for-Schleife werden die Aufträge in umgekehrter Reihenfolge (beginnend mit dem jüngsten) durchlaufen. Für jede Order Wir beginnen mit dem Abrufen des Auftragstickets mit der Funktion HistoryOrderGetTicket(). Wenn der Ticketabruf erfolgreich ist, extrahieren wir die verschiedenen Eigenschaften der Orders mit den Funktionen HistoryOrderGet... und speichern sie in den entsprechenden Feldern des Arrays orderInfo. Wenn der Ticketabruf fehlschlägt, protokolliert die Funktion eine Fehlermeldung zusammen mit dem Fehlercode zur Fehlersuche.

for(int x = totalOrdersHistory - 1; x >= 0; x--)
  {
   ResetLastError();
   orderTicket = HistoryOrderGetTicket(x);
   if(orderTicket > 0)
     {
      //- Order ticket selected ok, we can now save the order properties
      orderInfo[x].ticket = orderTicket;
      orderInfo[x].timeSetup = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_SETUP);
      orderInfo[x].timeDone = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_DONE);
      orderInfo[x].expirationTime = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_EXPIRATION);
      orderInfo[x].typeTime = (ENUM_ORDER_TYPE_TIME)HistoryOrderGetInteger(orderTicket, ORDER_TYPE_TIME);
      orderInfo[x].magic = HistoryOrderGetInteger(orderTicket, ORDER_MAGIC);
      orderInfo[x].reason = (ENUM_ORDER_REASON)HistoryOrderGetInteger(orderTicket, ORDER_REASON);
      orderInfo[x].type = (ENUM_ORDER_TYPE)HistoryOrderGetInteger(orderTicket, ORDER_TYPE);
      orderInfo[x].state = (ENUM_ORDER_STATE)HistoryOrderGetInteger(orderTicket, ORDER_STATE);
      orderInfo[x].typeFilling = (ENUM_ORDER_TYPE_FILLING)HistoryOrderGetInteger(orderTicket, ORDER_TYPE_FILLING);
      orderInfo[x].positionId = HistoryOrderGetInteger(orderTicket, ORDER_POSITION_ID);
      orderInfo[x].positionById = HistoryOrderGetInteger(orderTicket, ORDER_POSITION_BY_ID);
      orderInfo[x].symbol = HistoryOrderGetString(orderTicket, ORDER_SYMBOL);
      orderInfo[x].comment = HistoryOrderGetString(orderTicket, ORDER_COMMENT);
      orderInfo[x].volumeInitial = HistoryOrderGetDouble(orderTicket, ORDER_VOLUME_INITIAL);
      orderInfo[x].priceOpen = HistoryOrderGetDouble(orderTicket, ORDER_PRICE_OPEN);
      orderInfo[x].priceStopLimit = HistoryOrderGetDouble(orderTicket, ORDER_PRICE_STOPLIMIT);
      orderInfo[x].tpPrice = HistoryOrderGetDouble(orderTicket, ORDER_TP);
      orderInfo[x].slPrice = HistoryOrderGetDouble(orderTicket, ORDER_SL);
     }
   else
     {
      Print(
         __FUNCTION__, " HistoryOrderGetTicket(", x, ") failed. (orderTicket = ", orderTicket,
         ") *** Error Code: ", GetLastError()
      );
     }
  }

Nach der Bearbeitung aller Aufträge wird die Funktion ordnungsgemäß beendet. Hier ist die vollständige Implementierung der Funktion SaveOrdersData() mit allen enthaltenen Codesegmenten.

void SaveOrdersData()
  {
//- Get the number of loaded history orders
   int totalOrdersHistory = HistoryOrdersTotal();
   ulong orderTicket;
//-
//- Check if we have any orders in the history to be worked on
   if(totalOrdersHistory > 0)
     {
      //- Resize the dynamic array that stores the history orders
      ArrayResize(orderInfo, totalOrdersHistory);

      //- Let us loop through the order history and save them one by one
      for(int x = totalOrdersHistory - 1; x >= 0; x--)
        {
         ResetLastError();
         orderTicket = HistoryOrderGetTicket(x);
         if(orderTicket > 0)
           {
            //- Order ticket selected ok, we can now save the order properties
            orderInfo[x].ticket = orderTicket;
            orderInfo[x].timeSetup = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_SETUP);
            orderInfo[x].timeDone = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_DONE);
            orderInfo[x].expirationTime = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_EXPIRATION);
            orderInfo[x].typeTime = (ENUM_ORDER_TYPE_TIME)HistoryOrderGetInteger(orderTicket, ORDER_TYPE_TIME);
            orderInfo[x].magic = HistoryOrderGetInteger(orderTicket, ORDER_MAGIC);
            orderInfo[x].reason = (ENUM_ORDER_REASON)HistoryOrderGetInteger(orderTicket, ORDER_REASON);
            orderInfo[x].type = (ENUM_ORDER_TYPE)HistoryOrderGetInteger(orderTicket, ORDER_TYPE);
            orderInfo[x].state = (ENUM_ORDER_STATE)HistoryOrderGetInteger(orderTicket, ORDER_STATE);
            orderInfo[x].typeFilling = (ENUM_ORDER_TYPE_FILLING)HistoryOrderGetInteger(orderTicket, ORDER_TYPE_FILLING);
            orderInfo[x].positionId = HistoryOrderGetInteger(orderTicket, ORDER_POSITION_ID);
            orderInfo[x].positionById = HistoryOrderGetInteger(orderTicket, ORDER_POSITION_BY_ID);
            orderInfo[x].symbol = HistoryOrderGetString(orderTicket, ORDER_SYMBOL);
            orderInfo[x].comment = HistoryOrderGetString(orderTicket, ORDER_COMMENT);
            orderInfo[x].volumeInitial = HistoryOrderGetDouble(orderTicket, ORDER_VOLUME_INITIAL);
            orderInfo[x].priceOpen = HistoryOrderGetDouble(orderTicket, ORDER_PRICE_OPEN);
            orderInfo[x].priceStopLimit = HistoryOrderGetDouble(orderTicket, ORDER_PRICE_STOPLIMIT);
            orderInfo[x].tpPrice = HistoryOrderGetDouble(orderTicket, ORDER_TP);
            orderInfo[x].slPrice = HistoryOrderGetDouble(orderTicket, ORDER_SL);
           }
         else
           {
            Print(
               __FUNCTION__, " HistoryOrderGetTicket(", x, ") failed. (orderTicket = ", orderTicket,
               ") *** Error Code: ", GetLastError()
            );
           }
        }
     }
   else
     {
      Print(__FUNCTION__, ": No order history available to be processed, totalOrdersHistory = ", totalOrdersHistory);
     }
  }


Funktion Auftragshistorie drucken

Die Funktion PrintOrdersHistory() bietet eine wesentliche Funktion zur Anzeige der Details der Order-Historie innerhalb eines bestimmten Zeitraums. Sie fragt die zuvor gespeicherten Daten aus dem Array orderInfo ab und druckt alle relevanten Details der Aufträge aus. Diese Funktion ist als Export definiert, weil sie für externe Module oder MQL5-Anwendungen, die diese Bibliothek verwenden, zugänglich sein soll. Sie folgt einem ähnlichen Ansatz wie die Funktion PrintDealsHistory(). Im Folgenden finden Sie die vollständige Implementierung der Funktion PrintOrdersHistory() mit erläuternden Kommentaren, damit Sie besser verstehen, wie die einzelnen Teile des Codes funktionieren.

void PrintOrdersHistory(datetime fromDateTime, datetime toDateTime) export
  {
//- Get and save the orders history for the specified period
   GetHistoryData(fromDateTime, toDateTime, GET_ORDERS_HISTORY_DATA);
   int totalOrders = ArraySize(orderInfo);
   if(totalOrders <= 0)
     {
      Print("");
      Print(__FUNCTION__, ": No orders history found for the specified period.");
      return; //-- Exit the function
     }

   Print("");
   Print(__FUNCTION__, "-------------------------------------------------------------------------------");
   Print(
      "Found a total of ", totalOrders,
      " orders filled or cancelled between (", fromDateTime, ") and (", toDateTime, ")."
   );

   for(int r = 0; r < totalOrders; r++)
     {
      Print("---------------------------------------------------------------------------------------------------");
      Print("Order #", (r + 1));
      Print("Symbol: ", orderInfo[r].symbol);
      Print("Time Setup: ", orderInfo[r].timeSetup);
      Print("Type: ", EnumToString(orderInfo[r].type));
      Print("Ticket: ", orderInfo[r].ticket);
      Print("Position ID: ", orderInfo[r].positionId);
      Print("State: ", EnumToString(orderInfo[r].state));
      Print("Type Filling: ", EnumToString(orderInfo[r].typeFilling));
      Print("Type Time: ", EnumToString(orderInfo[r].typeTime));
      Print("Reason: ", EnumToString(orderInfo[r].reason));
      Print("Volume Initial: ", orderInfo[r].volumeInitial);
      Print("Price Open: ", orderInfo[r].priceOpen);
      Print("Price Stop Limit: ", orderInfo[r].priceStopLimit);
      Print("SL Price: ", orderInfo[r].slPrice);
      Print("TP Price: ", orderInfo[r].tpPrice);
      Print("Time Done: ", orderInfo[r].timeDone);
      Print("Expiration Time: ", orderInfo[r].expirationTime);
      Print("Comment: ", orderInfo[r].comment);
      Print("Magic: ", orderInfo[r].magic);
      Print("");
     }
  }


Funktion zum Speichern von Positionsdaten

Die Funktion SavePositionsData() organisiert die Historie der Deals und der Aufträge, um den Lebenszyklus jeder Position zu rekonstruieren. Sie spielt eine zentrale Rolle bei der Erstellung der Positionshistorie, indem sie Informationen aus den verfügbaren Daten zusammenführt. In der MQL5-Dokumentation werden Sie feststellen, dass es keine Standardfunktionen (wie HistoryPositionSelect() oder HistoryPositionsTotal()) für den direkten Zugriff auf historische Positionsdaten gibt. Daher müssen wir eine nutzerdefinierte Funktion erstellen, die Aufträge und Daten der Deals kombiniert und dabei die Positions-ID als Verbindungsschlüssel verwendet, um Deals mit ihren Ursprungsaufträgen zu verknüpfen.

Wir beginnen mit der Untersuchung der Deals, um alle Ausstiegs-Deals zu identifizieren, die anzeigen, dass eine Position geschlossen wurde. Von dort aus gehen wir zurück zu dem entsprechenden Einstiegs-Deals, um Einzelheiten über die Eröffnung der Position zu sammeln. Schließlich werden wir die Auftragshistorie nutzen, um die Informationen zur Positionshistorie mit zusätzlichem Kontext anzureichern, z. B. der Art des Ursprungsauftrags oder ob die Position durch einen schwebenden Auftrag ausgelöst wurde. Dieser schrittweise Prozess stellt sicher, dass der Lebenszyklus jeder Position - von der Eröffnung bis zur Schließung - genau rekonstruiert werden kann, sodass ein eindeutiger Prüfpfad entsteht.

Beginnen wir mit der Definition der Funktionssignatur. Da diese Funktion nur intern von den Kernmodulen der EX5-Bibliothek verwendet wird, ist sie nicht exportierbar.

void SavePositionsData()
  {
//-- Our function's code will go here
  }

Als Nächstes berechnen wir die Gesamtzahl der Deals im Array dealInfo, das alle Daten der Deals enthält. Danach wird die Größe des Arrays positionInfo geändert, in dem alle Daten des Positionsverlaufs gespeichert und für die erwartete Anzahl von Positionen vorbereitet werden.

int totalDealInfo = ArraySize(dealInfo);
ArrayResize(positionInfo, totalDealInfo);
int totalPositionsFound = 0, posIndex = 0;

Wenn keine Deals im Array dealInfo vorhanden sind (d.h. totalDealInfo == 0), verlassen wir die Funktion vorzeitig, da keine Daten zu verarbeiten sind.

if(totalDealInfo == 0)
  {
   return;
  }

Als Nächstes durchlaufen wir die Deals in umgekehrter Reihenfolge (beginnend mit dem jüngsten Deal), um sicherzustellen, dass wir die Ausstiegs-Deals den entsprechenden Einstiegs-Deals zuordnen können. Wir prüfen, ob es sich bei dem aktuellen Deal um ein Ausstiegs-Deals ist, indem wir seine Eintrittseigenschaft auswerten (dealInfo[x].entry == DEAL_ENTRY_OUT). Es ist wichtig, zunächst nach Exit-Deals zu suchen, da dies bestätigt, dass eine Position geschlossen wurde und nicht mehr aktiv ist. Wir wollen nur historische, geschlossene Positionen erfassen, keine aktiven.

for(int x = totalDealInfo - 1; x >= 0; x--)
  {
   if(dealInfo[x].entry == DEAL_ENTRY_OUT)
     {
      // Process exit deal
     }
  }

Wenn Ausstiegs-Deals gefunden werden, suchen wir nach dem entsprechenden Einstiegs-Deals durch Abgleich mit der POSITION_ID. Wenn ein Eintrag gefunden wird, beginnen wir mit dem Speichern der relevanten Informationen im Array positionInfo.

for(int k = ArraySize(dealInfo) - 1; k >= 0; k--)
  {
   if(dealInfo[k].positionId == positionId)
     {
      if(dealInfo[k].entry == DEAL_ENTRY_IN)
        {
         exitDealFound = true;
         totalPositionsFound++;
         posIndex = totalPositionsFound - 1;

         // Save the entry deal data
         positionInfo[posIndex].openingDealTicket = dealInfo[k].ticket;
         positionInfo[posIndex].openTime = dealInfo[k].time;
         positionInfo[posIndex].openPrice = dealInfo[k].price;
         positionInfo[posIndex].volume = dealInfo[k].volume;
         positionInfo[posIndex].magic = dealInfo[k].magic;
         positionInfo[posIndex].comment = dealInfo[k].comment;
        }
     }
  }

Nachdem das Ausstiegs-Deals mit einem Einstiegs-Deals abgeglichen wurde, speichern wir die Eigenschaften des Ausstiegs-Deals, wie z. B. den Schlusskurs, die Schlusszeit, den Gewinn, den Swap und die Provision. Wir berechnen auch die Laufzeit und den Nettogewinn des Handelsgeschäfts, indem wir den Swap und die Provision berücksichtigen.

if(exitDealFound)
  {
   if(dealInfo[x].type == DEAL_TYPE_BUY)
     {
      positionInfo[posIndex].type = POSITION_TYPE_SELL;
     }
   else
     {
      positionInfo[posIndex].type = POSITION_TYPE_BUY;
     }

   positionInfo[posIndex].positionId = dealInfo[x].positionId;
   positionInfo[posIndex].symbol = dealInfo[x].symbol;
   positionInfo[posIndex].profit = dealInfo[x].profit;
   positionInfo[posIndex].closingDealTicket = dealInfo[x].ticket;
   positionInfo[posIndex].closePrice = dealInfo[x].price;
   positionInfo[posIndex].closeTime = dealInfo[x].time;
   positionInfo[posIndex].swap = dealInfo[x].swap;
   positionInfo[posIndex].commission = dealInfo[x].commission;

   positionInfo[posIndex].duration = MathAbs((long)positionInfo[posIndex].closeTime -
                                     (long)positionInfo[posIndex].openTime);
   positionInfo[posIndex].netProfit = positionInfo[posIndex].profit + positionInfo[posIndex].swap -
                                      positionInfo[posIndex].commission;
  }

Für jede Position berechnen wir die Pip-Werte für Stop-Loss (SL) und Take-Profit (TP), je nachdem, ob es sich um eine Kauf- oder Verkaufsposition handelt. Wir verwenden den Punktwert des Symbols, um die Anzahl der Pips zu bestimmen.

if(positionInfo[posIndex].type == POSITION_TYPE_BUY)
  {
// Calculate TP and SL pip values for buy position
   if(positionInfo[posIndex].tpPrice > 0)
     {
      double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
      positionInfo[posIndex].tpPips = int((positionInfo[posIndex].tpPrice -
                                           positionInfo[posIndex].openPrice) / symbolPoint);
     }
   if(positionInfo[posIndex].slPrice > 0)
     {
      double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
      positionInfo[posIndex].slPips = int((positionInfo[posIndex].openPrice -
                                           positionInfo[posIndex].slPrice) / symbolPoint);
     }
// Calculate pip profit for buy position
   double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
   positionInfo[posIndex].pipProfit = int((positionInfo[posIndex].closePrice -
                                           positionInfo[posIndex].openPrice) / symbolPoint);
  }
else
  {
// Calculate TP and SL pip values for sell position
   if(positionInfo[posIndex].tpPrice > 0)
     {
      double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
      positionInfo[posIndex].tpPips = int((positionInfo[posIndex].openPrice -
                                           positionInfo[posIndex].tpPrice) / symbolPoint);
     }
   if(positionInfo[posIndex].slPrice > 0)
     {
      double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
      positionInfo[posIndex].slPips = int((positionInfo[posIndex].slPrice -
                                           positionInfo[posIndex].openPrice) / symbolPoint);
     }
// Calculate pip profit for sell position
   double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
   positionInfo[posIndex].pipProfit = int((positionInfo[posIndex].openPrice -
                                           positionInfo[posIndex].closePrice) / symbolPoint);
  }

Schließlich wird das Array orderInfo durchsucht, um den Auftrag zu finden, der die Position ausgelöst hat. Wir gleichen die POSITION_ID ab und stellen sicher, dass sich der Auftrag im Zustand ORDER_STATE_FILLED befindet. Sobald wir sie gefunden haben, speichern wir das Ticket und den Typ des Eröffnungsauftrags, anhand dessen wir feststellen können, ob die Position durch einen schwebenden Auftrag oder einen direkten Markteintritt eingeleitet wurde.

for(int k = 0; k < ArraySize(orderInfo); k++)
  {
   if(
      orderInfo[k].positionId == positionInfo[posIndex].positionId &&
      orderInfo[k].state == ORDER_STATE_FILLED
   )
     {
      positionInfo[posIndex].openingOrderTicket = orderInfo[k].ticket;
      positionInfo[posIndex].ticket = positionInfo[posIndex].openingOrderTicket;

      //- Determine if the position was initiated by a pending order or direct market entry
      switch(orderInfo[k].type)
        {
         case ORDER_TYPE_BUY_LIMIT:
         case ORDER_TYPE_BUY_STOP:
         case ORDER_TYPE_SELL_LIMIT:
         case ORDER_TYPE_SELL_STOP:
         case ORDER_TYPE_BUY_STOP_LIMIT:
         case ORDER_TYPE_SELL_STOP_LIMIT:
            positionInfo[posIndex].initiatedByPendingOrder = true;
            positionInfo[posIndex].initiatingOrderType = orderInfo[k].type;
            break;
         default:
            positionInfo[posIndex].initiatedByPendingOrder = false;
            positionInfo[posIndex].initiatingOrderType = orderInfo[k].type;
            break;
        }

      break; //- Exit the orderInfo loop once the required data is found
     }
  }

Um das Array positionInfo zu bereinigen, wird es verkleinert, um leere oder nicht verwendete Elemente zu entfernen, nachdem alle Positionen verarbeitet wurden.

ArrayResize(positionInfo, totalPositionsFound);

Hier ist die vollständige Implementierung der Funktion SavePositionsData() mit allen enthaltenen Codesegmenten.

void SavePositionsData()
  {
//- Since every transaction is recorded as a deal, we will begin by scanning the deals and link them
//- to different orders and generate the positions data using the POSITION_ID as the primary and foreign key
   int totalDealInfo = ArraySize(dealInfo);
   ArrayResize(positionInfo, totalDealInfo); //- Resize the position array to match the deals array
   int totalPositionsFound = 0, posIndex = 0;
   if(totalDealInfo == 0) //- Check if we have any deal history available for processing
     {
      return; //- No deal data to process found, we can't go on. exit the function
     }
//- Let us loop through the deals array
   for(int x = totalDealInfo - 1; x >= 0; x--)
     {
      //- First we check if it is an exit deal to close a position
      if(dealInfo[x].entry == DEAL_ENTRY_OUT)
        {
         //- We begin by saving the position id
         ulong positionId = dealInfo[x].positionId;
         bool exitDealFound = false;

         //- Now we check if we have an exit deal from this position and save it's properties
         for(int k = ArraySize(dealInfo) - 1; k >= 0; k--)
           {
            if(dealInfo[k].positionId == positionId)
              {
               if(dealInfo[k].entry == DEAL_ENTRY_IN)
                 {
                  exitDealFound = true;

                  totalPositionsFound++;
                  posIndex = totalPositionsFound - 1;

                  positionInfo[posIndex].openingDealTicket = dealInfo[k].ticket;
                  positionInfo[posIndex].openTime = dealInfo[k].time;
                  positionInfo[posIndex].openPrice = dealInfo[k].price;
                  positionInfo[posIndex].volume = dealInfo[k].volume;
                  positionInfo[posIndex].magic = dealInfo[k].magic;
                  positionInfo[posIndex].comment = dealInfo[k].comment;
                 }
              }
           }

         if(exitDealFound) //- Continue saving the exit deal data
           {
            //- Save the position type
            if(dealInfo[x].type == DEAL_TYPE_BUY)
              {
               //- If the exit deal is a buy, then the position was a sell trade
               positionInfo[posIndex].type = POSITION_TYPE_SELL;
              }
            else
              {
               //- If the exit deal is a sell, then the position was a buy trade
               positionInfo[posIndex].type = POSITION_TYPE_BUY;
              }

            positionInfo[posIndex].positionId = dealInfo[x].positionId;
            positionInfo[posIndex].symbol = dealInfo[x].symbol;
            positionInfo[posIndex].profit = dealInfo[x].profit;
            positionInfo[posIndex].closingDealTicket = dealInfo[x].ticket;
            positionInfo[posIndex].closePrice = dealInfo[x].price;
            positionInfo[posIndex].closeTime = dealInfo[x].time;
            positionInfo[posIndex].swap = dealInfo[x].swap;
            positionInfo[posIndex].commission = dealInfo[x].commission;
            positionInfo[posIndex].tpPrice = dealInfo[x].tpPrice;
            positionInfo[posIndex].tpPips = 0;
            positionInfo[posIndex].slPrice = dealInfo[x].slPrice;
            positionInfo[posIndex].slPips = 0;

            //- Calculate the trade duration in seconds
            positionInfo[posIndex].duration = MathAbs((long)positionInfo[posIndex].closeTime - (long)positionInfo[posIndex].openTime);

            //- Calculate the net profit after swap and commission
            positionInfo[posIndex].netProfit =
               positionInfo[posIndex].profit + positionInfo[posIndex].swap - positionInfo[posIndex].commission;

            //- Get pip values for the position
            if(positionInfo[posIndex].type == POSITION_TYPE_BUY) //- Buy position
              {
               //- Get sl and tp pip values
               if(positionInfo[posIndex].tpPrice > 0)
                 {
                  double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
                  positionInfo[posIndex].tpPips =
                     int((positionInfo[posIndex].tpPrice - positionInfo[posIndex].openPrice) / symbolPoint);
                 }
               if(positionInfo[posIndex].slPrice > 0)
                 {
                  double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
                  positionInfo[posIndex].slPips =
                     int((positionInfo[posIndex].openPrice - positionInfo[posIndex].slPrice) / symbolPoint);
                 }

               //- Get the buy profit in pip value
               double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
               positionInfo[posIndex].pipProfit =
                  int((positionInfo[posIndex].closePrice - positionInfo[posIndex].openPrice) / symbolPoint);
              }
            else //- Sell position
              {
               //- Get sl and tp pip values
               if(positionInfo[posIndex].tpPrice > 0)
                 {
                  double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
                  positionInfo[posIndex].tpPips =
                     int((positionInfo[posIndex].openPrice - positionInfo[posIndex].tpPrice) / symbolPoint);
                 }
               if(positionInfo[posIndex].slPrice > 0)
                 {
                  double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
                  positionInfo[posIndex].slPips =
                     int((positionInfo[posIndex].slPrice - positionInfo[posIndex].openPrice) / symbolPoint);
                 }

               //- Get the sell profit in pip value
               double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
               positionInfo[posIndex].pipProfit =
                  int((positionInfo[posIndex].openPrice - positionInfo[posIndex].closePrice) / symbolPoint);
              }

            //- Now we scan and get the opening order ticket in the orderInfo array
            for(int k = 0; k < ArraySize(orderInfo); k++) //- Search from the oldest to newest order
              {
               if(
                  orderInfo[k].positionId == positionInfo[posIndex].positionId &&
                  orderInfo[k].state == ORDER_STATE_FILLED
               )
                 {
                  //- Save the order ticket that intiated the position
                  positionInfo[posIndex].openingOrderTicket = orderInfo[k].ticket;
                  positionInfo[posIndex].ticket = positionInfo[posIndex].openingOrderTicket;

                  //- Determine if the position was initiated by a pending order or direct market entry
                  switch(orderInfo[k].type)
                    {
                     //- Pending order entry
                     case ORDER_TYPE_BUY_LIMIT:
                     case ORDER_TYPE_BUY_STOP:
                     case ORDER_TYPE_SELL_LIMIT:
                     case ORDER_TYPE_SELL_STOP:
                     case ORDER_TYPE_BUY_STOP_LIMIT:
                     case ORDER_TYPE_SELL_STOP_LIMIT:
                        positionInfo[posIndex].initiatedByPendingOrder = true;
                        positionInfo[posIndex].initiatingOrderType = orderInfo[k].type;
                        break;

                     //- Direct market entry
                     default:
                        positionInfo[posIndex].initiatedByPendingOrder = false;
                        positionInfo[posIndex].initiatingOrderType = orderInfo[k].type;
                        break;
                    }

                  break; //--- We have everything we need, exit the orderInfo loop
                 }
              }
           }
        }
      else //--- Position id not found
        {
         continue;//- skip to the next iteration
        }
     }
//- Resize the positionInfo array and delete all the indexes that have zero values
   ArrayResize(positionInfo, totalPositionsFound);
  }


Funktion zum Drucken der Positionshistorie

Die Funktion PrintPositionsHistory() dient dazu, eine detaillierte Historie der geschlossenen Positionen innerhalb eines bestimmten Zeitraums anzuzeigen. Sie greift auf zuvor gespeicherte Daten aus dem Array positionInfo zu und gibt die relevanten Details für jede Position aus. Diese Funktion ist exportierbar, sodass sie für externe Module oder MQL5-Anwendungen, die diese Bibliothek verwenden, zugänglich ist. Die Implementierung wird ähnlich aufgebaut sein wie bei anderen von uns entwickelten Druckfunktionen. Hier ist die vollständige Umsetzung mit ausführlichen Kommentaren zur Klarheit.

void PrintPositionsHistory(datetime fromDateTime, datetime toDateTime) export
  {
//- Get and save the deals, orders, positions history for the specified period
   GetHistoryData(fromDateTime, toDateTime, GET_POSITIONS_HISTORY_DATA);
   int totalPositionsClosed = ArraySize(positionInfo);
   if(totalPositionsClosed <= 0)
     {
      Print("");
      Print(__FUNCTION__, ": No position history found for the specified period.");
      return; //- Exit the function
     }

   Print("");
   Print(__FUNCTION__, "-------------------------------------------------------------------------------");
   Print(
      "Found a total of ", totalPositionsClosed,
      " positions closed between (", fromDateTime, ") and (", toDateTime, ")."
   );

   for(int r = 0; r < totalPositionsClosed; r++)
     {
      Print("---------------------------------------------------------------------------------------------------");
      Print("Position #", (r + 1));
      Print("Symbol: ", positionInfo[r].symbol);
      Print("Time Open: ", positionInfo[r].openTime);
      Print("Ticket: ", positionInfo[r].ticket);
      Print("Type: ", EnumToString(positionInfo[r].type));
      Print("Volume: ", positionInfo[r].volume);
      Print("0pen Price: ", positionInfo[r].openPrice);
      Print("SL Price: ", positionInfo[r].slPrice, " (slPips: ", positionInfo[r].slPips, ")");
      Print("TP Price: ", positionInfo[r].tpPrice, " (tpPips: ", positionInfo[r].tpPips, ")");
      Print("Close Price: ", positionInfo[r].closePrice);
      Print("Close Time: ", positionInfo[r].closeTime);
      Print("Trade Duration: ", positionInfo[r].duration);
      Print("Swap: ", positionInfo[r].swap, " ", AccountInfoString(ACCOUNT_CURRENCY));
      Print("Commission: ", positionInfo[r].commission, " ", AccountInfoString(ACCOUNT_CURRENCY));
      Print("Profit: ", positionInfo[r].profit, " ", AccountInfoString(ACCOUNT_CURRENCY));
      Print("Net profit: ", DoubleToString(positionInfo[r].netProfit, 2), " ", AccountInfoString(ACCOUNT_CURRENCY));
      Print("pipProfit: ", positionInfo[r].pipProfit);
      Print("Initiating Order Type: ", EnumToString(positionInfo[r].initiatingOrderType));
      Print("Initiated By Pending Order: ", positionInfo[r].initiatedByPendingOrder);
      Print("Comment: ", positionInfo[r].comment);
      Print("Magic: ", positionInfo[r].magic);
      Print("");
     }
  }


Funktion zum Speichern der Daten schwebender Aufträge

Die Funktion SavePendingOrdersData() verarbeitet die Daten aus der Auftragshistorie, um die Historie der schwebenden Aufträge zu erstellen und zu speichern. Diese Funktion filtert im Wesentlichen schwebende Aufträge aus der Auftragshistorie, speichert wichtige Details und berechnet spezifische Werte wie die Anzahl der Pips für Take-Profit (TP) und Stop-Loss (SL). Es spielt eine entscheidende Rolle bei der Verfolgung des Lebenszyklus von anhängigen Aufträgen, hilft bei der Erstellung einer genauen Auftragshistorie und ergänzt das System mit Daten darüber, wie jeder anhängige Auftrag strukturiert und ausgeführt wurde.

MQL5 verfügt derzeit nicht über Standardfunktionen wie HistoryPendingOrderSelect() oder HistoryPendingOrdersTotal() für den direkten Zugriff auf historische Daten über schwebende Aufträge. Daher müssen wir eine nutzerdefinierte Funktion erstellen, um die Historie der Aufträge zu scannen und eine Datenquelle zu erstellen, die alle ausgeführten oder stornierten schwebenden Aufträge innerhalb eines bestimmten historischen Zeitraums enthält.

Beginnen wir mit der Definition der Funktionssignatur. Da diese Funktion nur intern von den Kernmodulen der EX5-Bibliothek verwendet wird, ist sie nicht exportierbar.

void SavePendingOrdersData()
  {
//-- Function's code will go here
  }

Als Nächstes berechnen wir die Gesamtzahl der Aufträge im Array orderInfo , das die Details aller Aufträge enthält. Wir passen die Größe des Arrays pendingOrderInfo an die Gesamtzahl der Aufträge an und sorgen dafür, dass genügend Platz zum Speichern der gefilterten schwebenden Aufträge zur Verfügung steht.

int totalOrderInfo = ArraySize(orderInfo);
ArrayResize(pendingOrderInfo, totalOrderInfo);
int totalPendingOrdersFound = 0, pendingIndex = 0;

Wenn keine Aufträge zu verarbeiten sind (d.h. totalOrderInfo == 0), wird die Funktion sofort beendet, da keine offenen Auftragsdaten zu verarbeiten sind.

if(totalOrderInfo == 0)
  {
   return;
  }

Jetzt gehen wir die Aufträge in umgekehrter Reihenfolge durch, um sicherzustellen, dass wir die neuesten Aufträge zuerst bearbeiten. Innerhalb der Schleife wird geprüft, ob es sich bei dem aktuellen Auftrag um einen schwebenden Auftrag handelt, indem sein Typ ausgewertet wird. Die gespeicherte Auftragshistorie enthält schwebende Aufträge (wie Kauflimits, Verkaufsstopps usw.), die entweder ausgeführt (filled) und in Positionen umgewandelt oder storniert wurden, ohne zu Positionen zu werden.

for(int x = totalOrderInfo - 1; x >= 0; x--)
  {
   if(
      orderInfo[x].type == ORDER_TYPE_BUY_LIMIT || orderInfo[x].type == ORDER_TYPE_BUY_STOP ||
      orderInfo[x].type == ORDER_TYPE_SELL_LIMIT || orderInfo[x].type == ORDER_TYPE_SELL_STOP ||
      orderInfo[x].type == ORDER_TYPE_BUY_STOP_LIMIT || orderInfo[x].type == ORDER_TYPE_SELL_STOP_LIMIT
   )
     {
      totalPendingOrdersFound++;
      pendingIndex = totalPendingOrdersFound - 1;

      //-- Save the pending order properties into the pendingOrderInfo array

     }

Wenn es sich um einen schwebenden Auftrag handelt, speichern wir seine Eigenschaften (z. B. Typ, Status, Positions-ID, Ticket, Symbol, Zeit usw.) in dem Array pendingOrderInfo.

pendingOrderInfo[pendingIndex].type = orderInfo[x].type;
pendingOrderInfo[pendingIndex].state = orderInfo[x].state;
pendingOrderInfo[pendingIndex].positionId = orderInfo[x].positionId;
pendingOrderInfo[pendingIndex].ticket = orderInfo[x].ticket;
pendingOrderInfo[pendingIndex].symbol = orderInfo[x].symbol;
pendingOrderInfo[pendingIndex].timeSetup = orderInfo[x].timeSetup;
pendingOrderInfo[pendingIndex].expirationTime = orderInfo[x].expirationTime;
pendingOrderInfo[pendingIndex].timeDone = orderInfo[x].timeDone;
pendingOrderInfo[pendingIndex].typeTime = orderInfo[x].typeTime;
pendingOrderInfo[pendingIndex].priceOpen = orderInfo[x].priceOpen;
pendingOrderInfo[pendingIndex].tpPrice = orderInfo[x].tpPrice;
pendingOrderInfo[pendingIndex].slPrice = orderInfo[x].slPrice;

Anschließend berechnen wir die Anzahl der Pips für Take-Profit (TP) und Stop-Loss (SL), sofern diese angegeben sind. Dazu verwenden wir den Punktwert des Symbols, um die Anzahl der Pips zu bestimmen.

if(pendingOrderInfo[pendingIndex].tpPrice > 0)
  {
   double symbolPoint = SymbolInfoDouble(pendingOrderInfo[pendingIndex].symbol, SYMBOL_POINT);
   pendingOrderInfo[pendingIndex].tpPips =
      (int)MathAbs((pendingOrderInfo[pendingIndex].tpPrice - pendingOrderInfo[pendingIndex].priceOpen) / symbolPoint);
  }
if(pendingOrderInfo[pendingIndex].slPrice > 0)
  {
   double symbolPoint = SymbolInfoDouble(pendingOrderInfo[pendingIndex].symbol, SYMBOL_POINT);
   pendingOrderInfo[pendingIndex].slPips =
      (int)MathAbs((pendingOrderInfo[pendingIndex].slPrice - pendingOrderInfo[pendingIndex].priceOpen) / symbolPoint);
  }

Wir speichern auch zusätzliche Eigenschaften wie die magische Nummer des Auftrags, den Grund, den Füllungstyp, den Kommentar, das Anfangsvolumen und den Stop-Limit-Preis.

pendingOrderInfo[pendingIndex].magic = orderInfo[x].magic;
pendingOrderInfo[pendingIndex].reason = orderInfo[x].reason;
pendingOrderInfo[pendingIndex].typeFilling = orderInfo[x].typeFilling;
pendingOrderInfo[pendingIndex].comment = orderInfo[x].comment;
pendingOrderInfo[pendingIndex].volumeInitial = orderInfo[x].volumeInitial;
pendingOrderInfo[pendingIndex].priceStopLimit = orderInfo[x].priceStopLimit;

Nachdem alle Aufträge verarbeitet wurden, wird die Größe des Arrays pendingOrderInfo angepasst, um leere oder nicht verwendete Elemente zu entfernen und sicherzustellen, dass das Array nur die relevanten Daten der schwebenden Aufträge enthält.

ArrayResize(pendingOrderInfo, totalPendingOrdersFound);

Hier ist die vollständige Implementierung der Funktion SavePendingOrdersData() mit allen enthaltenen Codesegmenten.

void SavePendingOrdersData()
  {
//- Let us begin by scanning the orders and link them to different deals
   int totalOrderInfo = ArraySize(orderInfo);
   ArrayResize(pendingOrderInfo, totalOrderInfo);
   int totalPendingOrdersFound = 0, pendingIndex = 0;
   if(totalOrderInfo == 0)
     {
      return; //- No order data to process found, we can't go on. exit the function
     }

   for(int x = totalOrderInfo - 1; x >= 0; x--)
     {
      //- Check if it is a pending order and save its properties
      if(
         orderInfo[x].type == ORDER_TYPE_BUY_LIMIT || orderInfo[x].type == ORDER_TYPE_BUY_STOP ||
         orderInfo[x].type == ORDER_TYPE_SELL_LIMIT || orderInfo[x].type == ORDER_TYPE_SELL_STOP ||
         orderInfo[x].type == ORDER_TYPE_BUY_STOP_LIMIT || orderInfo[x].type == ORDER_TYPE_SELL_STOP_LIMIT
      )
        {
         totalPendingOrdersFound++;
         pendingIndex = totalPendingOrdersFound - 1;

         pendingOrderInfo[pendingIndex].type = orderInfo[x].type;
         pendingOrderInfo[pendingIndex].state = orderInfo[x].state;
         pendingOrderInfo[pendingIndex].positionId = orderInfo[x].positionId;
         pendingOrderInfo[pendingIndex].ticket = orderInfo[x].ticket;
         pendingOrderInfo[pendingIndex].symbol = orderInfo[x].symbol;
         pendingOrderInfo[pendingIndex].timeSetup = orderInfo[x].timeSetup;
         pendingOrderInfo[pendingIndex].expirationTime = orderInfo[x].expirationTime;
         pendingOrderInfo[pendingIndex].timeDone = orderInfo[x].timeDone;
         pendingOrderInfo[pendingIndex].typeTime = orderInfo[x].typeTime;
         pendingOrderInfo[pendingIndex].priceOpen = orderInfo[x].priceOpen;
         pendingOrderInfo[pendingIndex].tpPrice = orderInfo[x].tpPrice;
         pendingOrderInfo[pendingIndex].slPrice = orderInfo[x].slPrice;

         if(pendingOrderInfo[pendingIndex].tpPrice > 0)
           {
            double symbolPoint = SymbolInfoDouble(pendingOrderInfo[pendingIndex].symbol, SYMBOL_POINT);
            pendingOrderInfo[pendingIndex].tpPips =
               (int)MathAbs((pendingOrderInfo[pendingIndex].tpPrice - pendingOrderInfo[pendingIndex].priceOpen) / symbolPoint);
           }
         if(pendingOrderInfo[pendingIndex].slPrice > 0)
           {
            double symbolPoint = SymbolInfoDouble(pendingOrderInfo[pendingIndex].symbol, SYMBOL_POINT);
            pendingOrderInfo[pendingIndex].slPips =
               (int)MathAbs((pendingOrderInfo[pendingIndex].slPrice - pendingOrderInfo[pendingIndex].priceOpen) / symbolPoint);
           }

         pendingOrderInfo[pendingIndex].magic = orderInfo[x].magic;
         pendingOrderInfo[pendingIndex].reason = orderInfo[x].reason;
         pendingOrderInfo[pendingIndex].typeFilling = orderInfo[x].typeFilling;
         pendingOrderInfo[pendingIndex].comment = orderInfo[x].comment;
         pendingOrderInfo[pendingIndex].volumeInitial = orderInfo[x].volumeInitial;
         pendingOrderInfo[pendingIndex].priceStopLimit = orderInfo[x].priceStopLimit;

        }
     }
//--Resize the pendingOrderInfo array and delete all the indexes that have zero values
   ArrayResize(pendingOrderInfo, totalPendingOrdersFound);
  }


Funktion zum Drucken der Historie schwebender Aufträge

Die Funktion PrintPendingOrdersHistory() dient dazu, einen detaillierten Verlauf der ausgeführten oder stornierten schwebenden Aufträge innerhalb eines bestimmten Zeitrahmens anzuzeigen. Sie greift auf zuvor gespeicherte Daten aus dem Array pendingOrderInfo zu und druckt relevante Details für jede schwebende Order. Diese Funktion kann exportiert werden, sodass sie für externe Module oder MQL5-Anwendungen, die diese EX5-Bibliothek verwenden, zugänglich ist. Die Implementierung wird ähnlich aufgebaut sein wie bei anderen von uns entwickelten Druckfunktionen. Hier ist die vollständige Umsetzung mit ausführlichen Kommentaren zur Verdeutlichung.

void PrintPendingOrdersHistory(datetime fromDateTime, datetime toDateTime) export
  {
//- Get and save the pending orders history for the specified period
   GetHistoryData(fromDateTime, toDateTime, GET_PENDING_ORDERS_HISTORY_DATA);
   int totalPendingOrders = ArraySize(pendingOrderInfo);
   if(totalPendingOrders <= 0)
     {
      Print("");
      Print(__FUNCTION__, ": No pending orders history found for the specified period.");
      return; //- Exit the function
     }

   Print("");
   Print(__FUNCTION__, "-------------------------------------------------------------------------------");
   Print(
      "Found a total of ", totalPendingOrders,
      " pending orders filled or cancelled between (", fromDateTime, ") and (", toDateTime, ")."
   );

   for(int r = 0; r < totalPendingOrders; r++)
     {
      Print("---------------------------------------------------------------------------------------------------");
      Print("Pending Order #", (r + 1));
      Print("Symbol: ", pendingOrderInfo[r].symbol);
      Print("Time Setup: ", pendingOrderInfo[r].timeSetup);
      Print("Type: ", EnumToString(pendingOrderInfo[r].type));
      Print("Ticket: ", pendingOrderInfo[r].ticket);
      Print("State: ", EnumToString(pendingOrderInfo[r].state));
      Print("Time Done: ", pendingOrderInfo[r].timeDone);
      Print("Volume Initial: ", pendingOrderInfo[r].volumeInitial);
      Print("Price Open: ", pendingOrderInfo[r].priceOpen);
      Print("SL Price: ", pendingOrderInfo[r].slPrice, " (slPips: ", pendingOrderInfo[r].slPips, ")");
      Print("TP Price: ", pendingOrderInfo[r].tpPrice, " (slPips: ", pendingOrderInfo[r].slPips, ")");
      Print("Expiration Time: ", pendingOrderInfo[r].expirationTime);
      Print("Position ID: ", pendingOrderInfo[r].positionId);
      Print("Price Stop Limit: ", pendingOrderInfo[r].priceStopLimit);
      Print("Type Filling: ", EnumToString(pendingOrderInfo[r].typeFilling));
      Print("Type Time: ", EnumToString(pendingOrderInfo[r].typeTime));
      Print("Reason: ", EnumToString(pendingOrderInfo[r].reason));
      Print("Comment: ", pendingOrderInfo[r].comment);
      Print("Magic: ", pendingOrderInfo[r].magic);
      Print("");
     }
  }


Schlussfolgerung

In diesem Artikel haben wir untersucht, wie man MQL5 verwendet, um Transaktionsverlaufsdaten für Aufträge und Deals abzurufen. Sie haben gelernt, wie Sie diese Daten nutzen können, um die Historie geschlossener Positionen und schwebender Aufträge zu erstellen, einschließlich eines Prüfpfads, der den Lebenszyklus jeder geschlossenen Position verfolgt. Dazu gehören die Quelle, die Art des Abschlusses und andere wertvolle Details wie Nettogewinn, Pip-Gewinn, Pip-Wert für Stop-Loss und Take-Profit, Handelsdauer und mehr.

Wir haben auch die Kernfunktionen der Bibliothek History Manager EX5 entwickelt, die es uns ermöglicht, verschiedene Arten von historischen Daten abzufragen, zu speichern und zu kategorisieren . Diese grundlegenden Funktionen sind Teil des Bibliotheksmotors, der das Innenleben der Bibliothek steuert. Es gibt jedoch noch mehr zu tun. Die meisten Funktionen, die wir in diesem Artikel erstellt haben, sind vorbereitend und bilden die Grundlage für eine stärker nutzerorientierte Bibliothek.

Im nächsten Artikel werden wir die Bibliothek des History Manager EX5 durch die Einführung exportierbarer Funktionen zur Sortierung und Analyse historischer Daten auf der Grundlage allgemeiner Nutzeranforderungen erweitern. So können Sie beispielsweise die Eigenschaften der zuletzt geschlossenen Positionen abrufen, die zuletzt ausgeführten oder stornierten schwebenden Aufträge analysieren, die zuletzt geschlossene Position für ein bestimmtes Symbol überprüfen, den geschlossenen Gewinn des aktuellen Tages berechnen und die wöchentlichen Pip-Gewinne ermitteln.

Darüber hinaus werden wir fortschrittliche Sortier- und Analysemodule integrieren, um detaillierte Handelsberichte zu erstellen, die denen des MetaTrader 5 Strategy Testers ähneln. In diesen Berichten werden reale Daten aus dem Handelsverlauf analysiert, die einen Einblick in die Leistung eines Expert Advisors oder einer Handelsstrategie bieten. Sie können diese Daten auch programmatisch nach Parametern wie Symbolen oder magischen Zahlen filtern und sortieren.

Um eine reibungslose Implementierung zu ermöglichen, stellen wir eine umfassende Dokumentation für die History Manager EX5-Bibliothek mit praktischen Anwendungsbeispielen zur Verfügung. Diese Beispiele zeigen Ihnen, wie Sie die Bibliothek in Ihre Projekte integrieren und effektive Handelsanalysen durchführen können. Darüber hinaus werden wir einfache Expert Advisor-Beispiele und Schritt-für-Schritt-Demonstrationen einbeziehen, um Ihnen zu helfen, Ihre Handelsstrategien zu optimieren und die Möglichkeiten der Bibliothek voll auszuschöpfen.

Sie finden die angehängte Quellcode-Datei HistoryManager.mq5 am Ende dieses Artikels. Vielen Dank, dass Sie mir gefolgt sind, und ich wünsche Ihnen viel Erfolg bei Ihrem Handel und Ihrer MQL5-Programmierung!

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

Beigefügte Dateien |
HistoryManager.mq5 (33.95 KB)
Aufbau des Kerzenmodells Trend Constraint (Teil 9): Expert Advisor für mehrere Strategien (III) Aufbau des Kerzenmodells Trend Constraint (Teil 9): Expert Advisor für mehrere Strategien (III)
Willkommen zum dritten Teil unserer Trendserie! Heute werden wir uns mit der Verwendung von Divergenzen als Strategie zur Identifizierung optimaler Einstiegspunkte innerhalb des vorherrschenden Tagestrends beschäftigen. Wir werden auch einen nutzerdefinierten Gewinnsicherungsmechanismus einführen, der einem Trailing-Stop-Loss ähnelt, aber einzigartige Verbesserungen aufweist. Darüber hinaus werden wir den Experten Trend Constraint zu einer fortschrittlicheren Version ausbauen und eine neue Handelsausführungsbedingung einführen, die die bestehenden Bedingungen ergänzt. Im weiteren Verlauf werden wir die praktische Anwendung von MQL5 bei der Entwicklung von Algorithmen weiter erforschen und Ihnen tiefer gehende Einblicke und umsetzbare Techniken vermitteln.
Klassische Strategien neu interpretieren (Teil 12): EURUSD Ausbruchsstrategie Klassische Strategien neu interpretieren (Teil 12): EURUSD Ausbruchsstrategie
Begleiten Sie uns heute, wenn wir uns der Herausforderung stellen, eine profitable Ausbruchs-Handelsstrategie in MQL5 zu entwickeln. Wir haben das Währungspaar EURUSD ausgewählt und versucht, Kursausbrüche auf dem stündlichen Zeitrahmen zu handeln. Unser System hatte Schwierigkeiten, zwischen falschen Ausbrüchen und dem Beginn eines echten Trends zu unterscheiden. Wir haben unser System mit Filtern überlagert, die unsere Verluste minimieren und gleichzeitig unsere Gewinne erhöhen sollen. Am Ende haben wir unser System erfolgreich profitabel und weniger anfällig für falsche Ausbrüche gemacht.
Handelseinblicke über das Volumen: Trendbestätigung Handelseinblicke über das Volumen: Trendbestätigung
Die Enhanced Trend Confirmation Technique kombiniert Preisaktionen, Volumenanalysen und maschinelles Lernen, um echte Marktbewegungen zu identifizieren. Für die Handelsvalidierung sind sowohl Preisausbrüche als auch Volumensprünge (50 % über dem Durchschnitt) erforderlich, während ein neuronales LSTM-Netzwerk für zusätzliche Bestätigung sorgt. Das System verwendet eine ATR-basierte Positionsgröße und ein dynamisches Risikomanagement, wodurch es an verschiedene Marktbedingungen angepasst werden kann und gleichzeitig falsche Signale herausfiltert.
Risikomodell für ein Portfolio unter Verwendung des Kelly-Kriteriums und der Monte-Carlo-Simulation Risikomodell für ein Portfolio unter Verwendung des Kelly-Kriteriums und der Monte-Carlo-Simulation
Seit Jahrzehnten verwenden Händler die Formel des Kelly-Kriteriums, um den optimalen Anteil des Kapitals für eine Investition oder eine Wette zu bestimmen, um das langfristige Wachstum zu maximieren und gleichzeitig das Risiko des Ruins zu minimieren. Das blinde Befolgen des Kelly-Kriteriums auf der Grundlage der Ergebnisse eines einzigen Backtests ist jedoch für einzelne Händler oft gefährlich, da beim Live-Handel der Handelsvorsprung im Laufe der Zeit abnimmt und die vergangene Leistung keine Vorhersage für das zukünftige Ergebnis ist. In diesem Artikel werde ich einen realistischen Ansatz für die Anwendung des Kelly-Kriteriums für die Risikoallokation eines oder mehrerer EAs in MetaTrader 5 vorstellen und dabei die Ergebnisse der Monte-Carlo-Simulation von Python einbeziehen.