English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
MQL5 クックブック:パラメータ数無制限での複数通貨対応 EXPERT 作成

MQL5 クックブック:パラメータ数無制限での複数通貨対応 EXPERT 作成

MetaTrader 5 | 26 11月 2015, 07:36
745 0
Anatoli Kazharski
Anatoli Kazharski

はじめに

以前の記事 "MQL5 Cookbook: Multi-Currency Expert Advisor - Simple, Neat and Quick Approach"で考察されている複数通貨対応 Expert Advisor は使用されるシンボルと戦略パラメータ数がちいさい場合はひじょうに便利です。ただし MQL5では Expert Advisor の入力パラメータ数に制約があります。1024を越えてはいけないのです。

多くの場合この数は十分であってもそのように大きなパラメータリストを使うことはひじょうに不便です。既定のシンボルに対するパラメータに変更や最適化が必要となるたびに長いパラメータリストでその指定のシンボルに対するパラメータを探す必要があります。

より詳しい制約についてはクライアント端末の「ヘルプ」の入力 セクションで知ることができます。

本稿ではトレーディングシステムの最適化に対して一組のパラメータを使うパターンを作成していきます。同時にパラメータ数は無制限に認めます。シンボルリストは標準的なテキストファイル (*.txt) 内に作成します。各シンボルに対する入力パラメータもファイルに格納されます。

Expert Advisor は通常のオペレーションモードではシンボル一つについて動作しますが、「ストラテジーテスタ」では選択した多様なシンボルについて(各シンボル個別に)検証を行うことが可能であることをお伝えする必要があります。

既製のシンボルセットを保存することができることを考慮すると、実際シンボルリストを銘柄リストウィンドウで直接作成する方が都合がよいことでしょう。Expert Advisor が「ストラテジーテスタ」から直接「銘柄リスト」ウィンドウのシンボルリストを取得できるようにすることも可能かもしれません。ただ残念ながら現時点では「ストラテジーテスタ」から「銘柄リスト」にアクセスすることはできないため、事前にマニュアルでまたはスクリプトを持ち手シンボルリストを作成する必要があります。


Expert Advisor の作成

前稿 "MQL5 Cookbook: Multi-Currency Expert Advisor - Simple, Neat and Quick Approach" で取り上げている複数通過対応 Expert Advisor はテンプレートとして取り入れられます。まず入力パラメータを決めます。上述のようにパラメータは1組のみ残します。以下はExpert Advisorの入力パラメータリストです。

//--- Input parameters of the Expert Advisor
sinput long                      MagicNumber          =777;      // Magic number
sinput int                       Deviation            =10;       // Slippage
sinput string                    delimeter_00=""; // --------------------------------
sinput int                       SymbolNumber         =1;        // Number of the tested symbol
sinput bool                      RewriteParameters    =false;    // Rewriting parameters
sinput ENUM_INPUTS_READING_MODE  ParametersReadingMode=FILE;     // Parameter reading mode
sinput string                    delimeter_01=""; // --------------------------------
input  int                       IndicatorPeriod      =5;        // Indicator period
input  double                    TakeProfit           =100;      // Take Profit
input  double                    StopLoss             =50;       // Stop Loss
input  double                    TrailingStop         =10;       // Trailing Stop
input  bool                      Reverse              =true;     // Position reversal
input  double                    Lot                  =0.1;      // Lot
input  double                    VolumeIncrease       =0.1;      // Position volume increase
input  double                    VolumeIncreaseStep   =10;       // Volume increase step

入力モディファイアを持つパラメータのみファイルに書き込まれます。後に、これまで出くわしたことのない新しいパラメータ3つに関して拡張する必要があります。

  • SymbolNumber -このパラメータはシンボルリストを持つファイルからシンボル番号を指示します。0に設定されているとリストからの全シンボルが検証されます。リスト以上におよぶと検証は行われません。
  • RewriteParameters -このパラメータ値が真に設定されていれば、指定されたシンボルのパラメータ(SymbolNumber内の番号)を持つファイルが現在の入力パラメータ値を用いて再度書き込まれます。またこのパラメータがに設定されていればファイルからパラメータが読みだされます。
  • ParametersReadingMode -このパラメータは入力パラメータに関して読み出しモードを指示します。パラメータタイプはオプションを2つ提供する ENUM_INPUTS_READING_MODE カスタムリストです。そのオプションはファイルからの読み出しと現在パラメータの使用です。

以下がENUM_INPUTS_READING_MODE 列挙コードです。

//+------------------------------------------------------------------+
//| Input parameter reading modes                                    |
//+------------------------------------------------------------------+
enum ENUM_INPUTS_READING_MODE
  {
   FILE             = 0, // File
   INPUT_PARAMETERS = 1  // Input parameters
  };

シンボル番号は NUMBER_OF_SYMBOLS 定数を用いて事前に決められています。ここでこの値は異なるモードに依存するため、それをグローバル変数 SYMBOLS_COUNT に変更します。

//--- Number of traded symbols. It is calculated and depends on the testing mode and the number of symbols in the file
int SYMBOLS_COUNT=0に依存します。

Expert Advisorコードの冒頭では別の定数TESTED_PARAMETERS_COUNTを宣言します。それは配列サイズと入力パラメータを反復する場合はループ反復回数を決めます。

//--- Number of tested/optimized parameters
#define TESTED_PARAMETERS_COUNT 8

シンボルリストを持つファイルは、各シンボルにたいするパラメータを持つファイルフォルダ同様、端末の共通フォルダーに入れる必要があります。それは Expert Advisor の通常処理モードおよび検証中のどちらでもプログラムレベルにアクセス可能となるためです。

ここでのちに作業する配列の準備をします。変更をしようとしている前稿から流用している複数通貨対応 Expert Advisor の配列はすべて静的です。すなわちエレメントのサイズが事前に設定されているのです。ところがここではエレメントサイズはファイルで使用されるシンボル番号と入力パラメータの読み出しモードに依存するため配列をすべて動的に変更する必要があります。またファイル作業に必要な新しい配列(黄色で強調表示しています)を追加しようと思います。

//--- Arrays of input parameters
string InputSymbols[];            // Symbol names
//---
int    InputIndicatorPeriod[];    // Indicator periods
double InputTakeProfit[];         // Take Profit values
double InputStopLoss[];           // Stop Loss values
double InputTrailingStop[];       // Trailing Stop values
bool   InputReverse[];            // Values of position reversal flags
double InputLot[];                // Lot values
double InputVolumeIncrease[];     // Position volume increases
double InputVolumeIncreaseStep[]; // Volume increase steps
//--- Array of handles for indicator agents
int spy_indicator_handles[];
//--- Array of signal indicator handles
int signal_indicator_handles[];
//--- Data arrays for checking trading conditions
struct PriceData
  {
   double            value[];
  };
PriceData open[];      // Opening price of the bar
PriceData high[];      // High price of the bar
PriceData low[];       // Low price of the bar
PriceData close[];     // Closing price of the bar
PriceData indicator[]; // Array of indicator values
//--- Arrays for getting the opening time of the current bar
struct Datetime
  {
   datetime          time[];
  };
Datetime lastbar_time[];
//--- Array for checking the new bar for each symbol
datetime new_bar[];
//--- Array of input parameter names for writing to the file
string input_parameters[TESTED_PARAMETERS_COUNT]=
  {
   "IndicatorPeriod",   // Indicator period
   "TakeProfit",        // Take Profit
   "StopLoss",          // Stop Loss
   "TrailingStop",      // Trailing Stop
   "Reverse",           // Position reversal
   "Lot",               // Lot
   "VolumeIncrease",    // Position volume increase
   "VolumeIncreaseStep" // Volume increase step
  };
//--- Array for untested symbols
string temporary_symbols[];
//--- Array for storing input parameters from the file of the symbol selected for testing or trading
double tested_parameters_from_file[];
//--- Array of input parameter values for writing to the file
double tested_parameters_values[TESTED_PARAMETERS_COUNT];

それから配列サイズを決め、値に対して起動します。

上記で作成された入力パラメータ値の配列は、 InitializeArrays.mqhファイルに作成する予定のInitializeTestedParametersValues()関数内で初期化します。その配列はファイルに入力パラメータ値を書き込むのに使用されます。

//+------------------------------------------------------------------+
//| Initializing the array of tested input parameters                |
//+------------------------------------------------------------------+
void InitializeTestedParametersValues()
  {
   tested_parameters_values[0]=IndicatorPeriod;
   tested_parameters_values[1]=TakeProfit;
   tested_parameters_values[2]=StopLoss;
   tested_parameters_values[3]=TrailingStop;
   tested_parameters_values[4]=Reverse;
   tested_parameters_values[5]=Lot;
   tested_parameters_values[6]=VolumeIncrease;
   tested_parameters_values[7]=VolumeIncreaseStep;
  }

ではファイル処理について考察しましょう。まずもう一つ別の関数ライブラリ、FileFunctions.mqhを Expert Advisor のUnlimitedParametersEA\Include フォルダに作成します。このライブラリはファイルからデータを読んだりファイルにデータを書き込む関数を作成する際に利用します。それを Expert Advisor のメインファイルおよびプロジェクトのその他ファイルにインクルードします。

//---Include custom libraries
#include "Include\Enums.mqh"
#include "Include\InitializeArrays.mqh"
#include "Include\Errors.mqh"
#include "Include\FileFunctions.mqh"
#include "Include\TradeSignals.mqh"
#include "Include\TradeFunctions.mqh"
#include "Include\ToString.mqh"
#include "Include\Auxiliary.mqh"

テクストファイル:ReadSymbolsFromFile()からシンボルリストを読み出す関数を作成することからスタートします。このファイル( TestedSymbols.txtと名前を付けましょう)はMetaTrader 5 クライアントターミナルの共通フォルダのサブフォルダ \Files に入れます。私の場合それはC:\ProgramData\MetaQuotes\Terminal\Common ですが、標準定数 TERMINAL_COMMONDATA_PATH を用いてパスを注意して確認します。

TerminalInfoString(TERMINAL_COMMONDATA_PATH)
カスタムファイルはすべてクライアントターミナルの共通フォルダのサブフォルダ \Files<Terminal Data Folder>\MQL5\Files\に入れます。これらフォルダにはファイル関数 のみアクセス可能です。

機能性を確認するには、作成したテキストファイルにシンボルを数個追加します。その際、各シンボルは改行 ("\r\n")で分離します。

図1 ターミナルの共通フォルダのファイル内にあるシンボルリスト

図1 ターミナルの共通フォルダのファイル内にあるシンボルリスト

それから ReadSymbolsFromFile() 関数コードを見ていきます。

//+------------------------------------------------------------------+
//| Returning the number of strings (symbols) in the file and        |
//| filling the temporary array of symbols temporary_symbols[]       |
//+------------------------------------------------------------------+
//--- When preparing the file, symbols in the list should be separated with a line break
int ReadSymbolsFromFile(string file_name)
  {
   int strings_count=0; // String counter
//--- Open the file for reading from the common folder of the terminal
   int file_handle=FileOpen(file_name,FILE_READ|FILE_ANSI|FILE_COMMON);
//--- If the file handle has been obtained
   if(file_handle!=INVALID_HANDLE)
     {
      ulong  offset =0; // Offset for determining the position of the file pointer
      string text ="";  // The read string will be written to this variable
      //--- Read until the current position of the file pointer reaches the end of the file or until the program is deleted
      while(!FileIsEnding(file_handle) || !IsStopped())
        {
         //--- Read until the end of the string or until the program is deleted
         while(!FileIsLineEnding(file_handle) || !IsStopped())
           {
            //--- Read the whole string
            text=FileReadString(file_handle);
            //--- Get the position of the pointer
            offset=FileTell(file_handle);
            //--- Go to the next string if this is not the end of the file
            //    For this purpose, increase the offset of the file pointer
            if(!FileIsEnding(file_handle))
               offset++;
            //--- Move it to the next string
            FileSeek(file_handle,offset,SEEK_SET);
            //--- If the string is not empty
            if(text!="")
              {
               //--- Increase the string counter
               strings_count++;
               //--- Increase the size of the array of strings,
               ArrayResize(temporary_symbols,strings_count);
               //--- Write the read string to the current index
               temporary_symbols[strings_count-1]=text;
              }
            //--- Exit the nested loop
            break;
           }
         //--- If this is the end of the file, terminate the main loop
         if(FileIsEnding(file_handle))
            break;
        }
      //--- Close the file
      FileClose(file_handle);
     }
//--- Return the number of strings in the file
   return(strings_count);
  }

ここでテキストファイルが一行ずつ読まれます。読み出された各ファイナンシャルインスツルメントの名前は前に作成済みの一時的な temporary_symbols[] 配列に書き込まれます(トレードサーバー上のシンボルの実際のアクセスしやすさ は後に確認します)。また関数の末尾では読み出された文字列数が返されます。すなわちわれわれの Expert Advisor が検証される対処うのシンボル数です。

前に扱ったことのない関数や識別子に関する詳細情報はつねに MQL5 参考資料を参照してください。コード内のコメントはただ学習仮定にすぎません。

前に宣言されたシンボル名の InputSymbols[] 配列に情報を書き込むためのInitializeInputSymbols()関数を作成するInitializeArrays.mqh ファイルの話に戻ります。初心者の方はおそらく複雑だと思われるでしょう。そこでコードには詳しいコメントを施しました。

//+------------------------------------------------------------------+
//| Filling the InputSymbol[] array of symbols                       |
//+------------------------------------------------------------------+
void InitializeInputSymbols()
  {
   int    strings_count=0;    // Number of strings in the symbol file
   string checked_symbol="";  // To check the accessibility of the symbol on the trade server
//--- Get the number of symbols from the "TestedSymbols.txt" file
   strings_count=ReadSymbolsFromFile("TestedSymbols.txt");
//--- In optimization mode or in one of the two modes (testing or visualization), provided that the symbol NUMBER IS SPECIFIED
   if(IsOptimization() || ((IsTester() || IsVisualMode()) && SymbolNumber>0))
     {
      //--- Determine the symbol to be involved in parameter optimization
      for(int s=0; s<strings_count; s++)
        {
         //--- If the number specified in the parameters and the current loop index match
         if(s==SymbolNumber-1)
           {
            //--- Check whether the symbol is on the trade server
            if((checked_symbol=GetSymbolByName(temporary_symbols[s]))!="")
              {
               //--- Set the number of symbols
               SYMBOLS_COUNT=1;
               //--- Set the size of the array of symbols
               ArrayResize(InputSymbols,SYMBOLS_COUNT);
               //--- Write the symbol name
               InputSymbols[0]=checked_symbol;
              }
            //--- Exit
            return;
           }
        }
     }
//--- In testing or visualization mode, if you need to test ALL symbols from the list in the file
   if((IsTester() || IsVisualMode()) && SymbolNumber==0)
     {
      //--- Parameter reading mode: from the file
      if(ParametersReadingMode==FILE)
        {
         //--- Iterate over all symbols in the file
         for(int s=0; s<strings_count; s++)
           {
            //--- Check if the symbol is on the trade server
            if((checked_symbol=GetSymbolByName(temporary_symbols[s]))!="")
              {
               //--- Increase the symbol counter
               SYMBOLS_COUNT++;
               //--- Set the size of the array of symbols
               ArrayResize(InputSymbols,SYMBOLS_COUNT);
               //--- Write the symbol name
               InputSymbols[SYMBOLS_COUNT-1]=checked_symbol;
              }
           }
         //--- Exit
         return;
        }
      //--- Parameter reading mode: from input parameters of the Expert Advisor
      if(ParametersReadingMode==INPUT_PARAMETERS)
        {
         //--- Set the number of symbols
         SYMBOLS_COUNT=1;
         //--- Set the size of the array of symbols
         ArrayResize(InputSymbols,SYMBOLS_COUNT);
         //--- Write the current symbol name
         InputSymbols[0]=Symbol();
         //--- Exit
         return;
        }
     }
//--- In normal operation mode of the Expert Advisor, use the current chart symbol
   if(IsRealtime())
     {
      //--- Set the number of symbols
      SYMBOLS_COUNT=1;
      //--- Set the size of the array of symbols
      ArrayResize(InputSymbols,SYMBOLS_COUNT);
      //--- Write the symbol name
      InputSymbols[0]=Symbol();
     }
//---
  }

使用されるシンボル数によって入力パラメータの配列サイズが決まると、入力パラメータのその他配列すべてについてサイズを設定します。それを別の関数:ResizeInputParametersArrays()として実装します。

//+------------------------------------------------------------------+
//| Setting the new size for arrays of input parameters              |
//+------------------------------------------------------------------+
void ResizeInputParametersArrays()
  {
   ArrayResize(InputIndicatorPeriod,SYMBOLS_COUNT);
   ArrayResize(InputTakeProfit,SYMBOLS_COUNT);
   ArrayResize(InputStopLoss,SYMBOLS_COUNT);
   ArrayResize(InputTrailingStop,SYMBOLS_COUNT);
   ArrayResize(InputReverse,SYMBOLS_COUNT);
   ArrayResize(InputLot,SYMBOLS_COUNT);
   ArrayResize(InputVolumeIncrease,SYMBOLS_COUNT);
   ArrayResize(InputVolumeIncreaseStep,SYMBOLS_COUNT);
  }

ここで各シンボルについて入力パラメータ値を読むことを可能にする機能を作成する必要があります。同時に選択される入力モードおよび Expert Advisor の設定に応じてこれらパラメータ値を別個のファイルに書き込む機能も作成します。これはファイルからシンボルリストを読み出すのと似た方法で行います。複雑なタスクなので複数の手順に分けましょう。

まず、ファイルから配列に入力パラメータ読み出しを学習します。配列はダブルタイプの値を持ちます。のちにそれらのいくつか(インディケータ期間とポジション逆フラグ)をそれぞれ int タイプとブールタイプに変換します。オープンファイルのハンドルとファイルからパラメータ値が格納される配列が ReadInputParametersValuesFromFile() 関数に渡されます。

//+----------------------------------------------------------------------+
//| Reading parameters from the file and storing them in the passed array|
//| Text file format: key=value
//+----------------------------------------------------------------------+
bool ReadInputParametersValuesFromFile(int handle,double &array[])
  {
   int    delimiter_position  =0;  // Number of the symbol position "=" in the string
   int    strings_count       =0;  // String counter
   string read_string         =""; // The read string
   ulong  offset              =0;  // Position of the pointer (offset in bytes)
//--- Move the file pointer to the beginning of the file
   FileSeek(handle,0,SEEK_SET);
//--- Read until the current position of the file pointer reaches the end of the file
   while(!FileIsEnding(handle))
     {
      //--- If the user deleted the program
      if(IsStopped())
         return(false);
      //--- Read until the end of the string
      while(!FileIsLineEnding(handle))
        {
         //--- If the user deleted the program
         if(IsStopped())
            return(false);
         //--- Read the string
         read_string=FileReadString(handle);
         //--- Get the index of the separator ("=") in the read string
         delimiter_position=StringFind(read_string,"=",0);
         //--- Get everything that follows the separator ("=") until the end of the string
         read_string=StringSubstr(read_string,delimiter_position+1);
         //--- Place the obtained value converted to the double type in the output array
         array[strings_count]=StringToDouble(read_string);
         //--- Get the current position of the file pointer
         offset=FileTell(handle);
         //--- If it's the end of the string
         if(FileIsLineEnding(handle))
           {
            //--- Go to the next string if it's not the end of the file
            if(!FileIsEnding(handle))
               //--- Increase the offset of the file pointer by 1 to go to the next string
               offset++;
            //--- Move the file pointer relative to the beginning of the file
            FileSeek(handle,offset,SEEK_SET);
            //--- Increase the string counter
            strings_count++;
            //--- Exit the nested loop for reading the string
            break;
           }
        }
      //--- If it's the end of the file, exit the main loop
      if(FileIsEnding(handle))
         break;
     }
//--- Return the fact of successful completion
   return(true);
  }

この関数にはどのファイルハンドルと配列を渡すのでしょうか?それは "TestedSymbols.txt" ファイルから読み出してからどのシンボルを処理するかによります。シンボルはそれぞれ入力パラメータ値を持つ特定のテキストファイルに対応します。考察するシナリオが2とおりあります。

  1. ファイルが存在し、上述の関数 ReadInputParametersValuesFromFile() を用いてファイルから入力パラメータ値を読み出す、というもの。
  2. ファイルが存在しない、または既存の入力パラメータ値を書き直す必要がある場合。

入力パラメータ値を持つテキストファイルのフォーマット(.ini ファイルですが、必要に応じて考察する別の拡張ファイルを選択することも可能です)はシンプルです。

input_parameter_name1=value
input_parameter_name2=value
....
input_parameter_nameN=value

このロジックを単独の関数:ReadWriteInputParameters()内で融合します。以下がそのコードです。

//+------------------------------------------------------------------+
//| Reading/writing input parameters from/to a file for a symbol     |
//+------------------------------------------------------------------+
void ReadWriteInputParameters(int symbol_number,string path)
  {
   string file_name=path+InputSymbols[symbol_number]+".ini"; // File name
//---
   Print("Find the file '"+file_name+"' ...");
//--- Open the file with input parameters of the symbol
   int file_handle_read=FileOpen(file_name,FILE_READ|FILE_ANSI|FILE_COMMON);
   
//--- Scenario #1: the file exists and parameter values do not need to be rewritten
   if(file_handle_read!=INVALID_HANDLE && !RewriteParameters)
     {
      Print("The file '"+InputSymbols[symbol_number]+".ini' exists, reading...");
      //--- Set the array size
      ArrayResize(tested_parameters_from_file,TESTED_PARAMETERS_COUNT);
      //--- Fill the array with file values
      ReadInputParametersValuesFromFile(file_handle_read,tested_parameters_from_file);
      //--- If the array size is correct
      if(ArraySize(tested_parameters_from_file)==TESTED_PARAMETERS_COUNT)
        {
         //--- Write parameter values to arrays
         InputIndicatorPeriod[symbol_number]    =(int)tested_parameters_from_file[0];
         Print("InputIndicatorPeriod[symbol_number] = "+(string)InputIndicatorPeriod[symbol_number]);
         InputTakeProfit[symbol_number]         =tested_parameters_from_file[1];
         InputStopLoss[symbol_number]           =tested_parameters_from_file[2];
         InputTrailingStop[symbol_number]       =tested_parameters_from_file[3];
         InputReverse[symbol_number]            =(bool)tested_parameters_from_file[4];
         InputLot[symbol_number]                =tested_parameters_from_file[5];
         InputVolumeIncrease[symbol_number]     =tested_parameters_from_file[6];
         InputVolumeIncreaseStep[symbol_number] =tested_parameters_from_file[7];
        }
      //--- Close the file and exit
      FileClose(file_handle_read);
      return;
     }
//--- Scenario #2: If the file does not exist or the parameters need to be rewritten
   if(file_handle_read==INVALID_HANDLE || RewriteParameters)
     {
      //--- Close the handle of the file for reading
      FileClose(file_handle_read);
      //--- Get the handle of the file for writing
      int file_handle_write=FileOpen(file_name,FILE_WRITE|FILE_CSV|FILE_ANSI|FILE_COMMON,"");
      //--- If the handle has been obtained
      if(file_handle_write!=INVALID_HANDLE)
        {
         string delimiter="="; // Separator
         //--- Write the parameters
         for(int i=0; i<TESTED_PARAMETERS_COUNT; i++)
           {
            FileWrite(file_handle_write,input_parameters_names[i],delimiter,tested_parameters_values[i]);
            Print(input_parameters_names[i],delimiter,tested_parameters_values[i]);
           }
         //--- Write parameter values to arrays
         InputIndicatorPeriod[symbol_number]    =(int)tested_parameters_values[0];
         InputTakeProfit[symbol_number]         =tested_parameters_values[1];
         InputStopLoss[symbol_number]           =tested_parameters_values[2];
         InputTrailingStop[symbol_number]       =tested_parameters_values[3];
         InputReverse[symbol_number]            =(bool)tested_parameters_values[4];
         InputLot[symbol_number]                =tested_parameters_values[5];
         InputVolumeIncrease[symbol_number]     =tested_parameters_values[6];
         InputVolumeIncreaseStep[symbol_number] =tested_parameters_values[7];
         //--- Depending on the indication, print the relevant message
         if(RewriteParameters)
            Print("The file '"+InputSymbols[symbol_number]+".ini' with parameters of the '"+EXPERT_NAME+".ex5 Expert Advisor has been rewritten'");
         else
            Print("The file '"+InputSymbols[symbol_number]+".ini' with parameters of the '"+EXPERT_NAME+".ex5 Expert Advisor has been created'");
        }
      //--- Close the handle of the file for writing
      FileClose(file_handle_write);
     }
  }

最後のファイル関数: CreateInputParametersFolder() クライアントターミナルの共通フォルダ内の Expert Advisor の名前でフォルダを作成します。これは入力パラメータ値を持つテキストファイル(われわれの場合 .ini ファイルです)が読み出し/書き込みが行われるフォルダです。前の関数とちょうど同じようにフォルダが存在するか確認します。フォルダがうまく作成されるかすでに存在していたらこの関数はパスを返します。またエラーの場合は空の文字列を返します。

//+----------------------------------------------------------------------------------+
//| Creating a folder for files of input parameters in case the folder does not exist|
//| and returns the path in case of success                                          |
//+----------------------------------------------------------------------------------+
string CreateInputParametersFolder()
  {
   long   search_handle       =INVALID_HANDLE;   // Folder/file search handle
   string EA_root_folder      =EXPERT_NAME+"\\"; // Root folder of the Expert Advisor
   string returned_filename   ="";               // Name of the found object (file/folder)
   string search_path         ="";               // Search path
   string folder_filter       ="*";              // Search filter (* - check all files/folders)
   bool   is_root_folder      =false;            // Flag of existence/absence of the root folder of the Expert Advisor

//--- Find the root folder of the Expert Advisor
   search_path=folder_filter;
//--- Set the search handle in the common folder of the terminal
   search_handle=FileFindFirst(search_path,returned_filename,FILE_COMMON);
//--- If the first folder is the root folder, flag it
   if(returned_filename==EA_root_folder)
      is_root_folder=true;
//--- If the search handle has been obtained
   if(search_handle!=INVALID_HANDLE)
     {
      //--- If the first folder is not the root folder
      if(!is_root_folder)
        {
         //--- Iterate over all files to find the root folder
         while(FileFindNext(search_handle,returned_filename))
           {
            //--- Process terminated by the user
            if(IsStopped())
               return("");
            //--- If it is found, flag it
            if(returned_filename==EA_root_folder)
              {
               is_root_folder=true;
               break;
              }
           }
        }
      //--- Close the root folder search handle
      FileFindClose(search_handle);
      //search_handle=INVALID_HANDLE;
     }
//--- Otherwise print an error message
   else
      Print("Error when getting the search handle or "
            "the folder '"+TerminalInfoString(TERMINAL_COMMONDATA_PATH)+"' is empty: ",ErrorDescription(GetLastError()));

//--- Based on the check results, create the necessary folder
   search_path=EXPERT_NAME+"\\";
//--- If the root folder of the Expert Advisor does not exist
   if(!is_root_folder)
     {
      //--- Create it. 
      if(FolderCreate(EXPERT_NAME,FILE_COMMON))
        {
         //--- If the folder has been created, flag it
         is_root_folder=true;
         Print("The root folder of the '..\\"+EXPERT_NAME+"\\ Expert Advisor has been created'");
        }
      else
        {
         Print("Error when creating "
               "the root folder of the Expert Advisor: ",ErrorDescription(GetLastError()));
         return("");
        }
     }
//--- If the required folder exists
   if(is_root_folder)
      //--- Return the path to create a file for writing parameters of the Expert Advisor
      return(search_path+"\\");
//--- In case of errors, return an empty string
   return("");
  }

それでは上記の関数呼び出しを一緒に単独の関数: InitializeInputParametersArrays()に入れましょう。この関数は Expert Advisorと連携するとき、入力パラメータ初期化オプションを4つ処理します。

  1. 現在入力パラメータ値を用いた標準処理モード(または選択されたシンボルに対するパラメータ初期化)
  2. 検証または最適化時のパラメータのファイルへの再書き込み
  3. 選択シンボルの検証
  4. ファイルからのリスト上前シンボルの検証

処理すべてについての詳細説明はコードのコメントにあります。

//+-------------------------------------------------------------------+
//| Initializing arrays of input parameters depending on the mode     |
//+-------------------------------------------------------------------+
void InitializeInputParametersArrays()
  {
   string path=""; // To determine the folder that contains files with input parameters
//--- Mode #1 :
//    - standard operation mode of the Expert Advisor OR
//    - optimization mode OR
//    - reading from input parameters of the Expert Advisor without rewriting the file
   if(IsRealtime() || IsOptimization() || (ParametersReadingMode==INPUT_PARAMETERS && !RewriteParameters))
     {
      //--- Initialize parameter arrays to current values
      InitializeWithCurrentValues();
      return;
     }
//--- Mode #2 :
//    - rewriting parameters in the file for the specified symbol
   if(RewriteParameters)
     {
      //--- Initialize parameter arrays to current values
      InitializeWithCurrentValues();
      //--- If the folder of the Expert Advisor exists or in case no errors occurred when it was being created
      if((path=CreateInputParametersFolder())!="")
         //--- Write/read the file of symbol parameters
         ReadWriteInputParameters(0,path);
      //---
      return;
     }
//--- Mode #3 :
//    - testing (it may be in visualization mode, without optimization) the Expert Advisor on a SELECTED symbol
   if((IsTester() || IsVisualMode()) && !IsOptimization() && SymbolNumber>0)
     {
      //--- If the folder of the Expert Advisor exists or in case no errors occurred when it was being created
      if((path=CreateInputParametersFolder())!="")
        {
         //--- Iterate over all symbols (in this case, the number of symbols = 1)
         for(int s=0; s<SYMBOLS_COUNT; s++)
            //--- Write or read the file of symbol parameters
            ReadWriteInputParameters(s,path);
        }
      return;
     }
//--- Mode #4 :
//    - testing (it may be in visualization mode, without optimization) the Expert Advisor on ALL symbols
   if((IsTester() || IsVisualMode()) && !IsOptimization() && SymbolNumber==0)
     {
      //--- If the folder of the Expert Advisor exists and
      //    no errors occurred when it was being created
      if((path=CreateInputParametersFolder())!="")
        {
         //--- Iterate over all symbols
         for(int s=0; s<SYMBOLS_COUNT; s++)
            //--- Write or read the file of symbol parameters
            ReadWriteInputParameters(s,path);
        }
      return;
     }
  }

モード #1 および #2 では InitializeWithCurrentValues() 関数を使用します。これはゼロ(sole)インデックスを現在入力パラメータ値に対して初期化します。すなわちこの関数はシンボルが1個だけ必要な場合に使われます。

//+------------------------------------------------------------------+
//| Initializing arrays of input parameters to current values        |
//+------------------------------------------------------------------+
void InitializeWithCurrentValues()
  {
   InputIndicatorPeriod[0]=IndicatorPeriod;
   InputTakeProfit[0]=TakeProfit;
   InputStopLoss[0]=StopLoss;
   InputTrailingStop[0]=TrailingStop;
   InputReverse[0]=Reverse;
   InputLot[0]=Lot;
   InputVolumeIncrease[0]=VolumeIncrease;
   InputVolumeIncreaseStep[0]=VolumeIncreaseStep;
  }

ここではもっともシンプルですが、最重要なことがらを行う必要があります。それはエントリー点から上記関数を連続して呼ぶ機能、 OnInit() 関数の実装です。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
void OnInit()
  {
//--- Initialize the array of tested input parameters for writing to the file
   InitializeTestedParametersValues();
//--- Fill the array of symbol names
   InitializeInputSymbols();
//--- Set the size of arrays of input parameters
   ResizeInputParametersArrays();
//--- Initialize arrays of indicator handles
   InitializeIndicatorHandlesArrays();
//--- Initialize arrays of input parameters depending on the operation mode of the Expert Advisor
   InitializeInputParametersArrays();
//--- Get agent handles from the "EventsSpy.ex5" indicator
   GetSpyHandles();
//--- Get indicator handles
   GetIndicatorHandles();
//--- Initialize the new bar
   InitializeNewBarArray();
//--- Initialize price arrays and indicator buffers
   ResizeDataArrays();
  }

とうわけでコードは完了です。本稿添付のファイルで記述した関数を詳しく知ることができます。そこでは難しいことは何もありません。これまでの作業結果何を得たか、それをどのように利用することができるのか見るためさらに先に進みましょう。


パラメータの最適化と Expert Advisorの検証

すでにお話しましたが、クライアントターミナルの共通フォルダ内にシンボルリストを持つ TestedSymbols.txt ファイルが必要です。検証例として3とおりのシンボルについてリストを作成します。AUDUSDEURUSDNZDUSDです。それぞれシンボルに対して個別に連続して入力パラメータの最適化を行います。「ストラテジーテスタ」は以下のように設定します。

図2 ストラテジーテスタ設定

図2 ストラテジーテスタ設定

Expert Advisorに影響はないので、いずれかのシンボル(われわれの場合はEURUSD)を『設定』タブに設定することができます。そしてe Expert Advisorを最適化するためのパラメータを選択します。

図3 Expert Advisorの入力パラメータ

図3 Expert Advisorの入力パラメータ

上図は SymbolNumber パラメータ(検証されたシンボル数)が 1に設定されていることを示しています。これは最適化実行時 Expert Advisor が TestedSymbols.txt ファイルのリストにある最初のシンボルを使うことを意味します。われわれの場合それは AUDUSDです。

注意:この Expert Advisor の特殊性(シンボルリストがテキストファイルから読み出されるように設定されています)によりリモートエージェントによる最適化は行えません。

以下のこのシリーズの記事の一つでこの制約を回避してみます。

最適化完了後、検証を実行することができます。そして異なる最適化パスの結果を調査します。Expert Advisor にファイルからパラメータを読ませたいと思うなら、ParametersReadingMode パラメータ(パラメータ読み出しモード)のドロップダウンリストでFileを選択する必要があります。Expert Advisor の現在パラメータ(『設定』タブで設定します)を使用できるようにするには 入力パラメータ オプションを選択します。

入力パラメータ オプションは最適化結果を閲覧するために必ず必要です。最初に検証を実行する際、Expert Advisor はターミナルの共通フォルダ内に対応する名前でフォルダを1個作成します。作成されたフォルダは検証されるシンボルの現在パラメータを伴うファイルを持ちます。われわれの場合それは AUDUSD.iniです。このファイルの内容は下図で確認することができます。

図4 シンボルファイル内の入力パラメータリスト

図4 シンボルファイル内の入力パラメータリスト

必要なパラメータの組合せがみつかれば、RewriteParameters パラメータ(書き直すパラメータ)にを設定します。 パラメータファイルは更新されます。それから再び を設定し、最適化パスのその他結果を確認します。パラメータ読み出しモード パラメータのオプション間で切り替えるだけでファイルに書き込まれる値を入力パラメータに設定される値と結果同士を便利に比較できます。

その後 EURUSDに対して最適化を行います。これはシンボルリストファイルの2番目のシンボルです。このために検証シンボル番号 パラメータを 2に設定する必要がります。最適化に続き、パラメータを決めそれをファイルに書き込んだ後、リストの3番目のシンボルに対して同じ処理を行います。

すべてのシンボルについてのパラメータがファイルに書き込まれると、シンボルそれぞれに対してシンボル番号を指定することで結果を個別に閲覧するか、 検証シンボル番号 を 0に設定することですべてのシンボルについて集積された結果を閲覧することができます。私は以下のようにすべてのシンボルに対する集積結果を入手しました。

図5 複数通貨対応 Expert Advisorの集積検証結果

図5 複数通貨対応 Expert Advisorの集積検証結果


おわりに

結論として複数通貨対応 Expert Advisorsに対してかなり便利なパターンを取得しました。ご希望であればそれはもっと発展されることができます。本稿にはみなさんが考察されるためのExpert Advisor ファイルを持つダウンロード可能なアーカイブが添付されています。ファイルを解凍したら、 UnlimitedParametersEA フォルダを <MetaTrader 5 terminal folder>\MQL5\Expertsに入れてください。EventsSpy.mq5 インディケータは <MetaTrader 5 terminal folder>\MQL5\Indicatorsに入れる必要があります。また、クライアントターミナルの共通フォルダ内に TestedSymbols.txt テクストファイルを作成することを忘れないでください。

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

添付されたファイル |
eventsspy.mq5 (7.61 KB)
MQL5 クックブック: マルチ通貨 Expert Advisor - シンプル、かしこい、迅速なアプローチ MQL5 クックブック: マルチ通貨 Expert Advisor - シンプル、かしこい、迅速なアプローチ
本稿ではマルチ通貨 Expert Advisorに適切なシンプルなアプローチの実装について述べます。これは理想的な条件下でありながら各シンボルに対して異なるパラメータでExpert Advisor を検証/トレーディングする設定を可能にするということです。例として2個のシンボルに対するパターンを作成しますが、コードに少し変更を加えるだけで必要に応じてそれ以外のシンボルも追加できるようにしておきます。
MQL5 クックブック:ディールヒストリーのファイルへの書き込みと シンボルごとの残高チャートの Excel形式での作成 MQL5 クックブック:ディールヒストリーのファイルへの書き込みと シンボルごとの残高チャートの Excel形式での作成
さまざまなフォーラムのコミュニケーションの際、Microsoft Excel チャート形式のスクリーンショットとして表示される検証結果の例を多く使いました。そしてそのようなチャートの作成方法を教えてほしいと頻繁に質問を受けました。ついに本稿でそれを説明する時間を得ました。
MQL5 クックブック:トリプルスクリーン戦略に基づくトレーディングシステムに対するフレームワーク作成 MQL5 クックブック:トリプルスクリーン戦略に基づくトレーディングシステムに対するフレームワーク作成
本稿では MQL5で「リプルスクリーン」戦略に基づくトレーディングシステムに対するフレームワークを作成します。Expert Advisor を一から作成することはしません。代わりに、実質上すでにわれわれの目的に役だっている先行記事 "MQL5 Cookbook: Using Indicators to Set Trading Conditions in Expert Advisors" のプログラムを変更するだけとします。よって本稿は既製プログラムのパターンを簡単に変更する方法もお伝えします。
MQL5 クックブック:オーバーフィットの影響低減とクオート不足への対処 MQL5 クックブック:オーバーフィットの影響低減とクオート不足への対処
どのようなトレーディング戦略を使っていようと、将来の収益を確保するためどのパラメータを選択すべきかという疑問は常にあるものです。本稿は同時に複数のシンボルパラメータを最適化する機能を備えたExpert Advisor 例を提供します。この方法はパラメータのオーバーフィットによる影響を軽減し、1個のシンボルからのデータが調査に十分でない場合に対処するものです。