Das MQL5-Kochbuch: Speichern der Optimierungsergebnisse eines Expert Advisors auf Basis bestimmter Kriterien
Einleitung
Wir setzen die Serie der Beiträge zur MQL5-Programmierung fort. Diesmal sehen wir uns an, wie man bei der Optimierung der Parameter eines Expert Advisors Ergebnisse erhält. Mit der Umsetzung wird sichergestellt, dass die Werte des entsprechenden Durchlaufs in eine Datei geschrieben werden, wenn eine bestimmte in den externen Parametern festgelegte Bedingung erfüllt wird. Neben Testwerten speichern wir auch die Parameter, die zu diesen Ergebnissen geführt haben.
Entwicklung
Zur Umsetzung der Idee verwenden wir den vorgefertigten Expert Advisor mit einfachem Handelsalgorithmus aus dem Beitrag "Das MQL5-Kochbuch: Wie vermeidet man Fehler beim Einstellen/Modifizieren von Handelsebenen" und fügen einfach alle erforderlichen Funktionen hinzu. Der Quellcode wurde mithilfe der in den letzten Beiträgen dieser Serie angewendeten Herangehensweise vorbereitet. Also sind alle Funktionen in verschiedenen Dateien sortiert und in der Hauptdatei des Projekts enthalten. Sie sehen, wie Dateien in das Projekt eingebunden werden können, im Beitrag "Das MQL5-Kochbuch: Verwendung von Indikatoren zum Festlegen von Handelsbedingungen in Expert Advisors".
Um während der Optimierung Zugriff auf die Daten zu erhalten, können Sie spezielle MQL5-Funktionen nutzen: OnTesterInit(), OnTester(), OnTesterPass() und OnTesterDeinit(). Sehen wir uns jede von ihnen kurz an:
- OnTesterInit() – diese Funktion wird zum Bestimmen des Startpunkts der Optimierung verwendet.
- OnTester() – diese Funktion fügt nach jedem Optimierungsdurchlauf sogenannte Frames hinzu. Die Definition von Frames folgt im weiteren Verlauf des Beitrags.
- OnTesterPass() – diese Funktion empfängt Frames nach jedem Optimierungsdurchlauf.
- OnTesterDeinit() – diese Funktion erzeugt das Ereignis des Endes der Optimierung der Parameter des Expert Advisors.
Nun müssen wir definieren, was ein Frame ist. Ein Frame ist eine Art Datenstruktur eines einzelnen Optimierungsdurchlaufs. Während der Optimierung werden Frames im *.mqd-Archiv im Ordner MetaTrader 5\MQL5\Files\Tester gespeichert. Der Zugriff auf die Daten (Frames) dieses Archivs ist sowohl spontan während der Optimierung als auch nach der Optimierung möglich. Beispielsweise illustriert der Beitrag "Veranschaulichung einer Strategie im Prüfprogramm von MetaTrader 5", wie wir den Prozess der Optimierung ohne Vorbereitung visualisieren und anschließend das Ergebnis der Optimierung betrachten können.
In diesem Beitrag nutzen wir die folgenden Funktionen für die Arbeit mit Frames:
- FrameAdd() – fügt Daten aus einer Datei oder einem Array hinzu.
- FrameNext() – Aufruf zum Erhalten eines einzelnen numerischen Werts oder der gesamten Daten des Frames.
- FrameInputs() – ruft Eingabeparameter ab, auf deren Basis ein bestimmter Frame mit der angegebenen Durchlaufnummer geformt wird.
Weitere Informationen über die oben aufgeführten Funktionen finden Sie im Nachschlagewerk MQL5. Wir fangen wie immer mit den externen Parametern an. Unten sehen Sie, welche Parameter zu den bereits vorhandenen hinzugefügt werden müssen:
//--- External parameters of the Expert Advisor input int NumberOfBars = 2; // Number of one-direction bars sinput double Lot = 0.1; // Lot input double TakeProfit = 100; // Take Profit input double StopLoss = 50; // Stop Loss input double TrailingStop = 10; // Trailing Stop input bool Reverse = true; // Position reversal sinput string delimeter=""; // -------------------------------- sinput bool LogOptimizationReport = true; // Writing results to a file sinput CRITERION_RULE CriterionSelectionRule = RULE_AND; // Condition for writing sinput ENUM_STATS Criterion_01 = C_NO_CRITERION; // 01 - Criterion name sinput double CriterionValue_01 = 0; // ---- Criterion value sinput ENUM_STATS Criterion_02 = C_NO_CRITERION; // 02 - Criterion name sinput double CriterionValue_02 = 0; // ---- Criterion value sinput ENUM_STATS Criterion_03 = C_NO_CRITERION; // 03 - Criterion name sinput double CriterionValue_03 = 0; // ---- Criterion value
Der Parameter LogOptimizationReport zeigt, ob die Ergebnisse und Parameter während der Optimierung in eine Datei geschrieben werden sollten.
In diesem Beispiel implementieren wir eine Möglichkeit, bis zu drei Kriterien anzugeben, anhand derer die in eine Datei zu schreibenden Ergebnisse ausgewählt werden. Außerdem fügen wir eine Regel hinzu (Parameter CriterionSelectionRule), in der Sie festlegen können, ob die Ergebnisse geschrieben werden, wenn entweder alle angegebenen Bedingungen erfüllt sind (UND) oder mindestens eine von ihnen (ODER). Zu diesem Zweck erstellen wir eine Aufzählung in der Datei Enums.mqh:
//--- Rules for checking criteria enum CRITERION_RULE { RULE_AND = 0, // AND RULE_OR = 1 // OR };
Die Hauptparameter des Tests werden als Kriterien verwendet. Hier benötigen wir eine weitere Aufzählung:
//--- Statistical parameters enum ENUM_STATS { C_NO_CRITERION = 0, // No criterion C_STAT_PROFIT = 1, // Profit C_STAT_DEALS = 2, // Total deals C_STAT_PROFIT_FACTOR = 3, // Profit factor C_STAT_EXPECTED_PAYOFF = 4, // Expected payoff C_STAT_EQUITY_DDREL_PERCENT = 5, // Max. equity drawdown, % C_STAT_RECOVERY_FACTOR = 6, // Recovery factor C_STAT_SHARPE_RATIO = 7 // Sharpe ratio };
Jeder Parameter wird auf die Überschreitung des in den externen Parameters angegebenen Werts geprüft. Davon ausgenommen ist der maximale Wertverlust des Eigenkapitals, da die Auswahl auf Basis des minimalen Wertverlustes getroffen werden muss.
Wir müssen auch einige globale Variablen hinzufügen (siehe nachfolgender Code):
//--- Global variables int AllowedNumberOfBars=0; // For checking the NumberOfBars external parameter value string OptimizationResultsPath=""; // Location of the folder for saving folders and files int UsedCriteriaCount=0; // Number of used criteria int OptimizationFileHandle=-1; // Handle of the file of optimization results
Außerdem werden die folgenden Arrays benötigt:
int criteria[3]; // Criteria for optimization report generation double criteria_values[3]; // Values of criteria double stat_values[STAT_VALUES_COUNT]; // Array for testing parameters
Die Hauptdatei des Expert Advisors muss um Funktionen für die Verarbeitung von Ereignissen des Strategietesters erweitert werden, die am Anfang dieses Beitrags beschrieben wurden:
//+--------------------------------------------------------------------+ //| Optimization start | //+--------------------------------------------------------------------+ void OnTesterInit() { Print(__FUNCTION__,"(): Start Optimization \n-----------"); } //+--------------------------------------------------------------------+ //| Test completion event handler | //+--------------------------------------------------------------------+ double OnTester() { //--- If writing of optimization results is enabled if(LogOptimizationReport) //--- //--- return(0.0); } //+--------------------------------------------------------------------+ //| Next optimization pass | //+--------------------------------------------------------------------+ void OnTesterPass() { //--- If writing of optimization results is enabled if(LogOptimizationReport) //--- } //+--------------------------------------------------------------------+ //| End of optimization | //+--------------------------------------------------------------------+ void OnTesterDeinit() { Print("-----------\n",__FUNCTION__,"(): End Optimization"); //--- If writing of optimization results is enabled if(LogOptimizationReport) //--- }
Wenn wir die Optimierung jetzt starten, erscheint das Diagramm mit dem Symbol und Timeframe, auf denen der Expert Advisor ausgeführt wird, im Terminal. Nachrichten aus den im oben aufgeführten Code verwendeten Funktionen werden im Logbuch des Terminals anstelle des Logbuchs des Strategietesters gedruckt. Eine Nachricht der Funktion OnTesterInit() wird gleich am Anfang der Optimierung gedruckt. Während der Optimierung und nach ihrem Abschluss werden Sie allerdings keine Nachrichten im Logbuch sehen können. Wenn Sie das durch den Strategietester geöffnete Diagramm nach der Optimierung löschen, wird eine Nachricht der Funktion OnTesterDeinit() im Logbuch gedruckt. Warum?
Das Problem ist, dass die Funktion OnTester() die Funktion FrameAdd() nutzen muss, um einen Frame hinzuzufügen, wie unten abgebildet, um einen korrekten Betrieb sicherzustellen.
//+--------------------------------------------------------------------+ //| Test completion event handler | //+--------------------------------------------------------------------+ double OnTester() { //--- If writing of optimization results is enabled if(LogOptimizationReport) { //--- Create a frame FrameAdd("Statistics",1,0,stat_values); } //--- return(0.0); }
Jetzt wird während der Optimierung eine Nachricht der Funktion OnTesterPass() nach jedem Optimierungsdurchlauf in das Logbuch gedruckt und die Nachricht über den Abschluss der Optimierung wird am Ende der Optimierung durch die Funktion OnTesterDeinit() hinzugefügt. Die Nachricht über den Abschluss der Optimierung wird auch dann erzeugt, wenn die Optimierung manuell angehalten wird.
Abb. 1 – Ins Logbuch gedruckte Nachrichten aus Test- und Optimierungsfunktionen
Nun ist alles bereit und wir können mit den Funktionen zum Erstellen von Ordnern und Dateien, der Bestimmung festgelegter Optimierungsparameter und dem Schreiben der Ergebnisse, die die Bedingungen erfüllen, fortfahren.
Erstellen wir die Datei FileFunctions.mqh und fügen sie ins Projekt ein. Am Anfang der Datei schreiben wir die Funktion GetTestStatistics(), die per Verweis ein Array zum Ausfüllen jedes Optimierungsdurchlaufs mit Werten erhält.
//+--------------------------------------------------------------------+ //| Filling the array with test results | //+--------------------------------------------------------------------+ void GetTestStatistics(double &stat_array[]) { //--- Auxiliary variables for value adjustment double profit_factor=0,sharpe_ratio=0; //--- stat_array[0]=TesterStatistics(STAT_PROFIT); // Net profit upon completion of testing stat_array[1]=TesterStatistics(STAT_DEALS); // Number of executed deals //--- profit_factor=TesterStatistics(STAT_PROFIT_FACTOR); // Profit factor – the STAT_GROSS_PROFIT/STAT_GROSS_LOSS ratio stat_array[2]=(profit_factor==DBL_MAX) ? 0 : profit_factor; // adjust if necessary //--- stat_array[3]=TesterStatistics(STAT_EXPECTED_PAYOFF); // Expected payoff stat_array[4]=TesterStatistics(STAT_EQUITY_DDREL_PERCENT); // Max. equity drawdown, % stat_array[5]=TesterStatistics(STAT_RECOVERY_FACTOR); // Recovery factor – the STAT_PROFIT/STAT_BALANCE_DD ratio //--- sharpe_ratio=TesterStatistics(STAT_SHARPE_RATIO); // Sharpe ratio - investment portfolio (asset) efficiency index stat_array[6]=(sharpe_ratio==DBL_MAX) ? 0 : sharpe_ratio; // adjust if necessary }
Die Funktion GetTestStatistics() muss vor der Einfügung eines Frames eingefügt werden:
//+--------------------------------------------------------------------+ //| Test completion event handler | //+--------------------------------------------------------------------+ double OnTester() { //--- If writing of optimization results is enabled if(LogOptimizationReport) { //--- Fill the array with test values GetTestStatistics(stat_values); //--- Create a frame FrameAdd("Statistics",1,0,stat_values); } //--- return(0.0); }
Das ausgefüllte Array wird als letztes Argument an die Funktion FrameAdd() übergeben. Falls erforderlich, können Sie sogar eine Datendatei übergeben.
In der Funktion OnTesterPass() können wir die erhaltenen Daten nun prüfen. Um zu sehen, wie das funktioniert, zeigen wir zunächst einfach den Gewinn aus jedem Ergebnis im Logbuch des Terminals an. Nutzen Sie die Funktion FrameNext(), um die Werte des aktuellen Frames zu erhalten. Siehe folgendes Beispiel:
//+--------------------------------------------------------------------+ //| Next optimization pass | //+--------------------------------------------------------------------+ void OnTesterPass() { //--- If writing of optimization results is enabled if(LogOptimizationReport) { string name =""; // Public name/frame label ulong pass =0; // Number of the optimization pass at which the frame is added long id =0; // Public id of the frame double val =0.0; // Single numerical value of the frame //--- FrameNext(pass,name,id,val,stat_values); //--- Print(__FUNCTION__,"(): pass: "+IntegerToString(pass)+"; STAT_PROFIT: ",DoubleToString(stat_values[0],2)); } }
Wenn Sie die Funktion FrameNext() nicht nutzen, sind die Werte im Array stat_values gleich Null. Wenn alles richtig gemacht wird, erhalten wir das Ergebnis, das im nachfolgenden Screenshot abgebildet ist:
Abb. 2 – Ins Logbuch gedruckte Nachrichten der Funktion OnTesterPass()
Übrigens, wenn die Optimierung durchgeführt wird, ohne die externen Parameter zu modifizieren, werden die Ergebnisse aus dem Zwischenspeicher in den Strategietester geladen und umgehen die Funktionen OnTesterPass() und OnTesterDeinit(). Denken Sie daran, um nicht zu dem Trugschluss zu kommen, dass es einen Fehler gibt.
In der Datei FileFunctions.mqh erstellen wir die Funktion CreateOptimizationReport(). Die wichtigsten Aktivitäten finden in dieser Funktion statt. Der Code der Funktion ist nachfolgend aufgeführt:
//+--------------------------------------------------------------------+ //| Generating and writing report on optimization results | //+--------------------------------------------------------------------+ void CreateOptimizationReport() { static int passes_count=0; // Pass counter int parameters_count=0; // Number of Expert Advisor parameters int optimized_parameters_count=0; // Counter of optimized parameters string string_to_write=""; // String for writing bool include_criteria_list=false; // For determining the start of the list of parameters/criteria int equality_sign_index=0; // The '=' sign index in the string string name =""; // Public name/frame label ulong pass =0; // Number of the optimization pass at which the frame is added long id =0; // Public id of the frame double value =0.0; // Single numerical value of the frame string parameters_list[]; // List of the Expert Advisor parameters of the "parameterN=valueN" form string parameter_names[]; // Array of parameter names string parameter_values[]; // Array of parameter values //--- Increase the pass counter passes_count++; //--- Place statistical values into the array FrameNext(pass,name,id,value,stat_values); //--- Get the pass number, list of parameters, number of parameters FrameInputs(pass,parameters_list,parameters_count); //--- Iterate over the list of parameters in a loop (starting from the upper one on the list) // The list starts with the parameters that are flagged for optimization for(int i=0; i<parameters_count; i++) { //--- Get the criteria for selection of results at the first pass if(passes_count==1) { string current_value=""; // Current parameter value static int c=0,v=0,trigger=0; // Counters and trigger //--- Set a flag if you reached the list of criteria if(StringFind(parameters_list[i],"CriterionSelectionRule",0)>=0) { include_criteria_list=true; continue; } //--- At the last parameter, count the used criteria, // if the AND mode is selected if(CriterionSelectionRule==RULE_AND && i==parameters_count-1) CalculateUsedCriteria(); //--- If you reached criteria in the parameter list if(include_criteria_list) { //--- Determine names of criteria if(trigger==0) { equality_sign_index=StringFind(parameters_list[i],"=",0)+1; // Determine the '=' sign position in the string current_value =StringSubstr(parameters_list[i],equality_sign_index); // Get the parameter value //--- criteria[c]=(int)StringToInteger(current_value); trigger=1; // Next parameter will be a value c++; continue; } //--- Determine values of criteria if(trigger==1) { equality_sign_index=StringFind(parameters_list[i],"=",0)+1; // Determine the '=' sign position in the string current_value=StringSubstr(parameters_list[i],equality_sign_index); // Get the parameter value //--- criteria_values[v]=StringToDouble(current_value); trigger=0; // Next parameter will be a criterion v++; continue; } } } //--- If the parameter is enabled for optimization if(ParameterEnabledForOptimization(parameters_list[i])) { //--- Increase the counter of the optimized parameters optimized_parameters_count++; //--- Write the names of the optimized parameters to the array // only at the first pass (for headers) if(passes_count==1) { //--- Increase the size of the array of parameter values ArrayResize(parameter_names,optimized_parameters_count); //--- Determine the '=' sign position equality_sign_index=StringFind(parameters_list[i],"=",0); //--- Take the parameter name parameter_names[i]=StringSubstr(parameters_list[i],0,equality_sign_index); } //--- Increase the size of the array of parameter values ArrayResize(parameter_values,optimized_parameters_count); //--- Determine the '=' sign position equality_sign_index=StringFind(parameters_list[i],"=",0)+1; //--- Take the parameter value parameter_values[i]=StringSubstr(parameters_list[i],equality_sign_index); } } //--- Generate a string of values to the optimized parameters for(int i=0; i<STAT_VALUES_COUNT; i++) StringAdd(string_to_write,DoubleToString(stat_values[i],2)+","); //--- Add values of the optimized parameters to the string of values for(int i=0; i<optimized_parameters_count; i++) { //--- If it is the last value in the string, do not use the separator if(i==optimized_parameters_count-1) { StringAdd(string_to_write,parameter_values[i]); break; } //--- Otherwise use the separator else StringAdd(string_to_write,parameter_values[i]+","); } //--- At the first pass, generate the optimization report file with headers if(passes_count==1) WriteOptimizationReport(parameter_names); //--- Write data to the file of optimization results WriteOptimizationResults(string_to_write); }
Wir haben eine ziemlich große Funktion erhalten. Sehen wir sie uns näher an. Am Anfang, gleich nach der Deklarierung der Variablen und Arrays, erhalten wir mithilfe der Funktion FrameNext() die Daten des Frames, wie es in den weiter oben aufgeführten Beispielen demonstriert wird. Dann erhalten wir mithilfe der Funktion FrameInputs() die Liste der Parameter im String-Array parameters_list[] zusammen mit der Gesamtmenge der Parameter, die an die Variable parameters_count übergeben wird.
Die optimierten Parameter (im Strategietester mit Flags gekennzeichnet) in der Parameterliste aus der Funktion FrameInputs() befinden sich gleich am Anfang, unabhängig von ihrer Reihenfolge in der Liste der externen Parameter des Expert Advisors.
Darauf folgt eine Schleife, die über die Parameterliste iteriert. Das Kriterien-Array criteria[] und das Array der Werte von Kriterien criteria_values[] werden beim ersten Durchlauf befüllt. Die verwendeten Kriterien werden in der Funktion CalculateUsedCriteria() gezählt, wenn der Modus AND aktiv ist und der aktuelle Parameter der letzte ist:
//+--------------------------------------------------------------------+ //| Counting the number of used criteria | //+--------------------------------------------------------------------+ void CalculateUsedCriteria() { UsedCriteriaCount=0; // Zeroing out //--- Iterate over the list of criteria in a loop for(int i=0; i<ArraySize(criteria); i++) { //--- count the used criteria if(criteria[i]!=C_NO_CRITERION) UsedCriteriaCount++; } }
In derselben Schleife prüfen wir im weiteren Verlauf, ob irgendein Parameter für die Optimierung ausgewählt ist. Diese Prüfung wird bei jedem Durchlauf durchgeführt und wird durch die Funktion ParameterEnabledForOptimization() ausgelöst, an die der aktuelle externe Parameter zur Prüfung übergeben wird. Wenn die Funktion true ausgibt, wird der Parameter optimiert.
//+---------------------------------------------------------------------+ //| Checking whether the external parameter is enabled for optimization | //+---------------------------------------------------------------------+ bool ParameterEnabledForOptimization(string parameter_string) { bool enable; long value,start,step,stop; //--- Determine the '=' sign position in the string int equality_sign_index=StringFind(parameter_string,"=",0); //--- Get the parameter values ParameterGetRange(StringSubstr(parameter_string,0,equality_sign_index), enable,value,start,step,stop); //--- Return the parameter status return(enable); }
In diesem Fall werden die Arrays für Namen (parameter_names) und Parameterwerte (parameter_values) ausgefüllt. Das Array für Namen optimierter Parameter wird nur beim ersten Durchlauf befüllt.
Anschließend erzeugen wir mithilfe zweier Schleifen den String von Test- und Parameterwerten zum Schreiben in eine Datei. Dann wird die Datei zum Schreiben mithilfe der Funktion WriteOptimizationReport() beim ersten Durchlauf erzeugt.
//+--------------------------------------------------------------------+ //| Generating the optimization report file | //+--------------------------------------------------------------------+ void WriteOptimizationReport(string ¶meter_names[]) { int files_count =1; // Counter of optimization files //--- Generate a header to the optimized parameters string headers="#,PROFIT,TOTAL DEALS,PROFIT FACTOR,EXPECTED PAYOFF,EQUITY DD MAX REL%,RECOVERY FACTOR,SHARPE RATIO,"; //--- Add the optimized parameters to the header for(int i=0; i<ArraySize(parameter_names); i++) { if(i==ArraySize(parameter_names)-1) StringAdd(headers,parameter_names[i]); else StringAdd(headers,parameter_names[i]+","); } //--- Get the location for the optimization file and the number of files for the index number OptimizationResultsPath=CreateOptimizationResultsFolder(files_count); //--- If there is an error when getting the folder, exit if(OptimizationResultsPath=="") { Print("Empty path: ",OptimizationResultsPath); return; } else { OptimizationFileHandle=FileOpen(OptimizationResultsPath+"\optimization_results"+IntegerToString(files_count)+".csv", FILE_CSV|FILE_READ|FILE_WRITE|FILE_ANSI|FILE_COMMON,","); //--- if(OptimizationFileHandle!=INVALID_HANDLE) FileWrite(OptimizationFileHandle,headers); } }
Der Zweck der Funktion WriteOptimizationReport() ist die Erzeugung von Kopfzeilen, die bedarfsgemäße Erstellung von Ordnern im Stammverzeichnis des Terminals sowie die Erstellung einer Datei zum Schreiben. Das heißt, Dateien, die mit vorhergehenden Optimierungen zusammenhängen, werden nicht entfernt und die Funktion erstellt jedes Mal eine neue Datei mit der Indexnummer. Kopfzeilen werden in einer neu erstellen Datei gespeichert. Die Datei selbst bleibt bis zum Ende der Optimierung geöffnet.
Der oben aufgeführte Code enthält den String mit der Funktion CreateOptimizationResultsFolder(), in der Ordner zum Speichern von Dateien mit Optimierungsergebnissen gespeichert werden:
//+--------------------------------------------------------------------+ //| Creating folders for optimization results | //+--------------------------------------------------------------------+ string CreateOptimizationResultsFolder(int &files_count) { long search_handle =INVALID_HANDLE; // Search handle string returned_filename =""; // Name of the found object (file/folder) string path =""; // File/folder search location string search_filter ="*"; // Search filter (* - check all files/folders) string root_folder ="OPTIMIZATION_DATA\\"; // Root folder string expert_folder =EXPERT_NAME+"\\"; // Folder of the Expert Advisor bool root_folder_exists =false; // Flag of existence of the root folder bool expert_folder_exists=false; // Flag of existence of the Expert Advisor folder //--- Search for the OPTIMIZATION_DATA root folder in the common folder of the terminal path=search_filter; //--- Set the search handle in the common folder of all client terminals \Files search_handle=FileFindFirst(path,returned_filename,FILE_COMMON); //--- Print the location of the common folder of the terminal to the journal Print("TERMINAL_COMMONDATA_PATH: ",COMMONDATA_PATH); //--- If the first folder is the root folder, flag it if(returned_filename==root_folder) { root_folder_exists=true; Print("The "+root_folder+" root folder exists."); } //--- If the search handle has been obtained if(search_handle!=INVALID_HANDLE) { //--- If the first folder is not the root folder if(!root_folder_exists) { //--- Iterate over all files to find the root folder while(FileFindNext(search_handle,returned_filename)) { //--- If it is found, flag it if(returned_filename==root_folder) { root_folder_exists=true; Print("The "+root_folder+" root folder exists."); break; } } } //--- Close the root folder search handle FileFindClose(search_handle); } else { Print("Error when getting the search handle " "or the "+COMMONDATA_PATH+" folder is empty: ",ErrorDescription(GetLastError())); } //--- Search for the Expert Advisor folder in the OPTIMIZATION_DATA folder path=root_folder+search_filter; //--- Set the search handle in the ..\Files\OPTIMIZATION_DATA\ folder search_handle=FileFindFirst(path,returned_filename,FILE_COMMON); //--- If the first folder is the folder of the Expert Advisor if(returned_filename==expert_folder) { expert_folder_exists=true; // Remember this Print("The "+expert_folder+" Expert Advisor folder exists."); } //--- If the search handle has been obtained if(search_handle!=INVALID_HANDLE) { //--- If the first folder is not the folder of the Expert Advisor if(!expert_folder_exists) { //--- Iterate over all files in the DATA_OPTIMIZATION folder to find the folder of the Expert Advisor while(FileFindNext(search_handle,returned_filename)) { //--- If it is found, flag it if(returned_filename==expert_folder) { expert_folder_exists=true; Print("The "+expert_folder+" Expert Advisor folder exists."); break; } } } //--- Close the root folder search handle FileFindClose(search_handle); } else Print("Error when getting the search handle or the "+path+" folder is empty."); //--- Generate the path to count the files path=root_folder+expert_folder+search_filter; //--- Set the search handle in the ..\Files\OPTIMIZATION_DATA\ folder of optimization results search_handle=FileFindFirst(path,returned_filename,FILE_COMMON); //--- If the folder is not empty, start the count if(StringFind(returned_filename,"optimization_results",0)>=0) files_count++; //--- If the search handle has been obtained if(search_handle!=INVALID_HANDLE) { //--- Count all files in the Expert Advisor folder while(FileFindNext(search_handle,returned_filename)) files_count++; //--- Print("Total files: ",files_count); //--- Close the Expert Advisor folder search handle FileFindClose(search_handle); } else Print("Error when getting the search handle or the "+path+" folder is empty"); //--- Create the necessary folders based on the check results // If there is no OPTIMIZATION_DATA root folder if(!root_folder_exists) { if(FolderCreate("OPTIMIZATION_DATA",FILE_COMMON)) { root_folder_exists=true; Print("The root folder ..\Files\OPTIMIZATION_DATA\\ has been created"); } else { Print("Error when creating the OPTIMIZATION_DATA root folder: ", ErrorDescription(GetLastError())); return(""); } } //--- If there is no Expert Advisor folder if(!expert_folder_exists) { if(FolderCreate(root_folder+EXPERT_NAME,FILE_COMMON)) { expert_folder_exists=true; Print("The Expert Advisor folder ..\Files\OPTIMIZATION_DATA\\ has been created"+expert_folder); } else { Print("Error when creating the Expert Advisor folder ..\Files\\"+expert_folder+"\: ", ErrorDescription(GetLastError())); return(""); } } //--- If the necessary folders exist if(root_folder_exists && expert_folder_exists) { //--- Return the location for creating the file of optimization results return(root_folder+EXPERT_NAME); } //--- return(""); }
Der oben aufgeführte Code beinhaltet detaillierte Kommentare, sodass Sie keine Probleme haben sollten, ihn zu verstehen. Gehen wir also nur auf das Wichtigste ein.
Als Erstes prüfen wir das Vorhandensein des Stammverzeichnisses OPTIMIZATION_DATA, das die Ergebnisse der Optimierung beinhaltet. Falls der Ordner existiert, wird dies in der Variable root_folder_exists vermerkt. Das Such-Handle wird anschließend im Ordner OPTIMIZATION_DATA festgelegt, in dem wir das Vorhandensein des Ordners des Expert Advisors prüfen.
Weiter zählen wir die Dateien, die der Ordner des Expert Advisors enthält. Zuletzt werden auf Basis der Prüfergebnisse, falls erforderlich (wenn die Ordner nicht gefunden werden konnten), die erforderlichen Ordner erstellt und der Speicherort der neuen Datei mit der Indexnummer ausgegeben. Falls ein Fehler aufgetreten ist, wird ein leerer String ausgegeben.
Nun müssen wir nur die Funktion WriteOptimizationResults() betrachten, in der wir die Bedingungen für das Schreiben von Daten in die Datei prüfen und die Daten schreiben, wenn die Bedingungen erfüllt sind. Der Code dieser Funktion ist nachfolgend aufgeführt:
//+--------------------------------------------------------------------+ //| Writing the results of the optimization by criteria | //+--------------------------------------------------------------------+ void WriteOptimizationResults(string string_to_write) { bool condition=false; // To check the condition //--- If at least one criterion is satisfied if(CriterionSelectionRule==RULE_OR) condition=AccessCriterionOR(); //--- If all criteria are satisfied if(CriterionSelectionRule==RULE_AND) condition=AccessCriterionAND(); //--- If the conditions for criteria are satisfied if(condition) { //--- If the file of optimization results is opened if(OptimizationFileHandle!=INVALID_HANDLE) { int strings_count=0; // String counter //--- Get the number of strings in the file and move the pointer to the end strings_count=GetStringsCount(); //--- Write the string with criteria FileWrite(OptimizationFileHandle,IntegerToString(strings_count),string_to_write); } else Print("Invalid optimization file handle!"); } }
Sehen wir uns die Strings an, die die im Code markierten Funktionen enthalten. Die Wahl der zu verwendenden Funktion hängt von der Regel ab, die für die Überprüfung der Kriterien ausgewählt wurde. Wenn alle angegebenen Kriterien erfüllt werden müssen, verwenden wir die Funktion AccessCriterionAND():
//+--------------------------------------------------------------------+ //| Checking multiple conditions for writing to the file | //+--------------------------------------------------------------------+ bool AccessCriterionAND() { int count=0; // Criterion counter //--- Iterate over the array of criteria in a loop and see // if all the conditions for writing parameters to the file are met for(int i=0; i<ArraySize(criteria); i++) { //--- Move to the next iteration, if the criterion is not determined if(criteria[i]==C_NO_CRITERION) continue; //--- PROFIT if(criteria[i]==C_STAT_PROFIT) { if(stat_values[0]>criteria_values[i]) { count++; if(count==UsedCriteriaCount) return(true); } } //--- TOTAL DEALS if(criteria[i]==C_STAT_DEALS) { if(stat_values[1]>criteria_values[i]) { count++; if(count==UsedCriteriaCount) return(true); } } //--- PROFIT FACTOR if(criteria[i]==C_STAT_PROFIT_FACTOR) { if(stat_values[2]>criteria_values[i]) { count++; if(count==UsedCriteriaCount) return(true); } } //--- EXPECTED PAYOFF if(criteria[i]==C_STAT_EXPECTED_PAYOFF) { if(stat_values[3]>criteria_values[i]) { count++; if(count==UsedCriteriaCount) return(true); } } //--- EQUITY DD REL PERC if(criteria[i]==C_STAT_EQUITY_DDREL_PERCENT) { if(stat_values[4]<criteria_values[i]) { count++; if(count==UsedCriteriaCount) return(true); } } //--- RECOVERY FACTOR if(criteria[i]==C_STAT_RECOVERY_FACTOR) { if(stat_values[5]>criteria_values[i]) { count++; if(count==UsedCriteriaCount) return(true); } } //--- SHARPE RATIO if(criteria[i]==C_STAT_SHARPE_RATIO) { if(stat_values[6]>criteria_values[i]) { count++; if(count==UsedCriteriaCount) return(true); } } } //--- Conditions for writing are not met return(false); }
Wenn mindestens eins der angegebenen Kriterien erfüllt werden muss, nutzen Sie die Funktion AccessCriterionOR():
//+--------------------------------------------------------------------+ //| Checking for meeting one of the conditions for writing to the file | //+--------------------------------------------------------------------+ bool AccessCriterionOR() { //--- Iterate over the array of criteria in a loop and see // if all the conditions for writing parameters to the file are met for(int i=0; i<ArraySize(criteria); i++) { //--- if(criteria[i]==C_NO_CRITERION) continue; //--- PROFIT if(criteria[i]==C_STAT_PROFIT) { if(stat_values[0]>criteria_values[i]) return(true); } //--- TOTAL DEALS if(criteria[i]==C_STAT_DEALS) { if(stat_values[1]>criteria_values[i]) return(true); } //--- PROFIT FACTOR if(criteria[i]==C_STAT_PROFIT_FACTOR) { if(stat_values[2]>criteria_values[i]) return(true); } //--- EXPECTED PAYOFF if(criteria[i]==C_STAT_EXPECTED_PAYOFF) { if(stat_values[3]>criteria_values[i]) return(true); } //--- EQUITY DD REL PERC if(criteria[i]==C_STAT_EQUITY_DDREL_PERCENT) { if(stat_values[4]<criteria_values[i]) return(true); } //--- RECOVERY FACTOR if(criteria[i]==C_STAT_RECOVERY_FACTOR) { if(stat_values[5]>criteria_values[i]) return(true); } //--- SHARPE RATIO if(criteria[i]==C_STAT_SHARPE_RATIO) { if(stat_values[6]>criteria_values[i]) return(true); } } //--- Conditions for writing are not met return(false); }
Die Funktion GetStringsCount() verschiebt den Pointer zum Ende der Datei und gibt die Anzahl der Strings in der Datei aus:
//+--------------------------------------------------------------------+ //| Counting the number of strings in the file | //+--------------------------------------------------------------------+ int GetStringsCount() { int strings_count =0; // String counter ulong offset =0; // Offset for determining the position of the file pointer //--- Move the file pointer to the beginning FileSeek(OptimizationFileHandle,0,SEEK_SET); //--- Read until the current position of the file pointer reaches the end of the file while(!FileIsEnding(OptimizationFileHandle) || !IsStopped()) { //--- Read the whole string while(!FileIsLineEnding(OptimizationFileHandle) || !IsStopped()) { //--- Read the string FileReadString(OptimizationFileHandle); //--- Get the position of the pointer offset=FileTell(OptimizationFileHandle); //--- If it's the end of the string if(FileIsLineEnding(OptimizationFileHandle)) { //--- Move to the next string // if it's not the end of the file, increase the pointer counter if(!FileIsEnding(OptimizationFileHandle)) offset++; //--- Move the pointer FileSeek(OptimizationFileHandle,offset,SEEK_SET); //--- Increase the string counter strings_count++; break; } } //--- If it's the end of the file, exit the loop if(FileIsEnding(OptimizationFileHandle)) break; } //--- Move the pointer to the end of the file for writing FileSeek(OptimizationFileHandle,0,SEEK_END); //--- Return the number of strings return(strings_count); }
Nun ist alles fertig. Jetzt müssen wir die Funktion CreateOptimizationReport() in den Körper der Funktion OnTesterPass() einfügen und das Handle der Datei mit den Optimierungsergebnissen in der Funktion OnTesterDeinit() schließen.
Testen wir nun den Expert Advisor. Seine Parameter werden mithilfe des MQL5 Cloud Network optimiert. Der Strategietester muss gemäß dem folgenden Screenshot eingestellt werden:
Abb. 3 – Einstellungen des Strategietesters
Wir optimieren alle Parameter des Expert Advisors und stellen die Parameter so ein, dass nur Ergebnisse, in denen Profit Factor größer als 1 und Recovery Factor größer als 2 sind, in die Datei geschrieben werden (siehe nachfolgender Screenshot):
Abb. 4 – Einstellungen des Expert Advisors für die Optimierung der Parameter
Das MQL5 Cloud Network hat 101.000 Durchgänge in nur etwa 5 Minuten verarbeitet! Hätte ich nicht auf die Netzwerkressourcen zurückgegriffen, dann hätte die Optimierung mehrere Tage gedauert. Das ist eine großartige Gelegenheit für alle, die wissen, wie wertvoll Zeit ist.
Die erhaltene Datei kann nun in Excel geöffnet werden. Aus 101.000 Durchläufen wurden 719 Ergebnisse ausgewählt, die in die Datei geschrieben werden. Im nachfolgenden Screenshot habe ich die Spalten mit den Parametern markiert, auf deren Basis die Ergebnisse ausgewählt wurden:
Abb. 5 – Optimierungsergebnisse in Excel
Fazit
Damit ist dieser Beitrag abgeschlossen. Das Thema der Analyse von Optimierungsergebnissen kann noch viel weiter vertieft werden und wir werden in zukünftigen Beiträgen sicherlich darauf zurückkommen. Im Anhang des Beitrags finden Sie ein Archiv mit den Dateien des Expert Advisors.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/746
- 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.