Die Darstellung der Optimierung einer Handelsstrategie im MetaTrader 5

Anatoli Kazharski | 25 April, 2018


Inhalt

Einführung

Bei der Entwicklung von Handelsalgorithmen ist es sinnvoll, die Testergebnisse bei gleichzeitiger Optimierung der Parameter zu beobachten. Die einzelne Darstellung im Reiter Optimization Graph könnte jedoch nicht ausreichen, um die Effizienz einer Handelsstrategie zu beurteilen. Es wäre viel besser, die Saldenkurven mehrerer Tests gleichzeitig zu betrachten, um sie auch nach der Optimierung analysieren zu können. Wir haben eine solche Anwendung bereits im Artikel "Veranschaulichung einer Strategie im Prüfprogramm von MetaTrader 5" untersucht. Seitdem haben sich jedoch viele neue Möglichkeiten ergeben. Damit ist es nun möglich, eine ähnliche, aber wesentlich leistungsfähigere Anwendung zu erstellen.

Der Artikel implementiert eine MQL-Anwendung mit einem grafischen Interface zur erweiterten Darstellung der Optimierung. Das grafische Interface verwendet die letzte Version der Bibliothek EasyAndFast. Viele Benutzer der MQL-Community fragen sich, warum sie grafische Interfaces in MQL-Anwendungen benötigen. Dieser Artikel zeigt die Einsatzmöglichkeiten auf. Es kann auch für diejenigen nützlich sein, die die Bibliothek in ihrer Arbeit anwenden.

Entwickeln des grafischen Interfaces

Hier werde ich kurz die Entwicklung des grafischen Interfaces beschreiben. Wenn Sie bereits mit der Bibliothek EasyAndFast gearbeitet haben, werden Sie schnell verstehen, wie man sie benutzt und wie einfach es ist, das grafische Interface in Ihre MMS-Anwendung einzubinden.

Lassen Sie uns zunächst die allgemeine Struktur der entwickelten Anwendung beschreiben. Die Datei Programm.mqh soll die Klasse CProgramm enthalten. Diese Basisklasse sollte mit der grafischen Library Engine verbunden sein.

//+------------------------------------------------------------------+
//|                                                      Program.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//--- Bibliotheksklasse des grafischen Interfaces
#include <EasyAndFastGUI\WndEvents.mqh>
//+------------------------------------------------------------------+
//| Class for developing the application                             |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
  };

Die Bibliothek EasyAndFast wird in einem einzigen Block (Library GUI) angezeigt, um das Bild nicht zu überladen. Sie können alles in vollem Umfang auf der Seite der Bibliothek sehen. 

 Abb. 1. Einbinden der Bibliothek für das GUI

Abb. 1. Einbinden der Bibliothek für das GUI

Ähnliche Methoden sollten in der Klasse CProgram erstellt werden, um sich mit den Hauptfunktionen des MQL-Programms zu verbinden. Wir benötigen die Methoden der Art OnTesterXXX(), um mit Optimierungsrahmen zu arbeiten.

class CProgram : public CWndEvents
  {
public:
   //--- Initialisierung/Deinitialisierung
   bool              OnInitEvent(void);
   void              OnDeinitEvent(const int reason);
   //--- Ereignisbehandlung "neuer Tick"
   void              OnTickEvent(void);
   //--- Ereignisbehandlung Handel
   void              OnTradeEvent(void);
   //--- Timer
   void              OnTimerEvent(void);
   //--- Tester
   double            OnTesterEvent(void);
   void              OnTesterPassEvent(void);
   void              OnTesterInitEvent(void);
   void              OnTesterDeinitEvent(void);
  };

In diesem Fall sollten die Methoden in folgender Weise in der Hauptdatei der Anwendung aufgerufen werden:

//--- Einbinden der Anwendungsklasse
#include "Program.mqh"
CProgram program;
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Programminitialisierung
   if(!program.OnInitEvent())
     {
      ::Print(__FUNCTION__," > Failed to initialize!");
      return(INIT_FAILED);
     }  
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Experten                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) { program.OnDeinitEvent(reason); }
//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick(void) { program.OnTickEvent(); }
//+------------------------------------------------------------------+
//| Timer Funktion                                                   |
//+------------------------------------------------------------------+
void OnTimer(void) { program.OnTimerEvent(); }
//+------------------------------------------------------------------+
//| Funktion der Chart-Events                                        |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
   { program.ChartEvent(id,lparam,dparam,sparam); }
//+------------------------------------------------------------------+
//| Tester function                                                  |
//+------------------------------------------------------------------+
double OnTester(void) { return(program.OnTesterEvent()); }
//+------------------------------------------------------------------+
//| TesterInit function                                              |
//+------------------------------------------------------------------+
void OnTesterInit(void) { program.OnTesterInitEvent(); }
//+------------------------------------------------------------------+
//| TesterPass function                                              |
//+------------------------------------------------------------------+
void OnTesterPass(void) { program.OnTesterPassEvent(); }
//+------------------------------------------------------------------+
//| TesterDeinit function                                            |
//+------------------------------------------------------------------+
void OnTesterDeinit(void) { program.OnTesterDeinitEvent(); }
//+------------------------------------------------------------------+

Damit ist der Teil der Anwendung bereit für die Entwicklung des grafischen Interfaces. Die Hauptarbeit wird in der Klasse CProgram durchgeführt. Alle für die Arbeit notwendigen Dateien sind in Programm.mqh enthalten.

Definieren wir nun den Inhalt des grafischen Interfaces. Hier die Liste aller zu erstellenden Elemente.

  • Darstellung des Elements.
  • Feld zur Angabe der Anzahl der Salden, die in der Grafik angezeigt werden sollen.
  • Feld zur Einstellung der Geschwindigkeit der wiederholten Anzeige von Optimierungsergebnissen.
  • Taste zum Starten einer wiederholten Anzeige.
  • Tabelle der Ergebnisstatistik.
  • Tabelle zur Anzeige der externen Parameter des EA.
  • Diagramm der Saldenkurve.
  • Diagramm der Optimierungsergebnisse.
  • Statusleiste für die Anzeige zusätzlicher Informationen.
  • Fortschrittsanzeige, die einen Prozentsatz der angezeigten Ergebnisse vom Gesamtaufwand beim erneuten Blättern anzeigt.

Nachfolgend finden Sie Deklarationen der Instanzen der Klassen des Elements und deren Erstellungsmethoden (siehe den Code unten). Die Codes der Methoden werden in einer eigenen Datei - CreateFrameModeGUI.mqh abgelegt, die mit der Datei der Klasse CProgram verknüpft ist. Da der Code der entwickelten Anwendung wächst, wird die Methode der Verteilung nach einzelnen Dateien immer relevanter, was die Navigation im Projekt erleichtert.

class CProgram : public CWndEvents
  {
private:
   //--- Fenster
   CWindow           m_window1;
   //--- Statusleiste
   CStatusBar        m_status_bar;
   //--- Eingabefelder
   CTextEdit         m_curves_total;
   CTextEdit         m_sleep_ms;
   //--- Tasten
   CButton           m_reply_frames;
   //--- Tabellen
   CTable            m_table_stat;
   CTable            m_table_param;
   //--- Grafik
   CGraph            m_graph1;
   CGraph            m_graph2;
   //--- Fortschrittsanzeige
   CProgressBar      m_progress_bar;
   //---
public:
   //--- Erstellen eines grafischen Interfaces für die Arbeit mit Rahmen während der Optimierung
   bool              CreateFrameModeGUI(void);
   //---
private:
   //--- Form
   bool              CreateWindow(const string text);
   //--- Statusleiste
   bool              CreateStatusBar(const int x_gap,const int y_gap);
   //--- Tabellen
   bool              CreateTableStat(const int x_gap,const int y_gap);
   bool              CreateTableParam(const int x_gap,const int y_gap);
   //--- Eingabefelder
   bool              CreateCurvesTotal(const int x_gap,const int y_gap,const string text);
   bool              CreateSleep(const int x_gap,const int y_gap,const string text);
   //--- Tasten
   bool              CreateReplyFrames(const int x_gap,const int y_gap,const string text);
   //--- Grafik
   bool              CreateGraph1(const int x_gap,const int y_gap);
   bool              CreateGraph2(const int x_gap,const int y_gap);
   //--- Fortschrittsanzeige
   bool              CreateProgressBar(const int x_gap,const int y_gap,const string text);
  };
//+------------------------------------------------------------------+
//| Methoden zum Erstellen der Steuerelemente                        |
//+------------------------------------------------------------------+
#include "CreateFrameModeGUI.mqh"
//+------------------------------------------------------------------+

Binden wir auch die benötigte Datei in CreateFrameModeGUI.mqh ein. Wir zeigen hier nur eine Hauptmethode zur Erstellung des grafischen Interfaces der Anwendung als Beispiel:

//+------------------------------------------------------------------+
//|                                           CreateFrameModeGUI.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Program.mqh"
//+------------------------------------------------------------------+
//| Erstellen des grafischen Interfaces für die Analyse              |
//| der Optimierungsergebnisse und der Arbeit mit den Rahmen         |
//+------------------------------------------------------------------+
bool CProgram::CreateFrameModeGUI(void)
  {
//--- Erstellen des Interfaces nur für die Rahmen der Optimierung
   if(!::MQLInfoInteger(MQL_FRAME_MODE))
      return(false);
//--- Erstellen der Form der Steuerelemente
   if(!CreateWindow("Frame mode"))
      return(false);
//--- Erstellen der Steuerelemente
   if(!CreateStatusBar(1,23))
      return(false);
   if(!CreateCurvesTotal(7,25,"Curves total:"))
      return(false);
   if(!CreateSleep(145,25,"Sleep:"))
      return(false);
   if(!CreateReplyFrames(255,25,"Replay frames"))
      return(false);
   if(!CreateTableStat(2,50))
      return(false);
   if(!CreateTableParam(2,212))
      return(false);
   if(!CreateGraph1(200,50))
      return(false);
   if(!CreateGraph2(200,159))
      return(false);
//--- Fortschrittsanzeige
   if(!CreateProgressBar(2,3,"Processing..."))
      return(false);
//--- Komplettes Erstellen der GUI
   CWndEvents::CompletedGUI();
   return(true);
  }
...

Die Verbindung zwischen den Dateien, die zu einer Klasse gehören, wird durch den zweiseitigen, gelben Pfeil gezeigt:

 Abb. 2. Aufteilen des Projektes in mehrere Dateien

Fig. 2. Aufteilen des Projektes in mehrere Dateien



Entwicklung der Klasse des Rahmens

Schreiben wir eine eigene Klasse CFrameGenerator für den Rahmen. Die Klasse soll in FrameGenerator.mqh enthalten sein, die in Program.mqh eingebunden werden soll. Als Beispiel werde ich zwei Möglichkeiten demonstrieren, diese Rahmen zur Darstellung in grafischen Interfaces zu erhalten. 

  • Im ersten Fall werden Zeiger auf diese Objekte an Klassenmethoden übergeben, um Rahmen der Diagrammobjekte anzuzeigen.
  • Im zweiten Fall erhalten wir die Rahmendaten zum Ausfüllen der Tabellen aus anderen Kategorien mit speziellen Methoden. 

Sie entscheiden, welche dieser Optionen als Hauptoption verbleiben werden soll.

Die Bibliothek EasyAndFast verwendet die Klasse CGraphic aus der Standardbibliothek zur Visualisierung der Daten. Fügen wir es der Datei FrameGenerator.mqh hinzu, um auf seine Methoden zuzugreifen.

//+------------------------------------------------------------------+
//|                                               FrameGenerator.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include <Graphics\Graphic.mqh>
//+------------------------------------------------------------------+
//| Klasse, die die Optimierungsergebnisse erhält                    |
//+------------------------------------------------------------------+
class CFrameGenerator
  {
  };

Die Programmstruktur schaut jetzt so aus:

 Abb. 3. Die Verbindungen der Klassen des Projektes

Abb. 3. Die Verbindungen der Klassen des Projektes

Nun wollen wir sehen, wie CFrameGenerator Klasse organisiert ist. Benötigt werden auch Methoden zur Verarbeitung der Ereignisse des Strategie-Testers (siehe den Code dazu unten). Sie sind in ähnlichen Klassenmethoden des von uns entwickelten Programms aufzurufen — CProgram. Zeiger auf die graphischen Objekte, bei denen der aktuelle Optimierungsprozess angezeigt wird, werden an die Methode CFrameGenerator::OnTesterInitEvent() übergeben. 

  • Die erste Grafik (graph_balance) zeigt die angegebene Anzahl der letzten Serien der Optimierungsergebnisse an.
  • Die zweite Grafik (graph_result) zeigt die Gesamtergebnisse der Optimierung.
class CFrameGenerator
  {
private:
   //--- Grafik-Zeiger zur Darstellung der Daten
   CGraphic         *m_graph_balance;
   CGraphic         *m_graph_results;
   //---
public:
   //--- Ereignisbehandlung des Strategietesters
   void              OnTesterEvent(const double on_tester_value);
   void              OnTesterInitEvent(CGraphic *graph_balance,CGraphic *graph_result);
   void              OnTesterDeinitEvent(void);
   bool              OnTesterPassEvent(void);
  };
//+------------------------------------------------------------------+
//| Sollte im OnTesterInit() aufgerufen werden                       |
//+------------------------------------------------------------------+
void CFrameGenerator::OnTesterInitEvent(CGraphic *graph_balance,CGraphic *graph_results)
  {
   m_graph_balance =graph_balance;
   m_graph_results =graph_results;
  }

In beiden Diagrammen werden positive Ergebnisse in grün und negative rot angezeigt.

Von der Methode CFrameGenerator::OnTesterEvent() erhalten wir die Testergebnisse der Salden und der statistischen Werte. Diese Daten werden mit den Methoden CFrameGenerator::GetBalanceData() und CFrameGenerator::GetStatData() an einen Rahmen übergeben. Die Methode CFrameGenerator::GetBalanceData() übernimmt die gesamte Testhistorie und summiert alle in-/inout Transaktionen. Das erhaltene Ergebnis wird Schritt für Schritt im Array m_balance[] gespeichert. Dieses Array wiederum ist Mitglied der Klasse CFrameGenerator.

Das an einen Rahmen zu sendende dynamische Array wird der Methode CFrameGenerator::GetStatData() übergeben. Seine Größe soll der Größe des Arrays für die zuvor empfangene Ergebnissaldo entsprechen. Außerdem wird eine Reihe von Elementen, zu denen wir statistische Parameter erhalten, hinzugefügt.

//--- Anzahl der statistischen Werte
#define STAT_TOTAL 7
//+------------------------------------------------------------------+
//| Klasse für die Arbeit der Optimierungsergebnisse                 |
//+------------------------------------------------------------------+
class CFrameGenerator
  {
private:
   //--- Ergebnissaldo
   double            m_balance[];
   //---
private:
   //--- Erhalt des Saldos
   int               GetBalanceData(void);
   //--- Erhalt der statistischen Werte
   void              GetStatData(double &dst_array[],double on_tester_value);
  };
//+------------------------------------------------------------------+
//| Abfrage des Saldos                                               |
//+------------------------------------------------------------------+
int CFrameGenerator::GetBalanceData(void)
  {
   int    data_count      =0;
   double balance_current =0;
//--- Abfrage der gesamte Handelshistorie
   ::HistorySelect(0,LONG_MAX);
   uint deals_total=::HistoryDealsTotal();
//--- Sammeln der Daten der Positionen
   for(uint i=0; i<deals_total; i++)
     {
      //--- Abfrage der Ticketnummer
      ulong ticket=::HistoryDealGetTicket(i);
      if(ticket<1)
         continue;
      //--- Falls Anfangssaldo oder eine out-/inout Position
      long entry=::HistoryDealGetInteger(ticket,DEAL_ENTRY);
      if(i==0 || entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_INOUT)
        {
         double swap      =::HistoryDealGetDouble(ticket,DEAL_SWAP);
         double profit    =::HistoryDealGetDouble(ticket,DEAL_PROFIT);
         double commision =::HistoryDealGetDouble(ticket,DEAL_COMMISSION);
         //--- Saldenberechnung
         balance_current+=(profit+swap+commision);
         //--- Sichern im Array
         data_count++;
         ::ArrayResize(m_balance,data_count,100000);
         m_balance[data_count-1]=balance_current;
        }
     }
//--- Rückgabe der Anzahl der Daten
   return(data_count);
  }
//+------------------------------------------------------------------+
//| Erhalt der statistischen Werte                                   |
//+------------------------------------------------------------------+
void CFrameGenerator::GetStatData(double &dst_array[],double on_tester_value)
  {
   ::ArrayResize(dst_array,::ArraySize(m_balance)+STAT_TOTAL);
   ::ArrayCopy(dst_array,m_balance,STAT_TOTAL,0);
//--- Ausfüllen des ersten Arrays (STAT_TOTAL) mit den Testergebnissen
   dst_array[0] =::TesterStatistics(STAT_PROFIT);               // Nettogewinn
   dst_array[1] =::TesterStatistics(STAT_PROFIT_FACTOR);        // Profit-Faktor
   dst_array[2] =::TesterStatistics(STAT_RECOVERY_FACTOR);      // Erholungsfaktor
   dst_array[3] =::TesterStatistics(STAT_TRADES);               // Positionsanzahl
   dst_array[4] =::TesterStatistics(STAT_DEALS);                // Anzahl der Transaktionen
   dst_array[5] =::TesterStatistics(STAT_EQUITY_DDREL_PERCENT); // maximaler Drawdown in %
   dst_array[6] =on_tester_value;                               // Wert des Optimierungskriteriums
  }

Die Methoden CFrameGenerator::GetBalanceData() und CFrameGenerator::GetStatData() werden von der letzten Methode der Ereignisbehandlung aufgerufen, von CFrameGenerator::OnTesterEvent(). Data erhalten. Im Rahmen an des Terminals senden

//+------------------------------------------------------------------+
//| Vorbereiten des Salden-Arrays und absenden an den Rahmen         |
//| Aufruf der Funktion in der Funktion OnTester() des EAs           |
//+------------------------------------------------------------------+
void CFrameGenerator::OnTesterEvent(const double on_tester_value)
  {
//--- Abfragen des Saldos
   int data_count=GetBalanceData();
//--- Array zum Senden an den Rahmen
   double stat_data[];
   GetStatData(stat_data,on_tester_value);
//--- Erstellen eines Rahmens mit Daten, um es an das Terminal zu senden
   if(!::FrameAdd(::MQLInfoString(MQL_PROGRAM_NAME),1,data_count,stat_data))
      ::Print(__FUNCTION__," > Frame add error: ",::GetLastError());
   else
      ::Print(__FUNCTION__," > Frame added, Ok");
  }

Betrachten wir nun die Methoden, die von der Ereignisbehandlung im Rahmen während der Optimierung verwendet werden sollen — CFrameGenerator::OnTesterPassEvent(). Wir benötigen die Variablen für die Arbeit mit dem Rahmen: Name, ID, Laufnummer, akzeptierter Wert und akzeptiertes Datenarray. Alle diese Daten werden mit der oben dargestellten Funktion FrameAdd() an den Rahmen gesendet.

class CFrameGenerator
  {
private:
   //--- Variablen für den Rahmen
   string            m_name;
   ulong             m_pass;
   long              m_id;
   double            m_value;
   double            m_data[];
  };

Die Methode CFrameGenerator::SaveStatData() speichert das Array, das wir im Frame akzeptiert haben mit den statistischen Werten in einem eigenen Zeichen-Array. Dort sollen die Daten den Indikatornamen und dessen Wert enthalten. Das Symbol '=' wird als Trennzeichen verwendet.

class CFrameGenerator
  {
private:
   //--- Array mit den statistischen Werten
   string            m_stat_data[];
   //---
private:
   //--- Sichern der statistischen Werte 
   void              SaveStatData(void);
  };
//+------------------------------------------------------------------+
//| Sichern der statistischen Ergebnisse im Array                    |
//+------------------------------------------------------------------+
void CFrameGenerator::SaveStatData(void)
  {
//--- Array der statistischen Werte für den Rahmen
   double stat[];
   ::ArrayCopy(stat,m_data,0,0,STAT_TOTAL);
   ::ArrayResize(m_stat_data,STAT_TOTAL);
//--- Ausfüllen des Arrays mit den Testergebnissen
   m_stat_data[0] ="Net profit="+::StringFormat("%.2f",stat[0]);
   m_stat_data[1] ="Profit Factor="+::StringFormat("%.2f",stat[1]);
   m_stat_data[2] ="Factor Recovery="+::StringFormat("%.2f",stat[2]);
   m_stat_data[3] ="Trades="+::StringFormat("%G",stat[3]);
   m_stat_data[4] ="Deals="+::StringFormat("%G",stat[4]);
   m_stat_data[5] ="Equity DD="+::StringFormat("%.2f%%",stat[5]);
   m_stat_data[6] ="OnTester()="+::StringFormat("%G",stat[6]);
  }

Die statistischen Werte müssen in einem eigenen Array gespeichert werden, damit sie von der Klasse (CProgram) abgerufen werden können, um die Tabelle aufzufüllen. Um sie zu erhalten, wird die public Methode CFrameGenerator::CopyStatData() aufgerufen, die einen Array zum Kopieren übergibt.

class CFrameGenerator
  {
public:
   //--- Übergeben der statistischen Werte an das Array
   int               CopyStatData(string &dst_array[]) { return(::ArrayCopy(dst_array,m_stat_data)); }
  };

Um die Graphen der Ergebnisse während der Optimierung zu aktualisieren, benötigen wir zusätzlichen Methoden, die für das Hinzufügen positiver und negativer Ergebnisse zu den Arrays verantwortlich sind. Beachten Sie, dass das Ergebnis entlang der X-Achse zum aktuellen Wert des Ramhemzählers hinzugefügt wird. Daher werden die gebildeten Lücken nicht als Null im Diagramm angezeigt.

//--- Stand-by-Größe des Arrays
#define RESERVE_FRAMES 1000000
//+------------------------------------------------------------------+
//| Klasse für die Arbeit der Optimierungsergebnisse                 |
//+------------------------------------------------------------------+
class CFrameGenerator
  {
private:
   //--- Zähler des Rahmens
   ulong             m_frames_counter;
   //--- Daten der pos. und neg. Ergebnisse
   double            m_loss_x[];
   double            m_loss_y[];
   double            m_profit_x[];
   double            m_profit_y[];
   //---
private:
   //--- Hinzufügen der (1) negativen und (2) positiven Ergebnisse zum Array
   void              AddLoss(const double loss);
   void              AddProfit(const double profit);
  };
//+------------------------------------------------------------------+
//| Hinzufügen der negativen Ergebnisse zum Array                    |
//+------------------------------------------------------------------+
void CFrameGenerator::AddLoss(const double loss)
  {
   int size=::ArraySize(m_loss_y);
   ::ArrayResize(m_loss_y,size+1,RESERVE_FRAMES);
   ::ArrayResize(m_loss_x,size+1,RESERVE_FRAMES);
   m_loss_y[size] =loss;
   m_loss_x[size] =(double)m_frames_counter;
  }
//+------------------------------------------------------------------+
//| Hinzufügen der positiven Ergebnisse zum Array                    |
//+------------------------------------------------------------------+
void CFrameGenerator::AddProfit(const double profit)
  {
   int size=::ArraySize(m_profit_y);
   ::ArrayResize(m_profit_y,size+1,RESERVE_FRAMES);
   ::ArrayResize(m_profit_x,size+1,RESERVE_FRAMES);
   m_profit_y[size] =profit;
   m_profit_x[size] =(double)m_frames_counter;
  }

Hier sind die wichtigsten Methoden zum Aktualisieren der Diagramme CFrameGenerator::UpdateResultsGraph() und CFrameGenerator::UpdateBalanceGraph():

class CFrameGenerator
  {
private:
   //--- Aktualisieren der Grafik
   void              UpdateResultsGraph(void);
   //--- Aktualisieren der Saldenkurve
   void              UpdateBalanceGraph(void);
  };

In der Methode CFrameGenerator::UpdateResultsGraph() werden die Testergebnisse (Gewinn/Verlust) zu den Arrays hinzugefügt. Dann werden diese Daten auf dem entsprechenden Diagramm angezeigt. Die Titel der Reihe von Graphen zeigen die aktuelle Anzahl der Gewinner und Verlierer. 

//+------------------------------------------------------------------+
//| Update results graph                                             |
//+------------------------------------------------------------------+
void CFrameGenerator::UpdateResultsGraph(void)
  {
//--- Negative Ergebnisse
   if(m_data[0]<0)
      AddLoss(m_data[0]);
//--- Positive Ergebnisse
   else
      AddProfit(m_data[0]);
//--- Aktualisieren der Reihe der Grafik der Optimierungsergebnisse
   CCurve *curve=m_graph_results.CurveGetByIndex(0);
   curve.Name("P: "+(string)ProfitsTotal());
   curve.Update(m_profit_x,m_profit_y);
//---
   curve=m_graph_results.CurveGetByIndex(1);
   curve.Name("L: "+(string)LossesTotal());
   curve.Update(m_loss_x,m_loss_y);
//--- Eigenschaften der horizontalen Achse
   CAxis *x_axis=m_graph_results.XAxis();
   x_axis.Min(0);
   x_axis.Max(m_frames_counter);
   x_axis.DefaultStep((int)(m_frames_counter/8.0));
//--- Aktualisieren der Grafik
   m_graph_results.CalculateMaxMinValues();
   m_graph_results.CurvePlotAll();
   m_graph_results.Update();
  }

Am Anfang der Methode CFrameGenerator::UpdateBalanceGraph() werden die Daten der Salden des Arrays dem Rahmen übergebenen. Da mehrere Reihen im Diagramm dargestellt werden können, sollten wir die Aktualisierung der Reihen konsistent gestalten. Um dies zu erreichen, verwenden wir einen eigenen Zähler für de Reihen. Um die Anzahl der gleichzeitig angezeigten Reihen von Salden im Diagramm zu konfigurieren, benötigen wir die public Methode CFrameGenerator::SetCurvesTotal(). Sobald der Zähler darin die festgelegte Grenze erreicht, beginnt die Zählung von Anfang. Der Zähler eines Rahmens dient als dessen Namen. Die Farbe der Reihe hängt auch vom Ergebnis ab: Grün steht für ein positives, Rot für ein negatives Ergebnis.

Da die Anzahl der Positionen sich in jedem Test unterscheiden, sollten wir die größte Zahl einer Testreihe als Maximum der X-Achse setzen, damit alle in die Darstellung passen.

class CFrameGenerator
  {
private:
   //--- Anzahl der Reihen
   uint              m_curves_total;
   //--- Index der aktuellen Reihe der Grafik
   uint              m_last_serie_index;
   //--- Definition des Maximums
   double            m_curve_max[];
   //---
public:
   //--- Setzen der Anzahl der dargestellten Reihen in der Grafik
   void              SetCurvesTotal(const uint total);
  };
//+------------------------------------------------------------------+
//| Setzen der Anzahl der dargestellten Reihen in der Grafik         |
//+------------------------------------------------------------------+
void CFrameGenerator::SetCurvesTotal(const uint total)
  {
   m_curves_total=total;
   ::ArrayResize(m_curve_max,total);
   ::ArrayInitialize(m_curve_max,0);
  }
//+------------------------------------------------------------------+
//| Aktualisieren der Saldenkurve                                    |
//+------------------------------------------------------------------+
void CFrameGenerator::UpdateBalanceGraph(void)
  {
//--- Array der Salden des aktuellen Rahmens
   double serie[];
   ::ArrayCopy(serie,m_data,0,STAT_TOTAL,::ArraySize(m_data)-STAT_TOTAL);
//--- Senden des Arrays für die Darstellung in der Graphik der Salden
   CCurve *curve=m_graph_balance.CurveGetByIndex(m_last_serie_index);
   curve.Name((string)m_frames_counter);
   curve.Color((m_data[0]>=0)? ::ColorToARGB(clrLimeGreen) : ::ColorToARGB(clrRed));
   curve.Update(serie);
//--- Ermitteln der Größe der Reihen
   int serie_size=::ArraySize(serie);
   m_curve_max[m_last_serie_index]=serie_size;
//--- Bestimmen des Maximums der Elemente der Reihen
   double x_max=0;
   for(uint i=0; i<m_curves_total; i++)
      x_max=::fmax(x_max,m_curve_max[i]);
//--- Eigenschaften der horizontalen Achse
   CAxis *x_axis=m_graph_balance.XAxis();
   x_axis.Min(0);
   x_axis.Max(x_max);
   x_axis.DefaultStep((int)(x_max/8.0));
//--- Aktualisieren der Grafik
   m_graph_balance.CalculateMaxMinValues();
   m_graph_balance.CurvePlotAll();
   m_graph_balance.Update();
//--- Erhöhen des Zählers der Reihen
   m_last_serie_index++;
//--- Wird die Grenze erreicht, wird der Zähler der Reihen auf Null gesetzt
   if(m_last_serie_index>=m_curves_total)
      m_last_serie_index=0;
  }

Wir besprachen die notwendig Methoden für die Arbeit mit dem Rahmen. Betrachten wir nun die Ereignisbehandlung durch die Methode CFrameGenerator::OnTesterPassEvent() selbst. Sie gibt true zurück, während die Optimierung läuft und die Funktion FrameNext() liefert die Daten des Rahmens. Nach Abschluss der Optimierung gibt die Methode false zurück.

In der Liste der Parameter des EAs, die mit der Funktion FrameInputs() ermittelt werden können, werden zuerst die für die Optimierung eingestellten Parameter gefolgt von denen, die nicht an der Optimierung teilnehmen. 

Wenn die Daten des Rahmens erhalten werden, erlaubt uns die Funktion FrameInputs() , die Parameter des EAs während der aktuellen Optimierung zu erhalten. Dann speichern wir die statistischen Ergebnisse, aktualisieren die Grafiken und erhöhen den Zähler des Rahmens. Danach gibt die Methode CFrameGenerator::OnTesterPassEvent() bis zum nächsten Aufruf true zurück.

class CFrameGenerator
  {
private:
   //--- EA Parameter
   string            m_param_data[];
   uint              m_par_count;
  };
//+------------------------------------------------------------------+
//| Erhalt der Daten mit Rahmen während der Optimierung und Anzeige  |
//+------------------------------------------------------------------+
bool CFrameGenerator::OnTesterPassEvent(void)
  {
//--- Nach dem Erhalt des neuen Rahmens, versuchen die Daten dessen zu erhalten
   if(::FrameNext(m_pass,m_name,m_id,m_value,m_data))
     {
      //--- Abfrage der Eingabeparameter des EAs für den Rahmen
      ::FrameInputs(m_pass,m_param_data,m_par_count);
      //--- Sichern der statistischen Ergebnisse im Array
      SaveStatData();
      //--- Aktualisieren der Daten und der Saldenkurve
      UpdateResultsGraph();
      UpdateBalanceGraph();
      //--- Erhöhen des Rahmenzählers
      m_frames_counter++;
      return(true);
     }
//---
   return(false);
  }

Nach Abschluss der Optimierung wird das Ereignis TesterDeinit erzeugt und die Methode CFrameGenerator::OnTesterDeinitEvent() im Verarbeitungsmodus des Rahmens aufgerufen. Im Moment können nicht alle Rahmen während der Optimierung verarbeitet werden, daher ist die Ergebnisdarstellung unvollständig. Um das ganze Bild zu sehen, müssen Sie alle Rahmen mit der Methode CFrameGenerator::FinalRecalculateFrames() durchlaufen und den Graphen direkt nach der Optimierung neu laden.

Dazu verschieben wir den Zeiger auf den Anfang der Liste der Rahmen und setzen dann die Ergebnis-Arrays und Zähler der Rahmen auf Null. Dann durchlaufen Sie die vollständige Liste der Rahmen, füllen wir die Arrays mit positiven und negativen Ergebnissen aus und aktualisieren schließlich die Grafik.

class CFrameGenerator
  {
private:
   //--- Freigeben des Arrays
   void              ArraysFree(void);
   //--- Letzte Datenberechnung aller Rahmen nach der Optimierung
   void              FinalRecalculateFrames(void);
  };
//+------------------------------------------------------------------+
//| Freigeben des Arrays                                             |
//+------------------------------------------------------------------+
void CFrameGenerator::ArraysFree(void)
  {
   ::ArrayFree(m_loss_y);
   ::ArrayFree(m_loss_x);
   ::ArrayFree(m_profit_y);
   ::ArrayFree(m_profit_x);
  }
//+------------------------------------------------------------------+
//| Letzte Datenberechnung aller Rahmen nach der Optimierung         |
//+------------------------------------------------------------------+
void CFrameGenerator::FinalRecalculateFrames(void)
  {
//--- Setzen des Zeigers auf den Startpunkt
   ::FrameFirst();
//--- Rücksetzen der Zähler und der Arrays
   ArraysFree();
   m_frames_counter=0;
//--- Starten der Schleife über alle Rahmen
   while(::FrameNext(m_pass,m_name,m_id,m_value,m_data))
     {
      //--- Negative Ergebnisse
      if(m_data[0]<0)
         AddLoss(m_data[0]);
      //--- Positive Ergebnisse
      else
         AddProfit(m_data[0]);
      //--- Erhöhen des Rahmenzählers
      m_frames_counter++;
     }
//--- Aktualisieren der Grafik
   CCurve *curve=m_graph_results.CurveGetByIndex(0);
   curve.Name("P: "+(string)ProfitsTotal());
   curve.Update(m_profit_x,m_profit_y);
//---
   curve=m_graph_results.CurveGetByIndex(1);
   curve.Name("L: "+(string)LossesTotal());
   curve.Update(m_loss_x,m_loss_y);
//--- Eigenschaften der horizontalen Achse
   CAxis *x_axis=m_graph_results.XAxis();
   x_axis.Min(0);
   x_axis.Max(m_frames_counter);
   x_axis.DefaultStep((int)(m_frames_counter/8.0));
//--- Aktualisieren der Grafik
   m_graph_results.CalculateMaxMinValues();
   m_graph_results.CurvePlotAll();
   m_graph_results.Update();
  }

In diesem Fall schaut der der Code der Methode CFrameGenerator::OnTesterDeinitEvent()wie unten aufgeführt auf. Hier erinnern wir uns auch an die Anzahl der Rahmen und setzen den Zähler auf Null.

//+------------------------------------------------------------------+
//| Sollte im OnTesterDeinit() aufgerufen werden                     |
//+------------------------------------------------------------------+
void CFrameGenerator::OnTesterDeinitEvent(void)
  {
//--- Letzte Datenberechnung aller Rahmen nach der Optimierung
   FinalRecalculateFrames();
//--- Gesamtzahl der Rahmen und Rücksetzen des Zählers auf Null
   m_frames_total     =m_frames_counter;
   m_frames_counter   =0;
   m_last_serie_index =0;
  }

Als nächstes werfen wir einen Blick auf die Verwendung der Klassenmethoden CFrameGenerator in der Anwendungsklasse. 


Arbeiten mit den optimierten Daten in der Anwendungsklasse

Das grafische Interface wird in der Initialisierungsmethode CProgram::OnTesterInitEvent() des Tests erstellt. Danach sollte das grafische Interface unzugänglich gemacht werden. Dazu benötigen wir die zusätzlichen Methoden CProgram::IsAvailableGUI() und CProgram::IsLockedGUI(), die in anderen Klassenmethoden aus CProgram verwendet werden.

Lassen Sie uns den Generator des Rahmens initialisieren: Übergeben wir die Zeiger auf die Diagramme, die zur Visualisierung der Optimierungsergebnisse verwendet werden sollen.

class CProgram : public CWndEvents
  {
private:
   //--- Interface Verfügbarkeit
   void              IsAvailableGUI(const bool state);
   void              IsLockedGUI(const bool state);
  }
//+------------------------------------------------------------------+
//| Optimierungsbeginn                                               |
//+------------------------------------------------------------------+
void CProgram::OnTesterInitEvent(void)
  {
//--- Erstellen des grafischen Interfaces
   if(!CreateFrameModeGUI())
     {
      ::Print(__FUNCTION__," > Could not create the GUI!");
      return;
     }
//--- Grafisches Interface unzugänglich machen
   IsLockedGUI(false);
//--- Initialisierung des Rahmengenerators
   m_frame_gen.OnTesterInitEvent(m_graph1.GetGraphicPointer(),m_graph2.GetGraphicPointer());
  }
//+------------------------------------------------------------------+
//| Interface Verfügbarkeit                                          |
//+------------------------------------------------------------------+
void CProgram::IsAvailableGUI(const bool state)
  {
   m_window1.IsAvailable(state);
   m_sleep_ms.IsAvailable(state);
   m_curves_total.IsAvailable(state);
   m_reply_frames.IsAvailable(state);
  }
//+------------------------------------------------------------------+
//| Blockieren des Interfaces                                        |
//+------------------------------------------------------------------+
void CProgram::IsLockedGUI(const bool state)
  {
   m_window1.IsAvailable(state);
   m_sleep_ms.IsLocked(!state);
   m_curves_total.IsLocked(!state);
   m_reply_frames.IsLocked(!state);
  }

Wir haben bereits erwähnt, dass die Daten in den Tabellen in der Anwendungsklasse mit den Methoden CProgram::UpdateStatTable() und CProgram::UpdateParamTable() aktualisiert werden sollen. Der Code für beide Tabellen ist identisch, so dass wir hier beispielsweise nur eine besprechen. Die Namen und die Werte der Parameter werden in der gleichen Zeile, getrennt durch ein '=' dargestellt. Deshalb durchlaufen wir sie in einer Schleife und teilen sie in ein separates Array, das in zwei Elemente unterteilt ist. Dann geben wir diese Werte in Tabellenzellen ein.

class CProgram : public CWndEvents
  {
private:
   //--- Aktualisieren der statistischen Tabelle
   void              UpdateStatTable(void);
   //--- Aktualisieren der Parametertabelle
   void              UpdateParamTable(void);
  }
//+------------------------------------------------------------------+
//| Aktualisieren der statistischen Tabelle                          |
//+------------------------------------------------------------------+
void CProgram::UpdateStatTable(void)
  {
//--- Datenabfrage der statistischen Tabelle
   string stat_data[];
   int total=m_frame_gen.CopyStatData(stat_data);
   for(int i=0; i<total; i++)
     {
      //--- Teilen in zwei Zeilen und einfügen in die Tabelle
      string array[];
      if(::StringSplit(stat_data[i],'=',array)==2)
        {
         if(m_frame_gen.CurrentFrame()>1)
            m_table_stat.SetValue(1,i,array[1],0,true);
         else
           {
            m_table_stat.SetValue(0,i,array[0],0,true);
            m_table_stat.SetValue(1,i,array[1],0,true);
           }
        }
     }
//--- Aktualisieren der Tabelle
   m_table_stat.Update();
  }

Beide Methoden für die Aktualisierung der Tabellen werden in der Methode CProgram::OnTesterPassEvent() durch eine positive Antwort der Methode gleichen Namens CFrameGenerator::OnTesterPassEvent() aufgerufen:

//+------------------------------------------------------------------+
//| Optimierungsdurchlauf                                            |
//+------------------------------------------------------------------+
void CProgram::OnTesterPassEvent(void)
  {
//--- Erhaltene Testergebnisse und grafische Anzeige
   if(m_frame_gen.OnTesterPassEvent())
     {
      UpdateStatTable();
      UpdateParamTable();
     }
  }

Nach dem Ende der Optimierung berechnet die Methode CProgram::CalculateProfitsAndLosses() den Prozentsatz jeweils der Gewinner und Verlierer und zeigt diese Daten in der Statusleiste an:

class CProgram : public CWndEvents
  {
private:
   //--- Berechnung des Verhältnisses der Gewinner und Verlierer
   void              CalculateProfitsAndLosses(void);
  }
//+------------------------------------------------------------------+
//| Berechnung des Verhältnisses der Gewinner und Verlierer          |
//+------------------------------------------------------------------+
void CProgram::CalculateProfitsAndLosses(void)
  {
//--- Beenden, falls es keinen Rahmen gibt
   if(m_frame_gen.FramesTotal()<1)
      return;
//--- Anzahl der Gewinner und Verlierer
   int losses  =m_frame_gen.LossesTotal();
   int profits =m_frame_gen.ProfitsTotal();
//--- Prozentsatz
   string pl =::DoubleToString(((double)losses/(double)m_frame_gen.FramesTotal())*100,2);
   string pp =::DoubleToString(((double)profits/(double)m_frame_gen.FramesTotal())*100,2);;
//--- Anzeige in der Statusleiste
   m_status_bar.SetValue(1,"Profits: "+(string)profits+" ("+pp+"%)"+" / Losses: "+(string)losses+" ("+pl+"%)");
   m_status_bar.GetItemPointer(1).Update(true);
  }

Der Code der Methode zur Verarbeitung des Ereignisses TesterDeinit wird unten angezeigt. Die Initialisierung des Grafikkerns bedeutet, dass die Bewegung des Mauszeigers verfolgt und der Timer eingeschaltet werden soll. In der aktuellen Version MetaTrader 5 schaltet sich der Timer leider nicht ein, wenn die Optimierung abgeschlossen ist. Hoffen wir, dass sich diese Gelegenheit in Zukunft bietet.

//+------------------------------------------------------------------+
//| Ende der Optimierung                                             |
//+------------------------------------------------------------------+
void CProgram::OnTesterDeinitEvent(void)
  {
//--- Ende der Optimierung
   m_frame_gen.OnTesterDeinitEvent();
//--- Grafisches Interface zugänglich machen
   IsLockedGUI(true);
//--- Berechnung des Verhältnisses der Gewinner und Verlierer
   CalculateProfitsAndLosses();
//--- Initialisierung des Kerns der GUI
   CWndEvents::InitializeCore();
  }

Jetzt können wir auch mit den Daten des Rahmens arbeiten, nachdem die Optimierung abgeschlossen ist. Der EA wird in das Terminaldiagramm eingefügt, und die Frames können zur Analyse der Ergebnisse aufgerufen werden. Das grafische Interface macht alles intuitiv. In der Ereignisbehandlungsmethode CProgram::OnEvent() verfolgen wir:

  • Änderungen im Eingabefeld zur Einstellung der Anzahl der angezeigten Salden im Diagramm;
  • Starten der Anzeige der Optimierungsergebnisse.

Die Methode CProgram::UpdateBalanceGraph() wird zur Aktualisierung des Graphen nach Änderung der Anzahl der Reihen verwendet. Hier stellen wir die Anzahl der Reihen für die Arbeit im Generator des Rahmens ein und reservieren diese Anzahl auf der Grafik.

class CProgram : public CWndEvents
  {
private:
   //--- Aktualisieren der Grafik
   void              UpdateBalanceGraph(void);
  };
//+------------------------------------------------------------------+
//| Aktualisieren der Grafik                                         |
//+------------------------------------------------------------------+
void CProgram::UpdateBalanceGraph(void)
  {
//--- Setzen der Anzahl der Reihen
   int curves_total=(int)m_curves_total.GetValue();
   m_frame_gen.SetCurvesTotal(curves_total);
//--- Löschen der Reihen
   CGraphic *graph=m_graph1.GetGraphicPointer();
   int total=graph.CurvesTotal();
   for(int i=total-1; i>=0; i--)
      graph.CurveRemoveByIndex(i);
//--- Hinzufügen der Reihen
   double data[];
   for(int i=0; i<curves_total; i++)
      graph.CurveAdd(data,CURVE_LINES,"");
//--- Aktualisieren der Grafik
   graph.CurvePlotAll();
   graph.Update();
  }

Von der Ereignisbehandlung wird die Methode CProgram::UpdateBalanceGraph() aufgerufen, wenn im Eingabefeld die Taste (ON_CLICK_BUTTON) umgeschaltet wird und wenn ein Wert über die Tastatur in das Feld eingegeben wird (ON_END_EDIT):

//+------------------------------------------------------------------+
//| Ereignisbehandlung                                               |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Ereignis eines Tastendrucks
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- Ändern der Anzahl der Reihen der Grafik
      if(lparam==m_curves_total.Id())
        {
         UpdateBalanceGraph();
         return;
        }
      return;
     }
//--- Event of entering the value in the input field
   if(id==CHARTEVENT_CUSTOM+ON_END_EDIT)
     {
      //--- Ändern der Anzahl der Reihen der Grafik
      if(lparam==m_curves_total.Id())
        {
         UpdateBalanceGraph();
         return;
        }
      return;
     }
  }

Um die Ergebnisse nach der Optimierung in der Klasse CFrameGenerator anzuzeigen, ist die public Methode CFrameGenerator::ReplayFrames() implementiert. Hier definieren wir ganz am Anfang durch den Zähler der Rahmen: Wenn der Prozess gerade erst begonnen hat, werden die Arrays auf Null gesetzt, und der Zeiger auf die Rahmen wird an den Anfang der Liste verschoben. Danach werden die Rahmen durchlaufen und die gleichen Aktionen wie in der zuvor beschriebenen Methode CFrameGenerator::OnTesterPassEvent() ausgeführt. Wenn ein Rahmen empfangen wird, gibt die Methode true zurück. Nach Abschluss werden die Zähler der Rahmen und der Reihen auf Null gesetzt und die Methode gibt false zurück. 

class CFrameGenerator
  {
public:
   //--- Schleife über alle Rahmen
   bool              ReplayFrames(void);
  };
//+------------------------------------------------------------------+
//| Wiederholung der Rahmen nach dem Ende der Optimierung            |
//+------------------------------------------------------------------+
bool CFrameGenerator::ReplayFrames(void)
  {
//--- Setzen des Zeigers auf die Rahmen auf den Anfang
   if(m_frames_counter<1)
     {
      ArraysFree();
      ::FrameFirst();
     }
//--- Starten der Schleife über alle Rahmen
   if(::FrameNext(m_pass,m_name,m_id,m_value,m_data))
     {
      //--- Abfrage der Eingabewerte des EAs, die für den Rahmen relevant sind
      ::FrameInputs(m_pass,m_param_data,m_par_count);
      //--- Sichern der statistischen Ergebnisse im Array
      SaveStatData();
      //--- Aktualisieren der Daten und der Saldenkurve
      UpdateResultsGraph();
      UpdateBalanceGraph();
      //--- Erhöhen des Rahmenzählers
      m_frames_counter++;
      return(true);
     }
//--- Schleife beendet
   m_frames_counter   =0;
   m_last_serie_index =0;
   return(false);
  }

Die Methode CFrameGenerator::ReplayFrames() wird in der Klasse CProgram von der Methode ViewOptimizationResults() aufgerufen. Bevor die Rahmen gestartet werden, wird das grafische Interface unerreichbar. Die Geschwindigkeit des Blätterns kann durch die Pauseneinstellung von Sleep mittels des Eingabefeldes bestimmt werden. Zugleich zeigt die Statusleiste den Fortschrittsbalken, der die Zeit bis zum Ende darstellt.

class CFrameGenerator
  {
private:
   //--- Anzeige der Optimierungsergebnisse
   void              ViewOptimizationResults(void);
  };
//+------------------------------------------------------------------+
//| Anzeige der Optimierungsergebnisse                               |
//+------------------------------------------------------------------+
void CProgram::ViewOptimizationResults(void)
  {
//--- Grafisches Interface unzugänglich machen
   IsAvailableGUI(false);
//--- Pause
   int pause=(int)m_sleep_ms.GetValue();
//--- Abspielen der Rahmen
   while(m_frame_gen.ReplayFrames() && !::IsStopped())
     {
      //--- Aktualisieren der Tabellen
      UpdateStatTable();
      UpdateParamTable();
      //--- Aktualisieren der Fortschrittsanzeige
      m_progress_bar.Show();
      m_progress_bar.LabelText("Replay frames: "+string(m_frame_gen.CurrentFrame())+"/"+string(m_frame_gen.FramesTotal()));
      m_progress_bar.Update((int)m_frame_gen.CurrentFrame(),(int)m_frame_gen.FramesTotal());
      //--- Pause
      ::Sleep(pause);
     }
//--- Berechnung des Verhältnisses der Gewinner und Verlierer
   CalculateProfitsAndLosses();
//--- Ausblenden der Fortschrittsanzeige
   m_progress_bar.Hide();
//--- Grafisches Interface zugänglich machen
   IsAvailableGUI(true);
   m_reply_frames.MouseFocus(false);
   m_reply_frames.Update(true);
  }

Die Methode CProgram::ViewOptimizationResults() wird durch einen Druck auf die Taste Replay frames auf der Anwendung des grafischen Interfaces aufgerufen. ON_CLICK_BUTTON event is generated.

//+------------------------------------------------------------------+
//| Ereignisbehandlung                                               |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Ereignis eines Tastendrucks
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- Anzeige der Optimierungsergebnisse 
      if(lparam==m_reply_frames.Id())
        {
         ViewOptimizationResults();
         return;
        }
      //--- 
      ...
      return;
     }
  }

Jetzt ist es an der Zeit, die Ergebnisse anzusehen und zu definieren, was ein Benutzer während der Optimierung bei der Arbeit mit den Rahmen tatsächlich auf dem Diagramm sieht.


Ergebnisanzeige

Für Tests verwenden wir den Handelsalgorithmus aus der Standardauslieferung — Moving Average. Wir werden ihn ("as is") ohne Ergänzungen und Korrekturen verwenden. Alle Dateien der entwickelten Anwendung sollen sich im gleichen Ordner befinden. Die Strategie-Datei wird in die Datei Programm.mqh aufgenommen.

FormatString.mqh ist hier als Zusatz mit Funktionen zur Zeilenformatierung enthalten. Sie sind noch nicht Teil einer Klasse, also lassen Sie uns den Pfeil mit schwarzer Farbe markieren. Die resultierende Anwendungsstruktur sieht wie folgt aus:

Abb. 4. Einbinden der Klasse der Handelsstrategie und der Datei mit weiteren Funktionen 

Abb. 4. Einbinden der Klasse der Handelsstrategie und der Datei mit weiteren Funktionen

Versuchen wir jetzt die Parameter zu optimieren und schauen wir uns an, wie alles auf dem Terminal aussieht. Testeinstellungen: EURUSD H1, Zeitspanne 2017.01.01 – 2018.01.01.

Abb. 5. Darstellung der Ergebnisse des "Moving Average EA" aus der Standardauslieferung

Abb. 5. Darstellung der Ergebnisse des "Moving Average EA" aus der Standardauslieferung

Wie wir sehen können, erwies es sich als sehr informativ. Fast alle Ergebnisse für diesen Handelsalgorithmus sind negativ (95,23%). Wenn wir die Zeitspanne erhöhen, wird es noch schlimmer. Bei der Entwicklung eines Handelssystems sollten wir jedoch darauf achten, dass die meisten Ergebnisse positiv sind. Ansonsten führt der Algorithmus zu Verlusten und sollte nicht verwendet werden. Es ist notwendig, die Parameter auf mehr Daten zu optimieren und sicherzustellen, dass es so viele Positionen wie möglich gibt.  

Versuchen wir einen anderen Handelsalgorithmus aus der Standardauslieferung zu testen - MACD Sample.mq5. Er ist bereits als Klasse implementiert. Nach kleinen Verbesserungen können wir ihn einfach in unsere Anwendung einbinden, so wie die vorherige. Wir sollten es mit dem gleichen Symbol und Zeitrahmen testen. Obwohl wir die Zeitspanne für weitere Positionen in den Tests erhöhen sollten (2010.01.01 - 2018.01.01). Unten ist das Optimierungsergebnis eines Trading EA:

 Abb. 6. Anzeige der Ergebnisse des Beispiel-MACD aus der Standardauslieferung

Abb. 6. Darstellung der Optimierungsergebnisse des Beispiel-MACD

Hier sehen wir ein ganz anderes Ergebnis: 90,89% sind positiv.

Die Optimierung der Parameter kann je nach Datenmenge sehr lange dauern. Man muss nicht während des gesamten Prozesses vor Ihrem PC sitzen. Nach der Optimierung können Sie die Darstellung der Ergebnisse im beschleunigten Modus wiederholen, einfach indem Sie auf Replay frames drücken. Beginnen wir mit der Wiedergabe der Rahmen mit der Darstellungsgrenze von 25 Reihen. Hier ist, wie es aussieht:

Abb. 7. Ergebnisse des Beispiels EAs MACD nach der Optimierung

Abb. 7. Ergebnisse des Beispiels EAs MACD nach der Optimierung


Schlussfolgerung

In diesem Artikel haben wir die moderne Version des Programms zum Empfangen und Analysieren von Optimierungsergebnisse vorgestellt. Die Daten werden in einem grafischen Interface dargestellt, die auf Basis der Bibliothek EasyAndFast entwickelt wurde. 

Ein Nachteil dieser Lösung ist, dass es nach Abschluss der Optimierung im Verarbeitungsmodus der Rahmen nicht möglich ist, den Timer zu starten. Dies hat einige Einschränkungen bei der Arbeit mit dem gleichen grafischen Interface zur Folge. Das zweite Problem ist, dass die Deinitialisierung der Funktion OnDeinit() beim Entfernen des EA aus dem Diagramm nicht gestartet wird. Dies beeinträchtigt die korrekte Ereignisbehandlung. Möglicherweise werden diese Probleme in einem der zukünftigen Versionen des MetaTrader 5 gelöst.