English 日本語
preview
Erstellen von selbstoptimierenden Expert Advisor in MQL5 (Teil 8): Analyse mehrerer Strategien

Erstellen von selbstoptimierenden Expert Advisor in MQL5 (Teil 8): Analyse mehrerer Strategien

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

Es gibt viele Herausforderungen, mit denen wir als algorithmische Händler konfrontiert sind, die wir in dieser Serie bereits erörtert haben. Wir haben zum Beispiel festgestellt, dass es unseren statistischen Modellen leichter fällt, die zukünftigen Werte der technischen Indikatoren vorherzusagen als die zukünftigen Kursniveaus.

Wir haben auch die Vorteile eines Handelssystems untersucht, das die Beziehung zwischen der Strategie, die es verfolgt, und dem Markt, auf dem es diese Strategie anwendet, modelliert.

Unsere Modelle schnitten durchweg besser ab, wenn wir die klassische Aufgabe der direkten Preisvorhersage durch diese alternativen Aufgaben ersetzten. Direkte Preisvorhersagen sind schwierig, aber wenn wir den Rahmen des Problems ändern, können wir Modelle, die sich auf die klassische Aufgabe beschränken, übertreffen, ohne auf die gleichen statistischen Werkzeuge zurückgreifen zu müssen.

Heute werden wir eine neue potenzielle Strategie untersuchen, die auf unseren früheren Erkenntnissen aufbaut. Was wäre, wenn wir eine Anwendung erstellen, die drei verschiedene Handelsstrategien kennt? Kann diese Anwendung lernen, jeweils eine Strategie zu wählen und regelmäßig zur profitabelsten zu wechseln, anstatt alle drei gleichzeitig zu verfolgen? Wenn die Anwendung regelmäßig die Strategie wechseln kann, kann sie dann gewinnbringend die beste der drei ihr bekannten Strategien auswählen?

Eine solche Anwendung könnte nützlicher sein als ein fester Handelsalgorithmus, der alle drei Strategien oder eine Kombination aus ihnen verfolgt.

Um den Wert unseres statistischen Modells zu messen, benötigen wir zunächst eine Basisleistung, die unser Modell übertreffen sollte.

Wir werden drei unabhängige Handelsstrategien kombinieren: eine Moving Average Crossover Continuation Strategy, eine Relative Strength Index Momentum Strategy und eine Williams Percent Range Trend Breakout Strategy. Sie werden im Detail erläutert.

In diesem Artikel werden einige leistungsstarke Tools im MetaTrader 5 Terminal vorgestellt, wobei der Schwerpunkt auf dem Walk Forward Testing liegt. Vorwärtstests unterscheiden sich von Backtests, und wir werden diese Unterschiede später erklären.

Der Vorwärtstest liefert uns mehr Erkenntnisse als einfache Backtests, insbesondere in Kombination mit einem Optimierer, der neue Strategieparameter zum Testen generiert. Auf diese Weise können wir auf zuverlässige Weise profitable Einstellungen für unsere Handelsstrategie finden. MetaTrader 5 beinhaltet diese fortschrittliche Funktionalität mit seinen schneller und langsamer genetischer Optimierung.

Durch die Kombination dieser leistungsstarken Strategietest-Tools mit den zuverlässigen objektorientierten Entwurfsprinzipien, die wir in dieser Reihe behandeln, werden wir einen starken Konkurrenten entwerfen, testen und validieren, den unsere statistischen Modelle schlagen müssen.


Erste Schritte in MQL5

Diese Diskussion befasst sich mit dem Problem, wie man verschiedene Strategien am besten zu einer funktionierenden Strategie kombiniert. Hart kodierte Lösungen sind selten, insbesondere wenn mehrere Strategien gleichzeitig verwendet werden.

Die Kombination von Strategien ist spannend, weil sie Kreativität erfordert. Das bedeutet aber auch, dass wir unerwartete Nebenwirkungen minimieren müssen.

Händler wenden oft verschiedene Strategien gleichzeitig an. So kann beispielsweise eine Strategie Positionen eröffnen, während eine andere entscheidet, wann sie geschlossen wird. Jede Strategie konzentriert sich auf einen Teil des Problems. Wir wollen diesen menschlichen Ansatz nachahmen und gleichzeitig zeigen, wie man den MetaTrader 5 Strategietester verwendet, um gute Strategieeinstellungen zu finden.

Um Strategien zuverlässig zu kombinieren, werden wir jede Strategie in einer Klasse kapseln. Jede Klasse muss getestet werden, um zu beweisen, dass sie funktioniert. Eine einzige übergeordnete Klasse, „Parent“ genannt, wird die Basis für alle unsere Strategievarianten sein. Diese Klasse umfasst allgemeine Funktionen wie die Aktualisierung von Parametern und die Prüfung auf Kauf- oder Verkaufssignale.

Jede Klasse, die von der übergeordneten Klasse erbt, legt neu fest, was als Kauf- oder Verkaufssignal gilt. Dies geschieht, indem gemeinsame Methoden virtuell gemacht werden, sodass jede Strategie sie sicher außer Kraft setzen kann.

Wir haben nun genug gelernt, um mit dem Aufbau der ersten Klasse zu beginnen: der übergeordneten Strategieklasse.

In MQL5 beginnt jede Klasse mit dem Schlüsselwort class, gefolgt von dem Klassennamen. Es ist üblich, dass die Datei denselben Namen wie die Klasse trägt.

Einige Klassenmitglieder werden als virtuell gekennzeichnet, um dem Compiler mitzuteilen, dass diese Funktionen von Unterklassen außer Kraft gesetzt werden können. So kann jede Strategie selbst festlegen, wie sie diese Methoden sicher handhabt.

//+------------------------------------------------------------------+
//|                                                     Strategy.mqh |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

class Strategy
  {
private:
                     int  buffer_size;
                     bool buy;
                     bool sell;
public:
                     //--- Class constructors and destructors
                     Strategy(void);
                    ~Strategy(void);
                    
                    //--- Check if we have any valid trading signals from our strategy
                    virtual bool BuySignal(void);
                    virtual bool SellSignal(void);
                    
                    //--- Update the technical indicators in our strategy
                    virtual bool Update(void);
                    
                    //--- Get the size of the technical indicator buffers
                            int  GetIndicatorBufferSize(void);
  };

Unser Standardkonstruktor legt alle Standardwerte fest, die für alle Instanzen einer Strategie gelten.

//+------------------------------------------------------------------+
//| The only way to create an object of the class                    |
//+------------------------------------------------------------------+
Strategy::Strategy(void)
  {
      //--- Upon initialization, both flags should be false 
      buy = false;
      sell = false;
      buffer_size = 10;
  }

Wir benötigen mehrere Hilfsmethoden, die gemeinhin als Getter (Werte abrufen) und Setter (Werte setzen) bezeichnet werden. Zu Beginn werden wir eine Methode definieren, die die aktuelle Puffergröße zurückgibt, die wir für die in unserer Strategie verwendeten Indikatoren ausgewählt haben.

//+------------------------------------------------------------------+
//| The size of our indicator buffer                                 |
//+------------------------------------------------------------------+
int Strategy::GetIndicatorBufferSize(void)
   {
      int res = buffer_size;
      return(res);
   }

Außerdem muss jede Strategie über 2 Methoden verfügen, die uns jeweils informieren, ob Kauf- oder Verkaufssignale vorliegen. Jede Klasse, die von der Basisklasse abgeleitet wird, sollte die Regeln implementieren, die ihre Einträge definieren. Andernfalls wird die übergeordnete Klasse immer false zurückgeben und den Praktiker anweisen, diese Methode in der untergeordneten Klasse zu überschreiben.

//+------------------------------------------------------------------+
//| Check if our strategy is giving us any buy signals               |
//+------------------------------------------------------------------+
bool Strategy::BuySignal(void)
   {
      //--- The user is intended to overwrite the function in the child class
      //--- Otherwise, failing to do so will always return false as a safety feature
      Print("[WARNING] This function has only been implemented in the parent: ",__FUNCSIG__,"\nKindly make the necessary corrections to the child class");
      return(false);
   }
   
//+------------------------------------------------------------------+
//| Check if our strategy is giving us any sell signals              |
//+------------------------------------------------------------------+
bool Strategy::SellSignal(void)
   {
      //--- The user is intended to overwrite the function in the child class
      //--- Otherwise, failing to do so will always return false as a safety feature
      Print("[WARNING] This function has only been implemented in the parent: ",__FUNCSIG__,"\nKindly make the necessary corrections to the child class");
      return(false);
   }

Die Aktualisierung des Strategieobjekts hat zur Folge, dass alle Strategieparameter, die für den Handel verwendet werden, aktualisiert werden.

//+------------------------------------------------------------------+
//| Update our strategy parameters                                   |
//+------------------------------------------------------------------+
bool Strategy::Update(void)
   {
      //--- The user is intended to overwrite the function in the child class
      Print("[WARNING] This function has only been implemented in the parent: ",__FUNCSIG__,"\nKindly make the necessary corrections to the child class");
      return(false);
   }

Im Moment ist der Destruktor unserer Klasse leer.

//+------------------------------------------------------------------+
//| The class destructor is currently empty                          |
//+------------------------------------------------------------------+
Strategy::~Strategy(void)
  {
  }
//+------------------------------------------------------------------+

Wir beginnen mit der Definition des Körpers unserer Klasse. Die Klasse trägt den Namen „OpenCloseMACrossover“. Es handelt sich um eine Strategie, die sich auf 2 gleitende Durchschnittsindikatoren mit identischen Perioden stützt, die jeweils auf den Eröffnungs- und den Schlusskurs angewendet werden. Beachten Sie, dass die Methoden, die in der übergeordneten Klasse virtuell waren, auch in der untergeordneten Klasse virtuell sind.

Verkaufssignale werden generiert, wenn der offene gleitende Durchschnitt über dem Schlusskurs liegt. Das Gegenteil wird als Kaufsignal registriert. Wenn der durchschnittliche Schlusskurs größer ist als der durchschnittliche Eröffnungskurs, kann die Kursentwicklung als steigend angesehen werden, und ein starker Trend in diese Richtung kann anhalten.

//+------------------------------------------------------------------+
//|                                         OpenCloseMACrossover.mqh |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

#include<VolatilityDoctor\Strategies\Parent\Strategy.mqh>
#include<VolatilityDoctor\Indicators\MA.mqh>

class OpenCloseMACrossover : public Strategy
  {
private:
                     //--- Create 2 moving average instances
                     MA *ma_array[2];

public:
                     //---- Class constructors and destructor
                     OpenCloseMACrossover(string symbol,ENUM_TIMEFRAMES time_frame,int period,int shift,ENUM_MA_METHOD ma_mode);
                     ~OpenCloseMACrossover();
                     
                     //--- Class methods
                     virtual bool Update(void);
                     virtual bool BuySignal(void);
                     virtual bool SellSignal(void);
                    
  };

Wir haben bereits die Regeln der Handelsstrategie besprochen, sodass die Implementierung der Methoden, die diese Bedingungen überprüfen, eine einfache Aufgabe für uns ist.

//+------------------------------------------------------------------+
//| Check For a Buy Signal                                           |  
//+------------------------------------------------------------------+
bool OpenCloseMACrossover::BuySignal(void)
   {
      //--- Our buy signal is generated if the close moving average is above the open.
      return(ma_array[0].GetCurrentReading()>ma_array[1].GetCurrentReading());
   }

//+------------------------------------------------------------------+
//| Check For a Sell Signal                                          |  
//+------------------------------------------------------------------+
bool OpenCloseMACrossover::SellSignal(void)
   {
      //--- Our sell signal is generated if the open moving average is above the close.
      return(ma_array[0].GetCurrentReading()<ma_array[1].GetCurrentReading());
   }

Unsere Aktualisierungsmethode ruft die Indikator-Aktualisierungsfunktionen auf, die wir für unsere SingleBufferIndicator-Klasse entwickelt haben. Bei dieser Methode muss die Puffergröße als Parameter übergeben werden. Wir haben in unserer übergeordneten Klasse eine Methode erstellt, die uns die Puffergröße zurückgibt. Wir verweisen auf die übergeordnete Klasse, indem wir in unserem Aufruf „Strategy::GetIndicatorBufferSize()“ die Syntax des Doppelpunkts „::“ verwenden. Die Aktualisierungsmethode prüft abschließend, ob die aktualisierten Werte nicht 0 sind, bevor sie die Kontrolle an den Kontext zurückgibt, aus dem sie aufgerufen wurde.

//+------------------------------------------------------------------+
//| Our update method                                                |  
//+------------------------------------------------------------------+
bool OpenCloseMACrossover::Update(void)
   {
      //--- Copy indicator readings 
      //--- We will always get the buffer size from the parent class
      ma_array[0].SetIndicatorValues(Strategy::GetIndicatorBufferSize(),true);
      ma_array[1].SetIndicatorValues(Strategy::GetIndicatorBufferSize(),true);
      
      //--- Make sure neither of the indicator values equal 0
      if((ma_array[0].GetCurrentReading() * ma_array[1].GetCurrentReading()) != 0) return(true);
      
      //--- If one/both indicator values equal 0, something went wrong.
      return(false);
   }

Der Klassenkonstruktor erstellt dynamisch 2 neue Instanzen unserer gleitenden Durchschnittsindikatorobjekte und speichert ihre Zeiger in einem Array des gleichen Zeigertyps, d.h. des von uns definierten MA-Typs.

//+------------------------------------------------------------------+
//| Our class constructor                                            |
//+------------------------------------------------------------------+
OpenCloseMACrossover::OpenCloseMACrossover(string symbol,ENUM_TIMEFRAMES time_frame,int period,int shift,ENUM_MA_METHOD ma_mode)
  {
      //--- Create two instances of our moving average indiator objects
      ma_array[0] = new MA(symbol,time_frame,period,shift,ma_mode,PRICE_CLOSE);
      ma_array[1] = new MA(symbol,time_frame,period,shift,ma_mode,PRICE_OPEN);
      
      //--- Give feedback
      Print("Strategy class loaded correctly");
  }

Der Destruktor der Klasse löscht die dynamischen Objekte, die wir erstellt haben, und hilft uns, den Speicher, den wir verbrauchen, zu verwalten.

//+------------------------------------------------------------------+
//| Our class destructor                                             |
//+------------------------------------------------------------------+
OpenCloseMACrossover::~OpenCloseMACrossover()
  {
   //--- Delete the custom objects we made
   delete ma_array[0];
   delete ma_array[1];
   
   //--- Give feedback
   Print("Strategy deinitialized correctly. Goodbye");
  }
//+------------------------------------------------------------------+

Wir werden nun unsere erste Strategieklasse testen. Denken Sie daran, dass unsere endgültige Anwendung drei Klassen und drei verschiedene Strategien umfassen wird. Deshalb müssen wir als gute Entwickler jede Klasse einzeln gegen eine fest programmierte Version einer identischen Strategie testen. Der Test ist bestanden, wenn beide Strategien bei einem Backtest über denselben Zeitraum die gleichen Ergebnisse liefern. Das kann uns in Zukunft stundenlanges Suchen nach Fehlern ersparen.

Zunächst werden wir Systemkonstanten definieren, die wir in beiden Tests beibehalten werden. Wenn beide Strategien gleichwertig sind, sollten sie zu den gleichen Zeiten ausgelöst werden, die gleiche Anzahl von Positionen eröffnen und im gleichen Kauf-/Verkaufsverhältnis stehen. Die Wiederholung dieser Systemkonstanten ist ein bewusster Teil unseres Tests, da diese Konstanten die Strategieparameter steuern.

//+------------------------------------------------------------------+
//|                                                   MSA Test 1.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Define system constants                                          |
//+------------------------------------------------------------------+
#define MA_TYPE        MODE_EMA
#define MA_PERIOD      10
#define MA_TIME_FRAME  PERIOD_D1
#define MA_SHIFT       0
#define HOLDING_PERIOD 5

Als Nächstes laden wir unsere Abhängigkeiten.

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <VolatilityDoctor\Trade\TradeInfo.mqh>
#include <VolatilityDoctor\Time\Time.mqh>

Dann brauchen wir ein paar globale Variablen, um unsere technischen Indikatoren zu steuern und zu zählen, wie lange unsere Position offen ist.

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+

//--- Custom Types
CTrade    Trade;
TradeInfo *TradeInformation;
Time      *TradeTime;

//--- System Types
double ma_open[],ma_close[];
int    ma_open_handler,ma_close_handler;
intn    position_timer; 

Wenn unser System initialisiert ist, werden wir unsere technischen Indikatoren laden und überprüfen, ob sie einwandfrei sind.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Our technical indicators
   ma_close_handler = iMA(Symbol(),MA_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE,PRICE_CLOSE);
   ma_open_handler = iMA(Symbol(),MA_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE,PRICE_OPEN);

//--- Create dynamic instances of our custom types
   TradeTime        = new Time(Symbol(),MA_TIME_FRAME);
   TradeInformation = new TradeInfo(Symbol(),MA_TIME_FRAME);


//--- Safety checks
   if(ma_close_handler == INVALID_HANDLE)
      return(false);
   if(ma_open_handler == INVALID_HANDLE)
      return(false);

//--- Everything was fine
   return(INIT_SUCCEEDED);
  }
//--- End of OnInit Scope

Wenn unser System nicht mehr verwendet wird, geben wir die technischen Indikatoren frei, die wir geladen haben, und löschen die dynamischen Objekte, die wir bei der Einrichtung erstellt haben.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete the indicators and dynamic objects
   IndicatorRelease(ma_close_handler);
   IndicatorRelease(ma_open_handler);

   delete TradeTime;
   delete TradeInformation;

  }
//--- End of Deinit Scope

Wenn unser Terminal neue Kursniveaus erhält, prüfen wir zunächst, ob sich eine neue Kerze gebildet hat. Wenn dies der Fall ist, aktualisieren wir immer unsere technischen Indikatoren und prüfen dann, ob wir irgendwelche Positionen offen haben, sodass wir entweder nach einem Handelssignal suchen, wenn keine offen sind, oder bis zur Fälligkeit warten, bevor wir unsere Position schließen.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Check if a new daily candle has formed
   if(TradeTime.NewCandle())
     {

      //--- Update our technical indicators
      Update();

      //--- If we have no open positions
      if(PositionsTotal() == 0)
        {
         //--- Reset the position timer
         position_timer = 0;

         //--- Check for a trading signal
         CheckSignal();
        }

      //--- Otherwise
      else
        {
         //--- The position has reached maturity
         if(position_timer == HOLDING_PERIOD)
            Trade.PositionClose(Symbol());

         //--- Otherwise keep holding
         else
            position_timer++;
        }
     }
  }
//--- End of OnTick Scope

Unsere Aktualisierungsfunktion schreibt die aktuellen Indikatorwerte in den Indikatorpuffer und in die dafür angelegten Arrays.

//+------------------------------------------------------------------+
//| Update our technical indicators                                  |
//+------------------------------------------------------------------+
void Update(void)
  {
//--- Call the CopyBuffer method to get updated indicator values
   CopyBuffer(ma_close_handler,0,0,1,ma_close);
   CopyBuffer(ma_open_handler,0,0,1,ma_open);
  }
//--- End of Update Scope

Die Prüfsignalfunktion sucht nach den Handelsbedingungen, die wir zuvor definiert haben. Der gleitende Durchschnitt beim Schließen sollte über dem gleitenden Durchschnitt beim Öffnen liegen, damit wir die Kursbewegung als steigend einstufen können.

//+------------------------------------------------------------------+
//| Check for a trading signal using our cross-over strategy         |
//+------------------------------------------------------------------+
void CheckSignal(void)
  {
//--- Long positions when the close moving average is above the open
   if(ma_close[0] > ma_open[0])
     {
      Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,"");
      return;
     }

//--- Otherwise short
   else
      if(ma_close[0] < ma_open[0])
        {
         Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,"");
         return;
        }

  }
//--- End of CheckSignal Scope

Und schließlich sollten Sie die von Ihnen erstellten Systemvariablen am Ende Ihres Programms immer wieder zurücksetzen.

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef MA_PERIOD
#undef MA_SHIFT
#undef MA_TIME_FRAME
#undef MA_TYPE
#undef HOLDING_PERIOD
//+------------------------------------------------------------------+

Lassen Sie uns nun ein Basisniveau der Leistung für unsere Klasse festlegen. 

Abb. 1: Unsere anfänglichen Testeinstellungen für unsere Basisstufe

Der nächste Schritt besteht darin, den Start- und Endpunkt unseres Backtests festzulegen. Wir werden den täglichen Zeitrahmen auswählen, wenn Sie mitverfolgen möchten.

Abb. 2: Die Testdaten, die wir für die Basisstufe verwenden werden

Die Kapitalkurve, die von der fest kodierten Version der Strategie erzeugt wird, muss aus der von uns implementierten Klasse wiederhergestellt werden, vorausgesetzt, wir stellen beide Strategien so ein, dass sie mit denselben Parametern laufen. Lassen Sie uns nun überprüfen, ob wir die Klasse tatsächlich fehlerfrei implementiert haben. 

Abb. 3: Veranschaulichung der durch unsere Basisstrategie erstellten Kapitalkurve

Die genauen statistischen Details der beiden Tests werden nicht perfekt aufeinander abgestimmt sein. Erinnern Sie sich, dass unser Backtest zufällige Latenzzeiten und systematisches Rauschen simuliert. Daher streben wir an, dass die beiden Ergebnisse sehr nahe beieinander liegen, aber nicht identisch sind.

Abb. 4: Detaillierte Ergebnisse des Backtests, den wir mit unserer fest kodierten Version der Strategie des Kreuzens von gleitendem Durchschnitt durchgeführt haben

Die von uns definierten Systemkonstanten werden in dieser Version unseres Klassentests aus Konsistenzgründen unverändert verwendet.

//+------------------------------------------------------------------+
//|                                                   MSA Test 1.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Define system constants                                          |
//+------------------------------------------------------------------+
#define MA_TYPE        MODE_EMA
#define MA_PERIOD      10
#define MA_TIME_FRAME  PERIOD_D1
#define MA_SHIFT       0
#define HOLDING_PERIOD 5

Als Nächstes laden wir unsere Abhängigkeiten.

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <VolatilityDoctor\Time\Time.mqh>
#include <VolatilityDoctor\Trade\TradeInfo.mqh>
#include <VolatilityDoctor\Strategies\OpenCloseMACrossover.mqh>

Wir müssen eine neue globale Variable für unsere Strategieinstanz erstellen.

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+

//--- Custom Types
CTrade               Trade;
TradeInfo            *TradeInformation;
Time                 *TradeTime;
OpenCloseMACrossover *MACross;

//--- System Types
int    position_timer;

Der größte Teil der Anwendung bleibt unverändert. Wir isolieren die Auswirkungen der Signale, die von unserer Klasse erzeugt werden. Daher sollte der größte Teil dieses Codes dem Leser aus dem ersten von uns durchgeführten Test bekannt vorkommen.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Create dynamic instances of our custom types
   TradeTime        = new Time(Symbol(),MA_TIME_FRAME);
   TradeInformation = new TradeInfo(Symbol(),MA_TIME_FRAME);
   MACross          = new OpenCloseMACrossover(Symbol(),MA_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE);

//--- Everything was fine
   return(INIT_SUCCEEDED);
  }
//--- End of OnInit Scope

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete the dynamic objects
   delete TradeTime;
   delete TradeInformation;
   delete MACross;
  }
//--- End of Deinit Scope

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Check if a new daily candle has formed
   if(TradeTime.NewCandle())
     {
      //--- Update strategy
      Update();

      //--- If we have no open positions
      if(PositionsTotal() == 0)
        {
         //--- Reset the position timer
         position_timer = 0;

         //--- Check for a trading signal
         CheckSignal();
        }

      //--- Otherwise
      else
        {
         //--- The position has reached maturity
         if(position_timer == HOLDING_PERIOD)
            Trade.PositionClose(Symbol());

         //--- Otherwise keep holding
         else
            position_timer++;
        }
     }
  }
//--- End of OnTick Scope

Die Methoden, die wir definiert haben, um unsere Strategieparameter zu aktualisieren und Handelssignale zu erkennen, haben sich nur insofern geändert, als sie nun die von uns erstellte Klasse aufrufen und die Ergebnisse nicht mehr von Grund auf neu implementieren.

//+------------------------------------------------------------------+
//| Update our technical indicators                                  |
//+------------------------------------------------------------------+
void Update(void)
  {
//--- Update the strategy
   MACross.Update();
  }
//--- End of Update Scope

//+------------------------------------------------------------------+
//| Check for a trading signal using our cross-over strategy         |
//+------------------------------------------------------------------+
void CheckSignal(void)
  {
//--- Long positions when the close moving average is above the open
   if(MACross.BuySignal())
     {
      Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,"");
      return;
     }

//--- Otherwise short
   else
      if(MACross.SellSignal())
        {
         Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,"");
         return;
        }
  }
//--- End of CheckSignal Scope

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef MA_PERIOD
#undef MA_SHIFT
#undef MA_TIME_FRAME
#undef MA_TYPE
#undef HOLDING_PERIOD
//+------------------------------------------------------------------+

Wir sind nun bereit, mit dem Testen unserer Klasse mit der Strategie des MA-Kreuzens in MQL5 zu beginnen. Zunächst laden wir den Expert Advisor, der die Klasse ausführt, und richten die gleichen Backtest-Daten ein, die wir für den ersten Test verwendet haben.

Abb. 5: Die anfänglichen Daten, die wir bei der Bewertung unserer Strategie verwenden werden, werden durch unseren zukünftigen Vorwärtstest beeinflusst

Vergewissern Sie sich, dass die von Ihnen gewählten Einstellungen mit den ersten Einstellungen übereinstimmen, die wir verwendet haben.

Abb. 6: Wählen Sie „Echte Ticks“ und „Zufällige Verzögerung“ für robuste Backtests

Die detaillierten Testergebnisse der Klassen- und Hardcodestrategien sind fast identisch, beide Strategien platzierten 127 Trades, mit dem gleichen Kauf-Verkaufs-Verhältnis und erzielten Sharpe Ratios, die nahe beieinander liegen.

Abb. 7: Die detaillierten Statistiken, die von unserer Strategieklasse erstellt werden, stimmen mit den Ergebnissen überein, die wir mit der fest kodierten Handelsstrategie erhalten haben

Die von der Klasse erstellte Kapitalkurve ähnelt sehr stark Abb. 3. Die beiden Strategien stimmen also wie erwartet überein.

Abb. 8: Die von der Klasse erzeugte Kapitalkurve entspricht der fest kodierten Strategie

Jetzt können wir mit der Suche nach optimalen Strategieparametern beginnen, da wir überprüft haben, dass die Klasse korrekt implementiert wurde. 

Zunächst müssen wir die meisten Systemkonstanten in Nutzereingaben umwandeln. Dies ermöglicht unserem genetischen Optimierer, die Strategie für uns zu optimieren. Wir haben also nur eine einzige Systemdefinition.

//+------------------------------------------------------------------+
//|                                                   MSA Test 1.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define MA_SHIFT 0

//+------------------------------------------------------------------+
//| User Inputs                                                      |
//+------------------------------------------------------------------+
input   group          "Strategy Parameters"
input   int             MA_PERIOD      =        10;//Moving Average Period
input   int             HOLDING_PERIOD =         5;//Position Holding Period
input   ENUM_TIMEFRAMES MA_TIME_FRAME  = PERIOD_D1;//Moving Average Time Frame
input   ENUM_MA_METHOD  MA_TYPE        =  MODE_EMA;//Moving Average Type

Der Rest unseres Systems bleibt größtenteils unverändert, sodass wir nun den Unterschied zwischen einem Backtest und einem Forwardtest in MetaTrader 5 diskutieren wollen.

Bei einem Backtest wird eine Handelsstrategie einfach anhand historischer Daten getestet. Wir können unsere Daten über einfache historische Tests hinaus weiter nutzen. Durch die Aufteilung der Daten können wir einen Teil der Daten für die Suche nach Strategieparametern verwenden und dann die gefundenen Parameter mit dem verbleibenden Teil der Daten validieren.

Das ist die Tugend des Vorwärtstests. Wir sind nicht nur auf der Suche nach guten Einstellungen, sondern versuchen auch zu erfahren, wie stabil diese Einstellungen sind.

Setzen Sie daher den Parameter „Forward“ auf „1/2“, um 50 % Ihrer Daten für das Training und den Rest für die Tests zu verwenden.

Abb. 9: Wir setzen das Feld „Forward“ auf 1/2, um die Hälfte der Daten zum Trainieren und die andere Hälfte zum Testen zu verwenden.

Es gibt verschiedene Optimierungsstrategien, die uns in unserem MetaTrader 5 Terminal zur Verfügung stehen. Wir entscheiden uns für den „schnellen genetischen Algorithmus“, weil er unser System nicht überfordert, aber dennoch zuverlässige Ergebnisse liefert.

Abb. 10: Wir brauchen einen Optimierer, der nach jedem Test neue Strategieparameter generiert

Sobald der Test beginnt, wird ein Streudiagramm der Ergebnisse angezeigt, ähnlich wie in Abb. 11 unten. Anhand des Charts können wir sehen, wie gut wir bei jeder Trainingsiteration abgeschnitten haben. Der Optimierer sieht nur die Ergebnisse, die in der ersten Hälfte der Daten erzielt wurden, und nutzt diese, um bessere Parameter für die nächste Iteration zu lernen.

Abb. 11: Wir setzen die schnelle genetische Optimierung ein, um die Sharpe Ratio unserer Strategie zu verbessern. Jeder Punkt stellt die Ergebnisse der Testiterationen dar

Wir können mit der rechten Maustaste auf dieses Streudiagramm der Ergebnisse klicken und es auf verschiedene Weise bearbeiten. Wir können die gezeichnete Achse ändern oder das Chart sogar zu einer 3D-Darstellung unserer Ergebnisse machen.

Abb. 12: Mit dem Kontextmenü können wir die Achsen des Charts ändern und verschiedene Beziehungen untersuchen

Die Visualisierung der Daten in alternativen Formen kann uns dabei helfen, Phänomene zu beobachten, die zwischen unserer Strategie und dem von uns gewählten Markt auftreten. So zeigt sich zum Beispiel, dass unsere Strategie umso profitabler wird, je höher der Zeitrahmen ist, den wir wählen.

Abb. 13: Wir können beobachten, dass höhere Zeitrahmen uns helfen, bessere Sharpe Ratios zu erhalten, indem wir ein 3D-Balkendiagramm erstellen.

Der Strategietester liefert Ihnen auch detaillierte Ergebnisse für jede getestete Kombination von Eingaben. Beachten Sie, dass am unteren Rand der Tabelle eine Leiste vorhanden ist, die „Backtest“ und „Vorwärts“ trennt. 

Abb. 14:  MetaTrader 5 bietet uns auch eine detaillierte Analyse aller Strategieparameter, die er ausprobiert hat

Wenn Sie mit der rechten Maustaste auf diese Tabelle klicken, öffnet sich ein Kontextmenü, über das Sie viele nützliche Aufgaben ausführen können, z. B. festlegen, welche Spalten in die Tabelle aufgenommen werden sollen, oder sogar die Weiterleitungsergebnisse in eine Datei exportieren.

Abb. 15: Das Laden des Kontextmenüs auf der Ergebnistabelle zeigt uns, dass wir unsere Ergebnisse in das XML-Format für weitere Studien exportieren können

Wir können die Ergebnisse des Backtests und des Vorwärtstests getrennt betrachten. Die Ergebnisse des Backtests sind unten in Abb. 16 dargestellt. Erinnern Sie sich daran, dass wir mehr an den Vorwärts-Ergebnissen interessiert sind und daran, wie weit sie vom Backtest abgewichen sind.

Abb. 16: Detaillierte statistische Ergebnisse des Backtests, die unser genetischer Optimierer erzielte

Die Ergebnisse des Vorwärtstests ähneln stark denen des Backtests. Dies ist ein guter Indikator für potenzielle Stabilität. Andernfalls, wenn Ihre besten Ergebnisse in der Vorwärtsphase nicht mit den Ergebnissen der Backtests übereinstimmen, zeigt die Strategie instabile Eigenschaften.

Abb. 17: Dies sind die Ergebnisse, an denen wir besonders interessiert sind, nämlich die Vorwärtsergebnisse

Wir erhalten auch die Kapitalkurve, die sich aus beiden Tests ergibt. Die lange vertikale Linie in der Mitte markiert die Trennung zwischen dem Rückwärts- und dem Vorwärtstest.

Abb. 18: Beachten Sie, dass sowohl die im Training als auch die im Vorwärtstest ermittelte Kapitalkurve positive Trends aufweisen

Und schließlich sind dies die optimalen Strategieeinstellungen, die wir mit Hilfe unseres genetischen Optimierers gefunden haben.

Abb. 19: Die besten Einstellungen, die durch unseren genetischen Algorithmus ermittelt wurden


Schlussfolgerung

Dieser Artikel hat den Wert des MetaTrader 5 Strategietesters aufgezeigt. Leser, die nicht vorhaben, Klassen zu entwerfen, können dennoch praktische Anwendungsfälle für die Integration von mehr objektorientierten Programmierprinzipien (OOP), die in MQL5 eingebettet sind, in ihren Entwicklungsprozess erhalten. Dieser Artikel ermutigt die Leser auch dazu, gute Entwicklungspraktiken für die Erstellung und das Testen zuverlässiger Klassen anzuwenden.

Schließlich können die Leser durch die Verwendung der von uns hervorgehobenen Entwurfsprinzipien von OOP ihre Strategien zuverlässig aufbauen und sie leicht auf optimale Eingaben über verschiedene Symbole und Zeitrahmen hinweg testen. Nehmen Sie an unserer nächsten Diskussion teil, in der wir unsere RSI- und Gleitender-Durchschnitt-Strategien zusammenführen werden. 

Dateiname Beschreibung der Datei
MSA Test 1 Baseline.mq5 Die hartkodierte Implementierung unserer Kreuzungs-Strategie, die wir als Testergebnis verwendet haben, sollte unsere Klasse nachbilden.
MSA Test 1 Class.mq5 Die Datei, die unsere Strategieklasse des gleitenden Durchschnitts testet.
MSA Test 1.mq5 Wir haben diesen Expert Advisor verwendet, um mit dem MetaTrader 5 Strategietester nach guten Strategieparametern zu suchen.
OpenCloseMACrossover.mqh Die Klasse, die unsere Strategie des gleitenden Durchschnitts umsetzt.
Strategy.mqh Die Basisklasse für alle unsere Strategien.
MSA Test 1.ex5 Eine kompilierte Version unseres Expert Advisors.

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/18402

Beigefügte Dateien |
MSA_Test_1.mq5 (5.28 KB)
Strategy.mqh (3.98 KB)
MSA_Test_1.ex5 (44.16 KB)
Meistern der Log-Einträge (Teil 8): Fehlereinträge, die sich selbst übersetzen Meistern der Log-Einträge (Teil 8): Fehlereinträge, die sich selbst übersetzen
In diesem achten Teil der Serie Meistern der Log-Einträge untersuchen wir die Implementierung mehrsprachiger Fehlermeldungen in Logify, einer leistungsstarken Protokollierungsbibliothek für MQL5. Sie lernen, wie Sie Fehler mit Kontext strukturieren, Meldungen in mehrere Sprachen übersetzen und Protokolle dynamisch nach Schweregrad formatieren können. Und das alles in einem sauberen, erweiterbaren und produktionsreifen Design.
Entwicklung des Price Action Analysis Toolkit (Teil 30): Commodity Channel Index (CCI), Zero Line EA Entwicklung des Price Action Analysis Toolkit (Teil 30): Commodity Channel Index (CCI), Zero Line EA
Die Automatisierung der Preisaktionsanalyse ist der Weg in die Zukunft. In diesem Artikel verwenden wir den Dual CCI-Indikator, die Nulllinien-Kreuzungsstrategie, den EMA und die Kursentwicklung, um ein Tool zu entwickeln, das Handelssignale generiert und Stop-Loss- (SL) und Take-Profit-Levels (TP) unter Verwendung der ATR festlegt. Bitte lesen Sie diesen Artikel, um zu erfahren, wie wir bei der Entwicklung des „CCI Zero Line EA“ vorgehen.
Eine alternative Log-datei mit der Verwendung der HTML und CSS Eine alternative Log-datei mit der Verwendung der HTML und CSS
In diesem Artikel werden wir eine sehr einfache, aber leistungsfähige Bibliothek zur Erstellung der HTML-Dateien schreiben, dabei lernen wir auch, wie man eine ihre Darstellung einstellen kann (nach seinem Geschmack) und sehen wir, wie man es leicht in seinem Expert Advisor oder Skript hinzufügen oder verwenden kann.
MQL5-Assistenz-Techniken, die Sie kennen sollten (Teil 69): Verwendung der Muster von SAR und RVI MQL5-Assistenz-Techniken, die Sie kennen sollten (Teil 69): Verwendung der Muster von SAR und RVI
Der Parabolic-SAR (SAR) und der Relative Vigour Index (RVI) sind ein weiteres Paar von Indikatoren, die in Verbindung mit einem MQL5 Expert Advisor verwendet werden können. Auch dieses Indikatorpaar ist, wie die anderen, die wir in der Vergangenheit behandelt haben, komplementär, da der SAR den Trend definiert, während der RVI das Momentum überprüft. Wie üblich verwenden wir den MQL5-Assistenten, um das Potenzial dieser Indikatorenkombination zu ermitteln und zu testen.