![Guia prático do MQL5: Salvando resultados de otimização de um Expert Advisor baseado em critérios especificados](https://c.mql5.com/i/articles/overlay.png)
Guia prático do MQL5: Salvando resultados de otimização de um Expert Advisor baseado em critérios especificados
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
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
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áveise 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 ¶meter_names[]) { int files_count =1; // Counter of optimization files //--- Generate a header to the optimized parameters string headers="#,PROFIT,TOTAL DEALS,PROFIT FACTOR,EXPECTED PAYOFF,EQUITY DD MAX REL%,RECOVERY FACTOR,SHARPE RATIO,"; //--- Add the optimized parameters to the header for(int i=0; i<ArraySize(parameter_names); i++) { if(i==ArraySize(parameter_names)-1) StringAdd(headers,parameter_names[i]); else StringAdd(headers,parameter_names[i]+","); } //--- Get the location for the optimization file and the number of files for the index number OptimizationResultsPath=CreateOptimizationResultsFolder(files_count); //--- If there is an error when getting the folder, exit if(OptimizationResultsPath=="") { Print("Empty path: ",OptimizationResultsPath); return; } else { OptimizationFileHandle=FileOpen(OptimizationResultsPath+"\optimization_results"+IntegerToString(files_count)+".csv", FILE_CSV|FILE_READ|FILE_WRITE|FILE_ANSI|FILE_COMMON,","); //--- if(OptimizationFileHandle!=INVALID_HANDLE) FileWrite(OptimizationFileHandle,headers); } }
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
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
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
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 Ltd.
Artigo original: https://www.mql5.com/ru/articles/746
![Guia prático do MQL5: Notificações sonoras para eventos de negociação do MetaTrader 5](https://c.mql5.com/2/0/avatar__8.png)
![Estendendo a biblioteca padrão do MQL5 e reutilizando o código](https://c.mql5.com/2/0/regular-polyhedra-02.png)
![Guia prático do MQL5: Monitoramento de múltiplos períodos de tempo em uma única janela](https://c.mql5.com/2/0/avatar__9.png)
![Indicadores técnicos e filtros digitais](https://c.mql5.com/2/0/Indicators_as_digital_filters_MQL5.png)
![MQL5 - Linguagem para estratégias de negociação inseridas no terminal do cliente MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso