Permutação das barras de preços no MQL5
Introdução
O testador de estratégia do MetaTrader 5 é a principal ferramenta usada por muitos traders para avaliar o potencial dos Expert Advisors. Os desenvolvedores experientes podem usá-lo para criar EAs "engenhosos" que podem demonstrar um desempenho excepcional. Todos nós já vimos capturas de tela com curvas de patrimônio líquido mostrando um desempenho incrível. Elas certamente parecem impressionantes, mas, muitas vezs, quando a estratégia é aplicada no mercado real, o resultado é completamente diferente. Como podemos nos proteger contra esse tipo de truques? Neste artigo, examinaremos como funciona isso e demonstraremos como o teste de permutação pode ser utilizado para romper a cortina de fumaça por trás das curvas de capital enganosas e obter uma imagem mais precisa do desempenho da estratégia. Também, no artigo anterior, vimos a implementação de um algoritmo para a permutação de dados de ticks. Todavia, desta vez, descreveremos como permutar as barras de preços.
Permutação de dados OHLC
A permutação das barras de preços é um pouco mais complexa por estarem envolvidas várias séries. Semelhante à reorganização de dados de ticks, ao trabalhar com barras de preços, procuramos preservar a tendência geral da série de preços original. Também é importante que nunca permitamos que a abertura ou fechamento da barra ultrapasse o máximo e o mínimo. O objetivo é obter uma série de barras com exatamente a mesma distribuição de características que os dados originais.
Além da tendência, precisamos manter a dispersão das mudanças de preço à medida que a série avança da abertura para o fechamento. A dispersão das mudanças de preço entre a abertura e o fechamento deve ser a mesma nas barras permutadas, assim como nos dados originais. Além das próprias barras, precisamos garantir que a distribuição das mudanças de preço entre as barras também seja igual, o que inclui a diferença entre o fechamento de uma barra e a abertura da próxima.
Isso é importante para não desfavorecer a estratégia que está sendo testada. As características gerais da série devem ser semelhantes. A única diferença deve residir nos valores absolutos de cada abertura, máximo, mínimo e fechamento (OHLC) entre a primeira e a última barra. O código de implementação é muito semelhante ao código usado na classe CPermuteTicks, apresentada no artigo "Testes de Permutação de Monte Carlo no MetaTrader 5". O código para a permutação das barras de preços será encapsulado na classe CPermuteRates, contida em PermuteRates.mqh.
Classe 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 começa com a definição de uma estrutura simples, que armazena a diferença logarítmica dos preços brutos.
- rel_open armazena a diferença logarítmica entre a abertura atual e o fechamento da barra anterior.
- rel_high representa a diferença logarítmica entre o máximo da barra atual e o preço de abertura.
- rel_low se refer à diferença logarítmica entre o mínimo da barra atual e o preço de abertura.
- rel_close é, novamente, a diferença logarítmica entre o fechamento e a abertura da barra atual.
A estrutura personalizada CRelRates representa dados extraídos de MqlRates que serão permutados. Os outros membros da estrutura MqlRates não serão alterados. Como resultado final das barras de preços permutadas, esses membros da estrutura serão copiados a partir da série de preços original. Como já mencionado, apenas os valores OHLC serão alterados.
//+------------------------------------------------------------------+ //| 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; //--- }
A permutação é realizada no método Permute(). A estrutura CRelRates divide os dados das barras em dois tipos de descritores. A série de valores rel_open representa mudanças de uma barra para outra, enquanto rel_high, rel_low e rel_close representam mudanças dentro da barra. Para permutar as barras, primeiro embaralhamos a série de preços rel_open. Essa é a diferença entre as barras. Após isso, as mudanças dentro da barra são baralhadas. Uma nova série OHLC é construída com base nos dados das barras embaralhadas para obter novos valores de abertura com os correspondentes preços altos, mínimos e de fechamento, construídos com base nas mudanças das barras internas embaralhadas.
Alterações em CPermuteTicks
Existem várias diferenças entre CPermuteRates e a classe antiga CPermuteTicks. Uma delas é o uso de um gerador de números aleatórios próprio, que, como descobri, funciona um pouco mais rápido do que usar as funções embutidas do 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(); } //+------------------------------------------------------------------+
O código também se aplica à nova classe CPermuteTicks. As operações intermediárias desnecessárias foram eliminadas para aumentar a eficiência. Apenas os preços bid são embaralhados. Uma vez que outras propriedades dos ticks são copiadas da série original de ticks, isso resolve o problema que às vezes resultava em ticks reorganizados com spreads irreais. A seguir, é apresentada a nova série 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 continua a funcionar da mesma forma que a versão anterior. CPermuteRates opera de maneira similar. A diferença entre eles é que um trabalha com ticks, enquanto o outro com preços.
Classe CPermutedSymbolData
O script PrepareSymbolsForPermutationTest foi atualizado para refletir as mudanças feitas em CPermuteTicks, bem como a introdução de CPermuteRates. A funcionalidade do script é encapsulada na classe CPermutedSymbolData. Ela permite criar símbolos personalizados com ticks ou preços reorganizados baseados em um símbolo existente.
//+------------------------------------------------------------------+ //|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); };
Isso é alcançado especificando o tipo de dados a serem embaralhados (ticks ou preços) na chamada do construtor. A enumeração ENUM_RATES_TICKS descreve os parâmetros disponíveis para um parâmetro do construtor.
//+-----------------------------------------------------------------------------------------+ //|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; } }
Após criar uma instância de CPermutedSymbolData, deve-se chamar o método Initiate(), para especificar o símbolo e o período da data que definem os ticks ou preços nos quais as permutações serão baseadas.
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Se o Initiate() retornar true, pode-se chamar o método Generate() com a quantidade desejada de permutações. O método retornará o número de símbolos personalizados cujos dados foram sucessivamente preenchidos com ticks ou preços permutados.
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
O código do script é mencionado acima. Todo o código-fonte é anexado ao artigo.
Aplicação de testes de permutação
Na introdução do artigo, mencionei o problema enfrentado por muitos compradores potenciais de Expert Advisors. Existe a possibilidade de que vendedores desonestos possam usar táticas enganosas. Frequentemente, os vendedores exibem capturas de tela de curvas de capital atraentes que demonstram lucros potenciais. Muitos foram vítimas dessa tática e aprenderam com sua própria amarga experiência que essas capturas de tela foram criadas com base em estratégias artificiais. Nesta seção, examinaremos um dos EAs disponíveis no CodeBase, que pode ser usado para criar curvas de capital enganosas. Também aplicaremos o teste de permutação para revelar o fraude.
Visão geral do teste de permutação
O teste é bastante trabalhoso e requer significativos recursos temporais e computacionais. No entanto, na minha opinião, os resultados valem o esforço e podem proteger contra erros. O método utilizado envolve a seleção de uma amostra de teste adequada. A amostra é dividida em conjuntos de dados intra-amostra e extra-amostra. O Expert Advisor será otimizado com os dados intra-amostra, e o desempenho final será determinado por testes com dados extra-amostra usando parâmetros otimizados. Isso é feito usando a série original de dados, bem como pelo menos 100 conjuntos de dados de permutação. Isso foi exatamente o que fizemos para testar o Expert Advisor usado em nossa demonstração.
Teste do Expert Advisor grr-al
Qualquer um que tenha estudado a documentação do MQL5 ou explorado o CodeBase certamente se deparou com este Expert Advisor. Na documentação do MQL5, ele é chamado de "test grail". Quando executado no testador de estratégias no modo de geração de ticks "OHLC em M1" ou "Apenas preços de abertura", produz uma curva de patrimônio líquido impressionante. Este é o Expert Advisor que utilizaremos em nossa demonstração. Modificamos um pouco o código para exibir algumas variáveis globais para otimização. Dois dos três parâmetros foram escolhidos para otimização, nomeadamente SL, TP e 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;
As configurações usadas para otimização são mostradas na captura de tela.
O conjunto de dados escolhido foi o EURUSD H1 para todo o ano de 2022. Os primeiros seis meses de 2022 foram usados para otimização, e a segunda metade foi usada como período extra-amostra para testar os parâmetros ótimos.
Inicialmente, o script PrepareSymbolsForPermutationsTests foi usado para criar símbolos personalizados de dados permutados. O tempo de execução do programa foi cronometrado e anotado como mostrado abaixo. O erro estava relacionado ao fato de que na primeira tentativa não havia espaço suficiente no disco, e apenas 99 símbolos personalizados foram adicionados com sucesso.
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
O volume de dados gerados foi de quase 40 gigabytes de dados de ticks para um ano, quando os dados foram permutados 100 vezes!
Ao usar os preços, tudo era muito mais rápido e os dados ocupavam muito menos espaço.
NK 0 12:51:23.166 PrepareSymbolsForPermutationTests (EURUSD,M1) Number of permuted symbols is 100, Runtime 1.4mins
Com esses dados, cada símbolo foi otimizado com os conjuntos intra-amostra.
Os parâmetros que geraram o maior lucro absoluto foram usados no teste extra-amostra. A otimização e o teste extra-amostra foram realizados usando o modo tick por preços de abertura. Isso significa que o Expert Advisor tinha todas as vantagens para exibir resultados excepcionais.
Os resultados de todos os testes estão apresentados em um arquivo csv. IS Profit e OOS PROFIT se referem ao lucro intra-amostra e extra-amostra, respectivamente.
<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
O valor p calculado é 0,8217821782178217.
MO 0 09:49:57.991 ProcessOptFiles (EURUSD,MN1) P-value is 0.8217821782178217
Assim, a probabilidade de observar o desempenho alcançado no conjunto original de dados ao acaso é superior a 80 por cento. Isso claramente indica que o Expert Advisor é inútil.
Por que isso funciona?
A premissa do teste de permutação, no contexto do desenvolvimento de estratégias, é que a estratégia do EA representa a descrição de um padrão ou conjunto de regras usadas para obter vantagem na negociação. Quando os dados com os quais ele opera são alterados, os padrões originais, que o Expert Advisor poderia ter seguido para gerar lucro, podem ser violados. Se o Expert Advisor realmente negocia seguindo algum padrão, seu desempenho com dados permutados sofrerá. Após comparar o desempenho nos testes com e sem permutação, fica claro que, mesmo após a otimização, o EA na verdade depende de algum padrão ou regra única. O desempenho do conjunto de dados não permutados deve diferir dos testes de permutação.
Como vimos no teste demonstrado, o EA em questão utiliza o método de geração de ticks e não aplica nenhuma estratégia real (padrões ou regras). O teste de permutação conseguiu revelar isso.
Os testes de permutação também podem ser usados para determinar o grau de sobreajuste após a otimização. Para verificar o sobreajuste, precisamos testar e comparar o desempenho no conjunto de dados permutados e não permutados. O grau em que os indicadores de desempenho sem permutações diferem dos resultados com permutações pode ser usado para avaliar quantitativamente o sobreajuste. Quando o sobreajuste predomina, a diferença entre os resultados de desempenho permutados e não permutados será pequena. Veremos valores de p bastante altos.
Considerações finais
Examinamos a implementação de um algoritmo de permutação de barras de preços, além de atualizar o código para criar símbolos personalizados com ticks ou barras permutados. Os programas descritos foram usados para demonstrar o teste de permutação em um Expert Advisor com resultados de desempenho positivos. O teste de permutação é uma ferramenta importante para todos que estão interessados em negociação automática. Tão importante, na minha opinião, que deveria ser adicionado como uma função no testador de estratégias do MetaTrader 5.
Arquivo | Descrição |
---|---|
MQL5\Experts\grr-al.mq5 | Esta é uma versão ligeiramente modificada do Expert Advisor disponível no CodeBase em MQL5.com. Ele opera usando o método de geração de ticks do testador de estratégias no modo "OHLC em M1" |
MQL5\Include\NewSymbol.mqh | Definição da classe CNewSymbol para criar símbolos personalizados |
MQL5\Include\PermutedSymbolData.mqh | Definição da classe CPermutedSymbolData para criar símbolos personalizados com preços ou ticks permutados |
MQL5\Include\PermuteRates.mqh | Classe CPermuteRates para criar permutações do array de dados MqlRates |
MQL5\Include\PermuteTicks.mqh | Definição da classe CPermuteTicks para criar permutações do array de dados MqlTick |
MQL5\Include\UniformRandom.mqh | CUniFrand encapsula um gerador de números aleatórios uniformemente distribuídos |
MQL5\Scripts\PrepareSymbolsForPermutationTests.mq5 | Script que integra todo o código auxiliar para a criação de símbolos personalizados no MetaTrader 5. |
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/13591
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso