English Русский 中文 Deutsch 日本語 Português
preview
Permutación de las barras de precio en MQL5

Permutación de las barras de precio en MQL5

MetaTrader 5Ejemplos | 17 mayo 2024, 09:01
122 0
Francis Dube
Francis Dube

Introducción

El simulador de estrategias de MetaTrader 5 es la principal herramienta utilizada por muchos tráders para evaluar el potencial de los asesores. Los desarrolladores experimentados pueden usarlo para crear asesores "complejos" que demuestren un rendimiento excepcional. Todos hemos visto capturas de pantalla de curvas de equidad que muestran una eficiencia increíble. Ciertamente parecen impresionantes, pero a menudo, cuando la estrategia se aplica en el mercado real, el panorama resulta bastante diferente. ¿Cómo podemos protegernos contra estos trucos? En el presente artículo, examinaremos un sistema de este tipo y demostramos cómo puede utilizarse la prueba de permutación para romper la cortina de humo de las curvas de equidad engañosas y obtener una imagen más precisa del rendimiento de la estrategia. Asimismo, en el artículo anterior vimos la implementación del algoritmo de permutación de datos de ticks. Esta vez describiremos el método para permutar las barras de precio.


Permutación de los datos OHLC

La permutación de las barras de precio es un poco más complicada debido a la implicación de varias filas. Al igual que ocurre con la permutación de los datos de ticks, cuando tratamos con barras de precio intentamos mantener la tendencia general de la serie de precios original. También resulta importante que nunca permitamos que la apertura o el cierre de la barra vayan más allá del máximo y el mínimo. El objetivo es obtener una serie de barras con exactamente la misma distribución de características que en los datos originales.

Además de la tendencia, deberemos mantener la varianza de los cambios de precio a medida que la serie se mueve desde la apertura hasta el cierre. La dispersión de los cambios de precio entre la apertura y el cierre deberá ser la misma en las barras permutadas que en la barra original. Fuera de las barras en sí, tenemos que asegurarnos de que la distribución de los cambios de precios entre las barras también sea la misma, incluida la diferencia entre el cierre de una barra y la apertura de la siguiente.  

Esto es importante para no perjudicar la estrategia que estamos probando. Las características generales de la serie deberán ser similares. La única diferencia deberán ser los valores absolutos de cada apertura, máximo, mínimo y cierre (OHLC) entre la primera y la última barra. El código de implementación es muy similar al código usado en la clase CPermuteTicks presentado en el artículo "Pruebas de permutación de Monte Carlo en MetaTrader 5". El código de permutación de la barra de precios se encapsulará en la clase CPermuteRates contenida en PermuteRates.mqh.


Clase 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 comenzará definiendo una estructura simple que almacenará la diferencia logarítmica de los precios brutos.

  •  rel_open almacenará la diferencia logarítmica entre la apertura actual y el cierre de la barra anterior.
  • rel_high representará la diferencia logarítmica entre el máximo de la barra actual y el precio de apertura.
  •  rel_low se referirá a la diferencia logarítmica entre el mínimo de la barra actual y el precio de apertura.
  •  rel_close será de nuevo la diferencia logarítmica entre el cierre y la apertura de la barra actual.

La estructura personalizada CRelRates representará los datos extraídos de MqlRates que serán permutados. Los demás miembros de la estructura MqlRates no se modificarán. El resultado final de los precios permutados copiará estos miembros de la estructura de la serie de precios original. Como ya hemos mencionado, solo cambiarán los valores OHLC.

//+------------------------------------------------------------------+
//| Permute the bars                                                 |
//+------------------------------------------------------------------+
bool CPermuteRates::Permute(MqlRates &out_rates[])
  {
//---
   if(!m_initialized)
     {
      Print("Initialization error");
      ZeroMemory(out_rates);
      return false;
     }
//---
   int i,j;
   double temp=0.0;
//---
   i=ArraySize(m_rates)-2;
//---
   while(i > 1 && !IsStopped())
     {
      j = (int)(m_random.RandomDouble() * i) ;
      if(j >= i)
         j = i - 1 ;
      --i ;
      temp = m_differenced[i+1].rel_open ;
      m_differenced[i+1].rel_open = m_differenced[j+1].rel_open ;
      m_differenced[j+1].rel_open = temp ;
     }
//---
   i =ArraySize(m_rates)-2;
//---
   while(i > 1  && !IsStopped())
     {
      j = (int)(m_random.RandomDouble() * i) ;
      if(j >= i)
         j = i - 1 ;
      --i ;
      temp = m_differenced[i].rel_high;
      m_differenced[i].rel_high = m_differenced[j].rel_high ;
      m_differenced[j].rel_high = temp ;
      temp = m_differenced[i].rel_low ;
      m_differenced[i].rel_low = m_differenced[j].rel_low ;
      m_differenced[j].rel_low = temp ;
      temp = m_differenced[i].rel_close ;
      m_differenced[i].rel_close = m_differenced[j].rel_close ;
      m_differenced[j].rel_close = temp ;
     }
//---
   if(ArrayCopy(out_rates,m_rates)!=int(m_rates.Size()))
     {
      ZeroMemory(out_rates);
      Print("Copy error ", GetLastError());
      return false;
     }
//---
   for(i=1 ; i<ArraySize(out_rates) && !IsStopped() ; i++)
     {
      out_rates[i].open  = MathExp(((MathLog(out_rates[i-1].close)) + m_differenced[i-1].rel_open)) ;
      out_rates[i].high  = MathExp(((MathLog(out_rates[i].open)) + m_differenced[i-1].rel_high)) ;
      out_rates[i].low   = MathExp(((MathLog(out_rates[i].open)) + m_differenced[i-1].rel_low)) ;
      out_rates[i].close = MathExp(((MathLog(out_rates[i].open)) + m_differenced[i-1].rel_close)) ;
     }
//---
   if(IsStopped())
      return false;
//---
   return true;
//---
  }   


La permutación se realizará en el método Permute(). La estructura CRelRates separará los datos de las barras en dos tipos de descriptores. Una serie de valores rel_open representará cambios de una barra a la siguiente, mientras que rel_high, rel_low y rel_close representarán los cambios dentro de una barra. Para reordenar las barras, primero barajaremos la serie de precios rel_open. Esa será la diferencia entre las barras. Después, se mezclarán los cambios de la barra interior. La nueva serie OHLC se construirá a partir de los datos de las barras barajadas para producir nuevos valores de apertura con los correspondientes precios máximo, mínimo y de cierre construidos a partir de los cambios en las barras interiores permutadas.


Cambios en CPermuteTicks

Existe una serie de diferencias entre CPermuteRates y la antigua clase CPermuteTicks. Una de ellas es el uso de su propio generador de números aleatorios, que, como he observado, funciona un poco más rápido que el uso de las funciones incorporadas de 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();
  }
//+------------------------------------------------------------------+

El código también se aplicará a la nueva clase CPermuteTicks. Hemos eliminado las transacciones intermediarias innecesarias para mejorar la eficacia. Solo se barajan los precios de oferta. Como otras propiedades de los ticks se copian de la serie original de ticks, esto resolverá el problema que a veces lleva a barajar ticks con spreads poco realistas. A continuación le mostramos la nueva serie 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 sigue funcionando igual que la versión anterior. CPermuteRates funcionará de forma similar. La diferencia entre ambos es que uno trabajará con ticks y el otro con precios.


Clase CPermutedSymbolData

El script PrepareSymbolsForPermutationTest ha sido actualizado para reflejar los cambios realizados en CPermuteTicks así como la aparición de CPermuteRates. La funcionalidad del script estará encapsulada en la clase CPermutedSymbolData, y nos permitirá crear nuestros propios símbolos con una permutación de ticks o precios reorganizados basados en un 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);
  };


Esto se logra especificando el tipo de datos que vamos a barajar (ticks o precios) en la llamada al constructor. La enumeración ENUM_RATES_TICKS describirá los parámetros disponibles para un único parámetro constructor.

//+-----------------------------------------------------------------------------------------+
//|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;
     }
  }

Después de crear un ejemplar de CPermutedSymbolData, deberemos llamar al método Initiate() para especificar el símbolo y el periodo de la fecha que definirán los ticks o precios en los que se basarán las permutaciones.

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Si Initiate() retorna true, podremos llamar al método Generate() con el número de permutaciones deseado. El método retornará el número de símbolos de usuario cuyos datos se han rellenado correctamente con ticks o precios 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;
  }
//+------------------------------------------------------------------+

Arriba está el código del script. Todo el código fuente se adjunta al artículo.


Aplicación de las pruebas de permutación

En la introducción de este artículo, hemos mencionado un problema al que se enfrentan muchos compradores potenciales de asesores. Existe la posibilidad de que vendedores sin escrúpulos usen tácticas engañosas. A menudo, los vendedores muestran capturas de pantalla con atractivas curvas de equidad que muestran beneficios potenciales. Muchos han sido víctimas de esta táctica y han aprendido por amarga experiencia que estas capturas de pantalla se crearon a partir de estrategias artificiales. En esta sección, examinaremos uno de estos asesores disponibles en CodeBase que puede utilizarse para crear curvas de equidad engañosas. También aplicaremos la prueba de permutación para descubrir el engaño.

Curva de equidad

Revisión de la prueba de permutación

La prueba es bastante laboriosa y requiere mucho tiempo y recursos informáticos, aunque, en mi opinión, los resultados merecen la pena y pueden evitar que cometamos errores. El método usado consiste en seleccionar una muestra adecuada para las pruebas. La muestra se dividirá en conjuntos de datos dentro de la muestra y fuera de la muestra. El asesor se optimizará a partir de datos de la muestra, mientras que el rendimiento final se determinará usando pruebas realizadas con datos fuera de la muestra utilizando los parámetros optimizados. Para ello se utilizarán las series de datos originales, así como al menos 100 conjuntos de datos de permutación. Esto es exactamente lo que hemos hecho para probar el asesor utilizado en nuestra demostración.

Pruebas del asesor grr-al

Cualquiera que haya estudiado la documentación de MQL5 o el código base probablemente se haya encontrado con este asesor. En la documentación de MQL5, se llama "Grial de prueba". Cuando se ejecuta en el simulador de estrategias en el modo de generación de ticks "OHLC en M1" o "Solo precios de apertura", genera una curva de equidad impresionante. Este es el asesor que usaremos en nuestra demostración. Hemos cambiado el código un poco para mostrar algunas variables globales para la optimización. Hemos seleccionado dos de los tres parámetros para su optimización, a saber, SL, TP y 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;


Los ajustes usados para la optimización se muestran en la captura de pantalla.

Datos de entrada para la optimización



Como conjunto de datos, hemos elegido EURUSD H1 para todo el año 2022. Los seis primeros meses de 2022 se han usado para la optimización, mientras que la segunda mitad se ha utilizado como periodo fuera de muestra para probar los parámetros óptimos.

Ajustes de optimización


En primer lugar, hemos usado el script PrepareSymbolsForPermutationsTests para crear los símbolos de los datos de permutación personalizados. Luego hemos cronometrado la ejecución del programa y hemos la hemos marcado como se indica a continuación. El código de error se debe a que no había suficiente espacio en el disco en el primer intento, y solo hemos añadido con éxito 99 símbolos de usuario. 

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

¡La cantidad de datos generados ha sido de casi 40 gigabytes de datos de ticks en un año cuando los datos se han permutado 100 veces!


Tamaño de la carpeta de datos de ticks

Al utilizar los precios, todo ha sido mucho más rápido y los datos han ocupado mucho menos espacio.

NK      0       12:51:23.166    PrepareSymbolsForPermutationTests (EURUSD,M1)   Number of permuted symbols is 100, Runtime 1.4mins


Con estos datos, cada símbolo se ha optimizado con los conjuntos de la muestra.

Resultados de la optimización

En la prueba fuera de muestra hemos usado los parámetros que arrojaron los rendimientos absolutos más elevados. La optimización y las pruebas fuera de muestra se han realizado utilizando el modo de ticks según los precios de apertura. Esto significa que el asesor contaba con todas las ventajas para exhibir unos resultados sobresalientes.

Los resultados de todas las pruebas se han presentado en un archivo csv. IS Profit y OOS PROFIT son los beneficios dentro y fuera de la muestra, 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

El valor p calculado es de 0,8217821782178217.

MO      0       09:49:57.991    ProcessOptFiles (EURUSD,MN1)    P-value is 0.8217821782178217

Así pues, la probabilidad de observar aleatoriamente el rendimiento alcanzado en el conjunto de datos original supera el 80%, y esto indica claramente que el asesor es inútil.


¿Por qué esto funciona?

La premisa de la prueba de permutación en el contexto del desarrollo de estrategias es que una estrategia de asesor es una descripción de un patrón o conjunto de reglas utilizadas para obtener una ventaja en la negociación. Cuando los datos con los que trabajamos cambian, los patrones originales que el asesor habría seguido para obtener beneficios pueden verse alterados. Si el asesor negocia con algún patrón, su rendimiento con datos de permutación se verá afectado. Si comparamos el rendimiento de las pruebas con y sin permutación, queda claro que, incluso después de la optimización, el asesor se basa realmente en algún patrón o regla únicos. El rendimiento del conjunto de datos sin permutación deberá ser diferente del rendimiento de las pruebas de permutación.

Como hemos visto en la prueba mostrada, el asesor en cuestión utiliza el método de generación de ticks y no aplica ninguna estrategia real (patrones o reglas). La prueba de permutación ha podido detectar esto.

Las pruebas de permutación también pueden usarse para determinar el grado de sobreentrenamiento tras la optimización. Para verificar el sobreentrenamiento, tendremos que probar y comparar el rendimiento en la muestra de conjuntos de datos permutados y no permutados. La medida en que las medidas de rendimiento sin permutaciones difieren de aquellas con permutaciones puede usarse para cuantificar el sobreentrenamiento. Cuando prevalece el sobreentrenamiento, la diferencia entre los resultados de la permutación y los del rendimiento sin permutación será pequeña. Veremos valores de p bastante grandes.


Conclusión

Hoy hemos revisado la implementación del algoritmo de permutación de barras de precio y actualizado el código para crear símbolos personalizados con ticks o barras permutados. Los programas descritos se han utilizado para mostrar la prueba de permutación en un asesor con resultados positivos. Las pruebas aleatorias son una herramienta importante para cualquier persona interesada en el trading automatizado. Tan importante que creo que debería ser añadida como una característica al simulador de estrategias de MetaTrader 5.

Archivo
Descripción
MQL5\Experts\grr-al.mq5
Esta es una versión ligeramente modificada del asesor disponible en CodeBase en MQL5.com. Comercia utilizando el método de generación de ticks del simulador de estrategias en el modo "OHLC en M1".
MQL5\Include\NewSymbol.mqh
Definición de la clase CNewSymbol para crear símbolos personalizados
MQL5\Include\ PermutedSymbolData.mqh
Definición de la clase CPermutedSymbolData para crear símbolos personalizados con precios o ticks permutados.
MQL5\Include\PermuteRates.mqh
 Clase CPermuteRates para crear permutaciones del array de datos MqlRates
MQL5\Include\PermuteTicks.mqh
Definición de la clase CPermuteTicks para crear permutaciones del array de datos MqlTick
MQL5\Include\UniformRandom.mqh
CUniFrand encapsula un generador de números aleatorios distribuidos uniformemente
MQL5\Scripts\PrepareSymbolsForPermutationTests.mq5
Script que integra todo el código de ayuda para crear símbolos personalizados en MetaTrader 5.


Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/13591

Archivos adjuntos |
grr-al.mq5 (4.94 KB)
NewSymbol.mqh (29.34 KB)
PermuteRates.mqh (6.04 KB)
PermuteTicks.mqh (4.79 KB)
UniformRandom.mqh (2.84 KB)
Añadimos un LLM personalizado a un robot comercial (Parte 2): Ejemplo de despliegue del entorno Añadimos un LLM personalizado a un robot comercial (Parte 2): Ejemplo de despliegue del entorno
Los modelos lingüísticos (LLM) son una parte importante de la inteligencia artificial que evoluciona rápidamente, por lo que debemos plantearnos cómo integrar unos LLM potentes en nuestro comercio algorítmico. A la mayoría de la gente le resulta difícil adaptar estos modelos a sus necesidades, implantarlos de forma local y luego aplicarlos al trading algorítmico. En esta serie de artículos abordaremos un enfoque paso a paso para lograr este objetivo.
Preparación de indicadores de símbolo/periodo múltiple Preparación de indicadores de símbolo/periodo múltiple
En este artículo analizaremos los principios de la creación de los indicadores de símbolo/periodo múltiple y la obtención de datos de ellos en asesores e indicadores. Asimismo, veremos los principales matices de uso de los indicadores múltiples en asesores e indicadores, y su representación a través de los búferes del indicador personalizado.
Cuantificación en el aprendizaje automático (Parte 1): Teoría, ejemplo de código, análisis sintáctico de la aplicación CatBoost Cuantificación en el aprendizaje automático (Parte 1): Teoría, ejemplo de código, análisis sintáctico de la aplicación CatBoost
En este artículo, hablaremos de la aplicación teórica de la cuantificación en la construcción de modelos arbóreos. Asimismo, analizaremos los métodos de cuantificación implementados en CatBoost. El material se presentará sin fórmulas matemáticas complejas, en un lenguaje accesible.
Añadimos un LLM personalizado a un robot comercial (Parte 1): Desplegando el equipo y el entorno Añadimos un LLM personalizado a un robot comercial (Parte 1): Desplegando el equipo y el entorno
Los modelos lingüísticos (LLM) son una parte importante de la inteligencia artificial que evoluciona rápidamente, por lo que debemos plantearnos cómo integrar unos LLM potentes en nuestro comercio algorítmico. A la mayoría de la gente le resulta difícil personalizar estos potentes modelos para adaptarlos a sus necesidades, implantarlos de forma local y luego aplicarlos al trading algorítmico. En esta serie de artículos abordaremos un enfoque paso a paso para lograr este objetivo.