English Русский 中文 Deutsch 日本語
preview
Permutação das barras de preços no MQL5

Permutação das barras de preços no MQL5

MetaTrader 5Exemplos | 26 março 2024, 13:39
141 0
Francis Dube
Francis Dube

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.

Curva do patrimônio líquido

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.

Dados de entrada para otimização



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.

Configurações de otimização


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!


Tamanho da pasta de dados de ticks

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.

Resultados da otimização

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

Adicionando um LLM personalizado a um robô investidor (Parte 2): Exemplo de implementação de ambiente Adicionando um LLM personalizado a um robô investidor (Parte 2): Exemplo de implementação de ambiente
Os modelos de linguagem (LLMs) são uma parte importante da inteligência artificial que evolui rapidamente. E para aproveitar isso devemos pensar em como integrar LLMs avançados em nossa negociação algorítmica Muitos acham desafiador ajustar esses modelos de acordo com suas necessidades, implantá-los localmente e, logo, aplicá-los à negociação algorítmica. Esta série de artigos explorará uma abordagem passo a passo para alcançar esse objetivo.
Preparação de indicadores com vários símbolos/períodos Preparação de indicadores com vários símbolos/períodos
Neste artigo, examinaremos os princípios para criar indicadores com vários símbolos/períodos e recuperar dados deles dentro de EAs e indicadores. Veremos as nuances mais importantes ao usar multi-indicadores em EAs e indicadores, e sua plotagem mediante buffers de indicador personalizado.
Desenvolvendo um sistema de Replay (Parte 45): Projeto do Chart Trade (IV) Desenvolvendo um sistema de Replay (Parte 45): Projeto do Chart Trade (IV)
O principal neste artigo, é justamente a apresentação e explicação da classe C_ChartFloatingRAD. Temos o indicador Chart Trade, funcionando de uma maneira bastante interessante. No entanto, se você notará que ainda temos um numero bastante reduzido de objetos no gráfico. E mesmo assim temos exatamente o comportamento esperado. Podendo editar os valores presentes no indicador. A pergunta é: Como isto é possível ?!?! Neste artigo você começará a entender isto.
Experimentos com redes neurais (Parte 7): Transferência de indicadores Experimentos com redes neurais (Parte 7): Transferência de indicadores
Desta vez, veremos exemplos de passagem de indicadores ao perceptron. Abordaremos conceitos gerais, um Expert Advisor simples pronto, os resultados de sua otimização e testes forward.