MQL5 クックブック:オーバーフィットの影響低減とクオート不足への対処

Anatoli Kazharski | 25 11月, 2015

はじめに

多くのトレーダーはトレーディングシステムに対する最適なパラメータについて困った経験が何度もあると思います。実際トレードのアルゴリズムだけで十分です。それが将来どのように利用できるのか知りたいのです。どのようなトレーディング戦略を使っていようと、それがシンプルであろうと複雑であろうと、単一インスツルメントか複数インスツルメントであろうと、将来の収益を確保するためどのパラメータを選択すべきかという疑問は避けることのできないものです。

われわれは最適化期間(バックテスト)とそれに続く期間(フォワードテスト)をとおして良い結果を示すパラメータのあるトレーディングシステムを確認する傾向にあります。フォワードテストは実際あまり必要ではありません。適切な結果は履歴データを使って取得できるものなのです。

この方法では確かな答えを得ることのできないおおきな疑問を投げかけます。トレーディングシステムを最適化するにはどのくらいの量の履歴データが必要なのだろうか?問題は選択肢が多く存在することです。すべては利用しようとする価格変動の範囲によるのです。

最適化に必要な履歴の量に戻ると、利用可能なデータは時間内トレードで十分です。これはより長いタイムフレームに対してはつねに正しいとは限りません。一定のパターンの繰り返し数が増えるほど、すなわちトレード数が増えると、将来見ることを期待できる検証されるトレーディングシステムのパフォーマンスはより正しくなります。

特定インスツルメントの価格データが十分な数量の繰り返しを得、より確実に思うのに十分でなければどうでしょうか?答えは、利用可能なインスツルメントすべてからデータを利用する、です。


NeuroShell DayTrader Professionaの例l

MetaTrader 5でのプログラミングに進む前に NeuroShell DayTrader Professionalでの例を見てみましょう。それは複数のシンボルについてトレーディングシステム(コンストラクタに一致した)のパラメータを最適化する優れた機能を提供してくれます。トレードモジュール設定に必要なパラメータをセットし、各シンボル個別に最適化を行うか一度にすべてのシンボルに対して最適なパラメータ設定を見つけることができます。このオプションは 最適化 タブ内に見つけることができます。

NeuroShell DayTrader Professionalのトレードモジュール内最適化タブ

図1 NeuroShell DayTrader Professionalのトレードモジュール内最適化タブ

われわれの場合、2とおりの最適化手法の結果を比較する必要があるだけなので、任意のシンプルなトレーディングシステムが行います。よってシステム選択は現時点ではほとんど重要ではありません。

トレーディング戦略を NeuroShell DayTrader Professional でコンパイルする方法についての情報は、私のブログ(適切な情報を検索するかタグをつけることができます)にある別の記事で見ることができます。また NeuroShell DayTrader Professionalと互換性のあるフォーマットで MetaTrader 5 からクオートをダウンロードできるスクリプトを利用する方法について述べ例示している記事 "How to Prepare MetaTrader 5 Quotes for Other Applications"を読むことをお薦めします。

この検証を行うために、私は2000年から2013年1月まで8種類のシンボルについての日時バーあら取得したデータを準備しました。

NeuroShell DayTrader Professionalのテスト用シンボルリスト

図2 NeuroShell DayTrader Professionalのテスト用シンボルリスト

下図は2とおりの最適化結果を表示しています。上部は各シンボルがそれ自体のパラメータを取得する最適化結果です。一方下部はパラメータが全シンボル共通の最適化結果を示しています。

2とおりのパラメータ最適化モードの結果比較

図3 2とおりのパラメータ最適化モードの結果比較

共通パラメータを示す結果は各シンボルでパラメータが異なる場合ほどよくありません。それはトレーディングシステムが全シンボルに対して同一パラメータにより数多くの多様な価格変動パターン(変動率、トレンド/フラットの数)を対象とすると信頼性を高めます。

同じテーマを続けると、ボリュームの大きいデータを用いた最適化において論理的に別の引数を見つけることができます。特定の通過ペア、たとえば EURUSDの価格変動は後に(2、5、10年後)かなり異なるようです。たとえば GBPUSD の価格トレンドは EURUSD の過去の価格変動に似ており、その逆もしかりです。これは任意のインスツルメントに対して真であるのでそれに準備が必要です。


MetaTrader 5での例

MetaTrader 5ではどんなパラメータ最適化モードが提供されるか見ていきましょう。以下で最適化モードのドロップダウンリストにある矢印で3マークされている銘柄リストで選択されるあらゆるシンボル最適化モードを確認することができます。

図4 MetaTrader 5 ストラテジーテスタの最適化モード

図4 MetaTrader 5 ストラテジーテスタの最適化モード

このモードにより各シンボルについて一つずつ現パラメータを持つ EA の検証のみ可能です。検証に使用されるシンボルは現在銘柄ウィンドウで選択されているものです。すなわち、パラメータ最適化のこの場合行われません。ただし MetaTrader 5 および MQL5 によりこの考えをみなさんご自身で実装することが可能となります。

ここでそのような EAの実装方法を確認する必要があります。シンボルリストはテキストファイル(*.txt)で提供されます。のちにシンボルリストを複数セット格納する機能を実装します。各セットはセクション番号を持つ各々のヘッダのある個別セクションに設定されます。番号は視覚的確認をよりよく行うために必要です。

番号の前 # を入れることは重要である点に注意が必要です。それによりシンボルの配列に書き込む際 Expert Advisor が正しいデータセットを取得することができます。通常、ヘッダには任意の記号が入りますが常に # を持つ必要があります。番号記号は Expert Advisor がセクションを判断/数えるシンボルに従い任意の別の記号と置き換えることができます。その場合、置き換えはコードに反映する必要があります。

以下で検証用の3種類のシンボルセットを持つ SymbolsList.txt ファイルを確認することができます。表示されているこのファイルはのちにメソッド検証の際使用されます。

図5 検証用にテキストファイル内に提供される複数のシンボルセット

図5 検証用にテキストファイル内に提供される複数のシンボルセット

外部パラメータにはもう一つ別のパラメータSectionOfSymbolListを追加し、現検証で Expert Advisor が使用するシンボルセットを指示します。このパラメータはシンボルセットを決める値(ゼロ以上)を取ります。その値が利用可能なセット数を超えれば、Expert Advisor はログに対応する入力を下記、検証は現シンボルに対してのみ行われます。

SymbolsList.txt はローカル端末ディレクトリの Metatrader 5\MQL5\Files下に入れる必要があります。それは共通フォルダに入れることもできますが、この場合は MQL5 クラウドネットワークのパラメータ最適化には利用できません。また検証用ファイルと適切なカスタムインディケータへのアクセスが可能となるようにファイルの冒頭に以下の行を書き込む必要があります。

//--- Allow access to the external file and indicator for optimization in the cloud
#property tester_file      "SymbolsList.txt"
#property tester_indicator "EventsSpy.ex5"

われわれのOur Expert Advisor は記事 "MQL5 Cookbook: Developing a Multi-Currency Expert Advisor with Unlimited Number of Parameters"で取り上げられている既製のマルチ通貨 Expert Advisor を基にしています。そこにあるトレーディング戦略はひじょうにシンプルですが、メソッドの有効性を検証するには十分なものです。不要な部分を消去し、必要なものを追加し既存の適切なコードを修正するだけです。シリーズの以前に発表されている記事 "MQL5 Cookbook: Writing the History of Deals to a File and Creating Balance Charts for Each Symbol in Excel"に広範にわたって記載のある機能を保存するレポートによって確かにわれわれのExpert Advisor を強化します。対象メソッドの有効性を判定するにはすべてのシンボルに対する残高チャートも必要です。

Expert Advisor の外部パラメータは次のように変更します。

//--- External parameters of the Expert Advisor
sinput int    SectionOfSymbolList = 1;     // Section number in the symbol lists
sinput bool   UpdateReport        = false; // Report update
sinput string delimeter_00="";   // --------------------------------
sinput long   MagicNumber         = 777;   // Magic number
sinput int    Deviation           = 10;    // Slippage
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

外部パラメータに関する配列はすべて削除する必要があります。それらはのちに必要となりコード全体をとおして外部変数と置き換えるためですシンボルの動的配列、InputSymbols[] だけ残します。そのサイズはSymbolsList.txt ファイルにあるセットの一組から使用するシンボル数に依存します。Expert Advisor がストラテジーテスタ外で使用されるなら、その配列サイズは 1 です。それは実時間モードでは Expert Advisor はシンボル1個とのみ連携するためです。

対応する変更も配列初期化ファイル、InitializeArrays.mqh内で行われる必要があります。よって外部変数の配列初期化を行うすべての関数は削除します。よってInitializeArraySymbols() 関数は以下のように記述されます。

//+------------------------------------------------------------------+
//| Filling the array of symbols                                     |
//+------------------------------------------------------------------+
void InitializeArraySymbols()
  {
   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
//--- Test mode message
   string message_01="<--- All symbol names in the <- SymbolsList.txt -> file are incorrect ... --->\n"
                     "<--- ... or the value of the \"Section of List Symbols\" parameter is greater, "
                     "than the number of file sections! --->\n"
                     "<--- Therefore we will test only the current symbol. --->";
//--- Real-time mode message
   string message_02="<--- In real-time mode, we only work with the current symbol. --->";
//--- If in real-time mode
   if(!IsRealtime())
     {
      //--- Get the number of strings from the specified symbol set in the file and fill the temporary array of symbols
      strings_count=ReadSymbolsFromFile("SymbolsList.txt");
      //--- Iterate over all symbols from the specified set
      for(int s=0; s<strings_count; s++)
        {
         //--- If the correct string is returned following the symbol check
         if((checked_symbol=GetSymbolByName(temporary_symbols[s]))!="")
           {
            //--- increase the counter
            SYMBOLS_COUNT++;
            //--- set/increase the array size
            ArrayResize(InputSymbols,SYMBOLS_COUNT);
            //--- index with the symbol name
            InputSymbols[SYMBOLS_COUNT-1]=checked_symbol;
           }
        }
     }
//--- If all symbol names were not input correctly or if currently working in real-time mode
   if(SYMBOLS_COUNT==0)
     {
      //--- Real-time mode message
      if(IsRealtime())
         Print(message_02);
      //--- Test mode message
      if(!IsRealtime())
         Print(message_01);
      //--- We will work with the current symbol only
      SYMBOLS_COUNT=1;
      //--- set the array size and
      ArrayResize(InputSymbols,SYMBOLS_COUNT);
      //--- index with the current symbol name
      InputSymbols[0]=_Symbol;
     }
  }

ReadSymbolsFromFile() 関数のコードもまた変更の必要があります。それは前はシンボルリスト全体を読みましたが、これからは指定のシンボルセットだけを読むようにします。以下が修正済みの関数コードです。

//+------------------------------------------------------------------+
//| Returning the number of strings (symbols) from the specified     |
//| set in the file and filling the temporary array of symbols       |
//+------------------------------------------------------------------+
//--- When preparing the file, symbols in the list should be separated with a line break
int ReadSymbolsFromFile(string file_name)
  {
   ulong  offset         =0;   // Offset for determining the position of the file pointer
   string delimeter      ="#"; // Identifier of the section start
   string read_line      ="";  // For the check of the read string
   int    limit_count    =0;   // Counter limiting the number of the possibly open charts
   int    strings_count  =0;   // String counter
   int    sections_count =-1;  // Section counter
   
//--- Message 01
   string message_01="<--- The <- "+file_name+" -> file has not been prepared appropriately! --->\n"
                     "<--- The first string does not contain the section number identifier ("+delimeter+")! --->";
//--- Message 02
   string message_02="<--- The <- "+file_name+" -> file has not been prepared appropriately! --->\n"
                     "<--- There is no line break identifier in the last string, --->\n"
                     "<--- so only the current symbol will be involved in testing. --->";
//--- Message 03
   string message_03="<--- The <- "+file_name+" -> file could not be found! --->"
                     "<--- Only the current symbol will be involved in testing. --->";
                     
//--- Open the file (get the handle) for reading in the local directory of the terminal
   int file_handle=FileOpen(file_name,FILE_READ|FILE_ANSI,'\n');
//--- If the file handle has been obtained
   if(file_handle!=INVALID_HANDLE)
     {
      //--- 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
            read_line=FileReadString(file_handle);
            //--- If the section number identifier has been found
            if(StringFind(read_line,delimeter,0)>-1)
               //--- Increase the section counter
               sections_count++;
            //--- If the section has been read, exit the function
            if(sections_count>SectionOfSymbolList)
              {
               FileClose(file_handle); // Close the file
               return(strings_count);  // Return the number of strings in the file
              }
            //--- If this is the first iteration and the first string does not contain the section number identifier
            if(limit_count==0 && sections_count==-1)
              {
               PrepareArrayForOneSymbol(strings_count,message_01);
               //--- Close the file
               FileClose(file_handle);
               //--- Return the number of strings in the file
               return(strings_count);
              }
            //--- Increase the counter limiting the number of the possibly open charts
            limit_count++;
            //--- If the limit has been reached
            if(limit_count>=CHARTS_MAX)
              {
               PrepareArrayForOneSymbol(strings_count,message_02);
               //--- Close the file
               FileClose(file_handle);
               //--- Return the number of strings in the file
               return(strings_count);
              }
            //--- Get the position of the pointer
            offset=FileTell(file_handle);
            //--- If this is the end of the string
            if(FileIsLineEnding(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 we are not in the specified section of the file, exit the loop
               if(sections_count!=SectionOfSymbolList)
                  break;
               //--- Otherwise,
               else
                 {
                  //--- if the string is not empty
                  if(read_line!="")
                    {
                     //--- increase the string counter
                     strings_count++;
                     //--- increase the size of the array of strings,
                     ArrayResize(temporary_symbols,strings_count);
                     //--- write the string to the current index
                     temporary_symbols[strings_count-1]=read_line;
                    }
                 }
               //--- Exit the loop
               break;
              }
           }
         //--- If this is the end of the file, terminate the entire loop
         if(FileIsEnding(file_handle))
            break;
        }
      //--- Close the file
      FileClose(file_handle);
     }
   else
      PrepareArrayForOneSymbol(strings_count,message_03);
//--- Return the number of strings in the file
   return(strings_count);
  }

上記コードの文字列の中に強調表示されているのが判ります。それら文字列にはエラーの場合シンボル1個(現)に対する配列を準備するだけのPrepareArrayForOneSymbol() 関数が含まれます。

//+------------------------------------------------------------------+
//| Preparing an array for one symbol                                |
//+------------------------------------------------------------------+
void PrepareArrayForOneSymbol(int &strings_count,string message)
  {
//--- Print the message to the log
   Print(message);
//--- Array size
   strings_count=1;
//--- Set the size of the array of symbols
   ArrayResize(temporary_symbols,strings_count);
//--- Write the string with the current symbol name to the current index
   temporary_symbols[0]=_Symbol;
  }

これでパラメータ最適化目祖度検証を行うための準備がすべて整いました。検証に進む前に、レポートにもう一つ別のデータシリーズを追加します前にすべてのシンボルのバランスに加え、レポートファイルはパーセント表示のローカル極大値からのすべてのドローダウンを持ちました。ここではレポートは金額ベースのドローダウンもすべて対象とします。同時にレポートが作成される CreateSymbolBalanceReport() 関数を変更します。

以下はCreateSymbolBalanceReport() 関数コードです。

//+------------------------------------------------------------------+
//| Creating test report on deals in .csv format                     |
//+------------------------------------------------------------------+
void CreateSymbolBalanceReport()
  {
   int    file_handle =INVALID_HANDLE; // File handle
   string path        ="";             // File path

//--- If an error occurred when creating/getting the folder, exit
   if((path=CreateInputParametersFolder())=="")
      return;
//--- Create a file to write data in the common folder of the terminal
   file_handle=FileOpen(path+"\\LastTest.csv",FILE_CSV|FILE_WRITE|FILE_ANSI|FILE_COMMON);
//--- If the handle is valid (file created/opened)
   if(file_handle>0)
     {
      int           digits           =0;   // Number of decimal places in the price
      int           deals_total      =0;   // Number of deals in the specified history
      ulong         ticket           =0;   // Deal ticket
      double        drawdown_max     =0.0; // Drawdown
      double        balance          =0.0; // Balance
      string        delimeter        =","; // Delimiter
      string        string_to_write  ="";  // To generate the string for writing
      static double percent_drawdown =0.0; // Drawdown expressed as percentage
      static double money_drawdown   =0.0; // Drawdown in monetary terms

      //--- Generate the header string
      string headers="TIME,SYMBOL,DEAL TYPE,ENTRY TYPE,VOLUME,"
                     "PRICE,SWAP($),PROFIT($),DRAWDOWN(%),DRAWDOWN($),BALANCE";
      //--- If more than one symbol is involved, modify the header string
      if(SYMBOLS_COUNT>1)
        {
         for(int s=0; s<SYMBOLS_COUNT; s++)
            StringAdd(headers,","+InputSymbols[s]);
        }
      //--- Write the report headers
      FileWrite(file_handle,headers);
      //--- Get the complete history
      HistorySelect(0,TimeCurrent());
      //--- Get the number of deals
      deals_total=HistoryDealsTotal();
      //--- Resize the array of balances according to the number of symbols
      ArrayResize(symbol_balance,SYMBOLS_COUNT);
      //--- Resize the array of deals for each symbol
      for(int s=0; s<SYMBOLS_COUNT; s++)
         ArrayResize(symbol_balance[s].balance,deals_total);
      //--- Iterate in a loop and write the data
      for(int i=0; i<deals_total; i++)
        {
         //--- Get the deal ticket
         ticket=HistoryDealGetTicket(i);
         //--- Get all the deal properties
         GetHistoryDealProperties(ticket,D_ALL);
         //--- Get the number of digits in the price
         digits=(int)SymbolInfoInteger(deal.symbol,SYMBOL_DIGITS);
         //--- Calculate the overall balance
         balance+=deal.profit+deal.swap+deal.commission;
         //--- Calculate the max drawdown from the local maximum
         TesterDrawdownMaximum(i,balance,percent_drawdown,money_drawdown);
         //--- Generate a string for writing using concatenation
         StringConcatenate(string_to_write,
                           deal.time,delimeter,
                           DealSymbolToString(deal.symbol),delimeter,
                           DealTypeToString(deal.type),delimeter,
                           DealEntryToString(deal.entry),delimeter,
                           DealVolumeToString(deal.volume),delimeter,
                           DealPriceToString(deal.price,digits),delimeter,
                           DealSwapToString(deal.swap),delimeter,
                           DealProfitToString(deal.symbol,deal.profit),delimeter,
                           DrawdownToString(percent_drawdown),delimeter,
                           DrawdownToString(money_drawdown),delimeter,
                           DoubleToString(balance,2));
         //--- If more than one symbol is involved, write their balance values
         if(SYMBOLS_COUNT>1)
           {
            //--- Iterate over all symbols
            for(int s=0; s<SYMBOLS_COUNT; s++)
              {
               //--- If the symbols are equal and the deal result is non-zero
               if(deal.symbol==InputSymbols[s] && deal.profit!=0)
                 {
                  //--- Display the deal in the balance for the corresponding symbol
                  //    Take into consideration swap and commission
                  symbol_balance[s].balance[i]=symbol_balance[s].balance[i-1]+
                                               deal.profit+
                                               deal.swap+
                                               deal.commission;
                  //--- Add to the string
                  StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],2));
                 }
               //--- Otherwise write the previous value
               else
                 {
                  //--- If the deal type is "Balance" (the first deal)
                  if(deal.type==DEAL_TYPE_BALANCE)
                    {
                     //--- the balance is the same for all symbols
                     symbol_balance[s].balance[i]=balance;
                     StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],2));
                    }
                  //--- Otherwise write the previous value to the current index
                  else
                    {
                     symbol_balance[s].balance[i]=symbol_balance[s].balance[i-1];
                     StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],2));
                    }
                 }
              }
           }
         //--- Write the generated string
         FileWrite(file_handle,string_to_write);
         //--- Mandatory zeroing out of the variable for the next string
         string_to_write="";
        }
      //--- Close the file
      FileClose(file_handle);
     }
//--- If the file could not be created/opened, print the appropriate message
   else
      Print("Error creating the file! Error: "+IntegerToString(GetLastError())+"");
  }

かつてはドローダウンを DrawdownMaximumToString() 関数d計算しました。今回は TesterDrawdownMaximum() 関数で行います。同時にドローダウン値は基本の DrawdownToString() 関数を用いて文字列に変換されます。

以下がTesterDrawdownMaximum() 関数のコードです。

//+------------------------------------------------------------------+
//| Returning the max drawdown from the local maximum                |
//+------------------------------------------------------------------+
void TesterDrawdownMaximum(int deal_number,
                           double balance,
                           double &percent_drawdown,
                           double &money_drawdown)
  {
   ulong         ticket =0;   // Deal ticket
   string        str    ="";  // The string to be displayed in the report
//--- To calculate the local maximum and drawdown
   static double max    =0.0;
   static double min    =0.0;
   
//--- If this is the first deal
   if(deal_number==0)
     {
      //--- There is no drawdown yet
      percent_drawdown =0.0;
      money_drawdown   =0.0;
      //--- Set the initial point as the local maximum
      max=balance;
      min=balance;
     }
   else
     {
      //--- If the current balance is greater than in the memory, then...
      if(balance>max)
        {
         //--- Calculate the drawdown using the previous values:
         //    in monetary terms
         money_drawdown=max-min;
         //    expressed as percentage
         percent_drawdown=100-((min/max)*100);
         //--- Update the local maximum
         max=balance;
         min=balance;
        }
      //--- Otherwise
      else
        {
         //--- Return zero value of the drawdown
         money_drawdown=0.0;
         percent_drawdown=0.0;
         //--- Update the minimum
         min=fmin(min,balance);
         //--- If the deal ticket by its position in the list has been obtained, then...
         if((ticket=HistoryDealGetTicket(deal_number))>0)
           {
            //--- ...get the deal comment
            GetHistoryDealProperties(ticket,D_COMMENT);
            //--- Flag of the last deal
            static bool last_deal=false;
            //--- The last deal in the test can be identified by the "end of test" comment
            if(deal.comment=="end of test" && !last_deal)
              {
               //--- Set the flag
               last_deal=true;
               //--- Update the drawdown values:
               //    in monetary terms
               money_drawdown=max-min;
               //    expressed as percentage
               percent_drawdown+=100-((min/max)*100);
              }
           }
        }
     }
  }

以下は DrawdownToString() 関数のコードです。

//+------------------------------------------------------------------+
//| Converting drawdown to a string                                  |
//+------------------------------------------------------------------+
string DrawdownToString(double drawdown)
  {
   return((drawdown<=0) ? "" : DoubleToString(drawdown,2));
  }

これで Expert Advisor の検証と結果分析の準備がすべて整いました。本稿の最初に既製のファイル例を確認しました。以下のように行います。2番目のセット(シンボルは3セットあります。EURUSD、AUDUSD、USDCHFです)でシンボルに対するパラメータの最適化を行い、それに続いて3番目のセット(合計シンボル7個)からすべてのシンボルを用いて検証を実行し、パラメータ最適化の対象となっていないシンボルのデータに対する結果を確認します。


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

「ストラテジーテスタ」は以下のように設定します。

図6 最適化のためのストラテジーテスタ設置

図6 最適化のためのストラテジーテスタ設置

パラメータ最適化のため Expert Advisor 設定は以下です。

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

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

最適化は3個のシンボルを対象としており、各シンボルに対してポジションボリュームの増加が有効なので、ポジションオープンおよびポジションボリューム増加のために最小ロットを設定します。われわれの場合、値は 0.01です。

最適化後、最大リカバリーファクターの一番上の結果を選択し、ロットに対して VolumeIncrease パラメータを 0.1に設定します。結果は以下のとおりです。

図8 MetaTrader 5での検証結果

図8 MetaTrader 5での検証結果

以下で Excel 2010に表示された結果を確認することができます。

Excel 2010で表示される3シンボルについての検証結果

図9 Excel 2010で表示される3シンボルについての検証結果

金額ベースのドローダウンは2番目(補助的)スケールに関してチャートの下側にグリーンでマークして表示されています。

Excel 2010でのチャート化の限界についても知る必要があります(仕様と制限の完全リストはマイクロソフトオフィスウェブサイトの Excel specifications and limits ページで確認することができます)。

図10 Excel 2010 dのチャート化仕様および制約

図10 Excel 2010 dのチャート化仕様および制約

表では同時に 255 シンボルに対して検証を実行し結果をすべてチャートに表示していることを示しています。制約を受けるのはコンピュータリソースのみです。

現パラメータで3番目のセットから7シンボルについて検証を行い結果を確認します。

Excel 2010での7シンボルに対する検証結果

図11 Excel 2010での7シンボルに対する検証結果

対象の7シンボルで 6901 ディールを行います。チャートのデータは Excel 2010でひじょうに迅速に更新されます。


おわりに

紹介された方法はわれわれが使用したシンプルなトレーディング戦略でさえも良好な結果を示したことで注目に値すると思います。ここで最適化は7シンボル中3シンボルに対してのみ行われたことを念頭に置く必要があります。同時にすべてのシンボルに対して最適化を行うことで結果を改善することができます。ただまず第一にトレーディングシステムを改善する、またはいっそさまざまなトレーディングシステムのポートフォリオを持つことを目指す必要があります。この考えにはのちに戻ります。

それでおしまいです。われわれはマルチ通貨のトレーディング戦略の結果を調査するひじょうに便利なツールを入手しました。以下はみなさんの検討用 Expert Advisor のファイルのある zip ファイルでダウンロードできるものです。

ファイル抽出後、それを MetaTrader 5\MQL5\Experts ディレクトリのReduceOverfittingEAフォルダに入れます。そののち EventsSpy.mq5 インディケータはMetaTrader 5\MQL5\Indicatorsに入れる必要があります。SymbolsList.txtMetaTrader 5\MQL5\Filesの下位にあります。