Pruebas de permutación de Monte Carlo en MetaTrader 5
Introducción
Hace un tiempo, Alexéi Nikoláyev escribió un interesante artículo: "Aplicando el método de Monte Carlo para optimizar estrategias comerciales". En este se describen pruebas de permutación en las que las transacciones se cambian aleatoriamente. El autor menciona brevemente otro tipo de prueba de permutación en la que se cambia aleatoriamente una secuencia de datos de precios y se compara el rendimiento de un asesor con el rendimiento logrado al realizar las pruebas en muchas otras variaciones de la misma secuencia de series de precios.
En mi opinión, el autor asumió erróneamente que dicha prueba no se puede efectuar en un asesor personalizado que utilice MetaTrader 5. En cualquier caso, no del todo. En este artículo, mostraremos una prueba de permutación que involucrará datos de precios barajados aleatoriamente utilizando MetaTrader 5. Hoy presentaremos un código para permutar series de precios, así como un script que automatizará los pasos iniciales en la preparación para realizar una prueba de permutación de un asesor completo.
Descripción general de las pruebas de permutación
En resumen, el tipo de prueba de permutación que describiremos aquí implica un muestreo de los datos de precio. Lo preferible es realizar la prueba sobre una muestra. Tras probar esta serie de precios, anotaremos todos los criterios de rendimiento que podrían interesarnos medir. Luego cambiaremos aleatoriamente la secuencia de la serie de precios original, probaremos el asesor y comprobaremos su eficacia.
Haremos esto muchas veces, cambiando en cada ocasión la serie de precios y registrando los criterios de rendimiento resultantes que hemos anotado para otras pruebas. Esto deberá hacerse al menos cien veces, en realidad, lo ideal sería realizar el proceso varios miles de veces. Cuantas más veces permutemos y probemos, más fiables serán los resultados. Pero ¿qué esperamos de los resultados?
¿Por qué se necesitan pruebas de permutación?
Después de ejecutar una serie de pruebas iterativas, obtendremos un conjunto de métricas de rendimiento para cada permutación. No importa qué medida de rendimiento utilicemos, podría ser el ratio de Sharpe, el coeficiente de beneficio o simplemente el balance final o el beneficio neto. Digamos que se han dado 99 permutaciones (o 100 incluyendo la prueba original sin permutaciones). Entonces tendremos 100 métricas de rendimiento para comparar.
El siguiente paso será contar el número de veces que se ha superado la métrica de rendimiento para la prueba sin permutación e informar sobre ese número como una fracción de la cantidad de pruebas realizadas, en este caso 100. Este porcentaje será la probabilidad de obtener un resultado de prueba de no-permutación o mejor, como si el asesor no tuviera ningún potencial de beneficio. En estadística se conoce como valor p, y es el resultado de la comprobación de la hipótesis.
Continuando con nuestra prueba de permutación hipotética de 100 iteraciones, tendremos que exactamente 29 métricas de rendimiento de permutación han sido mejores que el punto de referencia sin permutación. Obtendremos un valor p de 0,3, que es 29+1/100. Esto significa que existe una probabilidad de 0,3 de que el asesor no rentable hubiera logrado el mismo o mejor rendimiento que el observado durante las pruebas sin permutación. Este resultado puede parecer alentador, pero queremos que los valores p sean lo más cercanos posible a cero, en el rango de 0,05 e inferiores.
La fórmula completa se ofrece a continuación:
z+1/r+1
donde r será el número de permutaciones realizadas y z será el número total de pruebas con mejor rendimiento. El procedimiento de permutación resulta importante para realizar la prueba correctamente.
Permutación de la serie de precios
Para permutar adecuadamente un conjunto de datos, deberemos asegurarnos de que todas las posibles variaciones de la secuencia sean igualmente probables. Para hacer esto, necesitaremos generar un número aleatorio distribuido uniformemente entre 0 y 1. La biblioteca estándar MQL5 ofrece una herramienta que cumple este requisito en la biblioteca estadística. Utilizándola, podremos especificar el rango de valores necesarios.
//+------------------------------------------------------------------+ //| 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); }
Para barajar los datos de precio se requieren ciertas condiciones. En primer lugar, no podemos simplemente cambiar la posición del valor del precio, ya que esto violaría las relaciones temporales inherentes a las series temporales financieras. Por ello, en lugar de los precios reales, reorganizaremos los cambios de precio. Al registrar los precios, en primer lugar, antes de compararlos, minimizaremos el impacto de los cambios en las diferencias de los precio subyacentes.
Usando este método, deberemos mantener el primer valor del precio y excluirlo de la permutación. El resultado de la reconstrucción de la serie supondrá la preservación de la tendencia presente en la secuencia de precios original. El único cambio serán los movimientos internos de precio entre los mismos primer y último precio de la serie original.
Antes de cambiar realmente la serie de precios, deberemos decidir qué datos utilizaremos. En MetaTrader 5, los datos de los gráficos se muestran como columnas basadas en datos de ticks. Permutar una serie de precios será mucho más fácil que permutar la información sobre las barras, por lo que utilizaremos los datos de ticks. El uso de ticks también introducirá otras complicaciones porque los ticks incluyen otra información además de los precios brutos. Hay información sobre indicadores de volumen, tiempo y ticks.
En primer lugar, la información sobre la hora y la bandera de ticks permanecerá sin cambios, por lo que nuestro procedimiento de permutación no debería modificar esta información. Solo nos interesa Bid, Ask y el volumen. La segunda complicación sería la posibilidad de que alguno de estos valores pueda ser cero, lo cual causaría problemas a la hora de aplicarles una transformación logarítmica. Para demostrar cómo superar estos problemas, podemos echar un vistazo al código.
Implementación del algoritmo de permutación de ticks.
La clase CPermuteTicks, contenida en el archivo de inclusión PermuteTicks.mqh implementará el procedimiento de permutación de ticks. Dentro de PermuteTicks.mqh incluiremos Uniform.mqh de la biblioteca estándar para lograr acceso a una utilidad que mostrará números aleatorios generados uniformemente dentro de un rango determinado. Las siguientes definiciones indicarán este rango. Tenga cuidado al cambiar estos valores. Asegúrese de que el valor mínimo sea realmente inferior al valor de umbral máximo.
//+------------------------------------------------------------------+ //| 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
La estructura CMqlTick representará los miembros correspondientes de la estructura MqlTick incorporada con la que funcionará la clase. El resto de la información sobre los ticks no se verá afectada.
//+------------------------------------------------------------------+ //| struct to handle tick data to be worked on | //+------------------------------------------------------------------+ struct CMqlTick { double ask_d; double bid_d; double vol_d; double volreal_d; };
La clase CPermuteTicks tiene tres propiedades de array privadas que almacenan: los ticks originales almacenados en m_ticks, los ticks transformados logarítmicamente almacenados en m_logticks y, finalmente, los ticks de diferencia recopilados en m_differenced.
//+------------------------------------------------------------------+ //| 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[]); };
m_initialized - bandera booleana que indica una operación de preprocesamiento exitosa antes de que se puedan realizar permutaciones.
Para utilizar la clase, el usuario deberá llamar al método Initialize() después de crear un ejemplar del objeto. El método requiere una serie de ticks que deberán permutarse. Dentro del método, se cambiarán los tamaños de los arrays de clases inaccesibles y se conectará LogTranformTicks() para transformar los datos de ticks. Esto se hará eliminando los valores cero o negativos y reemplazándolos con 1,0. Una vez completada la permutación, los datos de ticks transformados logarítmicamente se retornarán al dominio original utilizando el 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; }
Para mostrar los ticks permutados, llamaremos al método Permute(). Este requerirá un parámetro del array dinámico MqlTick, en el que se colocarán los ticks permutados. A continuación le mostraremos un procedimiento para mezclar ticks dentro de un ciclo while que cambiará la posición del valor de diferencia de tick según un número aleatorio generado en cada iteración.
//+------------------------------------------------------------------+ //|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); } //+------------------------------------------------------------------+
Una vez completadas todas las iteraciones, el array m_logticks se reconstruirá mediante el descarte de las diferencias utilizando los datos de tick m_differenced permutados. Finalmente, el argumento único del método Permute() se rellenará con los datos de m_logtick retornados al dominio de origen, con la hora del tick y la información del indicador copiadas de la serie de ticks original.
//+-------------------------------------------------------------------+ //|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; }
Ahora tendremos un algoritmo para procesar permutaciones de series de precios, pero esto es solo la mitad de la batalla. Todavía deberemos realizar una prueba.
Prueba de permutación
La prueba de permutación usará dos funciones del terminal MetaTrader 5. La primera nos permitirá crear nuestros propios símbolos y especificar sus propiedades. Con la segunda, podremos optimizar los asesores según los símbolos incluidos en la lista de la "Observación de Mercado". Es decir, se añadirán al menos dos pasos más al proceso.
Podremos permutar los ticks y crear nuestros propios símbolos basados en cualquiera existente. En este caso, para cada símbolo personalizado, se indicará una permutación única de ticks para el símbolo utilizado como base. Los símbolos se podrán crear manualmente, pero sería más inteligente automatizar la creación de símbolos y la adición de los ticks de permutación.
Esto es exactamente lo que hace el script PrepareSymbolsForPermutationTests. Los datos de entrada especificados por el usuario permitirán configurar el símbolo básico, el rango de fechas del símbolo básico que se usará en las permutaciones, el número de permutaciones requeridas que se corresponderá con el número de símbolos personalizados creados y un identificador opcional de tipo string que se añadirá a los nombres de los nuevos 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)); //--- } //+------------------------------------------------------------------+
El script generará automáticamente los nombres de los símbolos utilizando el nombre del símbolo básico con una enumeración al final. El código necesario se podrá encontrar en el archivo GenerateSymbols.mqh, que contiene la definición de la clase CGenerateSymbols. La definición de la clase se basará en otras dos dependencias: NewSymbol.mqh, que contiene la definición de la clase CNewSymbol adaptada del código contenido en el artículo "Recetas MQL5 – Prueba de estrés de una estrategia comercial con ayuda de 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; };
La clase nos ayudará a crear nuevos símbolos personalizados basados en los existentes. La última dependencia necesaria será PermuteTicks.mqh, que hemos encontrado antes.
//+------------------------------------------------------------------+ //| 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 tendrá dos funciones miembro que deberemos tener en cuenta. El método Initiate() deberá llamarse primero después de crear el objeto. Tiene cuatro parámetros que se corresponden con los parámetros de entrada personalizados del script ya mencionado.
//+-----------------------------------------------------------------------------------------+ //|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); }
El método Generate() tomará como entrada el número requerido de permutaciones y retornará el número de nuevos símbolos personalizados añadidos a la "Observación de mercado" del terminal.
El resultado de la ejecución del script aparecerá en la pestaña "Expertos" del 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; }
El siguiente paso consistirá en ejecutar la optimización en el simulador de estrategias: asegúrese de seleccionar el método de optimización más reciente y especificar el asesor que se está comprobando. Ejecute la prueba. Lo más probable es que lleve mucho tiempo. Al terminar, tendremos un conjunto de datos de rendimiento.
Ejemplo
Veamos cómo se ve todo ejecutando una prueba usando el asesor MACD Sample incluido. La prueba se realizará con el símbolo AUDUSD con 100 permutaciones especificadas en el script.
Después de ejecutar el script, tendremos 100 símbolos adicionales basados en los ticks permutados de la muestra del símbolo AUDUSD.
Finalmente, ejecutaremos una prueba de optimización.
La configuración del asesor se muestra a continuación.
Resultados de la prueba.
Resultados
La pestaña de resultados del simulador de estrategias muestra todas las métricas de rendimiento que podrían interesarnos; los símbolos están clasificados en orden descendente según los criterios de rendimiento seleccionados, que se pueden elegir en el menú desplegable en la esquina superior derecha de la ventana del simulador. En esta vista, el valor p se puede calcular fácilmente de forma manual o, de ser necesario, automáticamente procesando un archivo .xml que se puede exportar desde el simulador haciendo clic derecho, si fuera necesario.
Usando este ejemplo, ni siquiera necesitaremos hacer ningún cálculo, ya que podemos ver que los valores de prueba de los símbolos de origen se encuentran muy abajo en la pestaña de resultados, y los más de 10 símbolos permutados muestran un mejor rendimiento. Esto indica que el valor p es superior a 0,05.
Obviamente, el resultado de esta prueba deberá tomarse con cautela ya que el periodo de prueba seleccionado ha sido muy corto. Los usuarios deberán seleccionar un periodo de prueba que sea mucho más largo y más representativo de las condiciones que pueden ocurrir en el comercio real.
Como ya hemos mencionado, existen muchas opciones para procesar aún más nuestros resultados para calcular los valores p. Todas las operaciones posteriores se centrarán en analizar los datos del archivo XML exportado desde el simulador de estrategias. Le mostraremos cómo puede usar una aplicación de hoja de cálculo para procesar un archivo con unos pocos clics y pulsaciones de teclas.
Obviamente, después de exportar un archivo, será necesario registrar dónde se guarda. Ábralo usando cualquier aplicación de hoja de cálculo. La siguiente imagen muestra el uso de OpenOffice Calc gratuito, que ha añadido una nueva fila en la parte inferior de la tabla. Antes de continuar, sería prudente eliminar las líneas para los símbolos que no deberían incluirse en los cálculos. Debajo de cada columna correspondiente, el valor p se calculará utilizando una macro personalizada. La fórmula de macro utilizará las métricas de rendimiento del símbolo permutado (ubicadas en la línea 18 del documento mostrado), así como las métricas de rendimiento de los símbolos permutados para cada columna. La fórmula de la macro completa se muestra en la figura.
Además de utilizar una aplicación de hoja de cálculo, podríamos utilizar Python, que tiene muchos módulos para analizar archivos XML. Si el usuario conoce MQL5, podrá analizar archivos usando un script simple. Solo recuerde seleccionar un directorio accesible al exportar los resultados de optimización del simulador.
Conclusión
Hoy hemos demostrado que la prueba de permutación se puede aplicar a cualquier asesor sin acceso al código fuente. Este tipo de prueba de permutación resulta invaluable porque aplica estadísticas bastante sólidas que no requieren ninguna suposición sobre la distribución de los datos involucrados. No se puede decir lo mismo de muchas otras pruebas estadísticas usadas en el desarrollo de estrategias.
La mayor desventaja se relaciona con el tiempo y los recursos informáticos necesarios para realizar la prueba. Para trabajar, necesitaremos no solo un procesador potente, sino también una cantidad significativa de espacio en el disco. La creación de nuevos ticks y símbolos ocupará mucho espacio en el disco duro. En mi opinión, cualquiera que desee comprar asesores debería tener en cuenta este método de análisis. Claro que lleva tiempo, pero también puede impedirle tomar malas decisiones.
El análisis que usa datos de precio permutados se puede aplicar de diversas formas. Podemos utilizar el método para analizar el comportamiento de los indicadores, así como en diferentes etapas del desarrollo de la estrategia. Las posibilidades son amplísimas. A veces, al desarrollar o probar estrategias, puede parecer que no hay suficientes datos. El uso de series de precios permutadas aumentará significativamente la disponibilidad de datos para realizar pruebas. Los códigos fuente de todos los programas mql5 aquí descritos se adjuntan al artículo. Espero que sean de utilidad para los lectores.
Nombre del archivo | Tipo de programa | Descripción |
---|---|---|
GenerateSymbols.mqh | Archivo include | Definición de la clase CGenerateSymbols para generar símbolos con datos de ticks permutados a partir del símbolo básico seleccionado |
NewSymbol.mqh | Archivo include | Definición de la clase CNewSymbol para crear símbolos personalizados |
PermuteTicks.mqh | Archivo include | Define la clase CPermuteTicks para crear permutaciones de un array de datos de ticks. |
PrepareSymbolsForPermutationTests.mq5 | Archivo de script | Script que automatiza la creación de símbolos personalizados con permutación de ticks al preparar una prueba de permutación. |
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/13162
- 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