
Selbstoptimierende Expert Advisors in MQL5 (Teil 8): Analyse mehrerer Strategien (2)
In dieser Serie erkunden wir kreative Wege, um verschiedene Handelsstrategien zu einem einzigen, zusammenhängenden Ensemble zu kombinieren. Ziel ist es, die Grenzen der einzelnen Strategien zu überwinden, indem sie zu etwas Leistungsfähigerem zusammengeführt werden.
In unserer letzten Diskussion haben wir eine Superklasse erstellt, die als Grundlage für alle unsere Handelsstrategien dient. Mit dieser Superklasse konnten wir unsere erste Strategie, einen gleitenden Durchschnitts-Crossover, in MQL5 implementieren. Anschließend verglichen wir unsere flexible Strategieklasse mit einer fest programmierten Version derselben Strategie, um zu überprüfen, ob die Leistung übereinstimmt.
Mit dem MetaTrader 5-Strategietester konnten wir starke Parametereinstellungen für diese Strategie finden. Es kann schwierig sein, selbst gute Parameter zu finden. Deshalb ist der genetische Optimierer im MetaTrader 5 ein so wertvolles Werkzeug. Es hilft, den Prozess zu automatisieren, was Zeit und Mühe spart.
Wir haben auch die Technik des Vorwärtstests eingesetzt, um stabile Parametersätze aus unseren Ergebnissen herauszufiltern. Dies bestätigte, dass unsere Umsetzung genau und effizient war.
Heute gehen wir noch einen Schritt weiter. Wir werden eine zweite Strategie erstellen, die auf dem Relative Strength Index (RSI) basiert, und diese dann mit unserer Crossover-Strategie mit gleitendem Durchschnitt zusammenführen. Durch ihre Kombination wollen wir eine robustere und potenziell rentablere Gesamtstrategie schaffen. Wir werden auch den MetaTrader 5 Strategie-Tester verwenden, um diese neue kombinierte Strategie zu optimieren. Doch bevor wir uns damit befassen, ist es wichtig, über ein Schlüsselkonzept zu sprechen: Parameterminimierung.
Wenn wir weitere Komponenten zu unserer Strategie hinzufügen, kann die Zahl der Parameter schnell wachsen. Eine Strategie mit zu vielen veränderbaren Bestandteilen ist schwieriger und manchmal fast unmöglich, sie effektiv zu optimieren. Deshalb ist es wichtig, die Anzahl der Parameter so weit wie möglich einzuschränken. Wenn wir bestimmte Parameter auf konstante Werte festlegen, können wir uns auf die Optimierung der wichtigsten Parameter konzentrieren.
Um die Dinge in die richtige Perspektive zu rücken, erreichte unsere ursprüngliche Strategie des gleitenden Durchschnitts im Vorwärtstest eine Sharpe Ratio von 1,29, mit einem Gewinn von 133,51 $ bei 101 Handelsgeschäften. Im Gegensatz dazu erzielte die neue RSI-basierte Strategie, die wir heute entwickeln werden, eine Sharpe Ratio von 2,68 und einen Gewinn von 214,08 $ – bei nur 52 Handelsgeschäften.
Das bedeutet, dass die neue Strategie nicht nur mehr Geld einbrachte, sondern dies auch mit weniger Handelsgeschäften, geringerem Marktengagement und geringerem Risiko erreichte. Dies ist genau die Art von Leistung, die wir bei der Entwicklung von Handelsstrategien anstreben.
Wenn wir eng mit dem Strategietester zusammenarbeiten und ihn klug einsetzen, können wir profitablere Einstellungen aufdecken. Dennoch ist es wichtig, die Erwartungen zu steuern. Der MetaTrader 5 Strategietester kann eine schlechte Strategie nicht auf magische Weise korrigieren. Wenn eine Strategie von Grund auf fehlerhaft ist, wird keine noch so umfangreiche Optimierung sie rentabel machen.
Selbst mit einer guten Strategie garantiert die Optimierung keine besseren Ergebnisse, aber sie kann Ihre Chancen erheblich verbessern, wenn sie richtig eingesetzt wird. Mit einer soliden Grundlage und den richtigen Werkzeugen können wir unbegrenzt viel Leistung herausholen.
Erste Schritte in MQL5
Unsere Diskussion beginnt damit, dass wir zunächst die Klasse für unsere RSI-Midpoint-Strategie aufbauen. Das erste Ziel, das wir erreichen müssen, ist das Laden der Abhängigkeiten, die unsere Strategieklasse benötigt. Die erste Abhängigkeit ist die RSI-Klasse, die wir für unsere Indikatoren mit einem Puffer erstellt haben. Darüber hinaus benötigen wir auch die Oberklasse für alle unsere Strategien, auch Elternklasse genannt.
//+------------------------------------------------------------------+ //| RSIMidPoint.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" //+------------------------------------------------------------------+ //| Dependencies | //+------------------------------------------------------------------+ #include <VolatilityDoctor\Indicators\RSI.mqh> #include <VolatilityDoctor\Strategies\Parent\Strategy.mqh>
Nachdem wir nun unsere Abhängigkeiten geladen haben, können wir mit der Definition der RSI-Midpoint-Strategieklasse und ihrer Mitglieder beginnen. Beachten Sie, dass diese Mittelpunkt-Strategie von der Basisklasse für alle unsere Strategien erbt, die wir mit der Doppelpunkt-Syntax bezeichnen. Von dort aus definieren wir die Mitglieder unserer Strategieklasse RSIMidpoint. Beachten Sie, dass wir nur einige wenige Mitglieder zu definieren haben. Beachten Sie auch, dass alle virtuellen Methoden, die wir in der übergeordneten Strategieklasse erstellt haben, in unserer Klasse RSIMidpoint erneut als virtuell aufgeführt werden müssen.
class RSIMidPoint : public Strategy { private: //--- The instance of the RSI used in this strategy RSI *my_rsi; public: //--- Class constructor RSIMidPoint(string user_symbol,ENUM_TIMEFRAMES user_timeframe,int user_period,ENUM_APPLIED_PRICE user_price); //--- Class destructor ~RSIMidPoint(); //--- Class overrides virtual bool Update(void); virtual bool BuySignal(void); virtual bool SellSignal(void); };
Wir können nun überlegen, wie jede Methode speziell für unsere RSI-Strategie umgesetzt werden soll. Die Aktualisierungsmethode muss nur den Wert des RSI-Indikators aktualisieren und dann sicherstellen, dass der aktuelle Wert des RSI-Indikators nicht Null ist. Wenn das der Fall ist, dann ist alles gut gelaufen, ansonsten ist etwas schief gelaufen.
//+------------------------------------------------------------------+ //| Our strategy update method | //+------------------------------------------------------------------+ bool RSIMidPoint::Update(void) { //--- Set the indicator value my_rsi.SetIndicatorValues(Strategy::GetIndicatorBufferSize(),true); //--- Check readings are valid if(my_rsi.GetCurrentReading() != 0) return(true); //--- Something went wrong return(false); }
Als Nächstes betrachten wir die Fälle für unsere Kauf- und Verkaufssignale. Unsere Kaufsignale werden generiert, wenn unser RSI-Wert unter 50 liegt, während unsere Verkaufssignale generiert werden, wenn der RSI-Wert über 50 liegt.
//+------------------------------------------------------------------+ //| Check for our buy signal | //+------------------------------------------------------------------+ bool RSIMidPoint::BuySignal(void) { //--- Buy signals when the RSI is below 50 return(my_rsi.GetCurrentReading()<50); } //+------------------------------------------------------------------+ //| Check for our sell signal | //+------------------------------------------------------------------+ bool RSIMidPoint::SellSignal(void) { //--- Sell signals when the RSI is above 50 return(my_rsi.GetCurrentReading()>50); }
Schließlich müssen wir noch den Konstruktor und den Destruktor der Klasse betrachten. Der Klassenkonstruktor nimmt die für unseren RSI-Indikator erforderlichen Details auf. Insbesondere erfordert unser Konstruktor, dass der Nutzer angibt, auf welches Symbol, welchen Zeitrahmen, welche Periode und welchen Preis der RSI angewendet werden soll. Sobald dies alles festgelegt ist, laden wir diese Details und erstellen eine neue Instanz unseres RSI-Indikators.
Beachten Sie jedoch, dass diese spezielle Instanz des RSI-Indikators, die wir verwenden, nicht die gleiche RSI-Instanz ist, die mit MetaTrader 5 geladen wird. Dies ist ein von uns definierter nutzerdefinierter Typ, der viele andere nützliche Funktionen hat, die wir verwenden werden. Dennoch ist die Gesamtfunktionalität dieselbe, und die Leser sollten sich frei fühlen, einige dieser Methoden bei Bedarf von Grund auf zu implementieren.
//+------------------------------------------------------------------+ //| Our class constructor | //+------------------------------------------------------------------+ RSIMidPoint::RSIMidPoint(string user_symbol,ENUM_TIMEFRAMES user_timeframe,int user_period,ENUM_APPLIED_PRICE user_price) { my_rsi = new RSI(user_symbol,user_timeframe,user_period,user_price); Print("RSI-Mid-Point Strategy Loaded."); }
Schließlich löschen wir im Destruktor unserer Klasse den Zeiger auf das nutzerdefinierte RSI-Objekt, das wir erstellt haben.
//+------------------------------------------------------------------+ //| Our class destructor | //+------------------------------------------------------------------+ RSIMidPoint::~RSIMidPoint() { delete my_rsi; } //+------------------------------------------------------------------+
Nachdem wir nun die Klasse definiert haben, die unsere RSI-Midpoint-Strategie kapselt, müssen wir sicherstellen, dass die Klasse ohne Fehler erstellt wurde. Daher müssen wir zunächst ein Basis-Leistungsniveau mit einer fest kodierten Version der Strategie festlegen, damit wir unsere Klasse testen können, um zu sehen, ob die Klasse die gleichen Leistungsniveaus wiederherstellen kann, die mit der fest kodierten Version erreicht wurden.
Wenn diese beiden Strategien gleichwertig sind, dann sollten sie die gleichen Rentabilitätsniveaus aufweisen und die gleichen Statistiken liefern, wenn sie über den gleichen Zeitraum getestet werden. Bei der Definition unserer Basislinie müssen wir also zunächst die Systemkonstanten auflisten, die wir benötigen. Zum Beispiel der Preis, auf den der RSI angewendet werden soll, die RSI-Periode und der Zeitrahmen. Alle diese Konstanten müssen in beiden Tests gleich sein, damit wir faire Vergleiche anstellen können.
//+------------------------------------------------------------------+ //| MSA Test 2 Baseline.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 RSI_PRICE PRICE_CLOSE #define RSI_PERIOD 15 #define RSI_TIME_FRAME PERIOD_D1 #define HOLDING_PERIOD 5
Danach laden wir die benötigten Abhängigkeiten. In diesem Fall benötigen wir nur etwa drei Bibliotheken.
//+------------------------------------------------------------------+ //| Dependencies | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> #include <VolatilityDoctor\Trade\TradeInfo.mqh> #include <VolatilityDoctor\Time\Time.mqh>
Außerdem müssen wir auch einige globale Variablen definieren. Beachten Sie, dass wir unsere globalen Variablen in zwei Typen aufteilen können: nutzerdefinierte Typen und Systemtypen. Eigene Typen sind nutzerdefiniert. Systemdefinierte Typen sind in jeder Installation von MetaTrader 5 verfügbar, z.B. Doubles und Floats. Dies ist eine Möglichkeit, unsere globalen Variablen zu gruppieren, damit unser Code leichter zu pflegen ist.
//+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ //--- Custom Types CTrade Trade; TradeInfo *TradeInformation; Time *TradeTime; //--- System Types double rsi[],ma_close[]; int rsi_handler; int position_timer;
Von dort aus gehen wir nun zum Aufbau des Initialisierungsprozesses für unseren Expert Advisor über. Wenn unser Expert Advisor zum ersten Mal initialisiert wird, werden wir den RSI-Handler selbst erstellen und anschließend dynamische Instanzen einiger der benötigten nutzerdefinierten Typen erstellen. Wir brauchen diese nutzerdefinierten Typen vor allem, um die Zeit zu verfolgen und wichtige Handelsinformationen wie die kleinste zulässige Losgröße zu erhalten. Und von dort aus werden wir sicherstellen, dass der RSI-Handler sicher geladen wurde.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Our technical indicator rsi_handler = iRSI(Symbol(),RSI_TIME_FRAME,RSI_PERIOD,RSI_PRICE); //--- Create dynamic instances of our custom types TradeTime = new Time(Symbol(),RSI_TIME_FRAME); TradeInformation = new TradeInfo(Symbol(),RSI_TIME_FRAME); //--- Safety checks if(rsi_handler == INVALID_HANDLE) return(false); //--- Everything was fine return(INIT_SUCCEEDED); } //--- End of OnInit Scope
Wenn unser Expert Advisor nicht mehr verwendet wird, geben wir den Indikator frei, den wir nicht mehr verwenden, und löschen auch die von uns erstellten dynamischen Objekte.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Delete the indicators and dynamic objects IndicatorRelease(rsi_handler); delete TradeTime; delete TradeInformation; } //--- End of Deinit Scope
Während der OnTick-Funktion prüfen wir zunächst, ob sich eine neue Tageskerze gebildet hat, indem wir die Bibliothek verwenden, die wir dafür geschrieben haben. Wenn sich tatsächlich eine neue Kerze gebildet hat, werden wir unsere technischen Indikatoren aktualisieren und dann prüfen, ob wir Positionen eröffnet haben. Wenn das der Fall ist, dass wir keine Positionen offen haben, stellen wir sicher, dass unsere Positionszeit zurückgesetzt wurde und prüfen dann, ob ein Handelssignal vorliegt. Andernfalls, wenn wir offene Handelsgeschäfte haben, prüfen wir, ob unser Geschäft die Fälligkeit erreicht hat und geschlossen werden sollte. Andernfalls werden wir weiter warten.
//+------------------------------------------------------------------+ //| 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
Wir müssen uns auch Gedanken über die Umsetzung einiger der besprochenen Methoden machen. Die Aktualisierungsmethode ruft beispielsweise einfach den Kopierpuffer auf und kopiert die Indikatorwerte aus unserem RSI-Handler in das Array, das wir zu diesem Zweck angegeben haben. Wir kopieren nur den aktuellen Stand des RSI-Indikators. Wir brauchen die vorherigen Lesungen nicht.
//+------------------------------------------------------------------+ //| Update our technical indicators | //+------------------------------------------------------------------+ void Update(void) { //--- Call the CopyBuffer method to get updated indicator values CopyBuffer(rsi_handler,0,0,1,rsi); } //--- End of Update Scope
Auch hier sucht die Kontrollsignal-Methode einfach nach den Handelssignalen, die wir zuvor definiert haben – unser RSI wird kaufen, wenn der Wert unter 50 liegt; andernfalls werden wir verkaufen, wenn der Wert über 50 liegt.
//+------------------------------------------------------------------+ //| Check for a trading signal using our cross-over strategy | //+------------------------------------------------------------------+ void CheckSignal(void) { //--- Buy signals when the RSI is below 50 if(rsi[0] < 50) { Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,""); return; } //--- Sell signals when the RSI is above 50 else if(rsi[0] > 50) { Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,""); return; } } //--- End of CheckSignal Scope
Schließlich vereinheitlichen wir unsere Systemkonstanten, die wir zu Beginn definiert haben.
//+------------------------------------------------------------------+ //| Undefine system constants | //+------------------------------------------------------------------+ #undef RSI_PRICE #undef RSI_PERIOD #undef RSI_TIME_FRAME #undef HOLDING_PERIOD //+------------------------------------------------------------------+
Um mit unserem Rückentest zu beginnen, wählen wir zunächst den Ausbildungszeitraum. Unser Test wird vom ersten Januar 2022 bis zum ersten Mai 2025 laufen. Beachten Sie, dass wir diese Übung im täglichen Zeitrahmen durchführen werden.
Abbildung 1: Die Tage des Backtest, die wir für unsere Basismessung ausgewählt haben.
Unsere Simulation wird robuste Tests liefern, wenn wir die Verzögerungen auf „Zufällige Verzögerung“ einstellen. Dies simuliert Latenzzeiten, Slippage und andere Faktoren, die beim Handel mit realen Konten zu Spannungen führen.
Abbildung 2: Die Testeinstellungen, die wir für unseren Rückentest verwendet haben.
Die in Abb. 3 dargestellte Kapitalkurve ist das Ziel, das von der RSI-Handelsstrategieklasse reproduziert werden soll.
Abbildung 3: Die Kapitalkurve, die von der fest kodierten Version unserer Handelsstrategie erzeugt wird.
Wir können detaillierte Statistiken über die Leistung unserer Basisstrategie einsehen. Wir wollen, dass unsere Klasse im gleichen Zeitraum die gleichen statistischen Ergebnisse erzielt, um zu beweisen, dass sie solide ist.
Abbildung 4: Detaillierte Statistiken über die Performance unserer Handelsstrategie.
Nun müssen wir denselben Test implementieren, um festzustellen, ob unsere RSI-Strategieklasse gültig ist. Daher bleiben die meisten der zuvor definierten globalen Elemente, wie Systemkonstanten und globale Variablen, weitgehend unverändert. Zu den wichtigsten Änderungen, die wir an der Anwendung vornehmen werden, gehört die Initialisierung unserer Strategieklasse während der Initialisierungsphase des Expert Advisors.
//+------------------------------------------------------------------+ //| Dependencies | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> #include <VolatilityDoctor\Time\Time.mqh> #include <VolatilityDoctor\Trade\TradeInfo.mqh> #include <VolatilityDoctor\Strategies\RSIMidPoint.mqh> //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ //--- Custom Types CTrade Trade; TradeInfo *TradeInformation; Time *TradeTime; RSIMidPoint *RSIMid; //--- System Types int position_timer; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Create dynamic instances of our custom types TradeTime = new Time(Symbol(),RSI_TIME_FRAME); TradeInformation = new TradeInfo(Symbol(),RSI_TIME_FRAME); RSIMid = new RSIMidPoint(Symbol(),RSI_TIME_FRAME,RSI_PERIOD,RSI_PRICE); //--- Everything was fine return(INIT_SUCCEEDED); } //--- End of OnInit ScopeAußerdem müssen wir die Funktion OnDeinit aktualisieren, um sicherzustellen, dass das neu erstellte Objekt ordnungsgemäß gelöscht wird.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Delete the dynamic objects delete TradeTime; delete TradeInformation; delete RSIMid; } //--- End of Deinit ScopeSchließlich müssen wir sicherstellen, dass die Methode CheckSignal ordnungsgemäß aufgerufen wird.
//+------------------------------------------------------------------+ //| Check for a trading signal using our cross-over strategy | //+------------------------------------------------------------------+ void CheckSignal(void) { //--- Long positions when RSI is below 50 if(RSIMid.BuySignal()) { Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,""); return; } //--- Otherwise short positions when the RSI is above 50 else if(RSIMid.SellSignal()) { Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,""); return; } } //--- End of CheckSignal Scope
Wir werden nun die RSI-Strategieklasse, die wir erstellt haben, über denselben Zeitraum testen, den wir in unserem früheren Basistest verwendet haben.
Abbildung 5: Auswahl der Testdaten für unsere Klassenversion der RSI-Handelsstrategie.
Beide Tests wurden unter den Bedingungen der „Zufälligen Verzögerung“ (siehe Abb. 2) durchgeführt und ergaben ähnliche Kapitalkurven bei den Backtest-Einstellungen.
Abbildung 6: Die mit der Klassenversion unserer Handelsstrategie erzielte Kapitalkurve entspricht der hart kodierten Basislinie
Außerdem erzielten beide Tests in der detaillierten Analyse die gleichen statistischen Werte. Dies ist ein Zeichen dafür, dass wir die Klasse erfolgreich und ohne Fehler implementiert haben. Daher werden wir nun mit der Zusammenlegung der Strategieklassen für einen gleitenden Durchschnitt und für den relativen Stärke Index fortfahren.
Abbildung 7: Detaillierte Statistiken zur Analyse der Performance der Klassenversion unserer Handelsstrategie im Vergleich zur Baseline.
Nachdem wir nun überprüft haben, dass unsere klassenbasierten und fest kodierten Versionen der Handelsstrategie identische Ergebnisse liefern, können wir die Strategie des Kreuzens von gleitenden Durchschnitten mit der RSI-basierten Strategie zusammenführen, um eine neue Gesamtstrategie zu erstellen. Zunächst müssen wir einige Parameter auswählen, die wir beibehalten wollen. Es ist wichtig, das Wachstum des Parameterraums zu kontrollieren, da ein zu großer Parameterraum eine sinnvolle Optimierung nahezu unmöglich machen kann.
//+------------------------------------------------------------------+ //| 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 | //+------------------------------------------------------------------+ //--- Fix any parameters that can afford to remain fixed #define MA_SHIFT 0 #define MA_TYPE MODE_EMA #define RSI_PRICE PRICE_CLOSE
Außerdem müssen wir jetzt, da wir zwei Strategien gleichzeitig ausführen, festlegen, wie diese Strategien innerhalb von MQL5 interagieren werden. Um dies zu bewerkstelligen, können wir eine Enumeration verwenden, um eine Logik zu definieren, die in mehreren verwandten Zuständen existieren kann.
Dementsprechend haben wir einen Enumerator mit der Bezeichnung Strategiemodus erstellt, der drei mögliche Einstellungen hat. Die erste Einstellung, dargestellt durch 0, ist die Abstimmungspolitik. Bei dieser Strategie wird nur dann gehandelt, wenn sowohl der gleitende Durchschnitt als auch der RSI in die gleiche Richtung weisen. Mit anderen Worten: Beide Strategien müssen einen Kauf signalisieren, damit ein Kaufposition eröffnet werden kann.
Im ersten alternativen Modus ist die RSI-Strategie für die Eröffnung von Kaufpositionen zuständig, während die Strategie des gleitenden Durchschnitts für Verkaufspositionen zuständig ist. Der letzte Modus kehrt diese Zuordnung um und überlässt der Strategie des gleitenden Durchschnitts die Verantwortung für Käufe und der RSI-Strategie die Verantwortung für Verkäufe.
//+------------------------------------------------------------------+ //| User defined enumerator | //+------------------------------------------------------------------+ enum STRATEGY_MODE { MODE_ONE = 0, //Voting Policy MODE_TWO = 1, //RSI Buy & MA Sell MODE_THREE = 2 //MA Sell & RSI Buy };Durch diese Strukturierung der Strategie haben wir die Gesamtzahl der Eingabeparameter auf eine überschaubare Menge von fünf reduziert. Von dort aus laden wir die zuvor besprochenen Abhängigkeiten und initialisieren die Variablen, mit denen wir bereits vertraut sind.
//+------------------------------------------------------------------+ //| User Inputs | //+------------------------------------------------------------------+ input group "Moving Average Strategy Parameters" input int MA_PERIOD = 10;//Moving Average Period input group "RSI Strategy Parameters" input int RSI_PERIOD = 15;//RSI Period input group "Global Strategy Parameters" input ENUM_TIMEFRAMES STRATEGY_TIME_FRAME = PERIOD_D1;//Strategy Timeframe input int HOLDING_PERIOD = 5;//Position Maturity Period input STRATEGY_MODE USER_MODE = 0;//Operation Mode For Our Strategy //+------------------------------------------------------------------+ //| Dependencies | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> #include <VolatilityDoctor\Time\Time.mqh> #include <VolatilityDoctor\Trade\TradeInfo.mqh> #include <VolatilityDoctor\Strategies\OpenCloseMACrossover.mqh> #include <VolatilityDoctor\Strategies\RSIMidPoint.mqh> //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ //--- Custom Types CTrade Trade; Time *TradeTime; TradeInfo *TradeInformation; RSIMidPoint *RSIMid; OpenCloseMACrossover *MACross; //--- System Types int position_timer;
Während der Initialisierungsfunktion laden wir dieselben Klassen, die während des gesamten Entwicklungsprozesses verwendet werden. Wenn der Expert Advisor beendet wird, stellen wir sicher, dass alle dynamisch erstellten Objekte ordnungsgemäß gelöscht werden.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Create dynamic instances of our custom types TradeTime = new Time(Symbol(),STRATEGY_TIME_FRAME); TradeInformation = new TradeInfo(Symbol(),STRATEGY_TIME_FRAME); MACross = new OpenCloseMACrossover(Symbol(),STRATEGY_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE); RSIMid = new RSIMidPoint(Symbol(),STRATEGY_TIME_FRAME,RSI_PERIOD,RSI_PRICE); //--- 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; delete RSIMid; } //--- End of Deinit Scope
In der Funktion OnTick wenden wir dieselbe Logik an, die wir zuvor eingeführt haben. Da diese Logik bereits ausführlich behandelt wurde, wird die Funktion OnTick in diesem Abschnitt der Diskussion ausgelassen. Die Aktualisierungsfunktion wurde leicht geändert, da sie nun zwei separate Aktualisierungsmethoden aufruft – eine für jede Strategie. Schließlich hat die Methode CheckSignal die größte Änderung erfahren. Sie muss nun zunächst den aktuellen Strategiemodus bestimmen, bevor sie irgendwelche Handelsgeschäfte einleitet.
//+------------------------------------------------------------------+ //| Update our technical indicators | //+------------------------------------------------------------------+ void Update(void) { //--- Update the strategy RSIMid.Update(); MACross.Update(); } //--- End of Update Scope //+------------------------------------------------------------------+ //| Check for a trading signal using our cross-over strategy | //+------------------------------------------------------------------+ void CheckSignal(void) { //--- Both Stratetgies Should Cast The Same Vote if(USER_MODE == 0) { //--- Long positions when the close moving average is above the open if(MACross.BuySignal() && RSIMid.BuySignal()) { Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,""); return; } //--- Otherwise short else if(MACross.SellSignal() && RSIMid.SellSignal()) { Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,""); return; } } //--- RSI Opens All Long Positions & The Moving Average Opens Short Positions else if(USER_MODE == 1) { if(RSIMid.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; } } //--- RSI Opens All Short Positions & The Moving Average Opens Long Positions else if(USER_MODE == 2) { if(MACross.BuySignal()) { Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,""); return; } //--- Otherwise short else if(RSIMid.SellSignal()) { Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,""); return; } } } //--- End of CheckSignal Scope
Nachdem die gesamte Logik ausgeführt wurde, werden alle Systemkonstanten rückgängig gemacht, um das Programm sauber zu beenden.
//+------------------------------------------------------------------+ //| Undefine system constants | //+------------------------------------------------------------------+ #undef MA_SHIFT #undef RSI_PRICE #undef MA_TYPE //+------------------------------------------------------------------+
Nun wollen wir uns auf die Suche nach guten Parametereinstellungen für unsere Anwendung machen. Wählen Sie die soeben erstellte Anwendung aus und setzen Sie das Feld Vorwärts auf „1/2“. Auf diese Weise kann unser genetisches Optimierungsprogramm die Hälfte der historischen Daten, die uns zur Verfügung stehen, zum Erlernen guter Parameter verwenden, während die andere Hälfte dem Optimierungsprogramm verborgen bleibt und zur Überprüfung der Einstellungen verwendet wird, die das Optimierungsprogramm für praktikabel hält.
Abbildung 8: Einrichten unseres Vorwärtstests.
Nach der Einstellung „Zufällige Verzögerung“ wie in den vorherigen 2 Tests, erinnern wir uns daran, dass wir dieses Mal auch einen Optimierer auswählen wollen. Wenn Sie mehr Rechenleistung zur Verfügung haben, können Sie auch die anderen angebotenen Optimierungen ausprobieren, z. B. die Einstellung „Langsamer vollständiger Algorithmus“.
Abbildung 9: Wählen Sie im Feld Optimierung die Einstellung „Schneller genetisch basierter Algorithmus“.
Wählen Sie die Registerkarte „Eingaben“ am unteren Rand des Bildschirms, um das Panel mit den zu testenden Strategieparametern aufzurufen. Aktivieren Sie das Kontrollkästchen neben den einzelnen Parametern und wählen Sie dann einen Start- und Endpunkt für die zu durchsuchenden Werte. Beachten Sie, dass auch der Schrittparameter von entscheidender Bedeutung ist. Wenn Sie eine Schrittweite von 1 wählen, werden Ihre Ergebnisse zwar sehr detailliert sein, die Gesamtzahl der erforderlichen Schritte könnte jedoch astronomisch hoch sein. Im Allgemeinen werden Intervalle von 2, 4, 5 oder sogar 10 für besonders umfangreiche Recherchen empfohlen.
Abbildung 10: Auswahl der Einstellungsintervalle, in denen unsere Parameter durchsucht werden sollen.
Wenn Sie den Test starten, werden die Ergebnisse des Vorwärtstest als Streudiagramm angezeigt, wobei jeder Punkt auf dem Diagramm einen vollständigen Test darstellt, der mit einem eindeutigen Satz von Strategieparametern durchgeführt wurde.
Abbildung 11: Wir können die Ergebnisse des Strategietesters als zweidimensionales Streudiagramm darstellen, wobei der Gewinn auf der y-Achse liegt.
Unsere Strategie blieb auch außerhalb der Trainingsstichprobe, die unser genetischer Algorithmus zur Auswahl dieser Einstellungen verwendete, profitabel. Dies ist ein guter Indikator für eine mögliche künftige Stabilität, wenn wir beschließen, den Algorithmus für Realkapital einzusetzen.
Abb. 12: Die von unserer Handelsstrategie erzeugte Kapitalkurve weist außerhalb der Stichprobe einen positiven Trend auf.
Unser neues System ist weitaus rentabler als die vorhergehende Iteration. Es ist deutlich zu erkennen, dass die Kombination „Voting Policy“ am stabilsten erscheint
Abb. 13: Die Leistung unserer Anwendung verbesserte sich, nachdem wir ihr eine zusätzliche Strategie an die Hand gegeben hatten.
Dies waren die besten Ergebnisse, die wir mit den Ergebnissen des Vorwärtstest in unserer ersten Diskussion erzielt haben. Unser neues System übertrifft die vorherige Version, die nur eine Strategie kannte.
Abb. 14: Die besten Ergebnisse erzielten wir, als unsere Anwendung nur eine Strategie hatte.
Schlussfolgerung
Dieser Artikel hat den Leser durch die vielen Vorteile der Anwendung der Prinzipien der objektorientierten Programmierung (OOP) in MQL5, kombiniert mit menschlicher Kreativität, geführt. Sie hat gezeigt, dass es mehrere Möglichkeiten gibt, verschiedene Strategien in ein zusammenhängendes System zu integrieren. Die objektorientierte Programmierung ist ein leistungsfähiges und vielseitiges Werkzeug für solche kreativen Unternehmungen, da sie eine strukturierte, kontrollierte und vorhersehbare Entwicklung ermöglicht.
Aufbauend auf den hier vorgestellten grundlegenden Konzepten sind die Leser nun in der Lage, ihre eigenen privaten Handelsstrategien zu verfeinern und zu optimieren. Mit dem in MetaTrader 5 eingebauten Strategietester können die Leser mit dem Testen und Verbessern ihrer Implementierungen beginnen.
Diese Diskussion hat auch gezeigt, wie wichtig es ist, die Anzahl der Eingabeparameter in einer Strategie zu minimieren. Bei einer Strategie mit zu vielen Parametern wird es immer schwieriger, sie effektiv zu optimieren. Es ist zwar möglich, einen Teil der Rechenlast auf MQL5-Cloud-Network-Dienste zu verlagern, aber die Beibehaltung eines einfachen Designs führt oft zu besseren Ergebnissen und robusteren Strategien.
Durch die Anwendung der in diesem Artikel erörterten Grundsätze sollten die Leser nun über umsetzbare Erkenntnisse für den Aufbau ihrer eigenen Ensemble-Handelssysteme verfügen. Wir freuen uns auf Ihre weitere Teilnahme an zukünftigen Diskussionen, bei denen wir eine dritte Ebene in unsere Handelsstrategie einführen werden. Dieser nächste Schritt wird uns helfen, eine solide Grundlage für zukünftige statistische Modelle zu schaffen.
Dateiname | Beschreibung der Datei |
---|---|
MSA Test 2 Baseline.mq5 | Hardcodierte Testversion unserer RSI-Handelsstrategie. |
MSA Test 2 Class.mq5 | Testversion unserer RSI-Strategieklasse. |
MSA Test 2.ex5 | Kompilierte Version unserer Ensemble-Handelsstrategie. |
MSA Test 2.mq5 | Quellcode für die von uns entwickelte Ensemble-MA- und RSI-Anwendung. |
RSIMidPoint.mqh | MQL5-Klasse für unsere RSI-Mittelwertstrategie. |
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/18471
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.