English Русский 中文 Deutsch 日本語 Português
preview
Pruebas de permutación de Monte Carlo en MetaTrader 5

Pruebas de permutación de Monte Carlo en MetaTrader 5

MetaTrader 5Ejemplos | 1 febrero 2024, 08:52
237 0
Francis Dube
Francis Dube

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.

Ajustes del script



Después de ejecutar el script, tendremos 100 símbolos adicionales basados ​​en los ticks permutados de la muestra del símbolo AUDUSD.

Símbolos personalizados en la Observación



Finalmente, ejecutaremos una prueba de optimización.

Ajustes del simulador

  La configuración del asesor se muestra a continuación.

Parámetros del asesor

Resultados de la prueba.

de optimizaciónResultados


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.

Cálculo de los valores P en OpenOffice Calc

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

Archivos adjuntos |
NewSymbol.mqh (29.34 KB)
PermuteTicks.mqh (8.78 KB)
Mql5.zip (9.91 KB)
Aproximación por fuerza bruta a la búsqueda de patrones (Parte VI): Optimización cíclica Aproximación por fuerza bruta a la búsqueda de patrones (Parte VI): Optimización cíclica
En este artículo mostraremos la primera parte de las mejoras que nos permitieron no solo cerrar toda la cadena de automatización para comerciar en MetaTrader 4 y 5, sino también hacer algo mucho más interesante. A partir de ahora, esta solución nos permitirá automatizar completamente tanto el proceso de creación de asesores como el proceso de optimización, así como minimizar el gasto de recursos a la hora de encontrar configuraciones comerciales efectivas.
Teoría de Categorías en MQL5 (Parte 17): Funtores y monoides Teoría de Categorías en MQL5 (Parte 17): Funtores y monoides
Este es el último artículo de la serie sobre funtores. En él, revisaremos los monoides como categoría. Los monoides, que ya hemos introducido en esta serie, se utilizan aquí para ayudar a dimensionar la posición junto con los perceptrones multicapa.
Redes neuronales: así de sencillo (Parte 55): Control interno contrastado (CIC) Redes neuronales: así de sencillo (Parte 55): Control interno contrastado (CIC)
El aprendizaje contrastivo (Contrastive learning) supone un método de aprendizaje de representación no supervisado. Su objetivo consiste en entrenar un modelo para que destaque las similitudes y diferencias entre los conjuntos de datos. En este artículo, hablaremos del uso de enfoques de aprendizaje contrastivo para investigar las distintas habilidades del Actor.
Redes neuronales: así de sencillo (Parte 54): Usamos un codificador aleatorio para una exploración eficiente (RE3) Redes neuronales: así de sencillo (Parte 54): Usamos un codificador aleatorio para una exploración eficiente (RE3)
Siempre que analizamos métodos de aprendizaje por refuerzo, nos enfrentamos al problema de explorar eficientemente el entorno. Con frecuencia, la resolución de este problema hace que el algoritmo se complique, llevándonos al entrenamiento de modelos adicionales. En este artículo veremos un enfoque alternativo para resolver el presente problema.