English 日本語
preview
Selbstoptimierende Expert Advisors in MQL5 (Teil 9): Kreuzen zweier gleitender Durchschnitte

Selbstoptimierende Expert Advisors in MQL5 (Teil 9): Kreuzen zweier gleitender Durchschnitte

MetaTrader 5Beispiele |
190 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

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:

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

Beigefügte Dateien |
MQL5-Handelswerkzeuge (Teil 4): Verbesserung des Dashboards des Multi-Timeframe-Scanners mit dynamischer Positionierung und Umschaltfunktionen MQL5-Handelswerkzeuge (Teil 4): Verbesserung des Dashboards des Multi-Timeframe-Scanners mit dynamischer Positionierung und Umschaltfunktionen
In diesem Artikel erweitern wir das MQL5 Multi-Timeframe Scanner Dashboard mit beweglichen und umschaltbaren Funktionen. Wir ermöglichen das Verschieben des Dashboards und eine Option zum Minimieren/Maximieren für eine bessere Bildschirmnutzung. Wir implementieren und testen diese Verbesserungen für eine verbesserte Handelsflexibilität.
Vom Neuling zum Experten: Animierte Nachrichtenschlagzeilen mit MQL5 (V) – Ereignis-Erinnerungssystem Vom Neuling zum Experten: Animierte Nachrichtenschlagzeilen mit MQL5 (V) – Ereignis-Erinnerungssystem
In dieser Diskussion werden wir weitere Fortschritte bei der Integration einer verfeinerten Logik zur Ereigniswarnung für die vom „News Headline EA“ angezeigten wirtschaftlichen Kalenderereignisse untersuchen. Diese Verbesserung ist von entscheidender Bedeutung, da sie sicherstellt, dass die Nutzer rechtzeitig vor wichtigen Ereignissen benachrichtigt werden. Nehmen Sie an dieser Diskussion teil und erfahren Sie mehr.
Vom Neuling zum Experten: Animierte Nachrichtenschlagzeilen mit MQL5 (VI) – Strategie von schwebenden Aufträgen für den Nachrichtenhandel Vom Neuling zum Experten: Animierte Nachrichtenschlagzeilen mit MQL5 (VI) – Strategie von schwebenden Aufträgen für den Nachrichtenhandel
In diesem Artikel verlagern wir den Schwerpunkt auf die Integration einer nachrichtengesteuerten Auftragsausführungslogik, die den EA in die Lage versetzt, zu handeln und nicht nur zu informieren. Begleiten Sie uns, wenn wir erforschen, wie man die automatisierte Handelsausführung in MQL5 implementiert und den News Headline EA zu einem vollständig reaktionsfähigen Handelssystem erweitert. Expert Advisors bieten den Entwicklern von Algorithmen erhebliche Vorteile, da sie eine Vielzahl von Funktionen unterstützen. Bislang haben wir uns auf die Entwicklung eines Tools zur Präsentation von Nachrichten und Kalenderereignissen konzentriert, das mit integrierten KI-Einsichten und technischen Indikatoren ausgestattet ist.
Automatisieren von Handelsstrategien in MQL5 (Teil 23): Zone Recovery mit Trailing- und Basket-Logik Automatisieren von Handelsstrategien in MQL5 (Teil 23): Zone Recovery mit Trailing- und Basket-Logik
In diesem Artikel erweitern wir unser Zone Recovery System durch die Einführung von Trailing-Stops und Multi-Basket-Handelsfunktionen. Wir untersuchen, wie die verbesserte Architektur dynamische Trailing-Stops verwendet, um Gewinne zu sichern, und ein Basket-Management-System, um mehrere Handelssignale effizient zu verarbeiten. Durch Implementierung und Backtests demonstrieren wir ein robusteres Handelssystem, das auf eine adaptive Marktperformance zugeschnitten ist.