Permutación de las barras de precio en MQL5
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.
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.
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.
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!
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.
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
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso