English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Italiano
preview
MQL5'te ONNX modelleri nasıl kullanılır?

MQL5'te ONNX modelleri nasıl kullanılır?

MetaTrader 5Makine öğrenimi | 10 Ekim 2023, 10:55
861 0
MetaQuotes
MetaQuotes

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:

2.10.0
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)
C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Files\

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 start date= 2022-11-28 12:28:39.870685
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()

EURUSD H1 Kapanış fiyatları grafiği

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 özellikleri

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()

dict_keys(['loss', 'root_mean_squared_error', 'val_loss', 'val_root_mean_squared_error'])

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()

Kayıp fonksiyonu yinelemeleri grafiği

#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()


RMSE yinelemeleri grafiği

#evaluate training data
model.evaluate(x_train,y_train, batch_size = 32)

48/48 [==============================] - 1s 22ms/step - loss: 2.9911e-04 - root_mean_squared_error: 0.0173
[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)

48/48 [==============================] - 2s 18ms/step

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()

Eğitim veri kümesi üzerindeki tahminlerin grafiği

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()

Test veri kümesi üzerindeki tahminlerin grafiği


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). 

Uzman Danışman test ayarları


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.

Uzman Danışman test sonuçları



Ş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.

SL/TP kullanarak Uzman Danışman girdi parametreleri


Aynı dönem için SL/TP seviyeleri ile yapılan test sonuçları:

SL ve TP seviyelerini kullanan Uzman Danışman 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

Ekli dosyalar |
MQL5.zip (1243.88 KB)
MetaTrader VPS'i ilk kez başlatma: Adım adım talimatlar MetaTrader VPS'i ilk kez başlatma: Adım adım talimatlar
Ticaret robotları veya sinyal abonelikleri kullanan herkes er ya da geç işlem platformları için güvenilir bir 7/24 sunucu kiralama ihtiyacı duyar. Çeşitli nedenlerden dolayı MetaTrader VPS kullanmanızı öneririz. MQL5.community hesabınız üzerinden hizmet için rahatça ödeme yapabilir ve aboneliği yönetebilirsiniz.
Sıfırdan bir ticaret Uzman Danışmanı geliştirme (Bölüm 23): Yeni emir sistemi (VI) Sıfırdan bir ticaret Uzman Danışmanı geliştirme (Bölüm 23): Yeni emir sistemi (VI)
Emir sistemini daha akıcı hale getireceğiz. Bu makalede, pozisyon eşik seviyelerini çok daha hızlı değiştirebilmek için kodda yapılması gereken değişiklikleri ele alacağız.
Popülasyon optimizasyon algoritmaları: Ateş böceği algoritması (Firefly Algorithm, FA) Popülasyon optimizasyon algoritmaları: Ateş böceği algoritması (Firefly Algorithm, FA)
Bu makalede ateş böceği algoritması (Firefly Algorithm, FA) optimizasyon yöntemini ele alacağız. Yapılan değişiklik sayesinde algoritma, dışarıdan bakan bir oyuncudan gerçek bir derecelendirme tablosu liderine dönüştü.
Sıfırdan bir ticaret Uzman Danışmanı geliştirme (Bölüm 22): Yeni emir sistemi (V) Sıfırdan bir ticaret Uzman Danışmanı geliştirme (Bölüm 22): Yeni emir sistemi (V)
Bugün yeni emir sistemini geliştirmeye devam edeceğiz. Yeni bir sistemi uygulamak o kadar da kolay değildir, zira sık sık süreci büyük ölçüde zorlaştıran sorunlarla karşılaşırız. Bu sorunlar ortaya çıktığında durmalı ve ilerlediğimiz yönü yeniden analiz etmeliyiz.