
MQL5 Cookbook:指定の基準に基づく Expert Advisor 最適化結果の保存方法
はじめに
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 ジャーナルにプリントされた検証および最適化関数からのメッセージ
これでフォルダおよびファイルを作成、指定の最適化パラメータ判断、条件を満たす結果の書き込みを行う関数に進む準備が全て整いました。
ファイル FileFunctions.mqhを作成し、それをプロジェクトにインクルードします。このファイルの冒頭には、参照によって値のある最適化のパスをそれぞれ書き込む配列を取得する GetTestStatistics() 関数を書きます。
//+--------------------------------------------------------------------+ //| Filling the array with test results | //+--------------------------------------------------------------------+ void GetTestStatistics(double &stat_array[]) { //--- Auxiliary variables for value adjustment double profit_factor=0,sharpe_ratio=0; //--- stat_array[0]=TesterStatistics(STAT_PROFIT); // Net profit upon completion of testing stat_array[1]=TesterStatistics(STAT_DEALS); // Number of executed deals //--- profit_factor=TesterStatistics(STAT_PROFIT_FACTOR); // Profit factor – the STAT_GROSS_PROFIT/STAT_GROSS_LOSS ratio stat_array[2]=(profit_factor==DBL_MAX) ? 0 : profit_factor; // adjust if necessary //--- stat_array[3]=TesterStatistics(STAT_EXPECTED_PAYOFF); // Expected payoff stat_array[4]=TesterStatistics(STAT_EQUITY_DDREL_PERCENT); // Max. equity drawdown, % stat_array[5]=TesterStatistics(STAT_RECOVERY_FACTOR); // Recovery factor – the STAT_PROFIT/STAT_BALANCE_DD ratio //--- sharpe_ratio=TesterStatistics(STAT_SHARPE_RATIO); // Sharpe ratio - investment portfolio (asset) efficiency index stat_array[6]=(sharpe_ratio==DBL_MAX) ? 0 : sharpe_ratio; // adjust if necessary }
GetTestStatistics() 関数はフレーム追加以前に挿入する必要があります。
//+--------------------------------------------------------------------+ //| Test completion event handler | //+--------------------------------------------------------------------+ double OnTester() { //--- If writing of optimization results is enabled if(LogOptimizationReport) { //--- Fill the array with test values GetTestStatistics(stat_values); //--- Create a frame FrameAdd("Statistics",1,0,stat_values); } //--- return(0.0); }
書き込みが行われた配列は最後の引数として FrameAdd() 関数に渡されます。必要であればデータファイルも渡すことができます。
OnTesterPass() 関数では取得データの確認が可能です。それがどのように動作するか見るには、ただターミナルのジャーナル内にある各結果に対する収益を表示します。現在のフレーム値を取得するには FrameNext() を使います。以下の例を参照ください。
//+--------------------------------------------------------------------+ //| Next optimization pass | //+--------------------------------------------------------------------+ void OnTesterPass() { //--- If writing of optimization results is enabled if(LogOptimizationReport) { string name =""; // Public name/frame label ulong pass =0; // Number of the optimization pass at which the frame is added long id =0; // Public id of the frame double val =0.0; // Single numerical value of the frame //--- FrameNext(pass,name,id,val,stat_values); //--- Print(__FUNCTION__,"(): pass: "+IntegerToString(pass)+"; STAT_PROFIT: ",DoubleToString(stat_values[0],2)); } }
FrameNext() 関数を使わない場合、stat_values 配列の値はゼロとなります。ただしすべてが正しく行われると、下のスクリーンショットのような結果を得ることとなります。
図2 ジャーナルにプリントされた OnTesterPass() 関数からのメッセージ
ところで外部パラメータを変更することなく最適化を実行すると、結果は関数 OnTesterPass() および OnTesterDeinit() をバイパスしてキャッシュから「ストラテジーテスタ」にロードされます。エラーはないと考えることを忘れないようにします。
その上、FileFunctions.mqh では CreateOptimizationReport() 関数を作成します。主要な処理はこの関数内で行われます。以下がその関数のコードです。
//+--------------------------------------------------------------------+ //| Generating and writing report on optimization results | //+--------------------------------------------------------------------+ void CreateOptimizationReport() { static int passes_count=0; // Pass counter int parameters_count=0; // Number of Expert Advisor parameters int optimized_parameters_count=0; // Counter of optimized parameters string string_to_write=""; // String for writing bool include_criteria_list=false; // For determining the start of the list of parameters/criteria int equality_sign_index=0; // The '=' sign index in the string string name =""; // Public name/frame label ulong pass =0; // Number of the optimization pass at which the frame is added long id =0; // Public id of the frame double value =0.0; // Single numerical value of the frame string parameters_list[]; // List of the Expert Advisor parameters of the "parameterN=valueN" form string parameter_names[]; // Array of parameter names string parameter_values[]; // Array of parameter values //--- Increase the pass counter passes_count++; //--- Place statistical values into the array FrameNext(pass,name,id,value,stat_values); //--- Get the pass number, list of parameters, number of parameters FrameInputs(pass,parameters_list,parameters_count); //--- Iterate over the list of parameters in a loop (starting from the upper one on the list) // The list starts with the parameters that are flagged for optimization for(int i=0; i<parameters_count; i++) { //--- Get the criteria for selection of results at the first pass if(passes_count==1) { string current_value=""; // Current parameter value static int c=0,v=0,trigger=0; // Counters and trigger //--- Set a flag if you reached the list of criteria if(StringFind(parameters_list[i],"CriterionSelectionRule",0)>=0) { include_criteria_list=true; continue; } //--- At the last parameter, count the used criteria, // if the AND mode is selected if(CriterionSelectionRule==RULE_AND && i==parameters_count-1) CalculateUsedCriteria(); //--- If you reached criteria in the parameter list if(include_criteria_list) { //--- Determine names of criteria if(trigger==0) { equality_sign_index=StringFind(parameters_list[i],"=",0)+1; // Determine the '=' sign position in the string current_value =StringSubstr(parameters_list[i],equality_sign_index); // Get the parameter value //--- criteria[c]=(int)StringToInteger(current_value); trigger=1; // Next parameter will be a value c++; continue; } //--- Determine values of criteria if(trigger==1) { equality_sign_index=StringFind(parameters_list[i],"=",0)+1; // Determine the '=' sign position in the string current_value=StringSubstr(parameters_list[i],equality_sign_index); // Get the parameter value //--- criteria_values[v]=StringToDouble(current_value); trigger=0; // Next parameter will be a criterion v++; continue; } } } //--- If the parameter is enabled for optimization if(ParameterEnabledForOptimization(parameters_list[i])) { //--- Increase the counter of the optimized parameters optimized_parameters_count++; //--- Write the names of the optimized parameters to the array // only at the first pass (for headers) if(passes_count==1) { //--- Increase the size of the array of parameter values ArrayResize(parameter_names,optimized_parameters_count); //--- Determine the '=' sign position equality_sign_index=StringFind(parameters_list[i],"=",0); //--- Take the parameter name parameter_names[i]=StringSubstr(parameters_list[i],0,equality_sign_index); } //--- Increase the size of the array of parameter values ArrayResize(parameter_values,optimized_parameters_count); //--- Determine the '=' sign position equality_sign_index=StringFind(parameters_list[i],"=",0)+1; //--- Take the parameter value parameter_values[i]=StringSubstr(parameters_list[i],equality_sign_index); } } //--- Generate a string of values to the optimized parameters for(int i=0; i<STAT_VALUES_COUNT; i++) StringAdd(string_to_write,DoubleToString(stat_values[i],2)+","); //--- Add values of the optimized parameters to the string of values for(int i=0; i<optimized_parameters_count; i++) { //--- If it is the last value in the string, do not use the separator if(i==optimized_parameters_count-1) { StringAdd(string_to_write,parameter_values[i]); break; } //--- Otherwise use the separator else StringAdd(string_to_write,parameter_values[i]+","); } //--- At the first pass, generate the optimization report file with headers if(passes_count==1) WriteOptimizationReport(parameter_names); //--- Write data to the file of optimization results WriteOptimizationResults(string_to_write); }
かなり長い関数になってしまいました。それをじっくりと見ます。冒頭では、変数と配列の宣言直後、上記例に示されているように FrameNext() 関数を用いてフレームデータを取得します。それから FrameInputs() を使ってparameters_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 ¶meter_names[]) { int files_count =1; // Counter of optimization files //--- Generate a header to the optimized parameters string headers="#,PROFIT,TOTAL DEALS,PROFIT FACTOR,EXPECTED PAYOFF,EQUITY DD MAX REL%,RECOVERY FACTOR,SHARPE RATIO,"; //--- Add the optimized parameters to the header for(int i=0; i<ArraySize(parameter_names); i++) { if(i==ArraySize(parameter_names)-1) StringAdd(headers,parameter_names[i]); else StringAdd(headers,parameter_names[i]+","); } //--- Get the location for the optimization file and the number of files for the index number OptimizationResultsPath=CreateOptimizationResultsFolder(files_count); //--- If there is an error when getting the folder, exit if(OptimizationResultsPath=="") { Print("Empty path: ",OptimizationResultsPath); return; } else { OptimizationFileHandle=FileOpen(OptimizationResultsPath+"\optimization_results"+IntegerToString(files_count)+".csv", FILE_CSV|FILE_READ|FILE_WRITE|FILE_ANSI|FILE_COMMON,","); //--- if(OptimizationFileHandle!=INVALID_HANDLE) FileWrite(OptimizationFileHandle,headers); } }
WriteOptimizationReport() 関数の目的はヘッダの作成、必要であればターミナルの共通フォルダ内にフォルダの作成、書き込みのためのファイルを作成することです。前回の最適化に関連づいたファイルは消去され、この関数がインデックス番号と共に毎回新しいファイルを作成します。ヘッダは新しく作成されたファイルに保存されます。このファイル自体は最適化が終了するまで開いたままとなります。
上記のコードには CreateOptimizationResultsFolder() 関数を持つ文字列があり、そこで最適化結果と共にファイル保存用フォルダーが作成されます。
//+--------------------------------------------------------------------+ //| Creating folders for optimization results | //+--------------------------------------------------------------------+ string CreateOptimizationResultsFolder(int &files_count) { long search_handle =INVALID_HANDLE; // Search handle string returned_filename =""; // Name of the found object (file/folder) string path =""; // File/folder search location string search_filter ="*"; // Search filter (* - check all files/folders) string root_folder ="OPTIMIZATION_DATA\\"; // Root folder string expert_folder =EXPERT_NAME+"\\"; // Folder of the Expert Advisor bool root_folder_exists =false; // Flag of existence of the root folder bool expert_folder_exists=false; // Flag of existence of the Expert Advisor folder //--- Search for the OPTIMIZATION_DATA root folder in the common folder of the terminal path=search_filter; //--- Set the search handle in the common folder of all client terminals \Files search_handle=FileFindFirst(path,returned_filename,FILE_COMMON); //--- Print the location of the common folder of the terminal to the journal Print("TERMINAL_COMMONDATA_PATH: ",COMMONDATA_PATH); //--- If the first folder is the root folder, flag it if(returned_filename==root_folder) { root_folder_exists=true; Print("The "+root_folder+" root folder exists."); } //--- If the search handle has been obtained if(search_handle!=INVALID_HANDLE) { //--- If the first folder is not the root folder if(!root_folder_exists) { //--- Iterate over all files to find the root folder while(FileFindNext(search_handle,returned_filename)) { //--- If it is found, flag it if(returned_filename==root_folder) { root_folder_exists=true; Print("The "+root_folder+" root folder exists."); break; } } } //--- Close the root folder search handle FileFindClose(search_handle); } else { Print("Error when getting the search handle " "or the "+COMMONDATA_PATH+" folder is empty: ",ErrorDescription(GetLastError())); } //--- Search for the Expert Advisor folder in the OPTIMIZATION_DATA folder path=root_folder+search_filter; //--- Set the search handle in the ..\Files\OPTIMIZATION_DATA\ folder search_handle=FileFindFirst(path,returned_filename,FILE_COMMON); //--- If the first folder is the folder of the Expert Advisor if(returned_filename==expert_folder) { expert_folder_exists=true; // Remember this Print("The "+expert_folder+" Expert Advisor folder exists."); } //--- If the search handle has been obtained if(search_handle!=INVALID_HANDLE) { //--- If the first folder is not the folder of the Expert Advisor if(!expert_folder_exists) { //--- Iterate over all files in the DATA_OPTIMIZATION folder to find the folder of the Expert Advisor while(FileFindNext(search_handle,returned_filename)) { //--- If it is found, flag it if(returned_filename==expert_folder) { expert_folder_exists=true; Print("The "+expert_folder+" Expert Advisor folder exists."); break; } } } //--- Close the root folder search handle FileFindClose(search_handle); } else Print("Error when getting the search handle or the "+path+" folder is empty."); //--- Generate the path to count the files path=root_folder+expert_folder+search_filter; //--- Set the search handle in the ..\Files\OPTIMIZATION_DATA\ folder of optimization results search_handle=FileFindFirst(path,returned_filename,FILE_COMMON); //--- If the folder is not empty, start the count if(StringFind(returned_filename,"optimization_results",0)>=0) files_count++; //--- If the search handle has been obtained if(search_handle!=INVALID_HANDLE) { //--- Count all files in the Expert Advisor folder while(FileFindNext(search_handle,returned_filename)) files_count++; //--- Print("Total files: ",files_count); //--- Close the Expert Advisor folder search handle FileFindClose(search_handle); } else Print("Error when getting the search handle or the "+path+" folder is empty"); //--- Create the necessary folders based on the check results // If there is no OPTIMIZATION_DATA root folder if(!root_folder_exists) { if(FolderCreate("OPTIMIZATION_DATA",FILE_COMMON)) { root_folder_exists=true; Print("The root folder ..\Files\OPTIMIZATION_DATA\\ has been created"); } else { Print("Error when creating the OPTIMIZATION_DATA root folder: ", ErrorDescription(GetLastError())); return(""); } } //--- If there is no Expert Advisor folder if(!expert_folder_exists) { if(FolderCreate(root_folder+EXPERT_NAME,FILE_COMMON)) { expert_folder_exists=true; Print("The Expert Advisor folder ..\Files\OPTIMIZATION_DATA\\ has been created"+expert_folder); } else { Print("Error when creating the Expert Advisor folder ..\Files\\"+expert_folder+"\: ", ErrorDescription(GetLastError())); return(""); } } //--- If the necessary folders exist if(root_folder_exists && expert_folder_exists) { //--- Return the location for creating the file of optimization results return(root_folder+EXPERT_NAME); } //--- return(""); }
上記のコードは詳細コメントを伴って提供されているので理解するのに難しいことはないはずです。主要ポイントの要点を述べます。
まず、最適化結果を持つルートフォルダOPTIMIZATION_DATA を確認します。フォルダーが存在すれば、これは root_folder_exists 変数内でマークされます。それから 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 「ストラテジーテスタ」設定
Expert Advisor のパラメータをすべて最適化し、プロフィットファクター が 1 より大きく、リカバリーファクター が 2 より大きい結果だけがファイルに書き込まれるような基準のパラメータを設定します(スクリーンショット参照ください)。
図4 パラメータ最適化のための Expert Advisor 設定
分散コンピューティングの MQL5 クラウドネットワーク は5分以内にパスを 101,000 処理します!ネットワークリソースを利用しなければ、最適化を完了するまで数日かかったことでしょう。時間の価値がわかっている人には大きなチャンスです。
そして結果ファイルが Excelで開くことができます。ファイルに書き込むために101,000 個のパスから719 の結果が選択されました。下のスクリーンショットで結果を選択する基となるパラメータ行を強調表示しました。
図5 Excelに出力された最適化結果
おわりに
本稿も終わりに近づいてきました。最適化結果分析の話題を完全に語りつくすことはできていません。将来発表される記事でこの話題が繰り返されるのは確実です。本稿にみなさんの今後の考察のためにExpert Advisorのファイルを持つダウンロード可能なアーカイブを添付しています。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/746





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索