Lifehack für den Händler: "Stille" Optimierung oder die optische Auswertung des Handels

Vladimir Karputov | 30 September, 2016


Inhaltsverzeichnis


Warum diese Diagramme?

Während der Entwicklung einer neuen Handelsstrategie weiß man nie, wie erfolgreich sie sein könnte. Ein Handelsroboter verfügt fast immer über Eingabeparameter, die in den Regeln für die Erzeugung der Handelssignale verwendet werden. In diesem Fall, nach dem Schreiben des Handelsroboters, bleibt nur, mittels des Strategietesters die Eingabeparameter zu finden, die gute Ergebnisse im Backtesting ergeben.

Dieser Artikel eröffnet nun einen etwas anderen Blick auf den Prozess des Erstellens eines Handelsroboters. Vor der Optimierung der Eingabeparameter, kann man ganz einfach auf die Verteilung von Gewinnen und Verlusten abhängig vom Eröffnungszeitpunkt werfen. Alles in allem haben viele Strategien "günstige" und "ungünstige" Momente, um eine Position zu eröffnen. Dieser Artikel zeichnet Diagramme der Verteilung der Profitabilität der Positionen (beachten Sie, nicht die "deals", sondern nur die tatsächlichen Positionen!) abhängig vom Zeitpunkt ihrer Eröffnung. Durch das Studium dieser Diagramme erhält man ein etwas anderen Blick auf die Strategie.

Die Diagramme werden mittels Google Charts gezeichnet. Und für die visuelle Darstellung wurde die Form HTML gewählt. Schematisch werden die Diagramme als Tabellen auf einer Seite dargestellt.

 

Fig. 1. Ansicht eines HTML-Berichts  

Die ersten beiden Reihen sind zusammenfassende Statistiken des ganzen Kontos, die Reihen darunter die Statistiken für jedes Symbol in Bezug zu Stunde, Tag und Monat.


Schnellstart

Die Datei "DistributionOfProfits.mqh" sollte ins Include-Verzeichnis ...\MQL5\Include\ kopiert werden. Das Skript "test_report.mq5" mach die Aufrufe, um das Diagramm mit der Analyse ab dem anzugebenden Datum "start" zu zeichnen:

//+------------------------------------------------------------------+
//|                                                  test_report.mq5 |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
//---
input datetime start=D'2010.05.11 12:05:00';
#include <DistributionOfProfits.mqh>
//---
CDistributionOfProfits Analysis;
//+------------------------------------------------------------------+
//| Script Programm Start Funktion                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   Analysis.AnalysisTradingHistory(start);
   Analysis.ShowDistributionOfProfits();
  }
//+------------------------------------------------------------------+


1. Rekonstruktion der Positionen des bisherigen Handels

Warum eine Rekonstruktion der Positionen? Tatsächlich ist das der einzige Weg, um an die wichtigen Parameter, wie Gewinn und den Eröffnungszeitpunkt zu kommen. Definieren wir:

Positionsgewinn — der Gesamtgewinn aller Deals einer bestimmten Position;

Eröffnungszeitpunkt — Eröffnungszeitpunkt des ersten Auftrages dieser Position.

Das Terminal verfügt über eine Kontohistorie in Form der Aufträge und der Deals. Was wir aber brauchen, sind die Historie der Positionen. Dazu wenden wir uns der Kontohistorie ("trade history") zu und konzentrieren uns auf die Merkmale DEAL_POSITION_ID eines "Deals" und ORDER_POSITION_ID eines Auftrages. Der Grund für diese Wahl ist wie folgt. Jede Position hat ja ihre eineindeutige Kennung (POSITION_IDENTIFIER), die für jeden Auftrag (ORDER_POSITION_ID) und jeden Deal (DEAL_POSITION_ID) bestimmt ist und durch die die Positionen eröffnet, modifiziert oder geschlossen werden:

Position, Deals, Auftrag 

Fig. 2. Beziehungen von POSITION_IDENTIFIER, DEAL_POSITION_ID und ORDER_POSITION_ID

Mit anderen Worten, wenn die Deals mit der gleichen DEAL_POSITION_ID aus der Kontohistorie spezifiziert wurden, kann die Rekonstruktion der der Position garantiert werden. Erwähnt werden muss aber noch die Positionsumkehr. Aus der Dokumentation der Positionskennung:

Eine Umkehr der Positionsrichtung verändert den Identifikator der Position.

1.1. Umkehr der Positionsrichtung

Das heißt, wenn eine Position ihre Kennung nach einer Umkehr ändert, wird sie zu einer anderen Position. Wie wird die Kennung in diesen Fällen den "Deals" zugewiesen? Wird der "Deal", der zu der Positionsumkehr führte, der vorherigen oder der gedrehten (neuen) Position zugeordnet? Die Antwort darauf ist ein einfaches Experiment — in Form des Skripts position_reversal_v1.mq5

Das Skript führt drei Aufträge aus:

  • buy 0.01 — Eröffnen der Position
  • sell 0.02 — Drehen der Position
  • Schließen der Position

Der Startzeitpunkt sollte in den Eingabeparameter des Skripts angegeben werden. Dieser Parameter wird für die Abfrage der Kontohistorie benötigt. In diesem Beispiel: Das Skript läuft am 2016.09.05 um 10:32:59, der Eingabeparameter hat durch den Wert D'2016.09.05 10:32:00' eine kleine Toleranz.

Nach jeder Aktion schreibt das Skript jede Position mit dessen Kennung, sowie die Deals mit ihrer DEAL_POSITION_ID ins Log. Das sind die Logeinträge:

10:32:59.487    position_reversal_v1 (EURUSD,M3)    Buy 0.01, "EURUSD"
10:33:00.156    position_reversal_v1 (EURUSD,M3)    Position EURUSD POSITION_IDENTIFIER #96633525
10:33:00.156    position_reversal_v1 (EURUSD,M3)    Deal EURUSD volume 0.01 DEAL_POSITION_ID #96633525 profit 0.00
10:33:00.156    position_reversal_v1 (EURUSD,M3)    
10:33:06.187    position_reversal_v1 (EURUSD,M3)    Sell 0.02, "EURUSD"
10:33:06.871    position_reversal_v1 (EURUSD,M3)    Position EURUSD POSITION_IDENTIFIER #96633564
10:33:06.871    position_reversal_v1 (EURUSD,M3)    Deal EURUSD volume 0.01 DEAL_POSITION_ID #96633525 profit 0.00
10:33:06.871    position_reversal_v1 (EURUSD,M3)    Deal EURUSD volume 0.02 DEAL_POSITION_ID #96633525 profit -0.06
10:33:06.871    position_reversal_v1 (EURUSD,M3)    
10:33:12.924    position_reversal_v1 (EURUSD,M3)    PositionClose, "EURUSD"
10:33:13.593    position_reversal_v1 (EURUSD,M3)    Deal EURUSD volume 0.01 DEAL_POSITION_ID #96633525 profit 0.00
10:33:13.593    position_reversal_v1 (EURUSD,M3)    Deal EURUSD volume 0.02 DEAL_POSITION_ID #96633525 profit -0.06
10:33:13.593    position_reversal_v1 (EURUSD,M3)    Deal EURUSD volume 0.01 DEAL_POSITION_ID #96633564 profit -0.10
10:33:13.593    position_reversal_v1 (EURUSD,M3)    

Der erste Handelsoperation — buy 0.01. Dessen POSITION_IDENTIFIER ist 96633525. Die Kontohistorie enthält auch einen Deal — ihr zugeordnet wurde die DEAL_POSITION_ID mit 96633525. Für diesen Deal stimmen DEAL_POSITION_ID und POSITION_IDENTIFIER der Position überein, und das bedeutet, dieser Deal betrifft diese Position. 

Die zweite Operation — sell 0.02. Dieser Auftrag führt zur Positionsumkehr: Es gab eine Position 'buy 0.01', dann einen Auftrag 'sell 0.02', der zur offenen Position 'sell 0.01' führt. Der POSITION_IDENTIFIER der aktuellen Position wechselte zu 96633564. Das heißt, es gibt eine neue Position. Und was passiert in der Kontohistorie der Deals zur gleichen Zeit? In der Kontohistorie gibt es jetzt zwei Deals, beide mit derselben DEAL_POSITION_ID von 96633525. Gleichzeitig erwirtschaftet der zweite Deal "profit -0.06".

Also der Deal, der die Positionsumkehr bewirkte, gehört zur vorherigen Position.

Die dritte Handelsoperation — buy 0.01. Jetzt ist diese Position nicht mehr vorhanden (als ob sie geschlossen wurde), und die Kontohistorie enthält drei Deals: die beiden ersten haben dieselbe DEAL_POSITION_ID mit 96633525, aber diese Kennung änderte sich zu "96633564" im dritten Deal und mit der Ergänzung "profit -0.10". Das heißt, der dritte Deal gehört zur zweiten Position als Ergebnis der Positionsumkehr der ersten.

1.2. Berechnung des Gewinns einer Position

Auf Grund der Informationen aus Kapitel 1.1. können wir den Algorithmus für die Gewinnberechnung jeder rekonstruierten Position ganz genau aufstellen. Der Gesamterfolg aller Deals mit derselben Kennung DEAL_POSITION_ID ist der summierte Gewinn der Position, deren POSITION_IDENTIFIER gleich der DEAL_POSITION_ID ist. Aber, da die einzigen relevanten Operationen Kauf und Verkauf sind, müssen wir noch den Typ der Deals einschränken. Es werden nur die folgenden Deals aus der Enum.-Liste ENUM_DEAL_TYPE berücksichtigt:

ENUM_DEAL_TYPE

Bezeichnung

Beschreibung

DEAL_TYPE_BUY

Buy

DEAL_TYPE_SELL

Sell

 

Das erste Skript wurde etwas geändert und unter dem Namen position_reversal_v2.mq5 gesichert. Das Skript position_reversal_v2.mq5 enthält immer noch die drei Teile — buy 0.01, sell 0.02 und die Position schließen. Neu ist die Funktion PrintProfitPositions(): Sie berechnet den Gewinn der rekonstruierten Positionen. Schauen wir sie uns im Detail an:

//+------------------------------------------------------------------+
//| Print ptofit positions                                           |
//+------------------------------------------------------------------+
void PrintProfitPositions(void)
  {
//--- structure profit positions;
   struct struct_positions
     {
      long              position_id;
      double            position_profit;
      //--- constructor
                        struct_positions() {position_id=0; position_profit=0.0;}
     };
   struct_positions arr_struct_positions[];
//--- request trade history 
   HistorySelect(start,TimeCurrent());
   uint     total    =HistoryDealsTotal();
   ulong    ticket   =0;
   long     deal_id  =0;
   double   profit   =0;
   long     type     =0;
//--- for all deals 
   for(uint i=0;i<total;i++)
     {
      //--- try to get deals ticket 
      if((ticket=HistoryDealGetTicket(i))>0)
        {
         //--- get deals properties 
         deal_id  =HistoryDealGetInteger(ticket,DEAL_POSITION_ID);
         profit   =HistoryDealGetDouble(ticket,DEAL_PROFIT);
         type     =HistoryDealGetInteger(ticket,DEAL_TYPE);
         //--- only buy or sell
         if(type==DEAL_TYPE_BUY || type==DEAL_TYPE_SELL)
           {
            bool seach=false;
            int number=ArraySize(arr_struct_positions);
            for(int j=0;j<number;j++)
              {
               if(arr_struct_positions[j].position_id==deal_id)
                 {
                  arr_struct_positions[j].position_profit+=profit;
                  seach=true;
                  break;
                 }
              }
            if(!seach)
              {
               ArrayResize(arr_struct_positions,number+1);
               arr_struct_positions[number].position_id=deal_id;
               arr_struct_positions[number].position_profit+=profit;
              }
           }
        }
     }
//---
   int number=ArraySize(arr_struct_positions);
   for(int i=0;i<number;i++)
     {
      Print("id ",arr_struct_positions[i].position_id," profit ",arr_struct_positions[i].position_profit);
     }
  }

Zuerst wird die Struktur struct_positions deklariert:

//--- structure profit positions;
   struct struct_positions
     {
      long              id;
      double            profit;
      //--- constructor
                        struct_positions() {id=0; profit=0.0;}
     };
   struct_positions arr_struct_positions[];

Die Struktur struct_positions verfügt über zwei Felder:

id — Kennung der Position;

profit — Gewinn der Position.

Gleich danach wird der Array arr_struct_positions[] dieser Strukturen deklariert.

Unten ist der Teil mit den Hilfsvariablen für die Abfrage der Historie der Deals:

//--- request trade history 
   HistorySelect(start,TimeCurrent());
   uint     total    =HistoryDealsTotal();
   ulong    ticket   =0;
   long     deal_id  =0;
   double   profit   =0;
   long     type     =0;

Gefolgt von der Schleife der Abfrage der Kontohistorie:

//--- for all deals 
   for(uint i=0;i<total;i++)
     {
      //--- try to get deals ticket 
      if((ticket=HistoryDealGetTicket(i))>0)
        {
...
         //--- only buy or sell
         if(type==DEAL_TYPE_BUY || type==DEAL_TYPE_SELL)
           {
...
           }
        }
     }

Gleichzeitig, wie bereits erwähnt, vergessen wir nicht nur "Buy" und "Sell" zu berücksichtigen.

Und hier ist jetzt der Code, der über arr_struct_positions[] iteriert. Wird in der Kontohistorie eine Übereinstimmung von position_id und DEAL_POSITION_ID gefunden, dann wird der Gewinn des aktuellen Deals summiert in die Struktur mit dem entsprechenden Index des Arrays.

            bool seach=false;
            int number=ArraySize(arr_struct_positions);
            for(int j=0;j<number;j++)
              {
               if(arr_struct_positions[j].id==deal_id)
                 {
                  arr_struct_positions[j].profit+=profit;
                  seach=true;
                  break;
                 }
              }
            if(!seach)
              {
               ArrayResize(arr_struct_positions,number+1);
               arr_struct_positions[number].id=deal_id;
               arr_struct_positions[number].profit+=profit;
              }

Findet sich keine Übereinstimmung der id mit einer DEAL_POSITION_ID eines Deals in dem Array der Strukturen, dann wird der Array der Strukturen um eins erhöht und das neue Element erhält sofort die Werte.

Nachdem das Array der Strukturen komplett ist, wird über dieses Array iteriert, um die Kennungen der Positionen und deren Gewinne auszudrucken: 

//---
   int number=ArraySize(arr_struct_positions);
   for(int i=0;i<number;i++)
     {
      Print("id ",arr_struct_positions[i].id," profit ",arr_struct_positions[i].profit);
     }

1.3. Zeitpunkt der Eröffnung einer Position

Wir erweitern das Skript position_reversal_v1.mq5 script — ergänzt um den Ausdruck aller Aufträge mit ihren Parametern aus der Kontohistorie. Das Skript ist gesichert unter den Namen position_reversal_v3.mq5.

2016.09.06 15:05:34.399 position_reversal_v3 (USDJPY,M1)        Buy 0.01, "EURUSD"
2016.09.06 15:05:35.068 position_reversal_v3 (USDJPY,M1)        Position EURUSD POSITION_IDENTIFIER #96803513
2016.09.06 15:05:35.068 position_reversal_v3 (USDJPY,M1)        Deal EURUSD volume 0.01 DEAL_POSITION_ID #96803513 profit 0.00
2016.09.06 15:05:35.068 position_reversal_v3 (USDJPY,M1)        Order EURUSD initial_volume 0.01 ORDER_POSITION_ID #96803513 ORDER_TICKET 96803513
2016.09.06 15:05:35.068 position_reversal_v3 (USDJPY,M1)        
2016.09.06 15:05:41.088 position_reversal_v3 (USDJPY,M1)        Sell 0.02, "EURUSD"
2016.09.06 15:05:41.767 position_reversal_v3 (USDJPY,M1)        Position EURUSD POSITION_IDENTIFIER #96803543
2016.09.06 15:05:41.767 position_reversal_v3 (USDJPY,M1)        Deal EURUSD volume 0.01 DEAL_POSITION_ID #96803513 profit 0.00
2016.09.06 15:05:41.767 position_reversal_v3 (USDJPY,M1)        Deal EURUSD volume 0.02 DEAL_POSITION_ID #96803513 profit -0.08
2016.09.06 15:05:41.767 position_reversal_v3 (USDJPY,M1)        Order EURUSD initial_volume 0.01 ORDER_POSITION_ID #96803513 ORDER_TICKET 96803513
2016.09.06 15:05:41.767 position_reversal_v3 (USDJPY,M1)        Order EURUSD initial_volume 0.02 ORDER_POSITION_ID #96803543 ORDER_TICKET 96803543
2016.09.06 15:05:41.767 position_reversal_v3 (USDJPY,M1)        
2016.09.06 15:05:47.785 position_reversal_v3 (USDJPY,M1)        PositionClose, "EURUSD"
2016.09.06 15:05:48.455 position_reversal_v3 (USDJPY,M1)        Deal EURUSD volume 0.01 DEAL_POSITION_ID #96803513 profit 0.00
2016.09.06 15:05:48.455 position_reversal_v3 (USDJPY,M1)        Deal EURUSD volume 0.02 DEAL_POSITION_ID #96803513 profit -0.08
2016.09.06 15:05:48.455 position_reversal_v3 (USDJPY,M1)        Deal EURUSD volume 0.01 DEAL_POSITION_ID #96803543 profit -0.05
2016.09.06 15:05:48.455 position_reversal_v3 (USDJPY,M1)        Order EURUSD initial_volume 0.01 ORDER_POSITION_ID #96803513 ORDER_TICKET 96803513
2016.09.06 15:05:48.455 position_reversal_v3 (USDJPY,M1)        Order EURUSD initial_volume 0.02 ORDER_POSITION_ID #96803543 ORDER_TICKET 96803543
2016.09.06 15:05:48.455 position_reversal_v3 (USDJPY,M1)        Order EURUSD initial_volume 0.01 ORDER_POSITION_ID #96803543 ORDER_TICKET 96803561

Das Skript visualisiert folgendes Zitat aus der Hilfe:

Die Kennung einer Position ist eine eineindeutige Nummer, die jeder wiedereröffneten Position zugewiesen wird. Sie ändert sich nicht während ihrer Existenz und entspricht der Ticketnummer eines Auftrages zur Eröffnung einer Position

Also, um den Zeitpunkt der Eröffnung einer Position zu bestimmen, genügt es, in der Kontohistorie nach Aufträgen suchen, deren Ticketnummer (ORDER_TICKET) mit der Kennung der Position (POSITION_IDENTIFIER) übereinstimmt und die Zeit des gefundenen Auftrages (ORDER_TIME_DONE) zu verwenden.

1.4. Zwischenspeicherung der rekonstruierten Positionen

Die rekonstruierte Position wird im Array der Struktur struct_positions gesichert:

struct struct_positions
  {
   long              id;
   datetime          time;
   double            loss;
   double            profit;
   string            symbol_name;
   //--- constructor 
                     struct_positions() {id=0; time=0; loss=0.0; profit=0.0; symbol_name=NULL;}
  };

mit

id — Kennung der Position;

time — Eröffnungszeitpunkt der Position;

loss — Verlust der Position, dieser Wert wird mit einem "+" Zeichen eingetragen (das verbessert die Darstellung im Diagramm)

profit — Gewinn einer Position

symbol_name — Name des Symbols der Position.

Später, wenn verschiedene Diagramme gezeichnet werden, wird der Array der Strukturen struct_positions in einer positionskonsolidierten Datenbank der Kontohistorie gespeichert.


2. Google Charts

Das Angebot von Google Charts wird für die Darstellung der Analyse verwendet. Dafür wird das Diagramm in eine HTML-Seite eingebettet. Danach wird die Seite im Standardbrowser des Betriebssystems geöffnet (unter Verwendung von ShellExecuteW der Win API).

Zwei Diagrammtypen in diesem Artikel werden verwendet: Histogramm und Tortendiagramm. Untersuchen wir die Details.

2.1. Histogramm (Typus 'bar')

Der Typus 'bar' erlaubt die Darstellung folgender Diagramme auf der HTML-Seite:

bar  bar1

Hier ist der Code des ersten Diagramms. Sichern Sie es mit der Erweiterung *.html (oder laden Sie die Datei bar.html vom Ende des Artikels) und öffnen Sie sie in Ihrem Browser:

<html>
  <head>
    <!--Load the AJAX API-->
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript">

      google.charts.load('current', {'packages':['bar']});
      google.charts.setOnLoadCallback(drawChart1);
      function drawChart1() {
        var data1 = google.visualization.arrayToDataTable([
          ['Symbol', 'Profit', 'Loss'],
          ['Si-6.16', 82.00, 944.00],
          ['Si-9.16', 56.00, 11.00],
          ['SBRF-9.16', 546.00, 189.00],
        ]);

        var options1 = {
          chart: {
            title: 'Profit/loss by Symbols',
            subtitle: 'Summary',
          },
          bars: 'vertical',
          vAxis: {format: 'decimal'},
          width: 440,
          height: 400,
          colors: ['#5b9bd5', '#ed7d31', '#7570b3']
        };
        var chart = new google.charts.Bar(document.getElementById('chart_div1'));
        chart.draw(data1, options1);
      }

    </script>
  </head>

  <body>
    <!--Div that will hold the pie chart-->
      <div id="chart_div1"></div>
    <br/>
  </body>
</html>

Um Google Charts zu verwenden, muss der Code unter Einhaltung der unten beschrieben Regeln eingefügt werden.

Im Block <head> verbinden Sie die Loader-Dateien und Bibliotheken:

    <!--Load the AJAX API-->
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript">

Danach spezifizieren Sie den Diagrammtyp und die Funktionen mit den Daten für das Zeichnen (drawChart):

      google.charts.load('current', {'packages':['bar']});
      google.charts.setOnLoadCallback(drawChart1);

Die Funktion drawChart1() umfasst selbst drei Teile:

  • "var data1" — Basisdaten für das Zeichnen des Diagramms
  • "var options1" — Optionen, die die Parameter des Diagramms spezifizieren
  • Der Teil mit dem Pointer zum Element, das dargestellt werden soll:

      function drawChart1() {
        var data1 = google.visualization.arrayToDataTable([
          ['Symbol', 'Profit', 'Loss'],
          ['Si-6.16', 82.00, 944.00],
          ['Si-9.16', 56.00, 11.00],
          ['SBRF-9.16', 546.00, 189.00],
        ]);

        var options1 = {
          chart: {
            title: 'Profit/loss by Symbols',
            subtitle: 'Summary',
          },
          bars: 'vertical',
          vAxis: {format: 'decimal'},
          width: 440,
          height: 400,
          colors: ['#5b9bd5', '#ed7d31', '#7570b3']
        };
        var chart = new google.charts.Bar(document.getElementById('chart_div1'));
        chart.draw(data1, options1);
      }

In diesem Fall beeinflusst die An- oder Abwesenheit des letzten Kommas in dieser Zeile

          ['SBRF-9.16', 546.00, 189.00],

nicht die Ausführung des Codes der HTML-Seite, und das vereinfacht den Algorithmus zur Erstellung des Teils für die Daten. Bitte bedenken Sie: Da das Diagramm im Typus 'bar' gezeichnet wird, bestimmen Sie den Typus durch:

        var chart = new google.charts.Bar(document.getElementById('chart_div1'));

Der Bereich selbst ist im <body>:

    </script>
  </head>

  <body>
    <!--Div that will hold the pie chart-->
      <div id="chart_div1"></div>
    <br/>
  </body>
</html>

2.2. Tortendiagramm (Typus 'corechart')

Der Artikel verwendet auch diese Diagramme:

corechart 

Unten ist der Code einer HTML-Seite, der die Tortendiagramme erstellt. Sichern Sie den Code mit der Erweiterung *.html (oder laden Sie die Datei corechart.html vom Ende des Artikels) und öffnen Sie die Datei in einem Browser.

<html>
  <head>
    <!--Load the AJAX API-->
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript">

      google.charts.load('current', {'packages':['corechart']});
      google.charts.setOnLoadCallback(drawChart2);
      function drawChart2() {
        var data2 = google.visualization.arrayToDataTable([
          ['Symbols', 'Profit'],
          ['Si-6.16', 82.00],
          ['Si-9.16', 56.00],
          ['SBRF-9.16', 546.00],
        ]);

        var options2 = {
            title: 'Profit by Symbols, %',
            pieHole: 0.4,
            width: 440,
            height: 400,
        };
        var chart = new google.visualization.PieChart(document.getElementById('chart_div2'));
        chart.draw(data2, options2);
      }

    </script>
  </head>

  <body>
    <!--Div that will hold the pie chart-->
      <div id="chart_div2"></div>
    <br/>
  </body>
</html>

Funktionell sind die Teile in der gleichen Weise wie im obigen Beispiel angeordnet, mit kleinen Änderungen in der Darstellung der Daten. Für ein Tortendiagramm müssen die Daten wie folgt angegeben werden:

        var data2 = google.visualization.arrayToDataTable([
          ['Symbols', 'Profit'],
          ['Si-6.16', 82.00],
          ['Si-9.16', 56.00],
          ['SBRF-9.16', 546.00],
        ]);

        var options2 = {
            title: 'Profit by Symbols, %',
            pieHole: 0.4,
            width: 440,
            height: 400,
        };

und da das gezeigt Diagramm vom Typus 'corechart' ist, ist der Teil der Daten, die den Zeichenbereich festlegen, wie folgt:

        var chart = new google.visualization.PieChart(document.getElementById('chart_div2'));
        chart.draw(data2, options2);

2.3. Histogramm (Typus 'bar') + Tortendiagramm (Typus 'corechart') + Histogramm (Typus 'bar')

Der Code dieses Beispiels findet sich im der Datei bar_corechart_bar.html und kann ebenfalls vom Ende des Artikels heruntergeladen werden. Das Beispiel selbst sieht so aus (Bild ist verkleinert): 

bar+ corechart + bar 

Eine Konfiguration, die zur gleichen Zeit verschiedene Diagrammtypen darstellt, ist ein komplizierter Fall, denn jetzt muss die Funktion drawChart***() auf der Seite richtig platziert werden, ebenso wie die Teile für die Zeichenbereiche der Darstellung. Wenn Sie auf einer Seite Diagramme mehrerer Typen zeigen wollen (zum Beispiel Histogramm und Tortendiagramm), werden sie auf folgende Weise zusammengeführt:

      google.charts.load('current', {'packages':['bar', 'corechart']});

Das allgemeine Schema der Datei bar_corechart_bar.html ist dies:

<html>
  <head>
    <!--Load the AJAX API-->
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript">

      google.charts.load('current', {'packages':['bar', 'corechart']});
      google.charts.setOnLoadCallback(drawChart1);
      google.charts.setOnLoadCallback(drawChart2);
      google.charts.setOnLoadCallback(drawChart4);
      function drawChart1() {
        var data1 = ...

        var options1 = ...

        var chart = new google.charts.Bar(document.getElementById('chart_div1'));
        chart.draw(data1, options1);
      }

      function drawChart2() {
        var data2 = ...

        var options2 = ...

        var chart = new google.visualization.PieChart(document.getElementById('chart_div2'));
        chart.draw(data2, options2);
      }

      function drawChart4() {
        var data4 = ...

        var options4 = ...

        var chart = new google.charts.Bar(document.getElementById('chart_div4'));
        chart.draw(data4, options4);
      }

    </script>
  </head>

  <body>
    <!--Div that will hold the pie chart-->
      <table>
        <tr>
          <td><div id="chart_div1"></div></td>
          <td><div id="chart_div2"></div></td>
          <td><div id="chart_div4"></div></td>
      </table>
    <br/>
  </body>
</html>


Die Funktionen drawChart1 und drawChart4 zeichnen die Histogramme, die Funktion drawChart2 das Tortendiagramm.


3. Starten der Diagrammanalyse im Terminal (für das aktuelle Handelskonto)

Der einfachste Weg ist ein kleines Skript. Wird es auf dem Chart gestartet, startet es den Browser und zeigt die Diagramme der Verteilung der Analyse der Positionen abhängig vom Eröffnungszeitpunkt. Die Hauptsache ist die Angabe des des Datums für den Beginn der Analyse (Eingabeparameter "start"):

//+------------------------------------------------------------------+
//|                                                  test_report.mq5 |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
//---
input datetime start=D'2010.05.11 12:05:00';
#include <DistributionOfProfits.mqh>
//---
CDistributionOfProfits Analysis;
//+------------------------------------------------------------------+
//| Script Programm Start Funktion                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   Analysis.AnalysisTradingHistory(start);
   Analysis.ShowDistributionOfProfits();
  }
//+------------------------------------------------------------------+

Da die Datei "DistributionOfProfits.mqh" die System-DLL verwendet, muss der Import von DLLs erlaubt werden, wenn das Skript laufen soll:

dependencies 


4. Starten der Diagrammanalyse im Strategietester

Verwenden wir zum Beispiel den Expert Advisor "MACD Sample.mq5" von der Standardausstattung (Datenverzeichnis \MQL5\Experts\Examples\MACD\MACD Sample.mq5). Kopieren Sie diesen Experten in ein anderes Verzeichnis, damit die Änderungen das Original nicht verändern. Nennen Sie es "MACD Sample report.mq5". Ändern Sie Folgendes im EA:

//+------------------------------------------------------------------+
//|                                           MACD Sample report.mq5 |
//|                   Copyright 2009-2016, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright   "Copyright 2009-2016, MetaQuotes Software Corp."
#property link        "https://www.mql5.com"
#property version     "5.50"
#property description "It is important to make sure that the expert works with a normal"
#property description "chart and the user did not make any mistakes setting input"
#property description "variables (Lots, TakeProfit, TrailingStop) in our case,"
#property description "we check TakeProfit on a chart of more than 2*trend_period bars"

#define MACD_MAGIC 1234502
//---
#include <Trade\Trade.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\AccountInfo.mqh>
#include <DistributionOfProfits.mqh>
//---
input double InpLots          =0.1; // Lots
input int    InpTakeProfit    =50;  // Take Profit (in pips)
input int    InpTrailingStop  =30;  // Trailing Stop Level (in pips)
input int    InpMACDOpenLevel =3;   // MACD open level (in pips)
input int    InpMACDCloseLevel=2;   // MACD close level (in pips)
input int    InpMATrendPeriod =26;  // MA trend period
//---
int ExtTimeOut=10; // time out in seconds between trade operations
CDistributionOfProfits ExtDistribution;
//+------------------------------------------------------------------+
//| MACD Sample expert class                                         |
//+------------------------------------------------------------------+
class CSampleExpert
  {
protected:

Ergänzen Sie die Funktion OnTester() ganz am Ende:

//+------------------------------------------------------------------+
//| Tester function                                                  |
//+------------------------------------------------------------------+
double OnTester()
  {
//---
   double ret=0.0;
   ExtDistribution.AnalysisTradingHistory(0);
   ExtDistribution.ShowDistributionOfProfits();
//---
   return(ret);
  }

Vor dem Start des Testers erlauben Sie die Verwendung von DLLs in den Einstellungen des Terminal:

options ea 

Das endet mit einer Seite mit dem Analysediagramm von Gewinn/Verlust auf Stundenbasis:

profit/loss hours

Es ist evident, zu welchen Stunden gewinnträchtige Positionen eröffnet werden und wann die Verlustbringer. Die zwei Zeitpunkte mit Verlusten sind gekennzeichnet. Was, wenn wir das Handeln zu diesen unvorteilhaften Stunden beschränken? Oder, gehen wir weiter - kehren wir das Handelssignal um, um Gewinn statt Verlust zu machen? Der Leser ist aufgefordert, das selbst zu testen.

Interessante Ergebnisse erbringen auch Mehrwährungs-EAs im Strategietester. Als Beispiel wurde der freie Mehrwährungs-Experte aus der CodeBase verwendet. Zusätzlich wurde die Datei "#include <DistributionOfProfits.mqh>" geladen und die Variable "CDistributionOfProfits ExtDistribution" deklariert in dem Header des EA, und die Funktion "OnTester()" am Ende hinzugefügt. Nach einem Durchlauf erhalten wir das Ergebnis: "TestAnalysis.htm".

Beachten Sie, alle Diagramme sind interaktiv. Zum Beispiel im ersten Diagramm — "Profit/loss by symbols (Summary)" — können die Werte für jedes Symbol angezeigt werden, wenn die Maus über einem Balken steht:

profit/loss 1 profit/loss 2

Nun zu den Tortendiagrammen: Sie zeigen den Beitrag von jedem Symbol zu Gewinn und Verlust im Zeitraum der Analyse:

profit percent  loss percent

Der Rest der Diagramme zeigt die Profitabilität für jedes Symbol, abhängig von der Eröffnungszeit der Positionen:

 

Dies ist die "stille" Optimierung ohne viel Tamtam

Die Aufgabe, die Verteilung der Handelsergebnisse darzustellen, ist erledigt. Zugleich wird die Kontohistorie in Positionen konsolidiert und alle Analysediagramme werden ausschließlich für diese Positionen gezeigt. Gewinn und Verlust der Positionen werden nach drei Kriterien analysiert: nach Stunde, nach Wochentag und nach Monat.

Mit der Hilfe des Quellcodes aus diesem Artikel kann jede Strategie nach günstigen und ungünstigen Zeiten überprüft werden. Und, wer weiß, vielleicht entwickelt sich aus einer Anfangsidee mehrere, zum Beispiel Strategien für Trend- und Gegentrend. Oder es wird klar, zu welchen Handelszeiten (Asian, Europa oder Amerika) man besser nicht handelt. Versuchen Sie selbst diese "stille" Optimierung, die keine zusätzlichen Testläufe im Strategietester benötigt.

 

Änderungen

Kurz zu den Änderungen, die nach der ersten Veröffentlichung des Artikels am Code vorgenommen wurden.

"DistributionOfProfits.mqh" v.1.027: Einführung eines Schutzes der Programmoperationen während der Optimierung. Der Konstruktor verfügt über den Dateinamen "Non File", und zwei "public" Funktionen prüfen die Konstante MQL_OPTIMIZATION:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CDistributionOfProfits::CDistributionOfProfits(void) : m_name_file("Non File"),
                                                       m_color_loss("ed7d31"),
                                                       m_color_profit("5b9bd5"),
                                                       m_width("440"),
                                                       m_height("400"),
                                                       m_data_number(1)
  {
  }
//+------------------------------------------------------------------+
//| Analysis Trading History                                         |
//+------------------------------------------------------------------+
bool CDistributionOfProfits::AnalysisTradingHistory(const datetime start_time=0)
  {
//---
   if(MQLInfoInteger(MQL_OPTIMIZATION))
      return(false);
//+------------------------------------------------------------------+
//| Show Distribution Of Profits (start of the browser)              |
//+------------------------------------------------------------------+
void CDistributionOfProfits::ShowDistributionOfProfits(void)
  {
//---
   if(MQLInfoInteger(MQL_OPTIMIZATION))
      return;

"DistributionOfProfits.mqh" v.1.033: Jetzt können die Positionen in Geld oder Points oder in Geld und Points analysiert werden.

Diese Art der Analyse ist wie folgt umgesetzt:

//+------------------------------------------------------------------+
//|                                                  test_report.mq5 |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
//+------------------------------------------------------------------+
//| type of report                                                   |
//+------------------------------------------------------------------+
enum ANALYSIS_TYPE
  {
   ONLY_MONEY=0,        // in money 
   ONLY_POINTS=1,       // in points 
   MONEY_AND_POINTS=2,  // in money and points
  };
//---
input datetime start=D'2016.06.28 09:10:00';
input ANALYSIS_TYPE report_type=MONEY_AND_POINTS;
//---
#include <DistributionOfProfits.mqh>
//---
CDistributionOfProfits Analysis;
//+------------------------------------------------------------------+
//| Script Programm Start Funktion                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   Analysis.SetTypeOfAnalysis(report_type);
   Analysis.AnalysisTradingHistory(start);
   Analysis.ShowDistributionOfProfits();
  }
//+------------------------------------------------------------------+