English Русский 中文 Deutsch Português
preview
MQL5での価格バーの並べ替え

MQL5での価格バーの並べ替え

MetaTrader 5 | 29 1月 2024, 12:52
208 0
Francis Dube
Francis Dube

はじめに

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

添付されたファイル |
grr-al.mq5 (4.94 KB)
NewSymbol.mqh (29.34 KB)
PermuteRates.mqh (6.04 KB)
PermuteTicks.mqh (4.79 KB)
UniformRandom.mqh (2.84 KB)
知っておくべきMQL5ウィザードのテクニック(第07回):樹状図 知っておくべきMQL5ウィザードのテクニック(第07回):樹状図
分析や予測を目的としたデータの分類は、機械学習の中でも非常に多様な分野であり、数多くのアプローチや手法があります。この作品では、そのようなアプローチのひとつである「凝集型階層分類」を取り上げます。
MQL5で日付と時刻を扱う方法を学ぶ MQL5で日付と時刻を扱う方法を学ぶ
日付と時刻の取り扱いという、新しい重要なトピックについての新しい記事です。トレーダーとして、あるいは取引ツールのプログラマーとして、日付と時間という2つの側面をいかにうまく、効果的に扱うかを理解することは非常に重要です。そこで今回は、効果的な取引ツールを円滑かつシンプルに作成するために、日付と時刻をどのように扱えばよいのか、私ができる範囲で重要な情報をお伝えします。
独自のLLMをEAに統合する(第1部):ハードウェアと環境の導入 独自のLLMをEAに統合する(第1部):ハードウェアと環境の導入
今日の人工知能の急速な発展に伴い、言語モデル(LLM)は人工知能の重要な部分となっています。私たちは、強力なLLMをアルゴリズム取引に統合する方法を考える必要があります。ほとんどの人にとって、これらの強力なモデルをニーズに応じて微調整し、ローカルに展開して、アルゴリズム取引に適用することは困難です。本連載では、この目標を達成するために段階的なアプローチをとっていきます。
ONNXをマスターする:MQL5トレーダーにとってのゲームチェンジャー ONNXをマスターする:MQL5トレーダーにとってのゲームチェンジャー
機械学習モデルを交換するための強力なオープン標準形式であるONNXの世界に飛び込んでみましょう。ONNXを活用することでMQL5のアルゴリズム取引にどのような変革がもたらされ、トレーダーが最先端のAIモデルをシームレスに統合し、戦略を新たな高みに引き上げることができるようになるかがわかります。クロスプラットフォーム互換性の秘密を明らかにし、MQL5取引の取り組みでONNXの可能性を最大限に引き出す方法を学びましょう。ONNXをマスターするためのこの包括的なガイドで取引ゲームを向上させましょう。