English Deutsch 日本語
preview
Desarrollo de asesores expertos autooptimizantes en MQL5

Desarrollo de asesores expertos autooptimizantes en MQL5

MetaTrader 5Ejemplos | 14 agosto 2024, 11:10
31 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Introducción

Los sistemas automatizados de autooptimización son esenciales en los dinámicos mercados financieros actuales. En la era digital, los mercados se han vuelto notablemente más volátiles debido a la adopción generalizada de la negociación algorítmica, especialmente por parte de operadores de alta frecuencia. Según este documento de la SEC, SEC High-Frequency Trading Paper, los operadores de alta frecuencia representan casi la mitad de todas las operaciones en Europa y Estados Unidos.

El desarrollo de un bot de negociación que pueda ajustarse a las condiciones actuales del mercado es clave para la estabilidad de las estrategias de negociación algorítmica. Nuestro objetivo es ir más allá de la creación de robots estrechamente enfocados y limitados a unos pocos símbolos. Pretendemos diseñar sistemas con capacidad de aprendizaje y adaptación a cualquier símbolo comercial. Esta guía se centra en el uso de MQL5 para desarrollar bots que puedan autooptimizarse a cualquier entorno de trading.

MQL5 es ideal para esta tarea, en contra de algunas creencias. Su API ofrece amplias funciones matriciales y vectoriales que permiten crear modelos compactos de aprendizaje automático. Esta introducción hace hincapié en el uso de MQL5 para construir bots autooptimizables. La programación orientada a objetos reduce la repetición de códigos y mejora la adaptabilidad a distintos plazos y condiciones de mercado.

Optar por las capacidades matriciales y vectoriales de MQL5 frente a alternativas como ONNX y Python tiene ventajas considerables. El uso de un modelo ONNX requeriría instancias de modelo separadas para cada símbolo de negociación y nuevos modelos para cualquier cambio menor de los parámetros, como los ajustes del marco temporal. MQL5, sin embargo, ofrece adaptabilidad sin necesidad de gestionar numerosos modelos para condiciones variables.


Sinopsis: Desarrollar asesores expertos autooptimizables

Necesitamos un marco para evaluar la eficacia de nuestro asesor experto. Una vez definida una métrica de rendimiento definitiva, podemos maximizar o minimizar la métrica elegida en consecuencia. Al construir modelos de aprendizaje automático supervisado para la predicción de precios, nuestro objetivo es minimizar el error entre los valores predichos y las observaciones reales. Por otro lado, para los problemas de aprendizaje por refuerzo, el objetivo es maximizar las recompensas totales esperadas descontadas.

En este artículo minimizaremos la diferencia entre el precio futuro previsto pronosticado por nuestro asesor experto y el precio real observado en el futuro. Esto puede lograrse calculando las diferencias absolutas entre estos precios.

Este artículo explora los aspectos fundamentales de la construcción de un asesor experto autooptimizador. En futuros artículos profundizaremos en metodologías más avanzadas para crear asesores expertos autooptimizables utilizando funciones más avanzadas de la API MQL5.

Después de leer este artículo, el lector comprenderá:

  1. Una selección de funciones matriciales y vectoriales útiles.
  2. Conceptos básicos de programación orientada a objetos en MQL5.
  3. Un marco para construir asesores expertos dinámicos y autoadaptables en MQL5.

Autooptimización con descenso de gradiente

Nuestro objetivo es diseñar un asesor experto capaz de realinearse constantemente con las condiciones actuales del mercado. Para ello implementaremos el algoritmo de descenso de gradiente en MQL5. Para los lectores que no estén familiarizados con el algoritmo de descenso de gradiente, podría ser útil compararlo con el proceso mediante el cual un DJ configura su equipo de sonido. Imagina que eres un DJ que se prepara para actuar. Enciendes el equipo y el volumen es demasiado alto. ¿Qué harías después? Lo más probable es que reduzcas el volumen. Sin embargo, ahora el volumen está demasiado bajo, por lo que lo aumentarás. Este vaivén entre aumento y disminución se produce hasta que se encuentra un nivel equilibrado.

El algoritmo de descenso de gradiente funciona de forma similar. Comenzamos con coeficientes aleatorios en el modelo del mercado en el que nos encontramos. A continuación, medimos el error producido por nuestros coeficientes actuales. De forma similar a lo que hace el DJ, ajustamos iterativamente los coeficientes de nuestro modelo en la dirección opuesta al aumento del error. Tomamos derivadas con respecto a las entradas, como los coeficientes en un modelo lineal, para deducir en qué dirección aumenta el error.

Además, junto con los coeficientes, otro parámetro crucial en el descenso de gradiente es la tasa de aprendizaje, que es análoga a la magnitud del cambio de volumen que realiza el DJ con cada ajuste del control de volumen. La tasa de aprendizaje rige el tamaño del paso que daremos cada vez que ajustemos los parámetros de nuestro modelo. Si nuestra tasa de aprendizaje es demasiado grande o demasiado pequeña, nuestro modelo no conseguirá aprender de forma óptima.

Lo ideal es diseñar un asesor experto que optimice dinámicamente las tasas de aprendizaje y los coeficientes para cada escenario de mercado, incluso si cambiamos los plazos y el alcance de los datos. Esta adaptabilidad, con suerte, nos permitirá estar efectivamente en el lado correcto del mercado, aprovechando todo el potencial de las soluciones nativas para operar sin limitaciones.


Estrategia de negociación

Nuestra estrategia de negociación será un enfoque híbrido que empleará el análisis técnico y el aprendizaje automático. Utilizaremos una media móvil para ayudarnos a determinar la tendencia dominante del mercado. Si el precio está por encima de la media móvil asumiremos que la tendencia dominante es alcista, de lo contrario si el precio está por debajo de la media móvil asumiremos que la tendencia del mercado es bajista. Una vez que hayamos deducido la tendencia del mercado, buscaremos la confirmación de 2 indicadores de apoyo. El Índice de Fuerza Relativa (RSI, Relative Strength Index) y el Rango Porcentual de William (WPR, William's Percent Range). 

El indicador RSI da lecturas entre 0 y 100. Típicamente, cuando la lectura del RSI está por encima de 70 el valor se considera sobrecomprado y debe ser vendido, y si la lectura del RSI está por debajo de 30 el valor se considera sobrevendido y debe ser comprado.Esta estrategia funciona bien cuando se negocian valores que existen en números limitados, como acciones o materias primas, sin embargo cuando se negocian pares de divisas esta estrategia no tiene sentido intuitivo. Las divisas no pueden estar sobrevendidas o sobrecompradas, los bancos centrales pueden crear tanto o tan poco como consideren necesario, por lo tanto en nuestra estrategia cuando la lectura del RSI esté por encima de 50 querremos comprar en lugar de vender, del mismo modo cuando la lectura del RSI esté por debajo de 50 venderemos en lugar de comprar.

El indicador WPR da lecturas entre 0 y -100. Al igual que el RSI, el indicador WPR identifica las zonas de sobrecompra y sobreventa. Sin embargo las divisas no pueden ser sobrecompradas o sobrevendidas, la oferta de divisas es ilimitada, por lo tanto en nuestra estrategia interpretaremos el WPR ligeramente diferente. En nuestra estrategia, cuando el indicador WPR esté por encima de -20, lo registraremos como una señal de compra, y si el indicador WPR está por debajo de -80, lo registraremos como una señal de venta.

Si los 3 indicadores se alinean en el mismo lado del mercado, recurriremos finalmente a nuestro modelo para prever el precio esperado en el futuro. Si el pronóstico de nuestro modelo se alinea con nuestro sentimiento a partir del análisis de nuestros indicadores, abriremos la posición; de lo contrario, si nuestro modelo y nuestros indicadores dan señales contradictorias, esperaremos hasta que se alineen.

Nuestros niveles de Take Profit y Stop Loss también se establecerán dinámicamente utilizando los niveles actuales de volatilidad del mercado, tomaremos el valor absoluto de la diferencia entre el precio y la media móvil. Nuestro Stop Loss y Take Profit serán 2 veces el valor absoluto de la altura entre la media móvil y el precio de cierre. Nuestro razonamiento es que en condiciones de mercado letárgico, nuestro Stop Loss y Take Profit serán ajustados y en días de mercado volátil nuestro Stop Loss y Take Profit serán lo suficientemente amplios. En resumen, todo nuestro sistema se ajustará dinámicamente por sí mismo sin ninguna intervención por nuestra parte. 


Implementación en MQL5

Para empezar, primero tenemos que definir una clase para nuestro modelo de aprendizaje automático. Utilizar la programación orientada a objetos (OOP, Object-Oriented Programming) tiene muchas ventajas, especialmente para los proyectos de ciencia de datos. Imagina que hubieras creado un modelo de aprendizaje automático y luego copiaras manualmente ese código y lo insertaras en todos los asesores expertos que tienes. Días después, te das cuenta de un error que cometiste en una de las funciones de tu código. Si no utilizara los principios de diseño de la programación orientada a objetos, tendría que revisar manualmente cada instancia del código copiado y hacer las correcciones una a una. Sin embargo, si estuviera empleando los principios de diseño de programación orientada a objetos, sólo tendría que corregir la clase y volver a compilar los demás programas. En resumen, los principios de diseño de la programación orientada a objetos pueden proporcionarle un control definitivo y preciso sobre miles de instancias diferentes de su código.

Comenzamos construyendo una nueva clase en nuestro MetaEditor 5.

Creación de una nueva clase.

Fig 1: Creación de una nueva clase en MQL5.


Desde ahí establecemos el nombre de la clase. Asegúrate de que tu clase se guarda en la carpeta "Include", además te recomiendo que asignes a cada clase su propia carpeta, y que le des a la carpeta el mismo nombre que a la clase.Haciendo esto, será más fácil encontrar estas clases en el futuro.


Guardando la nueva clase.

Fig. 2: Creación de nuestra clase de regresión lineal.


Si seguiste los pasos anteriores, el asistente de MQL5 (MQL5 Wizard) te ayudará a producir un código similar a este.

class LinearRegegression
  {
private:

public:
                     LinearRegegression();
                    ~LinearRegegression();
  };
LinearRegegression::LinearRegegression()
  {
  }
LinearRegegression::~LinearRegegression()
  {
  }

Si es la primera vez que trabajas con programación orientada a objetos (OOP) en MQL5, entonces repasemos juntos el código anterior. En la parte superior tenemos la definición de la clase. La palabra clave `class` define todo este código como una clase; después de la palabra clave `class` se encuentra el nombre de la clase. Desde ahí entramos en el cuerpo de la clase. La palabra clave `private` define variables y funciones que no pueden ser accedidas desde fuera de la clase, mientras que la palabra clave `public` define variables y funciones que sí pueden ser accedidas desde fuera de la clase. Observa que ya tenemos dos funciones definidas en nuestra clase.

La primera función, `LinearRegression()`, la llamaremos constructor. Esta es la primera función llamada cada vez que lanzamos una nueva instancia de nuestra clase, y la última función "~LinearRegression()" la llamaremos destructor. El destructor es la última función llamada cada vez que eliminamos la clase de nuestro gráfico.

Ahora podemos pasar a definir las variables que utilizaremos para calcular nuestro modelo de regresión lineal.

  • La potencia máxima de la tasa de aprendizaje (max_learning_rate_power) define hasta qué punto buscaremos una buena tasa de aprendizaje.
  • "fetch" simplemente se refiere al número de velas que queremos analizar del mercado.
  • `start` y `predict` definen cuándo comenzaremos a recopilar datos y el punto desde el cual haremos nuestra predicción.
  • `look_ahead` define cuántos pasos hacia el futuro deseamos pronosticar.
  • `mae_array` es la matriz (array) que almacenará nuestra métrica de error.
  • `trained` es una bandera (flag) que nos indica si nuestro modelo ha sido entrenado y está listo para usarse.
  • `epochs_power` define el número de épocas que utilizaremos para entrenar nuestro modelo.
  • Tenemos dos vectores, `mae_train` y `mae_validation`, que almacenan nuestras métricas de error del entrenamiento y la validación.
  • Tenemos cuatro vectores: `x` e `y` de validación, y `x` e `y` de entrenamiento. Estos vectores contienen nuestros datos de entrenamiento y validación.
  • Los vectores `m` y `b` contienen estimaciones para los coeficientes `m` y `b` apropiados para nuestro modelo.
  • El double `forecast` es simplemente la predicción de nuestro modelo.
  • `learning_rate_power` es la potencia a la que elevaremos 0.1 para definir nuestra tasa de aprendizaje.
  • `Epochs` es el número de veces que entrenaremos el modelo.
  • `n` es el número de filas en nuestros datos; siempre es igual a `fetch`.
  • `output_end`, `output_start`, `input_end`, `input_start` definen nuestra división entre entrenamiento y prueba.
private:
                     //This is the highest power that we will raise ten to, as we are searching for coefficients
                     ulong max_learning_rate_power; 
                     //This is how many bars we should fetch
                     int fetch;
                     //This is where we will start collecting data, it is the end of our validation data set. 
                     datetime start,predict;
                     //This is how many steps into the future we want to forecast
                     int look_ahead;
                     //This is the array that will contain our MAE readings from testing different learning rates on the validation data
                     double mae_array[30];
                     //Trained flag to inform us if the model has been fit and optimised succesfuly and is ready for use
                     bool trained;
                     //The number to raise the power of 10 buy when calculating the number of epochs
                     int epochs_power;
                     //Our error metrics
                     vector mae_train,mae_validation;
                     //This vector contains our inputs validation and training set
                     vector x_validation,x_train;
                     //This vector contains our outputs validation and training set
                     vector y_validation,y_train;
                     //This vector contains our predictions on the validation set
                     vector y_hat_validation,y_hat_train;
                     //This vector contains our gradient coefficient
                     vector m;
                     //This vector contains our model bias
                     vector b;
                     //This is our model's forecast
                     double forecast;
                     //This is our current learning rate power
                     ulong learning_rate_power;
                     //This is the learning rate power we are currently evaluating
                     int lr_error_index;
                     //This is our current learning rate
                     double learning_rate;
                     //This is the number of rounds we will allow whilst training our model
                     double epochs;
                     //This is used in calculations, it is the number of rows in our data, or the fetch size.
                     ulong n;
                     //These are the times for our input and output data
                     datetime output_end,output_start,input_end,input_start;
                     //These are the index times for our input and output data
                     int index_output_end,index_output_start,index_input_end,index_input_start;
                     //This is the value we will use to scale our data
                     double first_reading;
                     bool allowed_to_evaluate;
                     //Update the learning rate
                     bool UpdateLearningRate(void);
                     //Update the number of epochs
                     bool UpdateEpochs(void);
                     //Set the number of epochs
                     bool SetEpochs(int _epochs_power);
                     //Reset the number of epochs 
                     bool ResetEpochs(void);
                     //Reset the learning rate
                     bool ResetLearningRate(void);
                     //This function will fit the coeffeicients
                     bool Fit(void);
                     //This function evaluates the current settings
                     bool Evaluate(ulong _index,int _epochs_power);
                     //This function will scale the input data
                     bool ScaleInputs(void);
                     //This function sets the learning rate
                     bool SetLearningRate(ulong _learning_rate_power);

Ahora pasamos a las definiciones públicas de nuestra clase.

public:
                     //Constructor 
                     LinearRegression();
                     //Fetch Current Validation Data
                     bool GetCurrentValidationData(void);
                     //Initialise the LinearRegressor Model
                     void Init(int _fetch,int _look_ahead);
                     //Function to determine if the model has been trained and is ready for use.
                     bool Trained(void);
                     //A function to train the model using the best learning rate and the most recent prices
                     bool Train(void);
                     //A function to predict future price using the current price.
                     double Predict(void);
                     //Destructor
                    ~LinearRegression();

El código anterior define las funciones que tenemos en nuestra clase y la firma de cada función, sin embargo, todavía tenemos que implementar cada función. Empecemos por implementar el constructor.

Observa que nuestro constructor no recibe entradas; esto se denomina constructor por defecto o constructor no paramétrico. Además, observa que el constructor no tiene tipo de retorno, ni siquiera `void`.

LinearRegression::LinearRegression()
  {
      Print("Current Symbol: ",_Symbol);
  }

Nuestro constructor, por diseño, no realiza ninguna acción aparte de mostrar el símbolo de negociación actual. Esta elección de diseño intencional nos permite recuperar las entradas de nuestro asesor experto, y utilizarlas para inicializar nuestro objeto de regresión lineal basado en estas entradas. En particular, el constructor se abstiene de establecer variables o valores por defecto, una tarea reservada para un método separado definido en la función `Init()`. En resumen, separar el constructor de la función `Init()` resulta altamente ventajoso, ya que nos permite recopilar dinámicamente entradas desde la configuración del asesor experto. Si el constructor hubiera sido responsable de la inicialización de variables, la recopilación dinámica de entradas habría estado limitada.

Ahora definamos la función `Init()` responsable de inicializar nuestras variables a sus valores por defecto. Después de inicializar nuestras variables, el método `Init` intentará automáticamente escalar las entradas y entrenar el modelo por nosotros.

void LinearRegression::Init(int _fetch,int _look_ahead)
   {
      //Clear The Chart
      ObjectsDeleteAll(0);      
      //Allow evaluations
      allowed_to_evaluate = true;
      //Epochs power
      epochs_power =4;
      //Set the number of epochs
      epochs = 5 * MathPow(10,epochs_power);
      //Has the model been trained?
      trained = false;
      //Set the maximum learning rate power
      max_learning_rate_power = 30;
      //Set the end of our validation data
      start = iTime(_Symbol,PERIOD_CURRENT,1);
      //This is how much data we're going to fetch
      this.fetch = _fetch - 1;
      //This is how far into the future we want to forecast
      this.look_ahead = _look_ahead + 1;
      //Set the gradient coefficient to a random value
      m = vector::Zeros(1);
      //Set the bias to a random value
      b = vector::Zeros(1);
      //Set the forecast to 0
      forecast = 0;
      //Our model's learning rate will start at 0
      learning_rate_power = 0;
      //This is the learning rate we are evaluting
      lr_error_index = 0;
      mae_train = vector::Full(1,MathPow(10,100));
      mae_validation = vector::Full(30,MathPow(10,10000));
      //Set the initial learning rate
      learning_rate = MathPow(0.1,(learning_rate_power));
      //Set the number of rows
      n = fetch;
      if(GetCurrentValidationData())
         {
            //Scale the data
            ScaleInputs();
            //Fit the model
            Fit();   
         }      
   }

La función `predict` está diseñada para devolver un tipo de dato `double` sin ningún parámetro; por eso tiene una entrada `void`. Es importante destacar que, para designar una función como miembro de una clase, la precedemos con el nombre de la clase, seguido de dos puntos dobles y el nombre de la función.

La función `predict` primero verificará si el modelo ha sido entrenado, llamando a la función `Trained()`. Una vez que se ha confirmado el estado de entrenamiento del modelo, procedemos a recopilar datos en tiempo real, específicamente el precio actual y el precio de cierre, junto con los datos de la marca de tiempo para el contexto de la predicción. Calculamos el precio predicho multiplicando el precio actual por `m` y sumando `b`. Luego, devolvemos la predicción o devolvemos 0 si el modelo no está entrenado.

double LinearRegression::Predict(void)
   {
      if(Trained())
         {
            double _current_reading = iClose(_Symbol,PERIOD_CURRENT,0);
            predict = iTime(_Symbol,PERIOD_CURRENT,0);
            
            double prediction = (m[0]*_current_reading)+b[0];
            if(prediction > _current_reading)
               {
                  Comment("Buy, forecast: ",prediction);
               }
            else if(prediction < _current_reading)
               {
                  Comment("Sell, forecast: ",prediction);
               }
            
            ObjectCreate(0,"prediction point",OBJ_VLINE,0,predict,0);
            ObjectCreate(0,"forecast",OBJ_HLINE,0,predict,prediction);
            return(prediction);
         }
         
      return(0);
   }

Nuestra siguiente función se encarga de recopilar datos de entrenamiento y validación, obteniendo siempre los últimos datos de mercado disponibles. Este proceso se realiza a través de la función `copy_rates`, específicamente diseñada para transferir datos históricos de precios a un vector.

Después de obtener los datos, tenemos que asegurarnos de que los vectores tienen el mismo tamaño, utilizando la función de tamaño del vector.

bool LinearRegression::GetCurrentValidationData(void)
   {
      //Indexes
      index_output_end = 1;
      index_output_start = index_output_end + fetch;
      index_input_end = index_output_end + look_ahead;
      index_input_start = index_output_start + look_ahead; 
      
      //Assigning time stamps
      output_end =  iTime(Symbol(),PERIOD_CURRENT,index_output_end);
      output_start = iTime(Symbol(),PERIOD_CURRENT,index_output_start);
      input_end = iTime(Symbol(),PERIOD_CURRENT,index_input_end);
      input_start = iTime(Symbol(),PERIOD_CURRENT,index_input_start);
      
      //Get the output data
      if(!y_validation.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,output_end,fetch))
         {
            Print("Failed to get market data: ",GetLastError());
            return(false);
         }
      //Get the input data
      if(!x_validation.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,input_end,fetch))
         {
            Print("Failed to get market data: ",GetLastError());
            return(false);
         }
      //Print the vectors we have
      if(x_validation.Size() != y_validation.Size())
         {
            Print("Failed to get market data: Our vectors aren't the same length.");
            return(false);
         }
         
         //Print the vectors and plot the data points
         Print("X validation: ",x_validation);
         ObjectCreate(0,"X validation end",OBJ_VLINE,0,input_end,0);
         ObjectCreate(0,"X validation start",OBJ_VLINE,0,input_start,0);
         
         //Print the vectors and plot the data points
         Print("y validation: ",y_validation);
         ObjectCreate(0,"y validation end",OBJ_VLINE,0,output_end,0);
         ObjectCreate(0,"y validation start",OBJ_VLINE,0,output_start,0);
         
         //Set the training data
         index_output_end = index_input_start + (look_ahead * 2);
         index_output_start = index_output_end + fetch;
         index_input_end = index_output_end + look_ahead;
         index_input_start = index_output_start + look_ahead; 
         
         //Assigning time stamps
         output_end =  iTime(Symbol(),PERIOD_CURRENT,index_output_end);
         output_start = iTime(Symbol(),PERIOD_CURRENT,index_output_start);
         input_end = iTime(Symbol(),PERIOD_CURRENT,index_input_end);
         input_start = iTime(Symbol(),PERIOD_CURRENT,index_input_start);
         
         //Copy the training data   
         if(!y_train.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,output_end,fetch))
            {
               Print("Error fetching training data ",GetLastError());
            }
            
         //Copy the training data   
         if(!x_train.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,input_end,fetch))
            {
               Print("Error fetching training data ",GetLastError());
            }
         
         //Check if the data matches
         if(x_train.Size() != y_train.Size())
            {
               Print("Error fetching training dataL: The x and y vectors are not the same size");
            }
           
           //Print the vectors and plot the data points 
            Print("X training: ",x_train);
            ObjectCreate(0,"X training end",OBJ_VLINE,0,input_end,0);
            ObjectCreate(0,"X training start",OBJ_VLINE,0,input_start,0);
            
            Print("y training: ",y_train);
            ObjectCreate(0,"y training end",OBJ_VLINE,0,output_end,0);
            ObjectCreate(0,"y training start",OBJ_VLINE,0,output_start,0);
            return(true);
   }

Ahora definimos nuestra función de ajuste. La función comienza utilizando los valores actuales de `m` y `b` para generar predicciones sobre los datos de entrenamiento. Posteriormente, evalúa el error dentro de los datos de entrenamiento calculando las diferencias absolutas entre las observaciones reales de `Y` y nuestras observaciones predichas de `Y`.

Una vez que se determina el error, calculamos el error medio utilizando otra función de vector eficiente, `Mean`, para calcular la media aritmética del vector de error.

A continuación, implementamos el algoritmo de descenso de gradiente aproximando las derivadas de nuestro error con respecto a `m` y `b`. Estas aproximaciones de las derivadas nos guían en la actualización de nuestros coeficientes mediante una fracción de las derivadas obtenidas.

Tras la actualización de los coeficientes, es imperativo validar los nuevos coeficientes, ya que ciertos escenarios pueden arrojar coeficientes inválidos como `NaN` o infinito. Este paso de validación es crucial para garantizar la integridad y utilidad de los coeficientes actualizados.

bool LinearRegression::Fit()
   {
      Print("Fitting a linear regression on the training set with learning rate ",learning_rate_power);

      Print("Evalutaions: ",allowed_to_evaluate);
      for(int i =0; i < epochs;i++)
         {
            //Measure error
            y_hat_train = (m[0]*x_train) + b[0];
            vector y_minus_y_hat = (y_train - y_hat_train);
            vector y_minus_y_hat_sqaured = MathAbs((y_train - y_hat_train));
            mae_train.Set(0,( y_minus_y_hat_sqaured.Mean()));
            vector x_times_y_minus_y_hat = (x_train*(y_train -y_hat_train));
            
            //Aproximate the derivatives
            double derivative_m = (-2.0/n) * x_times_y_minus_y_hat.Sum();
            double derivative_b = (-2.0/n) * y_minus_y_hat.Sum();
            
            //Update the linear parameters
            m[0] = m[0] - (learning_rate * derivative_m);
            b[0] = b[0] - (learning_rate * derivative_b);
         }
         
         //Finished fitting the coefficients
         Print("Fit on training data complete.\nm: ",m[0]," b: ",b[0]," mae ",mae_train[0],"\nlearning rate: ",learning_rate);
         
         if(allowed_to_evaluate)
            {
               Evaluate(learning_rate_power,epochs_power);
            }
            
         //Return true
         return(true);
   }

Pasemos a definir nuestra función `Evaluate`. La función se encarga de seleccionar la mejor tasa de aprendizaje para cada símbolo con el que operamos. Comenzamos verificando la validez de nuestros coeficientes. Si los coeficientes son cero o contienen valores `NaN`, se restablecen.

La razón detrás de este proceso de validación es seleccionar meticulosamente los coeficientes que produzcan el menor error de validación a través de diferentes tasas de aprendizaje. Los coeficientes no válidos, marcados por altos errores de validación, se excluyen de la consideración durante la fase de selección de coeficientes. Por el contrario, los coeficientes válidos se almacenan para un análisis posterior.

Posteriormente, utilizamos estos coeficientes almacenados para generar predicciones sobre nuestros datos de validación y evaluar el error. Este proceso iterativo implica actualizar las tasas de aprendizaje, ajustar el modelo y evaluar los errores. El límite máximo de iteraciones se establece en 30, lo que corresponde al valor máximo de la potencia de la tasa de aprendizaje (max_learning_rate_power).

A lo largo de la función `Evaluate`, se realiza una verificación continua para asegurar que el índice se mantenga dentro de los límites de la potencia máxima de la tasa de aprendizaje. Recopilamos los datos del error absoluto en un vector para un procesamiento eficiente, utilizando funciones de vector como `vector.Min()` y `Argmin()` para identificar la potencia de la tasa de aprendizaje asociada con el menor error de validación.

//This function evaluates the current coefficient settings and learning rate
bool LinearRegression::Evaluate(ulong _index)
   {
      Print("Evaluating the coefficients m:",m[0]," b: ",b[0]," at learning rate: ",learning_rate);
      
      //First check if the coefficient and learning rate are valid
      if((m.HasNan() > 0 || b.HasNan() > 0 || m[0] == 0 || b[0] == 0 || _index > max_learning_rate_power) && (_index < max_learning_rate_power))
         {
            Print("Coefficients are invalid");
            m[0] = 0;
            b[0] = 0 ;
            mae_array[_index] = MathPow(10,100000);
            //Update the learning rate
            UpdateLearningRate();
            //Fit the model again
            Fit();   
         }
         
      else
         {
            //Validation predictions
            if(_index < max_learning_rate_power)
               {
                  Print("Coefficients are valid, solution at index ",_index);
                  y_hat_validation = (m[0] * x_validation) + b[0];   
                  vector y_minus_y_hat_squared = MathAbs(y_validation - y_hat_validation);
                  //If everything is fine, let's assess the validation mae
                  mae_array[_index] = (1.0/n) * y_minus_y_hat_squared.Sum();
                  //What was the validation error?
                  Print("Validation error: ",(1.0/n) * y_minus_y_hat_squared.Sum());
                  //Update the learning rate
                  UpdateLearningRate();
                  //Fit the model again
                  Fit();   
               }
         }  
         
      if(_index == max_learning_rate_power)
         {
            for(int i = 0; i < max_learning_rate_power;i++)
               {
                  mae_validation[i] = mae_array[i];   
               }
            allowed_to_evaluate = false;
            trained = true;
            Print("Validation mae: \n",mae_validation);
            Print("Lowest validation mae: ",mae_validation.Min());
            ulong chosen_learning_rate = mae_validation.ArgMin();
            Print("Chosen learning rate ",MathPow(0.1,(chosen_learning_rate)));
            SetLearningRate(chosen_learning_rate);
            Fit();
         }
         
         return(true);
   }

También definiremos una función para escalar nuestras entradas. Esta función es fácil de entender, divide todas nuestras entradas por la primera entrada de nuestro vector de entrenamiento.

//This function will scale our inputs
bool LinearRegression::ScaleInputs(void)
   {
      //Set the first reading
      first_reading = x_train[0];
      x_train = x_train / first_reading;
      x_validation = x_validation / first_reading;
      return(true);
   }

A partir de ahí definimos el destrcutor, el destrcutor resetea todos los coeficientes que acabamos de optimizar.

LinearRegression::~LinearRegression()
  {
      ResetLearningRate();
      ResetLastError();
  }

Las funciones llamadas por el destructor se definen como sigue:

bool LinearRegression::ResetLearningRate(void)
   {
         learning_rate_power = 0;
         learning_rate = MathPow(0.1,learning_rate_power);
         return(true);
   }

Cuando lo juntamos todo, esta es nuestra definición de clase:

#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com"
#property version   "1.00"
class LinearRegression
  {
private:
                     //This is the highest power that we will raise ten to, as we are searching for coefficients
                     ulong max_learning_rate_power; 
                     //This is how many bars we should fetch
                     int fetch;
                     //This is where we will start collecting data, it is the end of our validation data set. 
                     datetime start,predict;
                     //This is how many steps into the future we want to forecast
                     int look_ahead;
                     //This is the array that will contain our MAE readings from testing different learning rates on the validation data
                     double mae_array[30];
                     //Trained flag to inform us if the model has been fit and optimised succesfuly and is ready for use
                     bool trained;
                     //The number to raise the power of 10 buy when calculating the number of epochs
                     int epochs_power;
                     //Our error metrics
                     vector mae_train,mae_validation;
                     //This vector contains our inputs validation and training set
                     vector x_validation,x_train;
                     //This vector contains our outputs validation and training set
                     vector y_validation,y_train;
                     //This vector contains our predictions on the validation set
                     vector y_hat_validation,y_hat_train;
                     //This vector contains our gradient coefficient
                     vector m;
                     //This vector contains our model bias
                     vector b;
                     //This is our model's forecast
                     double forecast;
                     //This is our current learning rate power
                     ulong learning_rate_power;
                     //This is the learning rate power we are currently evaluating
                     int lr_error_index;
                     //This is our current learning rate
                     double learning_rate;
                     //This is the number of rounds we will allow whilst training our model
                     double epochs;
                     //This is used in calculations, it is the number of rows in our data, or the fetch size.
                     ulong n;
                     //These are the times for our input and output data
                     datetime output_end,output_start,input_end,input_start;
                     //These are the index times for our input and output data
                     int index_output_end,index_output_start,index_input_end,index_input_start;
                     //This is the value we will use to scale our data
                     double first_reading;
                     bool allowed_to_evaluate;
                     //Update the learning rate
                     bool UpdateLearningRate(void);
                     //Update the number of epochs
                     bool UpdateEpochs(void);
                     //Set the number of epochs
                     bool SetEpochs(int _epochs_power);
                     //Reset the number of epochs 
                     bool ResetEpochs(void);
                     //Reset the learning rate
                     bool ResetLearningRate(void);
                     //This function will fit the coeffeicients
                     bool Fit(void);
                     //This function evaluates the current settings
                     bool Evaluate(ulong _index);
                     //This function will scale the input data
                     bool ScaleInputs(void);
                     //This function sets the learning rate
                     bool SetLearningRate(ulong _learning_rate_power);
                     
public:
                     //Constructor 
                     LinearRegression();
                     //Fetch Current Validation Data
                     bool GetCurrentValidationData(void);
                     //Initialise the LinearRegressor Model
                     void Init(int _fetch,int _look_ahead);
                     //Function to determine if the model has been trained and is ready for use.
                     bool Trained(void);
                     //A function to train the model using the best learning rate and the most recent prices
                     bool Train(void);
                     //A function to predict future price using the current price.
                     double Predict(void);
                     //Destructor
                    ~LinearRegression();
  };

bool LinearRegression::UpdateEpochs(void)
   {
      epochs_power = epochs_power + 1;
      epochs = MathPow(10,epochs_power);
      return(true);
   }

bool LinearRegression::ResetEpochs(void)
   {
      epochs_power = 0 ;
      epochs = MathPow(10,epochs_power);
      return(true);
   }

bool LinearRegression::SetEpochs(int _epochs_power)
   {
      epochs_power = _epochs_power;
      epochs = MathPow(10,epochs_power);
      return(true);
   }

double LinearRegression::Predict(void)
   {
      if(Trained())
         {
            double _current_reading = iClose(_Symbol,PERIOD_CURRENT,0);
            predict = iTime(_Symbol,PERIOD_CURRENT,0);
            
            double prediction = (m[0]*_current_reading)+b[0];
            if(prediction > _current_reading)
               {
                  Comment("Buy, forecast: ",prediction);
               }
            else if(prediction < _current_reading)
               {
                  Comment("Sell, forecast: ",prediction);
               }
            
            ObjectCreate(0,"prediction point",OBJ_VLINE,0,predict,0);
            ObjectCreate(0,"forecast",OBJ_HLINE,0,predict,prediction);
            return(prediction);
         }
         
      return(0);
   }

bool LinearRegression::GetCurrentValidationData(void)
   {
      //Indexes
      index_output_end = 1;
      index_output_start = index_output_end + fetch;
      index_input_end = index_output_end + look_ahead;
      index_input_start = index_output_start + look_ahead; 
      
      //Assigning time stamps
      output_end =  iTime(Symbol(),PERIOD_CURRENT,index_output_end);
      output_start = iTime(Symbol(),PERIOD_CURRENT,index_output_start);
      input_end = iTime(Symbol(),PERIOD_CURRENT,index_input_end);
      input_start = iTime(Symbol(),PERIOD_CURRENT,index_input_start);
      
      //Get the output data
      if(!y_validation.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,output_end,fetch))
         {
            Print("Failed to get market data: ",GetLastError());
            return(false);
         }
      //Get the input data
      if(!x_validation.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,input_end,fetch))
         {
            Print("Failed to get market data: ",GetLastError());
            return(false);
         }
      //Print the vectors we have
      if(x_validation.Size() != y_validation.Size())
         {
            Print("Failed to get market data: Our vectors aren't the same length.");
            return(false);
         }
         
         //Print the vectors and plot the data points
         Print("X validation: ",x_validation);
         ObjectCreate(0,"X validation end",OBJ_VLINE,0,input_end,0);
         ObjectCreate(0,"X validation start",OBJ_VLINE,0,input_start,0);
         
         //Print the vectors and plot the data points
         Print("y validation: ",y_validation);
         ObjectCreate(0,"y validation end",OBJ_VLINE,0,output_end,0);
         ObjectCreate(0,"y validation start",OBJ_VLINE,0,output_start,0);
         
         //Set the training data
         index_output_end = index_input_start + (look_ahead * 2);
         index_output_start = index_output_end + fetch;
         index_input_end = index_output_end + look_ahead;
         index_input_start = index_output_start + look_ahead; 
         
         //Assigning time stamps
         output_end =  iTime(Symbol(),PERIOD_CURRENT,index_output_end);
         output_start = iTime(Symbol(),PERIOD_CURRENT,index_output_start);
         input_end = iTime(Symbol(),PERIOD_CURRENT,index_input_end);
         input_start = iTime(Symbol(),PERIOD_CURRENT,index_input_start);
         
         //Copy the training data   
         if(!y_train.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,output_end,fetch))
            {
               Print("Error fetching training data ",GetLastError());
            }
            
         //Copy the training data   
         if(!x_train.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,input_end,fetch))
            {
               Print("Error fetching training data ",GetLastError());
            }
         
         //Check if the data matches
         if(x_train.Size() != y_train.Size())
            {
               Print("Error fetching training dataL: The x and y vectors are not the same size");
            }
           
           //Print the vectors and plot the data points 
            Print("X training: ",x_train);
            ObjectCreate(0,"X training end",OBJ_VLINE,0,input_end,0);
            ObjectCreate(0,"X training start",OBJ_VLINE,0,input_start,0);
            
            Print("y training: ",y_train);
            ObjectCreate(0,"y training end",OBJ_VLINE,0,output_end,0);
            ObjectCreate(0,"y training start",OBJ_VLINE,0,output_start,0);
            return(true);
   }

bool LinearRegression::Train(void)
   {
      m = vector::Zeros(1);
      //Set the bias to a random value
      b = vector::Zeros(1);
      forecast = 0;
      
      if(GetCurrentValidationData())
         {
            if(Fit())
               {
                  Print("Model last updated: ",iTime(_Symbol,PERIOD_CURRENT,0));
                  return(true);
               }
         }
      return(false);
   }

void LinearRegression::Init(int _fetch,int _look_ahead)
   {
      //Clear The Chart
      ObjectsDeleteAll(0);      
      //Allow evaluations
      allowed_to_evaluate = true;
      //Epochs power
      epochs_power =4;
      //Set the number of epochs
      epochs = 5 * MathPow(10,epochs_power);
      //Has the model been trained?
      trained = false;
      //Set the maximum learning rate power
      max_learning_rate_power = 30;
      //Set the end of our validation data
      start = iTime(_Symbol,PERIOD_CURRENT,1);
      //This is how much data we're going to fetch
      this.fetch = _fetch - 1;
      //This is how far into the future we want to forecast
      this.look_ahead = _look_ahead + 1;
      //Set the gradient coefficient to a random value
      m = vector::Zeros(1);
      //Set the bias to a random value
      b = vector::Zeros(1);
      //Set the forecast to 0
      forecast = 0;
      //Our model's learning rate will start at 0
      learning_rate_power = 0;
      //This is the learning rate we are evaluting
      lr_error_index = 0;
      mae_train = vector::Full(1,MathPow(10,100));
      mae_validation = vector::Full(30,MathPow(10,10000));
      //Set the initial learning rate
      learning_rate = MathPow(0.1,(learning_rate_power));
      //Set the number of rows
      n = fetch;
      if(GetCurrentValidationData())
         {
            //Scale the data
            ScaleInputs();
            //Fit the model
            Fit();   
         }      
   }

bool LinearRegression::Trained(void)
   {
      return(trained);
   }

bool LinearRegression::SetLearningRate(ulong _learning_rate_power)
   {
       learning_rate_power = _learning_rate_power;
       learning_rate = MathPow(0.1,(learning_rate_power));
       return(true);
   }

bool LinearRegression::UpdateLearningRate(void)
   {
         learning_rate_power = learning_rate_power + 1;
         learning_rate = MathPow(0.1,(learning_rate_power));
         Print("New learning rate: ",learning_rate," learning rate power: ",learning_rate_power);
         return(true);
   }

bool LinearRegression::ResetLearningRate(void)
   {
         learning_rate_power = 0;
         learning_rate = MathPow(0.1,learning_rate_power);
         return(true);
   }

LinearRegression::LinearRegression()
  {
            
      Print("Current Symbol: ",_Symbol);
  }

bool LinearRegression::Fit()
   {
      Print("Fitting a linear regression on the training set with learning rate ",learning_rate_power);

      Print("Evalutaions: ",allowed_to_evaluate);
      for(int i =0; i < epochs;i++)
         {
            //Measure error
            y_hat_train = (m[0]*x_train) + b[0];
            vector y_minus_y_hat = (y_train - y_hat_train);
            vector y_minus_y_hat_sqaured = MathAbs((y_train - y_hat_train));
            mae_train.Set(0,( y_minus_y_hat_sqaured.Mean()));
            vector x_times_y_minus_y_hat = (x_train*(y_train -y_hat_train));
            
            //Aproximate the derivatives
            double derivative_m = (-2.0/n) * x_times_y_minus_y_hat.Sum();
            double derivative_b = (-2.0/n) * y_minus_y_hat.Sum();
            
            //Update the linear parameters
            m[0] = m[0] - (learning_rate * derivative_m);
            b[0] = b[0] - (learning_rate * derivative_b);
         }
         
         //Finished fitting the coefficients
         Print("Fit on training data complete.\nm: ",m[0]," b: ",b[0]," mae ",mae_train[0],"\nlearning rate: ",learning_rate);
         
         if(allowed_to_evaluate)
            {
               Evaluate(learning_rate_power);
            }
            
         //Return true
         return(true);
   }

//This function evaluates the current coefficient settings and learning rate
bool LinearRegression::Evaluate(ulong _index)
   {
      Print("Evaluating the coefficients m:",m[0]," b: ",b[0]," at learning rate: ",learning_rate);
      
      //First check if the coefficient and learning rate are valid
      if((m.HasNan() > 0 || b.HasNan() > 0 || m[0] == 0 || b[0] == 0 || _index > max_learning_rate_power) && (_index < max_learning_rate_power))
         {
            Print("Coefficients are invalid");
            m[0] = 0;
            b[0] = 0 ;
            mae_array[_index] = MathPow(10,100000);
            //Update the learning rate
            UpdateLearningRate();
            //Fit the model again
            Fit();   
         }
         
      else
         {
            //Validation predictions
            if(_index < max_learning_rate_power)
               {
                  Print("Coefficients are valid, solution at index ",_index);
                  y_hat_validation = (m[0] * x_validation) + b[0];   
                  vector y_minus_y_hat_squared = MathAbs(y_validation - y_hat_validation);
                  //If everything is fine, let's assess the validation mae
                  mae_array[_index] = (1.0/n) * y_minus_y_hat_squared.Sum();
                  //What was the validation error?
                  Print("Validation error: ",(1.0/n) * y_minus_y_hat_squared.Sum());
                  //Update the learning rate
                  UpdateLearningRate();
                  //Fit the model again
                  Fit();   
               }
         }  
         
      if(_index == max_learning_rate_power)
         {
            for(int i = 0; i < max_learning_rate_power;i++)
               {
                  mae_validation[i] = mae_array[i];   
               }
            allowed_to_evaluate = false;
            trained = true;
            Print("Validation mae: \n",mae_validation);
            Print("Lowest validation mae: ",mae_validation.Min());
            ulong chosen_learning_rate = mae_validation.ArgMin();
            Print("Chosen learning rate ",MathPow(0.1,(chosen_learning_rate)));
            SetLearningRate(chosen_learning_rate);
            Fit();
         }
         
         return(true);
   }

//This function will scale our inputs
bool LinearRegression::ScaleInputs(void)
   {
      //Set the first reading
      first_reading = x_train[0];
      x_train = x_train / first_reading;
      x_validation = x_validation / first_reading;
      return(true);
   }

LinearRegression::~LinearRegression()
  {
      ResetLearningRate();
      ResetEpochs();
      ResetLastError();
  }

Ahora que hemos definido nuestra clase `LinearRegression`, estamos listos para utilizarla en nuestro asesor experto.

Comenzamos creando un nuevo asesor experto e incluyendo la clase en nuestro asesor experto.

#property copyright "Gamuchirai Zororo Ndawana"
#property link      "https://www.mql5.com"
#property version   "1.00"

//Include our linear regression class
#include  <LinearRegression/LinearRegression.mqh>
LinearRegression ExtLinearRegression;

El código anterior llama al constructor por defecto de nuestra clase `LinearRegression`.

A partir de ahí también incluimos otras clases útiles.

//Include the trade class
#include  <Trade/Trade.mqh>
CTrade Trade;

Definimos las entradas que necesita nuestro asesor experto.

//Inputs
int input look_ahead = 10; //How many steps into the future should we forecast?
int input fetch_data = 100; //How much data should we fetch?
int input ma_period = 10;  //Moving Average Period
int input rsi_period = 10; //RSI Period
int input wr_period = 10;  //Williams Percent R Period

También definiremos otras variables útiles para el análisis técnico, como el volumen mínimo de negociación permitido y los vectores para almacenar los buffers de nuestros indicadores.

//Technical Analysis
double min_volume =SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
//Indicator Handlers
int ma_handler,rsi_handler,wr_handler,total_time;
vector ma_vector,rsi_vector,wr_vector;
double _price;
ulong _ticket;

Una vez completado, estamos listos para definir el manejador `OnInit()` de nuestro asesor experto. Este manejador inicializa nuestro objeto de regresión lineal utilizando los parámetros que el usuario ha pasado al asesor experto y luego configura nuestros indicadores técnicos.

int OnInit()
  {
   //Setup our model
   ExtLinearRegression.Init(fetch_data,look_ahead);
   //Keep Track Of Time
   total_time = 0;
   //Set up our technical indicators
   ma_handler = iMA(_Symbol,PERIOD_CURRENT,ma_period,0,MODE_EMA,PRICE_CLOSE);
   rsi_handler = iRSI(_Symbol,PERIOD_CURRENT,rsi_period,PRICE_CLOSE);
   wr_handler = iWPR(_Symbol,PERIOD_CURRENT,wr_period);
   return(INIT_SUCCEEDED);
  }

Ahora llegamos a la función `OnTick()`. La función `OnTick()` realiza un seguimiento del tiempo, lo que nos permite ejecutar ciertas acciones después de cada nueva vela y otras acciones después de cada tick. En cada nueva vela, si el número total de velas que han transcurrido es mayor que el horizonte de previsión seleccionado por el usuario, debemos entrenar nuevamente nuestro modelo utilizando la función `Train` que implementamos. Además, nos gustaría actualizar los valores de los indicadores que tenemos registrados utilizando otra función de vector útil: `CopyIndicatorBuffer()`. Hemos creado una función encargada de ello. Finalmente, si tenemos una posición abierta, hemos creado una función encargada de gestionar las posiciones abiertas.

void OnTick()
  {
//---
         
      static datetime time_stamp;
      datetime current_time = iTime(_Symbol,PERIOD_CURRENT,0);
      
      if(time_stamp != current_time)
         {
            //Update the values of the indicators
            update_vectors();
            total_time += 1;
            
            if(total_time > look_ahead)
               {
                  total_time = 0;
                  //Let the model adapt to the market dynamically
                  ExtLinearRegression.Train();
               }
            
            //If our model is ready then let's start trading
            if(ExtLinearRegression.Trained())
               {
                  if(PositionsTotal() == 0)
                     {
                        analyse_indicators();   
                     }
               }
               
            if(PositionsTotal() == 1)
               {
                  //Get position ticket
                  _ticket = PositionGetTicket(0);
                  //Manage the position
                  manage_position(_ticket);
               }
            time_stamp = current_time;
         }
  }

Esta función se encarga de obtener las velas más actualizadas disponibles de nuestro bróker.

void update_vectors(void)
   {
            //Get the current reading of our indicators
            ma_vector.CopyIndicatorBuffer(ma_handler,0,1,1);
            rsi_vector.CopyIndicatorBuffer(rsi_handler,0,1,1);
            wr_vector.CopyIndicatorBuffer(wr_handler,0,1,1);
            _price = iClose(_Symbol,PERIOD_CURRENT,1);
   }
   

Esta función se encarga de interpretar nuestros indicadores y la previsión de nuestro modelo. Si todos están alineados, entonces podemos abrir una operación; de lo contrario, esperaremos a que se alineen.

void analyse_indicators(void)
   {
         double forecast = ExtLinearRegression.Predict();
         Comment("Forecast: ",forecast," Price: ",_price);
         //If price is above the moving average, check if the other indicators also confirm the buy signal
         if(_price - ma_vector[0] > 0)
            {
               if(rsi_vector[0] > 50)
                  {
                     if(wr_vector[0] > -20)
                        {
                            if(forecast > _price)
                                {
                                  Trade.Buy(min_volume,_Symbol,SymbolInfoDouble(_Symbol,SYMBOL_ASK),0,0);
                                }
                        }
                  }
            }
            
         //If price is below the moving average, check if the other indicators also confirm the sell signal
         if(_price - ma_vector[0] < 0)
            {
               if(rsi_vector[0] < 50)
                  {
                     if(wr_vector[0] < -80)
                        {
                           if( forecast < _price)
                              {
                                 Trade.Sell(min_volume,_Symbol,SymbolInfoDouble(_Symbol,SYMBOL_BID),0,0);
                              }
                        }
                  }
            }

Esta función se encarga de gestionar las posiciones abiertas que tengamos y de establecer el Stop Loss y el Take Profit de forma dinámica en función de los niveles de volatilidad actuales del mercado. Observe que solo se modificará la posición si esta no tiene Stop Loss o Take Profit.

void manage_position(ulong m_ticket)
   {
      if(PositionSelectByTicket(m_ticket))
         {
            double volatility =  2 * MathAbs(ma_vector[0] - _price);
            double entry = PositionGetDouble(POSITION_PRICE_OPEN);
            double current_sl = PositionGetDouble(POSITION_SL);
            double current_tp = PositionGetDouble(POSITION_TP);
            
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
               {
                  double new_sl = _price - volatility;
                  double new_tp = _price + volatility;
               
                  if(current_sl == 0 || current_tp == 0)
                     {
                        Trade.PositionModify(m_ticket,new_sl,new_tp);
                     }
               }

            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
               {
                  double new_sl = _price + volatility;
                  double new_tp = _price - volatility;
               
                  if(current_sl == 0 || current_tp == 0)
                     {
                        Trade.PositionModify(m_ticket,new_sl,new_tp);
                     }
               }
         }
   }

Nuestro asesor experto ahora se ve así:

`LinearRegression` EA.

Fig. 3: Asesor experto autoajustable.


Entradas del asesor experto autoajustable.

Fig. 4: Entradas para nuestro asesor experto autoajustable.


Siempre que apliques el asesor experto en cualquier símbolo, podrás ver los cálculos que está realizando en la pestaña `Expertos`.


Los cálculos realizados por nuestro asesor experto.

Fig. 5: Los cálculos realizados por nuestro asesor experto.


Backtesting de nuestro EA.

FIg 6: Backtesting de nuestro EA.


Recomendaciones

Este artículo enseña el método más simple posible de construir un asesor experto autooptimizable. Sin embargo, este no es el mejor enfoque posible; se trata de una búsqueda manual de coeficientes óptimos. La solución ideal emplearía cálculos matriciales y vectoriales más avanzados para encontrar automáticamente los coeficientes óptimos. De hecho, cuando usamos funciones de matrices y vectores, podemos construir nuestro modelo de regresión lineal sin necesidad de usar ningún bucle `for`. Nuestro código será más compacto y nuestros coeficientes serán más estables numéricamente. Las búsquedas manuales no siempre garantizan soluciones.


Conclusión

Construir asesores expertos autoajustables en MQL5 es sencillo gracias a las potentes funciones de matrices y vectores en la API de MQL5. En realidad, lo que podemos construir en MQL5 está únicamente limitado por nuestra comprensión de la API.

 

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

Archivos adjuntos |
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.
Operar con noticias de manera sencilla (Parte 1): Creando una base de datos Operar con noticias de manera sencilla (Parte 1): Creando una base de datos
Operar con noticias puede ser complicado y abrumador, en este artículo repasaremos los pasos para obtener datos de noticias. Además, conoceremos el calendario económico de MQL5 y lo que ofrece.
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.
Operaciones angulares para tráders Operaciones angulares para tráders
En este artículo se analizarán las operaciones angulares. Veremos varios métodos para construir ángulos y cómo aplicarlos en el trading.