English Русский 中文 Español Deutsch Português
preview
MetaTrader 5でのモンテカルロ並べ替え検定

MetaTrader 5でのモンテカルロ並べ替え検定

MetaTrader 5 | 8 1月 2024, 16:32
189 0
Francis Dube
Francis Dube

はじめに

Aleksey Nikolayevさんが、「モンテカルロ法を適用したトレーディング戦略の最適化」という興味深い記事を書いています。並べ替え検定の方法について説明したもので、検定で得られた一連の取引を無作為に交換するものです。著者は別のタイプの並べ替えテストについて簡単に触れています。そこでは、価格データのシーケンスがランダムに変更され、単一のエクスパートアドバイザー(EA)のパフォーマンスが、同じ価格系列の他の多数のシーケンスのバリエーションでテストされたときに達成されたパフォーマンスと比較されます。

私の意見では、著者はMetaTrader 5を使用して任意のEAでそのようなテストを実施する方法がないことを誤って示唆しています。少なくとも完全にはです。したがって、この記事では、MetaTrader 5を使用して、ランダムに並べ替えられた価格データを含む並べ替え検定について示します。価格系列を並べ替えるコードと、完全なEAの並べ替えテストを実施する準備の初期段階を自動化するスクリプトを紹介します。

並べ替え検定の概要

簡潔に言えば、これから説明する並べ替え検定は、価格データのサンプルを選択するものです。実施されるテストは、サンプルからであることが望ましいです。この価格系列でテストを実行した後、測定に興味がありそうなパフォーマンス基準をメモしておきます。次に、元の価格系列の順序をランダムに変更し、EAをテストしてパフォーマンスに注目します。

これを何度も繰り返し、その都度、価格系列を順列化し、他のテストで指摘したパフォーマンス基準を記録します。これを少なくとも100回、理想的には数千回行う必要があります。並べ替えとテストの回数が多ければ多いほど、結果はより確かなものになります。ただし、ちょっと待ってください。この結果から、テスト対象のEAについて何がわかるというのでしょうか。


並べ替え検定の実施価値

多くの反復テストが実施された場合、最終的には各順列によるパフォーマンス数値のコレクションが出来上がります。シャープレシオでも、プロフィットファクターでも、あるいは単に収支や純利益でもです。99回の並べ替えが行われたとします。元の並べ替えられていないテストを含めば100です。100の数字のパフォーマンスを比較できます。

次のステップは、並べ替えられていないテストのパフォーマンス数値を超えた回数を列挙し、この数値を実施したテストの割合(この例では100)として示すことです。この割合は、EAに利益の可能性がまったくなかった場合に、並び替えられていないテスト以上の結果が偶然に得られる確率です。統計学では、これはと呼ばれ、仮説検定の結果です。

100回反復する仮説的並び替えテストについて続けると、29回の並べ替えテストが、ベンチマークである並べ替えなしのテストよりも優れていました。p値は0.3 (= 29+1/100)となります。つまり、お金を失うEAが、並び替えされていないテスト運用から観察された以上のパフォーマンスを得た確率が0.3であることを意味します。このような結果は有望に思えるかもしれませんが、私たちが望むのは、0.05以下の範囲で限りなくゼロに近いp値です。

完全な計算式を以下に示します。

z+1/r+1

ここで、rは行われた並べ替えの数で、zはより良いパフォーマンスを持つ並べ替えテストの総数です。テストを適切に行うには、並べ替えの手順が重要です。

価格系列の順列化

データの集まりを正しく並べ替えるには、すべての可能な配列の変異が等しく起こりうることを確認しなければなりません。そのためには、0から1の間で一様に分布する乱数を生成する必要があります。MQL5標準ライブラリの統計ライブラリには、このニーズを満たすツールがあります。これを使えば、要求される値の範囲を指定することができます。

//+------------------------------------------------------------------+
//| Random variate from the Uniform distribution                     |
//+------------------------------------------------------------------+
//| Computes the random variable from the Uniform distribution       |
//| with parameters a and b.                                         |
//|                                                                  |
//| Arguments:                                                       |
//| a           : Lower endpoint (minimum)                           |
//| b           : Upper endpoint (maximum)                           |
//| error_code  : Variable for error code                            |
//|                                                                  |
//| Return value:                                                    |
//| The random value with uniform distribution.                      |
//+------------------------------------------------------------------+
double MathRandomUniform(const double a,const double b,int &error_code)
  {
//--- check NaN
   if(!MathIsValidNumber(a) || !MathIsValidNumber(b))
     {
      error_code=ERR_ARGUMENTS_NAN;
      return QNaN;
     }
//--- check upper bound
   if(b<a)
     {
      error_code=ERR_ARGUMENTS_INVALID;
      return QNaN;
     }

   error_code=ERR_OK;
//--- check ranges
   if(a==b)
      return a;
//---
   return a+MathRandomNonZero()*(b-a);
  }


価格データのシャッフルには独特の要求があります。まず、単純に価格値の位置を変えることはできません。金融時系列に特徴的な時間的関係を乱すことになるからです。そこで、実際の価格の代わりに、価格の変化を順列化します。差分を取る前にまず価格を対数変換することで、生の価格差の変動の影響を最小限に抑えます。

この方法を使うには、最初の価格値を保留し、並べ替えから除外しなければなりません。系列を再構築すると、元の価格系列に存在したトレンドが維持されることになります。唯一のバリエーションは、元の系列の同じ初値と終値の間の内部的な値動きです。


実際に価格系列を並べ替える前に、使用するデータを決めなければなりません。MetaTrader 5では、チャートデータはティックデータから構成されるバーとして表示されます。単一の価格系列の順列付けは、バー情報の順列付けよりもはるかに簡単です。そこで、ティックデータを使用します。ティックには生価格以外の情報も含まれるため、ティックを使用すると、他にも多くの複雑な問題が発生します。出来高、時間、ティックフラグの情報があります。


まず、時間とティックフラグの情報は変更されないので、並べ替えルーチンがこの情報を変更することはありません。私たちに関心があるのは、ビッド、アスク、出来高のみです。2つ目の複雑さは、これらの値のどれかがゼロになる可能性から来るもので、対数変換を適用する際に問題を引き起こします。これらの課題を克服する方法を示すために、いくらかコードを見てみましょう。

ティック順列アルゴリズムの実装

インクルードファイルPermuteTicks.mqhに含まれるCPermuteTicksクラスは、ティックの並べ替え手順を実装します。PermuteTicks.mqhの内部では、標準ライブラリのUniform.mqhをインクルードし、設定された範囲内で一様に生成された乱数を出力するユーティリティにアクセスできるようにしています。後続の定義ではこの範囲を指定します。これらの値を変更する必要がある場合は注意して、最小値が実際に最大しきい値よりも小さいことを確認してください。

//+------------------------------------------------------------------+
//|                                                 PermuteTicks.mqh |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#include<Math\Stat\Uniform.mqh>
//+-----------------------------------------------------------------------------------+
//| defines: representing range of random values from random number generator         |
//+-----------------------------------------------------------------------------------+
#define MIN_THRESHOLD 1e-5
#define MAX_THRESHOLD 1.0


CMqlTick構造体は、このクラスが操作するMqlTick構造体の対応するメンバーを表します。その他のティック情報には触れません。

//+------------------------------------------------------------------+
//| struct to handle tick data to be worked on                       |
//+------------------------------------------------------------------+
struct CMqlTick
  {
   double            ask_d;
   double            bid_d;
   double            vol_d;
   double            volreal_d;
  };


CPermuteTicksクラスには、3つのprivate配列プロパティがあります。1つ目はm_ticksに保持される元のティック、2つ目はm_logticksに保持される対数変換されたティック、最後はm_differencedに収集される差分化されたティックです。

//+------------------------------------------------------------------+
//| Class to enable permutation of a collection of ticks in an array |
//+------------------------------------------------------------------+
class CPermuteTicks
  {
private :
   MqlTick           m_ticks[];        //original tick data to be shuffled
   CMqlTick          m_logticks[];     //log transformed tick data of original ticks
   CMqlTick          m_differenced[];  //log difference of tick data
   bool              m_initialized;    //flag representing proper preparation of a dataset
   //helper methods
   bool              LogTransformTicks(void);
   bool              ExpTransformTicks(MqlTick &out_ticks[]);

public :
   //constructor
                     CPermuteTicks(void);
   //desctrucotr
                    ~CPermuteTicks(void);
   bool              Initialize(MqlTick &in_ticks[]);
   bool              Permute(MqlTick &out_ticks[]);
  };


m_initializedは、並べ替えを行う前の前処理が成功したことを示すブール値のフラグです。


このクラスを使用するには、オブジェクトのインスタンスを作成した後、Initialize()メソッドを呼び出す必要があります。このメソッドは、並べ替えの対象となるティックの配列を必要とします。メソッド内部では、アクセスできないクラスの配列のサイズが変更され、LogTranformTicks()がティックデータを変換するために使用されます。ゼロやマイナスの値を避け、1.0に置き換えます。並べ替えが行われ、ログ変換されたティックデータは、ExpTransformTicks() privateメソッドによって元のドメインに戻されます。

//+--------------------------------------------------------------------+
//|Initialize the permutation process by supplying ticks to be permuted|
//+--------------------------------------------------------------------+
bool CPermuteTicks::Initialize(MqlTick &in_ticks[])
  {
//---set or reset initialization flag  
   m_initialized=false;
//---check arraysize
   if(in_ticks.Size()<5)
     {
      Print("Insufficient amount of data supplied ");
      return false;
     }
//---copy ticks to local array
   if(ArrayCopy(m_ticks,in_ticks)!=int(in_ticks.Size()))
     {
      Print("Error copying ticks ", GetLastError());
      return false;
     }
//---ensure the size of m_differenced array
   if(m_differenced.Size()!=m_ticks.Size()-1)
      ArrayResize(m_differenced,m_ticks.Size()-1);
//---apply log transformation to relevant tick data members
   if(!LogTransformTicks())
     {
      Print("Log transformation failed ", GetLastError());
      return false;
     }
//---fill m_differenced with differenced values, excluding the first tick
   for(uint i=1; i<m_logticks.Size(); i++)
     {
      m_differenced[i-1].bid_d=(m_logticks[i].bid_d)-(m_logticks[i-1].bid_d);
      m_differenced[i-1].ask_d=(m_logticks[i].ask_d)-(m_logticks[i-1].ask_d);
      m_differenced[i-1].vol_d=(m_logticks[i].vol_d)-(m_logticks[i-1].vol_d);
      m_differenced[i-1].volreal_d=(m_logticks[i].volreal_d)-(m_logticks[i-1].volreal_d);
     }
//---set the initilization flag
   m_initialized=true;
//---
   return true;
  }


並べ替えられたティックを出力するには、Permute()という適切な名前のメソッドを呼び出します。動的なMqlTick配列という1つのパラメータが必要で、そこに並べ替えられたティックが配置されます。これは、各繰り返しで生成される乱数に応じて、差分ティック値の位置を入れ替えるwhileループの中にあります。

//+------------------------------------------------------------------+
//|Public method which applies permutation and gets permuted ticks   |
//+------------------------------------------------------------------+
bool CPermuteTicks::Permute(MqlTick &out_ticks[])
  {
//---zero out tick array  
   ZeroMemory(out_ticks);
//---ensure required data already supplied through initialization
   if(!m_initialized)
     {
      Print("not initialized");
      return false;
     }
//---resize output array if necessary
   if(out_ticks.Size()!=m_ticks.Size())
      ArrayResize(out_ticks,m_ticks.Size());
//---
   int i,j;
   CMqlTick tempvalue;

   i=(int)m_ticks.Size()-1;
   
   int error_value;
   double unif_rando;

   ulong time = GetTickCount64();

   while(i>1)
     {
      error_value=0;
      unif_rando=MathRandomUniform(MIN_THRESHOLD,MAX_THRESHOLD,error_value);
      if(!MathIsValidNumber(unif_rando))
        {
         Print("Invalid random value ",error_value);
         return(false);
        }
      j=(int)(unif_rando*i);
      if(j>=i)
         j=i-1;
      --i;
//---swap tick data randomly
      tempvalue.bid_d=m_differenced[i].bid_d;
      tempvalue.ask_d=m_differenced[i].ask_d;
      tempvalue.vol_d=m_differenced[i].vol_d;
      tempvalue.volreal_d=m_differenced[i].volreal_d;

      m_differenced[i].bid_d=m_differenced[j].bid_d;
      m_differenced[i].ask_d=m_differenced[j].ask_d;
      m_differenced[i].vol_d=m_differenced[j].vol_d;
      m_differenced[i].volreal_d=m_differenced[j].volreal_d;

      m_differenced[j].bid_d=tempvalue.bid_d;
      m_differenced[j].ask_d=tempvalue.ask_d;
      m_differenced[j].vol_d=tempvalue.vol_d;
      m_differenced[j].volreal_d=tempvalue.volreal_d;
     }
//---undo differencing 
   for(uint k = 1; k<m_ticks.Size(); k++)
     {
      m_logticks[k].bid_d=m_logticks[k-1].bid_d + m_differenced[k-1].bid_d;
      m_logticks[k].ask_d=m_logticks[k-1].ask_d + m_differenced[k-1].ask_d;
      m_logticks[k].vol_d=m_logticks[k-1].vol_d + m_differenced[k-1].vol_d;
      m_logticks[k].volreal_d=m_logticks[k-1].volreal_d + m_differenced[k-1].volreal_d;
     }
//---copy the first tick  
   out_ticks[0].bid=m_ticks[0].bid;
   out_ticks[0].ask=m_ticks[0].ask;
   out_ticks[0].volume=m_ticks[0].volume;
   out_ticks[0].volume_real=m_ticks[0].volume_real;
   out_ticks[0].flags=m_ticks[0].flags;
   out_ticks[0].last=m_ticks[0].last;
   out_ticks[0].time=m_ticks[0].time;
   out_ticks[0].time_msc=m_ticks[0].time_msc;     
//---return transformed data
   return ExpTransformTicks(out_ticks);
  }
//+------------------------------------------------------------------+


すべての反復が完了すると、m_logticks配列は、並べ替えられたm_differenced tickデータを使用して差分を元に戻し、再構築されます。最後に、Permute()メソッドの唯一の引数は、元のティック系列からコピーされた時刻とティックフラグ情報を持つ、元のドメインに戻されたm_logtickデータで満たされます。

//+-------------------------------------------------------------------+
//|Helper method applying log transformation                          |
//+-------------------------------------------------------------------+
bool CPermuteTicks::LogTransformTicks(void)
  {
//---resize m_logticks if necessary  
   if(m_logticks.Size()!=m_ticks.Size())
      ArrayResize(m_logticks,m_ticks.Size());
//---log transform only relevant data members, avoid negative and zero values
   for(uint i=0; i<m_ticks.Size(); i++)
     {
      m_logticks[i].bid_d=(m_ticks[i].bid>0)?MathLog(m_ticks[i].bid):MathLog(1e0);
      m_logticks[i].ask_d=(m_ticks[i].ask>0)?MathLog(m_ticks[i].ask):MathLog(1e0);
      m_logticks[i].vol_d=(m_ticks[i].volume>0)?MathLog(m_ticks[i].volume):MathLog(1e0);
      m_logticks[i].volreal_d=(m_ticks[i].volume_real>0)?MathLog(m_ticks[i].volume_real):MathLog(1e0);
     }
//---
   return true;
  }

//+-----------------------------------------------------------------------+
//|Helper method undoes log transformation before outputting permuted tick|
//+-----------------------------------------------------------------------+
bool CPermuteTicks::ExpTransformTicks(MqlTick &out_ticks[])
  {
//---apply exponential transform to data and copy original tick data member info
//---not involved in permutation operations
   for(uint k = 1; k<m_ticks.Size(); k++)
     {
      out_ticks[k].bid=(m_logticks[k].bid_d)?MathExp(m_logticks[k].bid_d):0;
      out_ticks[k].ask=(m_logticks[k].ask_d)?MathExp(m_logticks[k].ask_d):0;
      out_ticks[k].volume=(m_logticks[k].vol_d)?(ulong)MathExp(m_logticks[k].vol_d):0;
      out_ticks[k].volume_real=(m_logticks[k].volreal_d)?MathExp(m_logticks[k].volreal_d):0;
      out_ticks[k].flags=m_ticks[k].flags;
      out_ticks[k].last=m_ticks[k].last;
      out_ticks[k].time=m_ticks[k].time;
      out_ticks[k].time_msc=m_ticks[k].time_msc;
     }
//---
   return true;
  }


これで価格系列の順列を処理するアルゴリズムができましたが、これはいわば戦いの半分に過ぎません。


並べ替え検定の手順

順列検定の手順は、MetaTrader 5ターミナルの2つの機能を活用します。1つ目は、カスタム銘柄を作成し、そのプロパティを指定する機能です。2つ目は、気配値表示リストの有効な銘柄に従ってEAを最適化する機能です。このプロセスには少なくともあと2つの手順があります。

ティックの並べ替えやカスタム銘柄の作成が可能で、これらを組み合わせることで、既存の銘柄をベースにカスタム銘柄を生成することができます。各カスタム銘柄は、ベースとして使用される銘柄のティックのユニークな順列で指定されます。銘柄の作成は手作業で行うこともできますが、銘柄の作成とパー並び替えされたティックの追加をすべて自動化できるので、手作業の理由はありません。

PrepareSymbolsForPermutationTestsスクリプトはまさにこれを行います。ユーザー指定の入力により、ベース銘柄、順列で使用するベース銘柄からのティックの日付範囲、作成されるカスタム銘柄の数に対応する必要な順列数、新しいカスタム銘柄の名前に付加されるオプションの文字列識別子を設定することができます。
//+------------------------------------------------------------------+
//|                            PrepareSymbolsForPermutationTests.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include<GenerateSymbols.mqh>
#property script_show_inputs
//--- input parameters
input string   BaseSymbol="EURUSD";
input datetime StartDate=D'2023.06.01 00:00';
input datetime EndDate=D'2023.08.01 00:00';
input uint     Permutations=100;
input string   CustomID="";//SymID to be added to symbol permutation names
//---
CGenerateSymbols generateSymbols();
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   if(!generateSymbols.Initiate(BaseSymbol,CustomID,StartDate,EndDate))
       return;
//---
   Print("Number of newly generated symbols is ", generateSymbols.Generate(Permutations));
//---          
  }
//+------------------------------------------------------------------+


このスクリプトは、基本銘柄名を使用し、最後に列挙を加えた銘柄名を自動的に作成します。これらすべてを行うコードは、CGenerateSymbolsクラスの定義を含むGenerateSymbols.mqhに含まれています。このクラス定義は、他に2つの依存関係に依存しています。NewSymbol.mqhは、「MQL5 Cookbook:カスタム銘柄を使用した取引戦略のストレステスト」のコードから引用したCNewSymbolクラスの定義を含みます。

//+------------------------------------------------------------------+
//| Class CNewSymbol.                                                |
//| Purpose: Base class for a custom symbol.                         |
//+------------------------------------------------------------------+
class CNewSymbol : public CObject
  {
   //--- === Data members === ---
private:
   string            m_name;
   string            m_path;
   MqlTick           m_tick;
   ulong             m_from_msc;
   ulong             m_to_msc;
   uint              m_batch_size;
   bool              m_is_selected;
   //--- === Methods === ---
public:
   //--- constructor/destructor
   void              CNewSymbol(void);
   void             ~CNewSymbol(void) {};
   //--- create/delete
   int               Create(const string _name,const string _path="",const string _origin_name=NULL,
                            const uint _batch_size=1e6,const bool _is_selected=false);
   bool              Delete(void);
   //--- methods of access to protected data
   string            Name(void) const { return(m_name); }
   bool              RefreshRates(void);
   //--- fast access methods to the integer symbol properties
   bool              Select(void) const;
   bool              Select(const bool select);
   //--- service methods
   bool              Clone(const string _origin_symbol,const ulong _from_msc=0,const ulong _to_msc=0);
   bool              LoadTicks(const string _src_file_name);
   //--- API
   bool              SetProperty(ENUM_SYMBOL_INFO_DOUBLE _property,double _val) const;
   bool              SetProperty(ENUM_SYMBOL_INFO_INTEGER _property,long _val) const;
   bool              SetProperty(ENUM_SYMBOL_INFO_STRING _property,string _val) const;
   double            GetProperty(ENUM_SYMBOL_INFO_DOUBLE _property) const;
   long              GetProperty(ENUM_SYMBOL_INFO_INTEGER _property) const;
   string            GetProperty(ENUM_SYMBOL_INFO_STRING _property) const;
   bool              SetSessionQuote(const ENUM_DAY_OF_WEEK _day_of_week,const uint _session_index,
                                     const datetime _from,const datetime _to);
   bool              SetSessionTrade(const ENUM_DAY_OF_WEEK _day_of_week,const uint _session_index,
                                     const datetime _from,const datetime _to);
   int               RatesDelete(const datetime _from,const datetime _to);
   int               RatesReplace(const datetime _from,const datetime _to,const MqlRates &_rates[]);
   int               RatesUpdate(const MqlRates &_rates[]) const;
   int               TicksAdd(const MqlTick &_ticks[]) const;
   int               TicksDelete(const long _from_msc,long _to_msc) const;
   int               TicksReplace(const MqlTick &_ticks[]) const;
   //---
private:
   template<typename PT>
   bool              CloneProperty(const string _origin_symbol,const PT _prop_type) const;
   int               CloneTicks(const MqlTick &_ticks[]) const;
   int               CloneTicks(const string _origin_symbol) const;
  };

このクラスは、既存の銘柄を基に新しいカスタム銘柄を作成するのに役立ちます。最後に必要な依存関係は、すでに遭遇したPermuteTicks.mqhです。

//+------------------------------------------------------------------+
//|                                              GenerateSymbols.mqh |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#include<PermuteTicks.mqh>
#include<NewSymbol.mqh>
//+------------------------------------------------------------------+
//| defines:max number of ticks download attempts and array resize   |
//+------------------------------------------------------------------+
#define MAX_DOWNLOAD_ATTEMPTS 10 
#define RESIZE_RESERVE 100
//+------------------------------------------------------------------+
//|CGenerateSymbols class                                            |
//| creates custom symbols from an existing base symbol's tick data  |
//|  symbols represent permutations of base symbol's ticks           |
//+------------------------------------------------------------------+
class CGenerateSymbols
{
 private:
   string         m_basesymbol;     //base symbol
   string         m_symbols_id;     //common identifier added to names of new symbols 
   long           m_tickrangestart; //beginning date for range of base symbol's ticks
   long           m_tickrangestop;  //ending date for range of base symbol's ticks
   uint           m_permutations;   //number of permutations and ultimately the number of new symbols to create
   MqlTick        m_baseticks[];    //base symbol's ticks
   MqlTick        m_permutedticks[];//permuted ticks;
   CNewSymbol    *m_csymbols[];     //array of created symbols
   CPermuteTicks *m_shuffler;       //object used to shuffle tick data
   
 public: 
   CGenerateSymbols(void);
   ~CGenerateSymbols(void);                      
   bool Initiate(const string base_symbol,const string symbols_id,const datetime start_date,const datetime stop_date);
   uint Generate(const uint permutations);
};


CGenerateSymbolsには、知っておくべき2つのメンバー関数があります。Initiate()メソッドは、オブジェクトの生成後最初に呼び出される必要があり、すでに述べたスクリプトのユーザー入力に対応する4つのパラメータを持っています。

//+-----------------------------------------------------------------------------------------+
//|set and check parameters for symbol creation, download ticks and initialize tick shuffler|
//+-----------------------------------------------------------------------------------------+
bool CGenerateSymbols::Initiate(const string base_symbol,const string symbols_id,const datetime start_date,const datetime stop_date)
{
//---reset number of permutations previously done
 m_permutations=0;
//---set base symbol
 m_basesymbol=base_symbol;
//---make sure base symbol is selected, ie, visible in WatchList 
 if(!SymbolSelect(m_basesymbol,true))
  {
   Print("Failed to select ", m_basesymbol," error ", GetLastError());
   return false;
  }
//---set symbols id 
 m_symbols_id=symbols_id;
//---check, set ticks date range
 if(start_date>=stop_date)
   {
    Print("Invalid date range ");
    return false;
   }
 else
   {
    m_tickrangestart=long(start_date)*1000;
    m_tickrangestop=long(stop_date)*1000;
   }  
//---check shuffler object
   if(CheckPointer(m_shuffler)==POINTER_INVALID)
    {
     Print("CPermuteTicks object creation failed");
     return false;
    }
//---download ticks
   Comment("Downloading ticks");
   uint attempts=0;
   int downloaded=-1;
    while(attempts<MAX_DOWNLOAD_ATTEMPTS)
     {
      downloaded=CopyTicksRange(m_basesymbol,m_baseticks,COPY_TICKS_ALL,m_tickrangestart,m_tickrangestop);
      if(downloaded<=0)
        {
         Sleep(500);
         ++attempts;
        }
      else 
        break;   
     }
//---check download result
   if(downloaded<=0)
    {
     Print("Failed to get tick data for ",m_basesymbol," error ", GetLastError());
     return false;
    }          
  Comment("Ticks downloaded");  
//---return shuffler initialization result   
  return m_shuffler.Initialize(m_baseticks);        
}                      


Generate()メソッドは必要な順列数を入力として受け取り、ターミナルの気配値表示に追加された新しいカスタム銘柄の数を返します。
スクリプトの実行結果は、ターミナルの[エキスパート]タブに表示されます。

//+------------------------------------------------------------------+
//| generate symbols return newly created or refreshed symbols       |
//+------------------------------------------------------------------+
uint CGenerateSymbols::Generate(const uint permutations)
{
//---check permutations
 if(!permutations)
   {
    Print("Invalid parameter value for Permutations ");
    return 0;
   } 
//---resize m_csymbols
  if(m_csymbols.Size()!=m_permutations+permutations)
    ArrayResize(m_csymbols,m_permutations+permutations,RESIZE_RESERVE);
//---
  string symspath=m_basesymbol+m_symbols_id+"_PermutedTicks"; 
  int exists;
//---do more permutations
  for(uint i=m_permutations; i<m_csymbols.Size(); i++)
      {
       if(CheckPointer(m_csymbols[i])==POINTER_INVALID)
              m_csymbols[i]=new CNewSymbol();
       exists=m_csymbols[i].Create(m_basesymbol+m_symbols_id+"_"+string(i+1),symspath,m_basesymbol); 
       if(exists>0)
          {
           Comment("new symbol created "+m_basesymbol+m_symbols_id+"_"+string(i+1) );
            if(!m_csymbols[i].Clone(m_basesymbol) || !m_shuffler.Permute(m_permutedticks))
                 break;
            else
                {
                 m_csymbols[i].Select(true);
                 Comment("adding permuted ticks");
                 if(m_csymbols[i].TicksAdd(m_permutedticks)>0)
                      m_permutations++;
                }              
          }
       else
          {
           Comment("symbol exists "+m_basesymbol+m_symbols_id+"_"+string(i+1) );
           m_csymbols[i].Select(true);
           if(!m_shuffler.Permute(m_permutedticks))
                 break;
           Comment("replacing ticks ");
           if(m_csymbols[i].TicksReplace(m_permutedticks)>0)
              m_permutations++;
           else
              break;   
          } 
     }   
//---return successful number of permutated symbols
 Comment("");
//--- 
 return m_permutations;
}


次の手順は、ストラテジーテスターで最適化を実行し、最後の最適化方法を選択し、テストするEAを指定することです。テストには長い時間がかかる可能性があるため、テストを開始してしばらくの間は、他にやるべきことを見つけます。ストラテジーテスターが終われば、パフォーマンスデータのコレクションが手に入ります。

同梱のMACDサンプルEAを使ってテストを実行することで、実行の様子を見てみましょう。テストは、スクリプトで設定された100の順列でAUDUSD銘柄で実行されます。

スクリプト設定



スクリプトを実行した後、選択したAUDUSD銘柄からのサンプルの順列化されたティックに基づいて、100の余分な銘柄を取得します。

気配値表示のカスタム銘柄



最後に最適化テストを実行します。

テスター設定

  使用したEAの設定を以下に示します。

EA設定

テストの結果

最適化結果


ストラテジーテスターの[結果]タブには、私たちが興味を持ちそうなパフォーマンス数値がすべて表示され、テスターウィンドウの右上隅にあるドロップダウンメニューで選択できる、選択されたパフォーマンス基準に基づいて、銘柄が降順に並べられます。このビューから、p値を手動で簡単に計算することができます。また、必要であれば、テスターから右クリックでエクスポートできる.xmlファイルを処理することで自動的に計算することもできます。

この例を使用すると、計算を実行する必要さえありません。元の銘柄のテスト数値が[結果]タブのずっと下にあり、10を超える並べ替えられた銘柄がより優れたパフォーマンスをクロックしていることがわかるからです。これはp値が0.05以上であることを示しています。

もちろん、テスト期間が非常に短かったため、この結果は多少割り引いて見るべきでしょう。実際の取引で遭遇する可能性の高い条件を代表するような、より実質的な長さのテスト期間を選択する必要があります。

すでに述べたように、p値を計算するには、結果をさらに処理するための多くのオプションが用意されています。それ以降の操作は、ストラテジーテスターからエクスポートされたxmlファイルからのデータ解析が中心となります。ここでは、スプレッドシートアプリケーションを使って、数回のクリックとキー操作でファイルを処理する方法を紹介します。

明らかに、ファイルをエクスポートした後、保存場所をメモし、任意のスプレッドシートアプリを使って開きます。下の図は無料のOpenOffice Calcを使用したもので、表の一番下に新しい行が追加されています。これ以上先に進む前に、計算の一部とすべきでない記号の行を削除したほうがいいでしょう。該当する各列の下で、カスタムマクロを使ってp値が計算されます。マクロの計算式は、各列の並べ替え銘柄のパフォーマンスメトリクスと同様に、並べ替え銘柄のパフォーマンスメトリクス(示されたドキュメントの18行目)を参照します。  マクロの完全な計算式を図に示します。

OpenOffice CalcでのP値の計算

表計算アプリケーションを使う以外に、xmlファイルを解析するモジュールが豊富にあるpythonを使うこともできます。MQL5に習熟していれば、簡単なスクリプトでファイルを解析することも可能です。テスターから最適化の結果をエクスポートする際には、アクセス可能なディレクトリを選ぶことを忘れないでください。

結論

ソースコードにアクセスすることなく、並べ替えテストをあらゆるEAに適用できることを実証しました。このような並べ替え検定は、関係するデータの分布について仮定を置く必要のない、かなりロバストな統計量を適用するので、非常に貴重です。ストラテジーの開発に用いられる他の多くの統計的検定とは異なります。

最大の欠点は、テストを実施するのに必要な時間と計算資源に関するものです。強力なプロセッサーだけでなく、大容量のストレージも必要になります。新しいティックや銘柄を生成すると、ハードディスクの空き容量が消費されます。私の意見では、EAを購入することがある人は誰でも、この分析方法に注意すべきです。時間はかかりますが、将来的に損をするような間違った決断をせずに済むかもしれません。

順列化された価格データを使用した分析は、複数の方法で適用することができます。  この方法は、ストラテジー開発のさまざまな段階だけでなく、指標の挙動を分析するためにも使うことができます。可能性は広大です。ストラテジーを練ったりテストしたりするとき、データが足りないと思われることがあります。順列化された価格系列を使うことで、テストに利用できるデータが大幅に増えます。この記事で紹介したすべてのMQL5プログラムのソースコードを添付しておきます。

ファイル名
プログラムの種類
詳細
GenerateSymbols.mqh
インクルードファイル
選択された基本銘柄から並べ替えられたティックデータを持つ銘柄を生成するためのCGenerateSymbolsクラスの定義が含まれている
NewSymbol.mqh
インクルードファイル
カスタム銘柄を作成するためのCNewSymbolクラス定義が含まれている
PermuteTicks.mqh
インクルードファイル
ティックデータの配列の順列を作成するためのCPermuteTicksクラスを定義する
PrepareSymbolsForPermutationTests.mq5
スクリプトファイル
並べ替えテストの準備として、並べ替えられたティックを持つカスタム銘柄の作成を自動化するスクリプト


MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/13162

添付されたファイル |
NewSymbol.mqh (29.34 KB)
PermuteTicks.mqh (8.78 KB)
Mql5.zip (9.91 KB)
リプレイシステムの開発 - 市場シミュレーション(第17回):ティックそしてまたティック(I) リプレイシステムの開発 - 市場シミュレーション(第17回):ティックそしてまたティック(I)
ここでは、非常に興味深いものを実装する方法を見ていきますが、同時に、非常にわかりにくい点があるため非常に難しくなります。起こり得る最悪の事態は、自分をプロだと思っている一部のトレーダーが、資本市場におけるこれらの概念の重要性について何も知らないことです。さて、ここではプログラミングに焦点を当てていますが、私たちが実装しようとしているものにとって最も重要なのは市場取引に伴う問題のいくつかを理解することです。
リプレイシステムの開発 - 市場シミュレーション(第16回):新しいクラスシステム リプレイシステムの開発 - 市場シミュレーション(第16回):新しいクラスシステム
もっと仕事を整理する必要があります。コードはどんどん大きくなっており、今やらなければ不可能になります。分割して征服しましょう。MQL5では、このタスクを実行するのに役立つクラスを使用することができますが、そのためにはクラスに関する知識が必要です。おそらく初心者を最も混乱させるのは継承でしょう。この記事では、これらのメカニズムを実用的かつシンプルな方法で使用する方法を見ていきます。
ニューラルネットワークが簡単に(第52回):楽観論と分布補正の研究 ニューラルネットワークが簡単に(第52回):楽観論と分布補正の研究
経験再現バッファに基づいてモデルが訓練されるにつれて、現在のActor方策は保存されている例からどんどん離れていき、モデル全体としての訓練効率が低下します。今回は、強化学習アルゴリズムにおけるサンプルの利用効率を向上させるアルゴリズムについて見ていきます。
MQL5での発注を理解する MQL5での発注を理解する
取引システムを構築する際には、効果的に処理しなければならない作業があります。この作業は、注文の発注、または作成された取引システムに注文を自動的に処理させることです。これはあらゆる取引システムにおいて極めて重要だからです。この記事では、発注が効果的な取引システムを作成する作業のために理解する必要があるほとんどのトピックについて説明します。