English Русский 中文 Deutsch 日本語
preview
Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 8): Análisis de múltiples estrategias

Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 8): Análisis de múltiples estrategias

MetaTrader 5Ejemplos |
27 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Como operadores algorítmicos, nos enfrentamos a muchos desafíos, los cuales ya hemos analizado en esta serie. Por ejemplo, hemos observado que a nuestros modelos estadísticos les resulta más fácil predecir las lecturas futuras de los indicadores técnicos que predecir los niveles de precios futuros.

También analizamos las ventajas de un sistema de negociación que modela la relación entre la estrategia que sigue y el mercado en el que aplica dicha estrategia.

Nuestros modelos obtuvieron resultados consistentemente mejores cuando reemplazamos la tarea clásica de predicción directa de precios con estas tareas alternativas. La predicción directa de precios es difícil, pero cambiando la forma en que planteamos el problema, podemos superar a los modelos que se centran en la tarea clásica, utilizando las mismas herramientas estadísticas.

Hoy exploraremos una nueva estrategia potencial que se basa en nuestros hallazgos anteriores. ¿Y si creamos una aplicación que implemente tres estrategias de trading diferentes? ¿Puede esta aplicación aprender a elegir una estrategia a la vez, cambiando periódicamente a la más rentable en lugar de seguir las tres simultáneamente? Si la aplicación puede cambiar de estrategia periódicamente, ¿puede seleccionar de forma rentable la mejor de las tres que conoce?

Dicha aplicación podría ser más útil que un algoritmo de negociación fijo que ejecute las tres estrategias o una combinación de ellas.

Para medir el valor de nuestro modelo estadístico, primero necesitamos un nivel de rendimiento de referencia que nuestro modelo debería superar.

Combinaremos tres estrategias de negociación independientes: una estrategia de continuación de cruce de medias móviles, una estrategia de momentum basada en el RSI (índice de fuerza relativa) y una estrategia de ruptura de tendencia basada en Williams %R. Cada una se explicará en detalle.

Este artículo presentará algunas herramientas potentes en la terminal MetaTrader 5, centrándose en las pruebas de simulación (Walk Forward Testing). Las pruebas de avance son diferentes de las pruebas retrospectivas, y explicaremos estas diferencias más adelante.

Las pruebas de avance nos brindan más información que las pruebas retrospectivas simples, especialmente cuando se combinan con un optimizador que genera nuevos parámetros de estrategia para probar. Esto nos permite encontrar de forma sólida configuraciones rentables para nuestra estrategia de trading. MetaTrader 5 incluye esta funcionalidad avanzada con sus optimizadores genéticos rápidos y lentos.

Al combinar estas potentes herramientas de prueba de estrategias con los fiables principios de diseño orientado a objetos que abordamos en esta serie, diseñaremos, probaremos y validaremos un competidor sólido al que nuestros modelos estadísticos deberán superar.


Primeros pasos en MQL5

Este debate aborda el problema de cómo combinar mejor diferentes estrategias en una sola que funcione. Las soluciones codificadas de forma rígida son poco comunes, especialmente cuando se utilizan varias estrategias a la vez.

Combinar estrategias es emocionante porque requiere creatividad. Pero también significa que debemos minimizar los efectos secundarios inesperados.

Los operadores suelen utilizar diferentes estrategias de forma combinada. Por ejemplo, una estrategia podría abrir posiciones mientras que otra decide cuándo cerrarlas. Cada estrategia se centra en una parte del problema. Queremos imitar este enfoque humano a la vez que mostramos cómo utilizar el probador de estrategias de MetaTrader 5 para encontrar la configuración de estrategia adecuada.

Para combinar estrategias de forma fiable, encapsularemos cada estrategia en una clase. Cada clase debe ser probada para demostrar que funciona. Una única clase padre, denominada "Parent", será la base de todas nuestras variaciones de estrategia. Esta clase incluirá funciones comunes como la actualización de parámetros y la comprobación de señales de compra o venta.

Cada clase que hereda de la clase padre redefinirá lo que se considera una señal de compra o venta. Esto se logra declarando como virtuales los métodos compartidos, lo que permite que cada estrategia los sobrescriba de forma segura.

Ya hemos cubierto lo suficiente como para empezar a construir la primera clase: la clase de estrategia principal.

En MQL5, cada clase comienza con la palabra clave "class" seguida del nombre de la clase. Es práctica habitual nombrar el archivo igual que la clase.

Algunos miembros de la clase se marcarán como virtuales para indicarle al compilador que estas funciones pueden ser sobrescritas por las subclases. Esto permite que cada estrategia defina su propia manera de manejar estos métodos de forma segura.

//+------------------------------------------------------------------+
//|                                                     Strategy.mqh |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

class Strategy
  {
private:
                     int  buffer_size;
                     bool buy;
                     bool sell;
public:
                     //--- Class constructors and destructors
                     Strategy(void);
                    ~Strategy(void);
                    
                    //--- Check if we have any valid trading signals from our strategy
                    virtual bool BuySignal(void);
                    virtual bool SellSignal(void);
                    
                    //--- Update the technical indicators in our strategy
                    virtual bool Update(void);
                    
                    //--- Get the size of the technical indicator buffers
                            int  GetIndicatorBufferSize(void);
  };

Nuestro constructor predeterminado establece todos los valores predeterminados compartidos por todas las instancias de una estrategia.

//+------------------------------------------------------------------+
//| The only way to create an object of the class                    |
//+------------------------------------------------------------------+
Strategy::Strategy(void)
  {
      //--- Upon initialization, both flags should be false 
      buy = false;
      sell = false;
      buffer_size = 10;
  }

Necesitaremos varios métodos de utilidad, comúnmente conocidos como getters y setters. Para empezar, definiremos un método que devuelva el tamaño actual del búfer que hemos seleccionado para los indicadores empleados en nuestra estrategia.

//+------------------------------------------------------------------+
//| The size of our indicator buffer                                 |
//+------------------------------------------------------------------+
int Strategy::GetIndicatorBufferSize(void)
   {
      int res = buffer_size;
      return(res);
   }

También exigiremos que cada estrategia cuente con dos métodos que nos informen, respectivamente, si existen señales de compra o venta. Cada clase que hereda de la clase base debe implementar las reglas que definen sus entradas. De lo contrario, la clase padre siempre devolverá falso e indicará al usuario que sobrescriba este método en la clase hija.

//+------------------------------------------------------------------+
//| Check if our strategy is giving us any buy signals               |
//+------------------------------------------------------------------+
bool Strategy::BuySignal(void)
   {
      //--- The user is intended to overwrite the function in the child class
      //--- Otherwise, failing to do so will always return false as a safety feature
      Print("[WARNING] This function has only been implemented in the parent: ",__FUNCSIG__,"\nKindly make the necessary corrections to the child class");
      return(false);
   }
   
//+------------------------------------------------------------------+
//| Check if our strategy is giving us any sell signals              |
//+------------------------------------------------------------------+
bool Strategy::SellSignal(void)
   {
      //--- The user is intended to overwrite the function in the child class
      //--- Otherwise, failing to do so will always return false as a safety feature
      Print("[WARNING] This function has only been implemented in the parent: ",__FUNCSIG__,"\nKindly make the necessary corrections to the child class");
      return(false);
   }

Actualizar el objeto de estrategia implica actualizar cualquier parámetro de estrategia que se utilice para operar.

//+------------------------------------------------------------------+
//| Update our strategy parameters                                   |
//+------------------------------------------------------------------+
bool Strategy::Update(void)
   {
      //--- The user is intended to overwrite the function in the child class
      Print("[WARNING] This function has only been implemented in the parent: ",__FUNCSIG__,"\nKindly make the necessary corrections to the child class");
      return(false);
   }

Por ahora, nuestro destructor de clases está vacío.

//+------------------------------------------------------------------+
//| The class destructor is currently empty                          |
//+------------------------------------------------------------------+
Strategy::~Strategy(void)
  {
  }
//+------------------------------------------------------------------+

Comenzaremos definiendo el cuerpo de nuestra clase. La clase se titula "OpenCloseMACrossover". Se trata de una estrategia que se basa en dos indicadores de media móvil con periodos idénticos, aplicados respectivamente a los precios de apertura y cierre. Observe que los métodos que eran virtuales en nuestra clase padre, vuelven a ser virtuales en la clase hija.

Las señales de venta se generan cuando la media móvil de apertura está por encima de la de cierre. Lo contrario se registra como una señal de compra. El razonamiento es que, si el precio medio de cierre es mayor que el precio medio de apertura, entonces la acción del precio puede considerarse alcista y puede persistir una fuerte tendencia en esa dirección.

//+------------------------------------------------------------------+
//|                                         OpenCloseMACrossover.mqh |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

#include<VolatilityDoctor\Strategies\Parent\Strategy.mqh>
#include<VolatilityDoctor\Indicators\MA.mqh>

class OpenCloseMACrossover : public Strategy
  {
private:
                     //--- Create 2 moving average instances
                     MA *ma_array[2];

public:
                     //---- Class constructors and destructor
                     OpenCloseMACrossover(string symbol,ENUM_TIMEFRAMES time_frame,int period,int shift,ENUM_MA_METHOD ma_mode);
                     ~OpenCloseMACrossover();
                     
                     //--- Class methods
                     virtual bool Update(void);
                     virtual bool BuySignal(void);
                     virtual bool SellSignal(void);
                    
  };

Ya hemos analizado las reglas de la estrategia de negociación, por lo que implementar los métodos que verifican estas condiciones es una tarea sencilla para nosotros.

//+------------------------------------------------------------------+
//| Check For a Buy Signal                                           |  
//+------------------------------------------------------------------+
bool OpenCloseMACrossover::BuySignal(void)
   {
      //--- Our buy signal is generated if the close moving average is above the open.
      return(ma_array[0].GetCurrentReading()>ma_array[1].GetCurrentReading());
   }

//+------------------------------------------------------------------+
//| Check For a Sell Signal                                          |  
//+------------------------------------------------------------------+
bool OpenCloseMACrossover::SellSignal(void)
   {
      //--- Our sell signal is generated if the open moving average is above the close.
      return(ma_array[0].GetCurrentReading()<ma_array[1].GetCurrentReading());
   }

Nuestro método de actualización llama a las funciones de actualización del indicador que creamos para nuestra clase SingleBufferIndicator. Este método requiere que el tamaño del búfer se pase como parámetro. Creamos un método en nuestra clase padre para que nos devolviera el tamaño del búfer. Hacemos referencia a la clase padre utilizando la sintaxis de dos puntos dobles "::" en nuestra llamada "Strategy::GetIndicatorBufferSize()". Por último, el método de actualización comprobará que los valores actualizados no sean 0 antes de devolver el control al contexto desde el que fue llamado.

//+------------------------------------------------------------------+
//| Our update method                                                |  
//+------------------------------------------------------------------+
bool OpenCloseMACrossover::Update(void)
   {
      //--- Copy indicator readings 
      //--- We will always get the buffer size from the parent class
      ma_array[0].SetIndicatorValues(Strategy::GetIndicatorBufferSize(),true);
      ma_array[1].SetIndicatorValues(Strategy::GetIndicatorBufferSize(),true);
      
      //--- Make sure neither of the indicator values equal 0
      if((ma_array[0].GetCurrentReading() * ma_array[1].GetCurrentReading()) != 0) return(true);
      
      //--- If one/both indicator values equal 0, something went wrong.
      return(false);
   }

El constructor de la clase crea dinámicamente 2 nuevas instancias de nuestros objetos indicadores de media móvil y almacena sus punteros en una matriz del mismo tipo que el puntero, es decir, nuestro tipo MA definido a medida.

//+------------------------------------------------------------------+
//| Our class constructor                                            |
//+------------------------------------------------------------------+
OpenCloseMACrossover::OpenCloseMACrossover(string symbol,ENUM_TIMEFRAMES time_frame,int period,int shift,ENUM_MA_METHOD ma_mode)
  {
      //--- Create two instances of our moving average indiator objects
      ma_array[0] = new MA(symbol,time_frame,period,shift,ma_mode,PRICE_CLOSE);
      ma_array[1] = new MA(symbol,time_frame,period,shift,ma_mode,PRICE_OPEN);
      
      //--- Give feedback
      Print("Strategy class loaded correctly");
  }

El destructor de la clase elimina los objetos dinámicos que hemos creado y nos ayuda a gestionar la memoria que estamos consumiendo.

//+------------------------------------------------------------------+
//| Our class destructor                                             |
//+------------------------------------------------------------------+
OpenCloseMACrossover::~OpenCloseMACrossover()
  {
   //--- Delete the custom objects we made
   delete ma_array[0];
   delete ma_array[1];
   
   //--- Give feedback
   Print("Strategy deinitialized correctly. Goodbye");
  }
//+------------------------------------------------------------------+

Ahora procederemos a probar nuestra primera clase de estrategia. Recuerda que nuestra aplicación final incluirá tres clases, tres estrategias diferentes. Por lo tanto, como buenos desarrolladores, debemos probar cada clase individualmente, comparándola con una versión hardcodeada de una estrategia idéntica. La prueba se considera superada si ambas estrategias arrojan los mismos resultados al realizar pruebas retrospectivas durante el mismo período. Esto podría ahorrarnos horas de búsqueda de errores en el futuro.

En primer lugar, definiremos las constantes del sistema que mantendremos en ambas pruebas. Si ambas estrategias son equivalentes, deberían activarse al mismo tiempo, abrir el mismo número de posiciones y mantener la misma proporción de compra:venta. Repetir estas constantes del sistema es una parte deliberada de nuestra prueba porque estas constantes controlan los parámetros de la estrategia.

//+------------------------------------------------------------------+
//|                                                   MSA Test 1.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Define system constants                                          |
//+------------------------------------------------------------------+
#define MA_TYPE        MODE_EMA
#define MA_PERIOD      10
#define MA_TIME_FRAME  PERIOD_D1
#define MA_SHIFT       0
#define HOLDING_PERIOD 5

A continuación, cargaremos nuestras dependencias.

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <VolatilityDoctor\Trade\TradeInfo.mqh>
#include <VolatilityDoctor\Time\Time.mqh>

Luego, necesitamos algunas variables globales para controlar nuestros indicadores técnicos y llevar la cuenta de cuánto tiempo ha estado abierta nuestra posición.

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+

//--- Custom Types
CTrade    Trade;
TradeInfo *TradeInformation;
Time      *TradeTime;

//--- System Types
double ma_open[],ma_close[];
int    ma_open_handler,ma_close_handler;
intn    position_timer; 

Una vez inicializado nuestro sistema, cargaremos nuestros indicadores técnicos y comprobaremos que son correctos.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Our technical indicators
   ma_close_handler = iMA(Symbol(),MA_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE,PRICE_CLOSE);
   ma_open_handler = iMA(Symbol(),MA_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE,PRICE_OPEN);

//--- Create dynamic instances of our custom types
   TradeTime        = new Time(Symbol(),MA_TIME_FRAME);
   TradeInformation = new TradeInfo(Symbol(),MA_TIME_FRAME);


//--- Safety checks
   if(ma_close_handler == INVALID_HANDLE)
      return(false);
   if(ma_open_handler == INVALID_HANDLE)
      return(false);

//--- Everything was fine
   return(INIT_SUCCEEDED);
  }
//--- End of OnInit Scope

Si nuestro sistema deja de utilizarse, liberaremos los indicadores técnicos que cargamos y eliminaremos los objetos dinámicos que creamos durante el procedimiento de configuración.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete the indicators and dynamic objects
   IndicatorRelease(ma_close_handler);
   IndicatorRelease(ma_open_handler);

   delete TradeTime;
   delete TradeInformation;

  }
//--- End of Deinit Scope

Si nuestra terminal recibe nuevos niveles de precios, primero comprobaremos si se ha formado una nueva vela; en ese caso, actualizaremos nuestros indicadores técnicos y, a continuación, comprobaremos si tenemos alguna posición abierta para, si no hay ninguna abierta, buscar una señal de trading o esperar hasta el vencimiento antes de cerrar nuestra posición.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Check if a new daily candle has formed
   if(TradeTime.NewCandle())
     {

      //--- Update our technical indicators
      Update();

      //--- If we have no open positions
      if(PositionsTotal() == 0)
        {
         //--- Reset the position timer
         position_timer = 0;

         //--- Check for a trading signal
         CheckSignal();
        }

      //--- Otherwise
      else
        {
         //--- The position has reached maturity
         if(position_timer == HOLDING_PERIOD)
            Trade.PositionClose(Symbol());

         //--- Otherwise keep holding
         else
            position_timer++;
        }
     }
  }
//--- End of OnTick Scope

Nuestra función de actualización escribe las lecturas actuales del indicador en el búfer del indicador, en las matrices que hemos creado para ellas.

//+------------------------------------------------------------------+
//| Update our technical indicators                                  |
//+------------------------------------------------------------------+
void Update(void)
  {
//--- Call the CopyBuffer method to get updated indicator values
   CopyBuffer(ma_close_handler,0,0,1,ma_close);
   CopyBuffer(ma_open_handler,0,0,1,ma_open);
  }
//--- End of Update Scope

La función de verificación de señales busca las condiciones de negociación que definimos anteriormente; la media móvil de cierre debe estar por encima de la media móvil de apertura para que consideremos que la acción del precio es alcista.

//+------------------------------------------------------------------+
//| Check for a trading signal using our cross-over strategy         |
//+------------------------------------------------------------------+
void CheckSignal(void)
  {
//--- Long positions when the close moving average is above the open
   if(ma_close[0] > ma_open[0])
     {
      Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,"");
      return;
     }

//--- Otherwise short
   else
      if(ma_close[0] < ma_open[0])
        {
         Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,"");
         return;
        }

  }
//--- End of CheckSignal Scope

Por último, siempre anule la definición de las variables del sistema que haya creado al final de su programa.

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef MA_PERIOD
#undef MA_SHIFT
#undef MA_TIME_FRAME
#undef MA_TYPE
#undef HOLDING_PERIOD
//+------------------------------------------------------------------+

Establezcamos ahora un nivel de rendimiento básico para nuestra clase. 

Figura 1: Nuestra configuración de prueba inicial para nuestro nivel de referencia.

El siguiente paso es definir los puntos de inicio y fin de nuestra prueba retrospectiva. Seleccionaremos el intervalo de tiempo diario si desea seguir el ejemplo.

Figura 2: Las fechas de prueba que utilizaremos para el nivel de referencia.

La curva de capital, generada por la versión codificada de la estrategia, debe recuperarse de la clase que implementamos, siempre que configuremos ambas estrategias para que se ejecuten con los mismos parámetros. Vamos a comprobar ahora si hemos implementado la clase correctamente, sin errores. 

Figura 3: Visualización de la curva de capital establecida por nuestra estrategia de referencia.

Los detalles estadísticos precisos de ambas pruebas no coincidirán a la perfección. Recordemos que nuestra prueba retrospectiva simula latencia aleatoria y ruido sistemático. Por lo tanto, buscamos que ambos resultados sean muy similares entre sí, no idénticos.

Figura 4: Resultados detallados de la prueba retrospectiva que realizamos utilizando nuestra versión codificada de la estrategia de cruce de medias móviles.

Las constantes del sistema que definimos se utilizarán tal cual en esta versión de nuestra prueba de clase para mantener la coherencia.

//+------------------------------------------------------------------+
//|                                                   MSA Test 1.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Define system constants                                          |
//+------------------------------------------------------------------+
#define MA_TYPE        MODE_EMA
#define MA_PERIOD      10
#define MA_TIME_FRAME  PERIOD_D1
#define MA_SHIFT       0
#define HOLDING_PERIOD 5

A continuación, cargaremos nuestras dependencias.

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <VolatilityDoctor\Time\Time.mqh>
#include <VolatilityDoctor\Trade\TradeInfo.mqh>
#include <VolatilityDoctor\Strategies\OpenCloseMACrossover.mqh>

Necesitaremos crear una nueva variable global para nuestra instancia de estrategia.

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+

//--- Custom Types
CTrade               Trade;
TradeInfo            *TradeInformation;
Time                 *TradeTime;
OpenCloseMACrossover *MACross;

//--- System Types
int    position_timer;

En su mayor parte, la mayor parte de la aplicación permanece sin cambios. Estamos aislando los efectos de las señales que produce nuestra clase. Por lo tanto, la mayor parte de este código debería resultarle familiar al lector gracias a la primera prueba que implementamos.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Create dynamic instances of our custom types
   TradeTime        = new Time(Symbol(),MA_TIME_FRAME);
   TradeInformation = new TradeInfo(Symbol(),MA_TIME_FRAME);
   MACross          = new OpenCloseMACrossover(Symbol(),MA_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE);

//--- Everything was fine
   return(INIT_SUCCEEDED);
  }
//--- End of OnInit Scope

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete the dynamic objects
   delete TradeTime;
   delete TradeInformation;
   delete MACross;
  }
//--- End of Deinit Scope

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Check if a new daily candle has formed
   if(TradeTime.NewCandle())
     {
      //--- Update strategy
      Update();

      //--- If we have no open positions
      if(PositionsTotal() == 0)
        {
         //--- Reset the position timer
         position_timer = 0;

         //--- Check for a trading signal
         CheckSignal();
        }

      //--- Otherwise
      else
        {
         //--- The position has reached maturity
         if(position_timer == HOLDING_PERIOD)
            Trade.PositionClose(Symbol());

         //--- Otherwise keep holding
         else
            position_timer++;
        }
     }
  }
//--- End of OnTick Scope

Los métodos que definimos para actualizar los parámetros de nuestra estrategia y detectar señales de trading solo cambiaron en el sentido de que ahora recurren a la clase que creamos y ya no implementan los resultados desde cero.

//+------------------------------------------------------------------+
//| Update our technical indicators                                  |
//+------------------------------------------------------------------+
void Update(void)
  {
//--- Update the strategy
   MACross.Update();
  }
//--- End of Update Scope

//+------------------------------------------------------------------+
//| Check for a trading signal using our cross-over strategy         |
//+------------------------------------------------------------------+
void CheckSignal(void)
  {
//--- Long positions when the close moving average is above the open
   if(MACross.BuySignal())
     {
      Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,"");
      return;
     }

//--- Otherwise short
   else
      if(MACross.SellSignal())
        {
         Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,"");
         return;
        }
  }
//--- End of CheckSignal Scope

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef MA_PERIOD
#undef MA_SHIFT
#undef MA_TIME_FRAME
#undef MA_TYPE
#undef HOLDING_PERIOD
//+------------------------------------------------------------------+

Ya estamos listos para comenzar a probar nuestra clase de estrategia de cruce de MA MQL5. Primero cargaremos el Asesor Experto que ejecuta la clase y configuraremos las mismas fechas de prueba retrospectiva que utilizamos para la primera prueba.

Figura 5: Las fechas iniciales que utilizaremos al evaluar nuestra estrategia; estas fechas se verán afectadas por nuestra futura prueba prospectiva.

Asegúrese de que la configuración que ha seleccionado coincida con la configuración inicial que utilizamos.

Figura 6: Seleccione "Ticks reales" y "Retraso aleatorio" para realizar pruebas retrospectivas robustas.

Los resultados detallados de las pruebas realizadas con las estrategias de clase y las estrategias predefinidas son casi idénticos; ambas estrategias realizaron 127 operaciones, con la misma relación de compraventa y obtuvieron índices de Sharpe muy similares.

Figura 7: Las estadísticas detalladas producidas por nuestra clase de estrategia coinciden con los resultados obtenidos a partir de la estrategia de negociación codificada.

La curva de capital, elaborada por la clase, se asemeja mucho a la Figura 3. Por lo tanto, las dos estrategias coinciden tal como se esperaba.

Figura 8: La curva de capital generada por la clase coincide con la estrategia predefinida.

Ahora podemos empezar a buscar los parámetros óptimos de la estrategia, ya que hemos verificado que la clase se ha implementado correctamente. 

Primero, tendremos que cambiar la mayoría de las constantes del sistema por entradas del usuario. Esto permite que nuestro optimizador genético ajuste la estrategia por nosotros. Por lo tanto, solo tenemos una única definición de sistema.

//+------------------------------------------------------------------+
//|                                                   MSA Test 1.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define MA_SHIFT 0

//+------------------------------------------------------------------+
//| User Inputs                                                      |
//+------------------------------------------------------------------+
input   group          "Strategy Parameters"
input   int             MA_PERIOD      =        10;//Moving Average Period
input   int             HOLDING_PERIOD =         5;//Position Holding Period
input   ENUM_TIMEFRAMES MA_TIME_FRAME  = PERIOD_D1;//Moving Average Time Frame
input   ENUM_MA_METHOD  MA_TYPE        =  MODE_EMA;//Moving Average Type

El resto de nuestro sistema permanece prácticamente sin cambios, así que comencemos ahora a analizar la diferencia entre una prueba retrospectiva y una prueba prospectiva en MetaTrader 5.

Una prueba retrospectiva, sencillamente, ejecuta una estrategia de negociación sobre datos históricos. Podemos sacar más partido a nuestros datos, más allá de las simples pruebas históricas. Al dividir los datos, podemos usar una fracción de ellos para buscar parámetros de estrategia y luego validar los parámetros encontrados con la fracción restante de los datos.

Esta es la ventaja de las pruebas prospectivas. No solo buscamos una buena configuración, sino que también intentamos averiguar cuán estable es dicha configuración.

Por lo tanto, configure el parámetro "Forward" en "1/2" para usar el 50% de sus datos para el entrenamiento y el resto para las pruebas.

Figura 9: Establezca el campo "Adelante" en 1/2 para usar la mitad de los datos para el entrenamiento y la otra mitad para las pruebas.

Existen diferentes estrategias de optimización que nos han sido ofrecidas en nuestra terminal MetaTrader 5. Seleccionaremos el "Algoritmo rápido basado en genética" porque no exige demasiado a nuestro sistema, pero aun así proporciona resultados fiables.

Figura 10: Necesitamos un optimizador para generar nuevos parámetros de estrategia después de cada prueba.

Una vez que comience la prueba, observará un diagrama de dispersión de los resultados, similar a la Figura 11 que se muestra a continuación. El gráfico nos ayuda a visualizar qué tan bien nos desempeñamos en cada iteración de entrenamiento. El optimizador solo ve los resultados obtenidos en la primera mitad de los datos y los utiliza para aprender mejores parámetros que probar en la siguiente iteración.

Figura 11: Configuramos el optimizador genético rápido para mejorar el índice de Sharpe de nuestra estrategia. Cada punto representa los resultados de la iteración de la prueba.

Podemos hacer clic con el botón derecho en este diagrama de dispersión de resultados y manipularlo de muchas maneras. Podemos cambiar el eje que se representa en el gráfico, o incluso convertirlo en una representación tridimensional de nuestros resultados.

Figura 12: El menú contextual nos permite cambiar el eje del gráfico y explorar diferentes relaciones.

Visualizar los datos, en formatos alternativos, puede ayudarnos a observar los fenómenos que se producen entre nuestra estrategia y el mercado que hemos elegido. Por ejemplo, parece que a medida que elegimos plazos más amplios, nuestra estrategia se vuelve más rentable.

Figura 13: Podemos observar que los marcos temporales más amplios nos ayudan a obtener mejores índices de Sharpe mediante la creación de un gráfico de barras 3D.

El probador de estrategias también le proporcionará resultados detallados obtenidos a través de cada combinación de datos de entrada que haya probado. Observe que hay un panel en la parte inferior de la tabla que separa "Prueba retrospectiva" y "Prueba prospectiva". 

Figura 14: MetaTrader 5 también nos proporciona un análisis detallado de todos los parámetros de la estrategia que intentó

Si hace clic con el botón derecho del ratón en esta tabla, se cargará un menú contextual que nos permitirá realizar muchas tareas útiles, como decidir qué columnas deben incluirse en la tabla o incluso exportar los resultados de Forward a un archivo.

Figura 15: Al cargar el menú contextual en la tabla de resultados, se nos muestra que podemos exportar nuestros resultados en formato XML para estudios posteriores.

Podemos analizar en detalle los resultados de las pruebas retrospectivas y prospectivas por separado. Los resultados de la prueba retrospectiva se muestran a continuación en la figura 16. Recordemos que nos interesan más los resultados prospectivos y cuánto se desviaron de los resultados de la prueba retrospectiva.

Figura 16: Resultados estadísticos detallados de la prueba retrospectiva, obtenidos por nuestro optimizador genético.

Los resultados de la prueba directa se parecen mucho a los de la prueba retrospectiva. Este es un buen indicador de estabilidad potencial. De lo contrario, si sus mejores resultados en las pruebas directas no coinciden con sus resultados en las pruebas retrospectivas, entonces la estrategia está demostrando cualidades inestables.

Figura 17: Estos son los resultados que nos interesan particularmente, los resultados directos.

También se nos proporciona la curva de capital generada por ambas pruebas. La larga línea vertical en el centro marca la separación entre la prueba retrospectiva y prospectiva.

Figura 18: Obsérvese que tanto la curva de capital obtenida durante el entrenamiento como en la prueba prospectiva presentan tendencias positivas.

Por último, estas son las configuraciones de estrategia óptimas que nuestro optimizador genético nos ayudó a encontrar hoy.

Figura 19: Los mejores ajustes obtenidos por nuestra búsqueda mediante algoritmo genético


Conclusión

Este artículo ha demostrado el valor del probador de estrategias de MetaTrader 5. Los lectores que no tengan previsto diseñar clases aún pueden obtener casos de uso prácticos para integrar más principios de Programación Orientada a Objetos (POO) incorporados en MQL5 en su proceso de desarrollo. Este artículo también anima a los lectores a adoptar buenas prácticas de desarrollo para crear y probar clases fiables.

Finalmente, al utilizar los principios de diseño de la programación orientada a objetos que hemos destacado, los lectores pueden construir sus estrategias de forma fiable y probarlas fácilmente para obtener entradas óptimas en diferentes símbolos y marcos temporales. Únete a nuestra próxima charla, donde combinaremos nuestras estrategias de RSI y medias móviles. 

Nombre del archivo Descripción del archivo
MSA Test 1 Baseline.mq5 La implementación codificada de nuestra estrategia de cruce que utilizamos como resultado de la prueba debería ser emulada por nuestra clase.
MSA Test 1 Class.mq5 El archivo que prueba nuestra clase de estrategia de media móvil.
MSA Test 1.mq5 Utilizamos este asesor experto para buscar buenos parámetros de estrategia mediante el probador de estrategias de MetaTrader 5.
OpenCloseMACrossover.mqh La clase que implementa nuestra estrategia de promedio móvil.
Strategy.mqh La clase base para todas nuestras estrategias.
MSA Test 1.ex5 Una versión compilada de nuestro Asesor Experto.

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

Archivos adjuntos |
MSA_Test_1.mq5 (5.28 KB)
Strategy.mqh (3.98 KB)
MSA_Test_1.ex5 (44.16 KB)
Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 7): Trading con múltiples períodos simultáneamente Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 7): Trading con múltiples períodos simultáneamente
En esta serie de artículos, hemos analizado diversas formas de determinar cuál es el mejor período para utilizar nuestros indicadores técnicos. Hoy, demostraremos al lector cómo puede aplicar la lógica opuesta; es decir, en lugar de elegir un único período óptimo, le mostraremos cómo emplear todos los períodos disponibles de forma eficaz. Este enfoque reduce la cantidad de datos descartados y ofrece casos de uso alternativos para los algoritmos de aprendizaje automático, más allá de la predicción habitual de precios.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Introducción a MQL5 (Parte 22): Creación de un Asesor Experto para el patrón armónico 5-0 Introducción a MQL5 (Parte 22): Creación de un Asesor Experto para el patrón armónico 5-0
Este artículo explica cómo detectar y operar con el patrón armónico 5-0 en MQL5, validarlo utilizando niveles de Fibonacci y mostrarlo en el gráfico.