
Das MQL5-Kochbuch: Entwicklung eines mehrwährungsfähigen Expert Advisors mit unbegrenzter Anzahl von Parametern
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.
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.
- SymbolNumber – dieser Parameter kennzeichnet die Nummer des Symbols aus der Datei, die die Liste von Symbolen enthält. Falls er auf 0 gesetzt ist, werden alle Symbole aus der Liste getestet. Falls Sie über die Grenzen der Liste hinausgehen, wird kein Test durchgeführt.
- RewriteParameters – wenn der Wert dieses Parameters true ist, wird die Datei mit den Parametern des angegebenen Symbols (Nummer im Parameter SymbolNumber) mithilfe der aktuellen Werte des Eingabeparameters neu geschrieben. Falls der Wert false ist, werden Parameter aus einer Datei gelesen.
- ParametersReadingMode – dieser Parameter kennzeichnet den Lesemodus in Bezug auf Eingabeparameter. Der Parametertyp ist die benutzerdefinierte Aufzählung ENUM_INPUTS_READING_MODE, die zwei Optionen anbietet: Lesen aus einer Datei oder Verwendung aktueller Parameter.
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)
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.
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.
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:
- Die Datei existiert und wir lesen Werte von Eingabeparametern aus dieser Datei mithilfe der oben beschriebenen Funktion ReadInputParametersValuesFromFile().
- 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:
- Standardmodus (oder Parameteroptimierung für ein ausgewähltes Symbol) mithilfe aktueller Werte von Eingabeparametern
- Neuschreiben von Parametern in Dateien beim Testen oder Optimieren
- Testen eines ausgewählten Symbols
- 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.
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.
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.
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.
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.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/650





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.