Individuelle Darstellung der Handelshistorie und Erstellung von Berichtsdiagrammen

Andrey Azatskiy | 24 September, 2018

Einführung

Einer der wichtigsten Aspekte im Finanzhandel ist die Möglichkeit, die Performance zu kontrollieren und die eigenen Handelsgeschichte zu analysieren. Mit den historische Daten können Sie die eigene Handelsdynamik zu kontrollieren und die Gesamtleistung der verwendeten Strategie bewerten. Dies ist für alle Händler nützlich: für diejenigen, die Operationen manuell durchführen und für algorithmische Händler. In diesem Artikel schlage ich vor, Werkzeuge zu entwickeln, die eine solche Funktion implementieren. 

Das Kernstück jeder Handelsaktivität ist der Handelsalgorithmus, der die Gewinn- und Verlustkurve (PL-Kurve) bildet. Ein solcher Algorithmus kann mit einem synthetischen Vermögenswert verglichen werden, dessen Wert sich im Verhältnis zum Basiswert (d.h. dem gehandelten Instrument) bildet. Beispielsweise wird im Optionshandel die Formel von Black-Scholes verwendet, um einen solchen synthetischen Vermögenswert basierend auf dem Basispreis des Basiswertes zu berechnen. Aber es gibt keine solche Formel für einen Handelsalgorithmus. Dementsprechend kann der Start eines Algorithmus mit einer Kaufposition eines synthetischen Symbols verglichen werden, dessen Gewinn- und Verlustkurve durch den programmierten Algorithmus gebildet wird. Der von diesem "Vermögenswert" gebildete Gewinn kann in verschiedenen Zeiträumen unterschiedlich sein. Auch wenn es mit einem ökonometrischen Modell bewertet werden kann, kann dieses Modell nicht vereinheitlicht werden. Aber wie kann man dieses Vermögen und unsere Handelsstufen verfolgen? Eine der geeigneten Lösungen ist die rückwirkende Kontrolle des algorithmisierten Handels und das Erkennen von Abweichungen von erwarteten Ergebnissen.

Ich werde keine Ratschläge geben, wie man Algorithmen analysiert, sondern nur eine Reihe von Methoden zur Verfügung stellen, die es ermöglichen, das komplette Bild Ihrer Handelsgeschichte zu präsentieren. Basierend auf den gewonnenen Daten können Sie komplexe ökonometrische Modelle erstellen, Wahrscheinlichkeitsmerkmale berechnen und verschiedene Schlussfolgerungen ziehen.

Dieser Artikel wird in 2 Kapitel unterteilt. Im ersten (technischen) Kapitel werde ich Methoden zur Erstellung von Handelsberichten beschreiben, die auf dem Großteil der Informationen basieren, die in Ihren Terminals gespeichert sind. In diesem Abschnitt werden die für die Analyse genutzten Quelldaten verwendet. Im zweiten Abschnitt werden wir uns mit den wichtigsten Werten befassen, anhand derer wir die Handelsrückschau auf die ausgewählten Daten bewerten. Die Datenerfassung kann variiert werden: alle Vermögenswerte oder ein ausgewähltes Symbol, für die gesamte verfügbare Historie oder für einen bestimmten Zeitraum. Die Ergebnisse der Analyse werden in einer eigenen Datei präsentiert und im Terminal kurz visualisiert.

Ich habe Daten aus meiner realen Handelsgeschichte für die Analysebeispiele verwendet. Die Codebeispiele für die Umsetzung wurden mit Hilfe einer Testphase erstellt, die ich gezielt durch den Handel auf einem Demo-Konto angesammelt habe.



Kapitel 1. Aufbereitung der Daten für die statistische Analyse des Handels

Jede Analyse beginnt mit der Aufbereitung der Quelldaten. Hier geht es um die Handelsgeschichte für einen bestimmten Zeitraum. Das MetaTrader 5 Terminal speichert detaillierte Berichte. Aber manchmal können zu viele Informationen mit vielen Details eher behindern als helfen. In diesem Abschnitt werden wir versuchen, eine lesbare, kurze und informative Stichprobe der Handelsgeschichte zu erstellen, die weiter verarbeitet und analysiert werden kann.

Handelshistorie des MetaTrader 5 und verwandte Verarbeitungsmethoden

In der Regel wird beim Laden der Handelshistorie eine große Anzahl von Datensätzen erstellt. Jede Position besteht aus Entry/Exit-Deals, die wiederum in mehreren Stufen durchgeführt werden können. Außerdem gibt es Situationen, in denen die Position schrittweise skaliert und erneut gekauft wird. Darüber hinaus hat die Geschichte spezielle Linien für "virtuelle Geschäfte". Solche Geschäfte können Folgendes beinhalten:

Eine Schwierigkeit mit den Berichten hängt damit zusammen, dass die Historie nach der Öffnungszeit des Geschäfts sortiert ist. Deals können eine unterschiedliche Lebensdauer haben, daher kann eine angezeigte Reihung der Informationen nach dem Schließen der Positionen verzerrt sein. So wird beispielsweise die erste Position am 1. Juni eröffnet und am 5. Juni geschlossen. Das zweite wurde am 2. Juni eröffnet und am selben Tag geschlossen. Wir fixieren den Gewinn/Verlust dieses Geschäfts früher als den ersten, aber in der Tabelle wird es später sein, da es später eröffnet wurde.

Dementsprechend ist die in diesem Format aufgezeichnete Handelshistorie für die Handelsanalyse nicht geeignet. Es spiegelt jedoch die gesamte Historie der Vorgänge auf dem analysierten Konto wider. MQL5 enthält einen nützlichen Satz von Werkzeugen für die Arbeit mit dem beschriebenen Datenarray. Wir werden dieses Tool nutzen, um die Daten in einer lesbaren Form darzustellen. Zuerst müssen wir alle Daten als Handelshistorie ordnen, sortiert nach gehandelten Vermögenswerten und Geschäften. Lassen Sie uns zu diesem Zweck die folgenden Strukturen schaffen:

//+------------------------------------------------------------------+
//| Datenstruktur eines gewählten Deals                              |
//+------------------------------------------------------------------+
struct DealData
  {
   long              ticket;            // Ticket der Position
   long              order;             // Auftragsnummer der Position
   datetime          DT;                // Eröffnungsdatum der Position
   long              DT_msc;            // Eröffnungszeit der Position in Millisekunden
   ENUM_DEAL_TYPE    type;              // Positionstyp
   ENUM_DEAL_ENTRY   entry;             // Eröffnungspreis der Position
   long              magic;             // eindeutige Magicnummer der Position
   ENUM_DEAL_REASON  reason;            // Art des Auftrages/Platzierung
   long              ID;                // Positions-ID
   double            volume;            // Positionsvolumen (Lots)
   double            price;             // Eröffnungspreis der Position
   double            comission;         // Kommission
   double            swap;              // Swap
   double            profit;            // Gewinn/Verlust
   string            symbol;            // Symbol
   string            comment;           // Kommentar der Eröffnung
   string            ID_external;       // Externe ID 
  };
//+------------------------------------------------------------------+
//| Struktur für alle Deals bestimmter Positionen                    |
//| ausgewählt über die ID                                           |
//+------------------------------------------------------------------+
struct DealKeeper
  {
   DealData          deals[];           /* Liste aller Deals der Position (oder mehrerer Positionen im Falle einer Umkehr)*/
   string            symbol;            // Symbol
   long              ID;                // ID der Position(en)
   datetime          DT_min;            // Eröffnungszeitpunkt
   datetime          DT_max;            // Zeitpunkt des Schließens
  };

Aus dem Code ist ersichtlich, dass die Struktur DealData eine detaillierte Beschreibung der Geschäftsparameter enthält.

Unsere Hauptstruktur wird DealKeeper sein. Es enthält die Beschreibung der Position und aller darin enthaltenen Geschäfte. Der Hauptfilter einer Position im Terminal ist ihre ID, die für alle Geschäfte innerhalb der Position unverändert bleibt. Wenn also eine Position umgekehrt wird, bleibt die ID erhalten, aber die Richtung wird auf das Gegenteil geändert, weshalb die Struktur DealKeeper zwei Positionen enthält. Das Ausfüllen von DealKeeper durch unserem Code wird anhand der ID der Positionen gefiltert.

Lassen Sie uns im Detail betrachten, wie die Struktur ausgefüllt wird. Dies geschieht durch die Klasse CDealHistoryGetter, und zwar mit ihrer Funktion getHistory:.

//+-----------------------------------------------------------------------+
//| Klasse, die die Handelshistorie des Terminals lädt und                |
//| leicht lesbar macht                                                   |
//+-----------------------------------------------------------------------+
class CDealHistoryGetter
  {
public:
   bool              getHistory(DealKeeper &deals[],datetime from,datetime till);                // Rückgabe aller Daten der historischen Deals  
   bool              getIDArr(ID_struct &ID_arr[],datetime from,datetime till);                  // Rückgabe des Arrays mit den eindeutigen IDs
   bool              getDealsDetales(DealDetales &ans[],datetime from,datetime till);            // Rückgabe des Arrays der Deals, in dem jeder Zeile einem Deal entspricht.
private:

   void              addArray(ID_struct &Arr[],const ID_struct &value);                          // Eintragen in den dyn. Array
   void              addArray(DealKeeper &Arr[],const DealKeeper &value);                        // Eintragen in den dyn. Array
   void              addArray(DealData &Arr[],const DealData &value);                            // Eintragen in den dyn. Array
   void              addArr(DealDetales &Arr[],DealDetales &value);                              // Eintragen in den dyn. Array
   void              addArr(double &Arr[],double value);                                         // Eintragen in den dyn. Array

/*
    Wenn es Ausgaben in InOut gibt, dann wird es mehr als einen Eintrag im inputParam geben. 
    Wenn es keine Ausgaben von InOut gibt, dann wird es in InputParam nur eine Transaktion geben!  
*/
   void              getDeals_forSelectedKeeper(DealKeeper &inputParam,DealDetales &ans[]);      // Einzeleintrag einer gewählten Position für alle Positionen in inputParam
   double            MA_price(double &prices[],double &lots[]);                                  // Berechnen des gewichtenen Durchschnitts der Eröffnungspreise
   bool              isBorderPoint(DealData &data,BorderPointType &type);                        // Info. ob es ein Eckpunkt ist inkl. des Typs des Eckpunktes
   ENUM_DAY_OF_WEEK  getDay(datetime DT);                                                        // Wochentag des Datums
   double            calcContracts(double &Arr[],GetContractType type);                          // Info. über das Volumen der letzten Position
  };

Betrachten wir die Implementierung:

//+------------------------------------------------------------------+
//| Rückgabe aller Daten der historischen Deals                      |
//+------------------------------------------------------------------+
bool CDealHistoryGetter::getHistory(DealKeeper &deals[],datetime from,datetime till)
  {
   ArrayFree(deals);                                     // Freigeben der Ergebnisarrays
   ID_struct ID_arr[];
   if(getIDArr(ID_arr,from,till))                        // Holen der eindeutigen ID
     {
      int total=ArraySize(ID_arr);
      for(int i=0;i<total;i++)                           // Schleife über alle IDs
        {
         DealKeeper keeper;                              // Keeper der Deal der Positionen
         keeper.ID=ID_arr[i].ID;
         keeper.symbol=ID_arr[i].Symb;
         keeper.DT_max = LONG_MIN;
         keeper.DT_min = LONG_MAX;
         if(HistorySelectByPosition(ID_arr[i].ID))       // Auswahl aller Deals der angegebenen ID
           {

Erstens brauchen wir die eindeutigen IDs der Positionen. Dafür durchlaufen wir die Deal-IDs in einer kontinuierlichen Schleife und bilden eine Struktur mit zwei Feldern: Symbol und Positions-ID. Die erhaltenen Daten werden für den Betrieb der Funktion getHistory benötigt. 

Die Sprache MQL5 hat eine sehr nützliche Funktion HistorySelectByPosition, die die gesamte Historie der Geschäfte mit der übergebenen ID selektiert. Es wird uns helfen, unnötige Vorgänge (Kontobearbeitung, Ein- und Auszahlungsvorgänge, etc.) aus der Liste zu entfernen. Dadurch wird eine gebildete Historie aller Änderungen der Position, die bei geöffneter Position aufgetreten sind, gespeichert. Die Daten werden nach Datum und Uhrzeit sortiert. Wir müssen nur die Funktion HistoryDealsTotal verwenden, die die Gesamtzahl der zuvor geschriebenen Geschäfte mit der angegebenen ID zurückgibt.

int total_2=HistoryDealsTotal();
            for(int n=0;n<total_2;n++)                        // Schleife über alle gewählten Deals
              {
               long ticket=(long)HistoryDealGetTicket(n);
               DealData data;
               data.ID=keeper.ID;
               data.symbol=keeper.symbol;
               data.ticket= ticket;

               data.DT=(datetime)HistoryDealGetInteger(ticket,DEAL_TIME);
               keeper.DT_max=MathMax(keeper.DT_max,data.DT);
               keeper.DT_min=MathMin(keeper.DT_min,data.DT);
               data.order= HistoryDealGetInteger(ticket,DEAL_ORDER);
               data.type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket,DEAL_TYPE);
               data.DT_msc=HistoryDealGetInteger(ticket,DEAL_TIME_MSC);
               data.entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket,DEAL_ENTRY);
               data.magic = HistoryDealGetInteger(ticket,DEAL_MAGIC);
               data.reason= (ENUM_DEAL_REASON)HistoryDealGetInteger(ticket,DEAL_REASON);
               data.volume= HistoryDealGetDouble(ticket,DEAL_VOLUME);
               data.price = HistoryDealGetDouble(ticket,DEAL_PRICE);
               data.comission=HistoryDealGetDouble(ticket,DEAL_COMMISSION);
               data.swap=HistoryDealGetDouble(ticket,DEAL_SWAP);
               data.profit=HistoryDealGetDouble(ticket,DEAL_PROFIT);
               data.comment=HistoryDealGetString(ticket,DEAL_COMMENT);
               data.ID_external=HistoryDealGetString(ticket,DEAL_EXTERNAL_ID);

               addArray(keeper.deals,data);                  // Eintragen der Deals
              }

            if(ArraySize(keeper.deals)>0)
               addArray(deals,keeper);                       // Eintragen der Position
           }
        }
      return ArraySize(deals) > 0;
     }
   else
      return false;                                          // Wenn es keine eindeutige ID gibt
  }

Wenn wir also jede eindeutige ID durchgehen, bilden wir einen Array mit Daten, die die Position beschreiben. Das Array aus den Strukturen DealKeeper spiegelt eine detaillierte Handelshistorie des laufenden Kontos für den gewünschten Zeitraum wider.

Aufbereitung der Daten für die Analyse

Im vorherigen Abschnitt haben wir Daten erhalten. Exportieren wir die Daten in eine Datei. Nachfolgend finden Sie die Liste der Geschäfte für eine der analysierten Positionen:

Ticket Auftrag DT DT msc Typ Eröffnung Magic Ursache ID Volumen Preis Kommission Swap Gewinn Symbol Kommentar externe ID
10761601 69352663 23.11.2017 17:41 1,51146E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 506789 DEAL_REASON_EXPERT 69352663 1 58736 -0,5 0 0 Si-12.17 Open test position 23818051
10761602 69352663 23.11.2017 17:41 1,51146E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 506789 DEAL_REASON_EXPERT 69352663 1 58737 -0,5 0 0 Si-12.17 Open test position 23818052
10766760 0 24.11.2017 13:00 1,51153E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58682 0 0 -109 Si-12.17 [variation margin close]
10766761 0 24.11.2017 13:00 1,51153E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58682 0 0 0 Si-12.17 [variation margin open]
10769881 0 24.11.2017 15:48 1,51154E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58649 0 0 -66 Si-12.17 [variation margin close]
10769882 0 24.11.2017 15:48 1,51154E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58649 0 0 0 Si-12.17 [variation margin open]
10777315 0 27.11.2017 13:00 1,51179E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58420 0 0 -458 Si-12.17 [variation margin close]
10777316 0 27.11.2017 13:00 1,51179E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58420 0 0 0 Si-12.17 [variation margin open]
10780552 0 27.11.2017 15:48 1,5118E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58417 0 0 -6 Si-12.17 [variation margin close]
10780553 0 27.11.2017 15:48 1,5118E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58417 0 0 0 Si-12.17 [variation margin open]
10790453 0 28.11.2017 13:00 1,51187E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58589 0 0 344 Si-12.17 [variation margin close]
10790454 0 28.11.2017 13:00 1,51187E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58589 0 0 0 Si-12.17 [variation margin open]
10793477 0 28.11.2017 15:48 1,51188E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58525 0 0 -128 Si-12.17 [variation margin close]
10793478 0 28.11.2017 15:48 1,51188E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58525 0 0 0 Si-12.17 [variation margin open]
10801186 0 29.11.2017 13:00 1,51196E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58515 0 0 -20 Si-12.17 [variation margin close]
10801187 0 29.11.2017 13:00 1,51196E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58515 0 0 0 Si-12.17 [variation margin open]
10804587 0 29.11.2017 15:48 1,51197E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58531 0 0 32 Si-12.17 [variation margin close]
10804588 0 29.11.2017 15:48 1,51197E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58531 0 0 0 Si-12.17 [variation margin open]
10813418 0 30.11.2017 13:00 1,51205E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58843 0 0 624 Si-12.17 [variation margin close]
10813419 0 30.11.2017 13:00 1,51205E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58843 0 0 0 Si-12.17 [variation margin open]
10816400 0 30.11.2017 15:48 1,51206E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58609 0 0 -468 Si-12.17 [variation margin close]
10816401 0 30.11.2017 15:48 1,51206E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58609 0 0 0 Si-12.17 [variation margin open]
10824628 0 01.12.2017 13:00 1,51213E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58864 0 0 510 Si-12.17 [variation margin close]
10824629 0 01.12.2017 13:00 1,51213E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58864 0 0 0 Si-12.17 [variation margin open]
10828227 0 01.12.2017 15:48 1,51214E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58822 0 0 -84 Si-12.17 [variation margin close]
10828228 0 01.12.2017 15:48 1,51214E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58822 0 0 0 Si-12.17 [variation margin open]
10838074 0 04.12.2017 13:00 1,51239E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59093 0 0 542 Si-12.17 [variation margin close]
10838075 0 04.12.2017 13:00 1,51239E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59093 0 0 0 Si-12.17 [variation margin open]
10840722 0 04.12.2017 15:48 1,5124E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59036 0 0 -114 Si-12.17 [variation margin close]
10840723 0 04.12.2017 15:48 1,5124E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59036 0 0 0 Si-12.17 [variation margin open]
10848185 0 05.12.2017 13:00 1,51248E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58793 0 0 -486 Si-12.17 [variation margin close]
10848186 0 05.12.2017 13:00 1,51248E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58793 0 0 0 Si-12.17 [variation margin open]
10850473 0 05.12.2017 15:48 1,51249E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58881 0 0 176 Si-12.17 [variation margin close]
10850474 0 05.12.2017 15:48 1,51249E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58881 0 0 0 Si-12.17 [variation margin open]
10857862 0 06.12.2017 13:00 1,51257E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59181 0 0 600 Si-12.17 [variation margin close]
10857863 0 06.12.2017 13:00 1,51257E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59181 0 0 0 Si-12.17 [variation margin open]
10860776 0 06.12.2017 15:48 1,51258E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59246 0 0 130 Si-12.17 [variation margin close]
10860777 0 06.12.2017 15:48 1,51258E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59246 0 0 0 Si-12.17 [variation margin open]
10869047 0 07.12.2017 13:00 1,51265E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59325 0 0 158 Si-12.17 [variation margin close]
10869048 0 07.12.2017 13:00 1,51265E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59325 0 0 0 Si-12.17 [variation margin open]
10871856 0 07.12.2017 15:48 1,51266E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59365 0 0 80 Si-12.17 [variation margin close]
10871857 0 07.12.2017 15:48 1,51266E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59365 0 0 0 Si-12.17 [variation margin open]
10879894 0 08.12.2017 13:01 1,51274E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59460 0 0 190 Si-12.17 [variation margin close]
10879895 0 08.12.2017 13:01 1,51274E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59460 0 0 0 Si-12.17 [variation margin open]
10882283 0 08.12.2017 15:48 1,51275E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59421 0 0 -78 Si-12.17 [variation margin close]
10882284 0 08.12.2017 15:48 1,51275E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59421 0 0 0 Si-12.17 [variation margin open]
10888014 0 11.12.2017 13:00 1,513E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59318 0 0 -206 Si-12.17 [variation margin close]
10888015 0 11.12.2017 13:00 1,513E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59318 0 0 0 Si-12.17 [variation margin open]
10890195 0 11.12.2017 15:48 1,51301E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59280 0 0 -76 Si-12.17 [variation margin close]
10890196 0 11.12.2017 15:48 1,51301E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59280 0 0 0 Si-12.17 [variation margin open]
10895808 0 12.12.2017 13:00 1,51308E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58920 0 0 -720 Si-12.17 [variation margin close]
10895809 0 12.12.2017 13:00 1,51308E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58920 0 0 0 Si-12.17 [variation margin open]
10897839 0 12.12.2017 15:48 1,51309E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58909 0 0 -22 Si-12.17 [variation margin close]
10897840 0 12.12.2017 15:48 1,51309E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58909 0 0 0 Si-12.17 [variation margin open]
10903172 0 13.12.2017 13:00 1,51317E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59213 0 0 608 Si-12.17 [variation margin close]
10903173 0 13.12.2017 13:00 1,51317E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59213 0 0 0 Si-12.17 [variation margin open]
10905906 0 13.12.2017 15:48 1,51318E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59072 0 0 -282 Si-12.17 [variation margin close]
10905907 0 13.12.2017 15:48 1,51318E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59072 0 0 0 Si-12.17 [variation margin open]
10911277 0 14.12.2017 13:00 1,51326E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58674 0 0 -796 Si-12.17 [variation margin close]
10911278 0 14.12.2017 13:00 1,51326E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58674 0 0 0 Si-12.17 [variation margin open]
10912285 71645351 14.12.2017 14:48 1,51326E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 506789 DEAL_REASON_EXPERT 69352663 1 58661 -0,5 0 -13 Si-12.17 PartialClose position_2 25588426
10913632 0 14.12.2017 15:48 1,51327E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 1 58783 0 0 109 Si-12.17 [variation margin close]
10913633 0 14.12.2017 15:48 1,51327E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 1 58783 0 0 0 Si-12.17 [variation margin open]
10919412 0 15.12.2017 13:00 1,51334E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 1 58912 0 0 129 Si-12.17 [variation margin close]
10919413 0 15.12.2017 13:00 1,51334E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 1 58912 0 0 0 Si-12.17 [variation margin open]
10921766 0 15.12.2017 15:48 1,51335E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 1 58946 0 0 34 Si-12.17 [variation margin close]
10921767 0 15.12.2017 15:48 1,51335E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 1 58946 0 0 0 Si-12.17 [variation margin open]
10927382 0 18.12.2017 13:00 1,5136E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 1 58630 0 0 -316 Si-12.17 [variation margin close]
10927383 0 18.12.2017 13:00 1,5136E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 1 58630 0 0 0 Si-12.17 [variation margin open]
10929913 0 18.12.2017 15:48 1,51361E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 1 58664 0 0 34 Si-12.17 [variation margin close]
10929914 0 18.12.2017 15:48 1,51361E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 1 58664 0 0 0 Si-12.17 [variation margin open]
10934874 0 19.12.2017 13:00 1,51369E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 1 58635 0 0 -29 Si-12.17 [variation margin close]
10934875 0 19.12.2017 13:00 1,51369E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 1 58635 0 0 0 Si-12.17 [variation margin open]
10936988 0 19.12.2017 15:48 1,5137E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 1 58629 0 0 -6 Si-12.17 [variation margin close]
10936989 0 19.12.2017 15:48 1,5137E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 1 58629 0 0 0 Si-12.17 [variation margin open]
10941561 0 20.12.2017 13:00 1,51377E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 1 58657 0 0 28 Si-12.17 [variation margin close]
10941562 0 20.12.2017 13:00 1,51377E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 1 58657 0 0 0 Si-12.17 [variation margin open]
10943405 0 20.12.2017 15:48 1,51378E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 1 58684 0 0 27 Si-12.17 [variation margin close]
10943406 0 20.12.2017 15:48 1,51378E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 1 58684 0 0 0 Si-12.17 [variation margin open]
10948277 0 21.12.2017 13:00 1,51386E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 1 58560 0 0 -124 Si-12.17 [variation margin close]
10948278 0 21.12.2017 13:00 1,51386E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 1 58560 0 0 0 Si-12.17 [variation margin open]
10949780 0 21.12.2017 15:45 1,51387E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_CLIENT 69352663 1 58560 0 0 0 Si-12.17 [instrument expiration] 26163141

Das verwendete Beispiel basiert auf der Historie der USDRUR Futures-Geschäfte, die auf einem Demo-Konto im FORTS-Markt durchgeführt wurden. Aus der Tabelle ist ersichtlich, dass die Bestandserfassung in zwei Aufträge mit gleichem Losgröße aufgeteilt wurde. Der Exit wurde auch in zwei Deals durchgeführt. Die IDs der Orders, die Deals für jeden Ein- und Ausgang eröffnet haben (außer der Variation Margin), waren ungleich Null. Aber das letzte Geschäft, das die Position geschlossen hat, kam aus einer Order mit einer Null-ID. Dies geschah, weil die Position nicht manuell geschlossen wurde, sondern wegen der Verfallszeit der Position geschlossen wurde.

Dies ist eine visuell dargestellte Geschichte, die Geschäfte (einschließlich der "virtuellen" Aufträge) widerspiegelt, die innerhalb einer Position ausgeführt wurden. Sie ist jedoch für die Endlagenanalyse noch ungünstig und bedarf weiterer Verbesserungen. Wir benötigen eine Tabelle mit jeder Zeile, die das Finanzergebnis einer Position widerspiegelt und entsprechende Informationen liefert.

Erstellen wir die folgende Struktur:

//+------------------------------------------------------------------+
//| Structure, which stores the financial result and                 |
//| main information about the selected position                     |
//+------------------------------------------------------------------+
struct DealDetales
  {
   string            symbol;                // Symbol 
   datetime          DT_open;               // Eröffnungszeitpunkt
   ENUM_DAY_OF_WEEK  day_open;              // Wochentag der Positionseröffnung
   datetime          DT_close;              // Zeitpunkt des Schließens
   ENUM_DAY_OF_WEEK  day_close;             // Wochentag des Zeitpunkts des Schließens der Position
   double            volume;                // Volumen (lots)
   bool              isLong;                // Vorzeichen Long/Short
   double            price_in;              // Eröffnungspreis der Position
   double            price_out;             // Ausstiegspreis der Position
   double            pl_oneLot;             // Gewinn/Verlust bei einem Lot
   double            pl_forDeal;            // Erzielter realer Gewinn/Verlust mit Berücksichtigung der Kommission
   string            open_comment;          // Kommentar bei der Eröffnung
   string            close_comment;         // Kommentar beim Schließen
  };

Die Struktur wird durch die Methode getDealsDetales gebildet. Sie verwendet drei wichtige private Methoden: 

Betrachten wir die Methode MA_price im Detail. Wenn die offene Position beispielsweise mehrere Kontrakte zu unterschiedlichen Preisen betraf, wäre es falsch, den Preis des allerersten Geschäfts als Positionseröffnungspreis zu verwenden. Dies würde zu Fehlern in weiteren Berechnungen führen. Wir verwenden daher einen gewichteten Durchschnittswert. Wir werden die Preise der Eröffnung und des Schließens der Positionen verwenden und mit dem Volumen der geschlossenen Position gewichten. Auf diese Weise wird die Logik in einem Code implementiert:

//+------------------------------------------------------------------+
//| Berechnen des gewichteten Durchschnitts der Eröffnungspreise     |
//+------------------------------------------------------------------+
double CDealHistoryGetter::MA_price(double &prices[],double &lots[])
  {
   double summ=0;                           // Summe der gewichteten Preise
   double delemetr=0;                       // Summe der gewichteten Preise
   int total=ArraySize(prices);
   for(int i=0;i<total;i++)                 // Summenschleife
     {
      summ+=(prices[i]*lots[i]);
      delemetr+=lots[i];
     }
   return summ/delemetr;                    // Durchschnittsberechnung
  }
Was die gehandelten Losgrößen betrifft: Dies ist ein komplizierterer Fall, aber ist nicht schwer zu verstehen. Nehmen wir ein Beispiel für ein dynamisches Positionsmanagement: Wir fügen die Anzahl der Losgrößen schrittweise hinzu oder reduzieren sie:

In Out
Open (t1) 1 0
t2 2 0
t3 5 3
t4 1 0
t5 0 1
t6 1 0
t7 0 1
t8 1 0
t9 0 1
Close (t10) 0 5
Gesamt  11  11 

Während der Laufzeit der Position haben wir den gehandelten Vermögenswert mehrfach gekauft und verkauft (manchmal abwechselnd, manchmal fast gleichzeitig). So hatten wir 11 Kauf- und Verkaufslose. Dies bedeutet jedoch nicht, dass das maximal erreichte Positionsvolumen 11 Losgrößen entsprach. Notieren wir uns alle Ein- und Ausgänge: Positionserhöhung und Einträge haben das Vorzeichen +, Positionsschließungen und -reduzierungen haben das Vorzeichen -. Wir werden die Summe ähnlich wie eine Gewinn/Verlust-Kurve berechnen.


Deal-Volumen Positionsvolumen
deal 1 1 1
deal  2 2 3
deal 3 5 8
deal 4 -3 5
deal 5 1 6
deal 6 -1 5
deal 7 1 6
deal 8 -1 5
deal 9 1 6
deal 10 -1 5
deal 11  -5  0

Aus dieser Tabelle ist ersichtlich, dass das maximal erreichte Volumen 8 Lose betrug. Dies ist der benötigte Wert.

Hier ist der Code der Funktion, der die Anzahl der Losgrößen wiedergibt:

//+------------------------------------------------------------------+
//| Calculating the real volume of the position and                  |
//| obtaining information about the volume of the last position      |
//+------------------------------------------------------------------+
double CDealHistoryGetter::calcContracts(double &Arr[],GetContractType type)
  {
   int total;

   if((total=ArraySize(Arr))>1)                        // Wenn der Array größer Eins ist
     {
      double lotArr[];
      addArr(lotArr,Arr[0]);                           // Eintragen des ersten Lots in das Array
      for(int i=1;i<total;i++)                         // Schleife ab dem zweiten Arrayelement
         addArr(lotArr,(lotArr[i-1]+Arr[i]));          // Eintragen der Summe von vorheriger und aktueller Losgröße in das Array (lotArr[i-1]+Arr[i]))

      if(type==GET_REAL_CONTRACT)
         return lotArr[ArrayMaximum(lotArr)];          // Rückgabe des maximal gehandelten Lots
      else
         return lotArr[ArraySize(lotArr)-1];           // Rückgabe des zuletzt gehandelten Lots
     }
   else
      return Arr[0];
  }

Zusätzlich zur einfachen Zählung der Lose zeigt die Funktion das letzte aktive Los an (in unserem Beispiel ist es gleich 5).

Betrachten wir die Funktion isBorderPoint, die zum Filtern unnötiger Geschäfte erstellt wurde. Basierend auf der Datenstruktur können wir die Bedeutung eines Geschäfts anhand von 4 Variablen bestimmen:

Erstellen der Enumeration:

//+--------------------------------------------------------------------+
//| Hilfsenumeration für den Typ des bestimmten Datensatzes            |
//| Der Datensatz ist die Struktur DealData                            |
//+--------------------------------------------------------------------+
enum BorderPointType
  {
   UsualInput,          // normaler Eröffnungstyp (DEAL_ENTRY_IN) - der Anfang eines Deals
   UsualOutput,         // normaler Ausstiegstyp (DEAL_ENTRY_OUT) - das Ende des Deals
   OtherPoint,          // Saldenbewegungen, Positionskorrektur, Auszahlungen, Margenvariation — werden ignoriert
   InOut,               // Umkehrposition (DEAL_ENTRY_INOUT)
   OutBy                // Schließen einer Position mit einer umgekehrten Order (DEAL_ENTRY_OUT_BY)
  };
Wir brauchen vier der fünf oben vorgestellten Varianten. Alle zu ignorierenden Geschäfte werden in OtherPoint gesammelt. Die Kombinationen der Parameter für jede Variante einer Aufzählung sind in der Tabelle dargestellt. Der Funktionscode ist in den untenstehenden Dateien verfügbar. 
Enumeration variant Order ID ENUM_DEAL_ENTRY ENUM_DEAL_TYPE ENUM_DEAL_REASON
UsualInput  >0 DEAL_ENTRY_IN DEAL_TYPE_BUY DEAL_REASON_CLIENT
DEAL_REASON_EXPERT



DEAL_TYPE_SELL DEAL_REASON_WEB


DEAL_REASON_MOBILE



UsualOut >=0(=0 if closed by expiration) DEAL_ENTRY_OUT DEAL_TYPE_BUY DEAL_REASON_CLIENT
DEAL_REASON_EXPERT



DEAL_REASON_WEB



DEAL_TYPE_SELL DEAL_REASON_MOBILE


DEAL_REASON_SL



DEAL_REASON_TP



DEAL_REASON_SO



OtherPoint  0 DEAL_ENTRY_IN DEAL_TYPE_BUY DEAL_REASON_ROLLOVER
DEAL_TYPE_SELL



DEAL_TYPE_BALANCE



DEAL_TYPE_CREDIT



DEAL_TYPE_CHARGE



DEAL_TYPE_CORRECTION



DEAL_TYPE_BONUS DEAL_REASON_VMARGIN


DEAL_TYPE_COMMISSION



DEAL_TYPE_COMMISSION_DAILY



DEAL_ENTRY_OUT DEAL_TYPE_COMMISSION_MONTHLY


DEAL_TYPE_COMMISSION_AGENT_DAILY



DEAL_TYPE_COMMISSION_AGENT_MONTHLY



DEAL_TYPE_INTEREST DEAL_REASON_SPLIT


DEAL_TYPE_BUY_CANCELED



DEAL_TYPE_SELL_CANCELED



DEAL_DIVIDEND



DEAL_DIVIDEND_FRANKED



DEAL_TAX



InOut  >0 DEAL_ENTRY_INOUT  DEAL_TYPE_BUY DEAL_REASON_CLIENT
DEAL_REASON_EXPERT



DEAL_TYPE_SELL DEAL_REASON_WEB


DEAL_REASON_MOBILE



OutBy  >0 DEAL_ENTRY_OUT_BY  DEAL_TYPE_BUY DEAL_REASON_CLIENT
DEAL_REASON_EXPERT



DEAL_TYPE_SELL DEAL_REASON_WEB


DEAL_REASON_MOBILE



Wir bilden die erforderliche Historie mit der Methode getDeals_forSelectedKeeper. Betrachten wir die allgemeine Logik und analysieren wir dann die Aktionen für jede der oben beschriebenen Varianten der Aufzählungen im Detail (beginnend mit ab Zeile 303).

//+------------------------------------------------------------------+
//| Ein Datensatz je gewählter Position wird gebildet in inputParam  |
//+------------------------------------------------------------------+

void CDealHistoryGetter::getDeals_forSelectedKeeper(DealKeeper &inputParam,DealDetales &ans[])
  {
   ArrayFree(ans);
   int total=ArraySize(inputParam.deals);
   DealDetales detales;                                          // die Variable für die Ergebnisse der folgenden Schleife
   detales.symbol=inputParam.symbol;
   detales.volume= 0;
   detales.pl_forDeal=0;
   detales.pl_oneLot=0;
   detales.close_comment= "";
   detales.open_comment = "";
   detales.DT_open=0;

// Flag indicating whether the position should be added to the set
   bool isAdd=false;
   bool firstPL_setted=false;
// Arrays of entry prices, exit prices, entry lots, exit lots, contracts
   double price_In[],price_Out[],lot_In[],lot_Out[],contracts[]; // Zeile 404

   for(int i=0;i<total;i++)                                      // Schleife über die Deals (alle Deals haben diesselbe ID, aber es könnten mehr als eine Position geben, wenn der Typ des Deals ist InOut)
     {
      BorderPointType type;                                      // Dealtype, Zeile 408
      double pl_total=0;

      if(isBorderPoint(inputParam.deals[i],type))                // Prüfen, ob es ein Eckdeal ist und den Dealtyp, Zeile 301
        {
          // Zeile 413 
        } // Zeile 414
      else
        {
/*
         Wenn es sich nicht um einen Eckdeal handelt, verwenden wir einfach das Finanzergebnis.
         Es kann nur eine Variationsmarge und verschiedene Korrekturen geben.
         Ein- und Auszahlungsvorgänge werden bei der Erfassung der Ausgangsdaten herausgefiltert. 
*/
         detales.pl_forDeal+=(inputParam.deals[i].profit+inputParam.deals[i].comission);
         detales.pl_oneLot+=inputParam.deals[i].profit/calcContracts(contracts,GET_LAST_CONTRACT);
        }
     }

// Filtering active positions and do not save them
   if(isAdd && PositionSelect(inputParam.symbol))                 // Zeile 541
     {
      if(PositionGetInteger(POSITION_IDENTIFIER)==inputParam.ID)
         isAdd=false; 
     }                                                            // Zeile 546

// Save fixed and already inactive positions
   if(isAdd)
     {
      detales.price_in=MA_price(price_In,lot_In);
      detales.price_out=MA_price(price_Out,lot_Out);

      detales.volume=calcContracts(contracts,GET_REAL_CONTRACT);
      addArr(ans,detales);
     }
  }

Die Arrays werden in der Zeile 404 der Funktion deklariert. Sie werden weiterhin unter der in den Zeilen 411-414 angegebenen Bedingung verwendet. Nur Eckpunkte werden innerhalb dieser Bedingung berücksichtigt, d.h. die Geschäfte, die eine Position öffnen/schließen oder erhöhen/teilweise schließen.

Wenn das Geschäft nicht unter die erste Bedingung fällt, ist die einzige erforderliche Maßnahme die Berechnung des Gewinns/Verlustes. Unsere Historie enthält tatsächlich erzielte Gewinne/Verluste, die in einzelne Deals unterteilt sind. Jedes Geschäft spiegelt eine Änderung in Gewinn- und Verlustsaldo wider, beginnend mit dem Geschäft, das der Ausführung des analysierten Geschäfts vorausgeht. Der Gesamtpositionsgewinn entspricht der Summe der Gewinne und Verluste aller dieser Geschäfte. Wenn wir Gewinn/Verlust als Differenz zwischen Eröffnungs- und Schlusskurs berechnen, wird die Anzahl der Faktoren wie Slippage, Volumenänderungen innerhalb einer Position, Provisionen und Gebühren ignoriert.

Auf den Codezeilen 541-546 werden offene Positionen gefiltert und als Ergebnis gespeichert. Am Ende der Funktion wird folgendes berechnet: die Eröffnungs- und Schlusskurse sowie das maximale Positionsvolumen, das im Markt war.

Die Variable type wird zum Filtern von Eckpunkten verwendet. Wenn im Moment eine Eröffnungs- oder Geschäftserhöhung durchgeführt wird, wechseln wir zur nächsten Bedingung, die von der Zeile 413 ausgeht (siehe den vollständigen Text der Methode im Anhang).

if(type==UsualInput)                                                   // Ist es die erste Position oder eine Positionserhöhung
  {
   if(detales.DT_open==0)                                              // Zuweisen des Eröffnungszeitpunkts der Position
     {
      detales.DT_open=inputParam.deals[i].DT;
      detales.day_open=getDay(inputParam.deals[i].DT);
     }
   detales.isLong=inputParam.deals[i].type==DEAL_TYPE_BUY;             // Bestimmen der Positionsrichtung
   addArr(price_In,inputParam.deals[i].price);                         // Sichern des Eröffnungspreises
   addArr(lot_In,inputParam.deals[i].volume);                          // Sichern der Losgröße

   pl_total=(inputParam.deals[i].profit+inputParam.deals[i].comission);
   detales.pl_forDeal+=pl_total;                                       // Gewinn/Verlust des Deals inkl. Kommission
   if(!firstPL_setted)
     {
      detales.pl_oneLot=pl_total/inputParam.deals[i].volume;           // Gewinn/Verlust eines Deals inkl. Kommission, wenn 1 Lot gehandelt worden wäre
      firstPL_setted=true;
     }
   else
      detales.pl_oneLot=inputParam.deals[i].profit/calcContracts(contracts,GET_LAST_CONTRACT);                 

   if(StringCompare(inputParam.deals[i].comment,"")!=0)                // Kommentar des Deals
      detales.open_comment+=(StringCompare(detales.open_comment,"")==0 ?
                             inputParam.deals[i].comment :
                             (" | "+inputParam.deals[i].comment));
   addArr(contracts,inputParam.deals[i].volume);                       // Eintragen des Volumens des Deals mit einem "+" Zeichen
  }

Wir weisen auch das Datum der Positionseröffnung zu und berechnen den Gewinn (insgesamt und pro Lot). Das Schließen der Position erfolgt unter einer anderen Bedingung:

if(type==UsualOut || type==OutBy)                         // Schließen der Position
  {
/*
           Wir speichern das Ergebnis nicht sofort, da es mehrere Exits geben kann.
           Wir verändern daher das Flag nicht, um Datenverlust zu vermeiden. 
*/
   if(!isAdd)isAdd=true;                                  // Flag zur Sichern der Position

   detales.DT_close=inputParam.deals[i].DT;               // Zeitpunkt des Schließens
   detales.day_close=getDay(inputParam.deals[i].DT);      // Zeitpunkt des Schließens
   addArr(price_Out,inputParam.deals[i].price);           // Sichern des Ausstiegspreises
   addArr(lot_Out,inputParam.deals[i].volume);            // Sichern des Volumens beim Schließen

   pl_total=(inputParam.deals[i].profit+inputParam.deals[i].comission);          // Gewinn/Verlust eines Deals inkl. Kommission
   detales.pl_forDeal+=pl_total;

   if(i==total-1)
      detales.pl_oneLot+=pl_total/calcContracts(contracts,GET_LAST_CONTRACT);    // Gewinn/Verlust eines Deals inkl. Kommission wenn 1 Lot gehandelt worden wäre
   else
      detales.pl_oneLot+=inputParam.deals[i].profit/calcContracts(contracts,GET_LAST_CONTRACT); // Gewinn/Verlust eines Deals inkl. Kommission wenn 1 Lot gehandelt worden wäre

   if(StringCompare(inputParam.deals[i].comment,"")!=0)                          // Kommentar des Deals
      detales.close_comment+=(StringCompare(detales.close_comment,"")==0 ?
                              inputParam.deals[i].comment :
                              (" | "+inputParam.deals[i].comment));
   addArr(contracts,-inputParam.deals[i].volume);                                // Eintragen des Volumens eines Deals mit einem "-" Zeichen
  }

Diese Bedingung weist das Datum der Schließung einer Position zu und berechnet den Gewinn eines Geschäfts. Es kann mehrere Eröffnungen und Schließungen geben, so dass die Bedingungen mehrfach auftreten können.

Die InOut-Bedingung ist die Kombination aus der zweiten und ersten Bedingung. Sie tritt nur einmal auf und führt zu einer Positionsumkehr.

if(type==InOut)                                                                 // Positionsumkehr
  {
/*
           Teil 1:
           Sichern der vorherigen Position
*/
   firstPL_setted=true;
   double closingContract=calcContracts(contracts,GET_LAST_CONTRACT);           // Geschlossener Kontrakt
   double myLot=inputParam.deals[i].volume-closingContract;                     // Geöffneter Kontrakt

   addArr(contracts,-closingContract);                                          // Eintragen eines Dealvolumens mit einem "-" Zeichen
   detales.volume=calcContracts(contracts,GET_REAL_CONTRACT);                   // Ermitteln des max. realen Dealvolumens, das im Markt existiert

   detales.DT_close=inputParam.deals[i].DT;                                     // Zeitpunkt des Schließens
   detales.day_close=getDay(inputParam.deals[i].DT);                            // Zeitpunkt des Schließens
   addArr(price_Out,inputParam.deals[i].price);                                 // Ausstiegspreis
   addArr(lot_Out,closingContract);                                             // Ausstiegsvolumen

   pl_total=(inputParam.deals[i].profit*closingContract)/inputParam.deals[i].volume;        // Gewinn- und Verlustberechnung des geschlossenen Deals
   double commission_total=(inputParam.deals[i].comission*closingContract)/inputParam.deals[i].volume;
   detales.pl_forDeal+=(pl_total+commission_total);
   detales.pl_oneLot+=pl_total/closingContract;                                 // Gewinn/Verlust eines Deals inkl. Kommission, wenn 1 Lot gehandelt worden wäre
   if(StringCompare(inputParam.deals[i].comment,"")!=0)                         // Sichern des Kommentars beim Schließen
      detales.open_comment+=(StringCompare(detales.open_comment,"")==0 ?
                             inputParam.deals[i].comment :
                             (" | "+inputParam.deals[i].comment));

   detales.price_in=MA_price(price_In,lot_In);                                  // Abfrage des Eröffnungspreises (Durchschnitt)
   detales.price_out=MA_price(price_Out,lot_Out);                               // Abfragen des Ausstiegspreises (Durchschnitt)
   addArr(ans,detales);                                                         // Eintragen der formierten Position
   if(isAdd)isAdd=false;                                                        // Umkehren des Flags, wenn es gesetzt wurde

                                                                                // Freigabeteil der Daten
   ArrayFree(price_In);
   ArrayFree(price_Out);
   ArrayFree(lot_In);
   ArrayFree(lot_Out);
   ArrayFree(contracts);
   detales.close_comment="";
   detales.open_comment="";
   detales.volume=0;

/*
           Teil 2.
           Sichern der neuen Position nach dem Löschen der Daten aus dem Array detales
*/

   addArr(contracts,myLot);                                                     // Eintragen der Losgröße bei Eröffnung            

   pl_total=((inputParam.deals[i].profit+inputParam.deals[i].comission)*myLot)/inputParam.deals[i].volume; // Gewinn/Verlust eines Deals inkl. Kommission
   detales.pl_forDeal=pl_total;
   detales.pl_oneLot=pl_total/myLot;                                            // Gewinn/Verlust eines Deals inkl. Kommission, wenn 1 Lot gehandelt worden wäre
   addArr(lot_In,myLot);                                                        // Eintragen der Losgröße bei Eröffnung

   detales.open_comment=inputParam.deals[i].comment;                            // Sichern des Kommentars

   detales.DT_open=inputParam.deals[i].DT;                                      // Sichern der Eröffnungsdaten
   detales.day_open=getDay(inputParam.deals[i].DT);                             // Sichern des Eröffnungstages
   detales.isLong=inputParam.deals[i].type==DEAL_TYPE_BUY;                      // Ermitteln der Richtung des Deals
   addArr(price_In,inputParam.deals[i].price);                                  // Sichern des Eröffnungspreises
  }

Das Resultat der obigen Berechnungen ist eine Tabelle, in der jede Zeile die wichtigsten Parameter einer Position widerspiegelt. Jetzt können wir alle erforderlichen Berechnungen auf der Grundlage von Positionen erstellen und nicht von Geschäften, aus denen die Positionen bestehen.

Instrument Eröffnungszeit Eröffnungstag Schließzeit Schließtag Losgröße Richtung Eröffnungspreis Schlusskurs Gewinn/Verlust je Losgröße Gewinn/Verlust je Position Eröffnungskommentar Kommentar beim Schließen
RTS-12.17 17.11.2017 19:53 FRIDAY 17.11.2017 19:54 FRIDAY 2.00000000 Long 113200.00000000 113180.00000000 -25.78000000 -55.56000000
RTS-12.17 17.11.2017 19:54 FRIDAY 17.11.2017 19:54 FRIDAY 2.00000000 Short 113175.00000000 113205.00000000 -58.47000000 -79.33000000
RTS-12.17 17.11.2017 19:58 FRIDAY 17.11.2017 19:58 FRIDAY 1.00000000 Short 113240.00000000 113290.00000000 -63.44000000 -63.44000000
RTS-12.17 17.11.2017 19:58 FRIDAY 17.11.2017 19:58 FRIDAY 1.00000000 Long 113290.00000000 113250.00000000 -51.56000000 -51.56000000
Si-12.17 17.11.2017 20:00 FRIDAY 17.11.2017 20:00 FRIDAY 10.00000000 Long 59464.40000000 59452.80000000 -23.86000000 -126.00000000
Si-12.17 17.11.2017 20:00 FRIDAY 17.11.2017 20:00 FRIDAY 5.00000000 Short 59453.20000000 59454.80000000 -5.08666667 -13.00000000
Si-12.17 17.11.2017 20:02 FRIDAY 17.11.2017 20:02 FRIDAY 1.00000000 Short 59460.00000000 59468.00000000 -9.00000000 -9.00000000
Si-12.17 17.11.2017 20:02 FRIDAY 17.11.2017 20:03 FRIDAY 2.00000000 Long 59469.00000000 59460.00000000 -14.50000000 -20.00000000
Si-12.17 21.11.2017 20:50 TUESDAY 21.11.2017 21:06 TUESDAY 2.00000000 Long 59467.00000000 59455.00000000 -13.00000000 -26.00000000
Si-12.17 23.11.2017 17:41 THURSDAY 21.12.2017 15:45 THURSDAY 2.00000000 Long 58736.50000000 58610.50000000 -183.00000000 -253.50000000 Open test position | Open test position PartialClose position_2 | [instrument expiration]
RTS-12.17 23.11.2017 18:07 THURSDAY 14.12.2017 14:45 THURSDAY 1.00000000 Short 115680.00000000 114110.00000000 1822.39000000 1822.39000000 Open test position_2
RTS-3.18 30.01.2018 20:22 TUESDAY 30.01.2018 20:22 TUESDAY 2.00000000 Short 127675.00000000 127710.00000000 -61.01000000 -86.68000000
RTS-3.18 30.01.2018 20:24 TUESDAY 30.01.2018 20:24 TUESDAY 1.00000000 Long 127730.00000000 127710.00000000 -26.49000000 -26.49000000
RTS-3.18 30.01.2018 20:24 TUESDAY 30.01.2018 20:25 TUESDAY 1.00000000 Long 127730.00000000 127680.00000000 -60.21000000 -60.21000000
RTS-3.18 30.01.2018 20:25 TUESDAY 30.01.2018 20:25 TUESDAY 1.00000000 Long 127690.00000000 127660.00000000 -37.72000000 -37.72000000
RTS-3.18 30.01.2018 20:25 TUESDAY 30.01.2018 20:26 TUESDAY 1.00000000 Long 127670.00000000 127640.00000000 -37.73000000 -37.73000000
RTS-3.18 30.01.2018 20:29 TUESDAY 30.01.2018 20:30 TUESDAY 1.00000000 Long 127600.00000000 127540.00000000 -71.45000000 -71.45000000

Kapitel 2. Erstellen eines nutzerdefinierten Handelsberichts

Erstellen wir jetzt eine Klasse, die uns den Handelsbericht erzeugt. Zuerst definieren wir die Anforderungen an den Bericht.

  1. Der Bericht wird standardmäßige Gewinn- und Verlustdiagramme und erweiterte Diagramme für eine effizientere Leistungsbewertung aufweisen. Die Konstruktion von Diagrammen beginnt bei Null (unabhängig vom Anfangskapital) - dies erhöht die Objektivität der Bewertung.
  2. Wir generieren das "Buy and Hold"-Diagramm der Strategie, das einem Gewinn- und Verlustdiagramm ähnlich ist (beides wird mit der gleichen Funktion berechnet). Beide Diagramme werden auf allen Vermögenswerten basieren, die im Bericht erscheinen.
  3. Die wichtigsten Koeffizienten und Handelsergebnisse sollten in einer Tabelle dargestellt werden.
  4. Es werden zusätzliche Diagramme erstellt, wie z.B. Gewinn und Verlust nach Tagen.

Am Ende stellen wir die analysierten Parameter in Form von Variablendiagrammen dar und schreiben alle Berechnungsergebnisse in csv-Dateien. Die hier aufgeführten Beispiele basieren auf der realen Geschichte für einen bestimmten Zeitraum. Diese Geschichte ist unten angehängt. Das Skript zur Visualisierung und zum Herunterladen von Daten enthält folgende Funktionen: Die erste zeigt Beispiele für die angehängte Handelshistorie, die zweite basiert auf Ihrer eigenen, in Ihrem Terminal verfügbaren Historie.

Dieser Teil des Artikels zeigt nur wenig vom Code. Stattdessen konzentrieren wir uns darauf, die erhaltenen Daten zu untersuchen und ihre Bedeutung zu erklären. Alle Teile der Klasse, die den Bericht erstellt, sind detailliert kommentiert, so dass Sie sie leicht verstehen können. Zum besseren Verständnis der beschriebenen Funktionen stelle ich hier die Struktur der Klasse zur Verfügung.

class CReportGetter
  {
public:
                     CReportGetter(DealDetales &history[]);                      // Nur die Historie in der vorbereiteten Form wird übergeben
                     CReportGetter(DealDetales &history[],double balance);       // Wir können Historie und Saldo relativ zur durchgeführten Berechnung setzen
                    ~CReportGetter();

   bool              get_PLChart(PL_chartData &pl[],
                                 PL_chartData &pl_forOneLot[],
                                 PL_chartData &Indicative_pl[],
                                 string &Symb[]);                                // Gewinn- und Verlustdiagramm

   bool              get_BuyAndHold(PL_chartData &pl[],
                                    PL_chartData &pl_forOneLot[],
                                    PL_chartData &Indicative_pl[],
                                    string &Symb[]);                             // "Buy and hold"-Diagramm

   bool              get_PLHistogram(PL_chartData &pl[],
                                     PL_chartData &pl_forOneLot[],
                                     string &Symb[]);                            // Gewinn/Verlust-Akkumulationshistogramm

   bool              get_PL_forDays(PLForDaysData &ans,
                                    DailyPL_calcBy calcBy,
                                    DailyPL_calcType type,
                                    string &Symb[]);                             // Gewinn und Verlust# je Wochentag

   bool              get_extremePoints(PL_And_Lose &total,
                                       PL_And_Lose &forDeal,
                                       string &Symb[]);                          // Extrema (Höchst- und Tiefstwert eines Deals und der Akkumulation)

   bool              get_AbsolutePL(PL_And_Lose &total,
                                    PL_And_Lose &average,
                                    string &Symb[]);                             // Absolute Werte (akkcumuliert und durchschnittl. Gewinn und Verlust)

   bool              get_PL_And_Lose_percents(PL_And_Lose &ans,string &Symb[]);  // Diagramm der Verteilung der Deals mit Gewinn und Verlust

   bool              get_totalResults(TotalResult_struct &res,string &Symb[]);   // Tabelle der Hauptvariablen

   bool              get_PLDetales(PL_detales &ans,string &Symb[]);              // Kurzversion des Gewinn- und Verlustdiagramms

   void              get_Symbols(string &SymbArr[]);                             // Abfrage der Liste der in der Historie verfügbaren Symbole

private:
   DealDetales       history[];                                                  // Sichern der Handelshistorie
   double            balance;                                                    // Sichern der Salden

   void              addArray(DealDetales &Arr[],DealDetales &value);            // Eintragen der Daten in die dynamischen Arrays
   void              addArray(PL_chartData &Arr[],PL_chartData &value);          // Eintragen der Daten in die dynamischen Arrays
   void              addArray(string &Arr[],string value);                       // Eintragen der Daten in die dynamischen Arrays
   void              addArray(datetime &Arr[],datetime value);                   // Eintragen der Daten in die dynamischen Arrays

   void              sortHistory(DealDetales &arr[],bool byClose);               // Sortieren der Historie nach dem Zeitpunkt der Eröffnung oder des Schließens

   void              cmpDay(int i,ENUM_DAY_OF_WEEK day,ENUM_DAY_OF_WEEK etaloneDay,PLDrowDown &ans);      // Filling the PLDrowDown structure

   bool              isSymb(string &Symb[],int i);                               // Prüfen, ob das i-te Symbol der Historie im Array Symb ist

   bool              get_PLChart_byHistory(PL_chartData &pl[],
                                           PL_chartData &pl_forOneLot[],
                                           PL_chartData &Indicative_pl[],
                                           DealDetales &historyData[]);          // Gewinn- und Verlustdiagramm der erhaltenen Historie

   ENUM_DAY_OF_WEEK  getDay(datetime DT);                                        // Ermitteln des Wochentages aus dem Datum

  };

3 Arten von Gewinn- und Verlustdiagrammen

Um ein Gewinn- und Verlustdiagramm zu erstellen, sortieren wir die Ausgangsdaten nach dem Tag des Schließens der Positionen. Eine Erklärung ist hier nicht erforderlich, da es sich um ein Standarddiagramm handelt. Es besteht aus zwei Diagrammen mit Positionsschlussdaten entlang der X-Achse. Das erste ist das aktuelle Gewinn- und Verlustdiagramm, während das zweite den kumulierten Drawdown relativ zum maximalen Gewinn- und Verlust-Wert in Prozent anzeigt. Betrachten wir 3 Arten von Diagrammen.

  1. Die standardmäßigen kumulierten Gewinne.
  2. Kumulierte Gewinndiagramm ohne Berücksichtigung des Handelsvolumens, als ob wir im Laufe der gesamten Geschichte eine Charge auf alle Symbole gehandelt hätten.
  3. Das Gewinn- und Verlustdiagramm, normiert auf die maximal verlorene (oder profitable) Losgröße. Wenn sich das Gewinn- und Verlustdiagramm im roten Bereich befindet, dann teilen wir den i-ten Verlust durch die maximal profitable Losgröße. Wenn sich das Diagramm in der grünen Zone befindet, dann teilen wir den i-ten Gewinn durch die Losgröße des maximalen Verlusts.

Die ersten beiden Diagramme ermöglichen es zu verstehen, wie die Veränderung des gehandelten Volumens den Gewinn/Verlust beeinflusst. Die dritte erlaubt es, sich eine extreme Situation vorzustellen: Was würde passieren, wenn eine Reihe von Verlusten plötzlich einsetzten, und diese Verluste entsprachen dem maximal möglichen historischen Verlust pro Lot (was praktisch unmöglich ist). Es zeigt an, wie viele maximale Verluste in Folge der Gewinn und Verlust PL haben kann, bevor er 0 erreicht (alle Gewinne gehen verloren) - oder umgekehrt, was passieren würde, wenn alle Verluste durch eine Reihe von maximalen Gewinnen abgedeckt würden. Diese Diagrammtypen sind nachfolgend dargestellt:

Reales Gewinn- und Verlustdiagramm

1-Lot Gewinn- und Verlustdiagramm

Indikatives Gewinn- und Verlustdiagramm

Das Gewinn- und Verlustdiagramm für den Handel mit einem Lot ist attraktiver als das reale Diagramm. Der Gewinn im realen Handel übertraf den hypothetischen "Ein-Lot-Handel" um nur 30%. Die Losgröße lag zwischen 1 und 10. Der Drawdown in Prozent erreichte 30.000%. Die Situation ist nicht so schlimm, wie sie klingt. Wie bereits erwähnt, beginnt das Erstellen des Gewinn- und Verlustdiagramms bei Null, während der Drawdown relativ zum Maximalwert der Kurve des Gewinn- und Verlustdiagramms berechnet wird. Zu einem bestimmten Zeitpunkt, als der Verlust noch nicht den Maximalwert erreicht hatte (die rote Zone in der Grafik), stieg der Gewinn um mehrere Rubel und fiel dann auf -18.000 Rubel. Deshalb ist der Drawdown so groß. Der tatsächliche Drawdown lag nicht über 25% pro Lot und 50% bei den tatsächlichen Geschäften.

Drawdown des Handels mit einem Lot


Gesamtdrawdown


Das normierte Gewinn- und Verlustdiagramm bleibt bei einem Wert hängen. Dies deutet auf die Notwendigkeit hin, den Handelsansatz zu ändern (z.B. die Methode der Losgrößenbestimmung zu ändern) oder die Handelsalgorithmen neu zu optimieren. Das Gewinn- und Verlustdiagramm mit einer variablen Losgröße sieht schlechter aus als das Diagramm mit einem Lot für den gleichen Zeitraum, aber eine solche Positionsführung hatte immer noch positive Ergebnisse: Die Drawdowns wurden geringer.

Die Klasse, die den Handelsbericht generiert, heißt CReportGetter, und die Diagramme werden mit der Methode get_PLChart_byHistory erstellt. Diese Methode ist mit detaillierten Kommentaren in den Quellcode-Dateien versehen. Sie funktioniert wie folgt:

  1. Sie iteriert durch die vergangene Geschichte.
  2. Jede Position wird in die Berechnung von Gewinn und Verlust, des maximalen Gewinns und des Drawdowns einbezogen.
  3. Die Inanspruchnahme wird als kumulierter Wert im Verhältnis zum maximal erzielten Gewinn berechnet. Wenn das Gewinn- und Verlustdiagramm sofort in den roten Bereich fällt, wird der Drawdown bei jedem Auftreten eines neuen Niederstwertes neu berechnet. In diesem Fall ist der maximale Gewinn Null, und der Drawdown des i-ten und aller vorherigen Elemente wird im Verhältnis zum maximalen Verlust neu berechnet. Hier ist der Drawdown gleich 100%. Sobald der maximale Gewinn über Null steigt (die Gewinn- und Verlustkurve geht in den positiven Teil der Grafik über), wird der Drawdown nur noch im Verhältnis zum maximalen Gewinn weiter berechnet.

Lassen Sie uns eine Analyse durchführen, indem wir drei Diagramme vergleichen. Diese Methode ermöglicht es, viel mehr Fehler zu erkennen, als die Verwendung eines einzigen, standardmäßigen Gewinn- und Verlustdiagramms. Gewinn- und Verlustdiagramme der "Buy and Hold"-Strategie sind nach dem gleichen Algorithmus aufgebaut. Ich werde nur eines der Diagramme als Beispiel besprechen. Die anderen werden beim Start des Visualisierungsskripts automatisch erstellt.

Gewinn und Verlust der "Buy and Hold"-Strategie

Die Abbildung zeigt, dass, wenn wir die gleichen Vermögenswerte mit den gleichen Volumina gemäß der "Buy and Hold"-Strategie handeln würden, das Gewinn- und Verlustdiagramm kaum bei Null liegen würde und der Drawdown etwa 80.000 Rubel betragen würde (anstelle der 70.000 Rubel des maximal erzielten Gewinns).

Akkumulierendes Diagramm von Gewinn und Verlust

Dieses Diagramm stellt zwei Histogramme dar, eines über dem anderen. Ihr Aufbau unterscheidet sich vom standardmäßigen Gewinn- und Verlustdiagramm. Das Gewinnhistogramm wird durch die schrittweise Anhäufung von nur profitablen Positionen erstellt. Wenn die i-te Position verliert, ignorieren wir ihren Wert und schreiben den vorherigen Wert in das i-te Zeitintervall. Das Histogramm der Positionen mit Verlust ist nach der Spiegellogik aufgebaut. Nachfolgend finden Sie die Histogramme der Gewinn- und Verlustrechnung, die auf unseren Daten basieren.

Histogramm der realen Gewinn und Verluste

Histogramm von Gewinn und Verlust für den Handel mit 1 Lot

Das übliche Gewinn- und Verlustdiagramm ist eine einfache Differenz zwischen dem Gewinn- und Verlusthistogramm. Das erste, was sofort ins Auge fällt, ist der große Unterschied zwischen dem real erzielten und dem gehandelten Gewinn. Der real erzielte Gewinn übertraf den Ein-Lot-Handel um ca. 30%, während die Histogramm-Diagrammanalyse eine Differenz von fast 50% ergab. Die fehlenden 20% des Gewinns resultieren aus dem Unterschied in der Dynamik des Gewinnwachstums gegenüber den Verlusten in der ersten und zweiten Grafik. Veranschaulichen wir diese Analyse. Auf der Grundlage dieser Histogramme erstellen wir zwei Gewinn- und Verlustdynamikdiagramme. Die Formel ist sehr einfach:

Formel für den Profit Factor

Dynamik des Profit Factors


Dynamik des realen Proft Factors


Wir sehen, dass die Dynamik des Gewinns zu Beginn gewachsen ist, im Vergleich zu Verlusten. Dies ist eine ideale Entwicklung der Ereignisse: Mit jedem neuen Geschäft nimmt der Abstand des Gewinnhistogramms vom Histogramm der Verlustpositionen zu. Wenn das Gewinnwachstum über Verlusten eine positive Dynamik durch den gesamten Handel hat, erhalten wir eines der besten Ergebnisse. Gewinne werden schneller angesammelt, und Verluste werden mit der gleichen oder niedrigeren Rate im Verhältnis zum Gewinn erhöht.

Das nächste Segment der Charts zeigt, dass die prozentuale Differenz zwischen den Gewinn- und Verlusthistogrammen nicht mehr zunahm und sich weiter in einem Seitwärtstrend bewegte. Wenn der Seitwärtstrend des Gewinnwachstums über Verluste ein Intervall über eins hat, wird dies ebenfalls positiv bewertet. Wenn das Intervall gleich eins ist, dann nähert sich die PL-Kurve dem Nullpunkt (jeder neue Gewinn ist gleich jedem neuen Verlust). Wenn das Seitwärtsintervall unter 1 liegt, haben wir begonnen, unser Geld zu verlieren. Mit anderen Worten, diese Diagramme visualisieren eine allmähliche Veränderung des Gewinnfaktors. Hier sind die Diagramme:

Gewinn und Verlusthistogramm des Profit Factors


Gewinn- und Verlustdiagramm des Profit Factors


Das blaue Histogramm zeigt eine lineare Verlustanhäufung (die Skala reicht von 0 bis 100).

Die Diagramme zeigen, dass der Gewinnfaktor (das Verhältnis von Gewinnüberschuss zu Verlusten) immer größer als eins sein sollte. Die ideale Entwicklung ist das schrittweise Wachstum des Profit Factors. Das Gewinn- und Verlustdiagramm würde in diesem Fall exponentiell wachsen. Leider ist dies auf lange Sicht unrealistisch, aber eine solche Situation kann in einem kurzen Glückszeitraum eintreten, wenn man die Losgröße nach jedem profitablen Geschäft erhöht. Basierend auf diesem Beispiel können wir mit 100%iger Sicherheit behaupten, dass der wichtigste Punkt im Handel nicht ist, wie sich das Gewinn- und Verlustdiagramm ändert, sondern wie sich das Histogramm von profitablen und verlorenen Geschäften verhält, und folglich, was die Dynamik der Veränderung des Gewinnfaktors ist.

Betrachten wir nun die 20% entgangenen Gewinne. Siehe das Diagramm der Gewinnwachstumsdynamik mit 1 Lot: Das Gewinnwachstum stoppte im Intervall [1.4 - 1.6]. Dieser ist höher als [1.2 - 1.4]. Im Laufe der Zeit deckte diese Differenz 20% des potenziellen Gewinns ab. Mit anderen Worten, das Positionsgrößenmanagement hat positive Ergebnisse geliefert. Wenn Sie Methoden wie Martingal oder Anti-Martingal verwenden, können diese Diagramme, insbesondere die auf ihrer Grundlage berechneten Metriken, viele nützliche Informationen für die Analyse liefern. Diese Diagramme werden durch die Funktion get_PLHistogram erstellt.

Gewinn und Verlust pro Tag

Jeder, der jemals Roboter im Strategie-Tester getestet hat, kennt dieses Diagramm bereits. Die Bedeutung des Histogramms und die Konstruktionsmethodik sind identisch. Zusätzlich habe ich die Möglichkeit implementiert, sie mit absoluten Werten (erhalten durch einfache Addition von Gewinnen und Verlusten nach Tagen) und gemittelten Daten zu konstruieren. Die einfache Mittelungsmethode wird in der angehängten Klasse verwendet. Andere Mittelungsverfahren (Modus/Median, gewichtete Mittelung) können noch interessantere Ergebnisse liefern. Sie können diese Arten zur Mittelung selbst implementieren.

Außerdem habe ich die Gesamtzahl der Geschäfte (positiv und negativ) hinzugefügt: Es zeigt die Anzahl der Geschäfte nach Tagen an. Eine kombinierte Analyse dieses Histogramms mit dem Gewinn- und Verlustdiagramm nach Tag ergibt ein genaueres Handelsbild im Rahmen einer Woche. Zum Beispiel wird es das Erkennen von Situationen ermöglichen, in denen wir 50 kleine Verluste und 3 Gewinne pro Tag haben, und diese großen Gewinne decken alle Verluste ab. Hier ist ein Beispiel für ein Balkendiagramm, das auf absoluten Daten und Positionsschlusskurs basiert.

Gewinn und Verlust je Tag


Anzahl der Positionen je Tag


Das Diagramm je Wochentag zeigt, dass positive Geschäfte Verluste abdeckten, aber die Anzahl der Verlustgeschäfte war immer größer (im Allgemeinen ist diese Situation typisch für den algorithmischen Handel). Auch die Anzahl der negativen Deals hat die Anzahl der positiven Deals nie um mehr als 25% erhöht, was ebenfalls normal ist.

Extrempunkte und Absolutwerte

Das Diagramm der Extrema ist ein Histogramm von 4 Balken, das in zwei Gruppen unterteilt ist. Die Extrema sind die maximalen und minimalen Abweichungen im Gewinn- und Verlustdiagramm (der maximal erzielte Gewinn und der maximale kumulierte Verlust). Zwei weitere Balken zeigen den maximal profitablen und verlustreichen Deal an. Beide Gruppen basieren auf realen Handelsdaten und nicht auf Gewinn- und Verlustdaten pro Geschäft. Die Berechnung des maximalen Gewinns auf der Gewinn- und Verlustkurve ist der höchste Punkt in der Grafik. Die Methodik der Berechnung des maximalen Drawdown wurde oben beschrieben. Hier ist die zugehörige Formel:

Min Max

Diagramm der Extrema


Dieses Diagramm veranschaulicht die maximale Verteilung der Gewinne und Verluste. Der Anteil des maximalen Drawdowns im Vergleich zum maximalen Gewinn betrug 30%. Der Wert der maximalen Verlustposition im Vergleich zum profitabelsten lag bei 60%. Im Code ist dies als Schleife mit einem schrittweisen Vergleich der Daten der Gewinn und Verlustkurve gemäß den angegebenen Bedingungen implementiert.

Was die absoluten Werte betrifft, so werden sie am besten in tabellarischer Form dargestellt. Diese werden auch als zwei Gruppen von Daten dargestellt: Die erste ist die Summe aller Gewinne und die Summe aller Verluste; die zweite Gruppe ist der durchschnittliche Gewinn- und Verlustwert innerhalb der analysierten Historie.

Gesamt Durchschnitt
Gewinn 323237 2244.701
Drawdown 261534 1210.806

Tabellen mit einer kurzen Zusammenfassung des Gewinn- und Verlustdiagramms und den wichtigsten Handelsergebnissen

Diese Tabellen spiegeln die numerischen Merkmale der Handelsperformance wider. Der Code zur Tabellenerstellung enthält die folgenden Strukturen:

//+------------------------------------------------------------------+
//| The structure of trading results                                 |
//+------------------------------------------------------------------+
struct TotalResult_struct
  {
   double            PL;                                // Gesamtgewinn oder -verlust
   double            PL_to_Balance;                     // Verhältnis von Gewinn und Verlust zum aktuellen Saldo
   double            averagePL;                         // Durchschnittlicher Gewinn/Verlust
   double            averagePL_to_Balance;              // Verhältnis von durchschnittlichen Gewinn/Verlust zum aktuellen Saldo
   double            profitFactor;                      // Profit factor
   double            reciveryFactor;                    // Erholungsfaktor
   double            profitFactor;                      // Profit factor
   double            dealsInARow_to_reducePLTo_zerro;/* Wenn jetzt ein Gewinn existiert, ist es die Anzahl der Geschäfte in Folge mit maximalen Verlust pro Geschäft,
                                                        um den Gewinn auf dem Konto auf Null zu senken.
                                                        Wenn es jetzt Verlust existiert, die Anzahl der Geschäfte in Folge mit maximalen Gewinn je Geschäft,
                                                        zur Deckung von Verlusten bis auf Null*/

   double            maxDrowDown_byPL;                  // Maximaler Drawdown relativ zu Gewinn und Verlust
   double            maxDrowDown_forDeal;               // Maximaler Drawdown je Deal
   double            maxDrowDown_inPercents;            // Maximaler Drawdown relativ zu Gewinn und Verlust als Prozentsatz zum aktuellen Saldo
   datetime          maxDrowDown_byPL_DT;               // Der Zeitpunkt des maximalen Drawdowns relativ zu Gewinn und Verlust
   datetime          maxDrowDown_forDeal_DT             // Der Zeitpunkt des maximalen Drawdowns je Deal

   double            maxProfit_byPL;                    // Maximalgewinn von Gewinn und Verlust
   double            maxProfit_forDeal;                 // Maximalgewinn je Deal
   double            maxProfit_inPercents;              // Maximalgewinn Maximum als Prozentsatz zum aktuellen Saldo
   datetime          maxProfit_byPL_DT;                 // Der Zeitpunkt des Maximalgewinns von Gewinn und Verlust
   datetime          maxProfit_forDeal_DT;              // Der Zeitpunkt des Maximalgewinn alles Deals
  };
//+------------------------------------------------------------------+
//| Teil mit der Struktur PL_detales (deklariert unten)              |
//+------------------------------------------------------------------+
struct PL_detales_item
  {
   int               orders;                            // Anzahl der Deals
   double            orders_in_Percent;                 // Anzahl der Aufträge als % der Gesamtzahl aller Aufträge
   int               dealsInARow;                       // Deals in Folge
   double            totalResult;                       // Gesamtergebnis in Kontowährung
   double            averageResult;                     // Durchschnittsergebnis in Kontowährung
  };
//+-------------------------------------------------------------------+
//| Ein kurzes Gewinn- und Verlustdiagramm in zwei Hauptblöcken       |
//+-------------------------------------------------------------------+
struct PL_detales
  {
   PL_detales_item   profits;                           // Information der profitablen Deals
   PL_detales_item   loses;                             // Information der Verlustdeals
  };

Die erste Struktur, TotalResult_struct, ist eine Zusammenfassung der Schlüsselwerte in der gesamten angeforderten Handelshistorie. Es enthält die notwendigen Werte (Gewinn und Verlust pro Position, etc.) und die berechneten Koeffizienten der Handelsperformance.

Die zweite und dritte Struktur sind miteinander verbunden. PL_detales ist die Hauptstruktur mit einer kurzen Zusammenfassung des Gewinn- und Verlusthistogramms. Die folgenden Ergebnisse wurden mit der analysierten Historie erzielt:

Wert Bedeutung
Gewinn und Verlust 65039
Gewinn und Verlust zum Saldo 21,8986532
Durchschnittlicher Gewinn und Verlust 180,1634349
Durchschnittlicher Gewinn und Verlust zum Saldo 0,06066109
Profit Faktor 1,25097242
Erholungsfaktor 3,0838154
Auszahlungsquote 1,87645863
Deals bis Gewinn und Verlust Null wird 24,16908213
Drawdown relativ zu Gewinn und Verlust -23683
Drawdown je Lot -2691
Drawdown relativ zum Saldo -0,07974074
Datum des Drawdowns relativ zu Gewinn und Verlust 24.07.2017 13:04
Datum des Drawdowns je Lot 31.01.2017 21:53
Gewinn von Gewinn und Verlust 73034
Gewinn je 1 Lot 11266
Gewinn relativ zum Saldo 0,24590572
Datum des Gewinns von Gewinn und Verlust 27.06.2017 23:42
Datum des Gewinns je Lot 14.12.2016 12:51

Die zweite Tabelle scheut wie folgt aus:

Gewinne Verlierer
Durchschnittsergebnis 2244.701 -1210,81
Positionen in Folge 5 10
Gesamtzahl der Positionen 144 216
Positionen in % 0,398892 0,598338
Gesamtergebnis: 323237 -261534

Die Verteilung der Gewinner und Verlierer kann in Kreisform dargestellt werden:

% des Gewinns und des Drawdowns


Wie man sieht sind 40% der Positionen positiv.

Schlussfolgerung

Es ist kein Geheimnis, dass es bei der Verwendung von Algorithmen im Handel nicht ausreicht, sie nur zu optimieren und zu starten. Das Ergebnis, einen Algorithmus unmittelbar nach der Optimierung zur vollen Leistungsfähigkeit auszuführen, ohne ihn an die Portfoliostruktur anzupassen, kann zu unerwarteten Ergebnissen führen. Natürlich spielen Händler oft russisches Roulette. Es gibt jedoch viele vernünftige Händler, die es vorziehen, über ihre zukünftigen Schritte nachzudenken. Die im Artikel verwendete Technik der Analyse der Historie bietet diesen Händlern eine interessante Möglichkeit, die Optimalität der ausgewählten Gewichte zu testen, die jedem Handelsalgorithmus zugeordnet sind. 

Einer der interessanten Anwendungsbereiche ist es, verschiedene Portfolioberechnungsmethoden zu analysieren und diese mit der verfügbaren Handelshistorie zu testen und dann die neue Portfolioperformance mit den tatsächlichen Ergebnissen zu vergleichen. Dies kann durch den Handel von 1 Lot geschehen, der auf der Grundlage der realen Handelsgeschichte im ersten Kapitel dieses Artikels berechnet wird. Bitte beachten Sie jedoch, dass die in solchen Berechnungen gewonnenen Daten Näherungen sind, da sie die Liquidität der Vermögenswerte, Provisionen und verschiedene höhere Gewalt nicht berücksichtigen.


Die folgenden Dateien sind dem Artikel beigefügt:

  1. Die CSV-Datei dealHistory.csv mit der Handelshistorie (im Ordner MQL5\Files\article_4803) - Beispiele in diesem Artikel basieren auf dieser Handelshistorie.
  2. Quelldateien für MetaTrader 5, die sich unter MQL5\Scripts\Get_TradigHistory befinden.
Hilfsdateien:
    1. Die Funktion test_1 akzeptiert den Pfad zur angehängten Testhistorie-Datei als einen Parameter und den Pfad zur Ergebnisliste als zweiten Parameter. Diese Funktion erzeugt einen Bericht über die als erster Funktionsparameter übergebenen Testdateien.
    2. Die zweite Funktion test_2 akzeptiert den zweiten Ordnerpfad (die Pfade müssen unterschiedlich sein) als Parameter. Ein Bericht über Ihren Handel wird in diesem Ordner gespeichert.

Hauptdateien: