Guia prático do MQL5: Salvando resultados de otimização de um Expert Advisor baseado em critérios especificados

27 março 2014, 10:21
Anatoli Kazharski
0
1 782

Introdução

Continuamos as séries de artigos sobre a programação do MQL5. Desta vez, veremos como obter resultados de cada etapa de otimização durante a otimização do parâmetro do Expert Advisor. A implementação será feita de modo a garantir que, se forem atingidas as condições especificadas nos parâmetros externos, os valores das etapas correspondentes serão gravados em um arquivo. Além dos valores de teste, também salvaremos os parâmetros que levaram a tais resultados.

 

Desenvolvimento

Para implementar a ideia, vamos usar o Expert Advisor já feito, com um algoritmo de negociação simples, descrito no artigo "Guia prático do MQL5: Como evitar erros ao configurar/modificar os níveis de negociação" e somente adicionar todas as funções necessárias. O código-fonte foi preparado usando a abordagem empregada nos artigos mais recentes da série. Assim, todas as funções estão organizadas em diferentes arquivos e incluídas no arquivo do projeto principal. Você pode ver como os arquivos podem ser incluídos no projeto no artigo "Guia prático do MQL5: Usando indicadores para definir condições de negociação em Expert Advisors".

Para ter acesso aos dados do curso de otimização, você pode usar funções especiais do MQL5: OnTesterInit(), OnTester(), OnTesterPass() e OnTesterDeinit(). Vamos dar uma olhada rápida em cada uma delas:

  • OnTesterInit() - esta função é usada para determinar o início da otimização.
  • OnTester() - esta função é responsável por adicionar os tão chamados períodos após cada etapa da otimização. A definição de períodos será dada mais adiante.
  • OnTesterPass() - esta função recebe períodos após cada etapa de otimização.
  • OnTesterDeinit() - esta função gera o evento do final da otimização do parâmetro do Expert Advisor.

Agora devemos definir um período. O período é uma espécie de estrutura de dados de uma única etapa de otimização. Durante a otimização, os períodos são salvos no arquivo *.mqd criado na pasta MetaTrader 5/MQL5/Files/Tester. Os dados (períodos) deste arquivo podem ser acessados tanto durante a otimização "sem parar" quanto após sua conclusão. Por exemplo, o artigo "Visualize uma estratégia no verificador do MetaTrader 5" ilustra como podemos visualizar o processo de otimização "sem parar" e depois ver os resultados após a otimização.

Neste artigo, utilizaremos as seguintes funções para trabalhar com os períodos:

  • FrameAdd() - adiciona dados de um arquivo ou matriz.
  • FrameNext() - uma chamada para obter um único valor numérico ou os dados do período inteiro.
  • FrameInputs() - obtém os parâmetros de entrada com base no qual um determinado período com o número de etapas especificadas é formado.

Outras informações sobre as funções listadas acima podem ser encontradas na referência do MQL5. Como de costume, comecemos com os parâmetros externos. Abaixo, você pode ver quais parâmetros devem ser adicionados aos já existentes:

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

O parâmetro LogOptimizationReport será usado para indicar se os resultados e parâmetros devem ou não serem gravados em um arquivo durante a otimização.

Neste exemplo, vamos implementar a possibilidade de especificação de até três critérios com base nos quais os resultados serão selecionados para serem gravados em um arquivo. Também adicionaremos uma regra (o parâmetro CriterionSelectionRule), onde você pode especificar se os resultados serão gravados, caso todas as condições dadas sejam atingidas (E) ou, se pelo menos uma delas (OU) for encontrada. Para isso, criamos uma enumeração no arquivo Enums.mqh:

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

Os principais parâmetros de teste serão utilizados como critério. Aqui, precisamos de outra enumeração:

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

Cada parâmetro será verificado por exceder o valor especificado nos parâmetros externos, com a exceção do levantamento máximo patrimonial, já que a seleção deve ser feita com base no abaixamento mínimo.

Nós também precisamos adicionar algumas variáveis​ globais (ver o código abaixo):

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

Além disso, são necessárias as seguintes matrizes:

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

O arquivo principal do Expert Advisor deve ser aprimorado com funções para manipulação dos eventos do verificador de estratégia descritos no início do artigo:

//+--------------------------------------------------------------------+
//| 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 começarmos a otimização agora, o gráfico com o símbolo e o período de tempo em que o Expert Advisor está em execução aparecerá no terminal. As mensagens das funções usadas no código acima serão impressas ao diário do terminal em vez do diário do verificador de estratégia. Uma mensagem da função OnTesterInit() será impressa no início da otimização. Mas, durante a otimização e após sua conclusão, você não será capaz de ver quaisquer mensagens no diário. Se após a otimização você excluir o gráfico aberto pelo verificador de estratégia, uma mensagem da função OnTesterDeinit() será impressa ao diário. Por que isso?

O problema é que, a fim de assegurar o funcionamento correto, a função OnTester() necessita utilizar a função FrameAdd() para adicionar um período, como mostrado abaixo.

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

Agora, durante a otimização, uma mensagem da função OnTesterPass() será impressa ao diário depois de cada etapa de otimização, e a mensagem sobre a conclusão da otimização será adicionada após o final da otimização pela função OnTesterDeinit(). A mensagem de conclusão da otimização também será gerada se a otimização for parada manualmente.

Fig.1 - Mensagens das funções de teste e otimização impressas ao diário

Fig.1 - Mensagens das funções de teste e otimização impressas ao diário

Agora tudo está pronto para avançar às funções responsáveis ​pela criação de pastas e arquivos, determinação de parâmetros de otimização específicos e gravação dos resultados que satisfaçam as condições.

Vamos criar um arquivo, FileFunctions.mqh, e incluí-lo no projeto. No início deste arquivo, escrevemos a função GetTestStatistics(), que por referência obterá uma matriz para o preenchimento de valores de cada etapa de otimização.

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

A função GetTestStatistics() deve ser inserida antes da adição do período:

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

A matriz preenchida é passada à função FrameAdd() como o último argumento. Você pode até mesmo passar um arquivo de dados, se necessário.

Podemos agora verificar os dados obtidos através da função OnTesterPass(). Para ver como isso funciona, vamos, por agora, simplesmente exibir o lucro para cada resultado no diário do terminal. Use FrameNext() para obter os valores do período atual. Por favor, veja o exemplo abaixo:

//+--------------------------------------------------------------------+
//| 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 você não usa a função FrameNext(), os valores na matriz stat_values serão zero. Se, no entanto, tudo é feito corretamente, obteremos o resultado como mostrado na imagem abaixo:

Fig. 2 - Mensagens da função OnTesterPass() impressas ao diário

Fig. 2 - Mensagens da função OnTesterPass() impressas ao diário

A propósito, se a otimização for executada sem modificar os parâmetros externos, os resultados serão carregados ao verificador de estratégia do cache, contornando as funções OnTesterPass() e OnTesterDeinit(). Você deve ter isso em mente para não pensar que há um erro.

Além disso, em FileFunctions.mqh criamos uma função CreateOptimizationReport(). A principal atividade será realizada nesta função. O código da função é fornecido abaixo:

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

Obtivemos uma função bastante ampla. Vamos olhá-la mais de perto. No início, logo depois de declarar as variáveis​e matrizes, obtivemos os dados do período usando a função FrameNext(), como demonstrado nos exemplos dados acima. Em seguida, usando a função FrameInputs(), obtivemos a lista de parâmetros para a matriz de sequência parameters_list[], juntamente com o número total de parâmetros que são passados à variável parameters_count.

Os parâmetros otimizados (sinalizados no verificador de estratégia), na lista de parâmetros recebidos da função FrameInputs(), independentemente de sua ordem na lista dos parâmetros externos do Expert Advisor, estão localizados no início.

Isto é seguido pelo ciclo que itera sobre a lista de parâmetros. A matriz dos critérios criteria[] e dos valores de critérios criteria_values[] são preenchidas na primeira etapa. Os critérios utilizados são contados na função CalculateUsedCriteria(), desde que o modo AND esteja habilitado e o parâmetro atual seja o ú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++;
     }
  }

No mesmo ciclo ainda verificaremos se um determinado parâmetro está selecionado para otimização. A verificação é realizada a cada etapa e é feita usando a função ParameterEnabledForOptimization(), pela qual o parâmetro externo atual passa por verificação. Se a função retornar como verdadeira, o parâmetro será otimizado.

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

Neste caso, as matrizes para os nomes parameter_names e os valores de parâmetros parameter_values são preenchidos. A matriz para os nomes dos parâmetros otimizados só é preenchida na primeira etapa.

Em seguida, usando dois ciclos, geramos a sequência dos valores de teste e parâmetros para a gravação em um arquivo. Depois disso, o arquivo para gravação é gerado utilizando a função WriteOptimizationReport() na primeira etapa.

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

A finalidade da função WriteOptimizationReport() é gerar cabeçalhos, criar pastas, se necessário, na pasta comum do terminal, bem como a de criar um arquivo para gravação. Ou seja, os arquivos associados com otimizações anteriores não são removidos e a função cada vez cria um novo arquivo com o número do índice. Os cabeçalhos são salvos em um arquivo recém criado. O arquivo em si permanece aberto até o final da otimização.

O código acima contém a sequência com a função CreateOptimizationResultsFolder(), onde são criadas as pastas para salvar os arquivos com os resultados de otimização:

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

O código acima é fornecido com os comentários detalhados, logo, você não deve enfrentar qualquer dificuldade em entendê-lo. Vamos delinear os pontos-chave.

Primeiro, verifiquemos a pasta raiz OPTIMIZATION_DATA contendo os resultados da otimização. Se a pasta existir, isto é assinalado na variável root_folder_exists. O identificador de pesquisa é então definido na pasta OPTIMIZATION_DATA onde verificamos a pasta do Expert Advisor.

Contamos ainda os arquivos que a pasta Expert Advisor contém. Finalmente, com base nos resultados da verificação, quando necessário (se as pastas não puderam ser encontradas), as pastas necessárias são criadas e o local para o novo arquivo com o número de índice é retornado. Se ocorreu um erro, uma sequência vazia será retornada.

Agora, só precisamos considerar a função WriteOptimizationResults(), onde verificamos as condições para a gravação de dados ao arquivo e gravamos os mesmos se a condição for encontrada. O código desta função é fornecido abaixo:

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

Vamos dar uma olhada nas sequências que contêm as funções destacadas no código. A escolha da função utilizada depende da regra selecionada para a verificação dos critérios. Se todos os critérios especificados precisam ser atingidos, nós usamos a função 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 você precisa de pelo menos um dos critérios atingidos para estar satisfeito, use a função 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);
  }

A função GetStringsCount() move o indicador para o final do arquivo e retorna o número de sequências no arquivo:

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

Está tudo pronto agora. Agora precisamos inserir a função CreateOptimizationReport() ao corpo da função OnTesterPass() e fechar o identificador do arquivo de otimização na função OnTesterDeinit().

Agora vamos testar o Expert Advisor. Seus parâmetros serão otimizados usando a rede em nuvem do MQL5 de computação distribuída. O verificador de estratégia precisa ser definido como mostrado na captura de tela abaixo:

Fig. 3 - Definições do verificador de estratégia

Fig. 3 - Definições do verificador de estratégia

Vamos otimizar todos os parâmetros do Expert Advisor e definir os parâmetros dos critérios, de modo que só os resultados onde o fator de lucro for maior do que 1 e o fator de recuperação for maior do que 2 sejam gravados no arquivo (ver a captura de tela abaixo):

Fig. 4 - As configurações do Expert Advisor para a otimização de parâmetros

Fig. 4 - As configurações do Expert Advisor para a otimização de parâmetros

A rede em nuvem do MQL5 de computação distribuída processou 101 mil etapas em apenas 5 minutos! Se eu não tivesse usado os recursos de rede, a otimização teria levado vários dias para ser concluída. Essa é uma grande oportunidade para todos que conhecem o valor do tempo.

O arquivo resultante agora pode ser aberto no Excel. 719 resultados foram selecionados entre 101 mil etapas a serem gravadas no arquivo. Na captura de tela abaixo destaquei as colunas com os parâmetros, com base nos quais foram selecionados os resultados:

Fig. 5 - Resultados da otimização no Excel

Fig. 5 - Resultados da otimização no Excel

 

Conclusão

É hora de desenhar uma linha sobre este artigo. O tema da análise dos resultados de otimização está na verdade longe de ser totalmente terminado e certamente voltaremos a ele em artigos futuros. Em sua consideração, anexo ao artigo está o arquivo de download dos arquivos do Expert Advisor.

Traduzido do russo pela MetaQuotes Software Corp.
Artigo original: https://www.mql5.com/ru/articles/746

Arquivos anexados |
Estendendo a biblioteca padrão do MQL5 e reutilizando o código Estendendo a biblioteca padrão do MQL5 e reutilizando o código

A biblioteca padrão do MQL5 torna mais fácil a sua vida como desenvolvedor. No entanto, ela não implementa todas as necessidades de todos os desenvolvedores no mundo, então, se você achar que precisa de mais algum material de customização, você pode dar um passo a mais e estender. Este artigo o orienta através da integração do indicador técnico Zig-Zag MetaQuotes' na biblioteca padrão. Ficamos inspirados pela filosofia de design MetaQuotes' para alcançar o nosso objetivo.

Indicadores técnicos e filtros digitais Indicadores técnicos e filtros digitais

Neste artigo, os indicadores técnicos são tratados como filtros digitais. Princípios de operação e características básicas de filtros digitais são explicados. Além disso, algumas formas práticas de receber o núcleo filtro no terminal do MetaTrader 5 e a integração com um analisador de espectro pronto para o uso proposto no artigo "Construção de um analisador de espectro" são considerados. As características de pulso e de espectro dos filtros digitais típicos são usadas como exemplos.

Guia prático do MQL5: Notificações sonoras para eventos de negociação do MetaTrader 5 Guia prático do MQL5: Notificações sonoras para eventos de negociação do MetaTrader 5

Neste artigo vamos considerar questões como a inclusão de arquivos sonoros no arquivo do Expert Advisor e, por conseguinte, a adição de notificações sonoras aos eventos de negociação. O fato de que os arquivos serão incluídos significa que os arquivos sonoros estarão localizados dentro do Expert Advisor. Assim, ao dar a versão compilada do Expert Advisor (*.ex5) para outro usuário, você não terá que fornecer também os arquivos sonoros e explicar onde eles precisam ser salvos.

Guia prático do MQL5: Monitoramento de múltiplos períodos de tempo em uma única janela Guia prático do MQL5: Monitoramento de múltiplos períodos de tempo em uma única janela

Há 21 períodos de tempo disponíveis no MetaTrader 5 para análise. Você pode tirar proveito de objetos gráficos especiais, os quais você pode colocar no gráfico existente e definir o símbolo, período de tempo e algumas outras propriedades ali mesmo. Este artigo fornecerá informações detalhadas sobre tais objetos gráficos do gráfico: vamos criar um indicador com os controles (botões), que nos permitirão definir vários objetos gráficos em uma sub-janela ao mesmo tempo. Além disso, os objetos gráficos se encaixarão com precisão na sub-janela e serão ajustados automaticamente quando o gráfico principal ou a janela do terminal forem redimensionados.