
MQL5 细则手册:保存基于指定标准的“EA 交易”的优化结果
简介
我们继续有关 MQL5 编程的系列文章。这一次,我们来看一看如何获得“EA 交易”参数优化期间传递的每个优化的结果。将完成实现,以确保如果外部参数中指定的某个条件得到满足,对应的传递值将被写入文件。除了测试值,我们还将保存带来这样的结果的参数。
开发
要实现这个理念,我们将使用在“MQL5 细则手册:设置/修改交易水平时如何避免错误”一文中说明的带简单交易算法的现成“EA 交易”,只需为其添加所有必要函数。源代码使用在最近的一系列文章中采用的方法编写。因此,所有函数被放置在不同的文件中,然后文件包含在主项目文件中。您可以参见“MQL5 细则手册:在 EA 交易中使用指标设置交易条件”一文,了解如何将文件包含在项目中。
要在优化过程中访问数据,您可以使用特殊 MQL5 函数:OnTesterInit()、OnTester()、OnTesterPass() 和 OnTesterDeinit()。我们简单看一下每个函数:
- OnTesterInit() - 本函数用于决定优化开始。
- OnTester() - 本函数负责在每次优化传递后添加所谓的框架。框架的定义将在下文中给出。
- OnTesterPass() - 本函数在每次优化传递后获得框架。
- OnTesterDeinit() - 本函数生成结束“EA 交易”参数优化事件。
现在我们来定义框架。框架是单一优化传递的某种数据结构。在优化期间,框架保存于在 MetaTrader 5/MQL5/Files/Tester 文件夹中创建的 *.mqd 文档内。该档案的数据(框架)可在优化期间“动态”访问或在优化完成后访问。例如,“在 MetaTrader 5 测试程序中实现策略可视化”一文说明了我们如何实现对优化过程的“动态”可视化以及如何在优化后查看结果。
在本文中,我们将使用下述函数来处理框架:
- FrameAdd() - 从文件或数组添加数据。
- FrameNext() - 获得单个数字值或整个框架数据的调用。
- FrameInputs() - 获得带指定传递数的给定框架据以形成的输入参数。
有关上文列示函数的更多信息可在 MQL5 参考中找到。与往常一样,我们从外部参数开始。下面您可以看到哪些参数应添加至已经存在的:
//--- 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
LogOptimizationReport 参数将用于指示在优化期间是否应将结果和参数写入文件。
在本例中,我们将实现指定多达三个标准的可能性,将基于这些标准选择要写入文件的结果。我们还将添加一个规则(CriterionSelectionRule 参数),在此您可以指定在满足所有指定条件 (AND) 或满足至少一个条件 (OR) 时,是否将结果写入文件。为此,我们在 Enums.mqh 文件中创建一个枚举:
//--- Rules for checking criteria enum CRITERION_RULE { RULE_AND = 0, // AND RULE_OR = 1 // OR };
主测试参数将用作标准。在此我们需要另一个枚举:
//--- 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 };
将检查每个参数是否超出外部参数中指定的值,最大净值亏损除外,因为选择必须基于最小净值亏损进行。
我们还需要添加一些全局变量(参见下面的代码):
//--- 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
此外,下面的数组是必需的:
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
“EA 交易”的主文件需要使用在本文开头说明的处理策略测试程序的函数予以增强:
//+--------------------------------------------------------------------+ //| 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) //--- }
如果我们现在开始优化,含交易品种的图表以及“EA 交易”在其上运行的时间表将出现在终端中。来自上述代码中使用的函数的消息将在终端的日志而不是策略测试程序的日志中显示。来自 OnTesterInit() 函数的消息将在优化最开始时显示。但在优化期间和优化完成后,您将无法在日志中看到任何消息。如果在优化后您删除了策略测试程序打开的图表,来自 OnTesterDeinit() 函数的消息将在日志中显示。原因何在?
这是为了确保正确操作,OnTester() 函数需要使用 FrameAdd() 函数来添加框架,如下所示。
//+--------------------------------------------------------------------+ //| 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); }
现在,在优化期间,在每个优化传递后来自 OnTesterPass() 函数的消息将在日志中显示,且有关优化完成的消息将在优化完成后由 OnTesterDeinit() 函数添加。如果手动停止优化,也会生成优化完成消息。
图 1 - 来自测试和优化函数的消息在日志中显示
一切准备就绪,现在可以开始处理用于创建确定指定优化参数和写下满足条件的结果的文件夹和文件的函数。
我们创建一个文件 FileFunctions.mqh,将其包含在项目中。在文件的一开始,我们编写 GetTestStatistics() 函数,该函数将通过引用获得使用值填充每个优化传递的数组。
//+--------------------------------------------------------------------+ //| 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 }
GetTestStatistics() 函数必须在添加框架前插入:
//+--------------------------------------------------------------------+ //| 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); }
填充的数组作为最后一个自变量传递至 FrameAdd() 函数。如必要,您甚至可以传递数据文件。
在 OnTesterPass() 函数中,我们现在可以检查获得的数据。要查看它是如何工作的,我们现在将简单地在终端日志中为每个结果显示盈利。使用 FrameNext() 以获得当前框架值。请看下面的示例:
//+--------------------------------------------------------------------+ //| 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)); } }
如果您没有使用 FrameNext() 函数,stat_values 数组中的值将归零。然而,如果正确完成所有步骤,我们将获得如下方截屏所示的结果:
图 2 - 来自 OnTesterPass() 函数的消息在日志中显示
顺便一提,如果执行优化时没有修改外部参数,结果将绕过 OnTesterPass() 和 OnTesterDeinit() 函数从缓存加载至策略测试程序。您应牢记这一点,勿将此视为错误。
此外,我们在 FileFunctions.mqh 中创建 CreateOptimizationReport() 函数。关键活动将在此函数内执行。函数代码如下所示:
//+--------------------------------------------------------------------+ //| 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); }
我们得到一个很大的函数。我们来仔细看看这个函数。在最开始,就在声明变量和数组之后,我们如上例中所述,使用 FrameNext() 函数获得框架数据。然后,使用 FrameInputs() 函数,我们获得 parameters_list[] 字符串数组的参数列表,以及传递至 parameters_count 变量的参数总数。
接收自 FrameInputs() 函数的参数列表中的优化参数(在策略测试程序中标记)位于最开始处,而不论它们在“EA 交易”的外部参数列表中的顺序如何。
接下来是遍历参数列表的循环。标准数组 criteria[] 和标准值数组 criteria_values[] 在第一个传递处填充。如果启用 AND 模式且当前参数是最后一个,使用的标准在 CalculateUsedCriteria() 函数中进行计数:
//+--------------------------------------------------------------------+ //| 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++; } }
在同一个循环中,我们进一步检查是否有任何给定参数被选择用于优化。检查在每个传递处执行,并使用向其传递当前外部参数进行检查的 ParameterEnabledForOptimization() 函数完成。如果函数返回 ture,将优化参数。
//+---------------------------------------------------------------------+ //| 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); }
在这种情况下,名称 parameter_names 和参数值 parameter_values 数组被填充。用于优化参数名称的数组仅在第一个传递填充。
然后,使用两个循环,我们生成测试字符串和写入文件的参数值。之后,写入文件使用 WriteOptimizationReport() 函数在第一个传递生成。
//+--------------------------------------------------------------------+ //| 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); } }
WriteOptimizationReport() 函数的目的是生成头文件、必要时在终端的通用文件夹中创建文件夹以及创建用于写入的文件。也就是说,与之前优化有关的文件未被删除,且函数每次创建一个带索引号的新文件。头文件保存在新创建的文件中。文件本身保持打开,直至优化结束。
上述代码包含带 CreateOptimizationResultsFolder() 函数的字符串,用于保存包含优化结果的文件的文件夹在该函数中创建:
//+--------------------------------------------------------------------+ //| 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(""); }
上述代码提供了详细的注释,因此您在理解时不会碰到任何困难。我们仅仅列出几个关键点。
首先,我们检查包含优化结果的 OPTIMIZATION_DATA 根文件夹。如果文件夹存在,这在 root_folder_exists 变量中标示。然后搜索句柄在 OPTIMIZATION_DATA 文件夹中设置,我们在该文件夹中检查“EA 交易”文件夹。
接下来我们对“EA 交易”文件夹包含的文件进行计数。最后,基于检查结果,必要时(如果未找到文件夹)创建所需的文件夹并返回新文件的位置和索引号。如果发生错误,将返回一个空字符串。
现在,我们仅需要考虑 WriteOptimizationResults() 函数,在其中我们检查将数据写入文件的条件以及当条件满足时写入数据。此函数的代码如下:
//+--------------------------------------------------------------------+ //| 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!"); } }
我们来看看在代码中高亮显示的包含函数的字符串。使用函数的选择基于选定用于检查标准的规则。如果需要满足所有指定标准,我们使用 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); }
如果需要满足至少一个指定标准,使用 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); }
GetStringsCount() 函数将指针移动到文件末尾并返回文件字符串数量:
//+--------------------------------------------------------------------+ //| 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); }
一切准备就绪。现在,我们需要将 CreateOptimizationReport() 函数插入 OnTesterPass() 函数体,并在 OnTesterDeinit() 函数中关闭优化文件句柄。
现在我们来测试“EA 交易”。其参数将使用分布式计算的 MQL5 云网络进行优化。策略测试程序需要如下方截屏所示进行设置:
图 3 - 策略测试程序的设置
我们将优化“EA 交易”的所有参数和设置标准的参数,以便只有盈利系数大于 1 且回收系数大于 2 的结果被写入文件(参见下方截屏):
图 4 - 参数优化的“EA 交易”设置
分布式计算的 MQL5 云网络在短短 5 分钟内处理了 101,000 个传递!如果我未使用网络资源,优化需要数天才能完成。对于所有明白时间价值的人,这是一个绝佳机会。
结果文件现在可以在 Excel 中打开。在 101,000 个传递中,719 个结果被选出写入文件。在下面的截屏中,我高亮显示了含参数的列,结果基于这些参数选定:
图 5 - Excel 中的优化结果
总结
是时候结束本文了。优化结果分析的主题其实远未彻底探讨,在后续文章中我们还会再次提及。包含“EA 交易”文件的可下载文档已附于本文,供您参考。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/746
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.



是否可以只保存优化结果中前 10 个路径所使用的参数?
如果优化次数较多,记录的结果文件(从终端开始)可能会达到数十 GB,这就没有必要了。因此我想知道,是否可以在OnTesterDeinit() 函数中访问结果,并只将其中一小部分保存到文件中?
我修复了大部分错误和警告,但执行优化时不会生成报告。