Le MQL5 Cookbook : Enregistrement des résultats d'optimisation d'un Expert Advisor sur la base de critères spécifiés
Introduction
Nous continuons la série d'articles sur la programmation MQL5. Cette fois, nous verrons comment obtenir les résultats de chaque passe d'optimisation lors de l'optimisation des paramètres de l'Expert Advisor. La mise en œuvre sera effectuée de manière à garantir que si une certaine condition spécifiée dans les paramètres externes est remplie, les valeurs de passage correspondantes seront écrites dans un fichier. En plus des valeurs de test, nous enregistrerons également les paramètres qui ont conduit à de tels résultats.
Développement
Pour mettre en œuvre l'idée, nous allons utiliser l'Expert Advisor prêt à l'emploi avec un algorithme de trading simple décrit dans l'article "MQL5 Cookbook : Comment éviter les erreurs lors de la définition/modification des niveaux de trading" et ajoutez-y simplement toutes les fonctions nécessaires. Le code source a été préparé en utilisant l'approche employée dans les articles les plus récents de la série. Ainsi, toutes les fonctions sont organisées dans différents fichiers et incluses dans le fichier principal du projet. Vous pouvez voir comment les fichiers peuvent être inclus dans le projet dans l'article "MQL5 Cookbook : Utilisation d'indicateurs pour définir les conditions de trading dans les Expert Advisors".
Pour accéder aux données en cours d'optimisation, vous pouvez utiliser des fonctions spéciales MQL5 : OnTesterInit(), OnTester(), OnTesterPass() et OnTesterDeinit(). Jetons un coup d'œil à chacun d'eux :
- OnTesterInit() - cette fonction est utilisée pour déterminer le début de l'optimisation.
- OnTester() - cette fonction est responsable de l'ajout de ce qu'on appelle les frames après chaque passage d'optimisation. La définition des frames sera donnée plus loin.
- OnTesterPass() - cette fonction obtient des frames après chaque passe d'optimisation.
- OnTesterDeinit() - cette fonction génère l'événement de fin de l'optimisation des paramètres de l'Expert Advisor.
Maintenant, nous devons définir une frame. Une frame est une sorte de structure de données d'une seule passe d'optimisation. Pendant l'optimisation, les frames sont enregistrées dans l'archive *.mqd créée dans le dossier MetaTrader 5/MQL5/Files/Tester. Les données (frames) de cette archive sont accessibles à la fois pendant l'optimisation "à la volée" et après son achèvement. Par exemple, l'article "Visualisation d’une stratégie dans le testeur MetaTrader 5" illustre comment nous pouvons visualiser le processus d'optimisation "à la volée" puis visualiser les résultats suite à l'optimisation.
Dans cet article, nous utiliserons les fonctions suivantes pour travailler avec des frames :
- FrameAdd() - ajoute des données à partir d'un fichier ou d'un tableau.
- FrameNext() - un appel pour obtenir une seule valeur numérique ou l'intégralité des données de frame.
- FrameInputs() - obtient les paramètres d'entrée sur la base desquels une frame donnée avec le numéro de passe spécifié est formée.
Vous trouverez de plus amples informations sur les fonctions énumérées ci-dessus MQL5 Reference. Comme d'habitude, nous commençons par les paramètres externes. Ci-dessous, vous pouvez voir quels paramètres doivent être ajoutés à ceux déjà existants :
//--- 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
Le paramètre LogOptimizationReport sera utilisé pour indiquer si les résultats et les paramètres doivent ou non être écrits dans un fichier lors de l'optimisation.
Dans cet exemple, nous allons implémenter la possibilité de spécifier jusqu'à trois critères en fonction desquels les résultats seront sélectionnés pour être écrits dans un fichier. Nous allons également ajouter une règle (le paramètre CriterionSelectionRule) où vous pouvez spécifier si les résultats seront écrits si toutes les conditions données sont remplies (ET) ou si au moins l'une d'entre elles (OU) est remplie. Pour cela, nous créons une énumération dans le fichier Enums.mqh :
//--- Rules for checking criteria enum CRITERION_RULE { RULE_AND = 0, // AND RULE_OR = 1 // OR };
Les principaux paramètres de test seront utilisés comme critères. Ici, nous avons besoin d'une autre énumération :
//--- 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 };
Chaque paramètre sera vérifié afin de s'assurer qu'il ne dépasse pas la valeur spécifiée dans les paramètres externes, à l'exception du retrait maximal de l'équité, car la sélection doit se faire sur la base du retrait minimal.
Nous devons également ajouter quelques variables globales (voir le code ci-dessous) :
//--- 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
De plus, les baies suivantes sont requises :
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
Le fichier principal de l'Expert Advisor doit être enrichi des fonctions de gestion des événements Strategy Tester décrites au début de l'article :
//+--------------------------------------------------------------------+ //| 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) //--- }
Si nous commençons l'optimisation maintenant, le graphique avec le symbole et le cadre temporel sur lequel l'Expert Advisor s'exécute apparaîtra dans le terminal. Les messages des fonctions utilisées dans le code ci-dessus seront imprimés dans le journal du terminal au lieu du journal du testeur de stratégie. Un message de la fonction OnTesterInit() sera imprimé au tout début de l'optimisation. Mais pendant l'optimisation et à son achèvement, vous ne pourrez voir aucun message dans le journal. Si après l'optimisation vous supprimez le graphique ouvert par le Strategy Tester, un message de la fonction OnTesterDeinit() sera imprimé dans le journal. Pourquoi donc ?
Le fait est que pour garantir le bon fonctionnement, la fonction OnTester() doit utiliser la fonction FrameAdd() pour ajouter une frame, comme indiqué ci-dessous.
//+--------------------------------------------------------------------+ //| 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); }
Désormais, lors de l'optimisation, un message de la fonction OnTesterPass() sera imprimé dans le journal après chaque passe d'optimisation et le message concernant la fin de l'optimisation sera ajouté après la fin de l'optimisation par la fonction OnTesterDeinit(). Le message de fin d'optimisation sera également généré si l'optimisation est arrêtée manuellement.
Fig.1 - Messages des fonctions de test et d'optimisation imprimés dans le journal
Tout est maintenant prêt pour passer aux fonctions chargées de créer des dossiers et des fichiers, de déterminer les paramètres d'optimisation spécifiés et d'écrire les résultats qui satisfont aux conditions.
Créons un fichier, FileFunctions.mqh, et incluons-le dans le projet. Au tout début de ce fichier, nous écrivons la fonction GetTestStatistics() qui obtiendra par référence un tableau pour remplir chaque passe d'optimisation avec des valeurs.
//+--------------------------------------------------------------------+ //| 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 }
La fonction GetTestStatistics() doit être insérée avant d'ajouter une frame :
//+--------------------------------------------------------------------+ //| 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); }
Le tableau rempli est passé à la fonction FrameAdd() comme dernier argument. Vous pouvez même passer un fichier de données, si nécessaire.
Dans la fonction OnTesterPass(), nous pouvons maintenant vérifier les données obtenues. Pour voir comment cela fonctionne, nous allons pour l'instant simplement afficher le profit pour chaque résultat dans le journal du terminal. Utilisez FrameNext() pour obtenir les valeurs de frame actuelles. Veuillez consulter l'exemple ci-dessous :
//+--------------------------------------------------------------------+ //| 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)); } }
Si vous n'utilisez pas la fonction FrameNext(), les valeurs du tableau stat_values seront zéro. Si, cependant, tout est fait correctement, nous obtiendrons le résultat comme indiqué dans la capture d'écran ci-dessous :
Fig. 2 - Messages de la fonction OnTesterPass() imprimés dans le journal
D'ailleurs, si l'optimisation est exécutée sans modifier les paramètres externes, les résultats seront chargés dans le Strategy Tester à partir du cache, en contournant les fonctions OnTesterPass() et OnTesterDeinit(). Vous devez garder cela à l'esprit pour ne pas penser qu'il y a une erreur.
De plus, dans FileFunctions.mqh, nous créons une fonction CreateOptimizationReport(). L'activité clé sera exécutée dans cette fonction. Le code de fonction est fourni ci-dessous :
//+--------------------------------------------------------------------+ //| 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); }
Nous avons une fonction assez importante. Regardons-le de plus près. Au tout début, juste après avoir déclaré les variables et les tableaux, nous obtenons les données de frame en utilisant la fonction FrameNext() comme illustré dans les exemples donnés ci-dessus. Ensuite, à l'aide de la fonction FrameInputs(), nous obtenons la liste des paramètres du tableau de chaînes de caractères parameters_list[], ainsi que le nombre total de paramètres transmis à la variable parameters_count.
Les paramètres optimisés (marqués dans le Strategy Tester) dans la liste des paramètres reçus de la fonction FrameInputs() sont situés au tout début, quel que soit leur ordre dans la liste des paramètres externes de l'Expert Advisor.
Ceci est suivi par la boucle qui itère sur la liste des paramètres. Le tableau des critères critères[] et le tableau des valeurs des critères critères_valeurs[] sont remplis au tout premier passage. Les critères utilisés sont comptabilisés dans la fonction CalculateUsedCriteria(), à condition que le mode AND soit activé et que le paramètre courant soit le dernier :
//+--------------------------------------------------------------------+ //| 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++; } }
Dans la même boucle, nous vérifions en outre si un paramètre donné est sélectionné pour l'optimisation. La vérification est effectuée à chaque passage et est effectuée à l'aide de la fonction ParameterEnabledForOptimization() à laquelle le paramètre externe actuel est transmis pour vérification. Si la fonction retourne vrai, le paramètre sera optimisé.
//+---------------------------------------------------------------------+ //| 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); }
Dans ce cas, les tableaux pour les noms parameters_names et les valeurs de paramètre parameters_values sont remplis. Le tableau des noms de paramètres optimisés n'est rempli qu'au premier passage.
Ensuite, à l'aide de deux boucles, nous générons la chaîne de valeurs de test et de paramètre pour l'écriture dans un fichier. Ensuite, le fichier d'écriture est généré à l'aide de la fonction WriteOptimizationReport() lors du premier passage.
//+--------------------------------------------------------------------+ //| 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); } }
Le but de la fonction WriteOptimizationReport() est de générer des en-têtes, de créer des dossiers, si nécessaire, dans le dossier commun du terminal, ainsi que de créer un fichier en écriture. Autrement dit, les fichiers associés aux optimisations précédentes ne sont pas supprimés et la fonction crée à chaque fois un nouveau fichier avec le numéro d'index. Les en-têtes sont enregistrés dans un fichier nouvellement créé. Le fichier lui-même reste ouvert jusqu'à la fin de l'optimisation.
Le code ci-dessus contient la chaîne de caractères avec la fonction CreateOptimizationResultsFolder(), où les dossiers d'enregistrement des fichiers avec les résultats d'optimisation sont créés :
//+--------------------------------------------------------------------+ //| 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(""); }
Le code ci-dessus est fourni avec les commentaires détaillés, vous ne devriez donc avoir aucune difficulté à le comprendre. Décrivons simplement les points clés.
Tout d'abord, nous vérifions le dossier racine OPTIMIZATION_DATA contenant les résultats de l'optimisation. Si le dossier existe, il est marqué dans la variable root_folder_exists. Le descripteur de recherche est ensuite défini dans le dossier OPTIMIZATION_DATA où nous vérifions le dossier Expert Advisor.
Nous comptons en outre les fichiers que contient le dossier Expert Advisor. Enfin, en fonction des résultats de la vérification, si nécessaire (si les dossiers n'ont pas pu être trouvés), les dossiers requis sont créés et l'emplacement du nouveau fichier avec le numéro d'index est renvoyé. Si une erreur s'est produite, une chaîne vide sera renvoyée.
Maintenant, nous n'avons qu'à considérer la fonction WriteOptimizationResults() où nous vérifions les conditions d'écriture des données dans le fichier et écrivons les données si la condition est remplie. Le code de cette fonction est fourni ci-dessous :
//+--------------------------------------------------------------------+ //| 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!"); } }
Jetons un coup d'œil aux chaînes qui contiennent les fonctions mises en évidence dans le code. Le choix de la fonction utilisée dépend de la règle choisie pour vérifier les critères. Si tous les critères spécifiés doivent être satisfaits, nous utilisons la fonction 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); }
Si vous avez besoin qu'au moins un des critères spécifiés soit satisfait, utilisez la fonction 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); }
La fonction GetStringsCount() déplace le pointeur à la fin du fichier et renvoie le nombre de chaînes de caractères dans le fichier :
//+--------------------------------------------------------------------+ //| 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); }
Tout est réglé et prêt maintenant. Nous devons maintenant insérer la fonction CreateOptimizationReport() dans le corps de la fonction OnTesterPass() et fermer le handle de fichier d'optimisation dans la fonction OnTesterDeinit().
Testons maintenant l'Expert Advisor. Ses paramètres seront optimisés à l'aide du réseau MQL5 Cloud Network de calcul distribué. Le testeur de stratégie doit être configuré comme indiqué dans la capture d'écran ci-dessous :
Fig. 3 - Paramètres du testeur de stratégie
Nous allons optimiser tous les paramètres de l'Expert Advisor et définir les paramètres des critères de sorte que seuls les résultats où le facteur de profit est supérieur à 1 et le facteur de récupération est supérieur à 2 soient écrits dans le fichier (voir la capture d'écran ci-dessous) :
Fig. 4 - Les réglages de l'Expert Advisor pour l'optimisation des paramètres
Le réseau MQL5 Cloud Network d'informatique distribuée a traité 101 000 passages en seulement 5 minutes environ ! Si je n'avais pas utilisé les ressources du réseau, l'optimisation aurait pris plusieurs jours. C'est une grande opportunité pour tous ceux qui connaissent la valeur du temps.
Le fichier résultant peut maintenant être ouvert dans Excel. 719 résultats ont été sélectionnés sur 101 000 passages à écrire dans le fichier. Dans la capture d'écran ci-dessous, j'ai mis en évidence les colonnes avec les paramètres en fonction desquels les résultats ont été sélectionnés :
Fig. 5 - Résultats d'optimisation dans Excel
Conclusion
Il est temps de tirer un trait sur cet article. Le sujet de l'analyse des résultats d'optimisation est en effet loin d'être totalement épuisé et nous y reviendrons certainement dans les prochains articles. L'archive téléchargeable avec les fichiers de l'Expert Advisor est jointe à l'article pour votre considération.
Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/746
- Applications de trading gratuites
- Plus de 8 000 signaux à copier
- Actualités économiques pour explorer les marchés financiers
Vous acceptez la politique du site Web et les conditions d'utilisation