Previsión usando modelos ARIMA en MQL5
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.
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.
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.
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.
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.
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
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso