![MQL5での価格バーの並べ替え](https://c.mql5.com/2/59/Permuting_price_bars_600x314.jpg)
MQL5での価格バーの並べ替え
はじめに
Metatrader 5のストラテジーテスターは、エキスパートアドバイザー(EA)の可能性を評価するために多くの人が使用する主要ツールです。その機能は十分ですが、経験豊富な開発者はそれを使って、並外れたパフォーマンスを装う「トリック」EAを作ることができます。私たちは皆、EAの売り手の信じられないようなパフォーマンスを示す資本曲線のスクリーンショットを見たことがあります。一見するとすべてが印象的に見えますが、現実の世界で戦略を適用すると、まったく異なる資本曲線を生み出すことがよくあります。どうすれば、このような安っぽいトリックに引っかかる煩わしさから逃れられるのでしょうか。この記事では、そのようなシステムを見て、順列検定がどのように誤解を招くような資本曲線の煙幕を切り、戦略のパフォーマンスをより正確に把握するために使用できるかを示します。また、前回の記事では、ティックデータを並べ替えるアルゴリズムの実装を見ましたが、今回は、価格バーを並べ替える方法について説明します。
OHLCデータの並び替え
価格バーの並び替えは、複数のシリーズが関係するため、少し難しくなります。ティックデータの並べ替えと同様に、価格バーを扱う際には、元の価格系列の一般的なトレンドを維持するように努めます。また、バーの始値や終値が、それぞれ高値や安値の境界線を超えたり、下回ったりしないようにすることも重要です。目標は、元のデータとまったく同じ特徴分布を持つ一連のバーを得ることです。
トレンドに加え、シリーズが始値から終値まで進むにつれて、価格変動の分散を維持する必要があります。始値と終値の間の価格変動の広がりは、元のバーと同じでなければならなくなります。バーの外では、バー間の価格変動の分布が同じであることを確認しなければならなくなります。具体的には、あるバーの終値と次のバーの始値の差です。
これは、テストされる戦略が不利にならないようにするために重要です。シリーズの一般的な特性は似ているはずで、唯一の違いは、最初のバーと最後のバーの間の各始値、高値、安値、終値(OHLC)の絶対値のはずです。これを実装するコードは、「MetaTrader 5でのモンテカルロ並べ替え検定」で紹介したCPermuteTicksクラスで使用されているものとよく似ています。価格バーの並べ替えコードは、PermuteRates.mqhに含まれるCPermuteRatesクラスにカプセル化されます。
CPermuteRatesクラス
//+------------------------------------------------------------------+ //| struct to handle relative values of rate data to be worked on | //+------------------------------------------------------------------+ struct CRelRates { double rel_open; double rel_high; double rel_low; double rel_close; }; //+------------------------------------------------------------------+ //| Class to enable permuation of a collection of ticks in an array | //+------------------------------------------------------------------+ class CPermuteRates { private : MqlRates m_rates[]; //original rates to be shuffled CRelRates m_differenced[]; //log difference of rates bool m_initialized; //flag to signal state of random number object CUniFrand *m_random; //random number generator public : //constructor CPermuteRates(void); //desctructor ~CPermuteRates(void); bool Initialize(MqlRates &in_rates[]); bool Permute(MqlRates &out_rates[]); };
PermuteRate.mqhは、生価格の対数差を保存する単純な構造体の定義から始まります。
- rel_open:現在の始値と前のバーの終値との差の対数
- rel_high:現在のバーの高値と始値との差の対数
- rel_low:現在のバーの安値と始値との差の対数
- rel_close:現在のバーの終値と始値との差の対数
カスタムCRelRates構造体は、MqlRatesから抽出され、順列付けされるデータを表します。MqlRatesの他の構造体は変更されません。並び替えられたレートの最終結果には、元の価格シリーズからこれらの構成メンバーがコピーされます。すでに述べたように、変わるのはOHLCの値だけです。
//+------------------------------------------------------------------+ //| Permute the bars | //+------------------------------------------------------------------+ bool CPermuteRates::Permute(MqlRates &out_rates[]) { //--- if(!m_initialized) { Print("Initialization error"); ZeroMemory(out_rates); return false; } //--- int i,j; double temp=0.0; //--- i=ArraySize(m_rates)-2; //--- while(i > 1 && !IsStopped()) { j = (int)(m_random.RandomDouble() * i) ; if(j >= i) j = i - 1 ; --i ; temp = m_differenced[i+1].rel_open ; m_differenced[i+1].rel_open = m_differenced[j+1].rel_open ; m_differenced[j+1].rel_open = temp ; } //--- i =ArraySize(m_rates)-2; //--- while(i > 1 && !IsStopped()) { j = (int)(m_random.RandomDouble() * i) ; if(j >= i) j = i - 1 ; --i ; temp = m_differenced[i].rel_high; m_differenced[i].rel_high = m_differenced[j].rel_high ; m_differenced[j].rel_high = temp ; temp = m_differenced[i].rel_low ; m_differenced[i].rel_low = m_differenced[j].rel_low ; m_differenced[j].rel_low = temp ; temp = m_differenced[i].rel_close ; m_differenced[i].rel_close = m_differenced[j].rel_close ; m_differenced[j].rel_close = temp ; } //--- if(ArrayCopy(out_rates,m_rates)!=int(m_rates.Size())) { ZeroMemory(out_rates); Print("Copy error ", GetLastError()); return false; } //--- for(i=1 ; i<ArraySize(out_rates) && !IsStopped() ; i++) { out_rates[i].open = MathExp(((MathLog(out_rates[i-1].close)) + m_differenced[i-1].rel_open)) ; out_rates[i].high = MathExp(((MathLog(out_rates[i].open)) + m_differenced[i-1].rel_high)) ; out_rates[i].low = MathExp(((MathLog(out_rates[i].open)) + m_differenced[i-1].rel_low)) ; out_rates[i].close = MathExp(((MathLog(out_rates[i].open)) + m_differenced[i-1].rel_close)) ; } //--- if(IsStopped()) return false; //--- return true; //--- }
並べ替えはPermute()メソッドでおこなわれます。CRelRates構造体は、バーデータを 2 種類の記述子に分割します。rel_openの一連の値は、あるバーから次のバーへの変化を表し、rel_high、rel_low、rel_closeはバー内の変化を表します。バーを並べ替えるには、まずrel_openの一連の価格をシャッフルします。これらはバー間の違いです。そこからバー内の変化がシャッフルされます。新しいOHLC系列は、シャッフルされたバー間データから構築され、シャッフルされたバー内の変化から構築された対応する高値、安値、終値と新しい始値を得ます。
CPermuteTicksの変更点
CPermuteRatesと古いCPermuteTicksクラスには、いくつかの違いがあります。その1つが、カスタム乱数生成器の使用です。これは、MQL5の内蔵関数を使うより少し速いことがわかりました。
//+------------------------------------------------------------------+ //| UniformRandom.mqh | //| Copyright 2023, MetaQuotes Software Corp. | //| https://www.MQL5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Software Corp." #property link "https://www.MQL5.com" //+----------------------------------------------------------------------+ //| CUniFrand class: Uniformly distributed random 0 - 1 number generator| //+----------------------------------------------------------------------+ class CUniFrand { private : uint m_m[256]; int m_mwc_initialized; int m_mwc_seed; uint m_carry; uint random(void); public : //constructor CUniFrand(void); //desctructor ~CUniFrand(void); //optionally set a seed for number generator void SetSeed(const int iseed); //get random number between 0 and 1 double RandomDouble(void); }; //+------------------------------------------------------------------+ //| Default constructor | //+------------------------------------------------------------------+ CUniFrand::CUniFrand(void) { m_mwc_initialized=0; m_mwc_seed=123456789; m_carry=362436; } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CUniFrand::~CUniFrand(void) { } //+------------------------------------------------------------------+ //| creates and returns random integer number | //+------------------------------------------------------------------+ uint CUniFrand::random(void) { uint t,a=809430660; static uchar i; if(!m_mwc_initialized) { uint k,j=m_mwc_seed; m_mwc_initialized=1; for(k=0; k<256; k++) { j = 69069 * j + 12345; m_m[k]=j; } } t=a*m_m[++i] + m_carry; m_carry = (uint)(t>>32); m_m[i] = (uint)(t&UINT_MAX); return m_m[i]; } //+------------------------------------------------------------------+ //| Optionally set the seed for random number generator | //+------------------------------------------------------------------+ void CUniFrand::SetSeed(const int iseed) { m_mwc_seed=iseed; m_mwc_initialized=0; } //+------------------------------------------------------------------+ //| returns a random number between 0 and 1 | //+------------------------------------------------------------------+ double CUniFrand::RandomDouble(void) { double mult =1.0/UINT_MAX; return mult * random(); } //+------------------------------------------------------------------+
これは新しいCPermuteTicksクラスにも適用されています。効率化のために不要な中間業務は排除されています。Bid価格だけがシャッフルされます。他のティックプロパティが元のティックシリーズからコピーされることで、非現実的なスプレッドを持つ順列ティックになることがあった問題が解決されます。新しいCPermuteTickシリーズは以下の通りです。
//+------------------------------------------------------------------+ //| PermuteTicks.mqh | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.MQL5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.MQL5.com" #include<UniformRandom.mqh> //+------------------------------------------------------------------+ //| Class to enable permuation of a collection of ticks in an array | //+------------------------------------------------------------------+ class CPermuteTicks { private : MqlTick m_ticks[]; //original tick data to be shuffled double m_differenced[]; //log difference of tick data bool m_initialized; //flag representing proper preparation of a dataset CUniFrand *m_random; public : //constructor CPermuteTicks(void); //desctrucotr ~CPermuteTicks(void); bool Initialize(MqlTick &in_ticks[]); bool Permute(MqlTick &out_ticks[]); }; //+------------------------------------------------------------------+ //| constructor | //+------------------------------------------------------------------+ CPermuteTicks::CPermuteTicks(void):m_initialized(false) { m_random = new CUniFrand(); m_random.SetSeed(MathRand()); } //+------------------------------------------------------------------+ //| destructor | //+------------------------------------------------------------------+ CPermuteTicks::~CPermuteTicks(void) { delete m_random; //---clean up ArrayFree(m_ticks); //--- ArrayFree(m_differenced); //--- } //+--------------------------------------------------------------------+ //|Initialize the permutation process by supplying ticks to be permuted| //+--------------------------------------------------------------------+ bool CPermuteTicks::Initialize(MqlTick &in_ticks[]) { //---check the random number object if(m_random==NULL) { Print("Critical internal error, failed to initialize random number generator"); return false; } //---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); //---fill m_differenced with differenced values, excluding the first tick for(uint i=1; i<m_ticks.Size() && !IsStopped(); i++) { m_differenced[i-1]=MathLog(m_ticks[i].bid/m_ticks[i-1].bid);//(m_logticks[i])-(m_logticks[i-1]); } //---set the initilization flag m_initialized=true; //--- return true; } //+------------------------------------------------------------------+ //|Public method which applies permutation and gets permuted ticks | //+------------------------------------------------------------------+ bool CPermuteTicks::Permute(MqlTick &out_ticks[]) { //---ensure required data already supplied through initialization if(!m_initialized) { Print("not initialized"); return false; } //--- int i,j; double tempvalue; i=(int)m_ticks.Size()-1; while(i>1 && !IsStopped()) { j=(int)(m_random.RandomDouble()*i); if(j>=i) j=i-1; --i; //---swap tick data randomly tempvalue=m_differenced[i]; m_differenced[i]=m_differenced[j]; m_differenced[j]=tempvalue; } //---- if(IsStopped()) return false; //---copy the first tick if(ArrayCopy(out_ticks,m_ticks)!=int(m_ticks.Size())) { Print(__FUNCTION__," array copy failure ", GetLastError()); return false; } //---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() && !IsStopped(); k++) { out_ticks[k].bid=MathExp((MathLog(out_ticks[k-1].bid) + m_differenced[k-1]));//MathExp(m_logticks[k]); out_ticks[k].ask=out_ticks[k].bid + (m_ticks[k].ask - m_ticks[k].bid); } //--- if(IsStopped()) return false; else return true; } //+------------------------------------------------------------------+
CPermuteTicksは以前のバージョンと同じように動作し、CPermuteRatesも同様に動作します。この2つの違いは、一方が刻みで動くのに対し、もう一方はレートで動くことです。
CPermutedSymbolDataクラス
スクリプトPrepareSymbolsForPermutationTestが更新され、CPermuteTicksの変更とCPermuteRatesの導入が反映されました。スクリプトの機能で、CPermutedSymbolDataクラスで囲まれており、既存の銘柄を基に、並べ替えられたティックやレートを持つカスタム銘柄を作成することができます。
//+------------------------------------------------------------------+ //|Permute rates or ticks of symbol | //+------------------------------------------------------------------+ enum ENUM_RATES_TICKS { ENUM_USE_RATES=0,//Use rates ENUM_USE_TICKS//Use ticks }; //+------------------------------------------------------------------+ //| defines:max number of data download attempts and array resize | //+------------------------------------------------------------------+ #define MAX_DOWNLOAD_ATTEMPTS 10 #define RESIZE_RESERVE 100 //+------------------------------------------------------------------+ //|CPermuteSymbolData class | //| creates custom symbols from an existing base symbol's data | //| symbols represent permutations of base symbol's data | //+------------------------------------------------------------------+ class CPermuteSymbolData { private: ENUM_RATES_TICKS m_use_rates_or_ticks;//permute either ticks or rates string m_basesymbol; //base symbol string m_symbols_id; //common identifier added to names of new symbols datetime m_datarangestart; //beginning date for range of base symbol's data datetime m_datarangestop; //ending date for range of base symbol's data uint m_permutations; //number of permutations and ultimately the number of new symbols to create MqlTick m_baseticks[]; //base symbol's tick MqlTick m_permutedticks[]; //permuted ticks; MqlRates m_baserates[]; //base symbol's rates MqlRates m_permutedrates[]; //permuted rates; CPermuteRates *m_rates_shuffler; //object used to shuffle rates CPermuteTicks *m_ticks_shuffler; //object used to shuffle ticks CNewSymbol *m_csymbols[]; //array of created symbols public: CPermuteSymbolData(const ENUM_RATES_TICKS mode); ~CPermuteSymbolData(void); bool Initiate(const string base_symbol,const string symbols_id,const datetime start_date,const datetime stop_date); uint Generate(const uint permutations); };
これは、コンストラクタの呼び出しで、シャッフルするデータの種類(ティックまたはレート)を指定することで実現します。ENUM_RATES_TICKS列挙型は、コンストラクタの単一パラメータで使用可能なオプションを記述します。
//+-----------------------------------------------------------------------------------------+ //|set and check parameters for symbol creation, download data and initialize data shuffler | //+-----------------------------------------------------------------------------------------+ bool CPermuteSymbolData::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 data date range if(start_date>=stop_date) { Print("Invalid date range "); return false; } else { m_datarangestart= start_date; m_datarangestop = stop_date; } //---download data Comment("Downloading data"); uint attempts=0; int downloaded=-1; while(attempts<MAX_DOWNLOAD_ATTEMPTS && !IsStopped()) { downloaded=(m_use_rates_or_ticks==ENUM_USE_TICKS)?CopyTicksRange(m_basesymbol,m_baseticks,COPY_TICKS_ALL,long(m_datarangestart)*1000,long(m_datarangestop)*1000):CopyRates(m_basesymbol,PERIOD_M1,m_datarangestart,m_datarangestop,m_baserates); if(downloaded<=0) { Sleep(500); ++attempts; } else break; } //---check download result if(downloaded<=0) { Print("Failed to download data for ",m_basesymbol," error ", GetLastError()); Comment(""); return false; } //Print(downloaded," Ticks downloaded ", " data start ",m_basedata[0].time, " data end ", m_basedata[m_basedata.Size()-1].time); //---return shuffler initialization result switch(m_use_rates_or_ticks) { case ENUM_USE_TICKS: { if(m_ticks_shuffler==NULL) m_ticks_shuffler=new CPermuteTicks(); return m_ticks_shuffler.Initialize(m_baseticks); } case ENUM_USE_RATES: { if(m_rates_shuffler==NULL) m_rates_shuffler=new CPermuteRates(); return m_rates_shuffler.Initialize(m_baserates); } default: return false; } }
CPermutedSymbolDataのインスタンスが作成されたら、Initiate()メソッドを呼び出して、銘柄と、順列の基となるティックまたはレートを定義する日付期間を指定しなければならなくなります。
//+------------------------------------------------------------------+ //| generate symbols return newly created or refreshed symbols | //+------------------------------------------------------------------+ uint CPermuteSymbolData::Generate(const uint permutations) { //---check permutations if(!permutations) { Print("Invalid parameter value for Permutations "); Comment(""); 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+"_PermutedData"; //int exists; //---do more permutations for(uint i=m_permutations; i<m_csymbols.Size() && !IsStopped(); i++) { if(CheckPointer(m_csymbols[i])==POINTER_INVALID) m_csymbols[i]=new CNewSymbol(); if(m_csymbols[i].Create(m_basesymbol+m_symbols_id+"_"+string(i+1),symspath,m_basesymbol)<0) continue; Comment("Processing Symbol "+m_basesymbol+m_symbols_id+"_"+string(i+1)); if(!m_csymbols[i].Clone(m_basesymbol) || (m_use_rates_or_ticks==ENUM_USE_TICKS && !m_ticks_shuffler.Permute(m_permutedticks)) || (m_use_rates_or_ticks==ENUM_USE_RATES && !m_rates_shuffler.Permute(m_permutedrates))) break; else { m_csymbols[i].Select(true); Comment("Adding permuted data"); if(m_use_rates_or_ticks==ENUM_USE_TICKS) m_permutations+=(m_csymbols[i].TicksReplace(m_permutedticks)>0)?1:0; else m_permutations+=(m_csymbols[i].RatesUpdate(m_permutedrates)>0)?1:0; } } //---return successfull number of permutated symbols Comment(""); //--- if(IsStopped()) return 0; //--- return m_permutations; } //+------------------------------------------------------------------+
Initiate()がtrueを返した場合、必要な順列の数を指定してGenerate()メソッドを呼び出すことができます。このメソッドは、ティックまたはレートの置換が成功したカスタム銘柄の数を返します。
//+------------------------------------------------------------------+ //| 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<PermutedSymbolData.mqh> #property script_show_inputs //--- input parameters input string BaseSymbol="EURUSD"; input ENUM_RATES_TICKS PermuteRatesOrTicks=ENUM_USE_RATES; input datetime StartDate=D'2022.01.01 00:00'; input datetime EndDate=D'2023.01.01 00:00'; input uint Permutations=100; input string CustomID="_p";//SymID to be added to symbol permutation names //--- CPermuteSymbolData *symdata; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { ulong startime = GetTickCount64(); uint permutations_completed=0; // number of successfully added permuted data //---intialize the permuted symbol object symdata = new CPermuteSymbolData(PermuteRatesOrTicks); //---set the properties of the permuted symbol object if(symdata.Initiate(BaseSymbol,CustomID,StartDate,EndDate)) permutations_completed = symdata.Generate(Permutations); // do the permutations //---print number of symbols whose bar or tick data has been replenished. Print("Number of permuted symbols is ", permutations_completed, ", Runtime ",NormalizeDouble(double(GetTickCount64()-startime)/double(60000),1),"mins"); //---clean up delete symdata; } //+------------------------------------------------------------------+
上記はスクリプトのコードです。ソースコードはすべて本稿に添付があります。
並べ替え検定の適用
記事の冒頭で、EAの購入を検討している多くの人が直面する共通の問題についてお話しました。不誠実な売り手が商品を売り込むために、人を欺くような手口を使う可能性があります。売り手はしばしば、潜在的な利益を表すために、魅力的な資本曲線のスクリーンショットを表示します。多くの人がこのような手口の犠牲となり、これらのスクリーンショットが誘導された戦略から生み出されたものであることを身をもって知ることになります。このセクションでは、コードベースで利用可能な、誤解を招くような資本曲線を作成するために使用可能な悪名高いEAを見てみましょう。そして、順列検定を適用してごまかしを暴きます。
並べ替え検定の概要
この種のテストは非常に手間がかかり、かなりの時間と計算資源を必要とすることに留意すべきです。しかし、私見ではありますが、その結果は努力に値するものであり、とんでもない決断をせずに済むこともあります。採用された方法は、検査に適切なサンプルを選択することです。このサンプルをサンプル内とサンプル外のデータセットに分けます。EAはサンプル内データで最適化され、最適化されたパラメータを用いてサンプル外データで実施されたテストで最終的な性能が記録されます。これは、元のデータ系列と、少なくとも100の並べ替えデータセットを用いておこなわれます。デモで使用したEAをテストするためにおこなったのはまさにこれです。
grr-al EAのテスト
MQL5のドキュメントを調べたり、コードベースを探索したことのある人なら、このEAに出くわしたことがあるに違いありません。MQL5のドキュメントでは「テストの聖杯」と表現されています。ストラテジーテスターで1分OHLCまたは建値のみティック生成モードで実行すると、印象的なプロファイルを持つ資本曲線が生成されます。これが今回のデモで使用するEAです。最適化のため、グローバル変数のいくつかを公開するようにコードを少し修正しました。3つのパラメータのうち2つ、すなわちSL(ストップロス)とDELTAが最適化のために択されました。
#define MAGIC_NUMBER 12937 #define DEV 20 #define RISK 0.0 #define BASELOT 0.1 input double DELTA =30; input double SL =700; input double TP =100;
最適化に使用された設定は図に示されています。
選択したデータセットはEURUSDで、2022年通年の1時間足です。最適化には2022年の前半6カ月が使われ、後半は最適なパラメータをテストするためのサンプル外期間として使われました。
まず、PrepareSymbolsForPermutationsTestsスクリプトを使って、順列データのカスタム銘柄を生成しました。プログラムの実行時間を計測し、以下のように記録しました。このエラーコードは、最初の試行でドライブの空き容量がなくなり、99個のカスタム銘柄しか追加できなかったからです。
PR 0 11:53:04.548 PrepareSymbolsForPermutationTests (EURUSD,MN1) CNewSymbol::TicksReplace: failed to replace ticks! Error code: 5310 EL 0 11:53:04.702 PrepareSymbolsForPermutationTests (EURUSD,MN1) Number of permuted symbols is 99, Runtime 48.9mins
生成されたデータ量は、1年分のデータを100回並べ替えた場合、約40ギガバイトのティックデータとなりました。
身のために、レートを使った方がはるかに速く、場所もとりません。
NK 0 12:51:23.166 PrepareSymbolsForPermutationTests (EURUSD,M1) Number of permuted symbols is 100, Runtime 1.4mins
このデータを用いて、各銘柄はサンプル内セットで最適化されました。
最大の絶対利益をもたらしたパラメータが、サンプル外テストに使用されました。始値のみのティックモードを使用して、最適化とサンプル外テストを実施しました。つまり、EAには輝きを放つためのあらゆるアドバンテージがあったということです。
すべてのテスト結果をcsvファイルで以下に示します。IS利益とOOS利益は、それぞれサンプル内利益とサンプル外利益です。
<SYMBOL> <OPTIMAL DELTA> <OPTIMAL SL> <IS PROFIT> <OOS PROFIT> EURUSD 3.00 250.00 31995.60 32347.20 EURUSD_p_1 3.00 50.00 29283.40 34168.20 EURUSD_p_2 5.00 50.00 32283.50 21047.60 EURUSD_p_3 3.00 20.00 33696.20 34915.30 EURUSD_p_4 3.00 20.00 32589.30 38693.20 EURUSD_p_5 3.00 230.00 33771.10 40458.20 EURUSD_p_6 3.00 40.00 30899.10 34061.50 EURUSD_p_7 3.00 250.00 34309.10 31861.20 EURUSD_p_8 3.00 40.00 33729.00 35359.90 EURUSD_p_9 3.00 300.00 36027.90 38174.50 EURUSD_p_10 3.00 30.00 33405.90 35693.70 EURUSD_p_11 3.00 30.00 32723.30 36453.00 EURUSD_p_12 11.00 300.00 34191.20 34277.80 EURUSD_p_13 3.00 130.00 35029.70 33930.00 EURUSD_p_14 11.00 290.00 33924.40 34851.70 EURUSD_p_15 3.00 140.00 33920.50 32263.20 EURUSD_p_16 3.00 20.00 34388.00 33694.40 EURUSD_p_17 3.00 60.00 35081.70 35612.20 EURUSD_p_18 5.00 70.00 36830.00 40442.30 EURUSD_p_19 3.00 170.00 37693.70 37404.90 EURUSD_p_20 3.00 50.00 31265.30 34875.10 EURUSD_p_21 3.00 20.00 30248.10 38426.00 EURUSD_p_22 5.00 250.00 32369.80 37263.80 EURUSD_p_23 7.00 50.00 31197.50 35466.40 EURUSD_p_24 7.00 30.00 26252.20 34963.10 EURUSD_p_25 3.00 20.00 31343.90 37156.00 EURUSD_p_26 25.00 280.00 29762.10 27336.10 EURUSD_p_27 3.00 60.00 33775.10 37034.60 EURUSD_p_28 3.00 260.00 35341.70 36744.20 EURUSD_p_29 5.00 50.00 31775.80 34673.60 EURUSD_p_30 3.00 20.00 32520.30 37907.10 EURUSD_p_31 3.00 230.00 35481.40 42938.20 EURUSD_p_32 3.00 100.00 32862.70 38291.70 EURUSD_p_33 3.00 190.00 36511.70 26714.30 EURUSD_p_34 3.00 290.00 29809.10 35312.40 EURUSD_p_35 3.00 290.00 34044.60 33460.00 EURUSD_p_36 3.00 90.00 32203.10 35730.90 EURUSD_p_37 3.00 180.00 39506.50 30947.30 EURUSD_p_38 3.00 180.00 35844.90 41717.30 EURUSD_p_39 3.00 90.00 30602.30 35390.10 EURUSD_p_40 3.00 250.00 29592.20 33025.90 EURUSD_p_41 3.00 140.00 34281.80 31501.40 EURUSD_p_42 3.00 30.00 34235.70 39422.40 EURUSD_p_43 3.00 170.00 35580.10 35994.20 EURUSD_p_44 3.00 20.00 34400.60 36250.50 EURUSD_p_45 5.00 190.00 35942.70 31068.30 EURUSD_p_46 3.00 20.00 32560.60 37114.70 EURUSD_p_47 3.00 200.00 36837.30 40843.10 EURUSD_p_48 3.00 20.00 29188.30 33418.10 EURUSD_p_49 3.00 40.00 33985.60 29720.50 EURUSD_p_50 3.00 250.00 36849.00 38007.00 EURUSD_p_51 3.00 50.00 33867.90 39323.30 EURUSD_p_52 3.00 120.00 33066.30 39852.40 EURUSD_p_53 3.00 60.00 36977.30 37284.40 EURUSD_p_54 3.00 20.00 29990.30 35975.70 EURUSD_p_55 15.00 70.00 29872.80 34179.40 EURUSD_p_56 3.00 250.00 35909.60 35911.50 EURUSD_p_57 3.00 200.00 37642.70 34849.80 EURUSD_p_58 3.00 290.00 39164.00 35440.90 EURUSD_p_59 3.00 100.00 28312.70 33917.80 EURUSD_p_60 3.00 60.00 28141.60 38826.00 EURUSD_p_61 3.00 50.00 29670.90 34973.70 EURUSD_p_62 3.00 40.00 32170.80 31062.60 EURUSD_p_63 3.00 260.00 28312.80 29236.50 EURUSD_p_64 3.00 20.00 31632.50 35458.30 EURUSD_p_65 3.00 260.00 35345.20 38522.70 EURUSD_p_66 7.00 270.00 31077.60 34531.10 EURUSD_p_67 3.00 90.00 33893.70 30969.00 EURUSD_p_68 3.00 170.00 34118.70 37280.50 EURUSD_p_69 3.00 40.00 33867.50 35256.20 EURUSD_p_70 3.00 180.00 37710.60 30337.20 EURUSD_p_71 5.00 200.00 40851.10 40985.60 EURUSD_p_72 3.00 20.00 29258.40 31194.70 EURUSD_p_73 3.00 20.00 30956.50 38021.40 EURUSD_p_74 3.00 90.00 35807.40 32625.70 EURUSD_p_75 3.00 260.00 32801.10 36161.70 EURUSD_p_76 3.00 260.00 34825.40 28957.70 EURUSD_p_77 3.00 90.00 39725.80 35923.00 EURUSD_p_78 3.00 180.00 37880.80 37090.90 EURUSD_p_79 3.00 180.00 34191.50 38190.70 EURUSD_p_80 3.00 40.00 29235.30 33207.70 EURUSD_p_81 3.00 20.00 29923.50 34291.00 EURUSD_p_82 3.00 90.00 35077.80 37203.40 EURUSD_p_83 3.00 40.00 32901.50 32182.40 EURUSD_p_84 3.00 50.00 31302.60 34339.00 EURUSD_p_85 3.00 60.00 30336.90 37948.10 EURUSD_p_86 5.00 50.00 35166.10 37898.60 EURUSD_p_87 5.00 290.00 33005.20 32648.30 EURUSD_p_88 7.00 140.00 34349.70 31435.50 EURUSD_p_89 3.00 20.00 30680.20 37002.30 EURUSD_p_90 3.00 100.00 35382.50 37643.80 EURUSD_p_91 3.00 50.00 35187.20 36392.00 EURUSD_p_92 3.00 120.00 32423.10 35943.20 EURUSD_p_93 3.00 100.00 31722.70 39913.30 EURUSD_p_94 11.00 300.00 31548.40 32684.70 EURUSD_p_95 3.00 100.00 30094.00 38929.70 EURUSD_p_96 3.00 170.00 35400.30 29260.30 EURUSD_p_97 3.00 300.00 35696.50 35772.20 EURUSD_p_98 3.00 20.00 31336.20 35935.70 EURUSD_p_99 3.00 20.00 32466.30 39986.40 EURUSD_p_100 3.00 20.00 32082.40 33625.10
算出されたp値は0.8217821782178217です。
MO 0 09:49:57.991 ProcessOptFiles (EURUSD,MN1) P-value is 0.8217821782178217
これは、運によって元のデータセットで達成されたパフォーマンスを観察できる確率は80%以上であると断言しています。このEAが無価値であることが明確に示されています。
なぜこれが有効なのでしょうか。
ストラテジー開発の文脈における並び替え検定の前提は、EAストラテジーとは、取引で優位に立つために使用されるパターンやルールのセットの記述であるということです。対象となるデータが並び替えられると、それに利益を与えただろう本来のパターンが破壊されてしまいます。EAが実際に何らかのパターンに基づいて取引している場合、並べ替えられたデータでのパフォーマンスは低下します。並び替えられたテストと並び替えられていないテストのパフォーマンスを比較すると、最適化した後でも、EAが実際には何らかのユニークなパターンやルールに依存していることが明らかになります。並び替えられていないデータセットのパフォーマンスは、並び替えられたテストよりも際立っているはずです。
実証されたテストからわかるように、問題のEAはティック生成の方法を悪用することが知られており、実際の戦略(パターンやルール)を採用していません。並び替え検定によって、このことが明らかになりました。
並べ替え検定は、最適化後の過剰学習の程度を示すために使用することもできます。過剰学習をテストするためには、並び替えられたデータセットと並び替えられていないデータセットのサンプル内パフォーマンスをテストし、比較する必要があります。並び替えられていないパフォーマンス数値が、並び替えられた結果とどの程度異なるかは、過剰学習を定量化するために使用することができます。過剰学習が蔓延している場合、並び替えた結果と並び替えていない結果の差はほとんどありません。かなり大きなp値が見られるでしょう。
結論
価格バーを並べ替えるアルゴリズムの実装を見ました。また、並べ替えられたティックやバーを持つカスタム銘柄を生成するためのコードも更新されました。説明されたプログラムは、EAでの並べ替えテストを実証するために使用され、肯定的な結果が示されました。並び替え検定は、自動売買に関心のある人にとって不可欠なツールです。MT5のストラテジーテスターの機能として追加されるべきだと思います。
ファイル | 詳細 |
---|---|
MQL5\Experts\grr-al.mq5 | MQL5.comのコードベースで利用可能なEAを少し修正したもので、1分足OHLCモードでストラテジーテスターのティック生成方法を利用して取引する |
MQL5\Include\NewSymbol.mqh | カスタム銘柄を作成するためのCNewSymbolクラス定義が含まれている |
MQL5\Include\ PermutedSymbolData.mqh | 並べ替えられたレートやティックを持つカスタム銘柄を作成するためのCPermutedSymbolDataクラスを定義している |
MQL5\Include\PermuteRates.mqh | MqlRatesデータの配列の順列を作成するためのCPermuteRatesクラスが含まれている |
MQL5\Include\PermuteTicks.mqh | MqlTickデータの配列の順列を作成するためのCPermuteTicksクラスを定義する |
MQL5\Include\UniformRandom.mqh | CUniFrandは一様分布乱数生成器をカプセル化する |
MQL5\Scripts\PrepareSymbolsForPermutationTests.mq5 | Metatrader 5でカスタム銘柄を生成するためのすべてのコードユーティリティをまとめたスクリプト |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/13591
![知っておくべきMQL5ウィザードのテクニック(第07回):樹状図](https://c.mql5.com/2/59/Dendrograms_Logo.png)
![MQL5で日付と時刻を扱う方法を学ぶ](https://c.mql5.com/2/59/date_and_time_in_MQL5_logo__1.png)
![独自のLLMをEAに統合する(第1部):ハードウェアと環境の導入](https://c.mql5.com/2/59/Hardware_icon_up__1.png)
![ONNXをマスターする:MQL5トレーダーにとってのゲームチェンジャー](https://c.mql5.com/2/59/Mastering_ONNX_logo_up.png)
![MQL5 - MetaTrader 5クライアントターミナルにビルトインされたトレードストラテジーの言語](https://c.mql5.com/i/registerlandings/logo-2.png)
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索