English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
MQL5 Coobook: 지정된 기준에 따라 Expert Advisor의 최적화 결과 저장

MQL5 Coobook: 지정된 기준에 따라 Expert Advisor의 최적화 결과 저장

MetaTrader 5테스터 | 3 9월 2021, 11:05
112 0
Anatoli Kazharski
Anatoli Kazharski

소개

MQL5 프로그래밍에 대한 글 시리즈를 계속합니다. 이번에는 Expert Advisor 매개변수 최적화 중에 각 최적화 단계의 결과를 얻는 방법을 살펴보겠습니다. 외부 매개변수에 지정된 특정 조건이 충족되면 해당 통과 값이 파일에 기록되도록 구현이 수행됩니다. 테스트 값 외에도 그러한 결과를 가져온 매개변수도 저장합니다.

 

개발

아이디어를 구현하기 위해 "MQL5 Cookbook: 거래 수준을 설정/수정할 때 오류를 방지하는 방법"에 대한 글에 설명된 간단한 거래 알고리즘과 함께 기성 Expert Advisor를 사용하고 필요한 모든 기능을 이에 추가합니다. 소스 코드는 시리즈의 가장 최근 글에서 사용된 접근 방식을 사용하여 준비되었습니다. 따라서 모든 기능은 서로 다른 파일에 정렬되어 기본 프로젝트 파일에 포함됩니다. "MQL5 Coobook: 지표를 사용하여 Expert Advisor의 거래 조건 설정" 문서에서 프로젝트에 파일을 포함하는 방법을 확인할 수 있습니다.

최적화 과정에서 데이터에 액세스하려면 다음과 같이 특수 MQL5 함수를 사용할 수 있습니다: OnTesterInit(), OnTester(), OnTesterPass()OnTesterDeinit(). 각각에 대해 간단히 살펴보겠습니다.

  • OnTesterInit() - 이 함수는 최적화 시작을 결정하는 데 사용됩니다.
  • OnTester() - 이 함수는 모든 최적화 단계 후에 소위 프레임을 추가하는 역할을 합니다. 프레임의 정의는 아래에서 더 자세히 설명합니다.
  • OnTesterPass() - 이 함수는 모든 최적화 단계 후에 프레임을 가져옵니다.
  • OnTesterDeinit() - 이 함수는 Expert Advisor 매개변수 최적화 종료 이벤트를 생성합니다.

이제 프레임을 정의해야 합니다. 프레임은 단일 최적화 패스의 일종의 데이터 구조입니다. 최적화하는 동안 프레임은 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 매개변수는 결과 및 매개변수가 최적화 중에 파일에 기록되어야 하는지 여부를 나타내는 데 사용됩니다.

이 예에서는 결과가 파일에 기록되도록 선택되는 기준을 최대 3개까지 지정할 수 있는 가능성을 구현합니다. 또한 주어진 모든 조건(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

Expert Advisor의 기본 파일은 글 시작 부분에 설명된 Strategy Tester 이벤트를 처리하기 위한 기능으로 향상되어야 합니다.

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

지금 최적화를 시작하면 Expert Advisor가 실행 중인 기호와 시간 프레임이 있는 차트가 터미널에 나타납니다. 위의 코드에서 사용된 함수의 메시지는 Strategy Tester의 저널이 아닌 터미널의 저널에 출력됩니다. 최적화의 맨 처음에 OnTesterInit() 함수의 메시지가 프린트됩니다. 그러나 최적화 중 및 완료 시 저널에서 메시지를 볼 수 없습니다. 최적화 후에 Strategy Tester에서 연 차트를 삭제하면 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 배열의 값은 0이 됩니다. 그러나 모든 것이 올바르게 완료되면 아래 스크린샷과 같은 결과를 얻을 수 있습니다.

그림 2 - 저널에 프린트된 OnTesterPass() 함수의 메시지

그림 2 - 저널에 프린트된 OnTesterPass() 함수의 메시지

그런데 외부 매개변수를 수정하지 않고 최적화를 실행하면 결과가 OnTesterPass()OnTesterDeinit() 기능을 우회하여 캐시에서 Strategy Tester로 로드됩니다. 오류가 있다고 생각하지 않도록 이 점을 염두에 두어야 합니다.

또한 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_count 변수로 전달되는 매개변수의 총 수와 함께 parameters_list[] 문자열 배열에 대한 매개변수 목록을 얻게 됩니다.

FrameInputs() 함수에서 받은 매개변수 목록의 최적화된 매개변수(Strategy Tester에서 플래그됨)는 Expert Advisor의 외부 매개변수 목록에서 순서와 상관없이 맨 처음에 있습니다.

그 다음에는 매개변수 목록을 반복하는 루프가 있습니다. 기준 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() 함수를 사용하여 수행됩니다. 함수가 true를 반환하면 매개변수가 최적화됩니다.

//+---------------------------------------------------------------------+
//| 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 변수에 표시됩니다. 그러면 검색 핸들이 Expert Advisor 폴더를 확인하는 OPTIMIZATION_DATA 폴더에 설정됩니다.

Expert Advisor 폴더에 포함된 파일을 추가로 계산합니다. 마지막으로 검사 결과에 따라 필요한 경우(폴더를 찾을 수 없는 경우) 필요한 폴더가 생성되고 색인 번호가 있는 새 파일의 위치가 반환됩니다. 오류가 발생하면 빈 문자열이 반환됩니다.

이제 파일에 데이터를 쓰기 위한 조건을 확인하고 조건이 충족되면 데이터를 쓰는 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() 함수에서 최적화 파일 핸들을 닫아야 합니다.

이제 Expert Advisor를 테스트 해보겠습니다. 매개변수는 분산 컴퓨팅의 MQL5 클라우드 네트워크를 사용하여 최적화됩니다. 전략 테스터는 아래 스크린샷과 같이 설정해야 합니다.

그림 3 - 전략 테스터 설정

그림 3 - 전략 테스터 설정

Expert Advisor의 모든 매개변수를 최적화하고 기준의 매개변수를 설정하여 Profit Factor가 1보다 크고 Recovery Factor가 2보다 큰 결과만 나오도록 합니다. 파일에 기록되었습니다(아래 스크린샷 참조):

그림 4 - 매개변수 최적화를 위한 Expert Advisor 설정

그림 4 - 매개변수 최적화를 위한 Expert Advisor 설정

분산 컴퓨팅의 MQL5 클라우드 네트워크는 단 5분 만에 101,000개의 패스를 처리했습니다! 네트워크 리소스를 사용하지 않았다면 최적화를 완료하는 데 며칠이 걸렸을 것입니다. 그것은 시간의 가치를 아는 모든 사람들에게 좋은 기회입니다.

이제 결과 파일을 Excel에서 열 수 있습니다. 101,000개의 패스 중 719개의 결과가 파일에 기록되도록 선택되었습니다. 아래 스크린샷에서 결과가 선택되는 기준이 되는 매개변수가 있는 열을 강조표시했습니다.

그림 5 - Excel의 최적화 결과

그림 5 - Excel의 최적화 결과

 

결론

이 글 아래에 선을 그을 때입니다. 최적화 결과 분석의 주제는 사실 완전히 소진되지 않았으며 향후 글에서 확실히 다시 다룰 것입니다. 글에 첨부된 파일은 귀하가 고려할 수 있도록 Expert Advisor의 파일과 함께 다운로드 가능한 아카이브입니다.

MetaQuotes 소프트웨어 사를 통해 러시아어가 번역됨.
원본 기고글: https://www.mql5.com/ru/articles/746

MQL5 Coobook: MetaTrader 5 거래 이벤트에 대한 소리 알림 MQL5 Coobook: MetaTrader 5 거래 이벤트에 대한 소리 알림
이 글에서는 Expert Advisor의 파일에 사운드 파일을 포함하여 거래 이벤트에 사운드 알림을 추가하는 것과 같은 문제를 고려할 것입니다. 파일이 포함된다는 사실은 사운드 파일이 Expert Advisor 내부에 위치한다는 것을 의미합니다. 따라서 Expert Advisor의 컴파일된 버전(*.ex5)을 다른 사용자에게 제공할 때 사운드 파일도 제공하고 저장해야 하는 위치를 설명할 필요가 없습니다.
MQL5 표준 라이브러리 확장 및 코드 재사용 MQL5 표준 라이브러리 확장 및 코드 재사용
MQL5 표준 라이브러리는 개발자로서의 삶을 더 쉽게 만들어줍니다. 그럼에도 불구하고 전 세계 모든 개발자의 요구 사항을 모두 구현하지는 않으므로 사용자 지정 항목이 더 필요하다고 느끼면 한 단계 더 나아가 확장할 수 있습니다. 이 글은 MetaQuotes의 Zig-Zag 기술 지표를 표준 라이브러리에 통합하는 방법을 안내합니다. 우리는 MetaQuotes의 디자인 철학에서 영감을 얻어 목표를 달성합니다.
MQL5 Coobook: 단일 창에서 여러 시간 프레임 모니터링 MQL5 Coobook: 단일 창에서 여러 시간 프레임 모니터링
MetaTrader 5에는 분석을 위해 21개의 시간 프레임이 있습니다. 기존 차트에 배치하고 기호, 시간 프레임 및 기타 속성을 바로 설정할 수 있는 특수 차트 개체를 활용할 수 있습니다. 이 글에서는 이러한 차트 그래픽 개체에 대한 자세한 정보를 제공합니다. 하위 창에서 동시에 여러 차트 개체를 설정할 수 있는 컨트롤(버튼)이 있는 지표를 만듭니다. 또한 차트 개체는 하위 창에 정확하게 맞고 기본 차트 또는 터미널 창의 크기가 조정될 때 자동으로 조정됩니다.
기술 지표 및 디지털 필터 기술 지표 및 디지털 필터
이 글에서 기술 지표는 디지털 필터로 취급됩니다. 디지털 필터의 작동 원리와 기본 특성을 설명합니다. 또한 MetaTrader 5 터미널에서 필터 커널을 수신하고 "스펙트럼 분석기 구축" 글에서 제안한 기성 스펙트럼 분석기와 통합하는 몇 가지 실용적인 방법을 고려합니다. 일반적인 디지털 필터의 펄스 및 스펙트럼 특성을 예로 사용합니다.