MQL5'te ONNX modelleri nasıl kullanılır?
Giriş
A CNN-LSTM-Based Model to Forecast Stock Prices (Wenjie Lu, Jiazheng Li, Yifan Li, Aijun Sun, Jingyang Wang, Complexity magazine, vol. 2020, Article ID 6622927, 10 pages, 2020) makalesinin yazarları çeşitli hisse senedi fiyatı öngörü modellerini karşılaştırmıştır:
Stock price data have the characteristics of time series.
At the same time, based on machine learning long short-term memory (LSTM) which has the advantages of analyzing relationships among time series data through its memory function, we propose a forecasting method of stock price based on CNN-LSTM.
In the meanwhile, we use MLP, CNN, RNN, LSTM, CNN-RNN, and other forecasting models to predict the stock price one by one. Moreover, the forecasting results of these models are analyzed and compared.
The data utilized in this research concern the daily stock prices from July 1, 1991, to August 31, 2020, including 7127 trading days.
In terms of historical data, we choose eight features, including opening price, highest price, lowest price, closing price, volume, turnover, ups and downs, and change.
Firstly, we adopt CNN to efficiently extract features from the data, which are the items of the previous 10 days. And then, we adopt LSTM to predict the stock price with the extracted feature data.
According to the experimental results, the CNN-LSTM can provide a reliable stock price forecasting with the highest prediction accuracy.
This forecasting method not only provides a new research idea for stock price forecasting but also provides practical experience for scholars to study financial time series data.
İncelenen tüm modeller arasında, CNN-LSTM modelleri deneyler sırasında en iyi sonuçları vermiştir. Bu makalede, finansal zaman serilerini öngörmek için böyle bir modelin nasıl oluşturulacağını ve oluşturulan ONNX modelinin bir MQL5 Uzman Danışmanında nasıl kullanılacağını ele alacağız.
1. Bir model oluşturma
Python bir dizi özelleşmiş kütüphane sunar ve böylece makine öğrenimi modelleriyle çalışmak için kapsamlı yetenekler sağlar. Kütüphaneler veri hazırlama ve işlemeyi büyük ölçüde kolaylaştırır.
Makine öğrenimi projelerinin verimliliğini en üst düzeye çıkarmak için GPU kaynaklarını kullanmanızı öneririz. Birçok Windows kullanıcısı, mevcut TensorFlow sürümünü yüklemeye çalışırken sorunlarla karşılaşmıştır (video kılavuzundaki yorumlara bakın ve yükleme talimatlarının metin sürümü). Bu nedenle, TensorFlow 2.10.0'ı test ettik ve bu sürümü kullanmanızı öneririz. GPU hesaplamaları NVIDIA GeForce RTX 2080 Ti grafik kartı üzerinde CUDA 11.2 ve CUDNN 8.1.0.7 kütüphaneleri kullanılarak gerçekleştirilmiştir.
1.1. Python ve kütüphanelerin yüklenmesi
Eğer Python'a sahip değilseniz, yüklemelisiniz. Biz 3.9.16 sürümünü kullandık.
Ayrıca, kütüphaneleri de yükleyin (Conda/Anaconda kullanıyorsanız, bu komutları Anaconda Prompt'ta çalıştırın):
python.exe -m pip install --upgrade pip pip install --upgrade pandas pip install --upgrade scikit-learn pip install --upgrade matplotlib pip install --upgrade tqdm pip install --upgrade metatrader5 pip install --upgrade onnx==1.12 pip install --upgrade tf2onnx pip install --upgrade tensorflow==2.10.0
1.2. TensorFlow sürümünü ve GPU'yu kontrol etme
Aşağıdaki kod, yüklü TensorFlow sürümünü kontrol eder ve modelleri hesaplamak için GPU kullanmanın mümkün olup olmadığını doğrular:
#check tensorflow version print(tf.__version__) #check GPU support print(len(tf.config.list_physical_devices('GPU'))>0)
Gerekli sürüm doğru şekilde yüklenmişse, aşağıdaki sonucu göreceksiniz:
True
Modeli oluşturmak ve eğitmek için bir Python komut dosyası kullandık. Bu sürecin adımları aşağıda kısaca açıklanmıştır.
1.3. Modelin oluşturulması ve eğitimi
Komut dosyası, modelde kullanılacak Python kütüphanelerini içe aktararak başlar.
#Python libraries import matplotlib.pyplot as plt import MetaTrader5 as mt5 import tensorflow as tf import numpy as np import pandas as pd import tf2onnx from sklearn.model_selection import train_test_split from sys import argv
TensorFlow sürümü ve GPU kullanılabilirliği kontrol edilir:
#check tensorflow version print(tf.__version__)
2.10.0
#check GPU support print(len(tf.config.list_physical_devices('GPU'))>0)
True
Python'dan çalışma için MetaTrader 5 başlatılır:
#initialize MetaTrader5 for history data if not mt5.initialize(): print("initialize() failed, error code =",mt5.last_error()) quit()
MetaTrader 5 terminali hakkında bilgi al:
#show terminal info terminal_info=mt5.terminal_info() print(terminal_info)
TerminalInfo(community_account=True, community_connection=True, connected=True, dlls_allowed=False, trade_allowed=False, tradeapi_disabled=False, email_enabled=False, ftp_enabled=False, notifications_enabled=False, mqid=False, build=3640, maxbars=100000, codepage=0, ping_last=58768, community_balance=1.0, retransmission=0.015296317559440137, company='MetaQuotes Software Corp.', name='MetaTrader 5', language='English', path='C:\\Program Files\\MetaTrader 5', data_path='C:\\Users\\user\\AppData\\Roaming\\MetaQuotes\\Terminal\\D0E8209F77C8CF37AD8BF550E51FF075', commondata_path='C:\\Users\\user\\AppData\\Roaming\\MetaQuotes\\Terminal\\Common')
#show file path file_path=terminal_info.data_path+"\\MQL5\\Files\\" print(file_path)
Modelin kaydedileceği yol yazdırılır (bu örnekte, komut dosyası Jupyter Notebook'ta çalışmaktadır):
#data path to save the model data_path=argv[0] last_index=data_path.rfind("\\")+1 data_path=data_path[0:last_index] print("data path to save onnx model",data_path)
data path to save onnx model C:\Users\user\AppData\Roaming\Python\Python39\site-packages\
Geçmiş verileri talep etmek için tarihler hazırlanır. Örneğimizde, mevcut tarihten itibaren 120 gün için EURUSD H1 çubuklarını talep ediyoruz:
#set start and end dates for history data from datetime import timedelta,datetime end_date = datetime.now() start_date = end_date - timedelta(days=120) #print start and end dates print("data start date=",start_date) print("data end date=",end_date)
data end date= 2023-03-28 12:28:39.870685
EURUSD geçmiş verileri talep edilir:
#get EURUSD rates (H1) from start_date to end_date eurusd_rates = mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_H1, start_date, end_date)
İndirilen verilerin çıktısı alınır:
#check print(eurusd_rates)
#create dataframe df = pd.DataFrame(eurusd_rates)
Veri çerçevesinin başlangıcı ve sonu gösterilir:
#show dataframe head df.head()
#show dataframe tail df.tail()
#show dataframe shape (the number of rows and columns in the data set) df.shape
(2045, 8)
Yalnızca Kapanış fiyatları seçilir:
#prepare close prices only data = df.filter(['close']).values
Verilerin çıktısı alınır:
#show close prices plt.figure(figsize = (18,10)) plt.plot(data,'b',label = 'Original') plt.xlabel("Hours") plt.ylabel("Price") plt.title("EURUSD_H1") plt.legend()
MinMaxScaler kullanılarak kaynak fiyat verileri [0,1] aralığına ölçeklendirilir:
#scale data using MinMaxScaler from sklearn.preprocessing import MinMaxScaler scaler=MinMaxScaler(feature_range=(0,1)) scaled_data = scaler.fit_transform(data)
Verilerin ilk %80'i eğitim için kullanılacaktır.
#training size is 80% of the data training_size = int(len(scaled_data)*0.80) print("training size:",training_size)
training size: 1636
#create train data and check size train_data_initial = scaled_data[0:training_size,:] print(len(train_data_initial))
1636
#create test data and check size test_data_initial= scaled_data[training_size:,:1] print(len(test_data_initial))
409
Aşağıdaki fonksiyon eğitim sekansları oluşturur:
#split a univariate sequence into samples def split_sequence(sequence, n_steps): X, y = list(), list() for i in range(len(sequence)): #find the end of this pattern end_ix = i + n_steps #check if we are beyond the sequence if end_ix > len(sequence)-1: break #gather input and output parts of the pattern seq_x, seq_y = sequence[i:end_ix], sequence[end_ix] X.append(seq_x) y.append(seq_y) return np.array(X), np.array(y)
Kümeler inşa edilir:
#split into samples time_step = 120 x_train, y_train = split_sequence(train_data_initial, time_step) x_test, y_test = split_sequence(test_data_initial, time_step) #reshape input to be [samples, time steps, features] which is required for LSTM x_train =x_train.reshape(x_train.shape[0],x_train.shape[1],1) x_test = x_test.reshape(x_test.shape[0],x_test.shape[1],1)
Eğitim ve test için tensör şekilleri:
#show shape of train data x_train.shape
(1516, 120, 1)
#show shape of test data x_test.shape
(289, 120, 1)
#import keras libraries for the model import math from keras.models import Sequential from keras.layers import Dense,Activation,Conv1D,MaxPooling1D,Dropout from keras.layers import LSTM from keras.utils.vis_utils import plot_model from keras.metrics import RootMeanSquaredError as rmse from keras import optimizers
Model ayarlanır:
#define the model model = Sequential() model.add(Conv1D(filters=256, kernel_size=2,activation='relu',padding = 'same',input_shape=(120,1))) model.add(MaxPooling1D(pool_size=2)) model.add(LSTM(100, return_sequences = True)) model.add(Dropout(0.3)) model.add(LSTM(100, return_sequences = False)) model.add(Dropout(0.3)) model.add(Dense(units=1, activation = 'sigmoid')) model.compile(optimizer='adam', loss= 'mse' , metrics = [rmse()])
Model özellikleri gösterilir:
#show model model.summary()
Model eğitimi:
#measure time import time time_calc_start = time.time() #fit model with 300 epochs history=model.fit(x_train,y_train,epochs=300,validation_data=(x_test,y_test),batch_size=32,verbose=1) #calculate time fit_time_seconds = time.time() - time_calc_start print("fit time =",fit_time_seconds," seconds.")
Epoch 1/300
48/48 [==============================] - 8s 49ms/step - loss: 0.0129 - root_mean_squared_error: 0.1136 - val_loss: 0.0065 - val_root_mean_squared_error: 0.0804
...
Epoch 299/300
48/48 [==============================] - 2s 35ms/step - loss: 4.5197e-04 - root_mean_squared_error: 0.0213 - val_loss: 4.2535e-04 - val_root_mean_squared_error: 0.0206
Epoch 300/300
48/48 [==============================] - 2s 32ms/step - loss: 4.2967e-04 - root_mean_squared_error: 0.0207 - val_loss: 4.4040e-04 - val_root_mean_squared_error: 0.0210
fit time = 467.4918096065521 seconds.
Eğitim yaklaşık 8 dakika sürdü.
#show training history keys history.history.keys()
Eğitim ve test veri kümelerindeki optimizasyon dinamikleri:
#show iteration-loss graph for training and validation plt.figure(figsize = (18,10)) plt.plot(history.history['loss'],label='Training Loss',color='b') plt.plot(history.history['val_loss'],label='Validation-loss',color='g') plt.xlabel("Iteration") plt.ylabel("Loss") plt.title("LOSS") plt.legend()
#show iteration-rmse graph for training and validation plt.figure(figsize = (18,10)) plt.plot(history.history['root_mean_squared_error'],label='Training RMSE',color='b') plt.plot(history.history['val_root_mean_squared_error'],label='Validation-RMSE',color='g') plt.xlabel("Iteration") plt.ylabel("RMSE") plt.title("RMSE") plt.legend()
#evaluate training data model.evaluate(x_train,y_train, batch_size = 32)
[0.00029911252204328775, 0.01729486882686615]
#evaluate testing data model.evaluate(x_test,y_test, batch_size = 32)
10/10 [==============================] - 0s 31ms/step - loss: 4.4040e-04 - root_mean_squared_error: 0.0210
[0.00044039846397936344, 0.020985672250390053]
Eğitim veri kümesi üzerinde tahmin oluşturulur:
#prediction using training data train_predict = model.predict(x_train) plot_y_train = y_train.reshape(-1,1)
Eğitim aralığı için gerçek ve tahmin edilen grafiklerin çıktısı alınır:
#show actual vs predicted (training) graph plt.figure(figsize=(18,10)) plt.plot(scaler.inverse_transform(plot_y_train),color = 'b', label = 'Original') plt.plot(scaler.inverse_transform(train_predict),color='red', label = 'Predicted') plt.title("Prediction Graph Using Training Data") plt.xlabel("Hours") plt.ylabel("Price") plt.legend() plt.show()
Test veri kümesi üzerinde tahmin oluşturulur:
#prediction using testing data test_predict = model.predict(x_test) plot_y_test = y_test.reshape(-1,1)
11/11 [==============================] - 0s 11ms/step
Ölçütleri hesaplamak için verileri [0,1] aralığından dönüştürmemiz gerekir. Yine MinMaxScaler kullanıyoruz.
#calculate metrics from sklearn import metrics from sklearn.metrics import r2_score #transform data to real values value1=scaler.inverse_transform(plot_y_test) value2=scaler.inverse_transform(test_predict) #calc score score = np.sqrt(metrics.mean_squared_error(value1,value2)) print("RMSE : {}".format(score)) print("MSE :", metrics.mean_squared_error(value1,value2)) print("R2 score :",metrics.r2_score(value1,value2))
RMSE : 0.0015151631684117558
MSE : 2.295719426911551e-06
R2 score : 0.9683533377809039
#show actual vs predicted (testing) graph plt.figure(figsize=(18,10)) plt.plot(scaler.inverse_transform(plot_y_test),color = 'b', label = 'Original') plt.plot(scaler.inverse_transform(test_predict),color='g', label = 'Predicted') plt.title("Prediction Graph Using Testing Data") plt.xlabel("Hours") plt.ylabel("Price") plt.legend() plt.show()
Model bir onnx dosyasına aktarılır:
# save model to ONNX output_path = data_path+"model.eurusd.H1.120.onnx" onnx_model = tf2onnx.convert.from_keras(model, output_path=output_path) print(f"model saved to {output_path}") output_path = file_path+"model.eurusd.H1.120.onnx" onnx_model = tf2onnx.convert.from_keras(model, output_path=output_path) print(f"saved model to {output_path}") # finish mt5.shutdown()
Python komut dosyasının tam kodu bir Jupyter Notebook'ta makaleye eklenmiştir.
A CNN-LSTM-Based Model to Forecast Stock Prices makalesinde en iyi sonuç R^2 = 0.9646 ile CNN-LSTM mimarisine sahip modeller için elde edilmiştir. Örneğimizde, CNN-LSTM ağı R^2 = 0,9684 ile en iyi sonucu üretmiştir. Sonuçlara göre, bu tür modeller tahmin problemlerinin çözümünde etkili olabilir.
Böylece, finansal zaman serilerini tahmin etmek için CNN-LSTM modellerini oluşturan ve eğiten bir Python komut dosyası örneğini ele aldık.
2. Modeli MetaTrader 5'te kullanma
2.1. Başlamadan önce bilmeniz gerekenler
Bir model oluşturmanın iki yolu vardır: Bir onnx dosyasından bir model oluşturmak için OnnxCreate'i veya bir veri dizisinden oluşturmak için OnnxCreateFromBuffer'ı kullanabilirsiniz.
Bir ONNX modeli bir Uzman Danışmanda kaynak olarak kullanılıyorsa, modeli her değiştirdiğinizde Uzman Danışmanı yeniden derlemeniz gerekecektir.
Tüm modeller tam olarak tanımlanmış büyüklüklerde girdi ve/veya çıktı tensörüne sahip değildir. Normalde paket büyüklüğünden sorumlu olan ilk boyuttur. Bir modeli çalıştırmadan önce, OnnxSetInputShape ve OnnxSetOutputShape fonksiyonlarını kullanarak büyüklükleri açıkça belirtmeniz gerekir. Modelin girdi verileri, modeli eğitirken yapıldığı gibi hazırlanmalıdır.
Girdi ve çıktı verileri için, modelde kullanılan aynı türdeki dizileri, matrisleri ve/veya vektörleri kullanmanızı öneririz. Bu durumda, modeli çalıştırırken verileri dönüştürmeniz gerekmeyecektir. Veriler gerekli türde ifade edilemiyorsa, otomatik olarak dönüştürülecektir.
Modelinizi çalıştırmak için OnnxRun'ı kullanın. Bir modelin birden çok kez çalıştırılabileceğini unutmayın. Modeli kullandıktan sonra, OnnxRelease fonksiyonunu kullanarak serbest bırakın.
MQL5'te ONNX modelleri için eksiksiz dokümantasyon.
2.2. Bir onnx dosyasını okuma ve girdiler ve çıktılar hakkında bilgi alma
Modelimizi kullanabilmek için model konumunu, girdi veri türünü ve şeklini, ayrıca çıktı veri türünü ve şeklini bilmemiz gerekir. Daha önce oluşturulan komut dosyasına göre, model.eurusd.H1.120.onnx, onnx dosyasını oluşturan Python komut dosyası ile aynı klasörde bulunmaktadır. Girdi float32 türünde, 120 normalleştirilmiş Kapanış fiyatıdır (1'e eşit grup büyüklüğü ile çalışmak için); çıktı float32 türünde, model tarafından tahmin edilen normalleştirilmiş fiyattır.
Ayrıca, bir MQL5 komut dosyası kullanarak model girdi ve çıktı verilerini elde etmek için MQL5\Files klasöründe onnx dosyasını oluşturduk.
//+------------------------------------------------------------------+ //| OnnxModelInfo.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 UNDEFINED_REPLACE 1 //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { string file_names[]; if(FileSelectDialog("Open ONNX model",NULL,"ONNX files (*.onnx)|*.onnx|All files (*.*)|*.*",FSD_FILE_MUST_EXIST,file_names,NULL)<1) return; PrintFormat("Create model from %s with debug logs",file_names[0]); long session_handle=OnnxCreate(file_names[0],ONNX_DEBUG_LOGS); if(session_handle==INVALID_HANDLE) { Print("OnnxCreate error ",GetLastError()); return; } OnnxTypeInfo type_info; long input_count=OnnxGetInputCount(session_handle); Print("model has ",input_count," input(s)"); for(long i=0; i<input_count; i++) { string input_name=OnnxGetInputName(session_handle,i); Print(i," input name is ",input_name); if(OnnxGetInputTypeInfo(session_handle,i,type_info)) PrintTypeInfo(i,"input",type_info); } long output_count=OnnxGetOutputCount(session_handle); Print("model has ",output_count," output(s)"); for(long i=0; i<output_count; i++) { string output_name=OnnxGetOutputName(session_handle,i); Print(i," output name is ",output_name); if(OnnxGetOutputTypeInfo(session_handle,i,type_info)) PrintTypeInfo(i,"output",type_info); } OnnxRelease(session_handle); } //+------------------------------------------------------------------+ //| PrintTypeInfo | //+------------------------------------------------------------------+ void PrintTypeInfo(const long num,const string layer,const OnnxTypeInfo& type_info) { Print(" type ",EnumToString(type_info.type)); Print(" data type ",EnumToString(type_info.element_type)); if(type_info.dimensions.Size()>0) { bool dim_defined=(type_info.dimensions[0]>0); string dimensions=IntegerToString(type_info.dimensions[0]); for(long n=1; n<type_info.dimensions.Size(); n++) { if(type_info.dimensions[n]<=0) dim_defined=false; dimensions+=", "; dimensions+=IntegerToString(type_info.dimensions[n]); } Print(" shape [",dimensions,"]"); //--- not all dimensions defined if(!dim_defined) PrintFormat(" %I64d %s shape must be defined explicitly before model inference",num,layer); //--- reduce shape uint reduced=0; long dims[]; for(long n=0; n<type_info.dimensions.Size(); n++) { long dimension=type_info.dimensions[n]; //--- replace undefined dimension if(dimension<=0) dimension=UNDEFINED_REPLACE; //--- 1 can be reduced if(dimension>1) { ArrayResize(dims,reduced+1); dims[reduced++]=dimension; } } //--- all dimensions assumed 1 if(reduced==0) { ArrayResize(dims,1); dims[reduced++]=1; } //--- shape was reduced if(reduced<type_info.dimensions.Size()) { dimensions=IntegerToString(dims[0]); for(long n=1; n<dims.Size(); n++) { dimensions+=", "; dimensions+=IntegerToString(dims[n]); } string sentence=""; if(!dim_defined) sentence=" if undefined dimension set to "+(string)UNDEFINED_REPLACE; PrintFormat(" shape of %s data can be reduced to [%s]%s",layer,dimensions,sentence); } } else PrintFormat("no dimensions defined for %I64d %s",num,layer); } //+------------------------------------------------------------------+
Dosya seçimi penceresinden MQL5\Files içerisinde bulunan onnx dosyasını seçtik, OnnxCreate’i kullanarak dosyadan bir model oluşturduk ve aşağıdaki bilgileri elde ettik.
Create model from model.eurusd.H1.120.onnx with debug logs ONNX: Creating and using per session threadpools since use_per_session_threads_ is true ONNX: Dynamic block base set to 0 ONNX: Initializing session. ONNX: Adding default CPU execution provider. ONNX: Total shared scalar initializer count: 0 ONNX: Total fused reshape node count: 0 ONNX: Removing NodeArg 'Gather_out0'. It is no longer used by any node. ONNX: Removing NodeArg 'Gather_token_1_out0'. It is no longer used by any node. ONNX: Total shared scalar initializer count: 0 ONNX: Total fused reshape node count: 0 ONNX: Removing initializer 'sequential/conv1d/Conv1D/ExpandDims_1:0'. It is no longer used by any node. ONNX: Use DeviceBasedPartition as default ONNX: Saving initialized tensors. ONNX: Done saving initialized tensors ONNX: Session successfully initialized. model has 1 input(s) 0 input name is conv1d_input type ONNX_TYPE_TENSOR data type ONNX_DATA_TYPE_FLOAT shape [-1, 120, 1] 0 input shape must be defined explicitly before model inference shape of input data can be reduced to [120] if undefined dimension set to 1 model has 1 output(s) 0 output name is dense type ONNX_TYPE_TENSOR data type ONNX_DATA_TYPE_FLOAT shape [-1, 1] 0 output shape must be defined explicitly before model inference shape of output data can be reduced to [1] if undefined dimension set to 1
Hata ayıklama modu etkinleştirildiğinden,
long session_handle=OnnxCreate(file_names[0],ONNX_DEBUG_LOGS);
ONNX öneki olan günlüğe sahibiz.
Modelin aslında bir girdi ve bir çıktıya sahip olduğunu görüyoruz. Burada, girdi tensörünün ilk boyutu ve çıktı tensörünün ilk boyutu tanımlanmamıştır. Bu boyutların grup büyüklüğünden sorumlu olduğu varsayılmaktadır. Bu nedenle, modeli çalıştırmadan önce, hangi büyüklüklerle çalışacağımızı açıkça belirtmeliyiz (OnnxSetInputShape ve OnnxSetOutputShape). Genellikle modele yalnızca bir veri kümesi girilir. Bir sonraki "Bir ticaret Uzman Danışmanında ONNX modeli kullanma örneği" başlığı altında ayrıntılı bir örnek verilmiştir.
Verileri hazırlarken, boyutları [1, 120, 1] olan bir dizi kullanmak gerekli değildir. Tek boyutlu bir dizi veya 120 elemanlı bir vektör girebiliriz.
2.3. Bir ticaret Uzman Danışmanında ONNX modeli kullanma örneği
Bildiriler ve tanımlar
#include <Trade\Trade.mqh> input double InpLots = 1.0; // Lots amount to open position #resource "Python/model.120.H1.onnx" as uchar ExtModel[] #define SAMPLE_SIZE 120 long ExtHandle=INVALID_HANDLE; int ExtPredictedClass=-1; datetime ExtNextBar=0; datetime ExtNextDay=0; float ExtMin=0.0; float ExtMax=0.0; CTrade ExtTrade; //--- price movement prediction #define PRICE_UP 0 #define PRICE_SAME 1 #define PRICE_DOWN 2
OnInit fonksiyonu:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { if(_Symbol!="EURUSD" || _Period!=PERIOD_H1) { Print("model must work with EURUSD,H1"); return(INIT_FAILED); } //--- create a model from static buffer ExtHandle=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(ExtHandle==INVALID_HANDLE) { Print("OnnxCreateFromBuffer error ",GetLastError()); return(INIT_FAILED); } //--- 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,SAMPLE_SIZE,1}; if(!OnnxSetInputShape(ExtHandle,ONNX_DEFAULT,input_shape)) { Print("OnnxSetInputShape error ",GetLastError()); return(INIT_FAILED); } //--- 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(ExtHandle,0,output_shape)) { Print("OnnxSetOutputShape error ",GetLastError()); return(INIT_FAILED); } //--- return(INIT_SUCCEEDED); }
Mevcut sembol / zaman dilimi verilerini kullandığımız için yalnızca EURUSD, H1 ile çalışıyoruz.
Modelimiz Uzman Danışmana bir kaynak olarak dahil edilmiştir. Uzman Danışman tamamen kendi kendine yeterlidir ve harici bir onnx dosyasını okumayı gerektirmez. Kaynak dizisinden bir model oluşturulur.
Girdi ve çıktı veri şekilleri açıkça tanımlanmalıdır.
OnTick fonksiyonu:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- check new day if(TimeCurrent()>=ExtNextDay) { GetMinMax(); //--- set next day time ExtNextDay=TimeCurrent(); ExtNextDay-=ExtNextDay%PeriodSeconds(PERIOD_D1); ExtNextDay+=PeriodSeconds(PERIOD_D1); } //--- check new bar if(TimeCurrent()<ExtNextBar) return; //--- set next bar time ExtNextBar=TimeCurrent(); ExtNextBar-=ExtNextBar%PeriodSeconds(); ExtNextBar+=PeriodSeconds(); //--- check min and max double close=iClose(_Symbol,_Period,0); if(ExtMin>close) ExtMin=close; if(ExtMax<close) ExtMax=close; //--- predict next price PredictPrice(); //--- check trading according to prediction if(ExtPredictedClass>=0) if(PositionSelect(_Symbol)) CheckForClose(); else CheckForOpen(); }
Yeni bir günün başlangıcını tanımlıyoruz. Gün başlangıcı, 120 saatlik sekanstaki fiyatları normalleştirmek adına 120 günlük sekansın Düşük ve Yüksek değerlerini güncellemek için kullanılır. Model, girdi verilerini hazırlarken izlememiz gereken bu koşullar altında eğitilmiştir.
//+------------------------------------------------------------------+ //| Get minimal and maximal Close for last 120 days | //+------------------------------------------------------------------+ void GetMinMax(void) { vectorf close; close.CopyRates(_Symbol,PERIOD_D1,COPY_RATES_CLOSE,0,SAMPLE_SIZE); ExtMin=close.Min(); ExtMax=close.Max(); }
Gerekirse, gün içerisindeki Düşük ve Yüksek değerlerini değiştirebiliriz.
Tahmin fonksiyonu:
//+------------------------------------------------------------------+ //| Predict next price | //+------------------------------------------------------------------+ void PredictPrice(void) { static vectorf output_data(1); // vector to get result static vectorf x_norm(SAMPLE_SIZE); // vector for prices normalize //--- check for normalization possibility if(ExtMin>=ExtMax) { ExtPredictedClass=-1; return; } //--- request last bars if(!x_norm.CopyRates(_Symbol,_Period,COPY_RATES_CLOSE,1,SAMPLE_SIZE)) { ExtPredictedClass=-1; return; } float last_close=x_norm[SAMPLE_SIZE-1]; //--- normalize prices x_norm-=ExtMin; x_norm/=(ExtMax-ExtMin); //--- run the inference if(!OnnxRun(ExtHandle,ONNX_NO_CONVERSION,x_norm,output_data)) { ExtPredictedClass=-1; return; } //--- denormalize the price from the output value float predicted=output_data[0]*(ExtMax-ExtMin)+ExtMin; //--- classify predicted price movement float delta=last_close-predicted; if(fabs(delta)<=0.00001) ExtPredictedClass=PRICE_SAME; else { if(delta<0) ExtPredictedClass=PRICE_UP; else ExtPredictedClass=PRICE_DOWN; } }
İlk olarak, normalize edip edemeyeceğimizi kontrol ederiz. Normalleştirme, MinMaxScaler Python fonksiyonunda olduğu gibi uygulanır.
#scale data from sklearn.preprocessing import MinMaxScaler scaler=MinMaxScaler(feature_range=(0,1)) scaled_data = scaler.fit_transform(data)
Dolayısıyla, normalleştirme kodu çok basit ve anlaşılırdır.
Girdi verileri ve sonucu almak için vektörler statik olarak düzenlenmiştir. Bu, tüm program ömrü boyunca var olan, yeniden konumlandırılamayan bir arabelleği garanti eder. Böylece, ONNX modelinin girdi ve çıktı tensörleri modeli her çalıştırdığımızda yeniden oluşturulmaz.
Anahtar fonksiyon OnnxRun'dır. ONNX_NO_CONVERSION bayrağı, MQL5 float türü tam olarak ONNX_DATA_TYPE_FLOAT'a karşılık geldiğinden girdi ve çıktı verilerinin dönüştürülmemesi gerektiğini belirtir. ONNX_DEBUG bayrağı ayarlanmamıştır.
Sonrasında, elde edilen verileri tahmin edilen fiyata denormalize ediyoruz ve sınıfı belirliyoruz: fiyat yükselecek mi, düşecek mi yoksa değişmeyecek mi?
Ticaret stratejisi basittir. Her saatin başında, o saatin sonu için fiyat öngörüsünü kontrol ederiz. Tahmin edilen fiyat yükselirse, alış yaparız. Eğer model aşağı yönlü bir hareket tahmin ediyorsa, satış yaparız.
//+------------------------------------------------------------------+ //| Check for open position conditions | //+------------------------------------------------------------------+ void CheckForOpen(void) { ENUM_ORDER_TYPE signal=WRONG_VALUE; //--- check signals if(ExtPredictedClass==PRICE_DOWN) signal=ORDER_TYPE_SELL; // sell condition else { if(ExtPredictedClass==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; double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID); double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK); if(signal==ORDER_TYPE_SELL) price=bid; else price=ask; ExtTrade.PositionOpen(_Symbol,signal,InpLots,price,0.0,0.0); } } //+------------------------------------------------------------------+ //| Check for close position conditions | //+------------------------------------------------------------------+ void CheckForClose(void) { bool bsignal=false; //--- position already selected before long type=PositionGetInteger(POSITION_TYPE); //--- check signals if(type==POSITION_TYPE_BUY && ExtPredictedClass==PRICE_DOWN) bsignal=true; if(type==POSITION_TYPE_SELL && ExtPredictedClass==PRICE_UP) bsignal=true; //--- close position if possible if(bsignal && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { ExtTrade.PositionClose(_Symbol,3); //--- open opposite CheckForOpen(); } }
Şimdi, Strateji Sınayıcıda Uzman Danışman performansını kontrol edelim. Uzman Danışmanı yılın başından itibaren test etmek için modelin daha önceki veriler kullanılarak eğitilmesi gerekir. Bu nedenle, kullanılmayan kısımları kaldırarak ve eğitim bitiş tarihini test dönemiyle çakışmayacak şekilde değiştirerek Python komut dosyasını biraz değiştirdik.
ONNX.eurusd.H1.120.Training.py komut dosyası Python alt klasöründe bulunur ve doğrudan MetaEditor'da çalışır. Ortaya çıkan ONNX modeli aynı Python alt klasörüne kaydedilecek ve Uzman Danışman derlemesi sırasında bir kaynak olarak kullanılacaktır.
# Copyright 2023, MetaQuotes Ltd. # https://www.mql5.com # python libraries import MetaTrader5 as mt5 import tensorflow as tf import numpy as np import pandas as pd import tf2onnx # input parameters inp_model_name = "model.eurusd.H1.120.onnx" inp_history_size = 120 if not mt5.initialize(): print("initialize() failed, error code =",mt5.last_error()) quit() # we will save generated onnx-file near our script to use as resource from sys import argv data_path=argv[0] last_index=data_path.rfind("\\")+1 data_path=data_path[0:last_index] print("data path to save onnx model",data_path) # set start and end dates for history data from datetime import timedelta, datetime #end_date = datetime.now() end_date = datetime(2023, 1, 1, 0) start_date = end_date - timedelta(days=inp_history_size) # print start and end dates print("data start date =",start_date) print("data end date =",end_date) # get rates eurusd_rates = mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_H1, start_date, end_date) # create dataframe df = pd.DataFrame(eurusd_rates) # get close prices only data = df.filter(['close']).values # scale data from sklearn.preprocessing import MinMaxScaler scaler=MinMaxScaler(feature_range=(0,1)) scaled_data = scaler.fit_transform(data) # training size is 80% of the data training_size = int(len(scaled_data)*0.80) print("Training_size:",training_size) train_data_initial = scaled_data[0:training_size,:] test_data_initial = scaled_data[training_size:,:1] # split a univariate sequence into samples def split_sequence(sequence, n_steps): X, y = list(), list() for i in range(len(sequence)): # find the end of this pattern end_ix = i + n_steps # check if we are beyond the sequence if end_ix > len(sequence)-1: break # gather input and output parts of the pattern seq_x, seq_y = sequence[i:end_ix], sequence[end_ix] X.append(seq_x) y.append(seq_y) return np.array(X), np.array(y) # split into samples time_step = inp_history_size x_train, y_train = split_sequence(train_data_initial, time_step) x_test, y_test = split_sequence(test_data_initial, time_step) # reshape input to be [samples, time steps, features] which is required for LSTM x_train =x_train.reshape(x_train.shape[0],x_train.shape[1],1) x_test = x_test.reshape(x_test.shape[0],x_test.shape[1],1) # define model from keras.models import Sequential from keras.layers import Dense, Activation, Conv1D, MaxPooling1D, Dropout, Flatten, LSTM from keras.metrics import RootMeanSquaredError as rmse model = Sequential() model.add(Conv1D(filters=256, kernel_size=2, activation='relu',padding = 'same',input_shape=(inp_history_size,1))) model.add(MaxPooling1D(pool_size=2)) model.add(LSTM(100, return_sequences = True)) model.add(Dropout(0.3)) model.add(LSTM(100, return_sequences = False)) model.add(Dropout(0.3)) model.add(Dense(units=1, activation = 'sigmoid')) model.compile(optimizer='adam', loss= 'mse' , metrics = [rmse()]) # model training for 300 epochs history = model.fit(x_train, y_train, epochs = 300 , validation_data = (x_test,y_test), batch_size=32, verbose=2) # evaluate training data train_loss, train_rmse = model.evaluate(x_train,y_train, batch_size = 32) print(f"train_loss={train_loss:.3f}") print(f"train_rmse={train_rmse:.3f}") # evaluate testing data test_loss, test_rmse = model.evaluate(x_test,y_test, batch_size = 32) print(f"test_loss={test_loss:.3f}") print(f"test_rmse={test_rmse:.3f}") # save model to ONNX output_path = data_path+inp_model_name onnx_model = tf2onnx.convert.from_keras(model, output_path=output_path) print(f"saved model to {output_path}") # finish mt5.shutdown()
ONNX modeline dayalı bir Uzman Danışmanın test edilmesi
Şimdi, Uzman Danışmanı Strateji Sınayıcıda geçmiş veriler üzerinde test edelim. Modeli eğitmek için kullandığımız parametrelerin aynısını belirtiriz: EURUSD sembolü ve H1 zaman dilimi.
Test aralığı eğitim dönemini içermez: yılın başından itibaren başlar (01/01/2023).
Stratejiye göre, sinyaller her saatin başında bir kez kontrol edilir (Uzman Danışman yeni bir çubuğun ortaya çıkışını izler), bu nedenle tik modelleme modu önemli değildir. OnTick, sınayıcıda çubuk başına bir kez işlenecektir.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- check new day if(TimeCurrent()>=ExtNextDay) { GetMinMax(); //--- set next day time ExtNextDay=TimeCurrent(); ExtNextDay-=ExtNextDay%PeriodSeconds(PERIOD_D1); ExtNextDay+=PeriodSeconds(PERIOD_D1); } //--- check new bar if(TimeCurrent()<ExtNextBar) return; //--- set next bar time ExtNextBar=TimeCurrent(); ExtNextBar-=ExtNextBar%PeriodSeconds(); ExtNextBar+=PeriodSeconds(); //--- check min and max float close=(float)iClose(_Symbol,_Period,0); if(ExtMin>close) ExtMin=close; if(ExtMax<close) ExtMax=close; //--- predict next price PredictPrice(); //--- check trading according to prediction if(ExtPredictedClass>=0) if(PositionSelect(_Symbol)) CheckForClose(); else CheckForOpen(); }
Böylece, üç aylık dönem testi yalnızca birkaç saniye sürer. Sonuçlar aşağıda verilmiştir.
Şimdi, pozisyonun bir sinyalle açılmasını ve Zararı Durdur (SL) veya Kârı Al (TP) ile kapanmasını sağlamak için ticaret stratejisini değiştirelim.
input double InpLots = 1.0; // Lots amount to open position input bool InpUseStops = true; // Use stops in trading input int InpTakeProfit = 500; // TakeProfit level input int InpStopLoss = 500; // StopLoss level //+------------------------------------------------------------------+ //| Check for open position conditions | //+------------------------------------------------------------------+ void CheckForOpen(void) { ENUM_ORDER_TYPE signal=WRONG_VALUE; //--- check signals if(ExtPredictedClass==PRICE_DOWN) signal=ORDER_TYPE_SELL; // sell condition else { if(ExtPredictedClass==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,sl=0,tp=0; double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID); double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK); if(signal==ORDER_TYPE_SELL) { price=bid; if(InpUseStops) { sl=NormalizeDouble(bid+InpStopLoss*_Point,_Digits); tp=NormalizeDouble(ask-InpTakeProfit*_Point,_Digits); } } else { price=ask; if(InpUseStops) { sl=NormalizeDouble(ask-InpStopLoss*_Point,_Digits); tp=NormalizeDouble(bid+InpTakeProfit*_Point,_Digits); } } ExtTrade.PositionOpen(_Symbol,signal,InpLots,price,sl,tp); } } //+------------------------------------------------------------------+ //| Check for close position conditions | //+------------------------------------------------------------------+ void CheckForClose(void) { //--- position should be closed by stops if(InpUseStops) return; bool bsignal=false; //--- position already selected before long type=PositionGetInteger(POSITION_TYPE); //--- check signals if(type==POSITION_TYPE_BUY && ExtPredictedClass==PRICE_DOWN) bsignal=true; if(type==POSITION_TYPE_SELL && ExtPredictedClass==PRICE_UP) bsignal=true; //--- close position if possible if(bsignal && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { ExtTrade.PositionClose(_Symbol,3); //--- open opposite CheckForOpen(); } }
InpUseStops = true, bu da SL ve TP seviyelerinin pozisyon açılışında ayarlandığı anlamına gelir.
Aynı dönem için SL/TP seviyeleri ile yapılan test sonuçları:
Uzman Danışmanın ve eğitilmiş modelin (2023 yılının başına kadar) tam kaynak kodu ekte verilmiştir.
Sonuç
Makale, ONNX modellerinin MQL5 programlarında kullanılmasında zor bir şey olmadığını göstermektedir. Aslında modellerin uygulanması en kolay kısımken, yeterli bir ONNX modeli elde etmek çok daha zordur.
Makalede kullanılan modelin, MQL5 dilini kullanarak ONNX modelleriyle nasıl çalışılacağını göstermek için yalnızca tanıtım amacıyla sağlandığını lütfen unutmayın. Bu makalede sunulan Uzman Danışman gerçek ticaret için tasarlanmamıştır.
MetaQuotes Ltd tarafından Rusçadan çevrilmiştir.
Orijinal makale: https://www.mql5.com/ru/articles/12373
- Ü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