English Русский 中文 Español Deutsch 日本語
preview
Testes de permutação de Monte Carlo no MetaTrader 5

Testes de permutação de Monte Carlo no MetaTrader 5

MetaTrader 5Exemplos | 1 fevereiro 2024, 17:19
378 0
Francis Dube
Francis Dube

Introdução

Aleksei Nikolaev escreveu um artigo interessante intitulado "Aplicação do método de Monte Carlo para otimizar estratégias de negociação". Nele, ele explora testes de permutação, caracterizados pela alternância aleatória das transações. O autor também faz uma menção breve a outro estilo de teste de permutação, onde há uma alteração aleatória na sequência de dados de preços. Neste contexto, o desempenho de um Expert Advisor é avaliado em comparação com os resultados obtidos em múltiplas variações da mesma série de preços.

Na minha opinião, o autor supôs erroneamente que tais testes de permutação não podem ser realizados em um Expert Advisor arbitrário usando o MetaTrader 5. Pelo menos, não completamente. Neste artigo, iremos demonstrar a execução de um teste de permutação envolvendo séries de preços aleatoriamente permutadas utilizando o MetaTrader 5. Forneceremos o código necessário para realizar a permutação das séries de preços, assim como um script que facilita os procedimentos iniciais para a realização de um teste de permutação completo em um Expert Advisor.

Visão geral dos testes de permutação

O teste de permutação que descreveremos aqui envolve uma amostra de dados de preços. É preferível que o teste seja realizado com uma amostra. Após aplicar o teste nesta série de preços, anotamos todos os critérios de desempenho relevantes. Em seguida, embaralhamos a ordem da série de preços original, provamos o Expert Advisor e registramos seu desempenho.

Fazemos isso muitas vezes, a cada vez alterando as séries de preços e anotando os critérios de desempenho, que são essenciais para comparações futuras. Isso deve ser feito, no mínimo, cem vezes, mas idealmente várias milhares de vezes. Quanto mais vezes reorganizarmos e testarmos, mais confiáveis serão os resultados. Então, o que esperamos encontrar nesses resultados?


Por que são necessários testes de permutação?

Após uma série de testes iterativos, acumulamos um conjunto de indicadores de desempenho para cada permutação. Independente do indicador de desempenho escolhido – seja o índice de Sharpe, a taxa de lucro ou o saldo final/lucro líquido –, analisamos os resultados. Imagine que realizamos 99 permutações (ou 100, incluindo o teste original sem permutações), resultando em 100 indicadores de desempenho para análise.

O próximo passo é determinar quantas vezes o desempenho no teste original foi superado e expressar esse número como uma fração do total de testes, neste caso, 100. Essa fração representa a probabilidade de alcançar um resultado igual ou superior no teste sem permutação, como se o Expert Advisor não tivesse capacidade de gerar lucro. Em estatística, isso é conhecido como valor-p, um elemento crucial em testes de hipóteses.

Em nosso exemplo hipotético com 100 iterações, descobrimos que 29 indicadores de desempenho com permutações superaram o teste original. Isso nos leva a um valor-p de 0,3, ou seja, 29+1/100. Significa que existe uma chance de 0,3 de um Expert Advisor não lucrativo apresentar um desempenho igual ou superior ao observado no teste original. Embora isso possa parecer promissor, idealmente queremos que os valores-p sejam o mais próximos possível de zero, em torno de 0,05 ou menos.

A fórmula completa é:

z+1/r+1

onde r representa o número de permutações e z é o número total de testes com melhor desempenho. O procedimento de permutação é importante para a execução adequada do teste.

Permutação da série de preços

Para permutar corretamente um conjunto de dados, é importante assegurar que cada sequência possível tenha a mesma probabilidade de ocorrência. Isso requer a geração de um número aleatório uniformemente distribuído entre 0 e 1. A ferramenta necessária para isso é fornecida pela biblioteca padrão MQL5, especificamente na seção estatística. Com essa ferramenta, podemos definir o intervalo de valores que desejamos.

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

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


Quando se trata de permutar dados de preço, há condições específicas. Não podemos simplesmente trocar a posição de um valor de preço, já que isso interromperia as relações temporais inerentes às séries temporais financeiras. Por isso, optamos por permutar as variações de preço, em vez dos preços em si. Ao registrar os preços antes de compará-los, reduzimos a distorção nas diferenças de preços originais.

Neste método, devemos manter o primeiro valor de preço e excluí-lo da permutação. Ao reconstruir a série, isso assegura a preservação do padrão de tendência da sequência de preços original A única alteração ocorre nos movimentos internos dos preços, mantendo-se o primeiro e o último preço da série original.


Antes de realmente alterar a série de preços, devemos decidir quais dados utilizaremos. No MetaTrader 5, os dados dos gráficos são apresentados em colunas que derivam de dados de ticks. Permutar uma série de preços é muito mais simples do que permutar informações de barras, por isso, escolhemos dados de ticks. A utilização de ticks, contudo, introduz outras complexidades, já que os ticks englobam mais do que apenas os preços brutos. Eles contêm informações sobre volume, tempo e flags de tick.


Inicialmente, as informações de tempo e flag de tick são mantidas constantes, de modo que nosso procedimento de permutação não deve alterá-las. Nosso foco recai sobre o bid, ask e volume. Surge uma segunda complexidade: a possibilidade de esses valores serem zero, o que dificultaria a aplicação de transformações logarítmicas. Para ilustrar como contornar esses desafios, vamos analisar o código.

Implementando o algoritmo de permutação de ticks

A CPermuteTicks, contida no arquivo PermuteTicks.mqh, é a classe responsável pelo procedimento de permutação de ticks. Dentro do PermuteTicks.mqh, incluímos Uniform.mqh da biblioteca padrão para acessar a funcionalidade que gera números aleatórios distribuídos uniformemente dentro de um intervalo especificado. As definições que se seguem estabelecem esse intervalo. Tenha cuidado ao ajustar esses valores, assegurando que o mínimo esteja sempre abaixo do máximo estabelecido.

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


A estrutura CMqlTick representa os membros da estrutura embutida MqlTick, que é com a qual a classe interage. As demais informações dos ticks não são afetadas por este processo.

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


A classe CPermuteTicks possui três propriedades privadas de array: m_ticks, que armazena os ticks originais; m_logticks, que contém os ticks transformados logisticamente; e m_differenced, onde estão reunidos os ticks de diferença.

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

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


Um flag lógico, m_initialized, sinaliza quando a operação de pré-processamento foi concluída com sucesso, tornando possível realizar as permutações.


Para usar a classe, o usuário deve chamar o método Initialize() após criar uma instância do objeto. O método requer um array de ticks que precisam ser permutados. No método, os tamanhos dos arrays internos da classe são ajustados, e o LogTransformTicks() é aplicado para transformar os dados dos ticks. Isso inclui a remoção de valores nulos ou negativos, substituindo-os por 1,0. Após a permutação, os dados dos ticks logisticamente transformados são revertidos ao seu estado original pelo método privado ExpTransformTicks().

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


O método Permute() é o responsável por gerar os ticks permutados. Ele necessita de um array dinâmico MqlTick para receber os ticks permutados. O processo de reorganização ocorre dentro de um loop while, alterando a posição do valor diferencial do tick baseado no número aleatório gerado a cada iteração.

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

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

   ulong time = GetTickCount64();

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

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

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


Após finalizar todas as iterações, o array m_logticks é reconstituído, revertendo as diferenças usando os dados dos ticks permutados m_differenced. O único argumento do método Permute() é então preenchido com os dados de m_logtick revertidos ao seu domínio original, e as informações de tempo e flag de tick são copiadas da série original de ticks.

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

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


Com este algoritmo, podemos processar permutações em séries de preços, mas isso é apenas a metade do processo. A próxima etapa envolve a realização de testes.


Teste de permutação

O teste de permutação envolve duas funções do terminal MetaTrader 5. A primeira função permite criar símbolos personalizados e especificar suas propriedades, enquanto a segunda possibilita a otimização de Expert Advisors com base nos símbolos presentes na lista "Market Watch". Essencialmente, isso adiciona pelo menos mais dois passos ao processo.

Podemos permutar ticks e criar símbolos personalizados a partir de qualquer um existente. Ao fazer isso, para cada símbolo personalizado é especificada uma única permutação de ticks para o símbolo usado como base. Embora os símbolos possam ser criados manualmente, automatizar a criação de símbolos e a adição de ticks permutados é mais sensato.

É exatamente isso que o script PrepareSymbolsForPermutationTests faz. Ele utiliza dados de entrada do usuário para definir o símbolo base, o intervalo de datas para os ticks a serem usados nas permutações, o número de permutações desejadas (que se traduz no número de símbolos personalizados a serem criados), e um identificador de string opcional para ser adicionado aos nomes dos novos símbolos personalizados.
//+------------------------------------------------------------------+
//|                            PrepareSymbolsForPermutationTests.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include<GenerateSymbols.mqh>
#property script_show_inputs
//--- input parameters
input string   BaseSymbol="EURUSD";
input datetime StartDate=D'2023.06.01 00:00';
input datetime EndDate=D'2023.08.01 00:00';
input uint     Permutations=100;
input string   CustomID="";//SymID to be added to symbol permutation names
//---
CGenerateSymbols generateSymbols();
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   if(!generateSymbols.Initiate(BaseSymbol,CustomID,StartDate,EndDate))
       return;
//---
   Print("Number of newly generated symbols is ", generateSymbols.Generate(Permutations));
//---          
  }
//+------------------------------------------------------------------+


Automaticamente, o script gera nomes de símbolos, anexando uma enumeração ao nome do símbolo base. O código para essa funcionalidade está no arquivo GenerateSymbols.mqh, que contém a definição da classe CGenerateSymbols. Esta classe depende de outras duas: NewSymbol.mqh, que contém a definição da classe CNewSymbol, adaptada do código contido no artigo "Guia Prático do MQL5: Teste de estresse de uma estratégia de negociação utilizando os símbolos personalizados".

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

A classe ajuda a criar novos símbolos personalizados com base nos existentes. A última dependência necessária é a PermuteTicks.mqh, um arquivo já mencionado anteriormente.

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


CGenerateSymbols oferece duas funções principais. O método Initiate() deve ser chamado imediatamente após a criação do objeto e recebe quatro parâmetros que correspondem com as entradas fornecidas pelo usuário.

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


O método Generate(), por sua vez, processa o número especificado de permutações e informa quantos novos símbolos personalizados foram adicionados ao "Market Watch".
O resultado da execução do script é exibido na aba "Experts" do terminal.

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


O próximo passo envolve realizar a otimização no testador de estratégias, selecionar o método de otimização mais recente e especificar o Expert Advisor para o teste. Este processo pode ser demorado. Após sua conclusão, obtemos um conjunto de dados de desempenho detalhado.

Exemplo

Vejamos como isso funciona na prática, realizando um teste com o Expert Advisor MACD Sample, incluído no pacote. O teste ocorrerá no símbolo AUDUSD, com 100 permutações definidas no script.

Configurações do script



Após rodar o script, acabamos com 100 símbolos adicionais, todos baseados em ticks permutados do símbolo AUDUSD.

Símbolos personalizados no Market Watch



Finalmente, iniciaremos o teste de otimização.

Configurações do testador

  As configurações do Expert Advisor são mostradas abaixo.

Parâmetros do Expert Advisor

Resultados do teste.

Resultados da otimização


Na aba de resultados do testador de estratégias, encontramos todos os indicadores de desempenho relevantes, organizados em ordem decrescente de acordo com o critério de desempenho escolhido, acessível através de um menu suspenso no canto superior direito da janela do testador. Neste layout, é possível calcular o valor-p manualmente ou automaticamente ao processar o arquivo .xml, que pode ser facilmente exportado usando o clique direito do mouse no testador.

Neste exemplo específico, os cálculos são desnecessários para ver que os resultados de teste do símbolo original ficaram bem abaixo na lista de resultados, enquanto mais de 10 símbolos permutados apresentaram um desempenho superior. Isso sugere que o valor-p é maior que 0,05.

No entanto, deve-se encarar os resultados deste teste com uma dose de ceticismo, pois o período de teste selecionado foi relativamente curto. É recomendável escolher um período de teste mais extenso e representativo das condições de negociação real.

Como mencionado anteriormente, temos várias maneiras de processar os resultados para calcular os valores-p. O foco subsequente será na análise dos dados do arquivo XML exportado do testador de estratégias. Demonstraremos o uso de um aplicativo de planilha eletrônica para processar o arquivo com facilidade.

Depois de exportar o arquivo, lembre-se de registrar onde foi salvo. Abra-o com qualquer aplicativo de planilha eletrônica. A imagem a seguir mostra o uso do OpenOffice Calc, onde uma nova linha foi adicionada no final da tabela. Antes de continuar, pode ser prudente excluir as linhas referentes aos símbolos que não serão considerados nos cálculos. Abaixo de cada coluna apropriada, o valor-p é calculado por meio de um macro personalizado. Este macro utiliza tanto os indicadores de desempenho do símbolo permutado (na linha 18 do documento exibido) quanto os indicadores de desempenho de todos os símbolos permutados correspondentes. A fórmula exata do macro é mostrada na imagem.

Cálculo dos valores-p no OpenOffice Calc

Além do uso de um aplicativo de planilha eletrônica, Python é outra opção viável, com seus vários módulos para análise de arquivos XML. Se familiarizado com MQL5, os arquivos também podem ser processados usando um script simples. Apenas certifique-se de escolher um diretório facilmente acessível ao exportar os resultados da otimização do testador.

Conclusão

Demonstramos a aplicabilidade do teste de permutação a qualquer Expert Advisor, mesmo sem acesso ao código-fonte. Este método de teste é extremamente valioso, pois fornece uma abordagem estatística robusta que não depende de suposições sobre a distribuição dos dados. Tal característica o distingue de muitos outros testes estatísticos comumente utilizados no desenvolvimento de estratégias.

Contudo, a principal desvantagem reside no tempo e nos recursos computacionais exigidos para realizar o teste. Um processador potente e considerável espaço em disco são indispensáveis, já que a criação de novos ticks e símbolos demanda uma quantidade substancial de armazenamento. Na minha opinião, quem se dedica à aquisição de Expert Advisors deveria considerar este método de análise. Apesar de exigir tempo, ele pode prevenir decisões equivocadas.

A análise baseada em dados de preços permutados oferece múltiplas aplicações. Esse método pode ser empregado tanto para avaliar o comportamento dos indicadores quanto em diferentes fases do desenvolvimento estratégico. As possibilidades são vastas. Em certas ocasiões, durante o desenvolvimento ou teste de estratégias, os dados disponíveis podem parecer insuficientes. A utilização de séries de preços permutados amplia significativamente o volume de dados disponíveis para testes. Os códigos-fonte de todos os programas mql5 descritos aqui estão anexados ao artigo, esperando ser de utilidade para os leitores.

Nome do Arquivo
Tipo de Programa
Descrição
GenerateSymbols.mqh
Arquivo Include
Definição da classe CGenerateSymbols para geração de símbolos com dados de ticks permutados de um símbolo base selecionado
NewSymbol.mqh
Arquivo Include
Definição da classe CNewSymbol para a criação de símbolos personalizados
PermuteTicks.mqh
Arquivo Include
Definição da classe CPermuteTicks para a criação de permutações de um array de dados de ticks
PrepareSymbolsForPermutationTests.mq5
Arquivo de Script
Script que automatiza a criação de símbolos personalizados com permutação de ticks na preparação de um teste de permutação


Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/13162

Arquivos anexados |
NewSymbol.mqh (29.34 KB)
PermuteTicks.mqh (8.78 KB)
Mql5.zip (9.91 KB)
GIT: Mas que coisa é esta ? GIT: Mas que coisa é esta ?
Neste artigo apresentarei uma ferramenta de suma importância para quem desenvolve programas. Se você não conhece GIT, veja este artigo para ter uma noção do que se trata, tal ferramenta. E como usá-la junto ao MQL5.
Teoria das Categorias em MQL5 (Parte 17): funtores e monoides Teoria das Categorias em MQL5 (Parte 17): funtores e monoides
Este é o último artigo da série dedicada a funtores. Nele, reconsideramos monoides como uma categoria. Os monoides, que já apresentamos nesta série, são usados aqui para ajudar na definição do tamanho da posição juntamente com perceptrons multicamadas.
Desenvolvendo um sistema de Replay (Parte 42): Projeto do Chart Trade (I) Desenvolvendo um sistema de Replay (Parte 42): Projeto do Chart Trade (I)
Vamos agora criar algo um pouco mais interessante. No entanto, iremos fazer de forma que o código que mostrei no passado, estará completamente obsoleto. Mas não vou estragar a surpresa. Acompanhe o artigo para entender. Desde o inicio desta sequencia sobre como desenvolver um sistema de replay / simulação, venho dizendo que a ideia aqui, é usar a plataforma MetaTrader 5, de forma idêntica, tanto no sistema que estamos desenvolvendo, quanto no mercado real. É importante que isto se dê de maneira adequada. Você não vai querer treinar e aprender a lutar usando determinadas ferramentas, e na hora da briga ter que usar outras.
Redes neurais de maneira fácil (Parte 54): usando o codificador aleatório para exploração eficiente (RE3) Redes neurais de maneira fácil (Parte 54): usando o codificador aleatório para exploração eficiente (RE3)
A cada vez que consideramos métodos de aprendizado por reforço, nos deparamos com a questão da exploração eficiente do ambiente. A solução deste problema frequentemente leva à complexificação do algoritmo e ao treinamento de modelos adicionais. Neste artigo, vamos considerar uma abordagem alternativa para resolver esse problema.