MetaTrader 5 herunterladen

Das MQL5-Kochbuch: Abschwächen der Auswirkungen von Überanpassungen und Umgang mit mangelnden Geboten

10 Mai 2016, 10:57
Anatoli Kazharski
0
244

Einleitung

Ich glaube, viele Händler zerbrechen sich häufig den Kopf über die optimalen Parameter für ihre Handelssysteme. Tatsächlich ist ein Handelsalgorithmus allein nicht genug. Man muss festlegen, wie er weiterhin verwendet wird. Egal, was für eine Handelsstrategie Sie nutzen, ob sie einfach oder komplex ist, ein einzelnes oder mehrere Instrumente nutzt – Sie kommen nicht um die Frage herum, welche Parameter gewählt werden sollen, um zukünftige Gewinne zu sichern.

Wir prüfen gerne Handelssysteme mit Parametern, die über den Optimierungszeitraum (Backtesting) und über den nachfolgenden Zeitraum (Forward-Testing) gute Ergebnisse hervorgebracht haben. Forward-Testing ist in Wirklichkeit nicht unbedingt erforderlich. Relevante Ergebnisse lassen sich mithilfe historischer Daten abrufen.

Diese Methode führt zu einer wichtigen Frage, für die es keine endgültige Antwort gibt: Wie groß sollte die Menge der für die Optimierung eines Handelssystems genutzten historischen Daten sein? Das Problem ist, dass es zahlreiche Optionen gibt. Alles hängt vom Ausmaß der Preisschwankungen ab, von denen Sie sich einen Gewinn erwarten.

Mit der Rückkehr zur Menge der für die Optimierung erforderlichen historischen Daten schlussfolgern wir, dass die verfügbaren Daten für den stündlichen Handel ausreichen dürften. Bei längeren Timeframes trifft das nicht immer zu. Je höher die Anzahl der Wiederholungen eines gleichbleibenden Musters ist, d. h. je mehr Abschlüsse wir haben, desto wahrheitsgetreuer ist die Performance des getesteten Handelssystems, die wir in Zukunft erwarten können.

Was sollen wir tun, wenn die Preisdaten eines bestimmten Instruments nicht genügen, um eine ausreichende Anzahl von Wiederholungen zu erhalten und sich sicherer zu fühlen? Die Antwort lautet: Daten von allen verfügbaren Instrumenten verwenden.


Beispiel in NeuroShell DayTrader Professional

Bevor wir uns der Programmierung in MetaTrader 5 zuwenden, sehen wir uns ein Beispiel in NeuroShell DayTrader Professional an. Dieses Programm bietet hervorragende Funktionen für die Optimierung der Parameter eines Handelssystems (das mit einem Konstruktor kompiliert wurde) für mehrere Symbole. Sie können die erforderlichen Parameter in den Einstellungen des Handelsmoduls festlegen, Parameter separat für jedes Symbol optimieren oder einen optimalen Satz von Parametern für alle Symbole gleichzeitig finden. Diese Option finden Sie in der Registerkarte Optimization:

Registerkarte Optimization im Handelsmodul von NeuroShell DayTrader Professional

Abb. 1. Registerkarte Optimization im Handelsmodul von NeuroShell DayTrader Professional

Für unseren Anwendungsfall reicht jedes einfache Handelssystem aus, da wir nur die Ergebnisse von zwei Optimierungsmethoden vergleichen müssen, also ist die Wahl des Systems aktuell weniger wichtig.

Informationen zum Kompilieren von Handelsstrategien in NeuroShell DayTrader Professional finden Sie in anderen Beiträgen meines Blogs (Sie können die entsprechenden Informationen per Textsuche oder Tags suchen). Ich würde Ihnen auch raten, den Beitrag "Wie erstelle ich MetaTrader 5-Angebote für andere Applikationen" zu lesen, der beschreibt und vorführt, wie Sie mithilfe eines Scripts Gebote aus MetaTrader 5 in einem mit NeuroShell DayTrader Professional kompatiblen Format herunterladen können.

Für diesen Test habe ich Daten aus Tagesbalken für acht Symbole von 2000 bis Januar 2013 vorbereitet:

Symbolliste für einen Test in NeuroShell DayTrader Professional

Abb. 2. Symbolliste für einen Test in NeuroShell DayTrader Professional

Die nachfolgende Abbildung zeigt zwei Optimierungsergebnisse. Der obere Teil zeigt das Ergebnis der Optimierung an, bei der jedes Symbol seine eigenen Parameter erhält, der untere zeigt das Ergebnis, bei dem die Parameter für alle Symbole gleich sind.

Vergleich der Ergebnisse von zwei Parameter-Optimierungsmodi

Abb. 3. Vergleich der Ergebnisse von zwei Parameter-Optimierungsmodi

Das Ergebnis, das die gemeinsame Nutzung von Parametern zeigt, sieht nicht so gut aus wie das, bei dem die Parameter für jedes Symbol unterschiedlich sind. Allerdings macht es uns zuversichtlich, da das Handelssystem mit den gleichen Parametern für alle Symbole diverse Preisverhaltensmuster durchläuft (Volatilität, Menge von Trends/Flats).

Weitere Überlegungen zum selben Thema führen zu einem logischen Argument für die Optimierung mit einer größeren Menge an Daten. Es ist gut möglich, dass das Preisverhalten eines bestimmten Währungspaars, z. B. EURUSD, sich später (in zwei, fünf oder zehn Jahren) deutlich unterscheidet. Zum Beispiel können GBPUSD-Preistrends dem vergangenen Preisverhalten von EURUSD ähneln oder umgekehrt. Darauf müssen Sie vorbereitet sein, denn es gilt für jedes beliebige Instrument.


Beispiel in MetaTrader 5

Sehen wir uns an, welche Parameter-Optimierungsmodi in MetaTrader 5 zur Verfügung stehen. Unten sehen Sie den mit einem Pfeil in der Dropdown-Liste der Optimierungsmodi gekennzeichneten Optimierungsmodus All Symbols Selected in Market Watch (Alle in der Marktübersicht ausgewählten Symbole).

Abb. 4. Optimierungsmodi im MetaTrader 5 Strategietester

Abb. 4. Optimierungsmodi im MetaTrader 5 Strategietester

Dieser Modus ermöglicht Ihnen nur das Testen eines EAs mit den aktuellen Parametern auf jedem Symbol nacheinander. Die beim Testen verwendeten Symbole sind diejenigen, die aktuell in der Marktübersicht ausgewählt sind. In anderen Worten: In diesem Fall wird keine Optimierung von Parametern durchgeführt. Allerdings wird es Ihnen von MetaTrader 5 und MQL5 ermöglicht, diese Idee selbst umzusetzen.

Nun müssen wir herausfinden, wie ein solcher EA umgesetzt wird. Die Liste der Symbole wird in einer Textdatei (*.txt) erstellt. Ferner setzen wir die Möglichkeit um, mehrere Sätze von Symbollisten zu speichern. Jeder Satz wird sich in einem separaten Abschnitt befinden und seine eigene Kopfzeile mit Abschnittsnummer haben. Nummern sind erforderlich, um die Sichtprüfung zu erleichtern.

Wichtig ist, dass vor der Nummer das Zeichen # steht, damit der Expert Advisor beim Befüllen des Arrays von Symbolen die richtigen Daten erhält. Die Kopfzeile kann allgemein alle beliebigen Symbole enthalten, doch eines davon muss zu jeder Zeit das Zeichen # haben. Die Raute kann durch jedes beliebige Zeichen ersetzt werden, anhand dessen der Expert Advisor Abschnitte bestimmen/zählen wird. Die Ersetzung muss in diesem Fall im Code abgebildet sein.

Unten sehen Sie die Datei SymbolsList.txt, die drei Sätze von Symbolen für das Testing beinhaltet. Genau diese Datei wird beim Testen der Methode weiterhin genutzt.

Abb. 5. Mehrere Sätze von Symbolen in einer Textdatei für das Testing

Abb. 5. Mehrere Sätze von Symbolen in einer Textdatei für das Testing

In den externen Parametern fügen wir einen weiteren Parameter hinzu, SectionOfSymbolList, um den Satz von Symbolen zu kennzeichnen, den der Expert Advisor im aktuellen Test nutzen soll. Dieser Parameter nimmt (ab Null aufwärts) den Wert an, der den Symbolsatz definiert. Falls der Wert über der Anzahl der zur Verfügung stehenden Sätze liegt, schreibt der Expert Advisor einen entsprechenden Eintrag in den Log und das Testing wird nur auf dem aktuellen Symbol durchgeführt.

Die Datei SymbolsList.txt muss sich im lokalen Verzeichnis des Terminals unter Metatrader 5\MQL5\Files befinden. Sie kann auch im Stammverzeichnis abgelegt werden, doch in diesem Fall ist sie nicht für die Optimierung von Parametern im MQL5 Cloud Network verfügbar. Um den Zugriff auf die Datei und die relevanten benutzerdefinierten Indikatoren für das Testing zu ermöglichen, müssen wir die folgenden Zeilen am Anfang der Datei schreiben:

//--- Allow access to the external file and indicator for optimization in the cloud
#property tester_file      "SymbolsList.txt"
#property tester_indicator "EventsSpy.ex5"

Unser Expert Advisor wird auf dem vorgefertigten mehrwährungsfähigen Expert Advisor aus dem Beitrag "Das MQL5-Kochbuch: Entwicklung eines mehrwährungsfähigen Expert Advisors mit unbegrenzter Anzahl von Parametern" basieren. Seine zugrunde liegende Handelsstrategie ist ziemlich simpel, doch sie reicht aus, um die Effizienz der Methode zu testen. Wir entfernen einfach die nicht benötigten Teile, fügen hinzu, was wir brauchen, und korrigieren den bestehenden relevanten Code. Natürlich erweitern wir unseren Expert Advisor um die Funktion zum Speichern von Berichten, die im vorherigen Beitrag "Das MQL5-Kochbuch: Schreiben der Historie von Abschlüssen in eine Datei und Erstellen von Bilanzdiagrammen für jedes Symbol in Excel" ausführlich beschrieben wird. Bilanzdiagramme für alle Symbole sind auch erforderlich, um die Effizienz der betrachteten Methode zu bewerten.

Die externen Parameter des Expert Advisors müssen folgendermaßen modifiziert werden:

//--- External parameters of the Expert Advisor
sinput int    SectionOfSymbolList = 1;     // Section number in the symbol lists
sinput bool   UpdateReport        = false; // Report update
sinput string delimeter_00="";   // --------------------------------
sinput long   MagicNumber         = 777;   // Magic number
sinput int    Deviation           = 10;    // Slippage
sinput string delimeter_01="";   // --------------------------------
input  int    IndicatorPeriod     = 5;     // Indicator period
input  double TakeProfit          = 100;   // Take Profit
input  double StopLoss            = 50;    // Stop Loss
input  double TrailingStop        = 10;    // Trailing Stop
input  bool   Reverse             = true;  // Position reversal
input  double Lot                 = 0.1;   // Lot
input  double VolumeIncrease      = 0.1;   // Position volume increase
input  double VolumeIncreaseStep  = 10;    // Volume increase step

Alle mit externen Parametern in Verbindung stehenden Arrays müssen gelöscht werden, da sie nicht benötigt werden und im weiteren Verlauf im gesamten Code durch externe Variablen ersetzt werden müssen. Wir sollten nur das dynamische Array von Symbolen, InputSymbols[], übrig lassen, dessen Größe von der Anzahl der Symbole abhängt, die aus einem der Sätze in der Datei SymbolsList.txt verwendet werden. Wenn der Expert Advisor außerhalb des Strategietesters eingesetzt wird, entspricht die Größe dieses Arrays 1, da der Expert Advisor im Echtzeitmodus nur mit einem Symbol arbeitet.

Die entsprechenden Änderungen müssen auch in der Initialisierungsdatei des Arrays, InitializeArrays.mqh, vorgenommen werden. Das heißt, alle Funktionen, die Arrays von externen Variablen initialisieren, müssen gelöscht werden. Die Funktion InitializeArraySymbols() sieht nun so aus:

//+------------------------------------------------------------------+
//| Filling the array of symbols                                     |
//+------------------------------------------------------------------+
void InitializeArraySymbols()
  {
   int    strings_count  =0;   // Number of strings in the symbol file
   string checked_symbol ="";  // To check the accessibility of the symbol on the trade server
//--- Test mode message
   string message_01="<--- All symbol names in the <- SymbolsList.txt -> file are incorrect ... --->\n"
                     "<--- ... or the value of the \"Section of List Symbols\" parameter is greater, "
                     "than the number of file sections! --->\n"
                     "<--- Therefore we will test only the current symbol. --->";
//--- Real-time mode message
   string message_02="<--- In real-time mode, we only work with the current symbol. --->";
//--- If in real-time mode
   if(!IsRealtime())
     {
      //--- Get the number of strings from the specified symbol set in the file and fill the temporary array of symbols
      strings_count=ReadSymbolsFromFile("SymbolsList.txt");
      //--- Iterate over all symbols from the specified set
      for(int s=0; s<strings_count; s++)
        {
         //--- If the correct string is returned following the symbol check
         if((checked_symbol=GetSymbolByName(temporary_symbols[s]))!="")
           {
            //--- increase the counter
            SYMBOLS_COUNT++;
            //--- set/increase the array size
            ArrayResize(InputSymbols,SYMBOLS_COUNT);
            //--- index with the symbol name
            InputSymbols[SYMBOLS_COUNT-1]=checked_symbol;
           }
        }
     }
//--- If all symbol names were not input correctly or if currently working in real-time mode
   if(SYMBOLS_COUNT==0)
     {
      //--- Real-time mode message
      if(IsRealtime())
         Print(message_02);
      //--- Test mode message
      if(!IsRealtime())
         Print(message_01);
      //--- We will work with the current symbol only
      SYMBOLS_COUNT=1;
      //--- set the array size and
      ArrayResize(InputSymbols,SYMBOLS_COUNT);
      //--- index with the current symbol name
      InputSymbols[0]=_Symbol;
     }
  }

Der Code der Funktion ReadSymbolsFromFile() muss ebenfalls angepasst werden. Sie las ursprüngliche die gesamte Symbolliste. Jetzt muss sie nur noch den festgelegten Satz von Symbolen lesen. Nachfolgend sehen Sie den modifizierten Code der Funktion:

//+------------------------------------------------------------------+
//| Returning the number of strings (symbols) from the specified     |
//| set in the file and filling the temporary array of symbols       |
//+------------------------------------------------------------------+
//--- When preparing the file, symbols in the list should be separated with a line break
int ReadSymbolsFromFile(string file_name)
  {
   ulong  offset         =0;   // Offset for determining the position of the file pointer
   string delimeter      ="#"; // Identifier of the section start
   string read_line      ="";  // For the check of the read string
   int    limit_count    =0;   // Counter limiting the number of the possibly open charts
   int    strings_count  =0;   // String counter
   int    sections_count =-1;  // Section counter
   
//--- Message 01
   string message_01="<--- The <- "+file_name+" -> file has not been prepared appropriately! --->\n"
                     "<--- The first string does not contain the section number identifier ("+delimeter+")! --->";
//--- Message 02
   string message_02="<--- The <- "+file_name+" -> file has not been prepared appropriately! --->\n"
                     "<--- There is no line break identifier in the last string, --->\n"
                     "<--- so only the current symbol will be involved in testing. --->";
//--- Message 03
   string message_03="<--- The <- "+file_name+" -> file could not be found! --->"
                     "<--- Only the current symbol will be involved in testing. --->";
                     
//--- Open the file (get the handle) for reading in the local directory of the terminal
   int file_handle=FileOpen(file_name,FILE_READ|FILE_ANSI,'\n');
//--- If the file handle has been obtained
   if(file_handle!=INVALID_HANDLE)
     {
      //--- Read until the current position of the file pointer
      //    reaches the end of the file or until the program is deleted
      while(!FileIsEnding(file_handle) || !IsStopped())
        {
         //--- Read until the end of the string or until the program is deleted
         while(!FileIsLineEnding(file_handle) || !IsStopped())
           {
            //--- Read the whole string
            read_line=FileReadString(file_handle);
            //--- If the section number identifier has been found
            if(StringFind(read_line,delimeter,0)>-1)
               //--- Increase the section counter
               sections_count++;
            //--- If the section has been read, exit the function
            if(sections_count>SectionOfSymbolList)
              {
               FileClose(file_handle); // Close the file
               return(strings_count);  // Return the number of strings in the file
              }
            //--- If this is the first iteration and the first string does not contain the section number identifier
            if(limit_count==0 && sections_count==-1)
              {
               PrepareArrayForOneSymbol(strings_count,message_01);
               //--- Close the file
               FileClose(file_handle);
               //--- Return the number of strings in the file
               return(strings_count);
              }
            //--- Increase the counter limiting the number of the possibly open charts
            limit_count++;
            //--- If the limit has been reached
            if(limit_count>=CHARTS_MAX)
              {
               PrepareArrayForOneSymbol(strings_count,message_02);
               //--- Close the file
               FileClose(file_handle);
               //--- Return the number of strings in the file
               return(strings_count);
              }
            //--- Get the position of the pointer
            offset=FileTell(file_handle);
            //--- If this is the end of the string
            if(FileIsLineEnding(file_handle))
              {
               //--- Go to the next string if this is not the end of the file
               //    For this purpose, increase the offset of the file pointer
               if(!FileIsEnding(file_handle))
                  offset++;
               //--- move it to the next string
               FileSeek(file_handle,offset,SEEK_SET);
               //--- If we are not in the specified section of the file, exit the loop
               if(sections_count!=SectionOfSymbolList)
                  break;
               //--- Otherwise,
               else
                 {
                  //--- if the string is not empty
                  if(read_line!="")
                    {
                     //--- increase the string counter
                     strings_count++;
                     //--- increase the size of the array of strings,
                     ArrayResize(temporary_symbols,strings_count);
                     //--- write the string to the current index
                     temporary_symbols[strings_count-1]=read_line;
                    }
                 }
               //--- Exit the loop
               break;
              }
           }
         //--- If this is the end of the file, terminate the entire loop
         if(FileIsEnding(file_handle))
            break;
        }
      //--- Close the file
      FileClose(file_handle);
     }
   else
      PrepareArrayForOneSymbol(strings_count,message_03);
//--- Return the number of strings in the file
   return(strings_count);
  }

Sie sehen, dass einige Strings im oben aufgeführten Code markiert sind. Diese Strings enthalten die Funktion PrepareArrayForOneSymbol(), die im Fall eines Fehlers ein Array für ein (aktuelles) Symbol vorbereitet.

//+------------------------------------------------------------------+
//| Preparing an array for one symbol                                |
//+------------------------------------------------------------------+
void PrepareArrayForOneSymbol(int &strings_count,string message)
  {
//--- Print the message to the log
   Print(message);
//--- Array size
   strings_count=1;
//--- Set the size of the array of symbols
   ArrayResize(temporary_symbols,strings_count);
//--- Write the string with the current symbol name to the current index
   temporary_symbols[0]=_Symbol;
  }

Nun sind alle Vorbereitungen zum Testen der Parameter-Optimierungsmethode abgeschlossen. Doch bevor wir mit dem Testen fortfahren, fügen wir eine weitere Reihe von Daten zum Bericht hinzu. Vorher beinhaltete die Berichtdatei neben den Bilanzen aller Symbole alle Wertverluste aus lokalen Maxima als prozentualen Anteil. Nun wird der Bericht auch alle Wertverluste als Geldmengen beinhalten. Gleichzeitig modifizieren wir die Funktion CreateSymbolBalanceReport(), in der der Bericht erzeugt wird.

Der Code der Funktion CreateSymbolBalanceReport() ist nachfolgend aufgeführt:

//+------------------------------------------------------------------+
//| Creating test report on deals in .csv format                     |
//+------------------------------------------------------------------+
void CreateSymbolBalanceReport()
  {
   int    file_handle =INVALID_HANDLE; // File handle
   string path        ="";             // File path

//--- If an error occurred when creating/getting the folder, exit
   if((path=CreateInputParametersFolder())=="")
      return;
//--- Create a file to write data in the common folder of the terminal
   file_handle=FileOpen(path+"\\LastTest.csv",FILE_CSV|FILE_WRITE|FILE_ANSI|FILE_COMMON);
//--- If the handle is valid (file created/opened)
   if(file_handle>0)
     {
      int           digits           =0;   // Number of decimal places in the price
      int           deals_total      =0;   // Number of deals in the specified history
      ulong         ticket           =0;   // Deal ticket
      double        drawdown_max     =0.0; // Drawdown
      double        balance          =0.0; // Balance
      string        delimeter        =","; // Delimiter
      string        string_to_write  ="";  // To generate the string for writing
      static double percent_drawdown =0.0; // Drawdown expressed as percentage
      static double money_drawdown   =0.0; // Drawdown in monetary terms

      //--- Generate the header string
      string headers="TIME,SYMBOL,DEAL TYPE,ENTRY TYPE,VOLUME,"
                     "PRICE,SWAP($),PROFIT($),DRAWDOWN(%),DRAWDOWN($),BALANCE";
      //--- If more than one symbol is involved, modify the header string
      if(SYMBOLS_COUNT>1)
        {
         for(int s=0; s<SYMBOLS_COUNT; s++)
            StringAdd(headers,","+InputSymbols[s]);
        }
      //--- Write the report headers
      FileWrite(file_handle,headers);
      //--- Get the complete history
      HistorySelect(0,TimeCurrent());
      //--- Get the number of deals
      deals_total=HistoryDealsTotal();
      //--- Resize the array of balances according to the number of symbols
      ArrayResize(symbol_balance,SYMBOLS_COUNT);
      //--- Resize the array of deals for each symbol
      for(int s=0; s<SYMBOLS_COUNT; s++)
         ArrayResize(symbol_balance[s].balance,deals_total);
      //--- Iterate in a loop and write the data
      for(int i=0; i<deals_total; i++)
        {
         //--- Get the deal ticket
         ticket=HistoryDealGetTicket(i);
         //--- Get all the deal properties
         GetHistoryDealProperties(ticket,D_ALL);
         //--- Get the number of digits in the price
         digits=(int)SymbolInfoInteger(deal.symbol,SYMBOL_DIGITS);
         //--- Calculate the overall balance
         balance+=deal.profit+deal.swap+deal.commission;
         //--- Calculate the max drawdown from the local maximum
         TesterDrawdownMaximum(i,balance,percent_drawdown,money_drawdown);
         //--- Generate a string for writing using concatenation
         StringConcatenate(string_to_write,
                           deal.time,delimeter,
                           DealSymbolToString(deal.symbol),delimeter,
                           DealTypeToString(deal.type),delimeter,
                           DealEntryToString(deal.entry),delimeter,
                           DealVolumeToString(deal.volume),delimeter,
                           DealPriceToString(deal.price,digits),delimeter,
                           DealSwapToString(deal.swap),delimeter,
                           DealProfitToString(deal.symbol,deal.profit),delimeter,
                           DrawdownToString(percent_drawdown),delimeter,
                           DrawdownToString(money_drawdown),delimeter,
                           DoubleToString(balance,2));
         //--- If more than one symbol is involved, write their balance values
         if(SYMBOLS_COUNT>1)
           {
            //--- Iterate over all symbols
            for(int s=0; s<SYMBOLS_COUNT; s++)
              {
               //--- If the symbols are equal and the deal result is non-zero
               if(deal.symbol==InputSymbols[s] && deal.profit!=0)
                 {
                  //--- Display the deal in the balance for the corresponding symbol
                  //    Take into consideration swap and commission
                  symbol_balance[s].balance[i]=symbol_balance[s].balance[i-1]+
                                               deal.profit+
                                               deal.swap+
                                               deal.commission;
                  //--- Add to the string
                  StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],2));
                 }
               //--- Otherwise write the previous value
               else
                 {
                  //--- If the deal type is "Balance" (the first deal)
                  if(deal.type==DEAL_TYPE_BALANCE)
                    {
                     //--- the balance is the same for all symbols
                     symbol_balance[s].balance[i]=balance;
                     StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],2));
                    }
                  //--- Otherwise write the previous value to the current index
                  else
                    {
                     symbol_balance[s].balance[i]=symbol_balance[s].balance[i-1];
                     StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],2));
                    }
                 }
              }
           }
         //--- Write the generated string
         FileWrite(file_handle,string_to_write);
         //--- Mandatory zeroing out of the variable for the next string
         string_to_write="";
        }
      //--- Close the file
      FileClose(file_handle);
     }
//--- If the file could not be created/opened, print the appropriate message
   else
      Print("Error creating the file! Error: "+IntegerToString(GetLastError())+"");
  }

Wir berechneten Wertverluste bislang in der Funktion DrawdownMaximumToString(). Diese Berechnung findet nun in der Funktion TesterDrawdownMaximum() statt, während der Wert des Wertverlusts mithilfe der Basisfunktion DrawdownToString() in einen String konvertiert wird.

Der Code der Funktion TesterDrawdownMaximum() ist nachfolgend aufgeführt:

//+------------------------------------------------------------------+
//| Returning the max drawdown from the local maximum                |
//+------------------------------------------------------------------+
void TesterDrawdownMaximum(int deal_number,
                           double balance,
                           double &percent_drawdown,
                           double &money_drawdown)
  {
   ulong         ticket =0;   // Deal ticket
   string        str    ="";  // The string to be displayed in the report
//--- To calculate the local maximum and drawdown
   static double max    =0.0;
   static double min    =0.0;
   
//--- If this is the first deal
   if(deal_number==0)
     {
      //--- There is no drawdown yet
      percent_drawdown =0.0;
      money_drawdown   =0.0;
      //--- Set the initial point as the local maximum
      max=balance;
      min=balance;
     }
   else
     {
      //--- If the current balance is greater than in the memory, then...
      if(balance>max)
        {
         //--- Calculate the drawdown using the previous values:
         //    in monetary terms
         money_drawdown=max-min;
         //    expressed as percentage
         percent_drawdown=100-((min/max)*100);
         //--- Update the local maximum
         max=balance;
         min=balance;
        }
      //--- Otherwise
      else
        {
         //--- Return zero value of the drawdown
         money_drawdown=0.0;
         percent_drawdown=0.0;
         //--- Update the minimum
         min=fmin(min,balance);
         //--- If the deal ticket by its position in the list has been obtained, then...
         if((ticket=HistoryDealGetTicket(deal_number))>0)
           {
            //--- ...get the deal comment
            GetHistoryDealProperties(ticket,D_COMMENT);
            //--- Flag of the last deal
            static bool last_deal=false;
            //--- The last deal in the test can be identified by the "end of test" comment
            if(deal.comment=="end of test" && !last_deal)
              {
               //--- Set the flag
               last_deal=true;
               //--- Update the drawdown values:
               //    in monetary terms
               money_drawdown=max-min;
               //    expressed as percentage
               percent_drawdown+=100-((min/max)*100);
              }
           }
        }
     }
  }

Der Code der Funktion DrawdownToString() ist nachfolgend aufgeführt:

//+------------------------------------------------------------------+
//| Converting drawdown to a string                                  |
//+------------------------------------------------------------------+
string DrawdownToString(double drawdown)
  {
   return((drawdown<=0) ? "" : DoubleToString(drawdown,2));
  }

Nun ist alles bereit für den Test des Expert Advisors und die Analyse der Ergebnisse. Früher in diesem Beitrag haben wir ein Beispiel der vorgefertigten Datei gesehen. Gehen wir folgendermaßen vor: Optimieren Sie die Parameter für die Symbole im zweiten Satz (drei Symbole: EURUSD, AUDUSD und USDCHF) und führen Sie nach der Optimierung den Test unter Benutzung aller Symbole aus dem dritten Satz aus (insgesamt sieben Symbole), um die Ergebnisse für diejenigen Symbole zu sehen, deren Daten in der Optimierung der Parameter nicht enthalten waren.


Optimieren von Parametern und Testen des Expert Advisors

Der Strategietester muss folgendermaßen eingestellt werden:

Abb. 6. Einstellungen des Strategietesters für die Optimierung

Abb. 6. Einstellungen des Strategietesters für die Optimierung

Die Einstellungen des Expert Advisors für die Optimierung der Parameter sind nachfolgend aufgeführt:

Abb. 7. Einstellungen des Expert Advisors für die Optimierung der Parameter

Abb. 7. Einstellungen des Expert Advisors für die Optimierung der Parameter

Da die Optimierung drei Symbole beinhaltet und die Erhöhung des Positionsvolumens für jedes davon aktiviert ist, bestimmen wir das Mindestlos zum Zweck der Öffnung einer Position und der Erhöhung des Positionsvolumens. In unserem Fall beträgt der Wert 0,01.

Nach der Optimierung wählen wir das beste Ergebnis nach dem maximalen Erholungsfaktor und legen den Parameter VolumeIncrease für das Los mit 0,1 fest. Und hier haben wir das Ergebnis:

Abbildung 8. Testergebnis in MetaTrader 5

Abb. 8. Testergebnis in MetaTrader 5

Nachfolgend sehen Sie das Ergebnis, wie es in Excel 2010 dargestellt wird:

Testergebnis für drei Symbole, dargestellt in Excel 2010

Abb. 9. Testergebnis für drei Symbole, dargestellt in Excel 2010

Der Wertverlust als monetärer Wert wird im unteren Diagramm an der zweiten (Hilfs-)Skala mit grünen Markern dargestellt.

Sie müssen auch die Beschränkungen der Zeichnung von Diagrammen in Excel 2010 berücksichtigen (die vollständige Liste der Spezifikationen und Beschränkungen finden Sie auf der Seite Spezifikationen und Beschränkungen in Excel von Microsoft Office).

Abb. 10. Spezifikationen und Beschränkungen der Zeichnung von Diagrammen in Excel 2010

Abb. 10. Spezifikationen und Beschränkungen der Zeichnung von Diagrammen in Excel 2010

Die Tabelle zeigt, dass wir den Test für 255 Symbole gleichzeitig ausführen und die Ergebnisse im Diagramm anzeigen lassen können! Wir werden nur durch die Ressourcen des Computers eingeschränkt.

Führen wir den Test nun für sieben Symbole aus dem dritten Satz mit den aktuellen Parametern aus und sehen uns das Ergebnis an:

Testergebnis für sieben Symbole, dargestellt in Excel 2010

Abb. 11. Testergebnis für sieben Symbole, dargestellt in Excel 2010

Mit sieben Symbolen erhalten wir 6901 Abschlüsse. Die Daten im Diagramm werden in Excel 2010 schnell aktualisiert.


Fazit

Ich denke, die vorgestellte Methode verdient Aufmerksamkeit, da sogar eine einfache Handelsstrategie wie die von uns verwendete gute Ergebnisse aufweist. Hier sollten wir daran denken, dass die Optimierung nur für drei von sieben Symbolen durchgeführt wurde. Wir können versuchen, das Ergebnis zu verbessern, indem wir die Parameter für alle Symbole auf einmal optimieren. Allerdings sollte es unser wichtigstes Ziel sein, das Handelssystem zu verbessern oder, noch besser, ein Portfolio von verschiedenen Handelssystemen zur Verfügung zu haben. Wir kommen später auf diese Idee zurück.

Das ist alles. Wir verfügen über ein ziemlich hilfreiches Werkzeug für die Betrachtung der Ergebnisse von Mehrwährungs-Handelsstrategien. Nachfolgend finden Sie eine herunterladbare Zip-Datei mit den Dateien des Expert Advisors.

Legen Sie den Ordner ReduceOverfittingEA nach dem Entpacken der Dateien im Verzeichnis MetaTrader 5\MQL5\Experts ab. Außerdem muss der Indikator EventsSpy.mq5 im Ordner MetaTrader 5\MQL5\Indicators abgelegt werden. Die Datei SymbolsList.txt muss sich in MetaTrader 5\MQL5\Files befinden.

Übersetzt aus dem Russischen von MetaQuotes Software Corp.
Originalartikel: https://www.mql5.com/ru/articles/652

Beigefügte Dateien |
symbolslist.txt (0.1 KB)
eventsspy__1.mq5 (7.59 KB)
Das MQL5-Kochbuch: Schreiben der Historie von Abschlüssen in eine Datei und Erstellen von Bilanzdiagrammen für jedes Symbol in Excel Das MQL5-Kochbuch: Schreiben der Historie von Abschlüssen in eine Datei und Erstellen von Bilanzdiagrammen für jedes Symbol in Excel

Bei der Kommunikation in diversen Foren nutze ich oft Beispiele meiner Testergebnisse in der Darstellung in Form von Screenshots von Excel-Diagrammen. Ich werde häufig gebeten, zu erklären, wie solche Diagramme erstellt werden können. Nun habe ich endlich etwas Zeit gefunden, alles in diesem Beitrag zu erklären.

Das MQL5-Kochbuch: Entwicklung eines mehrwährungsfähigen Expert Advisors mit unbegrenzter Anzahl von Parametern Das MQL5-Kochbuch: Entwicklung eines mehrwährungsfähigen Expert Advisors mit unbegrenzter Anzahl von Parametern

In diesem Beitrag werden wir ein Muster erstellen, das einen einzelnen Satz von Parametern für die Optimierung eines Handelssystems nutzt und gleichzeitig eine unbegrenzte Anzahl von Parametern ermöglicht. Die Liste der Symbole wird in einer Standard-Textdatei (*.txt) erstellt. Die Eingabeparameter jedes Symbols werden ebenfalls in Dateien gespeichert. Auf diese Weise können wir die Terminal-seitige Begrenzung der Anzahl von Eingabeparametern eines Expert Advisors umgehen.

Multiple Regressionsanalyse. Anlegen und Prüfen von Strategien aus einer Hand Multiple Regressionsanalyse. Anlegen und Prüfen von Strategien aus einer Hand

Dieser Beitrag schildert die Anwendung der multiplen Regressionsanalyse bei der Entwicklung automatischer Handelssysteme (im Weiteren Expert-Systeme). Es werden Beispiele für ihren Einsatz bei der Automatisierung der Suche nach der richtigen Strategie sowie für eine ohne nennenswerte Vorkenntnisse in Sachen Programmierung angelegte und in ein Expert-System integrierte Regressionsgleichung.

Tipps für unerfahrene Auftraggeber Tipps für unerfahrene Auftraggeber

Eine Volksweisheit, die häufig den unterschiedlichsten Berühmtheiten zugeschrieben wird, lautet: „Nur wer nichts tut, macht keine Fehler.“ Wenn man nicht das Nichtstun selbst für einen Fehler hält, lässt sich diese Behauptung kaum bestreiten. Dagegen ist es absolut möglich, einmal begangene Fehler (eigene ebenso wie die anderer) zu analysieren, um die Anzahl zukünftiger Fehler zu minimieren. Hier wird der Versuch unternommen, mögliche Situationen auszuwerten, die bei der Arbeit mit dem Dienst „Freie Mitarbeit“ entstehen können.