
Erstellen von selbstoptimierenden Expert Advisor in MQL5 (Teil 8): Analyse mehrerer Strategien
Es gibt viele Herausforderungen, mit denen wir als algorithmische Händler konfrontiert sind, die wir in dieser Serie bereits erörtert haben. Wir haben zum Beispiel festgestellt, dass es unseren statistischen Modellen leichter fällt, die zukünftigen Werte der technischen Indikatoren vorherzusagen als die zukünftigen Kursniveaus.
Wir haben auch die Vorteile eines Handelssystems untersucht, das die Beziehung zwischen der Strategie, die es verfolgt, und dem Markt, auf dem es diese Strategie anwendet, modelliert.
Unsere Modelle schnitten durchweg besser ab, wenn wir die klassische Aufgabe der direkten Preisvorhersage durch diese alternativen Aufgaben ersetzten. Direkte Preisvorhersagen sind schwierig, aber wenn wir den Rahmen des Problems ändern, können wir Modelle, die sich auf die klassische Aufgabe beschränken, übertreffen, ohne auf die gleichen statistischen Werkzeuge zurückgreifen zu müssen.
Heute werden wir eine neue potenzielle Strategie untersuchen, die auf unseren früheren Erkenntnissen aufbaut. Was wäre, wenn wir eine Anwendung erstellen, die drei verschiedene Handelsstrategien kennt? Kann diese Anwendung lernen, jeweils eine Strategie zu wählen und regelmäßig zur profitabelsten zu wechseln, anstatt alle drei gleichzeitig zu verfolgen? Wenn die Anwendung regelmäßig die Strategie wechseln kann, kann sie dann gewinnbringend die beste der drei ihr bekannten Strategien auswählen?
Eine solche Anwendung könnte nützlicher sein als ein fester Handelsalgorithmus, der alle drei Strategien oder eine Kombination aus ihnen verfolgt.
Um den Wert unseres statistischen Modells zu messen, benötigen wir zunächst eine Basisleistung, die unser Modell übertreffen sollte.
Wir werden drei unabhängige Handelsstrategien kombinieren: eine Moving Average Crossover Continuation Strategy, eine Relative Strength Index Momentum Strategy und eine Williams Percent Range Trend Breakout Strategy. Sie werden im Detail erläutert.
In diesem Artikel werden einige leistungsstarke Tools im MetaTrader 5 Terminal vorgestellt, wobei der Schwerpunkt auf dem Walk Forward Testing liegt. Vorwärtstests unterscheiden sich von Backtests, und wir werden diese Unterschiede später erklären.
Der Vorwärtstest liefert uns mehr Erkenntnisse als einfache Backtests, insbesondere in Kombination mit einem Optimierer, der neue Strategieparameter zum Testen generiert. Auf diese Weise können wir auf zuverlässige Weise profitable Einstellungen für unsere Handelsstrategie finden. MetaTrader 5 beinhaltet diese fortschrittliche Funktionalität mit seinen schneller und langsamer genetischer Optimierung.
Durch die Kombination dieser leistungsstarken Strategietest-Tools mit den zuverlässigen objektorientierten Entwurfsprinzipien, die wir in dieser Reihe behandeln, werden wir einen starken Konkurrenten entwerfen, testen und validieren, den unsere statistischen Modelle schlagen müssen.
Erste Schritte in MQL5
Diese Diskussion befasst sich mit dem Problem, wie man verschiedene Strategien am besten zu einer funktionierenden Strategie kombiniert. Hart kodierte Lösungen sind selten, insbesondere wenn mehrere Strategien gleichzeitig verwendet werden.
Die Kombination von Strategien ist spannend, weil sie Kreativität erfordert. Das bedeutet aber auch, dass wir unerwartete Nebenwirkungen minimieren müssen.
Händler wenden oft verschiedene Strategien gleichzeitig an. So kann beispielsweise eine Strategie Positionen eröffnen, während eine andere entscheidet, wann sie geschlossen wird. Jede Strategie konzentriert sich auf einen Teil des Problems. Wir wollen diesen menschlichen Ansatz nachahmen und gleichzeitig zeigen, wie man den MetaTrader 5 Strategietester verwendet, um gute Strategieeinstellungen zu finden.
Um Strategien zuverlässig zu kombinieren, werden wir jede Strategie in einer Klasse kapseln. Jede Klasse muss getestet werden, um zu beweisen, dass sie funktioniert. Eine einzige übergeordnete Klasse, „Parent“ genannt, wird die Basis für alle unsere Strategievarianten sein. Diese Klasse umfasst allgemeine Funktionen wie die Aktualisierung von Parametern und die Prüfung auf Kauf- oder Verkaufssignale.
Jede Klasse, die von der übergeordneten Klasse erbt, legt neu fest, was als Kauf- oder Verkaufssignal gilt. Dies geschieht, indem gemeinsame Methoden virtuell gemacht werden, sodass jede Strategie sie sicher außer Kraft setzen kann.
Wir haben nun genug gelernt, um mit dem Aufbau der ersten Klasse zu beginnen: der übergeordneten Strategieklasse.
In MQL5 beginnt jede Klasse mit dem Schlüsselwort class, gefolgt von dem Klassennamen. Es ist üblich, dass die Datei denselben Namen wie die Klasse trägt.Einige Klassenmitglieder werden als virtuell gekennzeichnet, um dem Compiler mitzuteilen, dass diese Funktionen von Unterklassen außer Kraft gesetzt werden können. So kann jede Strategie selbst festlegen, wie sie diese Methoden sicher handhabt.
//+------------------------------------------------------------------+ //| Strategy.mqh | //| Gamuchirai Ndawana | //| https://www.mql5.com/en/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" class Strategy { private: int buffer_size; bool buy; bool sell; public: //--- Class constructors and destructors Strategy(void); ~Strategy(void); //--- Check if we have any valid trading signals from our strategy virtual bool BuySignal(void); virtual bool SellSignal(void); //--- Update the technical indicators in our strategy virtual bool Update(void); //--- Get the size of the technical indicator buffers int GetIndicatorBufferSize(void); };
Unser Standardkonstruktor legt alle Standardwerte fest, die für alle Instanzen einer Strategie gelten.
//+------------------------------------------------------------------+ //| The only way to create an object of the class | //+------------------------------------------------------------------+ Strategy::Strategy(void) { //--- Upon initialization, both flags should be false buy = false; sell = false; buffer_size = 10; }
Wir benötigen mehrere Hilfsmethoden, die gemeinhin als Getter (Werte abrufen) und Setter (Werte setzen) bezeichnet werden. Zu Beginn werden wir eine Methode definieren, die die aktuelle Puffergröße zurückgibt, die wir für die in unserer Strategie verwendeten Indikatoren ausgewählt haben.
//+------------------------------------------------------------------+ //| The size of our indicator buffer | //+------------------------------------------------------------------+ int Strategy::GetIndicatorBufferSize(void) { int res = buffer_size; return(res); }
Außerdem muss jede Strategie über 2 Methoden verfügen, die uns jeweils informieren, ob Kauf- oder Verkaufssignale vorliegen. Jede Klasse, die von der Basisklasse abgeleitet wird, sollte die Regeln implementieren, die ihre Einträge definieren. Andernfalls wird die übergeordnete Klasse immer false zurückgeben und den Praktiker anweisen, diese Methode in der untergeordneten Klasse zu überschreiben.
//+------------------------------------------------------------------+ //| Check if our strategy is giving us any buy signals | //+------------------------------------------------------------------+ bool Strategy::BuySignal(void) { //--- The user is intended to overwrite the function in the child class //--- Otherwise, failing to do so will always return false as a safety feature Print("[WARNING] This function has only been implemented in the parent: ",__FUNCSIG__,"\nKindly make the necessary corrections to the child class"); return(false); } //+------------------------------------------------------------------+ //| Check if our strategy is giving us any sell signals | //+------------------------------------------------------------------+ bool Strategy::SellSignal(void) { //--- The user is intended to overwrite the function in the child class //--- Otherwise, failing to do so will always return false as a safety feature Print("[WARNING] This function has only been implemented in the parent: ",__FUNCSIG__,"\nKindly make the necessary corrections to the child class"); return(false); }
Die Aktualisierung des Strategieobjekts hat zur Folge, dass alle Strategieparameter, die für den Handel verwendet werden, aktualisiert werden.
//+------------------------------------------------------------------+ //| Update our strategy parameters | //+------------------------------------------------------------------+ bool Strategy::Update(void) { //--- The user is intended to overwrite the function in the child class Print("[WARNING] This function has only been implemented in the parent: ",__FUNCSIG__,"\nKindly make the necessary corrections to the child class"); return(false); }
Im Moment ist der Destruktor unserer Klasse leer.
//+------------------------------------------------------------------+ //| The class destructor is currently empty | //+------------------------------------------------------------------+ Strategy::~Strategy(void) { } //+------------------------------------------------------------------+
Wir beginnen mit der Definition des Körpers unserer Klasse. Die Klasse trägt den Namen „OpenCloseMACrossover“. Es handelt sich um eine Strategie, die sich auf 2 gleitende Durchschnittsindikatoren mit identischen Perioden stützt, die jeweils auf den Eröffnungs- und den Schlusskurs angewendet werden. Beachten Sie, dass die Methoden, die in der übergeordneten Klasse virtuell waren, auch in der untergeordneten Klasse virtuell sind.
Verkaufssignale werden generiert, wenn der offene gleitende Durchschnitt über dem Schlusskurs liegt. Das Gegenteil wird als Kaufsignal registriert. Wenn der durchschnittliche Schlusskurs größer ist als der durchschnittliche Eröffnungskurs, kann die Kursentwicklung als steigend angesehen werden, und ein starker Trend in diese Richtung kann anhalten.
//+------------------------------------------------------------------+ //| OpenCloseMACrossover.mqh | //| Gamuchirai Ndawana | //| https://www.mql5.com/en/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" #include<VolatilityDoctor\Strategies\Parent\Strategy.mqh> #include<VolatilityDoctor\Indicators\MA.mqh> class OpenCloseMACrossover : public Strategy { private: //--- Create 2 moving average instances MA *ma_array[2]; public: //---- Class constructors and destructor OpenCloseMACrossover(string symbol,ENUM_TIMEFRAMES time_frame,int period,int shift,ENUM_MA_METHOD ma_mode); ~OpenCloseMACrossover(); //--- Class methods virtual bool Update(void); virtual bool BuySignal(void); virtual bool SellSignal(void); };
Wir haben bereits die Regeln der Handelsstrategie besprochen, sodass die Implementierung der Methoden, die diese Bedingungen überprüfen, eine einfache Aufgabe für uns ist.
//+------------------------------------------------------------------+ //| Check For a Buy Signal | //+------------------------------------------------------------------+ bool OpenCloseMACrossover::BuySignal(void) { //--- Our buy signal is generated if the close moving average is above the open. return(ma_array[0].GetCurrentReading()>ma_array[1].GetCurrentReading()); } //+------------------------------------------------------------------+ //| Check For a Sell Signal | //+------------------------------------------------------------------+ bool OpenCloseMACrossover::SellSignal(void) { //--- Our sell signal is generated if the open moving average is above the close. return(ma_array[0].GetCurrentReading()<ma_array[1].GetCurrentReading()); }
Unsere Aktualisierungsmethode ruft die Indikator-Aktualisierungsfunktionen auf, die wir für unsere SingleBufferIndicator-Klasse entwickelt haben. Bei dieser Methode muss die Puffergröße als Parameter übergeben werden. Wir haben in unserer übergeordneten Klasse eine Methode erstellt, die uns die Puffergröße zurückgibt. Wir verweisen auf die übergeordnete Klasse, indem wir in unserem Aufruf „Strategy::GetIndicatorBufferSize()“ die Syntax des Doppelpunkts „::“ verwenden. Die Aktualisierungsmethode prüft abschließend, ob die aktualisierten Werte nicht 0 sind, bevor sie die Kontrolle an den Kontext zurückgibt, aus dem sie aufgerufen wurde.
//+------------------------------------------------------------------+ //| Our update method | //+------------------------------------------------------------------+ bool OpenCloseMACrossover::Update(void) { //--- Copy indicator readings //--- We will always get the buffer size from the parent class ma_array[0].SetIndicatorValues(Strategy::GetIndicatorBufferSize(),true); ma_array[1].SetIndicatorValues(Strategy::GetIndicatorBufferSize(),true); //--- Make sure neither of the indicator values equal 0 if((ma_array[0].GetCurrentReading() * ma_array[1].GetCurrentReading()) != 0) return(true); //--- If one/both indicator values equal 0, something went wrong. return(false); }
Der Klassenkonstruktor erstellt dynamisch 2 neue Instanzen unserer gleitenden Durchschnittsindikatorobjekte und speichert ihre Zeiger in einem Array des gleichen Zeigertyps, d.h. des von uns definierten MA-Typs.
//+------------------------------------------------------------------+ //| Our class constructor | //+------------------------------------------------------------------+ OpenCloseMACrossover::OpenCloseMACrossover(string symbol,ENUM_TIMEFRAMES time_frame,int period,int shift,ENUM_MA_METHOD ma_mode) { //--- Create two instances of our moving average indiator objects ma_array[0] = new MA(symbol,time_frame,period,shift,ma_mode,PRICE_CLOSE); ma_array[1] = new MA(symbol,time_frame,period,shift,ma_mode,PRICE_OPEN); //--- Give feedback Print("Strategy class loaded correctly"); }
Der Destruktor der Klasse löscht die dynamischen Objekte, die wir erstellt haben, und hilft uns, den Speicher, den wir verbrauchen, zu verwalten.
//+------------------------------------------------------------------+ //| Our class destructor | //+------------------------------------------------------------------+ OpenCloseMACrossover::~OpenCloseMACrossover() { //--- Delete the custom objects we made delete ma_array[0]; delete ma_array[1]; //--- Give feedback Print("Strategy deinitialized correctly. Goodbye"); } //+------------------------------------------------------------------+
Wir werden nun unsere erste Strategieklasse testen. Denken Sie daran, dass unsere endgültige Anwendung drei Klassen und drei verschiedene Strategien umfassen wird. Deshalb müssen wir als gute Entwickler jede Klasse einzeln gegen eine fest programmierte Version einer identischen Strategie testen. Der Test ist bestanden, wenn beide Strategien bei einem Backtest über denselben Zeitraum die gleichen Ergebnisse liefern. Das kann uns in Zukunft stundenlanges Suchen nach Fehlern ersparen.
Zunächst werden wir Systemkonstanten definieren, die wir in beiden Tests beibehalten werden. Wenn beide Strategien gleichwertig sind, sollten sie zu den gleichen Zeiten ausgelöst werden, die gleiche Anzahl von Positionen eröffnen und im gleichen Kauf-/Verkaufsverhältnis stehen. Die Wiederholung dieser Systemkonstanten ist ein bewusster Teil unseres Tests, da diese Konstanten die Strategieparameter steuern.
//+------------------------------------------------------------------+ //| MSA Test 1.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/en/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| Define system constants | //+------------------------------------------------------------------+ #define MA_TYPE MODE_EMA #define MA_PERIOD 10 #define MA_TIME_FRAME PERIOD_D1 #define MA_SHIFT 0 #define HOLDING_PERIOD 5
Als Nächstes laden wir unsere Abhängigkeiten.
//+------------------------------------------------------------------+ //| Dependencies | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> #include <VolatilityDoctor\Trade\TradeInfo.mqh> #include <VolatilityDoctor\Time\Time.mqh>
Dann brauchen wir ein paar globale Variablen, um unsere technischen Indikatoren zu steuern und zu zählen, wie lange unsere Position offen ist.
//+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ //--- Custom Types CTrade Trade; TradeInfo *TradeInformation; Time *TradeTime; //--- System Types double ma_open[],ma_close[]; int ma_open_handler,ma_close_handler; intn position_timer;
Wenn unser System initialisiert ist, werden wir unsere technischen Indikatoren laden und überprüfen, ob sie einwandfrei sind.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Our technical indicators ma_close_handler = iMA(Symbol(),MA_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE,PRICE_CLOSE); ma_open_handler = iMA(Symbol(),MA_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE,PRICE_OPEN); //--- Create dynamic instances of our custom types TradeTime = new Time(Symbol(),MA_TIME_FRAME); TradeInformation = new TradeInfo(Symbol(),MA_TIME_FRAME); //--- Safety checks if(ma_close_handler == INVALID_HANDLE) return(false); if(ma_open_handler == INVALID_HANDLE) return(false); //--- Everything was fine return(INIT_SUCCEEDED); } //--- End of OnInit Scope
Wenn unser System nicht mehr verwendet wird, geben wir die technischen Indikatoren frei, die wir geladen haben, und löschen die dynamischen Objekte, die wir bei der Einrichtung erstellt haben.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Delete the indicators and dynamic objects IndicatorRelease(ma_close_handler); IndicatorRelease(ma_open_handler); delete TradeTime; delete TradeInformation; } //--- End of Deinit Scope
Wenn unser Terminal neue Kursniveaus erhält, prüfen wir zunächst, ob sich eine neue Kerze gebildet hat. Wenn dies der Fall ist, aktualisieren wir immer unsere technischen Indikatoren und prüfen dann, ob wir irgendwelche Positionen offen haben, sodass wir entweder nach einem Handelssignal suchen, wenn keine offen sind, oder bis zur Fälligkeit warten, bevor wir unsere Position schließen.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Check if a new daily candle has formed if(TradeTime.NewCandle()) { //--- Update our technical indicators Update(); //--- If we have no open positions if(PositionsTotal() == 0) { //--- Reset the position timer position_timer = 0; //--- Check for a trading signal CheckSignal(); } //--- Otherwise else { //--- The position has reached maturity if(position_timer == HOLDING_PERIOD) Trade.PositionClose(Symbol()); //--- Otherwise keep holding else position_timer++; } } } //--- End of OnTick Scope
Unsere Aktualisierungsfunktion schreibt die aktuellen Indikatorwerte in den Indikatorpuffer und in die dafür angelegten Arrays.
//+------------------------------------------------------------------+ //| Update our technical indicators | //+------------------------------------------------------------------+ void Update(void) { //--- Call the CopyBuffer method to get updated indicator values CopyBuffer(ma_close_handler,0,0,1,ma_close); CopyBuffer(ma_open_handler,0,0,1,ma_open); } //--- End of Update Scope
Die Prüfsignalfunktion sucht nach den Handelsbedingungen, die wir zuvor definiert haben. Der gleitende Durchschnitt beim Schließen sollte über dem gleitenden Durchschnitt beim Öffnen liegen, damit wir die Kursbewegung als steigend einstufen können.
//+------------------------------------------------------------------+ //| Check for a trading signal using our cross-over strategy | //+------------------------------------------------------------------+ void CheckSignal(void) { //--- Long positions when the close moving average is above the open if(ma_close[0] > ma_open[0]) { Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,""); return; } //--- Otherwise short else if(ma_close[0] < ma_open[0]) { Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,""); return; } } //--- End of CheckSignal Scope
Und schließlich sollten Sie die von Ihnen erstellten Systemvariablen am Ende Ihres Programms immer wieder zurücksetzen.
//+------------------------------------------------------------------+ //| Undefine system constants | //+------------------------------------------------------------------+ #undef MA_PERIOD #undef MA_SHIFT #undef MA_TIME_FRAME #undef MA_TYPE #undef HOLDING_PERIOD //+------------------------------------------------------------------+
Lassen Sie uns nun ein Basisniveau der Leistung für unsere Klasse festlegen.
Abb. 1: Unsere anfänglichen Testeinstellungen für unsere Basisstufe
Der nächste Schritt besteht darin, den Start- und Endpunkt unseres Backtests festzulegen. Wir werden den täglichen Zeitrahmen auswählen, wenn Sie mitverfolgen möchten.
Abb. 2: Die Testdaten, die wir für die Basisstufe verwenden werden
Die Kapitalkurve, die von der fest kodierten Version der Strategie erzeugt wird, muss aus der von uns implementierten Klasse wiederhergestellt werden, vorausgesetzt, wir stellen beide Strategien so ein, dass sie mit denselben Parametern laufen. Lassen Sie uns nun überprüfen, ob wir die Klasse tatsächlich fehlerfrei implementiert haben.
Abb. 3: Veranschaulichung der durch unsere Basisstrategie erstellten Kapitalkurve
Die genauen statistischen Details der beiden Tests werden nicht perfekt aufeinander abgestimmt sein. Erinnern Sie sich, dass unser Backtest zufällige Latenzzeiten und systematisches Rauschen simuliert. Daher streben wir an, dass die beiden Ergebnisse sehr nahe beieinander liegen, aber nicht identisch sind.
Abb. 4: Detaillierte Ergebnisse des Backtests, den wir mit unserer fest kodierten Version der Strategie des Kreuzens von gleitendem Durchschnitt durchgeführt haben
Die von uns definierten Systemkonstanten werden in dieser Version unseres Klassentests aus Konsistenzgründen unverändert verwendet.
//+------------------------------------------------------------------+ //| MSA Test 1.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/en/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| Define system constants | //+------------------------------------------------------------------+ #define MA_TYPE MODE_EMA #define MA_PERIOD 10 #define MA_TIME_FRAME PERIOD_D1 #define MA_SHIFT 0 #define HOLDING_PERIOD 5
Als Nächstes laden wir unsere Abhängigkeiten.
//+------------------------------------------------------------------+ //| Dependencies | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> #include <VolatilityDoctor\Time\Time.mqh> #include <VolatilityDoctor\Trade\TradeInfo.mqh> #include <VolatilityDoctor\Strategies\OpenCloseMACrossover.mqh>
Wir müssen eine neue globale Variable für unsere Strategieinstanz erstellen.
//+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ //--- Custom Types CTrade Trade; TradeInfo *TradeInformation; Time *TradeTime; OpenCloseMACrossover *MACross; //--- System Types int position_timer;
Der größte Teil der Anwendung bleibt unverändert. Wir isolieren die Auswirkungen der Signale, die von unserer Klasse erzeugt werden. Daher sollte der größte Teil dieses Codes dem Leser aus dem ersten von uns durchgeführten Test bekannt vorkommen.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Create dynamic instances of our custom types TradeTime = new Time(Symbol(),MA_TIME_FRAME); TradeInformation = new TradeInfo(Symbol(),MA_TIME_FRAME); MACross = new OpenCloseMACrossover(Symbol(),MA_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE); //--- Everything was fine return(INIT_SUCCEEDED); } //--- End of OnInit Scope //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Delete the dynamic objects delete TradeTime; delete TradeInformation; delete MACross; } //--- End of Deinit Scope //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Check if a new daily candle has formed if(TradeTime.NewCandle()) { //--- Update strategy Update(); //--- If we have no open positions if(PositionsTotal() == 0) { //--- Reset the position timer position_timer = 0; //--- Check for a trading signal CheckSignal(); } //--- Otherwise else { //--- The position has reached maturity if(position_timer == HOLDING_PERIOD) Trade.PositionClose(Symbol()); //--- Otherwise keep holding else position_timer++; } } } //--- End of OnTick Scope
Die Methoden, die wir definiert haben, um unsere Strategieparameter zu aktualisieren und Handelssignale zu erkennen, haben sich nur insofern geändert, als sie nun die von uns erstellte Klasse aufrufen und die Ergebnisse nicht mehr von Grund auf neu implementieren.
//+------------------------------------------------------------------+ //| Update our technical indicators | //+------------------------------------------------------------------+ void Update(void) { //--- Update the strategy MACross.Update(); } //--- End of Update Scope //+------------------------------------------------------------------+ //| Check for a trading signal using our cross-over strategy | //+------------------------------------------------------------------+ void CheckSignal(void) { //--- Long positions when the close moving average is above the open if(MACross.BuySignal()) { Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,""); return; } //--- Otherwise short else if(MACross.SellSignal()) { Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,""); return; } } //--- End of CheckSignal Scope //+------------------------------------------------------------------+ //| Undefine system constants | //+------------------------------------------------------------------+ #undef MA_PERIOD #undef MA_SHIFT #undef MA_TIME_FRAME #undef MA_TYPE #undef HOLDING_PERIOD //+------------------------------------------------------------------+
Wir sind nun bereit, mit dem Testen unserer Klasse mit der Strategie des MA-Kreuzens in MQL5 zu beginnen. Zunächst laden wir den Expert Advisor, der die Klasse ausführt, und richten die gleichen Backtest-Daten ein, die wir für den ersten Test verwendet haben.
Abb. 5: Die anfänglichen Daten, die wir bei der Bewertung unserer Strategie verwenden werden, werden durch unseren zukünftigen Vorwärtstest beeinflusst
Vergewissern Sie sich, dass die von Ihnen gewählten Einstellungen mit den ersten Einstellungen übereinstimmen, die wir verwendet haben.
Abb. 6: Wählen Sie „Echte Ticks“ und „Zufällige Verzögerung“ für robuste Backtests
Die detaillierten Testergebnisse der Klassen- und Hardcodestrategien sind fast identisch, beide Strategien platzierten 127 Trades, mit dem gleichen Kauf-Verkaufs-Verhältnis und erzielten Sharpe Ratios, die nahe beieinander liegen.
Abb. 7: Die detaillierten Statistiken, die von unserer Strategieklasse erstellt werden, stimmen mit den Ergebnissen überein, die wir mit der fest kodierten Handelsstrategie erhalten haben
Die von der Klasse erstellte Kapitalkurve ähnelt sehr stark Abb. 3. Die beiden Strategien stimmen also wie erwartet überein.
Abb. 8: Die von der Klasse erzeugte Kapitalkurve entspricht der fest kodierten Strategie
Jetzt können wir mit der Suche nach optimalen Strategieparametern beginnen, da wir überprüft haben, dass die Klasse korrekt implementiert wurde.
Zunächst müssen wir die meisten Systemkonstanten in Nutzereingaben umwandeln. Dies ermöglicht unserem genetischen Optimierer, die Strategie für uns zu optimieren. Wir haben also nur eine einzige Systemdefinition.
//+------------------------------------------------------------------+ //| MSA Test 1.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/en/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| System constants | //+------------------------------------------------------------------+ #define MA_SHIFT 0 //+------------------------------------------------------------------+ //| User Inputs | //+------------------------------------------------------------------+ input group "Strategy Parameters" input int MA_PERIOD = 10;//Moving Average Period input int HOLDING_PERIOD = 5;//Position Holding Period input ENUM_TIMEFRAMES MA_TIME_FRAME = PERIOD_D1;//Moving Average Time Frame input ENUM_MA_METHOD MA_TYPE = MODE_EMA;//Moving Average Type
Der Rest unseres Systems bleibt größtenteils unverändert, sodass wir nun den Unterschied zwischen einem Backtest und einem Forwardtest in MetaTrader 5 diskutieren wollen.
Bei einem Backtest wird eine Handelsstrategie einfach anhand historischer Daten getestet. Wir können unsere Daten über einfache historische Tests hinaus weiter nutzen. Durch die Aufteilung der Daten können wir einen Teil der Daten für die Suche nach Strategieparametern verwenden und dann die gefundenen Parameter mit dem verbleibenden Teil der Daten validieren.
Das ist die Tugend des Vorwärtstests. Wir sind nicht nur auf der Suche nach guten Einstellungen, sondern versuchen auch zu erfahren, wie stabil diese Einstellungen sind.
Setzen Sie daher den Parameter „Forward“ auf „1/2“, um 50 % Ihrer Daten für das Training und den Rest für die Tests zu verwenden.
Abb. 9: Wir setzen das Feld „Forward“ auf 1/2, um die Hälfte der Daten zum Trainieren und die andere Hälfte zum Testen zu verwenden.
Es gibt verschiedene Optimierungsstrategien, die uns in unserem MetaTrader 5 Terminal zur Verfügung stehen. Wir entscheiden uns für den „schnellen genetischen Algorithmus“, weil er unser System nicht überfordert, aber dennoch zuverlässige Ergebnisse liefert.
Abb. 10: Wir brauchen einen Optimierer, der nach jedem Test neue Strategieparameter generiert
Sobald der Test beginnt, wird ein Streudiagramm der Ergebnisse angezeigt, ähnlich wie in Abb. 11 unten. Anhand des Charts können wir sehen, wie gut wir bei jeder Trainingsiteration abgeschnitten haben. Der Optimierer sieht nur die Ergebnisse, die in der ersten Hälfte der Daten erzielt wurden, und nutzt diese, um bessere Parameter für die nächste Iteration zu lernen.
Abb. 11: Wir setzen die schnelle genetische Optimierung ein, um die Sharpe Ratio unserer Strategie zu verbessern. Jeder Punkt stellt die Ergebnisse der Testiterationen dar
Wir können mit der rechten Maustaste auf dieses Streudiagramm der Ergebnisse klicken und es auf verschiedene Weise bearbeiten. Wir können die gezeichnete Achse ändern oder das Chart sogar zu einer 3D-Darstellung unserer Ergebnisse machen.
Abb. 12: Mit dem Kontextmenü können wir die Achsen des Charts ändern und verschiedene Beziehungen untersuchen
Die Visualisierung der Daten in alternativen Formen kann uns dabei helfen, Phänomene zu beobachten, die zwischen unserer Strategie und dem von uns gewählten Markt auftreten. So zeigt sich zum Beispiel, dass unsere Strategie umso profitabler wird, je höher der Zeitrahmen ist, den wir wählen.
Abb. 13: Wir können beobachten, dass höhere Zeitrahmen uns helfen, bessere Sharpe Ratios zu erhalten, indem wir ein 3D-Balkendiagramm erstellen.
Der Strategietester liefert Ihnen auch detaillierte Ergebnisse für jede getestete Kombination von Eingaben. Beachten Sie, dass am unteren Rand der Tabelle eine Leiste vorhanden ist, die „Backtest“ und „Vorwärts“ trennt.
Abb. 14: MetaTrader 5 bietet uns auch eine detaillierte Analyse aller Strategieparameter, die er ausprobiert hat
Wenn Sie mit der rechten Maustaste auf diese Tabelle klicken, öffnet sich ein Kontextmenü, über das Sie viele nützliche Aufgaben ausführen können, z. B. festlegen, welche Spalten in die Tabelle aufgenommen werden sollen, oder sogar die Weiterleitungsergebnisse in eine Datei exportieren.
Abb. 15: Das Laden des Kontextmenüs auf der Ergebnistabelle zeigt uns, dass wir unsere Ergebnisse in das XML-Format für weitere Studien exportieren können
Wir können die Ergebnisse des Backtests und des Vorwärtstests getrennt betrachten. Die Ergebnisse des Backtests sind unten in Abb. 16 dargestellt. Erinnern Sie sich daran, dass wir mehr an den Vorwärts-Ergebnissen interessiert sind und daran, wie weit sie vom Backtest abgewichen sind.
Abb. 16: Detaillierte statistische Ergebnisse des Backtests, die unser genetischer Optimierer erzielte
Die Ergebnisse des Vorwärtstests ähneln stark denen des Backtests. Dies ist ein guter Indikator für potenzielle Stabilität. Andernfalls, wenn Ihre besten Ergebnisse in der Vorwärtsphase nicht mit den Ergebnissen der Backtests übereinstimmen, zeigt die Strategie instabile Eigenschaften.
Abb. 17: Dies sind die Ergebnisse, an denen wir besonders interessiert sind, nämlich die Vorwärtsergebnisse
Wir erhalten auch die Kapitalkurve, die sich aus beiden Tests ergibt. Die lange vertikale Linie in der Mitte markiert die Trennung zwischen dem Rückwärts- und dem Vorwärtstest.
Abb. 18: Beachten Sie, dass sowohl die im Training als auch die im Vorwärtstest ermittelte Kapitalkurve positive Trends aufweisen
Und schließlich sind dies die optimalen Strategieeinstellungen, die wir mit Hilfe unseres genetischen Optimierers gefunden haben.
Abb. 19: Die besten Einstellungen, die durch unseren genetischen Algorithmus ermittelt wurden
Schlussfolgerung
Dieser Artikel hat den Wert des MetaTrader 5 Strategietesters aufgezeigt. Leser, die nicht vorhaben, Klassen zu entwerfen, können dennoch praktische Anwendungsfälle für die Integration von mehr objektorientierten Programmierprinzipien (OOP), die in MQL5 eingebettet sind, in ihren Entwicklungsprozess erhalten. Dieser Artikel ermutigt die Leser auch dazu, gute Entwicklungspraktiken für die Erstellung und das Testen zuverlässiger Klassen anzuwenden.
Schließlich können die Leser durch die Verwendung der von uns hervorgehobenen Entwurfsprinzipien von OOP ihre Strategien zuverlässig aufbauen und sie leicht auf optimale Eingaben über verschiedene Symbole und Zeitrahmen hinweg testen. Nehmen Sie an unserer nächsten Diskussion teil, in der wir unsere RSI- und Gleitender-Durchschnitt-Strategien zusammenführen werden.
Dateiname | Beschreibung der Datei |
---|---|
MSA Test 1 Baseline.mq5 | Die hartkodierte Implementierung unserer Kreuzungs-Strategie, die wir als Testergebnis verwendet haben, sollte unsere Klasse nachbilden. |
MSA Test 1 Class.mq5 | Die Datei, die unsere Strategieklasse des gleitenden Durchschnitts testet. |
MSA Test 1.mq5 | Wir haben diesen Expert Advisor verwendet, um mit dem MetaTrader 5 Strategietester nach guten Strategieparametern zu suchen. |
OpenCloseMACrossover.mqh | Die Klasse, die unsere Strategie des gleitenden Durchschnitts umsetzt. |
Strategy.mqh | Die Basisklasse für alle unsere Strategien. |
MSA Test 1.ex5 | Eine kompilierte Version unseres Expert Advisors. |
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/18402
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.





- 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.