Selbstoptimierende Expert Advisors in MQL5 (Teil 9): Kreuzen zweier gleitender Durchschnitte
In unserer Artikelserie haben wir uns aus verschiedenen Blickwinkeln mit der Frage beschäftigt, wie sich die Verzögerung bei der klassischen Handelsstrategie des Kreuzens von gleitenden Durchschnitten verringern lässt.
Bei unseren ersten Versuchen haben wir statistische Modellierungstools eingesetzt, um Überkreuzungen gleitender Durchschnitte im Voraus zu prognostizieren. Wir haben Fortschritte in dieser Richtung gemacht und festgestellt, dass unter den richtigen Marktbedingungen die Vorhersage von Überkreuzungen gleitender Durchschnitte genauer sein kann als die direkte Vorhersage von Preisen. Daraufhin entdeckten wir eine weitere Methode, um die Verzögerung weiter zu verringern. Bei diesem Ansatz werden die Periodenlänge der beiden gleitenden Durchschnitte so festgelegt, dass sie einen gemeinsamen Wert haben, und stattdessen werden Überkreuzungen erzeugt, indem ein gleitender Durchschnitt auf den Eröffnungskurs und der andere auf den Schlusskurs angewendet wird. Bei diesem Ansatz werden die Periodenlängen der beiden gleitenden Durchschnitte so festgelegt, dass sie einen gemeinsamen Wert haben, und die Überschneidungen erzeugt werden, indem ein gleitender Durchschnitt auf den Eröffnungskurs und der andere auf den Schlusskurs angewendet wird.
In dieser Diskussion erkunden wir einen weiteren einzigartigen Ansatz, den wir bisher nicht in Betracht gezogen haben. Wie bei den meisten Problemen im Leben und in der Mathematik gibt es mehr als eine Möglichkeit, ein Problem anzugehen, und jede Lösung bringt ihre eigenen Vor- und Nachteile mit sich. Indem wir diese Alternativen abwägen, wollen wir verstehen, wie viel Kontrolle wir über die Verzögerung im System ausüben können.
Hier werden wir versuchen, was ich als Strategie des Double Moving Average Crossover, Kreuzen zweier gleitender Durchschnitte bezeichne. Wie in Abbildung 1 dargestellt, wird die klassische Kreuzungsstrategie mit gleitendem Durchschnitt in der Regel auf einen einzigen Zeitrahmen mit zwei gleitenden Durchschnitten unterschiedlicher Periodenlängen angewendet. Im Gegensatz zu unserer vorherigen Diskussion – bei der beide gleitenden Durchschnitte eine feste Periode hatten – kehren wir dieses Mal leicht zum klassischen Ansatz zurück und erlauben den beiden Indikatoren, unterschiedliche Perioden zu verwenden.
Das Problem bei dieser ursprünglichen Form ist, dass die Bestätigung von Einstiegssignalen oft zu spät kommt – nachdem die Bewegung bereits begonnen hat – was zu verspäteten Einstiegen oder verpassten Chancen führt.

Abbildung 1: Visualisierung unserer Strategie des Double Moving Average Crossover auf dem täglichen Zeitrahmen.
Was wir hier vorschlagen, ist nicht völlig neu. In der Tat haben diskretionäre oder menschliche Händler lange Zeit eine ähnliche Logik angewandt. Der Kerngedanke besteht darin, zunächst Kreuzungs-Muster auf einem höheren Zeitrahmen zu beobachten (z. B. auf dem Tages-Chart, wie in Abbildung 1 dargestellt). Wir reagieren jedoch nicht sofort auf diese Signale. Sobald wir einen Crossover auf dem höheren Zeitrahmen sehen, gehen wir stattdessen zu einem niedrigeren Zeitrahmen über – z. B. zum M30 in Abbildung 2 – und suchen nach Kreuzungs-Mustern, die dem entsprechen, was wir auf dem höheren Zeitrahmen beobachtet haben.
Menschliche Händler haben oft gesagt: „Handle im Einklang mit dem höheren Zeitrahmen.“ In den meisten unserer Diskussionen über den algorithmischen Handel wenden wir eine Strategie auf denselben Zeitrahmen an und handeln sie. Heute jedoch wenden wir die Strategie zweimal an – einmal auf einem höheren und ein zweites Mal auf einem niedrigeren Zeitrahmen. Der obere Zeitrahmen gibt einen Trend für den Tag vor, während wir im unteren Zeitrahmen nach Einstiegssignalen suchen, die mit diesem Trend übereinstimmen. Dies ist der Kern unserer doppelten Kreuzungs-Strategie. Indem wir zunächst einen Trend auf einem höheren Zeitrahmen festlegen und dann auf einem niedrigeren Zeitrahmen nach Gelegenheiten suchen, in Übereinstimmung mit diesem Trend zu handeln, hoffen wir, die Verzögerung auszugleichen, die durch die auf dem höheren Zeitrahmen generierten Signale entsteht.
Jetzt, wo wir uns einig sind, können wir mit der Umsetzung der Strategie beginnen, um festzustellen, ob sie sich bewährt hat. Bevor wir in den Code eintauchen, ist klar, dass dieser Ansatz mehrere Komponenten enthält, die wir sorgfältig berücksichtigen müssen. Eine wichtige Frage ist, wie wir die Zugangsbedingungen definieren. Angenommen, der höhere Zeitrahmen zeigt ein Aufwärtskreuz. Für den unteren Zeitrahmen gibt es dann zwei Optionen:
- Konträrer Einstieg: Warten Sie auf ein Abwärtskreuz auf dem unteren Zeitrahmen und wetten Sie dann dagegen – in dem Glauben, dass sich der untere Zeitrahmen am Ende des Tages wieder der Aufwärtstrend anpassen wird.
- Trendfolgender Einstieg: Warten Sie einfach darauf, dass der untere Zeitrahmen ein Aufwärtskreuz in dieselbe Richtung wie der obere Zeitrahmen bildet.
Diese beiden Optionen stellen unterschiedliche Ideologien für den Einstieg in den Handel im Rahmen dieser Strategie dar. Das Verlassen von Positionen führt zu noch mehr Komplexität und Variationen, die jeweils ihre eigenen Kompromisse mit sich bringen. So können wir beispielsweise beschließen, eine Position zu schließen, wenn der untere Zeitrahmen nicht mehr mit dem Trend des oberen Zeitrahmens übereinstimmt. Alternativ können wir nur dann aussteigen, wenn sich der Trend selbst auf dem höheren Zeitrahmen ändert. Wenn wir also den Tag mit einem Aufwärtssignal auf dem Tages-Chart beginnen, halten wir unsere Position, bis der höhere Zeitrahmen in eine Abwärtsbewegung umschlägt.
Wie Sie sehen, gibt es viele Möglichkeiten, mit dieser Methode in den Handel einzusteigen und ihn zu beenden. Anstatt sich bei der Ermittlung der optimalen Kombination allein auf den Verstand zu verlassen, müssen wir einen genetischen Optimierer einsetzen. Auf diese Weise können wir anhand von Marktdaten ermitteln, welche Kombination dieser Alternativen am rentabelsten sein könnte.

Abbildung 2: Visualisierung unserer Kreuzungs-Strategie mit gleitendem Durchschnitt in einem niedrigeren Zeitrahmen, dem M30.
Der erste Schritt besteht darin, einige Systemkonstanten zu definieren, die während der gesamten Entwicklungsphase unserer Anwendung unverändert bleiben müssen. Der Einfachheit halber halten wir die Zeitrahmen konstant: Der Tages-Chart dient als Proxy für unseren höheren Zeitrahmen, der M15 als unser niedrigerer Zeitrahmen und der H4 als Zeitrahmen für die Berechnung unseres Stop-Loss.
Wir könnten in Erwägung ziehen, diese Systemkonstanten in abstimmbare Parameter umzuwandeln, die ein genetischer Optimierer nutzen kann, um sicherzustellen, dass wir die bestmöglichen Einträge erhalten. Damit wir aber loslegen können, werden wir diese Werte beibehalten.//+------------------------------------------------------------------+ //| Double Crossover.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| System constants | //+------------------------------------------------------------------+ //--- System time frames #define TF_1 PERIOD_D1 #define TF_2 PERIOD_M15 #define TF_3 PERIOD_H4 #define LOT_MULTILPLE 1
Wir müssen auch eine Reihe von nutzerdefinierten Enumerationen definieren, um die verschiedenen Modi darzustellen, in denen unsere Strategie funktionieren kann. So kann die Anwendung beispielsweise in einem trendfolgenden oder einem mittelwertumkehrenden Modus laufen, und eine spezielle Enumeration ermöglicht es dem Nutzer, zwischen diesen beiden Modi zu wechseln. In ähnlicher Weise definieren wir eine weitere Enumeration, um anzugeben, ob unsere Abschlussbedingungen auf dem niedrigeren oder dem höheren Zeitrahmen bewertet werden sollen.
//+------------------------------------------------------------------+ //| Custom enumerations | //+------------------------------------------------------------------+ //--- What trading style should we follow when opening our positions, trend following or mean reverting? enum STRATEGY_MODES { TREND = 0, //Trend Following Mode MEAN_REVERTING = 1 //Mean Reverting Mode }; //--- Which time frame should we consult, when determining if we should close our position? enum CLOSING_TIME_FRAME { HIGHER_TIME_CLOSE = 0, //Close on higher time frames LOWER_TIME_CLOSE = 1 //Close on lower time frames };
Unsere Eingabeparameter sind recht einfach. Wir legen einen Periodenwert auf dem höheren Zeitrahmen fest und definieren dann eine Lücke zwischen der ersten und zweiten gleitenden Durchschnittsperiode. Durch diese Konstruktion wird sichergestellt, dass der genetische Optimierer eine Lücke von eins oder mehr auswählt, sodass die Differenz zwischen den Perioden konstruktionsbedingt nicht Null ist.
//+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input group "Technical Indicators" input int ma_1_period = 10; //Higher Time Frame Period input int ma_1_gap = 20; //Higher Time Frame Period Gap input int ma_2_period = 10; //Lower Time Frame Period input int ma_2_gap = 20; //Lower Time Frame Period Gap input group "Strategy Settings" input STRATEGY_MODES strategy_mode = 0; //Strategy Operation Mode input CLOSING_TIME_FRAME closing_tf = 0; //Strategy Closing Timeframe
Die Anwendung wird nur eine geringe Anzahl globaler Variablen benötigen, wie z.B. die technischen Indikatoren und Instanzen, die zur Verfolgung der aktuellen Marktpreise verwendet werden.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int ma_c_1_handle,ma_c_2_handle,ma_c_3_handle,ma_c_4_handle; double ma_c_1[],ma_c_2[],ma_c_3[],ma_c_4[]; double volume_min; double bid,ask; int state;
Die einzige externe Abhängigkeit, die für diese Übung erforderlich ist, ist die Trade-Bibliothek, die wir zum Laden und Schließen von Positionen nach Bedarf verwenden.
//+------------------------------------------------------------------+ //| Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade;
Bei der Initialisierung konfigurieren wir die technischen Indikatoren anhand der vom Nutzer übergebenen Einstellungen. Außerdem setzen wir den Systemstatus auf -1 zurück, was bedeutet, dass keine offenen Positionen vorhanden sind, und erfassen das zulässige Mindesthandelsvolumen am Markt.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- volume_min = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN); ma_c_2_handle = iMA(Symbol(),TF_1,ma_1_period,0,MODE_SMA,PRICE_CLOSE); ma_c_1_handle = iMA(Symbol(),TF_1,(ma_1_period + ma_1_gap),0,MODE_SMA,PRICE_CLOSE); ma_c_4_handle = iMA(Symbol(),TF_2,ma_2_period,0,MODE_SMA,PRICE_CLOSE); ma_c_3_handle = iMA(Symbol(),TF_2,(ma_2_period + ma_2_gap),0,MODE_SMA,PRICE_CLOSE); state = -1; //--- return(INIT_SUCCEEDED); }
Wenn die Anwendung nicht mehr verwendet wird, geben wir die technischen Indikatoren frei, um Speicherplatz zu schaffen.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- IndicatorRelease(ma_c_1_handle); IndicatorRelease(ma_c_2_handle); IndicatorRelease(ma_c_3_handle); IndicatorRelease(ma_c_4_handle); }
Wenn wir neue Kursdaten erhalten, suchen wir nach einer neuen Kerze im unteren Zeitrahmen (in diesem Fall M15). Wenn eine neue Kerze entdeckt wird, aktualisieren wir unseren internen Systemstatus.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- datetime current_time = iTime(Symbol(),TF_2,0); static datetime time_stamp; if(time_stamp != current_time) { time_stamp = current_time; update(); } } //+------------------------------------------------------------------+
Eine der wichtigsten Komponenten ist die Funktion zur Identifizierung von Handels-Setups. Diese Funktion akzeptiert einen Parameter namens padding, der die Größe des Stop-Loss für unsere Positionen darstellt. Die Auffüllung wird von der übergeordneten Funktion berechnet, die natürlich historische Bereichsebenen verwendet. Als Sicherheitsmaßnahme prüft die Setup-Funktion zunächst, ob die Summe der offenen Positionen gleich Null ist, um ein Overtrading zu verhindern.
Wenn ein Aufwärtskreuz im höheren Zeitrahmen auftritt und unsere Strategie im Trendfolgemodus ist, sucht die Setup-Funktion nach einem passenden Aufwärtskreuz im unteren Zeitrahmen. Wenn wir uns im Modus der Rückkehr zum Mittelwert befinden, sucht er nach dem gegenteiligen Kreuz (d.h. abwärts) und wettet dagegen. Die Logik ist spiegelbildlich für Verkaufseinstiege: Wenn ein Abwärtskreuz auf dem höheren Zeitrahmen auftritt, suchen wir nach einem entsprechenden Signal auf dem niedrigeren Zeitrahmen, abhängig vom gewählten Modus.
//+------------------------------------------------------------------+ //| Find a trading signal | //+------------------------------------------------------------------+ void find_setup(double padding) { if(PositionsTotal() == 0) { //--- Reset the system state state = -1; //--- Bullish on the higher time frame if(ma_c_1[0] > ma_c_2[0]) { //--- Trend following mode if((ma_c_3[0] > ma_c_4[0]) && (strategy_mode == 0)) { Trade.Buy(volume_min,Symbol(),ask,(bid - padding),0,""); state = 1; } //--- Mean reverting mode if((ma_c_3[0] < ma_c_4[0]) && (strategy_mode == 1)) { Trade.Buy(volume_min,Symbol(),ask,(bid - padding),0,""); state = 1; } } //--- Bearish on the higher time frame if(ma_c_1[0] < ma_c_2[0]) { //--- Trend following mode if((ma_c_3[0] < ma_c_4[0]) && (strategy_mode == 0)) { Trade.Sell(volume_min,Symbol(),bid,(ask + padding),0,""); state = 0; } //--- Mean reverting mode if((ma_c_3[0] > ma_c_4[0]) && (strategy_mode == 1)) { Trade.Sell(volume_min,Symbol(),bid,(ask + padding),0,""); state = 0; } } } }
Sobald eine Position eröffnet ist, rufen wir eine separate Positionsverwaltungsfunktion auf, die ebenfalls in zwei verschiedenen Modi arbeitet, je nachdem, ob wir den Handel auf der Grundlage des unteren oder des oberen Zeitrahmens schließen wollen. Der Systemstatus wird aktualisiert, wenn Positionen eröffnet werden, und dieser Status bestimmt die Handelsrichtung. Wenn zum Beispiel der Status Null ist, haben wir einen Verkauf eröffnet. Wenn der erste gleitende Durchschnitt jetzt über dem zweiten liegt (was auf ein Aufwärtsmomentum auf dem Tageskurs hindeutet) und wenn wir die Ausstiege auf der Grundlage des höheren Zeitrahmens verwalten, werden wir die Position schließen. Andernfalls, wenn wir uns auf den unteren Zeitrahmen stützen, warten wir auf ein Kreuzen dort. Es ist nicht sofort ersichtlich, welche Ausstiegsbedingung effektiver ist, daher müssen beide Optionen getestet werden.
//+------------------------------------------------------------------+ //| Manage our open positions | //+------------------------------------------------------------------+ void manage_setup(void) { if(closing_tf == 0) { if((state ==0) && (ma_c_1[0] > ma_c_2[0])) Trade.PositionClose(Symbol()); if((state ==1) && (ma_c_1[0] < ma_c_2[0])) Trade.PositionClose(Symbol()); } else if(closing_tf == 1) { if((state ==0) && (ma_c_3[0] > ma_c_4[0])) Trade.PositionClose(Symbol()); if((state ==1) && (ma_c_3[0] < ma_c_4[0])) Trade.PositionClose(Symbol()); } }
Schließlich benötigen wir eine Funktion zur Aktualisierung wichtiger Systemvariablen, wie z. B. der aktuellen Geld- und Briefkurse. Wir werden auch die letzten 10 Höchst- und Tiefstwerte verfolgen, die im dritten Zeitrahmen – unserem Risikozeitrahmen – auftreten. In diesem Beispiel ist der Risikozeitrahmen H4, der sich gut zwischen dem M15 und dem Tageszeitrahmen einfügt. Dies macht ihn zu einem nützlichen Kandidaten für die Messung des Marktrisikos und die Festlegung von Stop-Losses, die weder zu eng noch zu weit vom Einstieg entfernt sind.
//+------------------------------------------------------------------+ //| Update our technical indicators and positions | //+------------------------------------------------------------------+ void update(void) { //Update technical indicators and market readings CopyBuffer(ma_c_2_handle,0,0,1,ma_c_2); CopyBuffer(ma_c_1_handle,0,0,1,ma_c_1); CopyBuffer(ma_c_4_handle,0,0,1,ma_c_4); CopyBuffer(ma_c_3_handle,0,0,1,ma_c_3); bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); vector high = vector::Zeros(10); vector low = vector::Zeros(10); low.CopyRates(Symbol(),TF_3,COPY_RATES_LOW,0,10); high.CopyRates(Symbol(),TF_3,COPY_RATES_HIGH,0,10); vector var = high - low; double padding = var.Mean(); //Find an open position if(PositionsTotal() == 0) find_setup(padding); //Manage our open positions else if(PositionsTotal() > 0) manage_setup(); } //+------------------------------------------------------------------+
Um mit den Backtests beginnen zu können, müssen wir zunächst den Expertenberater auswählen, den wir gerade gemeinsam erstellt haben – den Double Crossover EX5. Danach wählen wir das EURUSD-Symbol, den einminütigen Zeitrahmen und einen Backtest-Zeitraum, der von Januar 2020 bis zu diesem Jahr reicht – ein Backtest über fünf Jahre. Um unsere Ergebnisse so realistisch wie möglich zu halten, werden wir den Test mit der Hälfte der vorliegenden Daten durchführen.

Abbildung 3: Wählen Sie unsere Handelsanwendung und unsere Schulungstermine.
Um reale Marktbedingungen zu simulieren, werden wir die Verzögerungen zufällig festlegen und echte Ticks als Modellierungsmodus verwenden. Wir erinnern daran, dass unser Optimierungsverfahren einen schnellen, genetisch basierten Algorithmus verwenden wird.

Abbildung 4: Auswahl der Bedingungen für die Marktsimulation.
Als Nächstes müssen wir die Eingabeparameter unserer Strategie auswählen, die, wie bereits erwähnt, angepasst werden müssen. Dazu gehören der Zeitraum, in dem sich der gleitende Durchschnitt kreuzt, und die Lücke sowohl auf dem höheren als auch auf dem niedrigeren Zeitrahmen. Außerdem aktivieren wir zwei Betriebsmodi für die Strategie: Trendfolge und Rückkehr zum Mittelwert. Schließlich kann die Strategie so konfiguriert werden, dass sie Positionen entweder auf der Grundlage des höheren oder des niedrigeren Zeitrahmens schließt. Alle diese Einstellungen werden von unserem genetischen Optimierer durchsucht, um uns bei der Auswahl der am besten geeigneten Parameter zu helfen.

Abbildung 5: Auswahl der Strategieparameter, die von unserem genetischen Optimierer angepasst werden sollen.
Die Backtest-Ergebnisse sind ermutigend. Wie in unseren Optimierungsergebnissen zu sehen ist, waren die meisten unserer Strategien profitabel – vor allem im Modus der Rückkehr zum Mittelwert. Betrachtet man jedoch die Ergebnisse des Vorwärtstests, so stellt man fest, dass die meisten profitablen Strategien im Trendfolgemodus und nicht, wie im Backtest beobachtet, im Modus der Rückkehr zum Mittelwert arbeiten.

Abbildung 6: Die Backtest-Ergebnisse aus unserem ersten Test.
Noch beunruhigender ist, dass bei einer genaueren Betrachtung der Ergebnisse des Vorwärtstest keine der Backtest-Strategien mit den Ergebnissen der Vorwärtstests übereinstimmt. Das heißt, fast keine der Strategien, die im Vorwärtstest gut abschnitten, waren auch im Backtest profitabel.

Abbildung 7: Die vorläufigen Ergebnisse unseres ersten Tests zeigen, dass die meisten unserer Strategien in beiden Tests nicht stabil waren.
Ich musste die Vorwärtstestergebnisse manuell filtern, um Strategieeinstellungen zu finden, die in beiden Tests profitabel waren. Dies ist ein Zeichen für Stabilität – eine Strategie sollte sowohl im Backtest als auch im Vorwärtstest profitabel sein. Die Tatsache, dass nur eine Handvoll Konfigurationen diesen Standard erfüllte, ermutigte mich, weitere Verfeinerungen zu versuchen.

Abbildung 8: Nur eine Handvoll der Strategien, die von unserem genetischen Optimierer erzeugt wurden, waren in beiden Tests profitabel. Dies ist kein guter Indikator.
Weitere Verbesserungen
Ein starkes Argument für die Verbesserung der Strategie ist die Möglichkeit für den genetischen Optimierer, den Zeitrahmen für die Berechnung der Stop-Loss- und Risikoparameter zu steuern. Außerdem überlassen wir dem Optimierer die Entscheidung, wie viele historische Balken für die Berechnung des Stop-Loss verwendet werden sollen. In unserem ersten Versuch waren wir davon ausgegangen, dass 10 H4-Balken ausreichen würden. Aber jetzt werden wir dem Optimierer erlauben, diese Einstellung anzupassen und zu testen, ob diese Änderung die Leistung verbessert. //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input group "Money Management Settings" input ENUM_TIMEFRAMES TF_3 = PERIOD_H4; //Risk Time Frame input int HISTORICAL_BARS = 10; //Historical bars for risk calculation
Wir müssen auch Änderungen am Code vornehmen. In der vorherigen Version übernahm die Aktualisierungsmethode die Berechnung der Auffüllungen für jede Position. In dieser neuen Version wird eine separate Funktion für das Auffüllen zuständig sein, da wir nun wollen, dass der Stop-Loss nachläuft und den Gewinnpositionen folgt.
//+------------------------------------------------------------------+ //| Get the stop loss size to use | //+------------------------------------------------------------------+ double get_padding(void) { vector high = vector::Zeros(10); vector low = vector::Zeros(10); low.CopyRates(Symbol(),TF_3,COPY_RATES_LOW,0,HISTORICAL_BARS); high.CopyRates(Symbol(),TF_3,COPY_RATES_HIGH,0,HISTORICAL_BARS); vector var = high - low; double padding = var.Mean(); return(padding); }
Die Aktualisierungsmethode wird entsprechend geändert. Das Padding wird nun mit der Methode get_padding berechnet. Der Teil der Aktualisierungsmethode, der ursprünglich die Positionen identifiziert hat, ruft nun eine Funktion auf, die für die Suche nach unserem Setup und eine andere Methode zur Verwaltung der offenen Positionen zuständig ist.
//+------------------------------------------------------------------+ //| Update our technical indicators and positions | //+------------------------------------------------------------------+ void update(void) { //Update technical indicators and market readings CopyBuffer(ma_c_2_handle,0,0,1,ma_c_2); CopyBuffer(ma_c_1_handle,0,0,1,ma_c_1); CopyBuffer(ma_c_4_handle,0,0,1,ma_c_4); CopyBuffer(ma_c_3_handle,0,0,1,ma_c_3); bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); double padding = get_padding(); //Find an open position if(PositionsTotal() == 0) find_setup(padding); //Manage our open positions else if(PositionsTotal() > 0) manage_setup(); } //+------------------------------------------------------------------+
Die Methode, die die Positionen verwaltet, prüft immer, ob der neue vorgeschlagene Stop-Loss-Wert profitabler ist als der aktuelle Wert. Ist dies der Fall, wird der Stop-Loss aktualisiert, andernfalls wird der aktuelle Wert beibehalten.
//+------------------------------------------------------------------+ //| Manage our open positions | //+------------------------------------------------------------------+ void manage_setup(void) { //Does the position exist? if(PositionSelect(Symbol())) { //Get the current stop loss double current_sl = PositionGetDouble(POSITION_SL); double padding = get_padding(); double new_sl; //Sell position if((state == 0)) { new_sl = (ask + padding); if(new_sl < current_sl) Trade.PositionModify(Symbol(),new_sl,0); } //Buy position if((state == 1)) { new_sl = (bid - padding); if(new_sl > current_sl) Trade.PositionModify(Symbol(),new_sl,0); } if(closing_tf == 0) { if((state ==0) && (ma_c_1[0] > ma_c_2[0])) Trade.PositionClose(Symbol()); if((state ==1) && (ma_c_1[0] < ma_c_2[0])) Trade.PositionClose(Symbol()); } else if(closing_tf == 1) { if((state ==0) && (ma_c_3[0] > ma_c_4[0])) Trade.PositionClose(Symbol()); if((state ==1) && (ma_c_3[0] < ma_c_4[0])) Trade.PositionClose(Symbol()); } } }
Zur Durchführung unserer Tests wählte ich eine Konfiguration aus, die sowohl im Backtest als auch im Vorwärtstest profitabel war, und behielt dann andere Einstellungen bei, während ich den genetischen Optimierer nach besseren Risikoparametereinstellungen suchen ließ.

Abbildung 9: Wir versuchen, unsere ersten Ergebnisse zu verbessern. Obwohl wir dem genetischen Optimierer erlaubt haben, die Risikoeinstellungen zu kontrollieren, waren unsere neuen Ergebnisse in beiden Tests noch nicht profitabel.
Leider stießen wir immer wieder auf das gleiche Problem. Und als wir unsere Risikoparametereinstellungen optimierten, war unsere Anwendung nur im Vorwärtstest profitabel und im Backtest nicht.

Abbildung 10: Unsere neuen Ergebnisse waren in beiden Tests noch nicht profitabel.
Schlussfolgerung
Wir haben bei dieser Übung viel gelernt. Bei unserer Strategie des Kreuzens zweier gleitender Durchschnitte haben wir festgestellt, dass wir den Umfang der Verzögerung in der Strategie tatsächlich steuern können – auch wenn die Folgen dieser Änderungen nicht immer sofort ersichtlich sind. Vielleicht sollten wir die Optimierung noch einmal mit allen verfügbaren Parametern gleichzeitig durchführen. Es ist möglich, dass die Anpassung eines Parameters bei gleichzeitiger Beibehaltung anderer Parameter nicht der optimale Ansatz ist. Die gleichzeitige Suche nach allen Parametern kann zu stabileren Ergebnissen führen.
In unserer weiteren Diskussion werden wir nach einer erneuten vollständigen Optimierung statistische Modelle auf der Grundlage der rentabelsten Konfigurationen erstellen. Dies kann uns helfen, die Verzögerung weiter zu verringern. Bis jetzt haben wir jedoch einen guten Start hingelegt. Denken Sie daran, dass die Optimierung keine Garantien bietet und dass KI kein Ersatz für fleißige Entwickler ist. Wir müssen unser Optimierungsverfahren so lange wiederholen, bis unser genetischer Optimierer uns Stapel von Strategien anbieten kann, die in beiden Tests rentabel sind, andernfalls ist die Optimierung wahrscheinlich verfrüht.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/18793
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.
MQL5-Handelswerkzeuge (Teil 4): Verbesserung des Dashboards des Multi-Timeframe-Scanners mit dynamischer Positionierung und Umschaltfunktionen
Vom Neuling zum Experten: Animierte Nachrichtenschlagzeilen mit MQL5 (V) – Ereignis-Erinnerungssystem
Vom Neuling zum Experten: Animierte Nachrichtenschlagzeilen mit MQL5 (VI) – Strategie von schwebenden Aufträgen für den Nachrichtenhandel
Automatisieren von Handelsstrategien in MQL5 (Teil 23): Zone Recovery mit Trailing- und Basket-Logik
- 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.