![Прогнозирование с помощью моделей ARIMA в MQL5](https://c.mql5.com/2/55/Forecasting_with_ARIMA_models_in_MQL5_600x314.jpg)
Прогнозирование с помощью моделей ARIMA в MQL5
Введение
Статья "Реализация алгоритма обучения ARIMA на MQL5" описывает класс CArima для построения моделей ARIMA. Хотя технически можно использовать класс для применения модели и прогнозирования, его нельзя назвать интуитивно понятным. В этой статье мы устраним этот недостаток и расширим класс, чтобы упростить использование методов применения моделей для прогнозирования. Мы обсудим некоторые сложности, связанные с реализацией прогнозов, а также некоторые новые функции, добавленные в класс. В заключении мы будем использовать полный класс для построения модели для прогнозирования цен на рынке Форекс, применяя его к советнику и индикатору.
Серия входных параметров
Хорошо известно, что модели ARIMA полагаются на временные зависимости в наборе данных. Поэтому, чтобы сделать один или несколько прогнозов, нам нужно передать модели ряд входных данных. Спецификация модели определяет минимальный размер входного ряда. Зная это, становится очевидным, что, если входной ряд неверен, будет невозможно делать какие-либо прогнозы или, по крайней мере, прогнозы не будут отражать применяемую модель. Различные типы моделей ARIMA предъявляют различные требования к размеру входного ряда, помимо порядка модели.
Реализация прогнозов для чистых авторегрессионных моделей тривиальна, поскольку все, что требуется, — это входные данные, равные наибольшему отставанию модели. Смешанные модели, использующие термины скользящего среднего, создают проблемы при составлении прогнозов. У нас пока нет фактических серий ошибок или инноваций. Чтобы преодолеть это ограничение, мы должны сначала решить, как будут рассчитываться начальные значения ошибок.
Этот процесс включает в себя сначала использование любых доступных параметров модели для получения начального состояния модели, которое исключает любые члены скользящего среднего, поскольку на данном этапе они считаются равными 0. Затем известные значения ряда используются для вычисления начальных значений ошибки путем циклического перебора ряда избыточных прогнозов. Эти начальные прогнозы являются избыточными, поскольку они не будут иметь ничего общего с окончательными прогнозами, которые нас в конечном итоге интересуют. Это, очевидно, предъявляет более высокие требования к количеству входных данных, необходимых для прогнозирования. Здесь важно оценить, сколько избыточных циклов прогнозирования необходимо выполнить, чтобы получить подходящие значения серии ошибок для получения достоверных прогнозов.
Тем не менее, есть еще что рассмотреть с точки зрения количества входных данных модели. Класс CArima имеет возможность указывать модели с несмежными лагами. Это предъявляет еще более высокие требования к количеству необходимых входных данных. В этом случае наибольшая задержка любого типа (AR/MA) добавит требования к размеру входных данных. Рассмотрим модель, определяемую функцией
price(t) = constant_term + AR*price(t-4)
Эта функция задает модель с одним термином AR с отставанием в четыре. Это означает, что текущая цена частично определяется значением за четыре временных интервала до этого. Несмотря на то, что нам требуется только одно такое значение, мы должны помнить о сохранении временных отношений входных данных. Поэтому вместо одного входного требования нам нужно четыре, в дополнение к другим требованиям модели. Окончательный определитель размера ряда входных данных зависит от того, требуется ли какое-либо дифференцирование.
Учет дифференцирования
Требования дифференцирования увеличивают количество требуемых входных данных не из-за факторов, связанных с вычислением прогнозируемого значения (значений), а из-за того, что дифференцирование приводит к потере информации. При создании дифференцированного ряда он всегда будет на единицу меньше по длине относительно исходного ряда. Этот более короткий ряд в конечном итоге будет передан в качестве входных данных для модели. Таким образом, в целом для компенсации необходимы дополнительные входные данные, которые соответствуют порядку дифференцирования, указанному моделью.
Помимо влияния, которое дифференцирование оказывает на размер входных данных, оно также влияет на окончательные сделанные прогнозы, поскольку они будут в дифференцированной области. Прогнозы вместе с предоставленным входным рядом должны быть объединены и интегрированы, чтобы вернуть объединенные значения в исходный домен.
Прогнозирование далеко в будущее
Иногда пользователям может быть интересно прогнозировать несколько временных интервалов в будущем на основе одного набора входных данных. Хотя делать это не рекомендуется, этот метод стоит изучить. Делая предсказания далеко в будущее, мы должны помнить об определенных аксиомах. Во-первых, чем дальше мы идем, тем в конечном итоге нам приходится использовать прогнозы из предыдущих временных интервалов в качестве входных данных для любых условий авторегрессии. Как только мы выходим за пределы известных значений ряда, которые используются в качестве начальных входных данных, у нас больше нет средств для вычисления значений ошибок, поскольку истинные будущие значения неизвестны. Следовательно, условия скользящего среднего для таких временных интервалов будут и могут приниматься только равными нулю. Это приводит к тому, что предсказанный ряд вырождается либо в чистый авторегрессионный процесс, если он указан, либо в процесс, определяемый только постоянным членом. Делать множественные прогнозы далеко в будущее следует, осознавая присущие этому процессу ограничения.
Дополнения к классу CArima
Первое изменение, внесенное в класс, относится к его родительскому методу CPowellsMethod. Метод Optimize() теперь защищен модификатором доступа и не может быть доступен извне класса. Естественно, это изменение распространяется и на CArima. В связи с этой модификацией имя включаемого файла, содержащего класс CPowellsMethod, изменено на 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); } };
Важной функцией, добавленной в класс, является возможность сохранять и загружать модели. Это позволяет обучать и сохранять модель для последующего использования или включения в любую другую 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; }
Метод SaveModel позволяет сохранять модели. Для этого требуется ввод строки, которая будет новым именем модели. Сам метод записывается в двоичный файл .model, хранящийся в каталоге моделей папки общих файлов (Terminal\Common\Files\models). Сохраненный файл содержит порядок модели, а также ее параметры, если она была обучена, включая значение суммы квадратов ошибок (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; }
Методу LoadModel требуется имя модели, и он считывает все атрибуты ранее сохраненной модели. Оба метода возвращают либо true, либо false, а полезные сообщения об ошибках записываются в журнал терминала.
string GetModelName(void) { return m_modelname;}
Метод GetModelName() возвращает имя модели. Он вернет пустую строку, если модель никогда не сохранялась, в противном случае он вернет имя, заданное при сохранении модели.
//+------------------------------------------------------------------+ //| 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)))); } //+------------------------------------------------------------------+
Также добавлены методы BIC и AIC. Метод BIC возвращает байесовский информационный критерий на основе значения модели sse. Метод AIC вычисляет информационный критерий акаике и работает аналогично функции BIC. Байесовский информационный критерий (БИК) и Информационный критерий Акаике (AIC) — это статистические показатели, используемые для выбора модели. Оба критерия направлены на то, чтобы сбалансировать качество подгонки модели с ее сложностью, поэтому предпочтение отдается более простым моделям, если они соответствуют данным почти так же хорошо, как и более сложные модели.
BIC и AIC отличаются тем, как они уравновешивают качество подгонки и сложность. BIC уделяет больше внимания простоте модели, чем AIC, что означает, что он отдает предпочтение даже более простым моделям, чем AIC. С другой стороны, AIC с большей вероятностью выберет более сложные модели, чем BIC. Проще говоря, BIC и AIC позволяют нам сравнивать разные модели и выбирать ту, которая лучше всего соответствует нашим данным, принимая во внимание сложность модели. Выбирая более простую модель, мы можем избежать избыточной подгонки, которая происходит, когда модель слишком сложна и слишком точно соответствует данным, что делает ее менее полезной для прогнозирования новых наблюдений.
Обе они возвращают 0 при ошибке и должны вызываться сразу после обучения модели, пока данные обучения еще загружены. Когда обученная модель инициализируется, данные, используемые для обучения, будут недоступны, поэтому ни BIC, ни AIC не могут быть рассчитаны.
uint GetMinModelInputs(void) { return(m_diff_order + GetMaxArLag() + (GetMaxMaLag()*m_infactor));}
Теперь также можно запросить минимальное количество входных данных, требуемых моделью. Это делается с помощью метода 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; }
Наконец, вызов Summary() записывает атрибуты модели в терминал. Строка больше не возвращается.
Реализация прогнозов
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);
Применение модели для прогнозирования реализуется с помощью двух перегруженных методов Predict(). Оба принимают в качестве входных данных два одинаковых входных параметра:
- num_pred - это целочисленное значение определяет количество желаемых прогнозов.
- predictions — это массив типа double, в котором выводятся прогнозируемые значения.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ 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; }
Методы Predict отличаются количеством параметров функции. Первый метод, требующий двух параметров, используется для прогнозирования на основе обучающих данных, используемых для получения оптимальных коэффициентов модели.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ 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; }
Для второго метода Predict требуется третий массив входных параметров, который должен содержать серию входных данных, которые будут использоваться для расчета прогнозов. Оба метода возвращают логическое значение, а также используют частную функцию 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; }
Метод evaluate() похож на метод func() с некоторыми небольшими отличиями. Он принимает желаемое количество прогнозов в качестве единственного аргумента и просматривает до пяти массивов в зависимости от спецификации модели. Он вычисляет новые прогнозы и при необходимости добавляет новые значения в ряд ошибок (инноваций). После этого прогнозируемые значения извлекаются и копируются в целевой массив, предоставленный методу Predict(). Методы Predict возвращают true в случае успеха и false в случае ошибки.
Использование класса
Чтобы продемонстрировать пользователю модифицированный класс CArima, мы создадим скрипт, который обучает несколько моделей и сохраняет лучшую из них, выполняя перебор. Затем мы покажем, как эту сохраненную модель можно использовать в индикаторе, используя ее для прогнозирования на шаг вперед. Наконец, мы используем ту же модель для создания простого советника.
//+------------------------------------------------------------------+ //| 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]; } //+------------------------------------------------------------------+
Скрипт упрощает поиск оптимальной чистой авторегрессионной модели, которая соответствует выборке цен закрытия. Следует подчеркнуть, что это всего лишь демонстрация. Можно реализовать более сложные модели с вариациями количества и типов терминов (AR/MA), не забывая при этом о возможности указывать несмежные лаги для этих терминов. Так что возможности обширны. Пока мы ограничимся подбором чистой авторегрессионной модели.
Скрипт позволяет установить максимальный ордер AR для прекращения поиска, а также символ, таймфрейм и период данных ценовой выборки. Важно использовать выборочные цены, представляющие условия, которые могут возникнуть при применении модели для прогнозирования.
Скрипт определяет оптимальную модель, выбирая модель с наименьшим значением sse среди набора обученных моделей. Затем выбранная модель сохраняется, а ее атрибуты выводятся в терминал.
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Индикатор использует выбранную модель, чтобы делать прогнозы на шаг вперед.
Указанная модель загружается при инициализации индикатора. Затем метод Predict используется в основном цикле индикатора, чтобы делать форвардные прогнозы на основе предоставленных входных данных цены закрытия.
//+------------------------------------------------------------------+ //| 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); };
Советник снова применяет сохраненную модель для реализации простой стратегии. Основываясь на предсказании следующего бара, мы покупаем, если прогнозируемое закрытие больше, чем последнее известное закрытие. И продаем, если прогнозируемые закрытие меньше.
Это просто демонстрация, которую нельзя использовать для торговли на реальном счете.
Код полного класса CArima и его зависимостей содержится в zip-файле, прикрепленном в конце статьи, вместе со скриптом, индикатором и советником, описанными в статье.
Заключение
Модели авторегрессии легко обучаются с помощью терминала MetaTrader 5 и применяются во всевозможных программах. Наиболее трудная часть - это спецификация модели и выбор. Чтобы преодолеть эти проблемы и построить эффективные авторегрессионные модели в анализе рынка форекс, трейдеры должны следовать некоторым передовым методам, а именно:- Начните с четкого исследовательского вопроса: прежде чем строить авторегрессионную модель, трейдеры должны определить четкий исследовательский вопрос и гипотезу, которую они хотят проверить. Это помогает гарантировать, что процесс моделирования остается сфокусированным и соответствующим целям трейдера.
- Собирайте высококачественные данные: точность модели во многом зависит от качества используемых данных. Трейдеры должны использовать надежные источники данных и следить за тем, чтобы они были чистыми, полными и соответствовали их исследовательскому вопросу.
- Тестируйте несколько моделей: трейдеры должны тестировать несколько моделей с разной задержкой и параметрами, чтобы определить наиболее точную и эффективную модель для своих данных.
- Проверяйте модели: после построения модели трейдеры должны проверить ее точность с помощью статистических методов.
- Контролируйте и корректируйте модель: поскольку рыночные условия со временем меняются, эффективность модели также может меняться. Трейдеры должны отслеживать производительность своей модели с течением времени и вносить необходимые коррективы, чтобы она продолжала давать точное представление о будущих движениях цен.
Файл | Описание |
---|---|
Mql5/include/Powells.mqh | файл include, содержащий объявление и определение класса CPowellsMethod |
Mql5/include/Arima.mqh | файл include для класса CArima |
Mql5/indicator/ArimaOneStepForecast.mql5 | исходный код индикатора, показывающий, как загрузить и применить модель в индикаторе |
Mql5/scripts/TrainARModel.mql5 | скрипт, демонстрирующий, как обучить модель и сохранить ее для последующего использования |
Mql5/experts/ArForecasterEA.mql5 | советник, показывающий использование сохраненной модели в советнике. |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/12798
![Нейросети — это просто (Часть 52): Исследование с оптимизмом и коррекцией распределения](https://c.mql5.com/2/57/optimistic-actor-critic-avatar.png)
![Как стать успешным поставщиком сигналов на MQL5.com](https://c.mql5.com/2/55/How_to_Become_a_Successful_Signal_Provider_Avatar.png)
![Торговые транзакции. Структуры запросов и ответов, описание и вывод в журнал](https://c.mql5.com/2/57/printformat_trading_transactions_avatar.png)
![Теория категорий в MQL5 (Часть 10): Моноидные группы](https://c.mql5.com/2/55/Category_Theory_Part_10_avatar.png)
![MQL5 - Язык торговых стратегий для клиентского терминала MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Как только не извратился, результата не получил.
В статье прикреплен скриншот из тестера, у которого дикий профит-фактор, но низкий фактор восстановления. Что там такое может быть и как выглядит график - секрет.
Если у автора получилось что-то, то, видимо, это особая тайна.
Кстати, настройки отличаются от того, что в статье. В общем, странный контент.
Долго мучился над Аримой, ничего путнего не выходило.
А сейчас путное вышло?