English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
MQL5 Cookbook:指定の基準に基づく Expert Advisor 最適化結果の保存方法

MQL5 Cookbook:指定の基準に基づく Expert Advisor 最適化結果の保存方法

MetaTrader 5テスター | 24 11月 2015, 15:11
781 0
Anatoli Kazharski
Anatoli Kazharski

はじめに

MQL5 プログラミングに関するシリーズを続けます。今回、われわれは Expert Advisor のパラメータ最適化の最中に各最適化パス結果を取得する方法を見ていきます。外部パラメータに指定された特定の条件が満たされれば対応するパス値がファイルに書き込まれることを確認できるよう実装が行われます。検証値以外にもそのような結果をもたらしたパラメータも保存します。

 

開発

その考えを実装するには、記事"MQL5 Cookbook: How to Avoid Errors When Setting/Modifying Trade Levels" に述べられているシンプルなトレーディングアルゴリズムを持つ既製の Expert Advisor を使っていくつもりです。ソースコードはシリーズの最新記事で採用している方法を使って準備しました。関数はすべて異なるファイルに整理し、メインのプロジェクトファイルにインクルードしています。ファイルをプロジェクトにインクルードする方法は記事 "MQL5 Cookbook: Using Indicators to Set Trading Conditions in Expert Advisors"で確認可能です。

最適化に関するデータにアクセスするには、次のような特殊な MQL5 関数を使います。OnTesterInit()OnTester()OnTesterPass()OnTesterDeinit()。それぞれを簡単に見ていきます。

  • OnTesterInit() -最適化開始を判断するのに使用されます。
  • OnTester() -各最適化パス後にいわゆるフレームを追加します。フレームの定義はのちほど行います。
  • OnTesterPass() -最適化パスごとにフレームを取得します。
  • OnTesterDeinit() - Expert Advisor パラメータ最適化の終了のイベントを作成します。

それではフレームの定義を行います。フレームはある種一回の最適化パスのデータストラクチャです。最適化中、フレームは MetaTrader 5/MQL5/Files/Tester フォルダに作成されるアーカイブ*.mqd に保存されます。本稿のデータ(フレーム)は最適化進行中、完了後のどちらでもアクセス可能です。たとえば、記事"Visualize a Strategy in the MetaTrader 5 Tester" は進行中の最適化プロセスを視覚化する方法を説明し、最適化の結果を表示しています。

本稿では、フレームと連携するのに次の関数を使用します。

  • 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)か、またはそのうちの最低1件を満たす(OR)かすれば結果を書き込むかどうか指定するルール(CriterionSelectionルールパラメータ)を追加します。このため、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 のメインファイルは、本稿冒頭に述べた「ストラテジーテスタ」イベントを処理する関数によって強化する必要があります。

//+--------------------------------------------------------------------+
//| 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 実行対象のシンボルとタイムフレームを持つチャートがターミナルに表示されます。上記コードで使用されている関数からのメッセージは「ストラテジーテスタ」のジャーナルではなく、ターミナルのジャーナルにプリントされます。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_count 変数に渡されるパラメータのトータル数と共に parameters_list[] 文字列配列へのパラメータリストを取得します。

FrameInputs() 関数から受け取られたパラメータリストにある最適化されたパラメータ(「ストラテジーテスタ」にフラグが付けられています)は一番最初の部分に位置しています。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() 関数を使用して行われます。この関数が真を返せば、パラメータは最適化されます。

//+---------------------------------------------------------------------+
//| 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 に書きこまれます。最適化されたパラメータ名用配列には最初のパス時のみ書きこまれます。

そして2つのループを用いて検証の文字列とファイルに書くためのパラメータ値を作成します。続いて最初のパスで 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);
  }

最低1つ指定の基準を満たすのでよければ、 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);
  }

これですべてが設定され準備できました。ここでは OnTesterPass() 関数本体に CreateOptimizationReport() 関数を挿入し、 OnTesterDeinit() 関数内の最適化ファイルハンドルを閉じる必要があります。

そして Expert Advisor を検証します。パラメータは分散コンピューティング MQL5 クラウドネットワーク を用いて最適化されます。「ストラテジーテスタ」を下のスクリーンショットのように設定する必要があります。

図3 「ストラテジーテスタ」設定

図3 「ストラテジーテスタ」設定

Expert Advisor のパラメータをすべて最適化し、プロフィットファクター が 1 より大きく、リカバリーファクター が 2 より大きい結果だけがファイルに書き込まれるような基準のパラメータを設定します(スクリーンショット参照ください)。

図4 パラメータ最適化のための Expert Advisor 設定

図4 パラメータ最適化のための Expert Advisor 設定

分散コンピューティングの MQL5 クラウドネットワーク は5分以内にパスを 101,000 処理します!ネットワークリソースを利用しなければ、最適化を完了するまで数日かかったことでしょう。時間の価値がわかっている人には大きなチャンスです。

そして結果ファイルが Excelで開くことができます。ファイルに書き込むために101,000 個のパスから719 の結果が選択されました。下のスクリーンショットで結果を選択する基となるパラメータ行を強調表示しました。

図5 Excelに出力された最適化結果

図5 Excelに出力された最適化結果

 

おわりに

本稿も終わりに近づいてきました。最適化結果分析の話題を完全に語りつくすことはできていません。将来発表される記事でこの話題が繰り返されるのは確実です。本稿にみなさんの今後の考察のためにExpert Advisorのファイルを持つダウンロード可能なアーカイブを添付しています。

MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/746

添付されたファイル |
MQL5 標準ライブラリの拡張とコードの再利用 MQL5 標準ライブラリの拡張とコードの再利用
MQL5 標準ライブラリによってみなさんの開発者としての生活は楽になります。しかしながらそれは世界中の開発者全員のすべてのニーズを実装するわけではありません。よってみなさんがカスタム的なものをもっと必要とするなら一歩先へすすんで拡張することができます。本稿は MetaQuotesの Zig-Zag テクニカルインディケータを標準ライブラリに統合する方法をご紹介します。私達はMetaQuotes の設計哲学により自分自身の目標を達成しようという気持ちになります。
MQL5 クックブック:MetaTrader 5トレードイベントへの音声通知 MQL5 クックブック:MetaTrader 5トレードイベントへの音声通知
本稿では Expert Advisor のファイルに音声ファイルをインクルードすること、それによりトレードイベントに音声通知を追加するなどの問題を考察します。事実、ファイルのインクルードが意味するところは Expert Advisor内に音声ファイルを入れるということです。よってコンパイルされた Expert Advisor (*.ex5) バージョンを他のユーザーに提供する際、音声ファイルは提供せずそれがどこの保存されるか説明する必要があるのです。
テクニカルインディケータとデジタルフィルター テクニカルインディケータとデジタルフィルター
本稿ではデジタルフィルターとしてテクニカルインディケータを取り上げます。デジタルフィルターの処理原則と基本特性が説明されます。また、MetaTrader 5 ターミナルでフィルターカーネルを受け取る実用的な方法と記事 "Building a Spectrum Analyzer" で提案されている既製のスペクトル解析機能との統合について考察します。例として典型的デジタルフィルターのパルスとスペクトル特性を使用します。
MQL5 クックブック: インジケーターサブウィンドウコンソールーボタン MQL5 クックブック: インジケーターサブウィンドウコンソールーボタン
この記事では、ボタンコンソールでユーザーインターフェースを開発する例を紹介します。ユーザーにインタラクティヴィティ性を伝えるため、ボタンはカーソルが図上にある際に色を変えます。ボタンの上にカーソルがある状態で、ボタンの色は、わずかに暗くなり、ボタンがクリックされた時には、わずかにより暗くなります。さらに、ツールチップをそれぞれのボタンに加え、直感的なインターフェースを作成します。