
Die Darstellung der Optimierung einer Handelsstrategie im MetaTrader 5
Inhalt
- Einführung
- Entwickeln des grafischen Interfaces
- Entwicklung der Klasse des Rahmens
- Arbeiten mit den optimierten Daten in der Anwendungsklasse
- Ergebnisanzeige
- Schlussfolgerung
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
Ä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:
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
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
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
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. 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
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.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/4395





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.