MQL5'te ONNX modellerinin nasıl bir araya getirileceğine dair bir örnek
Giriş
İstikrarlı ticaret için, genellikle hem ticareti yapılan enstrümanların hem de ticaret stratejilerinin çeşitlendirilmesi önerilir. Aynı durum makine öğrenimi modelleri için de geçerlidir: karmaşık bir model yerine daha basit birkaç model oluşturmak daha kolaydır. Ancak bu modelleri tek bir ONNX modelinde bir araya getirmek zor olabilir.
Bununla birlikte, birkaç eğitilmiş ONNX modelini tek bir MQL5 programında birleştirmek mümkündür. Bu makalede, oylama sınıflandırıcısı olarak adlandırılan topluluklardan birini ele alacağız. Size böyle bir topluluğu uygulamanın ne kadar kolay olduğunu göstereceğiz.
Proje için modeller
Örneğimiz için iki basit model kullanacağız: bir regresyon fiyat tahmin modeli ve bir sınıflandırma fiyat hareketi tahmin modeli. Modeller arasındaki temel fark, regresyonun niceliği tahmin ederken, sınıflandırmanın sınıfı tahmin etmesidir.
İlk model regresyondur.
2003'ten 2022'nin sonuna kadar EURUSD D1 verileri kullanılarak eğitilmiştir. Eğitim, 10 OHLC fiyatı serisi kullanılarak gerçekleştirilmiştir. Modelin eğitilebilirliğini artırmak için fiyatları normalleştiriyor ve serideki ortalama fiyatı serideki standart sapmaya bölüyoruz. Böylece, bir 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.
Sonuç olarak, model ertesi gün için kapanış fiyatını tahmin etmelidir.
Kod oldukça basittir. Burada yalnızca sunum amacıyla verilmiştir.
# Copyright 2023, MetaQuotes Ltd. # https://www.mql5.com from datetime import datetime 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 tqdm import tqdm from sys import argv if not mt5.initialize(): print("initialize() failed, error code =",mt5.last_error()) quit() # we will save generated onnx-file near the our script 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) # input parameters inp_model_name = "model.eurusd.D1.10.onnx" inp_history_size = 10 inp_start_date = datetime(2003, 1, 1, 0) inp_end_date = datetime(2023, 1, 1, 0) # get data from client terminal eurusd_rates = mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_D1, inp_start_date, inp_end_date) df = pd.DataFrame(eurusd_rates) # # collect dataset subroutine # def collect_dataset(df: pd.DataFrame, history_size: int): """ Collect dataset for the following regression problem: - input: history_size consecutive H1 bars; - output: close price for the next bar. :param df: D1 bars for a range of dates :param history_size: how many bars should be considered for making a prediction :return: features and labels """ n = len(df) xs = [] ys = [] for i in tqdm(range(n - history_size)): w = df.iloc[i: i + history_size + 1] x = w[['open', 'high', 'low', 'close']].iloc[:-1].values y = w.iloc[-1]['close'] xs.append(x) ys.append(y) X = np.array(xs) y = np.array(ys) return X, y ### # get prices X, y = collect_dataset(df, history_size=inp_history_size) # normalize prices m = X.mean(axis=1, keepdims=True) s = X.std(axis=1, keepdims=True) X_norm = (X - m) / s y_norm = (y - m[:, 0, 3]) / s[:, 0, 3] # split data to train and test sets X_train, X_test, y_train, y_test = train_test_split(X_norm, y_norm, test_size=0.2, random_state=0) # define model model = tf.keras.Sequential([ tf.keras.layers.LSTM(64, input_shape=(inp_history_size, 4)), tf.keras.layers.BatchNormalization(), tf.keras.layers.Dropout(0.1), tf.keras.layers.Dense(32, activation='relu'), tf.keras.layers.BatchNormalization(), tf.keras.layers.Dropout(0.1), tf.keras.layers.Dense(32, activation='relu'), tf.keras.layers.Dense(1) ]) model.compile(optimizer='adam', loss='mse', metrics=['mae']) # model training for 50 epochs lr_reduction = tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=3, min_lr=0.000001) history = model.fit(X_train, y_train, epochs=50, verbose=2, validation_split=0.15, callbacks=[lr_reduction]) # model evaluation test_loss, test_mae = model.evaluate(X_test, y_test) print(f"test_loss={test_loss:.3f}") print(f"test_mae={test_mae:.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()Regresyon modelimizin yürütüldüğü varsayılırsa, ortaya çıkan tahmin edilen fiyat şu sınıfa dönüştürülmelidir: fiyat düşer, fiyat değişmez, fiyat yükselir. Bu, oylama sınıflandırıcısını düzenlemek için gereklidir.
İkinci model ise sınıflandırma modelidir.
EURUSD D1 üzerinde 2010'dan 2022'nin sonuna kadar eğitilmiştir. Eğitim, 63 Kapanış fiyatı serisi kullanılarak gerçekleştirilmiştir. Çıktıda üç sınıftan biri tanımlanmalıdır: fiyat düşecek, fiyat 10 puan içerisinde kalacak veya fiyat yükselecek. İkinci sınıf nedeniyle modeli 2010'dan bu yana verileri kullanarak eğitmek zorunda kaldık - öncesinde, 2009'da piyasalar 4 basamaklı hassasiyetten 5 basamaklı hassasiyete geçti. Böylece, bir eski puan on yeni puan haline geldi.
Önceki modelde olduğu gibi, fiyat normalleştirilmiştir. Normalleştirme de aynıdır: serideki ortalama fiyattan sapmayı serideki standart sapmaya böleriz. Bu modelin fikri "Keras'ta MLP ile finansal zaman serisi tahmini" (Rusça) makalesinde açıklanmıştır. Bu model de sadece sunum amaçlı tasarlanmıştır.
# Copyright 2023, MetaQuotes Ltd. # https://www.mql5.com # # Classification model # 0,0,1 - predict price down # 0,1,0 - predict price same # 1,0,0 - predict price up # from datetime import datetime 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 tqdm import tqdm from keras.models import Sequential from keras.layers import Dense, Activation,Dropout, BatchNormalization, LeakyReLU from keras.optimizers import SGD from keras import regularizers from sys import argv # initialize MetaTrader 5 client terminal if not mt5.initialize(): print("initialize() failed, error code =",mt5.last_error()) quit() # we will save the generated onnx-file near the our script 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) # input parameters inp_model_name = "model.eurusd.D1.63.onnx" inp_history_size = 63 inp_start_date = datetime(2010, 1, 1, 0) inp_end_date = datetime(2023, 1, 1, 0) # get data from the client terminal eurusd_rates = mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_D1, inp_start_date, inp_end_date) df = pd.DataFrame(eurusd_rates) # # collect dataset subroutine # def collect_dataset(df: pd.DataFrame, history_size: int): """ Collect dataset for the following regression problem: - input: history_size consecutive H1 bars; - output: close price for the next bar. :param df: H1 bars for a range of dates :param history_size: how many bars should be considered for making a prediction :return: features and labels """ n = len(df) xs = [] ys = [] for i in tqdm(range(n - history_size)): w = df.iloc[i: i + history_size + 1] x = w[['close']].iloc[:-1].values delta = x[-1] - w.iloc[-1]['close'] if np.abs(delta)<=0.0001: y = 0, 1, 0 else: if delta<0: y = 1, 0, 0 else: y = 0, 0, 1 xs.append(x) ys.append(y) X = np.array(xs) Y = np.array(ys) return X, Y ### # get prices X, Y = collect_dataset(df, history_size=inp_history_size) # normalize prices m = X.mean(axis=1, keepdims=True) s = X.std(axis=1, keepdims=True) X_norm = (X - m) / s # split data to train and test sets X_train, X_test, Y_train, Y_test = train_test_split(X_norm, Y, test_size=0.1, random_state=0) # define model model = Sequential() model.add(Dense(64, input_dim=inp_history_size, activity_regularizer=regularizers.l2(0.01))) model.add(BatchNormalization()) model.add(LeakyReLU()) model.add(Dropout(0.3)) model.add(Dense(16, activity_regularizer=regularizers.l2(0.01))) model.add(BatchNormalization()) model.add(LeakyReLU()) model.add(Dense(3)) model.add(Activation('softmax')) opt = SGD(learning_rate=0.01, momentum=0.9) model.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy']) # model training for 300 epochs lr_reduction = tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.9, patience=5, min_lr=0.00001) history = model.fit(X_train, Y_train, epochs=300, validation_data=(X_test, Y_test), shuffle = True, batch_size=128, verbose=2, callbacks=[lr_reduction]) # model evaluation test_loss, test_accuracy = model.evaluate(X_test, Y_test) print(f"test_loss={test_loss:.3f}") print(f"test_accuracy={test_accuracy:.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()Modeller 2022 sonuna kadar verilerle eğitilmiş, böylece strateji sınayıcıda çalışmalarını göstermek için süre bırakılmıştır.
MQL5 Uzman Danışmanında bir ONNX modelleri topluluğu
Aşağıda, model topluluklarının olanaklarını göstermek için basit bir Uzman Danışman bulunmaktadır. MQL5'te ONNX modellerini kullanmanın ana ilkeleri bir önceki makalenin ikinci bölümünde açıklanmıştı.
İleri bildiriler ve tanımlar
#include <Trade\Trade.mqh> input double InpLots = 1.0; // Lots amount to open position #resource "Python/model.eurusd.D1.10.onnx" as uchar ExtModel1[] #resource "Python/model.eurusd.D1.63.onnx" as uchar ExtModel2[] #define SAMPLE_SIZE1 10 #define SAMPLE_SIZE2 63 long ExtHandle1=INVALID_HANDLE; long ExtHandle2=INVALID_HANDLE; int ExtPredictedClass1=-1; int ExtPredictedClass2=-1; int ExtPredictedClass=-1; datetime ExtNextBar=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_D1) { Print("model must work with EURUSD,D1"); return(INIT_FAILED); } //--- create first model from static buffer ExtHandle1=OnnxCreateFromBuffer(ExtModel1,ONNX_DEFAULT); if(ExtHandle1==INVALID_HANDLE) { Print("First model 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 (OHLC) const long input_shape1[] = {1,SAMPLE_SIZE1,4}; if(!OnnxSetInputShape(ExtHandle1,0,input_shape1)) { Print("First model 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_shape1[] = {1,1}; if(!OnnxSetOutputShape(ExtHandle1,0,output_shape1)) { Print("First model OnnxSetOutputShape error ",GetLastError()); return(INIT_FAILED); } //--- create second model from static buffer ExtHandle2=OnnxCreateFromBuffer(ExtModel2,ONNX_DEFAULT); if(ExtHandle2==INVALID_HANDLE) { Print("Second model 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 const long input_shape2[] = {1,SAMPLE_SIZE2}; if(!OnnxSetInputShape(ExtHandle2,0,input_shape2)) { Print("Second model 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 classes (up, same or down) const long output_shape2[] = {1,3}; if(!OnnxSetOutputShape(ExtHandle2,0,output_shape2)) { Print("Second model OnnxSetOutputShape error ",GetLastError()); return(INIT_FAILED); } //--- ok return(INIT_SUCCEEDED); }
Sadece EURUSD, D1 ile çalıştıracağız. Bunun nedeni, modeller günlük fiyatlar kullanılarak eğitilirken bizim mevcut sembol zaman diliminin verilerini kullanmamızdır.
Modeller Uzman Danışmana kaynak olarak dahil edilmiştir.
Girdi ve çıktı veri şekillerini açıkça tanımlamak önemlidir.
OnTick fonksiyonu
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- check new bar if(TimeCurrent()<ExtNextBar) return; //--- set next bar time ExtNextBar=TimeCurrent(); ExtNextBar-=ExtNextBar%PeriodSeconds(); ExtNextBar+=PeriodSeconds(); //--- predict price movement Predict(); //--- check trading according to prediction if(ExtPredictedClass>=0) if(PositionSelect(_Symbol)) CheckForClose(); else CheckForOpen(); }
Tüm ticaret işlemleri yalnızca günün başında gerçekleştirilir.
Tahmin fonksiyonu
//+------------------------------------------------------------------+ //| Voting classification | //+------------------------------------------------------------------+ void Predict(void) { //--- evaluate first model ExtPredictedClass1=PredictPrice(ExtHandle1,SAMPLE_SIZE1); //--- evaluate second model ExtPredictedClass2=PredictPriceMovement(ExtHandle2,SAMPLE_SIZE2); //--- vote if(ExtPredictedClass1==ExtPredictedClass2) ExtPredictedClass=ExtPredictedClass1; else ExtPredictedClass=-1; }
Her iki model de aynı sınıfı aldığında bir sınıf seçilmiş olarak kabul edilir. Bu bir oy çokluğudur. Ve toplulukta sadece iki model olduğu için, oy çokluğu "oy birliği" anlamına gelir.
Önceki 10 OHLC fiyatından gün Kapanış fiyatı tahmini
//+------------------------------------------------------------------+ //| Predict next price (first model) | //+------------------------------------------------------------------+ int PredictPrice(const long handle,const int sample_size) { static matrixf input_data(sample_size,4); // matrix for prepared input data static vectorf output_data(1); // vector to get result static matrix mm(sample_size,4); // matrix of horizontal vectors Mean static matrix ms(sample_size,4); // matrix of horizontal vectors Std static matrix x_norm(sample_size,4); // matrix for prices normalize //--- prepare input data matrix rates; //--- request last bars if(!rates.CopyRates(_Symbol,_Period,COPY_RATES_OHLC,1,sample_size)) return(-1); //--- 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<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(handle,ONNX_NO_CONVERSION,input_data,output_data)) return(-1); //--- denormalize the price from the output value double predicted=output_data[0]*s[3]+m[3]; //--- classify predicted price movement int predicted_class=-1; double delta=rates[3][sample_size-1]-predicted; if(fabs(delta)<=0.0001) predicted_class=PRICE_SAME; else { if(delta<0) predicted_class=PRICE_UP; else predicted_class=PRICE_DOWN; } return(predicted_class); }
Girdi verileri, modeli eğitirken uygulanan kurallara uygun olarak hazırlanmalıdır. Model yürütüldükten sonra, elde edilen değer tekrar fiyata dönüştürülür. Sınıf, serideki son Kapanış fiyatı ile ortaya çıkan fiyat arasındaki farka göre hesaplanır.
Fiyat hareketi tahmini, 63 günlük Kapanış fiyatından oluşan bir seriye dayanmaktadır:
//+------------------------------------------------------------------+ //| Predict price movement (second model) | //+------------------------------------------------------------------+ int PredictPriceMovement(const long handle,const int sample_size) { static vectorf input_data(sample_size); // vector for prepared input data static vectorf output_data(3); // vector to get result //--- request last bars if(!input_data.CopyRates(_Symbol,_Period,COPY_RATES_CLOSE,1,sample_size)) return(-1); //--- 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(handle,ONNX_NO_CONVERSION,input_data,output_data)) return(-1); //--- evaluate prediction return(int(output_data.ArgMax())); }
Fiyatlar, ilk modelde olduğu gibi aynı kurallar kullanılarak normalleştirilir. Ancak bu sefer kod daha kompakttır çünkü girdi bir matris değil bir vektördür. Sınıf, üç olasılığın maksimum değerine göre seçilir.
Ticaret stratejisi basittir. Ticaret işlemleri her günün başında gerçekleştirilir. Eğer tahmin "fiyat yükselecek" ise satın alırız; "fiyat düşecek" ise satarı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)) ExtTrade.PositionOpen(_Symbol,signal,InpLots, SymbolInfoDouble(_Symbol,signal==ORDER_TYPE_SELL ? SYMBOL_BID:SYMBOL_ASK), 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(); } }
Modelimizi 2023 yılı başına kadar olan verilerle eğittik. Öyleyse, test aralığını yılın başından itibaren ayarlayalım.
İşte yıl başından bu yana olan verilere dayanan test sonucu.
Her bir model için test sonuçlarını bilmek ilginç olacaktır.
Bunu yapmak için Uzman Danışman kaynak kodunu aşağıdaki gibi değiştirelim:
enum EnModels { USE_FIRST_MODEL, // Use first model only USE_SECOND_MODEL, // Use second model only USE_BOTH_MODELS // Use both models }; input EnModels InpModels = USE_BOTH_MODELS; // Models using input double InpLots = 1.0; // Lots amount to open position ... //+------------------------------------------------------------------+ //| Voting classification | //+------------------------------------------------------------------+ void Predict(void) { //--- evaluate first model if(InpModels==USE_BOTH_MODELS || InpModels==USE_FIRST_MODEL) ExtPredictedClass1=PredictPrice(ExtHandle1,SAMPLE_SIZE1); //--- evaluate second model if(InpModels==USE_BOTH_MODELS || InpModels==USE_SECOND_MODEL) ExtPredictedClass2=PredictPriceMovement(ExtHandle2,SAMPLE_SIZE2); //--- check predictions switch(InpModels) { case USE_FIRST_MODEL : ExtPredictedClass=ExtPredictedClass1; break; case USE_SECOND_MODEL : ExtPredictedClass=ExtPredictedClass2; break; case USE_BOTH_MODELS : if(ExtPredictedClass1==ExtPredictedClass2) ExtPredictedClass=ExtPredictedClass1; else ExtPredictedClass=-1; } }
"Yalnızca ilk modeli kullan" parametresini etkinleştirelim:
İlk model test sonuçları
Şimdi ikinci modeli test edelim. İşte ikinci model test sonuçları.
İkinci modelin ilkinden çok daha güçlü olduğu ortaya çıktı. Sonuçlar, zayıf modellerin bir araya getirilmesi gerektiği teorisini doğrulamaktadır. Ancak, bu makale topluluk oluşturma teorisi ile değil, pratik uygulama ile ilgiliydi.
Ö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ç
İki ONNX modelinden oluşan bir topluluğun çok basit ama açıklayıcı bir örneğini sunduk. Aynı anda kullanılan model sayısı sınırlıdır ve 256 modeli geçemez. Bununla birlikte, ikiden fazla modelin kullanılması bile Uzman Danışman programlamasına farklı bir yaklaşım gerektirecektir, yani nesne yönelimli programlama gerektirecektir.
Ama bu başka bir makalenin konusu.
MetaQuotes Ltd tarafından Rusçadan çevrilmiştir.
Orijinal makale: https://www.mql5.com/ru/articles/12433
- Ü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