English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Guía práctica de MQL5: Guardar los resultados de la optimización de un Asesor Experto en base a unos criterios especificados

Guía práctica de MQL5: Guardar los resultados de la optimización de un Asesor Experto en base a unos criterios especificados

MetaTrader 5Probador | 7 mayo 2014, 13:30
1 433 0
Anatoli Kazharski
Anatoli Kazharski

Introducción

Continuamos con la serie de artículos sobre la programación en MQL5. Esta vez, veremos cómo obtener los resultados de cada pasada de optimización durante el proceso de optimización de los parámetros del Asesor Experto. Se hará la implementación de modo que si se cumple cierta condición especificada en los parámetros externos, se escriben los valores correspondientes a la pasada de optimización en un archivo. Además de los valores de las pruebas, guardaremos también los parámetros que han llevado a estos resultados.

 

Desarrollo

Para implementar esta idea, vamos a utilizar un Asesor Experto que ya existe con un sencillo algoritmo de trading descrito en el artículo "Guía práctica de MQL5: Cómo evitar errores durante la configuración/modificación de los niveles de la operación de trading" y solo añadirle las funciones necesarias. He preparado el código fuente de la misma manera que se hizo en los últimos artículos de esta serie. De modo que todas las funciones están distribuidas en distintos archivos e incluidas en el archivo principal del proyecto. En el artículo "Guía práctica de MQL5: Usar los indicadores para configurar las condiciones de trading en los Asesores Expertos" puede ver cuántos archivos se pueden incluir en el proyecto.

Para conseguir el acceso a los datos durante la optimización, puede usar las funciones especiales de MQL5: OnTesterInit(), OnTester(), OnTesterPass() y OnTesterDeinit(). Echemos un vistazo a cada una de ellas:

  • OnTesterInit() - se usa esta función para determinar el inicio de la optimización.
  • OnTester() - esta función se encarga de añadir los llamados frames después de cada pasada de optimización. Más adelante explicaremos lo que son los frames.
  • OnTesterPass() - esta función obtiene los frames después de cada pasada de optimización.
  • OnTesterDeinit() - esta función genera el evento del final de la optimización de los parámetros del Asesor Experto.

Tenemos que definir ahora lo que es un período. El período es un tipo de estructura de datos de una única pasada de optimización. Durante la optimización, se guardan los frames en el archivo *.mqd creado en la carpeta MetaTrader 5/MQL5/Files/Tester. Se puede acceder a los datos (frames) de este archivo durante la optimización, "sobre la marcha", y después de su finalización. Por ejemplo, el artículo "Visualizar una estrategia en el simulador de Meta Trader 5" muestra cómo podemos visualizar el proceso de la optimización "sobre la marcha" y ver después los resultados de la optimización.

En este artículo, voy a utilizar las siguientes funciones para trabajar con frames:

  • FrameAdd() - añade datos a partir de un archivo o una matriz.
  • FrameNext() - una llamada para obtener un valor numérico único para todos los datos del frame.
  • FrameInputs() - obtiene los parámetros de entrada en función de los cuales se forma un frame determinado con el número de pasadas especificado.

Puede encontrar más información acerca de las funciones anteriores en el Manual de referencia de MQL5. Como siempre, empezamos con los parámetros externos. A continuación, puede ver cuáles son los parámetros que hay que añadir a los que ya existen:

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

Se usará el parámetro LogOptimizationReport para indicar si hay que escribir o no los resultados y los parámetros en un archivo durante la optimización.

En este ejemplo, vamos a implementar la posibilidad de especificar hasta tres criterios, en función de los cuales se seleccionan los resultados que hay que escribir en un archivo. También vamos a añadir una regla (el parámetro CriterionSelectionRule) dónde podemos especificar si hay que escribir los resultados cuando se cumplen todas las condiciones (AND) o cuando se cumple por lo menos una de ellas (OR). Para ello, hemos creado una enumeración en el archivo Enums.mqh:

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

Se utilizarán los principales parámetros de la prueba como criterio. En este caso, necesitamos otra enumeración:

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

Se comprobará cada parámetro para saber si ha excedido el valor especificado en los parámetros externos, excepto la reducción máxima de patrimonio, ya que la selección se hace en base a la reducción mínima.

También tenemos que añadir algunas variables globales (ver el siguiente código):

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

Además, necesitamos las siguientes matrices:

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

El archivo principal del Asesor Experto necesita ser mejorado con funciones para controlar los eventos del probador de estrategias descritos al principio del artículo:

//+--------------------------------------------------------------------+
//| 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 empezamos la optimización ahora, aparecerá en el terminal el gráfico con el símbolo y período de tiempo con los cuales se está ejecutando el Asesor Experto. Los mensajes procedentes de las funciones utilizadas en el código anterior se mostrarán en el diario del terminal en lugar del diario del probador de estrategias. Se mostrará un mensaje de la función OnTesterInit() al principio de la optimización. Pero durante la optimización y hasta su finalización, no podrá ver ningún mensaje en el diario. Si después de la optimización eliminamos el gráfico abierto mediante el probador de estrategias, se mostrará un mensaje de la función OnTesterDeinit() en el diario. ¿Por qué motivo?

Esto se debe a que para asegurar un funcionamiento correcto, la función OnTester() requiere usar la función FrameAdd() para añadir un frame, como se muestra a continuación.

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

Ahora, durante la optimización, aparecerá un mensaje de la función OnTesterPass() en el diario después de cada pasada de optimización y se añadirá el mensaje indicando la finalización de la optimización después de que esta haya finalizado mediante la función OnTesterDeinit(). También se mostrará el mensaje indicando la finalización de la optimización si se interrumpe manualmente.

Fig.1 - Visualización de los mensajes de las funciones de prueba y optimización en el diario

Fig.1 - Visualización de los mensajes de las funciones de prueba y optimización en el diario

Ya está todo listo para proceder con las funciones que se encargan de crear las carpetas y los archivos, de determinar los parámetros de optimización especificados y de escribir los resultados que cumplen las condiciones.

Vamos a crear un archivo, FileFunctions.mqh, e incluirlo en el proyecto. Al principio de este archivo, escribimos la función GetTestStatistics() que obtendrá por referencia una matriz que hay que rellenar con valores con cada pasada de optimización.

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

Hay que insertar la función GetTestStatistics() antes de añadir 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 función FrameAdd() recibe la matriz rellenada como último argumento. También le podemos enviar un archivo de datos, si es necesario.

Ahora, podemos comprobar los datos obtenidos en la función OnTesterPass(). Para ver cómo funciona, vamos a mostrar por ahora simplemente el beneficio para cada resultado en el diario del terminal. Utilice FrameNext() para obtener los valores actuales del frame. Véase el siguiente ejemplo:

//+--------------------------------------------------------------------+
//| 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 no utiliza la función FrameNext(), los valores de la matriz stat_values serán iguales a cero. No obstante, si todo se hace correctamente, obtendremos los resultados de la siguiente figura:

Fig. 2 - Visualización de los mensajes de la función OnTesterPass() en el diario

Fig. 2 - Visualización de los mensajes de la función OnTesterPass() en el diario

Por cierto, si se ejecuta la optimización sin modificar los parámetros externos, se cargarán los resultados en el probador de estrategias a partir de la memoria caché, sin pasar por las funciones OnTesterPass() y OnTesterDeinit(). Debe recordar que no tiene que pensar que hay un error.

Además, en FileFunctions.mqh creamos la función CreateOptimizationReport(). La principal actividad se lleva a cabo en esta función. Se proporciona el código de la función a continuación:

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

Hemos obtenido una función bastante grande. Vamos a analizarla con más detalle: Al principio, justo después de declarar las variables y las matrices, obtenemos los datos del frame mediante la función FrameNext(), como se demostró en los ejemplos anteriores. A continuación, mediante la función FrameInputs(), obtenemos una lista de parámetros de la matriz de tipo cadena (string) parameters_list[], junto con el número total de parámetros que se han enviado a la variable parameters_count.

Los parámetros optimizados (señalados en el probador de estrategias) en la lista de parámetros desde la función FrameInputs() se encuentran al principio, no importa su orden en la lista de los parámetros externos del Asesor Experto.

Les sigue un bucle que recorre la lista de parámetros. Se rellena la matriz de los criterios criteria[] y la matriz de los valores de los criterios criteria_values[] en la primera pasada. Se calculan los criterios en CalculateUsedCriteria(), siempre que el modo AND esté activado y el parámetro actual sea el último:

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

Además, comprobamos en el mismo bucle si está seleccionado algún parámetro para la optimización. Se lleva a cabo la comprobación a cada pasada y se hace mediante la función ParameterEnabledForOptimization() que recibe los parámetros externos actuales para la comprobación. Si la función devuelve true, se optimizará el parámetro.

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

En este caso, se rellenan las matrices de los nombres parameter_names y de los valores de los parámetros parameter_values. La matriz de los nombres de parámetros optimizados se rellena solamente en la primera pasada.

A continuación, mediante dos bucles, generamos la cadena de la prueba y los valores de los parámetros para escribirlos en un archivo. Después de esto, se genera el archivo para la escritura mediante la función WriteOptimizationReport() en la primera pasada.

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

La función WriteOptimizationReport() sirve para generar encabezados, crear carpetas, si hace falta, en la carpeta común del terminal, así como crear el archivo para la escritura. Es decir, no se eliminan los archivos relacionados con las optimizaciones anteriores y la función crea cada vez un nuevo archivo con el número de índice. Se guardan los encabezados en el nuevo archivo creado. El propio archivo permanece abierto hasta el final de la optimización.

El código anterior contiene la cadena con la función CreateOptimizationResultsFolder(), en la cual se crean las carpetas para guardar los archivos con los resultados de la optimización:

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

Se proporciona el código anterior con comentarios detallados para que no le resulte difícil de entender. Vamos a comentar solamente los puntos principales.

En primer lugar, comprobamos la presencia de la carpeta raíz OPTIMIZATION_DATA que contiene los resultados de la optimización. Si la carpeta existe, se indica en la variable root_folder_exists. El control de la búsqueda se pone entonces en la carpeta OPTIMIZATION_DATA en la cual comprobamos el Asesor Experto.

También contamos los archivos que contiene la carpeta del Asesor Experto. Por último, en función de los resultados de la comprobación, si hace falta (si no se pueden encontrar las carpetas), se crean las carpetas necesarias y se devuelve la ubicación del nuevo archivo con el número de índice. Si se produce un error, se devuelve una cadena vacía.

Ahora, solo tenemos que considerar la función WriteOptimizationResults() en la cual comprobamos las condiciones de escritura de los datos en el archivo y escribimos la condición si se cumple. El código de esta función es el siguiente:

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

Echemos un vistazo a las cadenas que contienen las funciones resaltadas en el código. La selección de la función utilizada depende de la regla elegida para la comprobación de los criterios. Si se tienen que cumplir todos los criterios especificados, usamos la función 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 necesitamos que se cumpla por lo menos un criterio especificado, usamos la función 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 función GetStringsCount() mueve el puntero al final del archivo y devuelve el número de cadenas en el archivo:

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

Ya está todo listo. Ahora, necesitamos insertar la función CreateOptimizationReport() en el cuerpo de la función OnTesterPass() y cerrar el controlador del archivo de optimización en la función OnTesterDeinit().

Probemos ahora el Asesor Experto. Se optimizarán sus parámetros mediante la red de computación distribuida MQL5 Cloud Network. Hay que configurar el Probador de Estrategias como se muestra en la siguiente figura:

Fig. 3 - Configuración del Probador de Estrategias

Fig. 3 - Configuración del Probador de Estrategias

Vamos a optimizar todos los parámetros del Asesor Experto y configurar los parámetros de los criterios de modo que solamente se escriben en el archivo los resultados con un Factor de beneficio superior a 1 y un Factor de recuperación superior a 2 (véase la siguiente figura):

Fig. 4 - Configuración del Asesor Experto para la optimización de los parámetros

Fig. 4 - Configuración del Asesor Experto para la optimización de los parámetros

¡La red de computación distribuida MQL5 Cloud Network ha procesado 101 000 pasadas en solo ~5 minutos! Si no hubiera utilizado los recursos de la red, la optimización hubiera tardado varios días en completarse. Es una excelente oportunidad por todos aquellos que valoran el tiempo.

Se puede abrir el archivo de los resultados en Excel. Se han seleccionado 719 resultados entre 101 000 pasadas para su escritura en el archivo. En la siguiente figura, he resaltado las columnas con los parámetros en función de los cuales se han seleccionado los resultados:

Fig. 5 - Resultados de la optimización en Excel

Fig. 5 - Resultados de la optimización en Excel

 

Conclusión

Es el momento de concluir este artículo. En realidad, El tema del análisis de los resultados de la optimización está lejos de estar completamente concluido y volveremos probablemente a él en futuros artículos. Se adjunta al artículo el material con los archivos del Asesor Experto para su estudio.

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/746

Archivos adjuntos |
Guía práctica de MQL5: Notificaciones sonoras para los eventos de trading de MetaTrader 5 Guía práctica de MQL5: Notificaciones sonoras para los eventos de trading de MetaTrader 5
En este artículo vamos a tratar el tema de la inclusión de archivos de sonido en el archivo del Asesor Experto y con ello añadir notificaciones sonoras a los eventos de trading. El hecho de que se incluyan los archivos significa que los archivos de sonido van a estar ubicados dentro del Asesor Experto. De modo que al proporcionar una versión compilada del Asesor Experto (*.ex5) a otro usuario, no tendrá que proporcionarle los archivos de sonido y explicarle dónde hay que guardarlos.
Ampliación de la librería estándar de MQL5 y la reutilización del código Ampliación de la librería estándar de MQL5 y la reutilización del código
La librería estándar de MQL5 le facilita la vida como desarrollador. No obstante, no abarca todas las necesidades de todos los desarrolladores del mundo, con lo cual querrá tener a su disposición más material personalizado para dar un paso más y ampliarla. En este artículo, se describe la integración del indicador técnico Zig-Zag de MetaQuotes en la librería estándar. Para conseguir nuestro objetivo, nos hemos basado en la filosofía de diseño de MetaQuotes.
Guía práctica de MQL5: Supervisar múltiples períodos de tiempo en una sola ventana Guía práctica de MQL5: Supervisar múltiples períodos de tiempo en una sola ventana
MetaTrader 5 ofrece 21 períodos de tiempo para el análisis. Puede aprovechar los objetos gráficos especiales que puede colocar en el gráfico existente y establecer el símbolo, el período de tiempo y otras propiedades. En este artículo se va a proporcionar una información detallada acerca de estos objetos gráficos: crearemos un indicador con controles (botones) que nos permitirán establecer múltiples objetos gráficos en una subventana al mismo tiempo. Además, se encajarán los objetos gráficos con precisión en la subventana y se ajustarán automáticamente al modificar el tamaño del gráfico principal o el de la ventana del terminal.
Indicadores técnicos y filtros digitales Indicadores técnicos y filtros digitales
En este artículo, se tratan los indicadores técnicos como si fueran filtros digitales. Se explican los principios de funcionamiento y las características básicas de los filtros digitales. Además, se van a tratar algunos métodos prácticos para obtener la respuesta al impulso (kernel) del filtro en el terminal de MetaTrader 5 y su integración con un analizador de espectro, que ya existe, descrito en el artículo "Construyendo un analizador de espectro". Como ejemplos, se usan las características del impulso y el espectro de los filtros digitales típicos.