English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Le MQL5 Cookbook : Enregistrement des résultats d'optimisation d'un Expert Advisor sur la base de critères spécifiés

Le MQL5 Cookbook : Enregistrement des résultats d'optimisation d'un Expert Advisor sur la base de critères spécifiés

MetaTrader 5Tester | 13 janvier 2022, 08:52
168 0
Anatoli Kazharski
Anatoli Kazharski

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

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

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 &parameter_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

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

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

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

Le MQL5 Cookbook : Notifications sonores pour les événements de trading MetaTrader 5 Le MQL5 Cookbook : Notifications sonores pour les événements de trading MetaTrader 5
Dans cet article, nous examinerons des problèmes tels que l'inclusion de fichiers sonores dans le fichier de l'Expert Advisor, et ainsi l'ajout de notifications sonores aux événements de trading. Le fait que les fichiers seront inclus signifie que les fichiers sonores seront situés à l'intérieur de l'Expert Advisor. Ainsi, lorsque vous donnez la version compilée de l'Expert Advisor (*.ex5) à un autre utilisateur, vous n'aurez pas à fournir également les fichiers sonores et à expliquer où ils doivent être sauvegardés.
Extension de la bibliothèque standard MQL5 et réutilisation du code Extension de la bibliothèque standard MQL5 et réutilisation du code
La bibliothèque standard MQL5 vous facilite la vie en tant que développeur. Néanmoins, il ne met pas en œuvre tous les besoins de tous les développeurs du monde, donc si vous sentez que vous avez besoin de plus de choses personnalisées, vous pouvez aller plus loin et étendre. Cet article vous guide tout au long de l’intégration de l’indicateur technique Zig-Zag de MetaQuotes dans la bibliothèque standard. Nous nous inspirons de la philosophie de conception de MetaQuotes pour atteindre notre objectif.
Le MQL5 Cookbook : Surveillance de plusieurs trames temporelles dans une seule fenêtre Le MQL5 Cookbook : Surveillance de plusieurs trames temporelles dans une seule fenêtre
Il y a 21 trames temporelles disponibles dans MetaTrader 5 destinées à l'analyse. Vous pouvez tirer parti d'objets graphiques spéciaux que vous pouvez placer sur le graphique existant et définir le symbole, la trame temporelle et d'autres propriétés à cet endroit. Cet article fournira des informations détaillées sur de tels objets graphiques de graphique : nous allons créer un indicateur avec des commandes (boutons) qui nous permettront de définir plusieurs objets de graphique dans une sous-fenêtre en même temps. De plus, les objets du graphique s'adapteront avec précision dans la sous-fenêtre et seront automatiquement ajustés lorsque le graphique principal ou la fenêtre du terminal est redimensionné.
Résultats du MetaTrader AppStore pour le troisième trimestre 2013 Résultats du MetaTrader AppStore pour le troisième trimestre 2013
Un autre trimestre de l'année s'est écoulé et nous avons décidé de résumer ses résultats pour MetaTrader AppStore - le plus grand magasin de robots de trading et d'indicateurs techniques pour les plateformes MetaTrader. Plus de 500 développeurs ont placé plus de 1 200 produits sur Market à la fin du trimestre considéré.