English Русский 中文 Deutsch 日本語
preview
Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 6): Reglas de negociación autoadaptativas (II)

Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 6): Reglas de negociación autoadaptativas (II)

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

En nuestro último artículo sobre reglas de negociación autoadaptativas, enlazado aquí, analizamos los problemas a los que se enfrenta un operador algorítmico que intenta seguir las mejores prácticas sobre cómo utilizar el indicador RSI.

Hemos descubierto que los resultados estandarizados no siempre son generados por el indicador, sino que dependen de varios factores, como el período, el marco temporal y también el mercado concreto en cuestión.

Para resolver este problema, postulamos que los operadores algorítmicos podrían estudiar el rango real del indicador, de modo que puedan reajustar el punto medio del indicador al centro de su rango observado, y no a su rango total posible. Al hacerlo, obtenemos ciertas garantías sobre la generación de señales de trading que no podemos obtener con las reglas tradicionales del RSI. Obtuvimos un mayor control sobre la nueva señal registrando una desviación media desde el punto medio y registrando únicamente las señales generadas por múltiplos de la desviación media.

Ahora avanzaremos más allá de nuestro intento inicial de crear una solución práctica. Hay varias mejoras que podemos hacer con respecto a nuestro último intento. La mejora integral que buscamos es la capacidad de intentar estimar el valor de los niveles de RSI que hemos elegido. 

En nuestra última discusión, simplemente asumimos que las desviaciones significativamente mayores que la desviación media podrían tender a ser más rentables. Sin embargo, no intentamos comprobar si esto era cierto. No hemos intentado cuantificar el valor de los nuevos niveles que proponemos ni compararlos con el valor de los niveles tradicionales, 70 y 30.

Además, en nuestra última discusión se consideró el caso en el que el período del RSI era fijo. Esta simplificación hizo que nuestro marco fuera más fácil de entender. Hoy centramos nuestra atención en el extremo opuesto del problema, cuando el usuario no está seguro del período adecuado que debe utilizar.



Visualizando el problema

En caso de que tengamos nuevos lectores, en la figura 1 a continuación, hemos adjuntado una captura de pantalla del gráfico diario del EURUSD con un RSI de 2 períodos. 

Figura 1

Figura 1: Visualizando nuestro RSI, la calidad de las señales generadas por un período corto.

Debajo de la figura 1, hemos aplicado un RSI de 70 períodos a la misma sección del gráfico de la figura 2. Podemos observar que la señal del RSI se está convirtiendo lentamente en una línea plana centrada en el nivel 50 del RSI. ¿Qué comentarios podríamos hacer al comparar las 2 imágenes? Bueno, un comentario que vale la pena mencionar es que, durante el período del EURUSD capturado en ambas cifras, el tipo de cambio simplemente cayó, desde el nivel de precio de 1,121 el 18 de septiembre de 2024 a mínimos de 1,051 el 2 de diciembre de 2024. Sin embargo, el RSI de 2 períodos cambió de nivel con demasiada frecuencia durante el mismo tiempo, y el RSI de 70 períodos no cambió de nivel en absoluto.

¿Significa esto que los traders deberían estar limitados para siempre a utilizar únicamente un rango estrecho de períodos cuando emplean el RSI? ¿Qué hace falta para que diseñemos algoritmos que seleccionen automáticamente un buen período RSI, sin intervención humana? Además, ¿cómo podemos escribir algoritmos que nos ayuden a encontrar buenos niveles comerciales, sin importar en qué período comencemos inicialmente?

Figura 2

Figura 2: Visualización de nuestro indicador RSI operando con un periodo largo.


Introducción a MQL5

Hay muchas maneras de abordar este problema. Podríamos usar bibliotecas en Python para generar lecturas RSI con diferentes períodos y optimizar nuestro período y niveles RSI de esa manera. Sin embargo, esto introduce posibles inconvenientes. La mayor limitación podría provenir potencialmente de ligeras diferencias en el cálculo que se implementa para calcular los valores de estos indicadores técnicos. 

Para evitar esto, implementaremos nuestra solución en MQL5. Al crear una clase RSI, podemos registrar rápidamente múltiples instancias de valores RSI en un archivo CSV y usar estos valores para realizar nuestro análisis numérico en Python para estimar un período RSI óptimo y niveles alternativos para usar además de 70 y 30.

Comenzaremos creando un script que nos permita recuperar manualmente los valores RSI y calcular el cambio en los niveles RSI. Luego, crearemos una clase que encapsule la funcionalidad que necesitamos. Queremos crear una cuadrícula de lecturas RSI con períodos que se incrementen en pasos de 5, desde 5 hasta 70. Pero antes de que podamos lograr esto, necesitamos implementar y probar nuestra clase. 

Construir la clase en un script nos permitirá probar rápidamente la salida de la clase, comparándola con la salida estándar obtenida manualmente del indicador. Si hemos especificado bien la clase, la salida generada por ambos métodos debería ser la misma. Esto nos dará una clase útil para generar 14 indicadores RSI con diferentes períodos, mientras realizamos un seguimiento del cambio en cada instancia del RSI, sobre cualquier otro símbolo que deseemos negociar.

Dado el uso que queremos hacer de esta clase RSI, tiene sentido empezar asegurándonos de que la clase tenga un mecanismo que nos impida intentar leer el valor del indicador antes de haber configurado el buffer en consecuencia. Comenzaremos construyendo esta parte de nuestra clase primero. Necesitamos declarar miembros privados de nuestra clase. Estas banderas booleanas privadas nos impedirán leer valores RSI antes de copiarlos del buffer del indicador.

//+------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2020, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs

//--- The RSI class will manage our indicator settings and provide useful transformations we need
class RSI
  {
   //--- Private members
private:
   //--- Have the indicator values been copied to the buffer?
   bool              indicator_values_initialized;
   bool              indicator_differenced_values_initialized;

También he incluido un método que devuelve cadenas para preguntar al usuario qué está sucediendo dentro del objeto y cómo resolver los problemas. El método toma un parámetro entero que informa dónde en el código se generó el error. De esta forma las soluciones suelen ser fáciles de sugerir como mensaje impreso en la terminal.

//--- Give the user feedback
string            user_feedback(int flag)
  {
   string message;

   //--- Check if the RSI indicator loaded correctly
   if(flag == 0)
     {
      //--- Check the indicator was loaded correctly
      if(IsValid())
         message = "RSI Indicator Class Loaded Correcrtly \n";
      return(message);
      //--- Something went wrong
      message = "Error loading RSI Indicator: [ERROR] " + (string) GetLastError();
      return(message);
     }

   //--- User tried getting indicator values before setting them
   if(flag == 1)
     {
      message = "Please set the indicator values before trying to fetch them from memory";
      return(message);
     }

   //--- We sueccessfully set our differenced indicator values
   if(flag == 2)
     {
      message = "Succesfully set differenced indicator values.";
      return(message);
     }

   //--- Failed  to set our differenced indicator values
   if(flag == 3)
     {
      message = "Failed to set our differenced indicator values: [ERROR] " + (string) GetLastError();
      return(message);
     }

   //--- The user is trying to retrieve an index beyond the buffer size and must update the buffer size first
   if(flag == 4)
     {
      message = "The user is attempting to use call an index beyond the buffer size, update the buffer size first";
      return(message);
     }

   //--- No feedback
   else
      return("");
  }

Ahora definiremos los miembros protegidos de nuestra clase. Estos miembros constituirán las partes móviles necesarias para inicializar una instancia de la clase iRSI() e interactuar con el búfer indicador.

   //--- Protected members
protected:
   //--- The handler for our RSI
   int               rsi_handler;
   //--- The Symbol our RSI should be applied on
   string            rsi_symbol;
   //--- Our RSI period
   int               rsi_period;
   //--- How far into the future we wish to forecast
   int               forecast_horizon;
   //--- The buffer for our RSI indicator
   double            rsi_reading[];
   vector            rsi_differenced_values;
   //--- The current size of the buffer the user last requested
   int               rsi_buffer_size;
   int               rsi_differenced_buffer_size;
   //--- The time frame our RSI should be applied on
   ENUM_TIMEFRAMES   rsi_time_frame;
   //--- The price should the RSI be applied on
   ENUM_APPLIED_PRICE rsi_price;

Pasando a los miembros de la clase pública. La primera función que necesitaremos es la que nos informa si nuestro manejador de indicadores es válido. Si nuestro controlador de indicadores no está configurado correctamente, podemos informar al usuario de inmediato.

//--- Now, we can define public members:
public:

   //--- Check if our indicator handler is valid
   bool              IsValid(void)
     {
      return((this.rsi_handler != INVALID_HANDLE));
     }

Nuestro constructor predeterminado creará un objeto iRSI con el EURUSD en el gráfico diario, durante un período de 5 días. Para garantizar que esta sea la opción deseada por el usuario, nuestra clase imprime el mercado y el período con el que trabaja. Además, el constructor predeterminado imprime específicamente para el usuario que la instancia actual del objeto RSI fue construida por el constructor predeterminado.

//--- Our default constructor
void              RSI(void):
                  indicator_values_initialized(false),
                  rsi_symbol("EURUSD"),
                  rsi_time_frame(PERIOD_D1),
                  rsi_period(5),
                  rsi_price(PRICE_CLOSE),
                  rsi_handler(iRSI(rsi_symbol,rsi_time_frame,rsi_period,rsi_price))
  {
   //--- Give the user feedback on initilization
   Print(user_feedback(0));
   //--- Remind the user they called the default constructor
   Print("Default Constructor Called: ",__FUNCSIG__," ",&this);
  }

De lo contrario, esperamos que el usuario llame al constructor paramétrico del objeto RSI y especifique todos los parámetros necesarios.

//--- Parametric constructor
   void              RSI(string user_symbol,ENUM_TIMEFRAMES user_time_frame,int user_period,ENUM_APPLIED_PRICE user_price)
     {
      indicator_values_initialized = false;
      rsi_symbol                   = user_symbol;
      rsi_time_frame               = user_time_frame;
      rsi_period                   = user_period;
      rsi_price                    = user_price;
      rsi_handler                  = iRSI(rsi_symbol,rsi_time_frame,rsi_period,rsi_price);
      //--- Give the user feedback on initilization
      Print(user_feedback(0));
     }

También necesitaremos un destructor para liberar los recursos del sistema que ya no necesitamos, lo que nos permitirá limpiar lo que dejamos de hacer.

//--- Destructor
void             ~RSI(void)
  {
   //--- Free up resources we don't need and reset our flags
   if(IndicatorRelease(rsi_handler))
     {
      indicator_differenced_values_initialized = false;
      indicator_values_initialized = false;
      Print("RSI System logging off");
     }
  }

Ahora, los métodos necesarios para interactuar con el buffer indicador son el componente clave de nuestra clase. Pedimos al usuario que especifique cuántos valores deben copiarse del buffer y si los valores deben organizarse como series. Luego realizamos pruebas para asegurarnos de que los valores RSI no nos devuelvan nulos, antes de finalizar la llamada al método.

//--- Copy readings for our RSI indicator
bool              SetIndicatorValues(int buffer_size,bool set_as_series)
  {
   rsi_buffer_size = buffer_size;
   CopyBuffer(this.rsi_handler,0,0,buffer_size,rsi_reading);
   if(set_as_series)
      ArraySetAsSeries(this.rsi_reading,true);
   indicator_values_initialized = true;

   //--- Did something go wrong?
   vector rsi_test;
   rsi_test.CopyIndicatorBuffer(rsi_handler,0,0,buffer_size);
   if(rsi_test.Sum() == 0)
      return(false);

   //--- Everything went fine.
   return(true);
  }

Una función simple para obtener la lectura RSI actual.

//--- Get the current RSI reading
double            GetCurrentReading(void)
  {
   double temp[];
   CopyBuffer(this.rsi_handler,0,0,1,temp);
   return(temp[0]);
  }

También es posible que necesitemos obtener valores en un índice específico, no solo el valor más reciente. Esta función, GetReadingAt(), nos proporciona esta utilidad. La función primero verifica que no estemos intentando ir más allá del tamaño del buffer que copiamos del indicador. Si se utiliza correctamente, la función devolverá la lectura del indicador en el índice especificado. De lo contrario, aparecerá un mensaje de error.

//--- Get a specific RSI reading
double            GetReadingAt(int index)
  {
   //--- Is the user trying to call indexes beyond the buffer?
   if(index > rsi_buffer_size)
     {
      Print(user_feedback(4));
      return(-1e10);
     }

   //--- Get the reading at the specified index
   if((indicator_values_initialized) && (index < rsi_buffer_size))
      return(rsi_reading[index]);

   //--- User is trying to get values that were not set prior
   else
    {
      Print(user_feedback(1));
      return(-1e10);
     }
  }

Nuestro interés también radica en el cambio en los valores del RSI. No nos basta con tener acceso a la lectura actual del RSI. También queremos tener acceso al cambio en los niveles de RSI que ocurre en cualquier tamaño de ventana arbitrario que especifiquemos. Como antes, simplemente llamamos a las funciones CopyBuffer para el usuario detrás de escena para calcular el crecimiento en los niveles de RSI, pero la clase también incluye una verificación adicional para asegurarnos de que la salida del cálculo no sea un vector de 0 antes de devolver la respuesta que encontró al usuario.

//--- Let's set the conditions for our differenced data
 bool              SetDifferencedIndicatorValues(int buffer_size,int differencing_period,bool set_as_series)
   {
    //--- Internal variables
    rsi_differenced_buffer_size = buffer_size;
    rsi_differenced_values = vector::Zeros(rsi_differenced_buffer_size);

    //--- Prepare to record the differences in our RSI readings
    double temp_buffer[];
    int fetch = (rsi_differenced_buffer_size + (2 * differencing_period));
    CopyBuffer(rsi_handler,0,0,fetch,temp_buffer);
    if(set_as_series)
       ArraySetAsSeries(temp_buffer,true);

    //--- Fill in our values iteratively
    for(int i = rsi_differenced_buffer_size;i > 1; i--)
      {
       rsi_differenced_values[i-1] = temp_buffer[i-1] - temp_buffer[i-1+differencing_period];
      }

    //--- If the norm of a vector is 0, the vector is empty!
    if(rsi_differenced_values.Norm(VECTOR_NORM_P) != 0)
      {
       Print(user_feedback(2));
       indicator_differenced_values_initialized = true;
       return(true);
      }

    indicator_differenced_values_initialized = false;
    Print(user_feedback(3));
    return(false);
   }

Por último, necesitamos un método para obtener el valor RSI diferenciado en un valor de índice específico. Nuevamente, nuestra función se asegura de que el usuario no intente llamar más allá del rango del búfer copiado. En tal caso, el usuario primero debe actualizar el tamaño del búfer y luego copiar el valor de índice deseado según corresponda.

//--- Get a differenced value at a specific index
double            GetDifferencedReadingAt(int index)
  {
   //--- Make sure we're not trying to call values beyond our index
   if(index > rsi_differenced_buffer_size)
     {
      Print(user_feedback(4));
      return(-1e10);
     }

   //--- Make sure our values have been set
   if(!indicator_differenced_values_initialized)
     {

      //--- The user is trying to use values before they were set in memory
      Print(user_feedback(1));
      return(-1e10);
     }

   //--- Return the differenced value of our indicator at a specific index
   if((indicator_differenced_values_initialized) && (index < rsi_differenced_buffer_size))
      return(rsi_differenced_values[index]);

   //--- Something went wrong.
   return(-1e10);
  }
};

Desarrollar el resto de nuestra prueba es sencillo. Controlaremos manualmente una instancia similar del indicador RSI, inicializada con la misma configuración. Si escribimos ambas lecturas en el mismo archivo, deberíamos observar información duplicada. De lo contrario, habríamos cometido un error en nuestra implementación de la clase.

//--- How far we want to forecast
#define HORIZON 10

//--- Our handlers for our indicators
int rsi_5_handle;

//--- Data structures to store the readings from our indicators
double rsi_5_reading[];

//--- File name
string file_name = Symbol() + " Testing RSI Class.csv";

//--- Amount of data requested
input int size = 3000;

Para el resto de nuestro script, solo necesitamos inicializar nuestra clase RSI y configurarla con los mismos parámetros que usaremos con una versión duplicada pero controlada manualmente del RSI.

//+------------------------------------------------------------------+
//| Our script execution                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Testing the RSI Class
//--- Initialize the class
   RSI my_rsi(Symbol(),PERIOD_CURRENT,5,PRICE_CLOSE);
   my_rsi.SetIndicatorValues(size,true);
   my_rsi.SetDifferencedIndicatorValues(size,10,true);

//---Setup our technical indicators
   rsi_5_handle  = iRSI(Symbol(),PERIOD_CURRENT,5,PRICE_CLOSE);
   int fetch = size + (2 * HORIZON);

//---Set the values as series
   CopyBuffer(rsi_5_handle,0,0,fetch,rsi_5_reading);
   ArraySetAsSeries(rsi_5_reading,true);

//---Write to file
   int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");

   for(int i=size;i>=1;i--)
     {
      if(i == size)
        {
         FileWrite(file_handle,"Time","RSI 5","RSI 5 Class","RSI 5 Difference","RSI 5 Class Difference");
        }

      else
        {
         FileWrite(file_handle,
                   iTime(_Symbol,PERIOD_CURRENT,i),
                   rsi_5_reading[i],
                   my_rsi.GetReadingAt(i),
                   rsi_5_reading[i]  - rsi_5_reading[i + HORIZON],
                   my_rsi.GetDifferencedReadingAt(i)
                  );
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+
#undef  HORIZON

Si nuestra clase se ha implementado correctamente, nuestro script de prueba producirá un archivo titulado "EURUSD Testing RSI Class" que contendrá lecturas RSI duplicadas. Como puede ver en la figura 3, nuestra clase RSI pasó nuestra prueba. Esta clase nos ahorra tiempo, ya que no tenemos que implementar los mismos métodos varias veces en muchos proyectos. Podemos simplemente importar nuestra clase RSI y llamar a los métodos que necesitemos.

Figura 3: Nuestra clase RSI ha pasado nuestra prueba, su uso es idéntico a trabajar manualmente con la clase indicadora RSI.

Ahora que estamos seguros de nuestra implementación de la clase, escribamos la clase en un archivo de inclusión dedicado, de modo que podamos compartirla con nuestro Asesor Experto y cualquier otro ejercicio que realicemos en el futuro que pueda requerir el mismo conjunto de funcionalidades. Una vez completamente compuesta, nuestra clase se ve así en su forma actual.

//+------------------------------------------------------------------+
//|                                                          RSI.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"

//+------------------------------------------------------------------+
//| This class will provide us with usefull functionality            |
//+------------------------------------------------------------------+
class RSI
  {
private:
   //--- Have the indicator values been copied to the buffer?
   bool              indicator_values_initialized;
   bool              indicator_differenced_values_initialized;

   //--- Give the user feedback
   string            user_feedback(int flag);

protected:
   //--- The handler for our RSI
   int               rsi_handler;
   //--- The Symbol our RSI should be applied on
   string            rsi_symbol;
   //--- Our RSI period
   int               rsi_period;
   //--- How far into the future we wish to forecast
   int               forecast_horizon;
   //--- The buffer for our RSI indicator
   double            rsi_reading[];
   vector            rsi_differenced_values;
   //--- The current size of the buffer the user last requested
   int               rsi_buffer_size;
   int               rsi_differenced_buffer_size;
   //--- The time frame our RSI should be applied on
   ENUM_TIMEFRAMES   rsi_time_frame;
   //--- The price should the RSI be applied on
   ENUM_APPLIED_PRICE rsi_price;

public:
                     RSI();
                     RSI(string user_symbol,ENUM_TIMEFRAMES user_time_frame,int user_period,ENUM_APPLIED_PRICE user_price);
                    ~RSI();
   bool              SetIndicatorValues(int buffer_size,bool set_as_series);
   bool              IsValid(void);
   double            GetCurrentReading(void);
   double            GetReadingAt(int index);
   bool              SetDifferencedIndicatorValues(int buffer_size,int differencing_period,bool set_as_series);
   double            GetDifferencedReadingAt(int index);
  };
//+------------------------------------------------------------------+
//| Our default constructor for our RSI class                        |
//+------------------------------------------------------------------+
void RSI::RSI()
  {
   indicator_values_initialized = false;
   rsi_symbol                   = "EURUSD";
   rsi_time_frame               = PERIOD_D1;
   rsi_period                   = 5;
   rsi_price                    = PRICE_CLOSE;
   rsi_handler                  = iRSI(rsi_symbol,rsi_time_frame,rsi_period,rsi_price);
//--- Give the user feedback on initilization
   Print(user_feedback(0));
//--- Remind the user they called the default constructor
   Print("Default Constructor Called: ",__FUNCSIG__," ",&this);
  }

//+------------------------------------------------------------------+
//| Our parametric constructor for our RSI class                     |
//+------------------------------------------------------------------+
void RSI::RSI(string user_symbol,ENUM_TIMEFRAMES user_time_frame,int user_period,ENUM_APPLIED_PRICE user_price)
  {
   indicator_values_initialized = false;
   rsi_symbol                   = user_symbol;
   rsi_time_frame               = user_time_frame;
   rsi_period                   = user_period;
   rsi_price                    = user_price;
   rsi_handler                  = iRSI(rsi_symbol,rsi_time_frame,rsi_period,rsi_price);
//--- Give the user feedback on initilization
   Print(user_feedback(0));
  }

//+------------------------------------------------------------------+
//| Our destructor for our RSI class                                 |
//+------------------------------------------------------------------+
void RSI::~RSI()
  {
//--- Free up resources we don't need and reset our flags
   if(IndicatorRelease(rsi_handler))
     {
      indicator_differenced_values_initialized = false;
      indicator_values_initialized = false;
      Print(user_feedback(5));
     }
  }

//+------------------------------------------------------------------+
//| Get our current reading from the RSI indicator                   |
//+------------------------------------------------------------------+
double RSI::GetCurrentReading(void)
  {
   double temp[];
   CopyBuffer(this.rsi_handler,0,0,1,temp);
   return(temp[0]);
  }

//+------------------------------------------------------------------+
//| Set our indicator values and our buffer size                     |
//+------------------------------------------------------------------+
bool              RSI::SetIndicatorValues(int buffer_size,bool set_as_series)
  {
   rsi_buffer_size = buffer_size;
   CopyBuffer(this.rsi_handler,0,0,buffer_size,rsi_reading);
   if(set_as_series)
      ArraySetAsSeries(this.rsi_reading,true);
   indicator_values_initialized = true;

//--- Did something go wrong?
   vector rsi_test;
   rsi_test.CopyIndicatorBuffer(rsi_handler,0,0,buffer_size);
   if(rsi_test.Sum() == 0)
      return(false);

//--- Everything went fine.
   return(true);
  }
//+--------------------------------------------------------------+
//| Let's set the conditions for our differenced data            |
//+--------------------------------------------------------------+
bool              RSI::SetDifferencedIndicatorValues(int buffer_size,int differencing_period,bool set_as_series)
  {
//--- Internal variables
   rsi_differenced_buffer_size = buffer_size;
   rsi_differenced_values = vector::Zeros(rsi_differenced_buffer_size);

//--- Prepare to record the differences in our RSI readings
   double temp_buffer[];
   int fetch = (rsi_differenced_buffer_size + (2 * differencing_period));
   CopyBuffer(rsi_handler,0,0,fetch,temp_buffer);
   if(set_as_series)
      ArraySetAsSeries(temp_buffer,true);

//--- Fill in our values iteratively
   for(int i = rsi_differenced_buffer_size;i > 1; i--)
     {
      rsi_differenced_values[i-1] = temp_buffer[i-1] - temp_buffer[i-1+differencing_period];
     }

//--- If the norm of a vector is 0, the vector is empty!
   if(rsi_differenced_values.Norm(VECTOR_NORM_P) != 0)
     {
      Print(user_feedback(2));
      indicator_differenced_values_initialized = true;
      return(true);
     }

   indicator_differenced_values_initialized = false;
   Print(user_feedback(3));
   return(false);
  }

 //--- Get a differenced value at a specific index
   double            RSI::GetDifferencedReadingAt(int index)
     {
      //--- Make sure we're not trying to call values beyond our index
      if(index > rsi_differenced_buffer_size)
        {
         Print(user_feedback(4));
         return(-1e10);
        }

      //--- Make sure our values have been set
      if(!indicator_differenced_values_initialized)
        {

         //--- The user is trying to use values before they were set in memory
         Print(user_feedback(1));
         return(-1e10);
        }

      //--- Return the differenced value of our indicator at a specific index
      if((indicator_differenced_values_initialized) && (index < rsi_differenced_buffer_size))
         return(rsi_differenced_values[index]);

      //--- Something went wrong.
      return(-1e10);
     }

//+------------------------------------------------------------------+
//| Get a reading at a specific index from our RSI buffer            |
//+------------------------------------------------------------------+
double            RSI::GetReadingAt(int index)
  {
//--- Is the user trying to call indexes beyond the buffer?
   if(index > rsi_buffer_size)
     {
      Print(user_feedback(4));
      return(-1e10);
     }

//--- Get the reading at the specified index
   if((indicator_values_initialized) && (index < rsi_buffer_size))
      return(rsi_reading[index]);

//--- User is trying to get values that were not set prior
   else
     {
      Print(user_feedback(1));
      return(-1e10);
     }
  }

//+------------------------------------------------------------------+
//| Check if our indicator handler is valid                          |
//+------------------------------------------------------------------+
bool RSI::IsValid(void)
  {
   return((this.rsi_handler != INVALID_HANDLE));
  }

//+------------------------------------------------------------------+
//| Give the user feedback on the actions he is performing           |
//+------------------------------------------------------------------+
string RSI::user_feedback(int flag)
  {
   string message;

//--- Check if the RSI indicator loaded correctly
   if(flag == 0)
     {
      //--- Check the indicator was loaded correctly
      if(IsValid())
         message = "RSI Indicator Class Loaded Correcrtly \nSymbol: " + (string) rsi_symbol + "\nPeriod: " + (string) rsi_period;
      return(message);
      //--- Something went wrong
      message = "Error loading RSI Indicator: [ERROR] " + (string) GetLastError();
      return(message);
     }

//--- User tried getting indicator values before setting them
   if(flag == 1)
     {
      message = "Please set the indicator values before trying to fetch them from memory, call SetIndicatorValues()";
      return(message);
     }

//--- We sueccessfully set our differenced indicator values
   if(flag == 2)
     {
      message = "Succesfully set differenced indicator values.";
      return(message);
     }

//--- Failed  to set our differenced indicator values
   if(flag == 3)
     {
      message = "Failed to set our differenced indicator values: [ERROR] " + (string) GetLastError();
      return(message);
     }

//--- The user is trying to retrieve an index beyond the buffer size and must update the buffer size first
   if(flag == 4)
     {
      message = "The user is attempting to use call an index beyond the buffer size, update the buffer size first";
      return(message);
     }

//--- The class has been deactivated by the user
   if(flag == 5)
     {
      message = "Goodbye.";
      return(message);
     }

//--- No feedback
   else
      return("");
  }
//+------------------------------------------------------------------+

Ahora obtengamos los datos de mercado que necesitamos utilizando una colección de instancias de nuestra clase RSI. Almacenaremos punteros a cada instancia de nuestra clase en una matriz del tipo personalizado que hemos definido. MQL5 nos permite generar objetos automáticamente sobre la marcha cuando los necesitamos. Sin embargo, esta flexibilidad tiene el precio de tener que limpiar siempre lo que dejamos de hacer para evitar problemas relacionados con pérdidas de memoria.

//+------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2020, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define HORIZON 10

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include <VolatilityDoctor\Indicators\RSI.mqh>

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
RSI *my_rsi_array[14];
string file_name = Symbol() + " RSI Algorithmic Input Selection.csv";

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input int size = 3000;

//+------------------------------------------------------------------+
//| Our script execution                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- How much data should we store in our indicator buffer?
   int fetch = size + (2 * HORIZON);

//--- Store pointers to our RSI objects
   for(int i = 0; i <= 13; i++)
     {
      //--- Create an RSI object
      my_rsi_array[i] = new RSI(Symbol(),PERIOD_CURRENT,((i+1) * 5),PRICE_CLOSE);
      //--- Set the RSI buffers
      my_rsi_array[i].SetIndicatorValues(fetch,true);
      my_rsi_array[i].SetDifferencedIndicatorValues(fetch,HORIZON,true);
     }

//---Write to file
   int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");

   for(int i=size;i>=1;i--)
     {
      if(i == size)
        {
         FileWrite(file_handle,"Time","True Close","Open","High","Low","Close","RSI 5","RSI 10","RSI 15","RSI 20","RSI 25","RSI 30","RSI 35","RSI 40","RSI 45","RSI 50","RSI 55","RSI 60","RSI 65","RSI 70","Diff RSI 5","Diff RSI 10","Diff RSI 15","Diff RSI 20","Diff RSI 25","Diff RSI 30","Diff RSI 35","Diff RSI 40","Diff RSI 45","Diff RSI 50","Diff RSI 55","Diff RSI 60","Diff RSI 65","Diff RSI 70");
        }

      else
        {
         FileWrite(file_handle,
                   iTime(_Symbol,PERIOD_CURRENT,i),
                   iClose(_Symbol,PERIOD_CURRENT,i),
                   iOpen(_Symbol,PERIOD_CURRENT,i) - iOpen(Symbol(),PERIOD_CURRENT,i + HORIZON),
                   iHigh(_Symbol,PERIOD_CURRENT,i) - iHigh(Symbol(),PERIOD_CURRENT,i + HORIZON),
                   iLow(_Symbol,PERIOD_CURRENT,i) - iLow(Symbol(),PERIOD_CURRENT,i + HORIZON),
                   iClose(_Symbol,PERIOD_CURRENT,i) - iClose(Symbol(),PERIOD_CURRENT,i + HORIZON),
                   my_rsi_array[0].GetReadingAt(i),
                   my_rsi_array[1].GetReadingAt(i),
                   my_rsi_array[2].GetReadingAt(i),
                   my_rsi_array[3].GetReadingAt(i),
                   my_rsi_array[4].GetReadingAt(i),
                   my_rsi_array[5].GetReadingAt(i),
                   my_rsi_array[6].GetReadingAt(i),
                   my_rsi_array[7].GetReadingAt(i),
                   my_rsi_array[8].GetReadingAt(i),
                   my_rsi_array[9].GetReadingAt(i),
                   my_rsi_array[10].GetReadingAt(i),
                   my_rsi_array[11].GetReadingAt(i),
                   my_rsi_array[12].GetReadingAt(i),
                   my_rsi_array[13].GetReadingAt(i),
                   my_rsi_array[0].GetDifferencedReadingAt(i),
                   my_rsi_array[1].GetDifferencedReadingAt(i),
                   my_rsi_array[2].GetDifferencedReadingAt(i),
                   my_rsi_array[3].GetDifferencedReadingAt(i),
                   my_rsi_array[4].GetDifferencedReadingAt(i),
                   my_rsi_array[5].GetDifferencedReadingAt(i),
                   my_rsi_array[6].GetDifferencedReadingAt(i),
                   my_rsi_array[7].GetDifferencedReadingAt(i),
                   my_rsi_array[8].GetDifferencedReadingAt(i),
                   my_rsi_array[9].GetDifferencedReadingAt(i),
                   my_rsi_array[10].GetDifferencedReadingAt(i),
                   my_rsi_array[11].GetDifferencedReadingAt(i),
                   my_rsi_array[12].GetDifferencedReadingAt(i),
                   my_rsi_array[13].GetDifferencedReadingAt(i)
                  );
        }
     }
//--- Close the file
   FileClose(file_handle);

//--- Delete our RSI object pointers
   for(int i = 0; i <= 13; i++)
     {
      delete my_rsi_array[i];
     }
  }
//+------------------------------------------------------------------+
#undef HORIZON


Analizando los datos en Python

Ahora podemos comenzar a analizar los datos que hemos recopilado de nuestro terminal MetaTrader 5. Nuestro primer paso será cargar las bibliotecas estándar que necesitamos.

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import plotly

Ahora lea los datos del mercado. Además, creemos una bandera para indicar si el nivel de precio actual es mayor o menor que el precio que se ofreció hace 10 días. Recuerde que 10 días es el mismo período que usamos en nuestro script para calcular el cambio en los niveles de RSI.

#Let's read in our market data
data = pd.read_csv("EURUSD RSI Algorithmic Input Selection.csv")
data['Bull'] = np.NaN

data.loc[data['True Close'] > data['True Close'].shift(10),'Bull'] = 1
data.loc[data['True Close'] < data['True Close'].shift(10),'Bull'] = 0

data.dropna(inplace=True)
data.reset_index(inplace=True,drop=True)

También necesitamos calcular los rendimientos reales del mercado.

#Estimate the market returns
#Define our forecast horizon
HORIZON = 10
data['Target'] = 0

data['Return'] = data['True Close'].shift(-HORIZON) - data['True Close']
data.loc[data['Return'] > 0,'Target'] = 1

#Drop missing values
data.dropna(inplace=True)

Lo más importante es que debemos eliminar todos los datos que se superponen con el periodo de prueba retrospectiva elegido.

#No cheating boys.
_ = data.iloc[((-365 * 3) + 95):,:]
data = data.iloc[:((-365 * 3) + 95),:]
data

Figura 4: Nuestro conjunto de datos ya no contiene ninguna de las fechas que se superponen con nuestro período de prueba retrospectiva.

Podemos visualizar la distribución de los rendimientos del mercado de 10 días del EURUSD y podemos ver rápidamente que los rendimientos del mercado están fijados alrededor de 0. Esta forma de distribución general no es una sorpresa y no es exclusiva del par EURUSD.

plt.title('Distribution of EURUSD 10 Day Returns')
plt.grid()
sns.histplot(data['Return'],color='black')

Figura 5: Visualización de la distribución del retorno del EURUSD de 10 días.

Este ejercicio nos ofrece una oportunidad única de ver visualmente la diferencia entre la distribución de los niveles de RSI en un período corto y los niveles de RSI en un período largo. Las líneas rojas verticales discontinuas marcan los niveles estandarizados 30 y 70. Las barras negras marcan la distribución de los niveles del RSI cuando su período se establece en 5. Podemos ver que el RSI de 5 períodos generará muchas señales más allá de los niveles estandarizados. Sin embargo, las barras blancas representan la distribución de los niveles de RSI cuando el período se establece en 70. Podemos ver visualmente que casi no se generarán señales en absoluto. Es este cambio en la forma de la distribución lo que hace que sea un desafío para los traders algorítmicos seguir siempre las "mejores prácticas" para usar un indicador.

plt.title('Comapring The Distribution of RSI Changes Across Different RSI Periods')
sns.histplot(data['RSI 5'],color='black')
sns.histplot(data['RSI 70'],color='white')
plt.xlabel('RSI Level')
plt.legend(['RSI 5','RSI 70'])
plt.axvline(30,color='red',linestyle='--')
plt.axvline(70,color='red',linestyle='--')
plt.grid()

Figura 6: Comparación de la distribución de los niveles de RSI en diferentes períodos de RSI.

La creación de un gráfico de dispersión con el cambio de 10 períodos en el RSI de 60 períodos en los ejes x e y, nos permite visualizar si existe una relación entre el cambio en el indicador y el objetivo. Parece que un cambio de 10 niveles del RSI puede ser una señal comercial razonable para abrir posiciones cortas si la lectura del RSI cae 10 niveles. O entrar en posiciones largas si el nivel RSI aumenta en 10.

plt.title('Scatter Plot of 10 Day Change in 50 Period RSI & EURUSD 10 Day Return')
sns.scatterplot(data,y='Diff RSI 60',x='Diff RSI 60',hue='Target')
plt.xlabel('50 Period RSI')
plt.ylabel('50 Period RSI')
plt.grid()
plt.axvline(-10,color='red',linestyle='--')
plt.axvline(10,color='red',linestyle='--')

Figura 7: Visualización de la relación entre el cambio en el RSI de 60 períodos y el objetivo.

Intentar combinar las señales generadas por los cambios en el RSI con diferentes períodos puede parecer una idea bastante razonable. Sin embargo, parece que no contribuye a separar mejor nuestras dos clases de interés.

plt.title('Scatter Plot of 10 Day Change in 5 Period RSI & EURUSD 10 Day Return')
sns.scatterplot(data,y='Diff RSI 60',x='Diff RSI 5',hue='Target')
plt.xlabel('5 Period RSI')
plt.ylabel('5 Period RSI')
plt.grid()


Figura 8: Parece que el simple uso de indicadores RSI con diferentes períodos es una fuente deficiente de confirmación.

Tenemos 14 indicadores RSI diferentes para elegir. En lugar de realizar 14 pruebas retrospectivas para decidir qué periodo nos conviene más, podemos estimar el periodo óptimo evaluando el rendimiento de un modelo entrenado con los 14 indicadores RSI como entradas y, a continuación, evaluando la importancia de las características que el modelo estadístico ha aprendido de los datos con los que se ha entrenado. 

Tenga en cuenta que siempre tenemos la opción de emplear modelos estadísticos para lograr precisión en las predicciones o para obtener interpretaciones y conocimientos. Hoy realizaremos esto último. Ajustaremos un modelo Ridge a las diferencias en las 14 entradas del indicador RSI. El modelo Ridge tiene sus propios parámetros de ajuste. Por lo tanto, realizaremos una búsqueda de cuadrícula sobre el espacio de entrada para el modelo estadístico Ridge. En particular, ajustaremos los parámetros de ajuste:

  • Alfa: El modelo de cresta requiere que se agregue una penalización para controlar los coeficientes del modelo. 
  • Tolerancia: Determina el cambio más pequeño necesario para establecer las condiciones de detención u otras condiciones para las subrutinas dependiendo del solucionador que el usuario haya seleccionado.

Para nuestro análisis, utilizaremos un modelo de cresta con el solucionador «sparse_cg». El lector también puede considerar ajustar el modelo si así lo desea.

El modelo Ridge nos resulta especialmente útil porque reduce sus coeficientes a 0 para reducir la pérdida del modelo. Por lo tanto, buscaremos un amplio espacio de configuraciones iniciales para nuestro modelo y luego nos centraremos en las configuraciones iniciales que produjeron el menor error. La configuración de los pesos del modelo en su modo de mejor rendimiento puede informarnos rápidamente de qué período RSI depende más nuestro modelo. En nuestro ejemplo particular de hoy, fue el cambio de 10 períodos en el RSI de 55 períodos el que obtuvo los coeficientes más grandes de nuestro modelo de mejor desempeño.

#Set the max levels we wish to check
ALPHA_LEVELS = 10
TOL_LEVELS   = 10

#DataFrame labels
r_c = 'TOL_LEVEL_'
r_r = 'ALHPA_LEVEL_'

results_columns = []
results_rows = []

for c in range(TOL_LEVELS):
    n_c = r_c + str(c)
    n_r = r_r + str(c)
    results_columns.append(n_c)
    results_rows.append(n_r)

#Create a DataFrame to store our results
results = pd.DataFrame(columns=results_columns,index=results_rows)

#Cross validate our model
for i in range(TOL_LEVELS):
    tol = 10 ** (-i)
    error = []
    for j in range(ALPHA_LEVELS):
        #Set alpha
        alpha = 10 ** (-j)
        
        #Its good practice to generally check the 0 case
        if(i == 0 & j == 0):
            model = Ridge(alpha=j,tol=i,solver='sparse_cg')

        #Otherwise use a float
        model = Ridge(alpha=alpha,tol=tol,solver='sparse_cg')

        #Store the error levels
        error.append(np.mean(np.abs(cross_val_score(model,data.loc[:,['Diff RSI 5',
       'Diff RSI 10', 'Diff RSI 15', 'Diff RSI 20', 'Diff RSI 25',
       'Diff RSI 30', 'Diff RSI 35', 'Diff RSI 40', 'Diff RSI 45',
       'Diff RSI 50', 'Diff RSI 55', 'Diff RSI 60', 'Diff RSI 65',
       'Diff RSI 70',]],data['Return'],cv=tscv))))
    
    #Record the error levels
    results.iloc[:,i] = error
    
results
    

La siguiente tabla resume nuestros resultados. Observamos que nuestras tasas de error más bajas se obtuvieron cuando establecimos los parámetros iniciales de nuestro modelo en 0.

Parámetro de ajuste  Error del modelo
ALHPA_LEVEL_0     0.053509  
ALHPA_LEVEL_1     0.056245
ALHPA_LEVEL_2     0.060158
ALHPA_LEVEL_3     0.062230
ALHPA_LEVEL_4     0.061521
ALHPA_LEVEL_5     0.064312
ALHPA_LEVEL_6     0.073248
ALHPA_LEVEL_7     0.079310
ALHPA_LEVEL_8    0.081914
ALHPA_LEVEL_9     0.085171

También es posible visualizar nuestros hallazgos utilizando un gráfico de contorno. Queremos utilizar modelos en la región de la gráfica asociada con bajo error, las regiones azules. Éstos son nuestros modelos con mejor rendimiento hasta el momento. Visualicemos ahora el tamaño de cada coeficiente en el modelo. El coeficiente más grande se asignará a la entrada de la que más dependa nuestro modelo.

import plotly.graph_objects as go

fig = go.Figure(data =
    go.Contour(
        z=results,
        colorscale='bluered'
    ))

fig.update_layout(
    width = 600,
    height = 400,
    title='Contour Plot Of Our Error Forecasting EURUSD Using Grid Search '
)

fig.show()

Figura 9: Hemos encontrado configuraciones de entrada óptimas para nuestro modelo Ridge que predice el retorno del EURUSD en 10 días.

Al representar gráficamente los datos, podemos ver rápidamente que al coeficiente asociado con el RSI del período 55 se le asignó el valor absoluto más grande. Esto nos da cierta confianza para limitar nuestro enfoque a esa configuración RSI particular.

#Let's visualize the importance of each column
model = Ridge(alpha=0,tol=0,solver='sparse_cg')

model.fit(data.loc[:,['Diff RSI 5',
       'Diff RSI 10', 'Diff RSI 15', 'Diff RSI 20', 'Diff RSI 25',
       'Diff RSI 30', 'Diff RSI 35', 'Diff RSI 40', 'Diff RSI 45',
       'Diff RSI 50', 'Diff RSI 55', 'Diff RSI 60', 'Diff RSI 65',
       'Diff RSI 70',]],data['Return'])

#Clearly our model relied on the 25 Period RSI the most, from all the data it had available at training
sns.barplot(np.abs(model.coef_),color='black')
plt.title('Rleative Feature Importance')
plt.ylabel('Coefficient Value')
plt.xlabel('Coefficient Index')
plt.grid()

Figura 10: Al coeficiente asociado con la Diferencia en el RSI de 55 Periodos se le asignó el valor más grande.

Ahora que hemos identificado nuestro período de interés, evaluemos también cómo cambia el error de nuestro modelo a medida que pasamos por niveles crecientes de RSI de 10. Crearemos 3 columnas adicionales en nuestro marco de datos. La columna 1 tendrá el valor 1 si la lectura del RSI es mayor que el primer valor que queremos comprobar. De lo contrario, si la lectura del RSI es menor que algún valor particular que deseamos verificar, la columna 2 tendrá el valor 1. En cualquier otro caso, la columna 3 tendrá el valor 1.

def objective(x):
    data = pd.read_csv("EURUSD RSI Algorithmic Input Selection.csv")
    data['0'] = 0
    data['1'] = 0
    data['2'] = 0
    HORIZON = 10
    data['Return'] = data['True Close'].shift(-HORIZON) - data['True Close']
    data.dropna(subset=['Return'],inplace=True)
    data.iloc[data['Diff RSI 55'] > x[0],12] = 1
    data.iloc[data['Diff RSI 55'] < x[1],13] = 1
    data.iloc[(data['Diff RSI 55'] < x[0]) & (data['RSI 55'] > x[1]),14] = 1
    #Calculate or RMSE When using those levels
    model = Ridge(alpha=0,tol=0,solver='sparse_cg')
    error = np.mean(np.abs(cross_val_score(model,data.iloc[:,12:15],data['Return'],cv=tscv)))
    return(error)

Evaluemos el error que produce nuestro modelo si establecemos 0 como nuestro nivel crítico.

#Bad rules for using the RSI
objective([0,0])

0.026897725573317266

Si ingresamos los niveles 70 y 30 en nuestra función, nuestro error aumenta. Realizaremos una búsqueda en cuadrícula en pasos de 10, para encontrar cambios en los niveles de RSI que se adapten mejor al RSI de 55 períodos. Nuestros resultados encontraron que el nivel de cambio óptimo aparece cerca de 10 niveles de RSI.

#Bad rules for using the RSI
objective([70,30])

0.031258730612736006

LEVELS  = 10
results = []

for i in np.arange(0,(LEVELS)):
    results.append(objective([i * 10,-(i * 10)]))

plt.plot(results,color='black')
plt.ylabel('Error Rate')
plt.xlabel('Change in RSI as multiples of 10')
plt.grid()
plt.scatter(results.index(min(results)),min(results),color='red')
plt.title('Measuring The Strength of Changes In RSI Levels')

Figura 11: Visualización del nivel de cambio óptimo en nuestro indicador RSI.

Realicemos ahora otra búsqueda más fina, entre el intervalo de cambios del RSI en el rango 0 y 20. Al observar más de cerca, observamos que el verdadero óptimo parece estar en el valor 9. Sin embargo, no realizamos tales ejercicios para ajustar perfectamente los datos históricos, eso se llama sobreajuste y es una mala práctica. Nuestro objetivo no es realizar un ejercicio de ajuste de curvas. En lugar de tomar el valor óptimo exactamente donde aparece en nuestro análisis de datos históricos, aceptamos el hecho de que la ubicación de los óptimos puede cambiar y, en cambio, nuestro objetivo es estar dentro de una cierta fracción de una desviación estándar del valor óptimo en cada lado para que sirva como nuestros intervalos de confianza.

LEVELS  = 20
coef = 0.5
results = []

for i in np.arange(0,(LEVELS),1):
    results.append(objective([i,-(i)]))

plt.plot(results)
plt.ylabel('Error Rate')
plt.xlabel('Change in RSI')
plt.grid()
plt.scatter(results.index(min(results)),min(results),color='red')
plt.title('Measuring The Strength of Changes In RSI Levels')


plt.axvline(results.index(min(results)),color='red')
plt.axvline(results.index(min(results)) - (coef * np.std(data['Diff RSI 55'])),linestyle='--',color='red')
plt.axvline(results.index(min(results)) + (coef * np.std(data['Diff RSI 55'])),linestyle='--',color='red')

Figura 12: Visualización de la tasa de error asociada con la configuración de diferentes umbrales para cambios en el nivel de RSI.

Podemos ver visualmente la región que creemos que puede ser óptima superpuesta sobre la distribución histórica del cambio en los cambios del RSI.

sns.histplot(data['Diff RSI 55'],color='black')
coef = 0.5
plt.axvline((results.index(min(results))),linestyle='--',color='red')
plt.axvline(results.index(min(results)) - (coef * np.std(data['Diff RSI 55'])),color='red')
plt.axvline(results.index(min(results)) + (coef * np.std(data['Diff RSI 55'])),color='red')
plt.axvline(-(results.index(min(results))),linestyle='--',color='red')
plt.axvline(-(results.index(min(results)) - (coef * np.std(data['Diff RSI 55']))),color='red')
plt.axvline(-(results.index(min(results)) + (coef * np.std(data['Diff RSI 55']))),color='red')
plt.title("Visualizing our Optimal Point in The Distribution")

Figura 13: Visualización de las regiones óptimas que hemos seleccionado para nuestras señales comerciales RSI.

Obtengamos los valores de nuestra estimación de buenos intervalos de confianza, estos servirán como valores críticos en nuestro EA que activan señales largas y cortas.

results.index(min(results)) + ( coef * np.std(data['Diff RSI 55']))

10.822857254027287

Y nuestro límite inferior.

results.index(min(results)) - (coef * np.std(data['Diff RSI 55']))

7.177142745972713

Obtengamos una explicación de nuestro modelo Ridge para interpretar el indicador RSI de una manera en la que quizás no hayamos pensado intuitivamente.

def explanation(x):
    data = pd.read_csv("EURUSD RSI Algorithmic Input Selection.csv")
    data['0'] = 0
    data['1'] = 0
    data['2'] = 0
    HORIZON = 10
    data['Return'] = data['True Close'].shift(-HORIZON) - data['True Close']
    data.dropna(subset=['Return'],inplace=True)
    data.iloc[data['Diff RSI 55'] > x[0],12] = 1
    data.iloc[data['Diff RSI 55'] < x[1],13] = 1
    data.iloc[(data['Diff RSI 55'] < x[0]) & (data['RSI 55'] > x[1]),14] = 1
    #Calculate or RMSE When using those levels
    model = Ridge(alpha=0,tol=0,solver='sparse_cg')
    model.fit(data.iloc[:,12:15],data['Return'])
    return(model.coef_.copy())

Vemos que, cuando el indicador RSI cambia en más de 9, nuestro valor óptimo, nuestro modelo aprendió coeficientes positivos, lo que implica que debemos entrar en posiciones largas. De lo contrario, el modelo afirma que deberíamos vender bajo cualquier otra condición. 

opt = 9

print(explanation([opt,-opt]))

[ 1.97234840e-04 -1.64215118e-04 -7.55222156e-05]


Construyendo nuestro asesor experto

Suponiendo que nuestro conocimiento del pasado es un buen modelo del futuro, ¿podemos construir una aplicación para operar con el EURUSD de forma rentable utilizando lo que ahora hemos aprendido sobre el mercado? Para comenzar, primero definiremos constantes importantes del sistema que necesitaremos en todo nuestro programa y en cualquier otra versión que podamos crear.

//+------------------------------------------------------------------+
//|                                  Algorithmic Input Selection.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 RSI_PERIOD 55
#define RSI_TIME_FRAME PERIOD_D1
#define SYSTEM_TIME_FRAME PERIOD_D1
#define RSI_PRICE  PRICE_CLOSE
#define RSI_BUFFER_SIZE 20
#define TRADING_VOLUME SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN)

Carguemos nuestras bibliotecas.

//+------------------------------------------------------------------+
//| Load our RSI library                                             |
//+------------------------------------------------------------------+
#include <VolatilityDoctor\Indicators\RSI.mqh>
#include <Trade\Trade.mqh>

Necesitaremos algunas variables globales.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
CTrade Trade;
RSI    rsi_55(Symbol(),RSI_TIME_FRAME,RSI_PERIOD,RSI_PRICE);
double last_value;
int    count;
int    ma_o_handler,ma_c_handler;
double ma_o[],ma_c[];
double trade_sl;

Cada uno de nuestros controladores de eventos llamará a su propio método dedicado para manejar los subprocesos necesarios para completar sus tareas.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   setup();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   IndicatorRelease(ma_c_handler);
   IndicatorRelease(ma_o_handler);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   update();
  }
//+------------------------------------------------------------------+

La función de actualización actualiza todas las variables de nuestro sistema y verifica si tenemos que abrir una posición o administrar nuestras posiciones abiertas.

//+------------------------------------------------------------------+
//| Update our system variables                                      |
//+------------------------------------------------------------------+
void update(void)
  {
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),SYSTEM_TIME_FRAME,0);

   if(time_stamp != current_time)
     {
      time_stamp = current_time;
      CopyBuffer(ma_c_handler,0,0,1,ma_c);
      CopyBuffer(ma_o_handler,0,0,1,ma_o);

      if((count == 0) && (PositionsTotal() == 0))
        {
         rsi_55.SetIndicatorValues(RSI_BUFFER_SIZE,true);
         last_value = rsi_55.GetReadingAt(RSI_BUFFER_SIZE - 1);
         count = 1;
        }

      if(PositionsTotal() == 0)
         check_signal();

      if(PositionsTotal() > 0)
         manage_setup();
     }
  }

Nuestras posiciones necesitan tener sus stop loss ajustados constantemente para garantizar que reduzcamos nuestros niveles de riesgo siempre que sea posible. 

//+------------------------------------------------------------------+
//| Manage our open trades                                           |
//+------------------------------------------------------------------+
void manage_setup(void)
  {
   double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
   double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);

   if(PositionSelect(Symbol()))
     {
      double current_sl = PositionGetDouble(POSITION_SL);
      double current_tp = PositionGetDouble(POSITION_TP);
      double new_sl = (current_tp > current_sl) ? (bid-trade_sl) : (ask+trade_sl);
      double new_tp = (current_tp < current_sl) ? (bid+trade_sl) : (ask-trade_sl);
      //--- Buy setup
      if((current_tp > current_sl) && (new_sl < current_sl))
         Trade.PositionModify(Symbol(),new_sl,new_tp);

      //--- Sell setup
      if((current_tp < current_sl) && (new_sl > current_sl))
         Trade.PositionModify(Symbol(),new_sl,new_tp);
     }
  }

Nuestra función de configuración es responsable de poner en marcha nuestro sistema desde cero. Preparará los indicadores que necesitamos y reiniciará nuestros contadores.

//+------------------------------------------------------------------+
//| Setup our global variables                                       |
//+------------------------------------------------------------------+
void setup(void)
  {
   ma_c_handler = iMA(Symbol(),SYSTEM_TIME_FRAME,2,0,MODE_EMA,PRICE_CLOSE);
   ma_o_handler = iMA(Symbol(),SYSTEM_TIME_FRAME,2,0,MODE_EMA,PRICE_OPEN);
   count        = 0;
   last_value   = 0;
   trade_sl     = 1.5e-2;
  }

Por último, nuestras reglas comerciales se han generado gracias a los valores de coeficiente que nuestro modelo aprendió de los datos de entrenamiento. Esta es la última función que definiremos antes de desdefinir las variables del sistema que creamos al comienzo de nuestro programa.

//+------------------------------------------------------------------+
//| Check if we have a trading setup                                 |
//+------------------------------------------------------------------+
void check_signal(void)
  {
   double current_reading = rsi_55.GetCurrentReading();
   Comment("Last Reading: ",last_value,"\nDifference in Reading: ",(last_value - current_reading));
   double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
   double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
   double cp_lb = 7.17;
   double cp_ub = 10.82;

   if((((last_value - current_reading) <= -(cp_lb)) && ((last_value - current_reading) > (cp_ub)))|| ((((last_value - current_reading) > -(cp_lb))) && ((last_value - current_reading) < (cp_lb))))
     {
      if(ma_o[0] > ma_c[0])
        {
         if(PositionsTotal() == 0)
           {
            Trade.Sell(TRADING_VOLUME,Symbol(),bid,(ask+trade_sl),(ask-trade_sl));
            count = 0;
           }
        }
     }

   if(((last_value - current_reading) >= (cp_lb)) < ((last_value - current_reading) < cp_ub))
     {
      if(ma_c[0] < ma_o[0])
        {
         if(PositionsTotal() == 0)
           {
            Trade.Buy(TRADING_VOLUME,Symbol(),ask,(bid-trade_sl),(bid+trade_sl));
            count = 0;
           }
        }
     }
  }
//+------------------------------------------------------------------+

#undef RSI_BUFFER_SIZE
#undef RSI_PERIOD
#undef RSI_PRICE
#undef RSI_TIME_FRAME
#undef SYSTEM_TIME_FRAME
#undef TRADING_VOLUME
//+------------------------------------------------------------------+

Comencemos ahora con nuestra prueba retrospectiva del sistema comercial. Recuerde que en la figura 4 eliminamos todos los datos que se superponen con nuestra prueba retrospectiva. Por lo tanto, esto puede servirnos como una aproximación cercana de cómo nuestra estrategia puede funcionar en tiempo real. Realizaremos una prueba retrospectiva de 3 años de nuestra estrategia con datos diarios, desde el 1 de enero de 2022 hasta marzo de 2025.

Nuestras fechas

Figura 14: Las fechas que utilizaremos para nuestra prueba retrospectiva de la estrategia comercial.

Para obtener mejores resultados, siempre utilizamos "Cada tick basado en ticks reales" porque ofrece la simulación más precisa de las condiciones pasadas del mercado en función de los ticks históricos que el terminal MetaTrader 5 recopiló del corredor.

Figura 15: Las condiciones en las que realizaremos nuestras pruebas son muy importantes y cambian la rentabilidad de nuestra estrategia.

La curva de capital producida por nuestro probador de estrategias parece prometedora. Pero interpretemos juntos las estadísticas detalladas, para obtener una visión completa del rendimiento de nuestra estrategia.

Figura 16: Curva de capital generada por la estrategia RSI de 55 períodos en el EURUSD.

La ejecución de nuestra estrategia produjo las siguientes estadísticas en nuestro probador de estrategias:

  • Ratio Sharpe: 0,92
  • Beneficio esperado: 2,49
  • Beneficio neto total: $151,87
  • Operaciones rentables: 57,38 %

Sin embargo, hay que tener en cuenta que, de las 61 operaciones realizadas en total, solo 4 fueron operaciones largas. ¿Por qué nuestra estrategia está tan desproporcionadamente sesgada hacia las ventas? ¿Cómo podemos corregir este sesgo? Echemos otro vistazo a nuestro gráfico EURUSD e intentemos entenderlo juntos.

Nuestra prueba retrospectiva

Figura 17: Estadísticas detalladas del desempeño histórico de nuestro asesor experto en el tipo de cambio diario EURUSD.


Mejorando nuestro asesor experto

En la figura 18 he adjuntado una captura de pantalla del tipo de cambio mensual del EURUSD. Las dos líneas verticales rojas marcan el comienzo del año 2009 y el final del año 2021, respectivamente. La línea vertical verde representa el inicio de los datos de entrenamiento que utilizamos para nuestro modelo estadístico. Es fácil comprender por qué el modelo aprendió un sesgo hacia las posiciones cortas, dada la tendencia bajista sostenida que comenzó en 2008.

Figura 18: Comprender por qué nuestro modelo aprendió un sentimiento tan bajista.

No siempre necesitamos más datos históricos para intentar corregir esto. Más bien, podemos adaptar un modelo más flexible que el modelo Ridge con el que comenzamos. El alumno más fuerte proporcionará entonces a nuestra estrategia señales de compra adicionales.

Es posible que le demos a nuestro asesor experto una idea razonable de la probabilidad de que el tipo de cambio suba en los próximos 10 días. Podemos entrenar un regresor Random Forest para predecir la probabilidad de que observemos una acción alcista en los precios. Cuando la probabilidad esperada supera el 0,5, nuestro asesor experto entrará en una posición alcista. De lo contrario, seguiremos la estrategia que aprendimos de nuestro modelo Ridge.


Modelización de probabilidades en Python

Para comenzar con nuestras correcciones, primero importaremos algunas bibliotecas.

from sklearn.metrics import accuracy_score
from sklearn.ensemble import RandomForestRegressor

A continuación, definiremos nuestras variables dependientes e independientes.

#Independent variable
X = data[['Diff RSI 55']]
#Dependent variable
y = data['Target']

Ajuste el modelo.

model = RandomForestRegressor()
model.fit(X,y)

Prepárese para exportar a ONNX.

import onnx
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType

Defina las formas de entrada.

inital_params = [("float_input",FloatTensorType([1,1]))]

Cree un prototipo ONNX y guárdelo en el disco.

onnx_proto = convert_sklearn(model=model,initial_types=inital_params,target_opset=12)
onnx.save(onnx_proto,"EURUSD Diff RSI 55 D1 1 1.onnx")

También puede visualizar su modelo utilizando la biblioteca netron, para asegurarse de que se han asignado los atributos de entrada y salida correctos a su modelo ONNX.

import netron
netron.start("EURUSD Diff RSI 55 D1 1 1.onnx")

Esta es una representación gráfica de nuestro regresor Random Forest y los atributos que tiene el modelo. Netron también se puede utilizar para visualizar redes neuronales y muchos otros tipos de modelos ONNX.

Modelo ONNX

Figura 19: Visualización de nuestro modelo ONNX Random Forest Regressor.

Las formas de entrada y salida fueron especificadas correctamente por ONNX, por lo que ahora podemos pasar a aplicar el regresor Random Forest para ayudar a nuestro asesor experto a predecir la probabilidad de que se produzca una acción alcista de los precios en los próximos 10 días.

Detalles del modelo ONNX

Figura 20: Los detalles de nuestro modelo ONNX coinciden con las especificaciones esperadas que queríamos comprobar.


Mejorando nuestro asesor experto

Ahora que hemos exportado un modelo probabilístico del mercado EURUSD, podemos importar nuestro modelo ONNX a nuestro asesor experto.

//+------------------------------------------------------------------+
//|                                  Algorithmic Input Selection.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 resources                                                 |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD Diff RSI 55 D1 1 1.onnx" as uchar onnx_model_buffer[];

También necesitaremos algunas macros nuevas que especifiquen la forma de nuestro modelo ONNX.

#define ONNX_INPUTS 1
#define ONNX_OUTPUTS 1

Además, el modelo ONNX merece algunas variables globales porque es posible que las necesitemos rápidamente en varias partes de nuestro código.

long   onnx_model;
vectorf onnx_output(1);
vectorf onnx_input(1);

En el fragmento de código siguiente, hemos excluido las partes del código base que no han cambiado y solo mostramos los cambios realizados para inicializar el modelo ONNX y establecer sus formas de parámetros.

//+------------------------------------------------------------------+
//| Setup our global variables                                       |
//+------------------------------------------------------------------+
bool setup(void)
  {
//--- Setup our technical indicators
  ...

//--- Create our ONNX model
   onnx_model = OnnxCreateFromBuffer(onnx_model_buffer,ONNX_DATA_TYPE_FLOAT);

//--- Validate the ONNX model
   if(onnx_model == INVALID_HANDLE)
     {
      return(false);
     }

//--- Define the I/O signature
   ulong onnx_param[] =  {1,1};

   if(!OnnxSetInputShape(onnx_model,0,onnx_param))
      return(false);

   if(!OnnxSetOutputShape(onnx_model,0,onnx_param))
      return(false);

   return(true);
  }

Además, nuestra comprobación de operaciones válidas se ha reducido para destacar solo el código adicional añadido y evitar la duplicación del mismo código.

//+------------------------------------------------------------------+
//| Check if we have a trading setup                                 |
//+------------------------------------------------------------------+
void check_signal(void)
  {
   rsi_55.SetDifferencedIndicatorValues(RSI_BUFFER_SIZE,HORIZON,true);
   onnx_input[0] = (float) rsi_55.GetDifferencedReadingAt(RSI_BUFFER_SIZE - 1);

//--- Our Random forest model
   if(!OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,onnx_input,onnx_output))
      Comment("Failed to obtain a forecast from our model!");

   else
     {
      if(onnx_output[0] > 0.5)
         if(ma_o[0] < ma_c[0])
            Trade.Buy(TRADING_VOLUME,Symbol(),ask,(bid-trade_sl),(bid+trade_sl));
      Print("Model Bullish Probabilty: ",onnx_output);
     }
}

En resumen, así es como se ve nuestra segunda versión de la estrategia comercial cuando está completamente compuesta.

//+------------------------------------------------------------------+
//|                                  Algorithmic Input Selection.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 resources                                                 |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD Diff RSI 55 D1 1 1.onnx" as uchar onnx_model_buffer[];

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define RSI_PERIOD 55
#define RSI_TIME_FRAME PERIOD_D1
#define SYSTEM_TIME_FRAME PERIOD_D1
#define RSI_PRICE  PRICE_CLOSE
#define RSI_BUFFER_SIZE 20
#define TRADING_VOLUME SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN)
#define ONNX_INPUTS 1
#define ONNX_OUTPUTS 1
#define HORIZON 10

//+------------------------------------------------------------------+
//| Load our RSI library                                             |
//+------------------------------------------------------------------+
#include <VolatilityDoctor\Indicators\RSI.mqh>
#include <Trade\Trade.mqh>

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
CTrade Trade;
RSI    rsi_55(Symbol(),RSI_TIME_FRAME,RSI_PERIOD,RSI_PRICE);
double last_value;
int    count;
int    ma_o_handler,ma_c_handler;
double ma_o[],ma_c[];
double trade_sl;
long   onnx_model;
vectorf onnx_output(1);
vectorf onnx_input(1);

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   setup();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   IndicatorRelease(ma_c_handler);
   IndicatorRelease(ma_o_handler);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   update();
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Update our system variables                                      |
//+------------------------------------------------------------------+
void update(void)
  {
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),SYSTEM_TIME_FRAME,0);

   if(time_stamp != current_time)
     {
      time_stamp = current_time;
      CopyBuffer(ma_c_handler,0,0,1,ma_c);
      CopyBuffer(ma_o_handler,0,0,1,ma_o);

      if((count == 0) && (PositionsTotal() == 0))
        {
         rsi_55.SetIndicatorValues(RSI_BUFFER_SIZE,true);
         rsi_55.SetDifferencedIndicatorValues(RSI_BUFFER_SIZE,HORIZON,true);
         last_value = rsi_55.GetReadingAt(RSI_BUFFER_SIZE - 1);
         count = 1;
        }

      if(PositionsTotal() == 0)
         check_signal();

      if(PositionsTotal() > 0)
         manage_setup();
     }
  }

//+------------------------------------------------------------------+
//| Manage our open trades                                           |
//+------------------------------------------------------------------+
void manage_setup(void)
  {
   double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
   double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);

   if(PositionSelect(Symbol()))
     {
      double current_sl = PositionGetDouble(POSITION_SL);
      double current_tp = PositionGetDouble(POSITION_TP);
      double new_sl = (current_tp > current_sl) ? (bid-trade_sl) : (ask+trade_sl);
      double new_tp = (current_tp < current_sl) ? (bid+trade_sl) : (ask-trade_sl);
      //--- Buy setup
      if((current_tp > current_sl) && (new_sl < current_sl))
         Trade.PositionModify(Symbol(),new_sl,new_tp);

      //--- Sell setup
      if((current_tp < current_sl) && (new_sl > current_sl))
         Trade.PositionModify(Symbol(),new_sl,new_tp);
     }
  }

//+------------------------------------------------------------------+
//| Setup our global variables                                       |
//+------------------------------------------------------------------+
bool setup(void)
  {
//--- Setup our technical indicators
   ma_c_handler = iMA(Symbol(),SYSTEM_TIME_FRAME,2,0,MODE_EMA,PRICE_CLOSE);
   ma_o_handler = iMA(Symbol(),SYSTEM_TIME_FRAME,2,0,MODE_EMA,PRICE_OPEN);
   count        = 0;
   last_value   = 0;
   trade_sl     = 1.5e-2;

//--- Create our ONNX model
   onnx_model = OnnxCreateFromBuffer(onnx_model_buffer,ONNX_DATA_TYPE_FLOAT);

//--- Validate the ONNX model
   if(onnx_model == INVALID_HANDLE)
     {
      return(false);
     }

//--- Define the I/O signature
   ulong onnx_param[] =  {1,1};

   if(!OnnxSetInputShape(onnx_model,0,onnx_param))
      return(false);

   if(!OnnxSetOutputShape(onnx_model,0,onnx_param))
      return(false);

   return(true);
  }

//+------------------------------------------------------------------+
//| Check if we have a trading setup                                 |
//+------------------------------------------------------------------+
void check_signal(void)
  {
   rsi_55.SetDifferencedIndicatorValues(RSI_BUFFER_SIZE,HORIZON,true);
   last_value = rsi_55.GetReadingAt(RSI_BUFFER_SIZE - 1);

   double current_reading = rsi_55.GetCurrentReading();
   Comment("Last Reading: ",last_value,"\nDifference in Reading: ",(last_value - current_reading));
   double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
   double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
   double cp_lb = 7.17;
   double cp_ub = 10.82;
   onnx_input[0] = (float) rsi_55.GetDifferencedReadingAt(RSI_BUFFER_SIZE - 1);

//--- Our Random forest model
   if(!OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,onnx_input,onnx_output))
      Comment("Failed to obtain a forecast from our model!");

   else
     {
      if(onnx_output[0] > 0.5)
         if(ma_o[0] < ma_c[0])
            Trade.Buy(TRADING_VOLUME,Symbol(),ask,(bid-trade_sl),(bid+trade_sl));
      Print("Model Bullish Probabilty: ",onnx_output);
     }

//--- The trading rules we learned from our Ridge Regression Model
//--- Ridge Regression Sell
   if((((last_value - current_reading) <= -(cp_lb)) && ((last_value - current_reading) > (cp_ub)))|| ((((last_value - current_reading) > -(cp_lb))) && ((last_value - current_reading) < (cp_lb))))
     {
      if(ma_o[0] > ma_c[0])
        {
         if(PositionsTotal() == 0)
           {
            Trade.Sell(TRADING_VOLUME,Symbol(),bid,(ask+trade_sl),(ask-trade_sl));
            count = 0;
           }
        }
     }
//--- Ridge Regression Buy
   else
      if(((last_value - current_reading) >= (cp_lb)) < ((last_value - current_reading) < cp_ub))
        {
         if(ma_c[0] < ma_o[0])
           {
            if(PositionsTotal() == 0)
              {
               Trade.Buy(TRADING_VOLUME,Symbol(),ask,(bid-trade_sl),(bid+trade_sl));
               count = 0;
              }
           }
        }
  }
//+------------------------------------------------------------------+

#undef RSI_BUFFER_SIZE
#undef RSI_PERIOD
#undef RSI_PRICE
#undef RSI_TIME_FRAME
#undef SYSTEM_TIME_FRAME
#undef TRADING_VOLUME
#undef ONNX_INPUTS
#undef ONNX_OUTPUTS
//+------------------------------------------------------------------+

Probaremos la estrategia en las mismas condiciones que se muestran en las figuras 14 y 15. Estos son los resultados que obtuvimos con la segunda versión de nuestra estrategia de trading. La curva de capital generada por nuestra segunda versión de la operación parece ser muy similar a la primera curva de capital que generamos. 

Figura 21: Visualización de la rentabilidad de nuestra segunda estrategia comercial. 

Solo cuando analizamos las estadísticas detalladas, empiezan a surgir las diferencias entre las dos estrategias. Nuestro índice de Sharpe y la rentabilidad esperada disminuyeron. Nuestra nueva estrategia realizó 85 operaciones, lo que supone un 39 % más que las 61 operaciones realizadas por nuestra estrategia inicial. Además, nuestro número total de posiciones largas aumentó de solo 4 en la prueba inicial a 42 en nuestra segunda prueba. Eso supone un aumento del 950 %. Por lo tanto, cuando consideramos que el riesgo adicional que estamos asumiendo es significativo y, sin embargo, nuestras estadísticas de precisión y rentabilidad solo están disminuyendo marginalmente, comenzamos a generar expectativas positivas sobre el uso de la estrategia. En nuestra prueba anterior, el 57,38 % de todas nuestras operaciones fueron rentables, y ahora el 56,47 %, lo que supone una reducción de la precisión de aproximadamente el 1,59 %.

Figura 22: Estadísticas detalladas del rendimiento de la versión revisada de nuestra estrategia comercial.



Conclusión

Después de leer este artículo, el lector comprenderá cómo puede emplear técnicas de búsqueda por cuadrícula junto con modelos estadísticos para seleccionar el período óptimo para utilizar los indicadores sin tener que realizar múltiples pruebas retrospectivas y buscar manualmente todos los períodos posibles. Además, el lector también ha aprendido una posible forma de estimar y comparar el valor de los nuevos niveles del RSI con los que desea operar, frente al valor de los niveles tradicionales de 70 y 30, lo que le permite operar en condiciones de mercado adversas con una renovada confianza en su capacidad.

Nombre del archivo Descripción
Algorithmic Inputs Selection.ipynb El Jupyter Notebook que usamos para realizar análisis numéricos usando Python.
EURUSD Testing RSI Class.mql5 El script MQL5 que usamos para probar nuestra implementación de nuestra clase RSI personalizada.
EURUSD RSI Algorithmic Input Selection.mql5 El script que usamos para obtener datos históricos del mercado.
Algorithmic Input Selection.mql5 Nuestra versión inicial de la estrategia comercial que construimos.
Algorithmic Input Selection 2.mql5 Nuestra versión refinada de la estrategia comercial que corrigió el sesgo aprendido por nuestra estrategia comercial inicial.

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

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.
Aprendizaje automático y Data Science (Parte 35): NumPy en MQL5, el arte de crear algoritmos complejos con menos código Aprendizaje automático y Data Science (Parte 35): NumPy en MQL5, el arte de crear algoritmos complejos con menos código
La biblioteca NumPy impulsa casi todos los algoritmos de aprendizaje automático en el lenguaje de programación Python. En este artículo vamos a implementar un módulo similar que contiene una colección de todo el código complejo para ayudarnos a crear modelos y algoritmos sofisticados de cualquier tipo.
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.
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 18): Introducción a la teoría de los cuartos (III) — Quarters Board Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 18): Introducción a la teoría de los cuartos (III) — Quarters Board
En este artículo mejoramos el script Quarters original con la introducción del Quarters Board, una herramienta que te permite alternar los niveles de cuartos directamente en el gráfico sin necesidad de volver a revisar el código. Puede activar o desactivar fácilmente niveles específicos, y el EA también proporciona comentarios sobre la dirección de la tendencia para ayudarle a comprender mejor los movimientos del mercado.