MQL5 细则手册:保存基于指定标准的“EA 交易”的优化结果

Anatoli Kazharski | 20 六月, 2014

简介

我们继续有关 MQL5 编程的系列文章。这一次,我们来看一看如何获得“EA 交易”参数优化期间传递的每个优化的结果。将完成实现,以确保如果外部参数中指定的某个条件得到满足,对应的传递值将被写入文件。除了测试值,我们还将保存带来这样的结果的参数。

 

开发

要实现这个理念,我们将使用在“MQL5 细则手册:设置/修改交易水平时如何避免错误”一文中说明的带简单交易算法的现成“EA 交易”,只需为其添加所有必要函数。源代码使用在最近的一系列文章中采用的方法编写。因此,所有函数被放置在不同的文件中,然后文件包含在主项目文件中。您可以参见“MQL5 细则手册:在 EA 交易中使用指标设置交易条件”一文,了解如何将文件包含在项目中。

要在优化过程中访问数据,您可以使用特殊 MQL5 函数:OnTesterInit()OnTester()OnTesterPass()OnTesterDeinit()。我们简单看一下每个函数:

现在我们来定义框架。框架是单一优化传递的某种数据结构。在优化期间,框架保存于在 MetaTrader 5/MQL5/Files/Tester 文件夹中创建的 *.mqd 文档内。该档案的数据(框架)可在优化期间“动态”访问或在优化完成后访问。例如,“在 MetaTrader 5 测试程序中实现策略可视化”一文说明了我们如何实现对优化过程的“动态”可视化以及如何在优化后查看结果。

在本文中,我们将使用下述函数来处理框架:

有关上文列示函数的更多信息可在 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 - 来自测试和优化函数的消息在日志中显示

图 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() 函数的消息在日志中显示

图 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 &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);
     }
  }

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 - 策略测试程序的设置

图 3 - 策略测试程序的设置

我们将优化“EA 交易”的所有参数和设置标准的参数,以便只有盈利系数大于 1 且回收系数大于 2 的结果被写入文件(参见下方截屏):

图 4 - 参数优化的“EA 交易”设置

图 4 - 参数优化的“EA 交易”设置

分布式计算的 MQL5 云网络在短短 5 分钟内处理了 101,000 个传递!如果我未使用网络资源,优化需要数天才能完成。对于所有明白时间价值的人,这是一个绝佳机会。

结果文件现在可以在 Excel 中打开。在 101,000 个传递中,719 个结果被选出写入文件。在下面的截屏中,我高亮显示了含参数的列,结果基于这些参数选定:

图 5 - Excel 中的优化结果

图 5 - Excel 中的优化结果

 

总结

是时候结束本文了。优化结果分析的主题其实远未彻底探讨,在后续文章中我们还会再次提及。包含“EA 交易”文件的可下载文档已附于本文,供您参考。