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

Anatoli Kazharski | 10 Mai, 2016

Einleitung

Der im vorherigen Beitrag "Das MQL5-Kochbuch: Mehrwährungsfähiger Expert Advisor – eine einfache, saubere und schnelle Herangehensweise" betrachtete mehrwährungsfähige Expert Advisor kann äußerst hilfreich sein, wenn die Anzahl der Symbole und Parameter von Handelsstrategien gering ist. Allerdings ist die Anzahl der Eingabeparameter eines Expert Advisors in MQL5 begrenzt: Es kann nicht mehr als 1024 geben.

Und auch wenn diese Menge in den meisten Fällen ausreichen wird, ist es äußerst unpraktisch, eine so große Liste von Parametern zu nutzen. Jedes Mal, wenn eine Änderung oder Optimierung von Parametern für ein bestimmtes Symbol erforderlich ist, müssen Sie Parameter für dieses Symbol in der langen Parameterliste suchen.

Im Abschnitt Eingabeparameter in der Hilfe des Client Terminals können Sie sich mit den weiteren Einschränkungen vertraut machen.

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.

Hier muss erwähnt werden, dass der Expert Advisor im normalen Betrieb auf einem Symbol arbeiten wird, Sie ihn im Strategietester aber auf einer Vielzahl ausgewählter Symbole testen können (separat auf jedem Symbol).

Tatsächlich wäre es sogar noch praktischer, die Liste der Symbole direkt im Fenster Marktübersicht zu erstellen, da es das Speichern vorgefertigter Sätze von Symbolen ermöglicht. Wir können den Expert Advisor sogar so einrichten, dass er die Symbolliste im Fenster Marktübersicht direkt aus dem Strategietester erhält. Doch leider ist es derzeit nicht möglich, aus dem Strategietester auf die Marktübersicht zuzugreifen, also müssen wir die Liste der Symbole vorab manuell oder mithilfe eines Scripts erstellen.


Entwicklung des Expert Advisors

Der im vorherigen Beitrag "Das MQL5-Kochbuch: Mehrwährungsfähiger Expert Advisor – eine einfache, saubere und schnelle Herangehensweise" behandelte mehrwährungsfähige Expert Advisor wird als Vorlage genommen. Bestimmen wir zunächst die Eingabeparameter. Wie bereits erwähnt, lassen wir nur einen Satz von Parametern übrig. Nachfolgend sehen Sie die Liste der Eingabeparameter des Expert Advisors:

//--- Input parameters of the Expert Advisor
sinput long                      MagicNumber          =777;      // Magic number
sinput int                       Deviation            =10;       // Slippage
sinput string                    delimeter_00=""; // --------------------------------
sinput int                       SymbolNumber         =1;        // Number of the tested symbol
sinput bool                      RewriteParameters    =false;    // Rewriting parameters
sinput ENUM_INPUTS_READING_MODE  ParametersReadingMode=FILE;     // Parameter reading mode
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

Nur Parameter mit dem Modifikator input werden in eine Datei geschrieben. Ferner sollten wir näher auf die drei neuen Parameter eingehen, auf die wir nie vorher gestoßen sind.

Code der Aufzählung ENUM_INPUTS_READING_MODE:

//+------------------------------------------------------------------+
//| Input parameter reading modes                                    |
//+------------------------------------------------------------------+
enum ENUM_INPUTS_READING_MODE
  {
   FILE             = 0, // File
   INPUT_PARAMETERS = 1  // Input parameters
  };

Die Anzahl der Symbole wurde bislang durch die Konstante NUMBER_OF_SYMBOLS bestimmt. Dieser Wert hängt nun von verschiedenen Modi ab, deshalb ändern wir die Konstante zur globalen Variable SYMBOLS_COUNT:

//--- Number of traded symbols. It is calculated and depends on the testing mode and the number of symbols in the file
int SYMBOLS_COUNT=0;

Am Anfang des Codes des Expert Advisors deklarieren wir eine weitere Konstante, TESTED_PARAMETERS_COUNT, die Array-Größen und die Anzahl der Iterationen von Schleifen beim Iterieren über Eingabeparameter bestimmt:

//--- Number of tested/optimized parameters
#define TESTED_PARAMETERS_COUNT 8

Die Datei mit der Symbolliste sowie der Dateiordner mit den Parametern für jedes Symbol müssen im Stammverzeichnis des Terminals abgelegt werden, da auf dieses sowohl im normalen Betrieb des Expert Advisors als auch beim Testen zugegriffen werden kann.

Nun müssen wir Arrays vorbereiten, mit denen wir später arbeiten werden. Alle Arrays im mehrwährungsfähigen Expert Advisor aus dem vorherigen Beitrag, die wir modifizieren werden, sind statisch, d. h. die Größe ihrer Elemente ist voreingestellt. Wir müssen sie alle dynamisch machen, da die Größe nun von der Menge der in der Datei verwendeten Symbole und dem Lesemodus der Eingabeparameter abhängt. Wir fügen außerdem neue Arrays hinzu (gelb markiert), die für die Arbeit mit Dateien erforderlich sind:

//--- Arrays of input parameters
string InputSymbols[];            // Symbol names
//---
int    InputIndicatorPeriod[];    // Indicator periods
double InputTakeProfit[];         // Take Profit values
double InputStopLoss[];           // Stop Loss values
double InputTrailingStop[];       // Trailing Stop values
bool   InputReverse[];            // Values of position reversal flags
double InputLot[];                // Lot values
double InputVolumeIncrease[];     // Position volume increases
double InputVolumeIncreaseStep[]; // Volume increase steps
//--- Array of handles for indicator agents
int spy_indicator_handles[];
//--- Array of signal indicator handles
int signal_indicator_handles[];
//--- Data arrays for checking trading conditions
struct PriceData
  {
   double            value[];
  };
PriceData open[];      // Opening price of the bar
PriceData high[];      // High price of the bar
PriceData low[];       // Low price of the bar
PriceData close[];     // Closing price of the bar
PriceData indicator[]; // Array of indicator values
//--- Arrays for getting the opening time of the current bar
struct Datetime
  {
   datetime          time[];
  };
Datetime lastbar_time[];
//--- Array for checking the new bar for each symbol
datetime new_bar[];
//--- Array of input parameter names for writing to the file
string input_parameters[TESTED_PARAMETERS_COUNT]=
  {
   "IndicatorPeriod",   // Indicator period
   "TakeProfit",        // Take Profit
   "StopLoss",          // Stop Loss
   "TrailingStop",      // Trailing Stop
   "Reverse",           // Position reversal
   "Lot",               // Lot
   "VolumeIncrease",    // Position volume increase
   "VolumeIncreaseStep" // Volume increase step
  };
//--- Array for untested symbols
string temporary_symbols[];
//--- Array for storing input parameters from the file of the symbol selected for testing or trading
double tested_parameters_from_file[];
//--- Array of input parameter values for writing to the file
double tested_parameters_values[TESTED_PARAMETERS_COUNT];

Dann müssen wir die Array-Größen bestimmen und sie als Werte initialisieren.

Das oben erstellte Array von Eingabeparametern wird in der Funktion InitializeTestedParametersValues() initialisiert, die wir in der Datei InitializeArrays.mqh erstellen werden. Dieses Array wird beim Schreiben der Werte von Eingabeparametern in die Datei genutzt.

//+------------------------------------------------------------------+
//| Initializing the array of tested input parameters                |
//+------------------------------------------------------------------+
void InitializeTestedParametersValues()
  {
   tested_parameters_values[0]=IndicatorPeriod;
   tested_parameters_values[1]=TakeProfit;
   tested_parameters_values[2]=StopLoss;
   tested_parameters_values[3]=TrailingStop;
   tested_parameters_values[4]=Reverse;
   tested_parameters_values[5]=Lot;
   tested_parameters_values[6]=VolumeIncrease;
   tested_parameters_values[7]=VolumeIncreaseStep;
  }

Betrachten wir nun die Arbeit mit Dateien. Erstellen Sie zunächst eine weitere Funktionsbibliothek, FileFunctions.mqh, im Ordner UnlimitedParametersEA\Include Ihres Expert Advisors. Diese Bibliothek wird für die Erstellung von Funktionen in Bezug auf das Lesen und Schreiben von Daten in eine Datei verwendet. Fügen Sie sie in die Hauptdatei des Expert Advisors und andere Dateien des Projekts ein.

//---Include custom libraries
#include "Include\Enums.mqh"
#include "Include\InitializeArrays.mqh"
#include "Include\Errors.mqh"
#include "Include\FileFunctions.mqh"
#include "Include\TradeSignals.mqh"
#include "Include\TradeFunctions.mqh"
#include "Include\ToString.mqh"
#include "Include\Auxiliary.mqh"

Wir beginnen mit der Erstellung einer Funktion für das Lesen der Symbolliste aus einer Textdatei: ReadSymbolsFromFile(). Diese Datei (nennen wir sie TestedSymbols.txt) muss im Unterordner \Files des Stammverzeichnisses des MetaTrader 5 Client Terminals abgelegt werden. In meinem Fall wäre das C:\ProgramData\MetaQuotes\Terminal\Common, doch Sie sollten den Pfad mithilfe der Standardkonstante TERMINAL_COMMONDATA_PATH sorgfältig überprüfen:

TerminalInfoString(TERMINAL_COMMONDATA_PATH)
Alle benutzerdefinierten Dateien müssen im Unterordner \Files des Stammverzeichnisses des Client Terminals oder in <Datenorder des Terminals>\MQL5\Files abgelegt werden. Dateifunktionen können nur auf diese Ordner zugreifen.

Fügen Sie einige Symbole in der erstellten Textdatei hinzu und trennen Sie jedes der Symbole mit einem Zeilenumbruch ("\r\n"), um die Funktionalität zu testen:

Abb. 1. Symbolliste in der Datei aus dem Stammverzeichnis des Terminals.

Abb. 1. Symbolliste in der Datei aus dem Stammverzeichnis des Terminals.

Betrachten wir nun den Code der Funktion ReadSymbolsFromFile():

//+------------------------------------------------------------------+
//| Returning the number of strings (symbols) in the file and        |
//| filling the temporary array of symbols temporary_symbols[]       |
//+------------------------------------------------------------------+
//--- When preparing the file, symbols in the list should be separated with a line break
int ReadSymbolsFromFile(string file_name)
  {
   int strings_count=0; // String counter
//--- Open the file for reading from the common folder of the terminal
   int file_handle=FileOpen(file_name,FILE_READ|FILE_ANSI|FILE_COMMON);
//--- If the file handle has been obtained
   if(file_handle!=INVALID_HANDLE)
     {
      ulong  offset =0; // Offset for determining the position of the file pointer
      string text ="";  // The read string will be written to this variable
      //--- 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
            text=FileReadString(file_handle);
            //--- Get the position of the pointer
            offset=FileTell(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 the string is not empty
            if(text!="")
              {
               //--- Increase the string counter
               strings_count++;
               //--- Increase the size of the array of strings,
               ArrayResize(temporary_symbols,strings_count);
               //--- Write the read string to the current index
               temporary_symbols[strings_count-1]=text;
              }
            //--- Exit the nested loop
            break;
           }
         //--- If this is the end of the file, terminate the main loop
         if(FileIsEnding(file_handle))
            break;
        }
      //--- Close the file
      FileClose(file_handle);
     }
//--- Return the number of strings in the file
   return(strings_count);
  }

Hier wird die Textdatei Zeile für Zeile gelesen. Der Name jedes gelesenen Finanzinstruments wird in das früher erstellte temporäre Array temporary_symbols[] geschrieben (die tatsächliche Verfügbarkeit des Symbols auf dem Handelsserver wird später geprüft). Ferner gibt die Funktion am Ende die Anzahl der gelesenen Strings aus, d. h. die Anzahl der Symbole, auf denen unser Expert Advisor getestet wird.

Detaillierte Informationen über Funktionen und Identifikatoren, die Sie vorher nie gesehen haben, finden Sie immer im Nachschlagewerk MQL5. Die im Code enthaltenen Kommentare erleichtern den Lernprozess.

Kehren wir zur Datei InitializeArrays.mqh zurück, in der wir die Funktion InitializeInputSymbols() zum Ausfüllen des vorher deklarierten Arrays von Symbolnamen InputSymbols[] erstellen. Neueinsteiger finden sie vielleicht etwas schwierig, deshalb habe ich detaillierte Kommentare im Code eingefügt:

//+------------------------------------------------------------------+
//| Filling the InputSymbol[] array of symbols                       |
//+------------------------------------------------------------------+
void InitializeInputSymbols()
  {
   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
//--- Get the number of symbols from the "TestedSymbols.txt" file
   strings_count=ReadSymbolsFromFile("TestedSymbols.txt");
//--- In optimization mode or in one of the two modes (testing or visualization), provided that the symbol NUMBER IS SPECIFIED
   if(IsOptimization() || ((IsTester() || IsVisualMode()) && SymbolNumber>0))
     {
      //--- Determine the symbol to be involved in parameter optimization
      for(int s=0; s<strings_count; s++)
        {
         //--- If the number specified in the parameters and the current loop index match
         if(s==SymbolNumber-1)
           {
            //--- Check whether the symbol is on the trade server
            if((checked_symbol=GetSymbolByName(temporary_symbols[s]))!="")
              {
               //--- Set the number of symbols
               SYMBOLS_COUNT=1;
               //--- Set the size of the array of symbols
               ArrayResize(InputSymbols,SYMBOLS_COUNT);
               //--- Write the symbol name
               InputSymbols[0]=checked_symbol;
              }
            //--- Exit
            return;
           }
        }
     }
//--- In testing or visualization mode, if you need to test ALL symbols from the list in the file
   if((IsTester() || IsVisualMode()) && SymbolNumber==0)
     {
      //--- Parameter reading mode: from the file
      if(ParametersReadingMode==FILE)
        {
         //--- Iterate over all symbols in the file
         for(int s=0; s<strings_count; s++)
           {
            //--- Check if the symbol is on the trade server
            if((checked_symbol=GetSymbolByName(temporary_symbols[s]))!="")
              {
               //--- Increase the symbol counter
               SYMBOLS_COUNT++;
               //--- Set the size of the array of symbols
               ArrayResize(InputSymbols,SYMBOLS_COUNT);
               //--- Write the symbol name
               InputSymbols[SYMBOLS_COUNT-1]=checked_symbol;
              }
           }
         //--- Exit
         return;
        }
      //--- Parameter reading mode: from input parameters of the Expert Advisor
      if(ParametersReadingMode==INPUT_PARAMETERS)
        {
         //--- Set the number of symbols
         SYMBOLS_COUNT=1;
         //--- Set the size of the array of symbols
         ArrayResize(InputSymbols,SYMBOLS_COUNT);
         //--- Write the current symbol name
         InputSymbols[0]=Symbol();
         //--- Exit
         return;
        }
     }
//--- In normal operation mode of the Expert Advisor, use the current chart symbol
   if(IsRealtime())
     {
      //--- Set the number of symbols
      SYMBOLS_COUNT=1;
      //--- Set the size of the array of symbols
      ArrayResize(InputSymbols,SYMBOLS_COUNT);
      //--- Write the symbol name
      InputSymbols[0]=Symbol();
     }
//---
  }

Sobald die Größe des Arrays von Eingabeparameter durch die Anzahl der verwendeten Symbole bestimmt wurde, müssen Sie die Größe aller anderen Arrays von Eingabeparametern festlegen. Setzen wir dies in einer separaten Funktion um: ResizeInputParametersArrays():

//+------------------------------------------------------------------+
//| Setting the new size for arrays of input parameters              |
//+------------------------------------------------------------------+
void ResizeInputParametersArrays()
  {
   ArrayResize(InputIndicatorPeriod,SYMBOLS_COUNT);
   ArrayResize(InputTakeProfit,SYMBOLS_COUNT);
   ArrayResize(InputStopLoss,SYMBOLS_COUNT);
   ArrayResize(InputTrailingStop,SYMBOLS_COUNT);
   ArrayResize(InputReverse,SYMBOLS_COUNT);
   ArrayResize(InputLot,SYMBOLS_COUNT);
   ArrayResize(InputVolumeIncrease,SYMBOLS_COUNT);
   ArrayResize(InputVolumeIncreaseStep,SYMBOLS_COUNT);
  }

Nun müssen wir eine Funktionalität erstellen, die es uns ermöglicht, Werte von Eingabeparametern für jedes Symbol zu lesen und diese Parameterwerte abhängig vom ausgewählten Eingabemodus und den Einstellungen des Expert Advisors in eine separate Datei (pro Symbol) zu schreiben. Dies ist ähnlich wie das Lesen der Symbolliste aus der Datei. Es ist eine komplexe Aufgabe, deshalb teilen wir sie in mehrere Vorgänge auf.

Als Erstes müssen wir lernen, Werte von Eingabeparametern aus einer Datei in ein Array zu lesen. Das Array wird Werte des Typen double beinhalten. Später konvertieren wir einige von ihnen (Zeitraum des Indikators und Flag für die Umkehr von Positionen) in int- bzw. bool-Typen. Das Handle der offenen Datei und das Array, in dem die Parameterwerte aus der Datei gespeichert sind, werden an die Funktion ReadInputParametersValuesFromFile() übergeben:

//+----------------------------------------------------------------------+
//| Reading parameters from the file and storing them in the passed array|
//| Text file format: key=value
//+----------------------------------------------------------------------+
bool ReadInputParametersValuesFromFile(int handle,double &array[])
  {
   int    delimiter_position  =0;  // Number of the symbol position "=" in the string
   int    strings_count       =0;  // String counter
   string read_string         =""; // The read string
   ulong  offset              =0;  // Position of the pointer (offset in bytes)
//--- Move the file pointer to the beginning of the file
   FileSeek(handle,0,SEEK_SET);
//--- Read until the current position of the file pointer reaches the end of the file
   while(!FileIsEnding(handle))
     {
      //--- If the user deleted the program
      if(IsStopped())
         return(false);
      //--- Read until the end of the string
      while(!FileIsLineEnding(handle))
        {
         //--- If the user deleted the program
         if(IsStopped())
            return(false);
         //--- Read the string
         read_string=FileReadString(handle);
         //--- Get the index of the separator ("=") in the read string
         delimiter_position=StringFind(read_string,"=",0);
         //--- Get everything that follows the separator ("=") until the end of the string
         read_string=StringSubstr(read_string,delimiter_position+1);
         //--- Place the obtained value converted to the double type in the output array
         array[strings_count]=StringToDouble(read_string);
         //--- Get the current position of the file pointer
         offset=FileTell(handle);
         //--- If it's the end of the string
         if(FileIsLineEnding(handle))
           {
            //--- Go to the next string if it's not the end of the file
            if(!FileIsEnding(handle))
               //--- Increase the offset of the file pointer by 1 to go to the next string
               offset++;
            //--- Move the file pointer relative to the beginning of the file
            FileSeek(handle,offset,SEEK_SET);
            //--- Increase the string counter
            strings_count++;
            //--- Exit the nested loop for reading the string
            break;
           }
        }
      //--- If it's the end of the file, exit the main loop
      if(FileIsEnding(handle))
         break;
     }
//--- Return the fact of successful completion
   return(true);
  }

Welche Datei-Handles und Arrays übergeben wir an diese Funktion? Das hängt davon ab, mit welchen Symbolen wir arbeiten werden, nachdem wir sie aus der Datei "TestedSymbols.txt" gelesen haben. Jedes Symbol entspricht einer bestimmten Textdatei, die Werte von Eingabeparametern beinhaltet. Es müssen zwei Szenarios berücksichtigt werden:

  1. Die Datei existiert und wir lesen Werte von Eingabeparametern aus dieser Datei mithilfe der oben beschriebenen Funktion ReadInputParametersValuesFromFile().
  2. Die Datei existiert nicht oder wir müssen die existierenden Werte von Eingabeparametern neu schreiben.

Das Format der Textdatei (eine .ini-Datei, wobei Sie jede beliebige andere Erweiterung wählen können, die Sie als nötig betrachten), die Werte von Eingabeparametern beinhaltet, ist einfach:

input_parameter_name1=value
input_parameter_name2=value
....
input_parameter_nameN=value

Kombinieren wir diese Logik in einer Funktion, ReadWriteInputParameters(), deren Code Sie nachfolgend sehen:

//+------------------------------------------------------------------+
//| Reading/writing input parameters from/to a file for a symbol     |
//+------------------------------------------------------------------+
void ReadWriteInputParameters(int symbol_number,string path)
  {
   string file_name=path+InputSymbols[symbol_number]+".ini"; // File name
//---
   Print("Find the file '"+file_name+"' ...");
//--- Open the file with input parameters of the symbol
   int file_handle_read=FileOpen(file_name,FILE_READ|FILE_ANSI|FILE_COMMON);
   
//--- Scenario #1: the file exists and parameter values do not need to be rewritten
   if(file_handle_read!=INVALID_HANDLE && !RewriteParameters)
     {
      Print("The file '"+InputSymbols[symbol_number]+".ini' exists, reading...");
      //--- Set the array size
      ArrayResize(tested_parameters_from_file,TESTED_PARAMETERS_COUNT);
      //--- Fill the array with file values
      ReadInputParametersValuesFromFile(file_handle_read,tested_parameters_from_file);
      //--- If the array size is correct
      if(ArraySize(tested_parameters_from_file)==TESTED_PARAMETERS_COUNT)
        {
         //--- Write parameter values to arrays
         InputIndicatorPeriod[symbol_number]    =(int)tested_parameters_from_file[0];
         Print("InputIndicatorPeriod[symbol_number] = "+(string)InputIndicatorPeriod[symbol_number]);
         InputTakeProfit[symbol_number]         =tested_parameters_from_file[1];
         InputStopLoss[symbol_number]           =tested_parameters_from_file[2];
         InputTrailingStop[symbol_number]       =tested_parameters_from_file[3];
         InputReverse[symbol_number]            =(bool)tested_parameters_from_file[4];
         InputLot[symbol_number]                =tested_parameters_from_file[5];
         InputVolumeIncrease[symbol_number]     =tested_parameters_from_file[6];
         InputVolumeIncreaseStep[symbol_number] =tested_parameters_from_file[7];
        }
      //--- Close the file and exit
      FileClose(file_handle_read);
      return;
     }
//--- Scenario #2: If the file does not exist or the parameters need to be rewritten
   if(file_handle_read==INVALID_HANDLE || RewriteParameters)
     {
      //--- Close the handle of the file for reading
      FileClose(file_handle_read);
      //--- Get the handle of the file for writing
      int file_handle_write=FileOpen(file_name,FILE_WRITE|FILE_CSV|FILE_ANSI|FILE_COMMON,"");
      //--- If the handle has been obtained
      if(file_handle_write!=INVALID_HANDLE)
        {
         string delimiter="="; // Separator
         //--- Write the parameters
         for(int i=0; i<TESTED_PARAMETERS_COUNT; i++)
           {
            FileWrite(file_handle_write,input_parameters_names[i],delimiter,tested_parameters_values[i]);
            Print(input_parameters_names[i],delimiter,tested_parameters_values[i]);
           }
         //--- Write parameter values to arrays
         InputIndicatorPeriod[symbol_number]    =(int)tested_parameters_values[0];
         InputTakeProfit[symbol_number]         =tested_parameters_values[1];
         InputStopLoss[symbol_number]           =tested_parameters_values[2];
         InputTrailingStop[symbol_number]       =tested_parameters_values[3];
         InputReverse[symbol_number]            =(bool)tested_parameters_values[4];
         InputLot[symbol_number]                =tested_parameters_values[5];
         InputVolumeIncrease[symbol_number]     =tested_parameters_values[6];
         InputVolumeIncreaseStep[symbol_number] =tested_parameters_values[7];
         //--- Depending on the indication, print the relevant message
         if(RewriteParameters)
            Print("The file '"+InputSymbols[symbol_number]+".ini' with parameters of the '"+EXPERT_NAME+".ex5 Expert Advisor has been rewritten'");
         else
            Print("The file '"+InputSymbols[symbol_number]+".ini' with parameters of the '"+EXPERT_NAME+".ex5 Expert Advisor has been created'");
        }
      //--- Close the handle of the file for writing
      FileClose(file_handle_write);
     }
  }

Die letzte Dateifunktion, CreateInputParametersFolder(), erstellt einen Ordner mit dem Namen des Expert Advisors im Stammverzeichnis des Client Terminals. Das ist der Ordner, aus dem Textdateien (in unserem Fall .ini-Dateien) mit Werten von Eingabeparametern gelesen/geschrieben werden. Wie in der vorherigen Funktion prüfen wir, ob der Ordner existiert. Wenn der Ordner erfolgreich erstellt wurde oder bereits existiert, gibt die Funktion den Pfad bzw., im Fall eines Fehlers, einen leeren String aus:

//+----------------------------------------------------------------------------------+
//| Creating a folder for files of input parameters in case the folder does not exist|
//| and returns the path in case of success                                          |
//+----------------------------------------------------------------------------------+
string CreateInputParametersFolder()
  {
   long   search_handle       =INVALID_HANDLE;   // Folder/file search handle
   string EA_root_folder      =EXPERT_NAME+"\\"; // Root folder of the Expert Advisor
   string returned_filename   ="";               // Name of the found object (file/folder)
   string search_path         ="";               // Search path
   string folder_filter       ="*";              // Search filter (* - check all files/folders)
   bool   is_root_folder      =false;            // Flag of existence/absence of the root folder of the Expert Advisor

//--- Find the root folder of the Expert Advisor
   search_path=folder_filter;
//--- Set the search handle in the common folder of the terminal
   search_handle=FileFindFirst(search_path,returned_filename,FILE_COMMON);
//--- If the first folder is the root folder, flag it
   if(returned_filename==EA_root_folder)
      is_root_folder=true;
//--- If the search handle has been obtained
   if(search_handle!=INVALID_HANDLE)
     {
      //--- If the first folder is not the root folder
      if(!is_root_folder)
        {
         //--- Iterate over all files to find the root folder
         while(FileFindNext(search_handle,returned_filename))
           {
            //--- Process terminated by the user
            if(IsStopped())
               return("");
            //--- If it is found, flag it
            if(returned_filename==EA_root_folder)
              {
               is_root_folder=true;
               break;
              }
           }
        }
      //--- Close the root folder search handle
      FileFindClose(search_handle);
      //search_handle=INVALID_HANDLE;
     }
//--- Otherwise print an error message
   else
      Print("Error when getting the search handle or "
            "the folder '"+TerminalInfoString(TERMINAL_COMMONDATA_PATH)+"' is empty: ",ErrorDescription(GetLastError()));

//--- Based on the check results, create the necessary folder
   search_path=EXPERT_NAME+"\\";
//--- If the root folder of the Expert Advisor does not exist
   if(!is_root_folder)
     {
      //--- Create it. 
      if(FolderCreate(EXPERT_NAME,FILE_COMMON))
        {
         //--- If the folder has been created, flag it
         is_root_folder=true;
         Print("The root folder of the '..\\"+EXPERT_NAME+"\\ Expert Advisor has been created'");
        }
      else
        {
         Print("Error when creating "
               "the root folder of the Expert Advisor: ",ErrorDescription(GetLastError()));
         return("");
        }
     }
//--- If the required folder exists
   if(is_root_folder)
      //--- Return the path to create a file for writing parameters of the Expert Advisor
      return(search_path+"\\");
//--- In case of errors, return an empty string
   return("");
  }

Vereinen wir nun die vorhergehenden Funktionsaufrufe in einer einzigen Funktion: InitializeInputParametersArrays(). Diese Funktion umfasst 4 Optionen der Initialisierung von Eingabeparametern beim Arbeiten mit dem Expert Advisor:

  1. Standardmodus (oder Parameteroptimierung für ein ausgewähltes Symbol) mithilfe aktueller Werte von Eingabeparametern
  2. Neuschreiben von Parametern in Dateien beim Testen oder Optimieren
  3. Testen eines ausgewählten Symbols
  4. Testen aller Symbole aus der Liste in der Datei

Alle Vorgänge werden in detaillierten Kommentaren zum Code erklärt:

//+-------------------------------------------------------------------+
//| Initializing arrays of input parameters depending on the mode     |
//+-------------------------------------------------------------------+
void InitializeInputParametersArrays()
  {
   string path=""; // To determine the folder that contains files with input parameters
//--- Mode #1 :
//    - standard operation mode of the Expert Advisor OR
//    - optimization mode OR
//    - reading from input parameters of the Expert Advisor without rewriting the file
   if(IsRealtime() || IsOptimization() || (ParametersReadingMode==INPUT_PARAMETERS && !RewriteParameters))
     {
      //--- Initialize parameter arrays to current values
      InitializeWithCurrentValues();
      return;
     }
//--- Mode #2 :
//    - rewriting parameters in the file for the specified symbol
   if(RewriteParameters)
     {
      //--- Initialize parameter arrays to current values
      InitializeWithCurrentValues();
      //--- If the folder of the Expert Advisor exists or in case no errors occurred when it was being created
      if((path=CreateInputParametersFolder())!="")
         //--- Write/read the file of symbol parameters
         ReadWriteInputParameters(0,path);
      //---
      return;
     }
//--- Mode #3 :
//    - testing (it may be in visualization mode, without optimization) the Expert Advisor on a SELECTED symbol
   if((IsTester() || IsVisualMode()) && !IsOptimization() && SymbolNumber>0)
     {
      //--- If the folder of the Expert Advisor exists or in case no errors occurred when it was being created
      if((path=CreateInputParametersFolder())!="")
        {
         //--- Iterate over all symbols (in this case, the number of symbols = 1)
         for(int s=0; s<SYMBOLS_COUNT; s++)
            //--- Write or read the file of symbol parameters
            ReadWriteInputParameters(s,path);
        }
      return;
     }
//--- Mode #4 :
//    - testing (it may be in visualization mode, without optimization) the Expert Advisor on ALL symbols
   if((IsTester() || IsVisualMode()) && !IsOptimization() && SymbolNumber==0)
     {
      //--- If the folder of the Expert Advisor exists and
      //    no errors occurred when it was being created
      if((path=CreateInputParametersFolder())!="")
        {
         //--- Iterate over all symbols
         for(int s=0; s<SYMBOLS_COUNT; s++)
            //--- Write or read the file of symbol parameters
            ReadWriteInputParameters(s,path);
        }
      return;
     }
  }

In den Modi #1 und #2 nutzen wir die Funktion InitializeWithCurrentValues(). Sie initialisiert den (einzigen) Nullindex mit aktuellen Werten der Eingabeparameter. In anderen Worten: Diese Funktion wird genutzt, wenn nur ein Symbol erforderlich ist:

//+------------------------------------------------------------------+
//| Initializing arrays of input parameters to current values        |
//+------------------------------------------------------------------+
void InitializeWithCurrentValues()
  {
   InputIndicatorPeriod[0]=IndicatorPeriod;
   InputTakeProfit[0]=TakeProfit;
   InputStopLoss[0]=StopLoss;
   InputTrailingStop[0]=TrailingStop;
   InputReverse[0]=Reverse;
   InputLot[0]=Lot;
   InputVolumeIncrease[0]=VolumeIncrease;
   InputVolumeIncreaseStep[0]=VolumeIncreaseStep;
  }

Nun steht das Einfachste, aber gleichzeitig Wichtigste an: die Umsetzung aufeinanderfolgender Aufrufe der oben aufgeführten Funktionen ab dem Eintrittspunkt, der Funktion OnInit():

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
void OnInit()
  {
//--- Initialize the array of tested input parameters for writing to the file
   InitializeTestedParametersValues();
//--- Fill the array of symbol names
   InitializeInputSymbols();
//--- Set the size of arrays of input parameters
   ResizeInputParametersArrays();
//--- Initialize arrays of indicator handles
   InitializeIndicatorHandlesArrays();
//--- Initialize arrays of input parameters depending on the operation mode of the Expert Advisor
   InitializeInputParametersArrays();
//--- Get agent handles from the "EventsSpy.ex5" indicator
   GetSpyHandles();
//--- Get indicator handles
   GetIndicatorHandles();
//--- Initialize the new bar
   InitializeNewBarArray();
//--- Initialize price arrays and indicator buffers
   ResizeDataArrays();
  }

Nun sind wir mit dem Code fertig. Sie können sich mithilfe der an den Beitrag angehängten Dateien mit den beschriebenen Funktionen vertraut machen. Sie enthalten nichts Kompliziertes. Fahren wir nun fort und sehen uns an, welches Ergebnis wir bekommen und wie es genutzt werden kann.


Optimieren von Parametern und Testen des Expert Advisors

Wie bereits erwähnt, muss die Funktion TestedSymbols.txt mit der Symbolliste im Stammverzeichnis des Client Terminals liegen. Als Beispiel/zu Testzwecken erstellen wir eine Liste von drei Symbolen: AUDUSD, EURUSD und NZDUSD. Wir werden nun nacheinander die Eingabeparameter separat für jedes Symbol optimieren. Der Strategietester muss folgendermaßen eingestellt werden:

Abb. 2. Einstellungen des Strategietesters.

Abb. 2. Einstellungen des Strategietesters.

Sie können jedes beliebige Symbol (in unserem Fall EURUSD) in der Registerkarte "Settings" (Einstellungen) einrichten, da es sich nicht auf den Expert Advisor auswirkt. Anschließend wählen wir Parameter für die Optimierung des Expert Advisors aus:

Abb. 3. Eingabeparameter des Expert Advisors.

Abb. 3. Eingabeparameter des Expert Advisors.

Die obige Abbildung zeigt, dass der Parameter SymbolNumber (Nummer des getesteten Symbols) 1 ist. Das bedeutet, dass wir bei der Optimierung des Expert Advisors das erste Symbol aus der Liste in der Datei TestedSymbols.txt nutzen werden. In unserem Fall ist es AUDUSD.

Hinweis: Aufgrund der Besonderheiten dieses Expert Advisors (die Symbolliste wird durch Lesen aus der Textdatei festgelegt) ist keine Optimierung durch Remote Agents möglich.

Wir werden in einem der folgenden Beiträge dieser Serie versuchen, diese Einschränkung zu umgehen.

Nach dem Abschluss der Optimierung können Sie Tests ausführen und die Ergebnisse der verschiedenen Optimierungsdurchläufe studieren. Wenn Sie möchten, dass der Expert Advisor Parameter aus der Datei liest, müssen Sie in der Dropdown-Liste des Parameters ParametersReadingMode (Parameter-Lesemodus) den Eintrag File auswählen. Um die aktuellen Parameter des Expert Advisors (Einstellung in der Registerkarte "Settings") nutzen zu können, müssen Sie die Option Eingabeparameter wählen.

Die Option Eingabeparameter wird natürlich zum Betrachten der Optimierungsergebnisse benötigt. Wenn Sie den Test zum ersten Mal ausführen, erstellt der Expert Advisor einen Ordner mit dem entsprechenden Namen im Stammverzeichnis des Terminals. Der erstellte Ordner enthält eine Datei mit den aktuellen Parametern des getesteten Symbols. In unserem Fall ist es AUDUSD.ini. Sie sehen den Inhalt dieser Datei in der nachfolgenden Abbildung:

Abb. 4. Liste von Eingabeparametern in der Datei des Symbols.

Abb. 4. Liste von Eingabeparametern in der Datei des Symbols.

Wenn die erforderliche Kombination von Parametern gefunden wurde, müssen Sie true im Parameter RewriteParameters (Neuschreiben von Parametern) festlegen und den Test erneut ausführen. Die Parameterdatei wird aktualisiert. Anschließend können Sie wieder false festlegen und andere Ergebnisse von Optimierungsdurchläufen ansehen. Es ist auch praktisch, in die Datei geschriebene Ergebnisse nach Werten mit solchen Ergebnissen zu vergleichen, die durch einfaches Umschalten zwischen den Optionen des Parameters Parameter-Lesemodus in den Eingabeparametern festgelegt werden.

Anschließend führen wir die Optimierung für EURUSD aus, das zweite Symbol auf der Liste in der Datei. Dazu müssen wir den Wert des Parameters Nummer des getesteten Symbols auf 2 stellen. Nach der Optimierung sowie der Bestimmung der Parameter und dem Schreiben der Parameter in die Datei muss der gleiche Vorgang für das dritte Symbol auf der Liste durchgeführt werden.

Sobald die Parameter für alle Symbole in die Datei geschrieben wurden, können Sie entweder die Ergebnisse für jedes Symbol separat betrachten, indem Sie die Symbolnummer angeben, oder die gesammelten Ergebnisse für alle Symbole, indem Sie die Nummer des getesteten Symbols auf 0 stellen. Ich habe das folgende kumulative Ergebnis für alle Symbole erhalten:

Abb. 5. Kumulatives Ergebnis des mehrwährungsfähigen Expert Advisors.

Abb. 5. Kumulatives Ergebnis des mehrwährungsfähigen Expert Advisors.


Fazit

Wir haben ein ziemlich praktisches Muster für mehrwährungsfähige Expert Advisors erhalten. Falls nötig, lässt es sich noch weiter entwickeln. Im Anhang des Beitrags finden Sie ein Archiv mit den Dateien des Expert Advisors. Legen Sie den Ordner UnlimitedParametersEA nach dem Entpacken unter <Ordner des MetaTrader 5 Terminals>\MQL5\Experts ab. Der Indikator EventsSpy.mq5 muss in <Ordner des MetaTrader 5 Terminals>\MQL5\Indicators abgelegt werden. Vergessen Sie auch nicht, die Textdatei TestedSymbols.txt im Stammverzeichnis des Client Terminals zu erstellen.