Manuale MQL5: Salvataggio dei risultati di ottimizzazione di un Expert Advisor in base a criteri specificati

Anatoli Kazharski | 11 gennaio, 2022

Introduzione

Continuiamo la serie di articoli sulla programmazione MQL5. Questa volta vedremo come ottenere i risultati di ogni passaggio di ottimizzazione durante l'ottimizzazione dei parametri di Expert Advisor. L'implementazione verrà eseguita in modo da garantire che se viene soddisfatta una determinata condizione specificata nei parametri esterni, i valori di passaggio corrispondenti verranno scritti in un file. Oltre ai valori di test, salveremo anche i parametri che hanno portato a tali risultati.

 

Sviluppo

Per implementare l'idea, utilizzeremo l'Expert Advisor pronto all’uso con un semplice algoritmo di trading descritto nell'articolo "Mauale MQL5: Come evitare errori durante l'impostazione / modifica dei livelli di trading" e basta aggiungere ad esso tutte le funzioni necessarie. Il codice sorgente è stato preparato utilizzando l'approccio utilizzato negli articoli più recenti della serie. Quindi, tutte le funzioni sono disposte in diversi file e incluse nel file di progetto principale. Puoi vedere come i file possono essere inclusi nel progetto, nell'articolo "Mauale MQL5: Utilizzo degli indicatori per impostare le condizioni di trading in Expert Advisors".

Per ottenere l'accesso ai dati nel corso dell'ottimizzazione, è possibile utilizzare speciali funzioni MQL5: OnTesterInit(), OnTester(), OnTesterPass() and OnTesterDeinit(). Diamo una rapida occhiata a ciascuno di essi:

Ora dovremmo definire una cornice. Frame è una sorta di struttura dati di un singolo passaggio di ottimizzazione. Durante l'ottimizzazione, i frame vengono salvati nell'archivio *.mqd creato nella cartella MetaTrader 5/MQL5/Files/Tester. I dati (frame) di questo archivio sono accessibili sia durante l'ottimizzazione "al volo" che dopo il suo completamento. Ad esempio, l'articolo "Visualizza una strategia nel MetaTrader 5 Tester" illustra come possiamo visualizzare il processo di ottimizzazione "al volo" e quindi visualizzare i risultati a seguito dell'ottimizzazione.

In questo articolo, useremo le seguenti funzioni per lavorare con i frame:

Ulteriori informazioni sulle funzioni sopra elencate sono disponibili nella Guida di riferimento MQL5. Come al solito, iniziamo con parametri esterni. Di seguito puoi vedere quali parametri dovrebbero essere aggiunti a quelli già esistenti:

//--- 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

Il parametro LogOptimizationReport verrà utilizzato per indicare se i risultati e i parametri devono o meno essere scritti in un file durante l'ottimizzazione.

In questo esempio, implementeremo la possibilità di specificare fino a tre criteri in base ai quali verranno selezionati i risultati per essere scritti in un file. Aggiungeremo anche una regola ( parametroCriterionSelectionRule) in cui è possibile specificare se i risultati verranno scritti se tutte le condizioni fornite sono soddisfatte (AND) o se almeno una di esse (OR) è soddisfatta. A tale scopo, creiamo un'enumerazione nel file Enums.mqh:

//--- Rules for checking criteria
enum CRITERION_RULE
  {
   RULE_AND = 0, // AND
   RULE_OR  = 1  // OR
  };

I principali parametri di prova saranno utilizzati come criteri. Qui, abbiamo bisogno di un'altra enumerazione:

//--- 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
  };

Ogni parametro verrà controllato per superare il valore specificato nei parametri esterni, ad eccezione del prelievo massimo di equità in quanto la selezione deve essere effettuata in base al prelievo minimo.

Dobbiamo anche aggiungere alcune variabili globali (vedi il codice qui sotto):

//--- 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

Inoltre, sono necessari i seguenti array:

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

Il file principale di Expert Advisor deve essere migliorato con funzioni per la gestione degli eventi di Strategy Tester descritte all'inizio dell'articolo:

//+--------------------------------------------------------------------+
//| 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)
      //---
  }

Se iniziamo l'ottimizzazione ora, il grafico con il simbolo e l'intervallo di tempo su cui è in esecuzione l'Expert Advisor apparirà nel terminale. I messaggi provenienti dalle funzioni utilizzate nel codice precedente verranno stampati sul diario del terminale anziché sul diario dello Strategy Tester. Un messaggio dalla funzione OnTesterInit() verrà stampato all'inizio dell'ottimizzazione. Ma durante l'ottimizzazione e al suo completamento, non sarai in grado di vedere alcun messaggio nel diario. Se dopo l'ottimizzazione si elimina il grafico aperto dallo Strategy Tester, un messaggio dalla funzione OnTesterDeinit() verrà stampato nel journal. Perché?

Il fatto è che per garantire il corretto funzionamento, la funzione OnTester() deve utilizzare la funzione FrameAdd() per aggiungere un frame, come mostrato di seguito.

//+--------------------------------------------------------------------+
//| 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);
  }

Ora, durante l'ottimizzazione, un messaggio dalla funzione OnTesterPass() verrà stampato sul journal dopo ogni passaggio di ottimizzazione e il messaggio relativo al completamento dell'ottimizzazione verrà aggiunto dopo la fine dell'ottimizzazione dalla funzione OnTesterDeinit(). Il messaggio di completamento dell'ottimizzazione verrà generato anche se l'ottimizzazione viene interrotta manualmente.

Fig.1 - Messaggi dalle funzioni di test e ottimizzazione, stampati sul journal

Fig.1 - Messaggi dalle funzioni di test e ottimizzazione, stampati sul journal

Tutto è ora pronto per procedere alle funzioni responsabili della creazione di cartelle e file, determinando i parametri di ottimizzazione specificati e scrivendo i risultati che soddisfano le condizioni.

Creiamo un file, FileFunctions.mqh,, e includiamolo nel progetto. All'inizio di questo file, scriviamo la funzione GetTestStatistics() che per riferimento otterrà una matrice per riempire ogni passaggio di ottimizzazione con valori.

//+--------------------------------------------------------------------+
//| 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 funzione GetTestStatistics() deve essere inserita prima di aggiungere un 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);
  }

La matrice riempita viene passata alla funzione FrameAdd() come ultimo argomento. Puoi anche passare un file di dati, se necessario.

Nella funzione OnTesterPass(), ora possiamo controllare i dati ottenuti. Per vedere come funziona, per ora mostreremo semplicemente il profitto per ogni risultato nel diario del terminale. Utilizzare FrameNext() per ottenere i valori dei fotogrammi correnti. Si prega di consultare l'esempio seguente:

//+--------------------------------------------------------------------+
//| 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));
     }
  }

Se non si utilizza la funzione FrameNext(), i valori nella matrice stat_values saranno zero. Se, tuttavia, tutto è fatto correttamente, otterremo il risultato come mostrato nello screenshot qui sotto:

Fig. 2 - Messaggi dalla funzione OnTesterPass() stampati sul journal

Fig. 2 - Messaggi dalla funzione OnTesterPass() stampati sul journal

A proposito, se l'ottimizzazione viene eseguita senza modificare i parametri esterni, i risultati verranno caricati su Strategy Tester dalla cache, ignorando le funzioni OnTesterPass() e OnTesterDeinit(). Dovresti tenerlo a mente per non pensare che ci sia un errore.

Inoltre, in FileFunctions.mqh creiamo una funzione CreateOptimizationReport(). L'attività chiave verrà eseguita all'interno di questa funzione. Il codice funzione è fornito di seguito:

//+--------------------------------------------------------------------+
//| 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);
  }

Abbiamo una funzione abbastanza grande. Diamo un'occhiata più da vicino. All'inizio, subito dopo aver dichiarato le variabili e gli array, otteniamo i dati del frame utilizzando la funzione FrameNext() come dimostrato negli esempi sopra riportati. Quindi, utilizzando la funzione FrameInputs(), otteniamo l'elenco dei parametri per la matrice di stringhe parameters_list[], insieme al numero totale di parametri passati alla variabile parameters_count.

I parametri ottimizzati (contrassegnati nello Strategy Tester) nell'elenco dei parametri ricevuto dalla funzione FrameInputs() si trovano all'inizio, indipendentemente dal loro ordine nell'elenco dei parametri esterni dell'Expert Advisor.

Questo è seguito dal ciclo che itera sull'elenco dei parametri. La matrice di criteri[] e la matrice di valori di criteri criteria_values[] vengono compilati al primo passaggio. I criteri utilizzati vengono conteggiati nella funzione CalculateUsedCriteria(), a condizione che la modalità AND sia abilitata e che il parametro corrente sia l'ultimo:

//+--------------------------------------------------------------------+
//| 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++;
     }
  }

Nello stesso ciclo controlliamo ulteriormente se un dato parametro è selezionato per l'ottimizzazione. Il controllo viene eseguito ad ogni passaggio e viene eseguito utilizzando la funzione ParameterEnabledForOptimization() a cui viene passato il parametro esterno corrente per il controllo. Se la funzione restituisce true, il parametro verrà ottimizzato.

//+---------------------------------------------------------------------+
//| 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 questo caso, le matrici per i nomi parameter_names e i valori dei parametri parameter_values vengono riempite. La matrice per i nomi dei parametri ottimizzati viene riempita solo al primo passaggio.

Quindi, utilizzando due cicli, generiamo la stringa di valori di test e parametri per la scrittura in un file. Successivamente il file per la scrittura viene generato utilizzando la funzione WriteOptimizationReport() al primo passaggio.

//+--------------------------------------------------------------------+
//| 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);
     }
  }

Lo scopo della funzione WriteOptimizationReport() è generare intestazioni, creare cartelle, se necessario, nella cartella comune del terminale e creare un file per la scrittura. Cioè, i file associati alle ottimizzazioni precedenti non vengono rimossi e la funzione ogni volta crea un nuovo file con il numero di indice. Le intestazioni vengono salvate in un file appena creato. Il file stesso rimane aperto fino alla fine dell'ottimizzazione.

Il codice precedente contiene la stringa con la funzione CreateOptimizationResultsFolder(), in cui vengono create le cartelle per il salvataggio dei file con risultati di ottimizzazione:

//+--------------------------------------------------------------------+
//| 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("");
  }

Il codice di cui sopra viene fornito con i commenti dettagliati in modo da non dover affrontare alcuna difficoltà nel comprenderlo. Descriviamo solo i punti chiave.

Innanzitutto, controlliamo la cartella principale OPTIMIZATION_DATA contenente i risultati dell'ottimizzazione. Se la cartella esiste, questa è contrassegnata nella variabile root_folder_exists. L'handle di ricerca viene quindi impostato nella cartella OPTIMIZATION_DATA in cui controlliamo la cartella Expert Advisor.

Contiamo inoltre i file contenuti nella cartella Expert Advisor. Infine, in base ai risultati del controllo, se necessario (se non è stato possibile trovare le cartelle), vengono create le cartelle richieste e viene restituito il percorso per il nuovo file con il numero di indice. Se si è verificato un errore, verrà restituita una stringa vuota.

Ora, dobbiamo solo considerare la funzione WriteOptimizationResults() in cui controlliamo le condizioni per la scrittura dei dati nel file e scriviamo i dati se la condizione è soddisfatta. Il codice di questa funzione è fornito di seguito:

//+--------------------------------------------------------------------+
//| 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!");
     }
  }

Diamo un'occhiata alle stringhe che contengono le funzioni evidenziate nel codice. La scelta della funzione utilizzata dipende dalla regola selezionata per il controllo dei criteri. Se è necessario soddisfare tutti i criteri specificati, utilizziamo la funzione 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);
  }

Se è necessario soddisfare almeno uno dei criteri specificati, utilizzare la funzione 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 funzione GetStringsCount() sposta il puntatore alla fine del file e restituisce il numero di stringhe nel file:

//+--------------------------------------------------------------------+
//| 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);
  }

Tutto è pronto e pronto ora. Ora dobbiamo inserire la funzione CreateOptimizationReport() nel corpo della funzione OnTesterPass() e chiudere l'handle del file di ottimizzazione nella funzione OnTesterDeinit().

Testiamo ora l'Expert Advisor. I suoi parametri saranno ottimizzati utilizzando la rete cloud MQL5 di calcolo distribuito. Lo Strategy Tester deve essere impostato come mostrato nello screenshot qui sotto:

Fig. 3 - Impostazioni del Tester Strategico

Fig. 3 - Impostazioni del tester strategico

Ottimizzeremo tutti i parametri dell'Expert Advisor e imposteremo i parametri dei criteri in modo che solo i risultati in cui Profit Factor è maggiore di 1 e Recovery Factor è maggiore di 2 vengano scritti nel file (vedi lo screenshot qui sotto):

Fig. 4 - Le impostazioni di Expert Advisor per l'ottimizzazione dei parametri

Fig. 4 - Le impostazioni di Expert Advisor per l'ottimizzazione dei parametri

La rete cloud MQL5 di calcolo distribuito ha elaborato 101.000 passaggi in soli ~ 5 minuti! Se non avessi utilizzato le risorse di rete, l'ottimizzazione avrebbe richiesto diversi giorni per essere completata. Questa è una grande opportunità per tutti coloro che conoscono il valore del tempo.

Il file risultante può ora essere aperto in Excel. Sono stati selezionati 719 risultati su 101.000 passaggi da scrivere sul file. Nello screenshot qui sotto, ho evidenziato le colonne con i parametri in base ai quali sono stati selezionati i risultati:

Fig. 5 - Risultati dell'ottimizzazione in Excel

Fig. 5 - Risultati dell'ottimizzazione in Excel

 

Conclusione

È tempo di tracciare una linea sotto questo articolo. L'argomento dell'analisi dei risultati dell'ottimizzazione è infatti ben lungi dall'essere completamente esaurito e sicuramente ci torneremo nei prossimi articoli. Allegato all'articolo è l'archivio scaricabile con i file dell'Expert Advisor per la vostra considerazione.