English Русский 中文 Deutsch 日本語 Português
preview
Previsión usando modelos ARIMA en MQL5

Previsión usando modelos ARIMA en MQL5

MetaTrader 5Sistemas comerciales | 25 septiembre 2023, 17:13
568 0
Francis Dube
Francis Dube

Introducción

El artículo "Implementando el algoritmo de aprendizaje ARIMA en MQL5" describe la clase CArima para construir modelos ARIMA. Aunque técnicamente resulta posible utilizar la clase para la aplicación y predicción de modelos, no puede calificarse de intuitiva. En este artículo, abordaremos esta carencia y ampliaremos la clase para simplificar el uso de las técnicas de aplicación de modelos para la predicción. Hoy discutiremos algunas de las complejidades que implica la implementación de predicciones, así como algunas de las nuevas características añadidas a la clase. Por último, usaremos la clase completa para construir un modelo de predicción de precios de divisas, aplicándolo a un asesor y a un indicador.


Serie de parámetros de entrada

Es bien sabido que los modelos ARIMA se basan en las dependencias temporales del conjunto de datos, así que, para hacer una o varias predicciones, tendremos que dar al modelo una serie de datos de entrada. La especificación del modelo define el tamaño mínimo de la serie de entrada. Sabiendo esto, resulta obvio que si la serie de entrada es incorrecta, no podremos realizar predicciones, o al menos las predicciones no reflejarán el modelo aplicado. Los distintos tipos de modelos ARIMA tienen diferentes requisitos en cuanto al tamaño de la serie de entrada, además del orden del modelo.

La implementación de predicciones para modelos autorregresivos puros es trivial, ya que todo lo que se necesita son datos de entrada iguales al mayor retraso del modelo. Los modelos mixtos que usan los términos de media móvil plantean problemas a la hora de hacer previsiones. Aún no tenemos una serie real de errores o innovaciones. Para superar dicha limitación, primero deberemos decidir cómo se calcularán los valores de error iniciales.

Este proceso implicará el uso, en primer lugar, de los parámetros disponibles del modelo para obtener un estado inicial del modelo que excluya cualquier término de media móvil, ya que se supone que en esta fase son iguales a 0. A continuación, se utilizarán los valores conocidos de la serie para calcular los valores de error iniciales enumerando cíclicamente una serie de predicciones redundantes. Estas proyecciones iniciales son redundantes porque no tendrán nada que ver con las previsiones finales que nos interesan en última instancia. Obviamente, esto plantea mayores exigencias en cuanto a la cantidad de datos de entrada necesarios para la predicción. En este caso, resultará importante estimar cuántos ciclos de previsión redundantes habrá que realizar para obtener valores de series de error adecuados que produzcan previsiones fiables.

No obstante, deberemos tener en cuenta además la cantidad de datos de entrada del modelo. La clase CArima tiene la capacidad de especificar modelos con retrasos no adyacentes. Esto plantea requisitos aún más exigentes en cuanto a la cantidad de datos de entrada necesarios. En este caso, el mayor retardo de cualquier tipo (AR/MA) se sumará a los requisitos de tamaño de los datos de entrada. Vamos a analizar un modelo definido por la función                                                                                             

price(t) = constant_term + AR*price(t-4)

Esta función especifica un modelo AR de un solo término con un retraso igual a cuatro. Esto significa que el precio actual estará parcialmente determinado por el valor de los cuatro intervalos temporales anteriores a este. Aunque solo necesitamos un valor de este tipo, deberemos acordarnos de conservar las relaciones temporales de los datos de entrada. Así que en lugar de un requisito de entrada, necesitaremos cuatro, además de los demás requisitos del modelo. El determinante final del tamaño de la serie de datos de entrada dependerá de si se requiere alguna diferenciación.


Contabilizando la diferenciación

Los requisitos de diferenciación aumentan la cantidad de datos de entrada necesarios, no debido a factores relacionados con el cálculo del valor o los valores predichos, sino porque la diferenciación provoca una pérdida de información. Al crear una serie diferenciada, siempre será una unidad más corta en longitud con respecto a la serie original. Esta serie más corta se transmitirá finalmente como la entrada al modelo. Por ello, en general, se requieren datos de entrada adicionales para compensar la coincidencia con el orden de diferenciación especificado por el modelo.

Además del efecto que la diferenciación tiene sobre el tamaño de los datos de entrada, también afectará a las predicciones finales realizadas, ya que estarán en el dominio diferenciado. Las predicciones junto con las series de entrada ofrecidas deberán combinarse e integrarse para retornar los valores combinados al dominio original.


Previsión a largo plazo

En ocasiones, los usuarios pueden estar interesados en predecir múltiples intervalos temporales en el futuro basándose en un único conjunto de datos de entrada. Aunque no se recomienda hacer esto, merece la pena analizar dicho método. Para realizar predicciones a largo plazo, deberemos tener en cuenta ciertos axiomas. En primer lugar, cuanto más avancemos, con mayor frecuencia tendremos que utilizar las predicciones de intervalos temporales anteriores como entradas para cualquier condición autorregresiva. Una vez que vayamos más allá de los valores conocidos de las series utilizadas como entradas iniciales, ya no dispondremos de medios para calcular los valores de error porque desconoceremos los verdaderos valores futuros. En consecuencia, las condiciones de la media móvil para dichos intervalos de tiempo serán nulas, y solo podrán suponerse como tales. Esto hará que la serie pronosticada degenere o bien en un proceso autorregresivo puro, si se especifica, o en un proceso definido únicamente por un término constante. La realización de múltiples previsiones a largo plazo debe hacerse reconociendo las limitaciones inherentes al proceso.

Adiciones a la clase CArima

El primer cambio realizado en la clase se relaciona con su método padre CPowellsMethod. El método Optimize() está ahora protegido por un modificador de acceso y no se puede acceder a él desde fuera de la clase. Naturalmente, este cambio se aplicará también a CArima. Debido a esta modificación, el nombre del fichero de inclusión que contiene la clase CPowellsMethod ha sido modificado a Powells.mqh.

//-----------------------------------------------------------------------------------
// Minimization of Functions.
// Unconstrained Powell’s Method.
// References:
// 1. Numerical Recipes in C. The Art of Scientific Computing.
//-----------------------------------------------------------------------------------
class PowellsMethod:public CObject
  {
protected:
   double            P[],Xi[];
   double            Pcom[],Xicom[],Xt[];
   double            Pt[],Ptt[],Xit[];
   int               N;
   double            Fret;
   int               Iter;
   int               ItMaxPowell;
   double            FtolPowell;
   int               ItMaxBrent;
   double            FtolBrent;
   int               MaxIterFlag;
   int               Optimize(double &p[],int n=0);
public:
   void              PowellsMethod(void);
   void              SetItMaxPowell(int n)           { ItMaxPowell=n; }
   void              SetFtolPowell(double er)        { FtolPowell=er; }
   void              SetItMaxBrent(int n)            { ItMaxBrent=n;  }
   void              SetFtolBrent(double er)         { FtolBrent=er;  }
   double            GetFret(void)                   { return(Fret);  }
   int               GetIter(void)                   { return(Iter);  }
private:
   void              powell(void);
   void              linmin(void);
   void              mnbrak(double &ax,double &bx,double &cx,double &fa,double &fb,double &fc);
   double            brent(double ax,double bx,double cx,double &xmin);
   double            f1dim(double x);
   virtual double    func(const double &p[]) { return(0); }
  };


Una característica importante añadida a la clase es la posibilidad de almacenar y cargar modelos. Esto nos permitirá entrenar y guardar un modelo para su uso posterior o su inclusión en cualquier otro programa MQL5.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CArima::SaveModel(const string model_name)
  {
   uint model_order[]= {m_const,m_ar_order,m_diff_order,m_ma_order};

   CFileBin file;
   ResetLastError();
   if(!file.Open("models\\"+model_name+".model",FILE_WRITE|FILE_COMMON))
     {
      Print("Failed to save model.Error: ",GetLastError());
      return false;
     }

   m_modelname=(m_modelname=="")?model_name:m_modelname;

   long written=0;

   written = file.WriteIntegerArray(model_order);

   if(!written)
     {
      Print("Failed write operation, ",__LINE__,".Error: ",GetLastError());
      return false;
     }

   if(m_ar_order)
     {
      written = file.WriteIntegerArray(m_arlags);

      if(!written)
        {
         Print("Failed write operation, ",__LINE__,".Error: ",GetLastError());
         return false;
        }
     }

   if(m_ma_order)
     {
      written = file.WriteIntegerArray(m_malags);

      if(!written)
        {
         Print("Failed write operation, ",__LINE__,".Error: ",GetLastError());
         return false;
        }
     }

   written = file.WriteDoubleArray(m_model);

   if(!written)
     {
      Print("Failed write operation, ",__LINE__,".Error: ",GetLastError());
      return false;
     }

   written = file.WriteDouble(m_sse);

   if(!written)
     {
      Print("Failed write operation, ",__LINE__,".Error: ",GetLastError());
      return false;
     }


   file.Close();

   return true;

  }


El método SaveModel permite guardar modelos, para ello, deberemos introducir una cadena, que será el nuevo nombre del modelo. El método en sí se escribirá en el archivo binario .model almacenado en el directorio de modelos de la carpeta de archivos compartidos (Terminal\Common\Files\models). El archivo guardado contendrá el orden del modelo, así como sus parámetros si ha sido entrenado, incluyendo el valor de la suma de errores cuadráticos (sse).

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CArima::LoadModel(const string model_name)
  {
   int found=StringFind(model_name,".model");

   if(found>=0)
      m_modelname=StringSubstr(model_name,0,found);
   else
      m_modelname=model_name;

   if(StringFind(m_modelname,"\\")>=0)
      return false;

   string filename="models\\"+m_modelname+".model";

   if(!FileIsExist(filename,FILE_COMMON))
     {
      Print("Failed to find model, ",__LINE__,".Error: ",GetLastError());
      return false;
     }

   CFileBin file;
   ResetLastError();
   if(file.Open(filename,FILE_READ|FILE_COMMON)<0)
     {
      Print("Failed open operation, ",__LINE__,".Error: ",GetLastError());
      return false;
     }

   uint model_order[];


   file.Seek(0,SEEK_SET);

   if(!file.ReadIntegerArray(model_order,0,4))
     {
      Print("Failed read operation, ",__LINE__,".Error: ",GetLastError());
      return false;
     }

   m_const=bool(model_order[0]);
   m_ar_order=model_order[1];
   m_diff_order=model_order[2];
   m_ma_order=model_order[3];

   file.Seek(sizeof(uint)*4,SEEK_SET);

   if(m_ar_order && !file.ReadIntegerArray(m_arlags,0,m_ar_order))
     {
      Print("Failed read operation, ",__LINE__,".Error: ",GetLastError());
      return false;
     }

   if(!m_ar_order)
      ArrayFree(m_arlags);



   if(m_ar_order)
      file.Seek(sizeof(uint)*(4+m_ar_order),SEEK_SET);


   if(m_ma_order && !file.ReadIntegerArray(m_malags,0,m_ma_order))
     {
      Print("Failed read operation, ",__LINE__,".Error: ",GetLastError());
      return false;
     }

   ArrayPrint(m_malags);

   if(!m_ma_order)
      ArrayFree(m_malags);


   if(m_ar_order || m_ma_order)
      file.Seek(sizeof(uint)*(4+m_ar_order+m_ma_order),SEEK_SET);



   if(!file.ReadDoubleArray(m_model,0,m_ma_order+m_ar_order+1))
     {
      Print("Failed read operation, ",__LINE__,".Error: ",GetLastError());
      return false;
     }

   file.Seek(sizeof(uint)*(4+m_ar_order+m_ma_order) + sizeof(double)*ArraySize(m_model),SEEK_SET);

   if(!file.ReadDouble(m_sse))
     {
      Print("Failed read operation, ",__LINE__,".Error: ",GetLastError());
      return false;
     }

   if(m_model[1])
      m_istrained=true;
   else
      m_istrained=false;

   ZeroMemory(m_differenced);
   ZeroMemory(m_leads);
   ZeroMemory(m_innovation);

   m_insize=0;

   return true;
  }


El método LoadModel requerirá un nombre de modelo y leerá todos los atributos de un modelo previamente guardado. Ambos métodos retornarán true o false, mientras que los mensajes de error útiles se escribirán en el diario de registro del terminal.

string            GetModelName(void)                      { return m_modelname;}

El método GetModelName() retornará el nombre del modelo. Así, retornarán una cadena vacía si el modelo nunca se ha guardado; en caso contrario, devolverá el nombre que se le dio al almacenar el modelo.

//+------------------------------------------------------------------+
//| calculate the bayesian information criterion                     |
//+------------------------------------------------------------------+
double CArima::BIC(void)
  {
   if(!m_istrained||!m_sse)
     {
      Print(m_modelname," Model not trained. Train the model first to calculate the BIC.");
      return 0;
     }

   if(!m_differenced.Size())
     {
      Print("To calculate the BIC, supply a training data set");
      return 0;
     }
   uint n = m_differenced.Size();
   uint k = m_ar_order+m_ma_order+m_diff_order+uint(m_const);

   return((n*MathLog(m_sse/n)) + (k*MathLog(n)));

  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double CArima::AIC(void)
  {
   if(!m_istrained||!m_sse)
     {
      Print(m_modelname," Model not trained. Train the model first to calculate the AIC.");
      return 0;
     }

   if(!m_differenced.Size())
     {
      Print("To calculate the AIC, supply a training data set");
      return 0;
     }

   uint n = m_differenced.Size();
   uint k = m_ar_order+m_ma_order+m_diff_order+uint(m_const);

   return((2.0*k)+(double(n)*MathLog(m_sse/double(n))));
  }
//+------------------------------------------------------------------+


También hemos añadido los métodos BIC y AIC. El método BIC retornará el criterio de información bayesiano basado en el valor del modelo sse. El método AIC calculará el criterio de información de Akaike y funcionará de forma similar a la función BIC. El criterio de Información Bayesiano (BIC) y el criterio de información de Akaike (AIC) son las medidas estadísticas utilizadas para la selección de modelos. Ambos criterios pretenden equilibrar la calidad del ajuste del modelo con su complejidad, de forma que se favorezcan los modelos más sencillos si se ajustan a los datos casi tan bien como los modelos más complejos.

El BIC y el AIC difieren en la manera en que equilibran la calidad y la complejidad del ajuste. El BIC presta más atención a la simplicidad del modelo que el AIC, lo cual significa que favorecerá modelos aún más simples que el AIC. Por otra parte, es más probable que el AIC elija modelos más complejos que el BIC. En pocas palabras, el BIC y el AIC nos permiten comparar distintos modelos y seleccionar el que mejor se ajuste a nuestros datos, teniendo en cuenta la complejidad del modelo. Seleccionando un modelo más sencillo, podremos evitar el sobreajuste que se produce cuando un modelo resulta demasiado complejo y se ajusta demasiado a los datos, lo cual lo hace menos útil para predecir nuevas observaciones.

Ambos retornarán 0 en caso de error y deberán llamarse inmediatamente después de entrenar el modelo mientras los datos de entrenamiento aún están cargados. Al inicializarse el modelo entrenado, los datos usados para el entrenamiento no estarán disponibles, por lo que no se podrán calcular ni el BIC ni el AIC.

uint              GetMinModelInputs(void)                 { return(m_diff_order + GetMaxArLag() + (GetMaxMaLag()*m_infactor));}

Ahora también podremos solicitar la cantidad mínima de datos de entrada que requiere el modelo. Para ello usaremos el método GetMinModelInputs().

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CArima::Summary(void)
  {

   string print = m_modelname+" Arima("+IntegerToString(m_ar_order)+","+IntegerToString(m_diff_order)+","+IntegerToString(m_ma_order)+")\n";
   print+= "SSE : "+string(m_sse);

   int k=0;
   if(m_const)
      print+="\nConstant: "+string(m_model[k++]);
   else
      k++;
   for(uint i=0; i<m_ar_order; i++)
      print+="\nAR coefficient at lag "+IntegerToString(m_arlags[i])+": "+string(m_model[k++]);
   for(uint j=0; j<m_ma_order; j++)
      print+="\nMA coefficient at lag "+IntegerToString(m_malags[j])+": "+string(m_model[k++]);

   Print(print);

   return;

  }


Por último, la llamada a Summary() escribirá los atributos del modelo en el terminal. La cadena ya no se retornará.

Realización de previsiones

   bool              Predict(const uint num_pred,double &predictions[]);
   bool              Predict(const uint num_pred,double &in_raw[], double &predictions[]);
   bool              SaveModel(const string model_name);
   bool              LoadModel(const string model_name);
   double            BIC(void);
   double            AIC(void);

La aplicación del modelo para la predicción se realizará usando dos métodos sobrecargados Predict(). Ambos tomarán como datos de entrada dos parámetros idénticos:

  • num_pred — valor entero que especificará el número de predicciones deseadas.
  • predictions — array de tipo double en el que se imprimirán los valores predichos.
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CArima::Predict(const uint num_pred,double &predictions[])
  {
   if(!num_pred)
     {
      Print("Invalid number of predictions");
      return false;
     }

   if(!m_istrained || !m_insize)
     {
      ZeroMemory(predictions);
      if(m_istrained)
         Print("Model not trained");
      else
         Print("No input data available to make predictions");
      return false;
     }

   ArrayResize(m_differenced,ArraySize(m_differenced)+num_pred,num_pred);

   ArrayResize(m_innovation,ArraySize(m_differenced));

   evaluate(num_pred);

   if(m_diff_order)
     {
      double raw[];
      integrate(m_differenced,m_leads,raw);
      ArrayPrint(raw,_Digits,NULL,m_insize-5);
      ArrayCopy(predictions,raw,0,m_insize+m_diff_order);
      ArrayFree(raw);
     }
   else
      ArrayCopy(predictions,m_differenced,0,m_insize);

   return true;
  }

Los métodos de predicción diferirán en el número de parámetros de la función. El primer método, que requiere dos parámetros, se utilizará para realizar predicciones con la ayuda de los datos de entrenamiento usados para obtener los coeficientes óptimos del modelo.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CArima::Predict(const uint num_pred,double &in_raw[],double &predictions[])
  {
   if(!num_pred)
     {
      Print("Invalid number of predictions");
      return false;
     }

   if(!m_istrained)
     {
      ZeroMemory(predictions);
      Print("Model not trained");
      return false;
     }

   int numofinputs=0;

   if(m_ar_order)
      numofinputs+=(int)GetMaxArLag();
   if(m_ma_order)
      numofinputs+=int(GetMaxMaLag()*m_infactor);
   if(m_diff_order)
      numofinputs+=(int)m_diff_order;

   if(in_raw.Size()<(uint)numofinputs)
     {
      ZeroMemory(predictions);
      Print("Input dataset size inadequate. Size required: ",numofinputs);
      return false;
     }

   ZeroMemory(m_differenced);

   if(m_diff_order)
     {
      difference(m_diff_order,in_raw,m_differenced,m_leads);
      m_insize=m_differenced.Size();
     }
   else
     {
      m_insize=in_raw.Size();
      ArrayCopy(m_differenced,in_raw);
     }


   if(m_differenced.Size()!=(m_insize+num_pred))
      ArrayResize(m_differenced,m_insize+num_pred,num_pred);

   ArrayFill(m_differenced,m_insize,num_pred,0.0);

   if(m_innovation.Size()!=m_insize+num_pred)
      ArrayResize(m_innovation,ArraySize(m_differenced));

   ArrayInitialize(m_innovation,0.0);

   evaluate(num_pred);

   if(m_diff_order)
     {
      double raw[];
      integrate(m_differenced,m_leads,raw);
      ArrayCopy(predictions,raw,0,m_insize+m_diff_order);
      ArrayFree(raw);
     }
   else
      ArrayCopy(predictions,m_differenced,0,m_insize);

   return true;

  }


El segundo método Predict requiere un tercer array de parámetros de entrada que debe contener la serie de datos de entrada que se utilizará para calcular las predicciones. Ambos métodos retornarán un valor booleano y también utilizarán la función privada evaluate.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CArima::evaluate(const uint num_p)
  {

   double pred=0;
   uint start_shift=(m_ma_order)?((!m_innovation[m_insize-1])?m_insize-(GetMaxMaLag()*m_infactor):m_insize):m_insize;
   uint d_size=(uint)ArraySize(m_differenced);

   int p_shift;

   for(uint i=start_shift; i<d_size; i++)
     {
      p_shift=0;
      pred=0;
      if(i>=m_insize)
         m_innovation[i]=0.0;
      if(m_const)
         pred+=m_model[p_shift++];
      for(uint j=0; j<m_ar_order; j++)
         pred+=m_model[p_shift++]*m_differenced[i-m_arlags[j]];
      for(uint k=0; i>=GetMaxMaLag() && k<m_ma_order; k++)
         pred+=m_model[p_shift++]*m_innovation[i-m_malags[k]];
      if(i>=m_insize)
         m_differenced[i]=pred;
      if(i<m_insize)
         m_innovation[i]=pred-m_differenced[i];
     }

   return;
  }


El método evaluate() es similar al método func() con algunas pequeñas diferencias. Este tomará el número deseado de predicciones como único argumento y examinará hasta cinco arrays según la especificación del modelo. Luego calculará nuevas predicciones y añadirá nuevos valores a la serie de errores (innovaciones) si fuera necesario. A continuación, se extraerán los valores predichos y se copiarán en el array objetivo proporcionado al método Predict(). Los métodos de predicción retornarán true en caso de éxito y false en caso de error.


Uso de la clase

Para demostrar al usuario la clase CArima modificada, crearemos un script que entrenará múltiples modelos y almacenará el mejor realizando una búsqueda de fuerza bruta. A continuación, mostraremos cómo podemos utilizar este modelo almacenado en un indicador, utilizándolo para realizar previsiones con un paso de antelación. Por último, utilizaremos el mismo modelo para crear un asesor sencillo.

//+------------------------------------------------------------------+
//|                                                 TrainARModel.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
#include<Arima.mqh>

enum ENUM_QUOTES
  {
   closeprices,//Close price
   medianprices//Mid price
  };

input uint MaximumSearchLag=5;
input bool DifferenceQuotes=true;
input datetime TrainingDataStartDate=D'2020.01.01 00:01';
input datetime TrainingDataStopDate=D'2021.01.01 23:59';
input string Sy="AUDUSD";//Set The Symbol
input ENUM_TIMEFRAMES SetTimeFrame=PERIOD_M1;
input ENUM_QUOTES quotestypes = closeprices;
input string SetModelName = "ModelName";

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   CArima *arima[];
   uint max_it=MaximumSearchLag;
   double sse[],quotes[],mid_prices[];


   if(!max_it)
      ++max_it;


   MqlRates prices[];

   int a_size=CopyRates(Sy,SetTimeFrame,TrainingDataStartDate,TrainingDataStopDate,prices);

   if(a_size<=0)
     {
      Print("downloaded size is ", a_size," error ",GetLastError());
      return;
     }

   ArrayResize(arima,max_it);
   ArrayResize(sse,max_it);
   ArrayResize(quotes,a_size);

   for(uint i=0; i<prices.Size(); i++)
     {

      switch(quotestypes)
        {
         case medianprices:
            quotes[i]=(prices[i].high+prices[i].low)/2;
            break;
         case closeprices:
            quotes[i]=prices[i].close;
            break;
        }
     }

   uint u=0;
   for(uint i=0; i<max_it; i++)
     {
      u=uint(DifferenceQuotes);
      arima[i]=new CArima(i+1,u,0,true);
      if(arima[i].Fit(quotes))
        {
         sse[i]=arima[i].GetSSE()*1.e14;
         Print("Fitting model ",i+1," completed successfully.");
        }
      else
        {
         sse[i]=DBL_MAX;
         Print("Fitting model ",i+1, " failed.");
        }


     }

   int index = ArrayMinimum(sse);
   Print("**** Saved model *****");
   arima[index].Summary();
//save the best model for later use.
   arima[index].SaveModel(SetModelName);

   for(int i=0; i<(int)arima.Size(); i++)
      if(CheckPointer(arima[i])==POINTER_DYNAMIC)
         delete arima[i];

  }
//+------------------------------------------------------------------+


El script facilitará la búsqueda de un modelo autorregresivo puro óptimo que se ajuste a una muestra de precios de cierre. Conviene subrayar que se trata solo de una demostración. Podemos aplicar modelos más complejos con variaciones en el número y los tipos de términos (AR/MA), sin olvidar la posibilidad de especificar retardos no contiguos para dichos términos. Así que las posibilidades son enormes. Por ahora, nos limitaremos a ajustar un modelo autorregresivo puro.


Entrenamiento de modelos


El script permite establecer el orden AR máximo para detener la búsqueda, así como el símbolo, el marco temporal y el periodo de los datos de muestra de precios. Además, será importante utilizar precios de muestra que representen las condiciones que pueden darse al aplicar el modelo a la previsión.

Representación de los parámetros guardados del modelo


El script determinará el modelo óptimo seleccionando el modelo con el valor sse más bajo entre el conjunto de modelos entrenados. El modelo seleccionado se guardará y sus atributos se enviarán al terminal.

//+------------------------------------------------------------------+
//|                                         ArimaOneStepForecast.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
#include<Arima.mqh>
//--- plot PredictedPrice
#property indicator_label1  "PredictedPrice"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- input parameters
input string   ModelName = "Model name";

//--- indicator buffers
uint     NumberOfPredictions=1;
double         PredictedPriceBuffer[];
double         forecast[];
double         pricebuffer[];
uint         modelinputs;

CArima arima;
double mj[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,PredictedPriceBuffer,INDICATOR_DATA);


   ArraySetAsSeries(PredictedPriceBuffer,true);

   if(!arima.LoadModel(ModelName))
      return(INIT_FAILED);
//---
   modelinputs=arima.GetMinModelInputs();

   ArrayResize(pricebuffer,modelinputs);

   ArrayResize(forecast,NumberOfPredictions);

   if(modelinputs<=0)
      return(INIT_FAILED);

   arima.Summary();

   arima.GetModelParameters(mj);

   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   ArraySetAsSeries(time,true);
   int limit = (prev_calculated<=0)?1000-(int)modelinputs-1:rates_total-prev_calculated+1;

   if(NewBar(time[0]))
     {
      for(int i = limit; i>=0; i--)
        {
         if(CopyClose(_Symbol,_Period,i+1,modelinputs,pricebuffer)==modelinputs)
            if(arima.Predict(NumberOfPredictions,pricebuffer,forecast))
               PredictedPriceBuffer[i]=forecast[NumberOfPredictions-1];
        }
     }

//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool NewBar(datetime c_time)
  {
   static datetime prev_time;

   if(c_time>prev_time)
     {
      prev_time=c_time;
      return true;
     }

   return false;
  }
//+------------------------------------------------------------------+


A continuación, el indicador utilizará el modelo seleccionado para hacer predicciones un paso por delante.

Indicador

El modelo especificado se cargará al inicializarse el indicador. A continuación, el método Predict se utilizará en el ciclo principal del indicador para realizar previsiones forward basadas en los datos de precios de cierre proporcionados.

//+------------------------------------------------------------------+
//|                                               ArForecasterEA.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Trade\Trade.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\AccountInfo.mqh>
#include <Arima.mqh>
//---
input string ModelName   ="model name"; // Saved Arima model name
input long   InpMagicNumber = 87383;
input double InpLots          =0.1; // Lots
input int    InpTakeProfit    =40;  // Take Profit (in pips)
input int    InpTrailingStop  =30;  // Trailing Stop Level (in pips)

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int    InpOpenThreshold =1; // Differential to trigger trade (in points)
int    InpStopLoss     = 0;//   Stoploss (in pips)

//---
//+------------------------------------------------------------------+
//| ARIMA Sample expert class                                        |
//+------------------------------------------------------------------+
class CArExpert
  {
protected:
   double            m_adjusted_point;             // point value adjusted for 3 or 5 points
   CTrade            m_trade;                      // trading object
   CSymbolInfo       m_symbol;                     // symbol info object
   CPositionInfo     m_position;                   // trade position object
   CAccountInfo      m_account;                    // account info wrapper
   CArima            *m_arima;                      // Arma object pointer
   uint              m_inputs;                     // Minimum number of inputs for Arma model
   //--- indicator buffers
   double            m_buffer[];                   // close prices buffer
   double            m_pbuffer[1];                 // predicted close prices go here


   //---
   double            m_open_level;


   double            m_traling_stop;
   double            m_take_profit;
   double            m_stop_loss;

public:
                     CArExpert(void);
                    ~CArExpert(void);
   bool              Init(void);
   void              Deinit(void);
   void              OnTick(void);

protected:
   bool              InitCheckParameters(const int digits_adjust);
   bool              InitModel(void);
   bool              CheckForOpen(void);
   bool              LongModified(void);
   bool              ShortModified(void);
   bool              LongOpened(void);
   bool              ShortOpened(void);
  };


El asesor experto aplicará de nuevo el modelo guardado para implementar una estrategia sencilla. Basándonos en la predicción de la siguiente barra, compraremos si el cierre previsto es mayor que el último cierre conocido, y venderemos si los cierres previstos son inferiores a ella. 

Resultados de las pruebas históricas

                                         

  Esto es solo una demostración que no se puede utilizar para operar en una cuenta real.

El código de la clase CArima al completo y sus dependencias se encuentra en el archivo zip adjunto al final del artículo, junto con el script, el indicador y el asesor descritos en el artículo.


Conclusión

Los modelos de autorregresión pueden entrenarse fácilmente con el terminal MetaTrader 5 y utilizarse en todo tipo de programas. La parte más difícil es la especificación y la selección del modelo. Para superar estos problemas y construir modelos autorregresivos eficaces en el análisis del mercado de divisas, los tráders deberán seguir algunas prácticas recomendadas, a saber:
  • Empezar con una pregunta de investigación clara: antes de construir un modelo autorregresivo, los tráders deberán identificar una pregunta de investigación clara y la hipótesis que quieren probar. Esto le ayudará a garantizar que el proceso de modelización se mantenga enfocado y se corresponda con los objetivos del tráder.
  • Recoger datos de alta calidad: la precisión del modelo dependerá en gran medida de la calidad de los datos utilizados. Los tráders deberán utilizar fuentes de datos fiables y asegurarse de que estén limpias, completas y sean pertinentes para su pregunta de investigación.
  • Probar varios modelos: los tráders deberán probar varios modelos con diferentes retrasos y parámetros para determinar el modelo más preciso y eficaz para sus datos.
  • Validar los modelos: tras construir un modelo, los tráders deberán comprobar su precisión utilizando métodos estadísticos.
  • Supervisar y ajustar el modelo: como las condiciones del mercado cambian con el tiempo, la eficacia del modelo también podría cambiar. Los tráders deberán supervisar el rendimiento de su modelo a lo largo del tiempo y realizar los ajustes necesarios para que siga ofreciendo una representación exacta de los futuros movimientos de precio.
Siguiendo estas técnicas avanzadas, los tráders podrán crear modelos autorregresivos eficaces para analizar el mercado de divisas y obtener información valiosa sobre las futuras tendencias del mercado. Espero que el código resulte útil a otros usuarios y que les inspire para ampliar la biblioteca. ¡Buena suerte!


Archivo
Descripción
Mql5/include/Powells.mqh
archivo include que contiene la declaración y definición de la clase CPowellsMethod
Mql5/include/Arima.mqh
archivo include para la clase CArima
Mql5/indicator/ArimaOneStepForecast.mql5
código fuente del indicador que muestra cómo cargar y aplicar el modelo en el indicador
Mql5/scripts/TrainARModel.mql5
Script que muestra cómo entrenar un modelo y guardarlo para su uso posterior.
Mql5/experts/ArForecasterEA.mql5
asesor que muestra el uso del modelo guardado en el asesor.


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

Archivos adjuntos |
Arima.mqh (22.87 KB)
Powells.mqh (17.57 KB)
ArForecasterEA.mq5 (14.83 KB)
TrainARModel.mq5 (2.77 KB)
Mql5.zip (12.64 KB)
Cómo detectar tendencias y patrones de gráficos usando MQL5 Cómo detectar tendencias y patrones de gráficos usando MQL5
El artículo presenta un método para detectar automáticamente patrones de acción del precio usando MQL5, tales como tendencias (ascendentes, descendentes, laterales) y patrones de gráficos (pico doble, valle doble).
Cómo convertirse en un proveedor de señales exitoso en MQL5.com Cómo convertirse en un proveedor de señales exitoso en MQL5.com
El objetivo principal de este artículo es mostrar un camino sencillo y pormenorizado para convertirse en el mejor proveedor de señales en MQL5.com. Basándome en mis conocimientos y experiencia, explicaré lo que cualquier proveedor de señales necesita para tener éxito, incluido cómo encontrar, probar y optimizar una buena estrategia. Además, le ofreceré consejos sobre cómo publicar su señal, escribir una descripción convincente y realizar una promoción y gestión efectivas.
Desarrollamos un indicador Heiken Ashi personalizado utilizando MQL5 Desarrollamos un indicador Heiken Ashi personalizado utilizando MQL5
En este artículo, aprenderemos cómo crear nuestro propio indicador usando MQL5 según nuestras preferencias. Dicho indicador se utilizará en MetaTrader 5 para interpretar gráficos o como parte de asesores expertos.
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 16): Un nuevo sistema de clases Desarrollo de un sistema de repetición — Simulación de mercado (Parte 16): Un nuevo sistema de clases
Precisamos organizarnos mejor. El código está creciendo y si no lo organizamos ahora, será imposible hacerlo después. Así que vamos a dividir para conquistar. El hecho de que MQL5 nos permita usar clases nos ayudará en esta tarea. Pero para hacerlo, es necesario que tengas algún conocimiento sobre algunas cosas relacionadas con las clases. Y tal vez lo que más confunde a los aspirantes y principiantes es la herencia. Así que en este artículo, te mostraré de manera práctica y sencilla cómo usar estos mecanismos.