
ONNX modellerinin regresyon metrikleri kullanılarak değerlendirilmesi
Giriş
Regresyon, etiketlenmemiş bir örnekten gerçek değeri tahmin etme görevidir. Regresyonun iyi bilinen bir örneği, büyüklük, ağırlık, renk, berraklık gibi özelliklere dayalı olarak bir elmasın değerinin tahmin edilmesidir.
Regresyon metrikleri, regresyon modeli tahminlerinin doğruluğunu değerlendirmek için kullanılır. Benzer algoritmalara rağmen, regresyon metrikleri anlamsal olarak benzer kayıp fonksiyonlarından farklıdır. Aralarındaki farkı anlamak önemlidir. Aşağıdaki şekilde formüle edilebilir:
-
Kayıp fonksiyonu, bir model oluşturma problemini bir optimizasyon problemine indirgediğimiz anda ortaya çıkar. Genellikle iyi özelliklere sahip olması istenir (örn. türevlenebilirlik).
-
Bir metrik, genellikle model parametrelerine değil, yalnızca tahmin edilen değerlere bağlı olan harici bir objektif kalite kriteridir.
MQL5'te regresyon metrikleri
MQL5 dili aşağıdaki metriklere sahiptir:
- Hataların mutlak değerlerinin ortalaması (Mean Absolute Error, MAE)
- Hataların karelerinin ortalaması (Mean Squared Error, MSE)
- Hataların karelerinin ortalamasının karekökü (Root Mean Squared Error, RMSE)
- R-kare (R-squared, R2)
- Yüzde olarak MAE (Mean Absolute Percentage Error, MAPE)
- Yüzde olarak MSE (Mean Squared Percentage Error, MSPE)
- Logaritmik ölçekte hesaplanan RMSE (Root Mean Squared Logarithmic Error, RMSLE)
MQL5'te regresyon metriklerinin sayısının artırılması beklenmektedir.
Regresyon metriklerinin kısa özellikleri
MAE, mutlak hatayı (tahmin edilen sayının gerçek sayıdan ne kadar farklı olduğunu) hesaplar. Hata, amaç fonksiyonunun değeri ile aynı birimde ölçülür. Hata değeri, olası değerler aralığına göre yorumlanır. Örneğin, hedef değerler 1 ila 1.5 aralığındaysa, 10 değerindeki MAE çok büyük bir hatadır; 10000...15000 aralığı için ise oldukça kabul edilebilirdir. Geniş bir değer dağılımına sahip tahminleri değerlendirmek için uygun değildir.
MSE'de, kare alma nedeniyle her hatanın kendi ağırlığı vardır. Bu nedenle tahmin ve gerçeklik arasındaki büyük farklılıklar çok daha belirgindir.
RMSE, MSE ile aynı avantajlara sahiptir, ancak hata, amaç fonksiyonunun değerleriyle aynı birimde ölçüldüğü için anlaşılması daha kolaydır. Anomalilere ve uç değerlere karşı çok hassastır. MAE ve RMSE, bir tahmin kümesindeki hata varyasyonunu belirlemek için birlikte kullanılabilir. RMSE her zaman MAE'den büyük veya MAE'ye eşittir. Aralarındaki fark ne kadar büyükse, örneklemdeki bireysel hataların yayılımı da o kadar büyük olur. RMSE = MAE ise, tüm hatalar aynı büyüklüğe sahiptir.
R2 - belirlilik katsayısı, iki rastgele değişken arasındaki ilişkinin gücünü gösterir. Modelin açıklayabildiği veri çeşitliliğinin payının belirlenmesine yardımcı olur. Model her zaman doğru tahminde bulunursa, metrik 1 olur. Önemsiz model için bu değer 0'dır. Model veri eğilimini takip etmezken, model önemsiz olandan daha kötü tahmin yaparsa metrik değeri negatif olabilir.
MAPE - boyutu yoktur ve yorumlanması çok kolaydır. Hem ondalık hem de yüzde olarak ifade edilebilir. MQL5'te ondalık sayılarla ifade edilir. Örneğin, 0.1 değeri hatanın gerçek değerin %10'u kadar olduğunu gösterir. Bu metriğin arkasındaki fikir, göreceli sapmalara karşı duyarlılıktır. Gerçek ölçü birimleriyle çalışmanız gereken görevler için uygun değildir.
MSPE, MSE'nin ağırlıklandırılmış bir versiyonu olarak düşünülebilir; burada ağırlık, gözlemlenen değerin karesiyle ters orantılıdır. Dolayısıyla, gözlemlenen değerler arttıkça hata azalma eğilimi gösterir.
RMSLE, gerçek değerler birkaç büyüklük mertebesine yayıldığında kullanılır. Tanım gereği, tahmin edilen ve gerçek gözlemlenen değerler negatif olamaz.
Yukarıdaki tüm metriklerin hesaplanmasına yönelik algoritmalar VectorRegressionMetric.mqh kaynak dosyasında sağlanmaktadır.
ONNX modelleri
Önceki günlük çubuklarından günün kapanış fiyatını (EURUSD, D1) tahmin eden 4 regresyon modeli kullandık. Bu modelleri daha önceki makalelerde ele almıştık: "ONNX modellerini sınıflara sarma", "MQL5'te ONNX modellerinin nasıl bir araya getirileceğine dair bir örnek" ve "MQL5'te ONNX modelleri nasıl kullanılır?". Bu nedenle, modelleri eğitmek için kullanılan kuralları burada tekrar etmeyeceğiz. Tüm modelleri eğitmek için kullanılan komut dosyaları, bu makaleye ekli zip arşivinin Python alt klasöründe yer almaktadır. Eğitilmiş onnx modelleri - model.eurusd.D1.10, model.eurusd.D1.30, model.eurusd.D1.52 ve model.eurusd.D1.63 de burada yer almaktadır.
ONNX modellerini sınıflara sarma
Bir önceki makalede, ONNX modelleri için temel sınıfı ve sınıflandırma modelleri için türetilmiş sınıfları gösterdik. Temel sınıfı daha esnek hale getirmek için bazı küçük değişiklikler uyguladık.
//+------------------------------------------------------------------+ //| ModelSymbolPeriod.mqh | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ //--- price movement prediction #define PRICE_UP 0 #define PRICE_SAME 1 #define PRICE_DOWN 2 //+------------------------------------------------------------------+ //| Base class for models based on trained symbol and period | //+------------------------------------------------------------------+ class CModelSymbolPeriod { protected: string m_name; // model name long m_handle; // created model session handle string m_symbol; // symbol of trained data ENUM_TIMEFRAMES m_period; // timeframe of trained data datetime m_next_bar; // time of next bar (we work at bar begin only) double m_class_delta; // delta to recognize "price the same" in regression models public: //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CModelSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES period,const double class_delta=0.0001) { m_name=""; m_handle=INVALID_HANDLE; m_symbol=symbol; m_period=period; m_next_bar=0; m_class_delta=class_delta; } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ ~CModelSymbolPeriod(void) { Shutdown(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string GetModelName(void) { return(m_name); } //+------------------------------------------------------------------+ //| virtual stub for Init | //+------------------------------------------------------------------+ virtual bool Init(const string symbol, const ENUM_TIMEFRAMES period) { return(false); } //+------------------------------------------------------------------+ //| Check for initialization, create model | //+------------------------------------------------------------------+ bool CheckInit(const string symbol, const ENUM_TIMEFRAMES period,const uchar& model[]) { //--- check symbol, period if(symbol!=m_symbol || period!=m_period) { PrintFormat("Model must work with %s,%s",m_symbol,EnumToString(m_period)); return(false); } //--- create a model from static buffer m_handle=OnnxCreateFromBuffer(model,ONNX_DEFAULT); if(m_handle==INVALID_HANDLE) { Print("OnnxCreateFromBuffer error ",GetLastError()); return(false); } //--- ok return(true); } //+------------------------------------------------------------------+ //| Release ONNX session | //+------------------------------------------------------------------+ void Shutdown(void) { if(m_handle!=INVALID_HANDLE) { OnnxRelease(m_handle); m_handle=INVALID_HANDLE; } } //+------------------------------------------------------------------+ //| Check for continue OnTick | //+------------------------------------------------------------------+ virtual bool CheckOnTick(void) { //--- check new bar if(TimeCurrent()<m_next_bar) return(false); //--- set next bar time m_next_bar=TimeCurrent(); m_next_bar-=m_next_bar%PeriodSeconds(m_period); m_next_bar+=PeriodSeconds(m_period); //--- work on new day bar return(true); } //+------------------------------------------------------------------+ //| virtual stub for PredictPrice (regression model) | //+------------------------------------------------------------------+ virtual double PredictPrice(datetime date) { return(DBL_MAX); } //+------------------------------------------------------------------+ //| Predict class (regression ~> classification) | //+------------------------------------------------------------------+ virtual int PredictClass(datetime date,vector& probabilities) { date-=date%PeriodSeconds(m_period); double predicted_price=PredictPrice(date); if(predicted_price==DBL_MAX) return(-1); double last_close[2]; if(CopyClose(m_symbol,m_period,date,2,last_close)!=2) return(-1); double prev_price=last_close[0]; //--- classify predicted price movement int predicted_class=-1; double delta=prev_price-predicted_price; if(fabs(delta)<=m_class_delta) predicted_class=PRICE_SAME; else { if(delta<0) predicted_class=PRICE_UP; else predicted_class=PRICE_DOWN; } //--- set predicted probability as 1.0 probabilities.Fill(0); if(predicted_class<(int)probabilities.Size()) probabilities[predicted_class]=1; //--- and return predicted class return(predicted_class); } }; //+------------------------------------------------------------------+
PredictPrice ve PredictClass metotlarına bir datetime parametresi ekledik, böylece yalnızca mevcut nokta için değil, zaman içerisindeki herhangi bir nokta için tahminler yapabiliriz. Bu, bir tahmin vektörü oluşturmak için faydalı olacaktır.
D1_10 model sınıfı
İlk modelimiz model.eurusd.D1.10.onnx olarak adlandırılır. EURUSD D1 üzerinde 10 OHLC fiyatı serisi üzerinde eğitilmiş regresyon modeli.//+------------------------------------------------------------------+ //| ModelEurusdD1_10.mqh | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include "ModelSymbolPeriod.mqh" #resource "Python/model.eurusd.D1.10.onnx" as uchar model_eurusd_D1_10[] //+------------------------------------------------------------------+ //| ONNX-model wrapper class | //+------------------------------------------------------------------+ class CModelEurusdD1_10 : public CModelSymbolPeriod { private: int m_sample_size; public: //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CModelEurusdD1_10(void) : CModelSymbolPeriod("EURUSD",PERIOD_D1) { m_name="D1_10"; m_sample_size=10; } //+------------------------------------------------------------------+ //| ONNX-model initialization | //+------------------------------------------------------------------+ virtual bool Init(const string symbol, const ENUM_TIMEFRAMES period) { //--- check symbol, period, create model if(!CModelSymbolPeriod::CheckInit(symbol,period,model_eurusd_D1_10)) { Print("model_eurusd_D1_10 : initialization error"); return(false); } //--- since not all sizes defined in the input tensor we must set them explicitly //--- first index - batch size, second index - series size, third index - number of series (OHLC) const long input_shape[] = {1,m_sample_size,4}; if(!OnnxSetInputShape(m_handle,0,input_shape)) { Print("model_eurusd_D1_10 : OnnxSetInputShape error ",GetLastError()); return(false); } //--- since not all sizes defined in the output tensor we must set them explicitly //--- first index - batch size, must match the batch size of the input tensor //--- second index - number of predicted prices const long output_shape[] = {1,1}; if(!OnnxSetOutputShape(m_handle,0,output_shape)) { Print("model_eurusd_D1_10 : OnnxSetOutputShape error ",GetLastError()); return(false); } //--- ok return(true); } //+------------------------------------------------------------------+ //| Predict price | //+------------------------------------------------------------------+ virtual double PredictPrice(datetime date) { static matrixf input_data(m_sample_size,4); // matrix for prepared input data static vectorf output_data(1); // vector to get result static matrix mm(m_sample_size,4); // matrix of horizontal vectors Mean static matrix ms(m_sample_size,4); // matrix of horizontal vectors Std static matrix x_norm(m_sample_size,4); // matrix for prices normalize //--- prepare input data matrix rates; //--- request last bars date-=date%PeriodSeconds(m_period); if(!rates.CopyRates(m_symbol,m_period,COPY_RATES_OHLC,date-1,m_sample_size)) return(DBL_MAX); //--- get series Mean vector m=rates.Mean(1); //--- get series Std vector s=rates.Std(1); //--- prepare matrices for prices normalization for(int i=0; i<m_sample_size; i++) { mm.Row(m,i); ms.Row(s,i); } //--- the input of the model must be a set of vertical OHLC vectors x_norm=rates.Transpose(); //--- normalize prices x_norm-=mm; x_norm/=ms; //--- run the inference input_data.Assign(x_norm); if(!OnnxRun(m_handle,ONNX_NO_CONVERSION,input_data,output_data)) return(DBL_MAX); //--- denormalize the price from the output value double predicted=output_data[0]*s[3]+m[3]; //--- return prediction return(predicted); } }; //+------------------------------------------------------------------+
Bu model, MQL5\Shared Projects\ONNX.Price.Prediction adlı herkese açık projede yayınlanan ilk modelimize benzerdir.
10 OHLC fiyatından oluşan seri, eğitim sırasında olduğu gibi normalleştirilmelidir, yani serideki ortalama fiyattan sapma, serideki standart sapmaya bölünür. Böylece, seriyi ortalaması 0 ve yayılımı 1 olan belirli bir aralığa koyuyoruz, bu da eğitim sırasında yakınsamayı iyileştirir.
D1_30 model sınıfı
İkinci model model.eurusd.D1.30.onnx olarak adlandırılır. EURUSD D1 üzerinde eğitilen regresyon modeli, 30 Kapanış fiyatı ve ortalama alma periyotları 21 ve 34 olan iki basit hareketli ortalama serisine dayanmaktadır.
//+------------------------------------------------------------------+ //| ModelEurusdD1_30.mqh | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include "ModelSymbolPeriod.mqh" #resource "Python/model.eurusd.D1.30.onnx" as uchar model_eurusd_D1_30[] //+------------------------------------------------------------------+ //| ONNX-model wrapper class | //+------------------------------------------------------------------+ class CModelEurusdD1_30 : public CModelSymbolPeriod { private: int m_sample_size; int m_fast_period; int m_slow_period; int m_sma_fast; int m_sma_slow; public: //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CModelEurusdD1_30(void) : CModelSymbolPeriod("EURUSD",PERIOD_D1) { m_name="D1_30"; m_sample_size=30; m_fast_period=21; m_slow_period=34; m_sma_fast=INVALID_HANDLE; m_sma_slow=INVALID_HANDLE; } //+------------------------------------------------------------------+ //| ONNX-model initialization | //+------------------------------------------------------------------+ virtual bool Init(const string symbol, const ENUM_TIMEFRAMES period) { //--- check symbol, period, create model if(!CModelSymbolPeriod::CheckInit(symbol,period,model_eurusd_D1_30)) { Print("model_eurusd_D1_30 : initialization error"); return(false); } //--- since not all sizes defined in the input tensor we must set them explicitly //--- first index - batch size, second index - series size, third index - number of series (Close, MA fast, MA slow) const long input_shape[] = {1,m_sample_size,3}; if(!OnnxSetInputShape(m_handle,0,input_shape)) { Print("model_eurusd_D1_30 : OnnxSetInputShape error ",GetLastError()); return(false); } //--- since not all sizes defined in the output tensor we must set them explicitly //--- first index - batch size, must match the batch size of the input tensor //--- second index - number of predicted prices const long output_shape[] = {1,1}; if(!OnnxSetOutputShape(m_handle,0,output_shape)) { Print("model_eurusd_D1_30 : OnnxSetOutputShape error ",GetLastError()); return(false); } //--- indicators m_sma_fast=iMA(m_symbol,m_period,m_fast_period,0,MODE_SMA,PRICE_CLOSE); m_sma_slow=iMA(m_symbol,m_period,m_slow_period,0,MODE_SMA,PRICE_CLOSE); if(m_sma_fast==INVALID_HANDLE || m_sma_slow==INVALID_HANDLE) { Print("model_eurusd_D1_30 : cannot create indicator"); return(false); } //--- ok return(true); } //+------------------------------------------------------------------+ //| Predict price | //+------------------------------------------------------------------+ virtual double PredictPrice(datetime date) { static matrixf input_data(m_sample_size,3); // matrix for prepared input data static vectorf output_data(1); // vector to get result static matrix x_norm(m_sample_size,3); // matrix for prices normalize static vector vtemp(m_sample_size); static double ma_buffer[]; //--- request last bars date-=date%PeriodSeconds(m_period); if(!vtemp.CopyRates(m_symbol,m_period,COPY_RATES_CLOSE,date-1,m_sample_size)) return(DBL_MAX); //--- get series Mean double m=vtemp.Mean(); //--- get series Std double s=vtemp.Std(); //--- normalize vtemp-=m; vtemp/=s; x_norm.Col(vtemp,0); //--- fast sma if(CopyBuffer(m_sma_fast,0,date-1,m_sample_size,ma_buffer)!=m_sample_size) return(-1); vtemp.Assign(ma_buffer); m=vtemp.Mean(); s=vtemp.Std(); vtemp-=m; vtemp/=s; x_norm.Col(vtemp,1); //--- slow sma if(CopyBuffer(m_sma_slow,0,date-1,m_sample_size,ma_buffer)!=m_sample_size) return(-1); vtemp.Assign(ma_buffer); m=vtemp.Mean(); s=vtemp.Std(); vtemp-=m; vtemp/=s; x_norm.Col(vtemp,2); //--- run the inference input_data.Assign(x_norm); if(!OnnxRun(m_handle,ONNX_NO_CONVERSION,input_data,output_data)) return(DBL_MAX); //--- denormalize the price from the output value double predicted=output_data[0]*s+m; //--- return prediction return(predicted); } }; //+------------------------------------------------------------------+
Önceki sınıfta olduğu gibi, CheckInit temel sınıf metodu Init metodunda çağrılır. Temel sınıf metodunda, ONNX modeli için bir oturum oluşturulur ve girdi ve çıktı tensörlerinin büyüklükleri açıkça ayarlanır.
PredictPrice metodu, önceki 30 Kapanış fiyatından ve hesaplanan hareketli ortalamalardan oluşan bir seri sağlar. Veriler eğitimde olduğu gibi aynı şekilde normalleştirilir.
Model, "ONNX modellerini sınıflara sarma" makalesi için geliştirilmiş ve bu makale için sınıflandırmadan regresyona dönüştürülmüştür.
D1_52 model sınıfı
Üçüncü model model.eurusd.D1.52.onnx olarak adlandırılır. EURUSD D1 üzerinde 52 Kapanış fiyatı serisi üzerinde eğitilmiş regresyon modeli.
//+------------------------------------------------------------------+ //| ModelEurusdD1_52.mqh | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include "ModelSymbolPeriod.mqh" #resource "Python/model.eurusd.D1.52.onnx" as uchar model_eurusd_D1_52[] //+------------------------------------------------------------------+ //| ONNX-model wrapper class | //+------------------------------------------------------------------+ class CModelEurusdD1_52 : public CModelSymbolPeriod { private: int m_sample_size; public: //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CModelEurusdD1_52(void) : CModelSymbolPeriod("EURUSD",PERIOD_D1,0.0001) { m_name="D1_52"; m_sample_size=52; } //+------------------------------------------------------------------+ //| ONNX-model initialization | //+------------------------------------------------------------------+ virtual bool Init(const string symbol, const ENUM_TIMEFRAMES period) { //--- check symbol, period, create model if(!CModelSymbolPeriod::CheckInit(symbol,period,model_eurusd_D1_52)) { Print("model_eurusd_D1_52 : initialization error"); return(false); } //--- since not all sizes defined in the input tensor we must set them explicitly //--- first index - batch size, second index - series size, third index - number of series (only Close) const long input_shape[] = {1,m_sample_size,1}; if(!OnnxSetInputShape(m_handle,0,input_shape)) { Print("model_eurusd_D1_52 : OnnxSetInputShape error ",GetLastError()); return(false); } //--- since not all sizes defined in the output tensor we must set them explicitly //--- first index - batch size, must match the batch size of the input tensor //--- second index - number of predicted prices (we only predict Close) const long output_shape[] = {1,1}; if(!OnnxSetOutputShape(m_handle,0,output_shape)) { Print("model_eurusd_D1_52 : OnnxSetOutputShape error ",GetLastError()); return(false); } //--- ok return(true); } //+------------------------------------------------------------------+ //| Predict price | //+------------------------------------------------------------------+ virtual double PredictPrice(datetime date) { static vectorf output_data(1); // vector to get result static vector x_norm(m_sample_size); // vector for prices normalize //--- set date to day begin date-=date%PeriodSeconds(m_period); //--- check for calculate min and max double price_min=0; double price_max=0; GetMinMaxClose(date,price_min,price_max); //--- check for normalization possibility if(price_min>=price_max) return(DBL_MAX); //--- request last bars if(!x_norm.CopyRates(m_symbol,m_period,COPY_RATES_CLOSE,date-1,m_sample_size)) return(DBL_MAX); //--- normalize prices x_norm-=price_min; x_norm/=(price_max-price_min); //--- run the inference if(!OnnxRun(m_handle,ONNX_DEFAULT,x_norm,output_data)) return(DBL_MAX); //--- denormalize the price from the output value double predicted=output_data[0]*(price_max-price_min)+price_min; //--- return prediction return(predicted); } private: //+------------------------------------------------------------------+ //| Get minimal and maximal Close for last 52 weeks | //+------------------------------------------------------------------+ void GetMinMaxClose(const datetime date,double& price_min,double& price_max) { static vector close; close.CopyRates(m_symbol,m_period,COPY_RATES_CLOSE,date,m_sample_size*7+1); price_min=close.Min(); price_max=close.Max(); } }; //+------------------------------------------------------------------+
Modeli sunmadan önce yapılan fiyat normalleştirmesi öncekilerden farklıdır. Eğitim sırasında MinMaxScaler kullanılmıştır. Bu nedenle, tahmin tarihinden önceki 52 haftalık dönem için minimum ve maksimum fiyatları alırız.
Model, "MQL5'te ONNX modelleri nasıl kullanılır?" makalesinde anlatılana benzerdir.
D1_63 model sınıfı
Son olarak, dördüncü model model.eurusd.D1.63.onnx olarak adlandırılır. EURUSD D1 üzerinde 63 Kapanış fiyatı serisi üzerinde eğitilmiş regresyon modeli.
//+------------------------------------------------------------------+ //| ModelEurusdD1_63.mqh | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include "ModelSymbolPeriod.mqh" #resource "Python/model.eurusd.D1.63.onnx" as uchar model_eurusd_D1_63[] //+------------------------------------------------------------------+ //| ONNX-model wrapper class | //+------------------------------------------------------------------+ class CModelEurusdD1_63 : public CModelSymbolPeriod { private: int m_sample_size; public: //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CModelEurusdD1_63(void) : CModelSymbolPeriod("EURUSD",PERIOD_D1) { m_name="D1_63"; m_sample_size=63; } //+------------------------------------------------------------------+ //| ONNX-model initialization | //+------------------------------------------------------------------+ virtual bool Init(const string symbol, const ENUM_TIMEFRAMES period) { //--- check symbol, period, create model if(!CModelSymbolPeriod::CheckInit(symbol,period,model_eurusd_D1_63)) { Print("model_eurusd_D1_63 : initialization error"); return(false); } //--- since not all sizes defined in the input tensor we must set them explicitly //--- first index - batch size, second index - series size const long input_shape[] = {1,m_sample_size}; if(!OnnxSetInputShape(m_handle,0,input_shape)) { Print("model_eurusd_D1_63 : OnnxSetInputShape error ",GetLastError()); return(false); } //--- since not all sizes defined in the output tensor we must set them explicitly //--- first index - batch size, must match the batch size of the input tensor //--- second index - number of predicted prices const long output_shape[] = {1,1}; if(!OnnxSetOutputShape(m_handle,0,output_shape)) { Print("model_eurusd_D1_63 : OnnxSetOutputShape error ",GetLastError()); return(false); } //--- ok return(true); } //+------------------------------------------------------------------+ //| Predict price | //+------------------------------------------------------------------+ virtual double PredictPrice(datetime date) { static vectorf input_data(m_sample_size); // vector for prepared input data static vectorf output_data(1); // vector to get result //--- request last bars date-=date%PeriodSeconds(m_period); if(!input_data.CopyRates(m_symbol,m_period,COPY_RATES_CLOSE,date-1,m_sample_size)) return(DBL_MAX); //--- get series Mean float m=input_data.Mean(); //--- get series Std float s=input_data.Std(); //--- normalize prices input_data-=m; input_data/=s; //--- run the inference if(!OnnxRun(m_handle,ONNX_NO_CONVERSION,input_data,output_data)) return(DBL_MAX); //--- denormalize the price from the output value double predicted=output_data[0]*s+m; //--- return prediction return(predicted); } }; //+------------------------------------------------------------------+
PredictPrice metodu, önceki 63 Kapanış fiyatından oluşan bir seri sağlar. Veriler, birinci ve ikinci modellerde olduğu gibi aynı şekilde normalleştirilir.
Model, "MQL5'te ONNX modellerinin nasıl bir araya getirileceğine dair bir örnek" makalesi için halihazırda geliştirilmiştir.
Tüm modelleri tek bir komut dosyasında birleştirme. Gerçeklik, tahminler ve regresyon metrikleri
Regresyon metriklerini uygulamak için belirli sayıda tahmin yapmalı (vector_pred) ve aynı tarihler için gerçek verileri almalıyız (vector_true).
Tüm modellerimiz aynı temel sınıftan türeyen sınıflara sarıldığından, hepsini bir kerede değerlendirebiliriz.
Komut dosyası çok basittir.
//+------------------------------------------------------------------+ //| ONNX.eurusd.D1.4M.Metrics.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" #define MODELS 4 #include "ModelEurusdD1_10.mqh" #include "ModelEurusdD1_30.mqh" #include "ModelEurusdD1_52.mqh" #include "ModelEurusdD1_63.mqh" #property script_show_inputs input datetime InpStartDate = D'2023.01.01'; input datetime InpStopDate = D'2023.01.31'; CModelSymbolPeriod *ExtModels[MODELS]; struct PredictedPrices { string model; double pred[]; }; PredictedPrices ExtPredicted[MODELS]; double ExtClose[]; struct Metrics { string model; double mae; double mse; double rmse; double r2; double mape; double mspe; double rmsle; }; Metrics ExtMetrics[MODELS]; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- init section if(!Init()) return; //--- predictions test loop datetime dates[]; if(CopyTime(_Symbol,_Period,InpStartDate,InpStopDate,dates)<=0) { Print("Cannot get data from ",InpStartDate," to ",InpStopDate); return; } for(uint n=0; n<dates.Size(); n++) GetPredictions(dates[n]); CopyClose(_Symbol,_Period,InpStartDate,InpStopDate,ExtClose); CalculateMetrics(); //--- deinit section Deinit(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool Init() { ExtModels[0]=new CModelEurusdD1_10; ExtModels[1]=new CModelEurusdD1_30; ExtModels[2]=new CModelEurusdD1_52; ExtModels[3]=new CModelEurusdD1_63; for(long i=0; i<ExtModels.Size(); i++) { if(!ExtModels[i].Init(_Symbol,_Period)) { Deinit(); return(false); } } for(long i=0; i<ExtModels.Size(); i++) ExtPredicted[i].model=ExtModels[i].GetModelName(); return(true); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void Deinit() { for(uint i=0; i<ExtModels.Size(); i++) delete ExtModels[i]; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void GetPredictions(datetime date) { //--- collect predicted prices for(uint i=0; i<ExtModels.Size(); i++) ExtPredicted[i].pred.Push(ExtModels[i].PredictPrice(date)); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CalculateMetrics() { vector vector_pred,vector_true; vector_true.Assign(ExtClose); for(uint i=0; i<ExtModels.Size(); i++) { ExtMetrics[i].model=ExtPredicted[i].model; vector_pred.Assign(ExtPredicted[i].pred); ExtMetrics[i].mae =vector_pred.RegressionMetric(vector_true,REGRESSION_MAE); ExtMetrics[i].mse =vector_pred.RegressionMetric(vector_true,REGRESSION_MSE); ExtMetrics[i].rmse =vector_pred.RegressionMetric(vector_true,REGRESSION_RMSE); ExtMetrics[i].r2 =vector_pred.RegressionMetric(vector_true,REGRESSION_R2); ExtMetrics[i].mape =vector_pred.RegressionMetric(vector_true,REGRESSION_MAPE); ExtMetrics[i].mspe =vector_pred.RegressionMetric(vector_true,REGRESSION_MSPE); ExtMetrics[i].rmsle=vector_pred.RegressionMetric(vector_true,REGRESSION_RMSLE); } ArrayPrint(ExtMetrics); } //+------------------------------------------------------------------+
Komut dosyasını EURUSD D1 grafiğinde çalıştıralım ve tarihleri 1 Ocak ile 31 Ocak 2023 arasında ayarlayalım. Ne görüyoruz?
[model] [mae] [mse] [rmse] [r2] [mape] [mspe] [rmsle] [0] "D1_10" 0.00381 0.00003 0.00530 0.77720 0.00356 0.00002 0.00257 [1] "D1_30" 0.01809 0.00039 0.01963 -2.05545 0.01680 0.00033 0.00952 [2] "D1_52" 0.00472 0.00004 0.00642 0.67327 0.00440 0.00004 0.00311 [3] "D1_63" 0.00413 0.00003 0.00559 0.75230 0.00385 0.00003 0.00270
Negatif R-kare değeri ikinci satırda hemen göze çarpmaktadır. Bu, modelin çalışmadığı anlamına gelir. Tahmin grafiklerine bakmak ilginç olacaktır.
D1_30 grafiğini gerçek Kapanış fiyatlarından ve diğer tahminlerden çok uzakta görüyoruz. Bu modele ilişkin metriklerin hiçbiri cesaret verici değildir. MAE, 1809 fiyat noktasının tahmin doğruluğunu gösteriyor! Bununla birlikte, modelin başlangıçta bir önceki makale için bir regresyon modeli değil, bir sınıflandırma modeli olarak geliştirildiğini unutmayın. Örnek oldukça nettir.
Diğer modelleri ayrı ayrı ele alalım.
Analiz için ilk aday D1_10'dur
[model] [mae] [mse] [rmse] [r2] [mape] [mspe] [rmsle] [0] "D1_10" 0.00381 0.00003 0.00530 0.77720 0.00356 0.00002 0.00257
Şimdi bu model tarafından tahmin edilen fiyatların grafiğine bakalım.
RMSLE metriği pek anlamlı değildir, çünkü 1.05 ile 1.09 arasındaki fark bir büyüklük mertebesinden çok daha azdır. MAPE ve MSPE metrikleri, EURUSD döviz kurunun bire yakın olması nedeniyle MAE ve MSE'ye yakın değerlere sahiptir. Ancak, yüzde sapmaları hesaplarken, mutlak sapmaları hesaplarken mevcut olmayan bir nüans vardır.
MAPE = |(y_true-y_pred)/y_true| if y_true = 10 and y_pred = 5 MAPE = 0.5 if y_true = 5 and y_pred = 10 MAPE = 1.0
Başka bir deyişle, bu metrik (MSPE gibi) asimetriktir. Bu, tahminin gerçekten daha yüksek olduğu durumda daha büyük bir hata elde edeceğimiz anlamına gelir.
Tamamen metodolojik amaçlarla, yani MQL5'te ONNX modelleriyle nasıl çalışabileceğinizi göstermek için bir araya getirilen basit model için R-kare metriğinin iyi sonucu elde edilmiştir.
İkinci aday - D1_63
[model] [mae] [mse] [rmse] [r2] [mape] [mspe] [rmsle] [3] "D1_63" 0.00413 0.00003 0.00559 0.75230 0.00385 0.00003 0.00270
Tahmin, görsel olarak bir öncekine çok benzemektedir. Metrik değerler bu benzerliği doğruluyor.
[0] "D1_10" 0.00381 0.00003 0.00530 0.77720 0.00356 0.00002 0.00257 [3] "D1_63" 0.00413 0.00003 0.00559 0.75230 0.00385 0.00003 0.00270
Şimdi bu modellerden hangisinin aynı dönemde sınayıcıda daha iyi performans göstereceğini göreceğiz.
Şimdi D1_52 için
[model] [mae] [mse] [rmse] [r2] [mape] [mspe] [rmsle] [2] "D1_52" 0.00472 0.00004 0.00642 0.67327 0.00440 0.00004 0.00311
Bunu sadece R-kare değeri 0.5'ten büyük olduğu için dikkate alıyoruz.
Tahmin edilen fiyatların neredeyse tamamı, en kötü durumumuzda olduğu gibi Kapanış grafiğinin altındadır. Önceki iki modelle karşılaştırılabilir metrik değerlere rağmen, bu model herhangi bir iyimserlik uyandırmamaktadır. Bunu bir sonraki paragrafta kontrol edeceğiz.
ONNX modellerini sınayıcıda çalıştırma
Aşağıda modellerimizi sınayıcıda kontrol etmek için çok basit bir Uzman Danışman verilmiştir.
//+------------------------------------------------------------------+ //| ONNX.eurusd.D1.Prediction.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 "ModelEurusdD1_10.mqh" #include "ModelEurusdD1_30.mqh" #include "ModelEurusdD1_52.mqh" #include "ModelEurusdD1_63.mqh" #include <Trade\Trade.mqh> input double InpLots = 1.0; // Lots amount to open position //CModelEurusdD1_10 ExtModel; //CModelEurusdD1_30 ExtModel; CModelEurusdD1_52 ExtModel; //CModelEurusdD1_63 ExtModel; CTrade ExtTrade; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { if(!ExtModel.Init(_Symbol,_Period)) return(INIT_FAILED); Print("model ",ExtModel.GetModelName()); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ExtModel.Shutdown(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if(!ExtModel.CheckOnTick()) return; //--- predict next price movement vector prob(3); int predicted_class=ExtModel.PredictClass(TimeCurrent(),prob); Print("predicted class ",predicted_class); //--- check trading according to prediction if(predicted_class>=0) if(PositionSelect(_Symbol)) CheckForClose(predicted_class); else CheckForOpen(predicted_class); } //+------------------------------------------------------------------+ //| Check for open position conditions | //+------------------------------------------------------------------+ void CheckForOpen(const int predicted_class) { ENUM_ORDER_TYPE signal=WRONG_VALUE; //--- check signals if(predicted_class==PRICE_DOWN) signal=ORDER_TYPE_SELL; // sell condition else { if(predicted_class==PRICE_UP) signal=ORDER_TYPE_BUY; // buy condition } //--- open position if possible according to signal if(signal!=WRONG_VALUE && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { double price=SymbolInfoDouble(_Symbol,(signal==ORDER_TYPE_SELL) ? SYMBOL_BID : SYMBOL_ASK); ExtTrade.PositionOpen(_Symbol,signal,InpLots,price,0,0); } } //+------------------------------------------------------------------+ //| Check for close position conditions | //+------------------------------------------------------------------+ void CheckForClose(const int predicted_class) { bool bsignal=false; //--- position already selected before long type=PositionGetInteger(POSITION_TYPE); //--- check signals if(type==POSITION_TYPE_BUY && predicted_class==PRICE_DOWN) bsignal=true; if(type==POSITION_TYPE_SELL && predicted_class==PRICE_UP) bsignal=true; //--- close position if possible if(bsignal && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { ExtTrade.PositionClose(_Symbol,3); //--- open opposite CheckForOpen(predicted_class); } } //+------------------------------------------------------------------+
Gerçekten de, D1_52 modeline göre, yalnızca bir satış işlemi açıldı ve bu modele göre trend, tüm test dönemi boyunca değişmedi.
2023.06.09 16:18:31.967 Symbols EURUSD: symbol to be synchronized 2023.06.09 16:18:31.968 Symbols EURUSD: symbol synchronized, 3720 bytes of symbol info received 2023.06.09 16:18:32.023 History EURUSD: load 27 bytes of history data to synchronize in 0:00:00.001 2023.06.09 16:18:32.023 History EURUSD: history synchronized from 2011.01.03 to 2023.04.07 2023.06.09 16:18:32.124 History EURUSD,Daily: history cache allocated for 283 bars and contains 260 bars from 2022.01.03 00:00 to 2022.12.30 00:00 2023.06.09 16:18:32.124 History EURUSD,Daily: history begins from 2022.01.03 00:00 2023.06.09 16:18:32.126 Tester EURUSD,Daily (MetaQuotes-Demo): 1 minutes OHLC ticks generating 2023.06.09 16:18:32.126 Tester EURUSD,Daily: testing of Experts\article_2\ONNX.eurusd.D1.Prediction.ex5 from 2023.01.01 00:00 to 2023.02.01 00:00 started with inputs: 2023.06.09 16:18:32.126 Tester InpLots=1.0 2023.06.09 16:18:32.161 ONNX api version 1.16.0 initialized 2023.06.09 16:18:32.180 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.01 00:00:00 model D1_52 2023.06.09 16:18:32.194 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.02 07:02:00 predicted class 2 2023.06.09 16:18:32.194 Trade 2023.01.02 07:02:00 instant sell 1 EURUSD at 1.07016 (1.07016 / 1.07023 / 1.07016) 2023.06.09 16:18:32.194 Trades 2023.01.02 07:02:00 deal #2 sell 1 EURUSD at 1.07016 done (based on order #2) 2023.06.09 16:18:32.194 Trade 2023.01.02 07:02:00 deal performed [#2 sell 1 EURUSD at 1.07016] 2023.06.09 16:18:32.194 Trade 2023.01.02 07:02:00 order performed sell 1 at 1.07016 [#2 sell 1 EURUSD at 1.07016] 2023.06.09 16:18:32.195 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.02 07:02:00 CTrade::OrderSend: instant sell 1.00 EURUSD at 1.07016 [done at 1.07016] 2023.06.09 16:18:32.196 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.03 00:00:00 predicted class 2 2023.06.09 16:18:32.199 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.04 00:00:00 predicted class 2 2023.06.09 16:18:32.201 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.05 00:00:30 predicted class 2 2023.06.09 16:18:32.203 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.06 00:00:00 predicted class 2 2023.06.09 16:18:32.206 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.09 00:02:00 predicted class 2 2023.06.09 16:18:32.208 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.10 00:00:00 predicted class 2 2023.06.09 16:18:32.210 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.11 00:00:00 predicted class 2 2023.06.09 16:18:32.213 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.12 00:00:00 predicted class 2 2023.06.09 16:18:32.215 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.13 00:00:00 predicted class 2 2023.06.09 16:18:32.217 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.16 00:03:00 predicted class 2 2023.06.09 16:18:32.220 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.17 00:00:00 predicted class 2 2023.06.09 16:18:32.222 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.18 00:00:30 predicted class 2 2023.06.09 16:18:32.224 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.19 00:00:00 predicted class 2 2023.06.09 16:18:32.227 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.20 00:00:30 predicted class 2 2023.06.09 16:18:32.229 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.23 00:02:00 predicted class 2 2023.06.09 16:18:32.231 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.24 00:00:00 predicted class 2 2023.06.09 16:18:32.234 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.25 00:00:00 predicted class 2 2023.06.09 16:18:32.236 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.26 00:00:00 predicted class 2 2023.06.09 16:18:32.238 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.27 00:00:00 predicted class 2 2023.06.09 16:18:32.241 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.30 00:03:00 predicted class 2 2023.06.09 16:18:32.243 ONNX.eurusd.D1.Prediction (EURUSD,D1) 2023.01.31 00:00:00 predicted class 2 2023.06.09 16:18:32.245 Trade 2023.01.31 23:59:59 position closed due end of test at 1.08621 [#2 sell 1 EURUSD 1.07016] 2023.06.09 16:18:32.245 Trades 2023.01.31 23:59:59 deal #3 buy 1 EURUSD at 1.08621 done (based on order #3) 2023.06.09 16:18:32.245 Trade 2023.01.31 23:59:59 deal performed [#3 buy 1 EURUSD at 1.08621] 2023.06.09 16:18:32.245 Trade 2023.01.31 23:59:59 order performed buy 1 at 1.08621 [#3 buy 1 EURUSD at 1.08621] 2023.06.09 16:18:32.245 Tester final balance 8366.00 USD 2023.06.09 16:18:32.249 Tester EURUSD,Daily: 123499 ticks, 22 bars generated. Environment synchronized in 0:00:00.043. Test passed in 0:00:00.294 (including ticks preprocessing 0:00:00.016).
Önceki bölümde de belirtildiği üzere, D1_52 modeli iyimserlik uyandırmamaktadır. Bu durum test sonuçlarıyla da teyit edilmiştir.
Sadece iki satırlık kodu değiştirelim.
#include "ModelEurusdD1_10.mqh" #include "ModelEurusdD1_30.mqh" #include "ModelEurusdD1_52.mqh" #include "ModelEurusdD1_63.mqh" #include <Trade\Trade.mqh> input double InpLots = 1.0; // Lots amount to open position CModelEurusdD1_10 ExtModel; //CModelEurusdD1_30 ExtModel; //CModelEurusdD1_52 ExtModel; //CModelEurusdD1_63 ExtModel; CTrade ExtTrade;
Ve test için D1_10 modelini başlatalım.
Sonuçlar iyidir. Test grafiği de umut vericidir.
İki satırlık kodu tekrar düzeltelim ve D1_63 modelini test edelim.
Grafik:
Test grafiği D1_10 modelinden çok daha kötüdür.
D1_10 ve D1_63 modellerini karşılaştırdığımızda, ilk modelin ikincisinden daha iyi regresyon metriklerine sahip olduğunu görebiliriz. Sınayıcı da aynı şeyi gösteriyor.
Önemli not: Makalede kullanılan modellerin yalnızca MQL5 dilini kullanarak ONNX modelleriyle nasıl çalışılacağını göstermek için sunulduğunu lütfen unutmayın. Uzman Danışman, gerçek hesaplarda ticaret yapmak için tasarlanmamıştır.
Sonuç
Fiyat tahmin modellerini değerlendirmek için en uygun metrik R-kare değeridir. MAE - RMSE - MAPE metriklerinin bir arada değerlendirilmesi çok faydalı olabilir. RMSLE metriği fiyat tahmini görevlerinde dikkate alınmayabilir. Bazı değişikliklerle aynı model olsa bile, değerlendirme için birkaç modele sahip olmak çok yararlıdır.
Ciddi bir araştırma için 22 değerlik bir örneklemin yetersiz olduğunun farkındayız, ancak amacımız istatistiksel bir çalışma yapmak değildi. Bunun yerine, sadece bir kullanım örneği sunduk.
MetaQuotes Ltd tarafından Rusçadan çevrilmiştir.
Orijinal makale: https://www.mql5.com/ru/articles/12772





- Ücretsiz alım-satım uygulamaları
- İşlem kopyalama için 8.000'den fazla sinyal
- Finansal piyasaları keşfetmek için ekonomik haberler
Gizlilik ve Veri Koruma Politikasını ve MQL5.com Kullanım Şartlarını kabul edersiniz