English Русский 中文 Español Deutsch Português
preview
MQL5とPythonで自己最適化エキスパートアドバイザーを構築する(第3回):Boom 1000アルゴリズムの解読

MQL5とPythonで自己最適化エキスパートアドバイザーを構築する(第3回):Boom 1000アルゴリズムの解読

MetaTrader 5 | 12 11月 2024, 11:09
353 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana


Derivの合成市場をすべて個別に分析します。まずは最も有名な合成市場であるBoom 1000から始めます。Boom 1000は、その不安定で予測不可能な動きで有名です。この市場は、ゆっくりとした短い同サイズの弱気ローソク足と、その後に突然、超高層ビルサイズの強気ローソク足が続くという特徴があります。強気ローソク足は、ローソク足に関連付けられたティックが通常クライアント端末に送信されないため、緩和するのが特に難しく、すべてのストップロスが毎回確実にスリッページで突破されます。

そのため、成功しているトレーダーのほとんどは、Boom 1000を取引する際に買いの機会のみを狙うという緩やかな戦略を立てています。Boom 1000はM1時間枠で20分間下落する可能性があり、その動き全体を1本のローソク足で再現する可能性があることを思い出してください。したがって、その圧倒的な強気の性質を考えると、成功しているトレーダーは、売りセットアップよりもBoom 1000の買いセットアップに重点を置くことで、これを有利に利用しようとします。

一方、Derivの合成商品の価格レベルに応じて値が決まる新しい従属変数を単純に作成できれば、Boom 1000を予測する際に捉えられるよりも正確にモデル化できる新しい関係を作成できる可能性があります。言い換えれば、指標を市場に適用し、指標と市場の関係をモデル化すれば、より高い精度レベルを実現できる可能性があります。うまくいけば、新しいターゲットによって精度レベルが向上するだけでなく、実際の価格変動を忠実に反映することになります。つまり、指標の読み取り値が下がると予想される場合、価格レベルも下がると予想されます。機械学習は、関数の入力があることを前提として関数を近似することに重点を置いていることを思い出してください。Derivが乱数生成アルゴリズムで使用している入力はどれも持っていませんが、指標を市場に適用することで、指標が依存するすべての入力にアクセスできます。


方法論の概要

提案された戦略の実現可能性を評価するために、本日私が作成したカスタマイズされたスクリプトを使用して、MetaTrader 5端末から、各インスタンスのM1データ100,000行とRSI指標読み取り値を時間的に取得しました。スクリプトを読み込んだ後、探索的データ分析を実行しました。RSI読み取り値が下がると、Boom 1000の価格レベルも83%下がることがわかりました。これは、価格レベルがどこになるかを知ることができるため、RSI値を予測できることに利点があることを示しています。ただし、これはまた、約17%の確率でRSIが私たちを誤った方向に導くことも意味します。

Boom 1000のRSIと価格レベルの間には、読み取り値が0.016と、相関レベルが弱いことが観察されました。作成した散布図のいずれも、データに識別可能な関係性を明らかにせず、より高次元でのプロットも試みましたが、これも無駄に終わりました。データを効果的に分離するのはかなり難しいようです。

私たちの努力はそこで終わらず、その後、データセットを2つに分割しました。1つは訓練と最適化用、もう1つは検証と過剰適合のテスト用です。また、2つのターゲットも作成しました。1つのターゲットは価格レベルの変化を捉え、もう1つはRSI読み取り値の変化を捉えました。 

価格レベルとRSIレベルの変化をそれぞれ予測する2つの同一のディープニューラルネットワーク分類器の訓練を進めたところ、最初のモデルは約53%の精度レベルを達成し、後者は約63%の精度レベルを達成しました。さらに、RSIの変化を予測する際の誤差レベルの分散は低く、後者のモデルの方が効果的に学習した可能性があることを示唆しています。残念ながら、訓練セットに過剰適合することなくディープニューラルネットワークを調整することはできませんでした。これは、未知の検証データでデフォルトのニューラルネットワークを上回ることができなかったという事実から推測できます。訓練と検証の両方で精度レベルを測定するために、ランダムシャッフルなしで5倍の時系列クロス検証を実行しました。

デフォルトのRSIモデルを最高のパフォーマンスモデルとして選択し、ONNX形式にエクスポートして、最終的にMQL5でカスタマイズされたBoom 1000 AI搭載EAを構築しました。


データの取得

まず、MetaTrader 5端末から必要なデータを取得する必要があります。このタスクは、便利なMQL5スクリプトによって自動的に処理されます。私が作成したスクリプトは、Boom 1000に関連付けられた市場相場、各ローソク足のタイムスタンプ、および関連するRSI指標読み取り値を取得してから、それらをCSV形式で書き出します。データを書き出す前にRSIバッファをシリーズとして設定していることに注意してください。この手順は重要です。そうしないと、RSIデータが逆時系列になり、望ましくない結果になります。

//+------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2020, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Zororo Ndawana"
#property link      "https://www.mql5.com/ja/users/gamuchiraindawa"
#property version   "1.00"
#property script_show_inputs

//+------------------------------------------------------------------+
//| Script Inputs                                                    |
//+------------------------------------------------------------------+
input int size = 100000; //How much data should we fetch?

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int rsi_handler;
double rsi_buffer[];


//+------------------------------------------------------------------+
//| On start function                                                |
//+------------------------------------------------------------------+
void OnStart()
  {

//--- Load indicator
   rsi_handler = iRSI(_Symbol,PERIOD_CURRENT,20,PRICE_CLOSE);
   CopyBuffer(rsi_handler,0,0,size,rsi_buffer);
   ArraySetAsSeries(rsi_buffer,true);

//--- File name
   string file_name = "Market Data " + Symbol() + ".csv";

//--- Write to file
   int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");

   for(int i= size;i>=0;i--)
     {
      if(i == size)
        {
         FileWrite(file_handle,"Time","Open","High","Low","Close","RSI");
        }

      else
        {
         FileWrite(file_handle,iTime(Symbol(),PERIOD_CURRENT,i),
                   iOpen(Symbol(),PERIOD_CURRENT,i),
                   iHigh(Symbol(),PERIOD_CURRENT,i),
                   iLow(Symbol(),PERIOD_CURRENT,i),
                   iClose(Symbol(),PERIOD_CURRENT,i),
                   rsi_buffer[i]
                  );

         Print("Time: ",iTime(Symbol(),PERIOD_CURRENT,i),"Close: ",iClose(Symbol(),PERIOD_CURRENT,i),"RSI",rsi_buffer[i]);
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+


データクリーニング

これで、可視化のためにデータを準備する準備が整いました。まず必要なライブラリをインポートしましょう。
#Import the libraries we need
import pandas as pd
import numpy as np

ライブラリのバージョンを表示します。

#Display library versions
print(f"Pandas version {pd.__version__}")
print(f"Numpy version {np.__version__}")
Pandas version 2.2.1
Numpy version 1.26.4

次にCSVファイルを読み込みます。

#Read in the data we need
boom_1000 = pd.read_csv("Market Data Boom 1000 Index.csv")

データを見てみましょう。

#Let's see the data
boom_1000

図1:Boom 1000市場データ

ここで、予測期間を定義してみましょう。

#Define how far into the future we should forecast
look_ahead = 20

ここで、予測期間を定義し、可視化とプロットの目的でデータに追加のラベルを追加する必要があります。

#Let's add targets and labels for plotting
boom_1000["Price Target"] = boom_1000["Close"].shift(-look_ahead)
boom_1000["RSI Target"] = boom_1000["RSI"].shift(-look_ahead)

#Let's also add binary targets for plotting purposes
boom_1000["Price Binary Target"] = np.nan
boom_1000["RSI Binary Target"] = np.nan

#Label the binary targets
boom_1000.loc[boom_1000["Price Target"] < boom_1000["Close"],"Price Binary Target"] = 0
boom_1000.loc[boom_1000["Price Target"] > boom_1000["Close"],"Price Binary Target"] = 1

boom_1000.loc[boom_1000["RSI Target"] < boom_1000["RSI"],"RSI Binary Target"] = 0
boom_1000.loc[boom_1000["RSI Target"] > boom_1000["RSI"],"RSI Binary Target"] = 1

#Drop na values
boom_1000.dropna(inplace=True)

ここで、モデル入力と比較する2つのターゲットを定義しましょう。

#Define the predictors and targets
predictors = ["Open","High","Low","Close","RSI"]
old_target = "Price Binary Target"
new_target = "RSI Binary Target"


探索的データ分析

必要なライブラリをインポートしましょう。

#Exploratory data analysis
import seaborn as sns

使用されているライブラリのバージョンを表示します。

print(f"Seaborn version {sns.__version__}")
Seaborn version 0.13.2
RSIによって生成される信号の純度を評価してみましょう。ここで言う純度とは、「RSIレベルが下がれば、価格レベルも下がるのか」という疑問に対する答えです。この量を計算するには、まずRSIとPrice Binary Targetが一致しなかったインスタンスの数を数え、次にこの数をデータセット全体の行数で割ります。この量を1から引くと、RSIとPrice Binary Targetが一致したインスタンスの合計割合が算出されます。計算によると、83%の確率でRSIとPriceは同じ方向に変化しているようです。
#Let's assess the purity of the signals generated
rsi_purity = 1 - boom_1000.loc[boom_1000["RSI Binary Target"] != boom_1000["Price Binary Target"]].shape[0] / boom_1000.shape[0]
print(f"Price and the RSI tend to move together {rsi_purity * 100}% of the time")
Price and the RSI tend to move together 83.69041189061593% of the time

この量はかなり高いため、データを可視化する際に良好なレベルの分離が得られるというある程度の信頼度が得られます。まず、X軸でRSIレベルが下がった(列0)または上がった(列1)すべてのインスタンスをまとめたカテゴリプロットを作成し、終値をY軸にとります。その後、各ポイントを青またはオレンジに色付けして、Boom 1000の価格レベルがそれぞれ上昇または下落したインスタンスを示します。下のプロットからわかるように、列0は主に青で、オレンジ色の点がいくつか散在しています。列1ではその逆になっています。これは、RSIがここでは価格変動の適切な区切りであることを示しています。ただし、完璧ではないため、将来的には追加の指標が必要になる可能性があります。

#Let's see this purity level we just calculated
sns.catplot(data=boom_1000,x="RSI Binary Target",y="Close",hue="Price Binary Target")


図2:RSIがデータをどれだけうまく分割しているかを示すカテゴリプロット

また、RSI とBoom 1000の終値の関係を視覚化するために散布図も作成しました。残念ながら、ご覧のとおり、この2つの間には関係がないようです。青とオレンジの点がランダムに交互に並ぶ長いスパゲッティのような跡が見られますが、これはターゲットに影響を与える他の変数があることを示している可能性があります。

#Let us also observe a scatter plot of the two
sns.scatterplot(data=boom_1000,x="RSI",y="Close",hue="Price Binary Target")

図3:終値とRSI値の散布図

おそらく、可視化しようとしている関係は 2 次元では確認できないので、データを 3 次元で可視化してみましょう。そうすれば、見逃していた隠れた相互作用効果を観察できるかもしれません。

必要なライブラリをインポートします。

#Let's create 3D scatter plots
import matplotlib.pyplot as plt

プロットするデータの量を定義します。

#Define the plot end
end = 10000

次に、3D散布図を作成します。残念ながら、データはランダムに分布しているように見え、有利に活用できる目立ったパターンは見当たりません。

#Visualizing our data in 3D
fig = plt.figure(figsize=(7,7))
ax = fig.add_subplot(111,projection='3d')
colors = ['blue' if movement == 0 else 'red' for movement in boom_1000.loc[0:end,"Price Binary Target"]]
ax.scatter(boom_1000.loc[0:end,"RSI"],boom_1000.loc[0:end,"High"],boom_1000.loc[0:end,"Close"],c=colors)

ax.set_xlabel('Boom 1000 RSI')
ax.set_ylabel('Boom 1000 High')
ax.set_zlabel('Boom 1000 Close')

図4:Boom 1000市場の3D散布図とRSIとの関係

ここで、RSIと価格データ間の相関レベルを分析してみましょう。相関レベルはかなり低く、正確にはほぼ0です。

#Let's analyze the correlation levels
sns.heatmap(boom_1000.loc[:,predictors].corr(),annot=True)

図5:相関行列のヒートマップ



データのモデル化の準備

データのモデリングを始める前に、まずデータをスケーリングして標準化する必要があります。まず、必要なライブラリをインポートしましょう。

#Preparing to model the data
import sklearn
from sklearn.preprocessing import RobustScaler
from sklearn.neural_network import MLPClassifier,MLPRegressor
from sklearn.model_selection import TimeSeriesSplit, train_test_split
from sklearn.metrics import accuracy_score

ライブラリのバージョンを表示します。

#Display library version
print(f"Sklearn version {sklearn.__version__}")
Sklearn version 1.4.1.post1

データをスケーリングします。

#Scale our data
X = pd.DataFrame(RobustScaler().fit_transform(boom_1000.loc[:,predictors]),columns=predictors)

古い目標と新しい目標を定義します。

#Our old and new target
old_y = boom_1000.loc[:,"Price Binary Target"]
new_y = boom_1000.loc[:,"RSI Binary Target"]

訓練とテスト用に分割します。

#Perform train test splits
train_X,test_X,ohlc_train_y,ohlc_test_y = train_test_split(X,old_y,shuffle=False,test_size=0.5)
_,_,rsi_train_y,rsi_test_y = train_test_split(X,new_y,shuffle=False,test_size=0.5)

検証における精度レベルを保存するためのデータフレームを準備します。

#Prepare data frames to store our accuracy levels
validation_accuracy = pd.DataFrame(index=np.arange(0,5),columns=["Close Accuracy","RSI Accuracy"])

次に、時系列スピットオブジェクトを作成します。

#Let's create the time series split object
tscv =  TimeSeriesSplit(gap=look_ahead,n_splits=5)


データのモデリング

これで、交差検証を実行して、2つのターゲット間の精度レベルの変化を観察する準備が整いました。

#Instatiate the model
model = MLPClassifier(hidden_layer_sizes=(30,10),max_iter=200)
#Cross validate the model
for i,(train,test) in enumerate(tscv.split(train_X)):
        model.fit(train_X.loc[train[0]:train[-1],:],ohlc_train_y.loc[train[0]:train[-1]])
        validation_accuracy.iloc[i,0] = accuracy_score(ohlc_train_y.loc[test[0]:test[-1]],model.predict(train_X.loc[test[0]:test[-1],:]))

以下が精度レベルです。

validation_accuracy
近接精度     
 RSIの精度
0.53703
0.663186
0.544592
0.623575
0.479534
0.597647
0.57064
0.651422
 0.545913  0.616373

平均精度レベルは、RSI値の変化を予測する方がよい可能性があることを即座に示しています。

validation_accuracy.mean()
Close Accuracy    0.535542
RSI Accuracy      0.630441
dtype: object

標準偏差が低いほど、モデルの予測の確実性が高まります。モデルは、価格の変化よりも確実にRSIを予測することを学習したようです。

validation_accuracy.std()

Close Accuracy    0.033765
RSI Accuracy      0.026613
dtype: object

それぞれのモデルのパフォーマンスをプロットしてみましょう。

validation_accuracy.plot()

図6検証精度の可視化

最後に、ボックスプロットは2つのモデル間のパフォーマンスの違いを観察するのに役立ちます。ご覧のとおり、RSIモデルはPriceモデルをはるかに上回るパフォーマンスを発揮しています。

#Our RSI validation accuracy is better
sns.boxplot(validation_accuracy)

図7:検証の精度を示すボックスプロット


特徴量の重要性

RSI値を予測するためにどの特徴量が重要であるかを分析しましょう。まず、ニューラルネットワークで前進選択を実行します。前進選択はヌルモデルから開始し、モデルのパフォーマンスをこれ以上強化できなくなるまで、一度に1つの特徴量を順番に追加します。

まず、必要なライブラリをインポートします。
#Feature importance
import mlxtend
from mlxtend.feature_selection import SequentialFeatureSelector as SFS

ライブラリのバージョンを表示します。

print(f"Mlxtend version: {mlxtend.__version__}")
Mlxtend version:0.23.1

モデルを再初期化します。

#Reinitialize the model
model = MLPClassifier(hidden_layer_sizes=(30,10),max_iter=200)

特徴量選択器を設定します。

#Define the forward feature selector
sfs1 = SFS(
        model,
        k_features=(1,X.shape[1]),
        n_jobs=-1,
        forward=True,
        cv=5,
        scoring="accuracy"
)

特徴量選択器を適合させます。

#Fit the feature selector
sfs = sfs1.fit(train_X,rsi_train_y)

特定された最も重要な特徴量を見てみましょう。利用可能な特徴量がすべて選択されました。

sfs.k_feature_names_
('Open', 'High', 'Low', 'Close', 'RSI')

特徴量選択プロセスを可視化してみましょう。まず、必要なライブラリをインポートします。

#Importing the libraries we need
from mlxtend.plotting import plot_sequential_feature_selection as plot_sfs
import matplotlib.pyplot as plt

次に結果をプロットします。

fig1 = plot_sfs(sfs.get_metric_dict(), kind='std_err')

plt.title('Sequential Forward Selection')
plt.grid()
plt.show()

図8:特徴量選択プロセスの可視化

相互情報量(MI)により、各予測子の潜在能力を理解することができます。MIスコアが高いほど、一般的に予測子の有用性が高まります。MIは、データ内の非線形依存関係を捉えることができます。最後に、MIは対数スケールであるため、実際には3を超えるMIスコアはめったに見られません。

必要なライブラリをインポートします。

#Let's analyze our MI scores
from sklearn.feature_selection import mutual_info_classif

MIスコアを計算します。

mi_scores = pd.DataFrame(mutual_info_classif(train_X,rsi_train_y).reshape(1,5),columns=predictors)

結果をプロットすると、MIによるとRSI列が最も重要な列であることがわかります。

#Let's visualize the results
mi_scores.plot.bar()



図9:MIスコアの可視化


パラメータ調整

ここで、モデルを調整して、さらにパフォーマンスを向上させてみます。sklearnライブラリのRandomizedSearchCVモジュールを使用すると、機械学習モデルを簡単に調整できます。機械学習モデルを調整する場合、精度と計算時間の間でトレードオフが発生します。この2つを決定するために、反復の合計回数を調整します。必要なライブラリをインポートしましょう。

#Parameter tuning
from sklearn.model_selection import RandomizedSearchCV

モデルを初期化します。

#Reinitialize the model
model = MLPClassifier(hidden_layer_sizes=(30,10),max_iter=200)

チューナーオブジェクトを定義します。

#Define the tuner
tuner = RandomizedSearchCV(
        model,
        {
                "activation":["relu","tanh","logistic","identity"],
                "solver":["adam","sgd","lbfgs"],
                "alpha":[0.1,0.01,0.001,0.00001,0.000001],
                "learning_rate": ["constant","invscaling","adaptive"],
                "learning_rate_init":[0.1,0.01,0.001,0.0001,0.000001,0.0000001],
                "power_t":[0.1,0.5,0.9,0.01,0.001,0.0001],
                "shuffle":[True,False],
                "tol":[0.1,0.01,0.001,0.0001,0.00001],
        },
        n_iter=300,
        cv=5,
        n_jobs=-1,
        scoring="accuracy"
)

チューナーオブジェクトを適合させます。

#Fit the tuner
tuner_results =tuner.fit(train_X,rsi_train_y)

以下が、見つかった最良のパラメータです。

#Best parameters
tuner_results.best_params_
{'tol':1e-05,
 'solver': 'lbfgs',
 'shuffle':True,
 'power_t':0.0001,
 'learning_rate_init':0.01,
 'learning_rate': 'adaptive',
 'alpha':1e-06,
 'activation': 'logistic'}


過剰適合のテスト

過剰適合をテストするには、検証データでデフォルトモデルとカスタマイズしたモデルを交差検証します。デフォルトモデルのパフォーマンスが優れている場合は、訓練セットが過剰適合されたことがわかります。それ以外の場合は、ハイパーパラメータの調整が正常に実行されたことを意味します。

2つのモデルを初期化します。

#Testing for overfitting
default_model = MLPClassifier(hidden_layer_sizes=(30,10),max_iter=200)
customized_model = MLPClassifier(
                                hidden_layer_sizes=(30,10),
                                max_iter=200,
                                tol=0.00001,
                                solver="lbfgs",
                                shuffle=True,
                                power_t=0.0001,
                                learning_rate_init=0.01,
                                learning_rate="adaptive",
                                alpha=0.000001,
                                activation="logistic"
                                )

両方のモデルを訓練データに適合させます。

#First we will train both models on the training set
default_model.fit(train_X,rsi_train_y)
customized_model.fit(train_X,rsi_train_y)

両方のデータセットのインデックスをリセットします。

#Now we will reset our indexes
rsi_test_y = rsi_test_y.reset_index()
test_X = test_X.reset_index()

データを書式設定します。

#Format the data
rsi_test_y = rsi_test_y.loc[:,"RSI Binary Target"]
test_X = test_X.loc[:,predictors]

精度レベルを保存するためのデータフレームを準備します。

#Prepare a data frame to store our accuracy levels
validation_error = pd.DataFrame(index=np.arange(0,5),columns=["Default Neural Network","Customized Neural Network"])

各モデルを交差検証して過剰適合をテストします。

#Perform cross validation
for i,(train,test) in enumerate(tscv.split(test_X)):
        customized_model.fit(test_X.loc[train[0]:train[-1],predictors],rsi_test_y.loc[train[0]:train[-1]])
        validation_error.iloc[i,1] = accuracy_score(rsi_test_y.loc[test[0]:test[-1]],customized_model.predict(test_X.loc[test[0]:test[-1]]))

以下は、検証におけるパフォーマンスレベルです。

validation_error
デフォルトのニューラルネットワーク
カスタマイズされたニューラルネットワーク
0.627656
0.597767
0.637258
0.635938
0.621414
0.631977
0.6429
0.6411
0.664866
0.652503

平均パフォーマンス レベルを分析すると、デフォルトのモデルがカスタマイズされたモデルよりもわずかに優れていることが明確にわかります。

validation_error.mean()
Default Neural Network       0.638819
Customized Neural Network    0.631857
dtype: object

さらに、カスタマイズされたモデルは、精度スコアのばらつきが少ないため、より高いスキルを発揮しました。

validation_error.std()
Default Neural Network       0.016771
Customized Neural Network    0.020557
dtype: object

結果をグラフにしてみましょう。

validation_error.plot()

図10:過剰適合のテストを可視化する

ボックスプロットを見ると、カスタマイズされたモデルは安定性が低く、デフォルトモデルでは確認できない外れ値があることがわかります。さらに、デフォルトモデルの方が平均パフォーマンスがわずかに優れています。したがって、カスタマイズされたモデルよりもデフォルトモデルを選択します。

sns.boxplot(validation_error)

図11:過剰適合のテストを可視化する II


ONNXへのエクスポートの準備

モデルをONNX形式にエクスポートする前に、まずMetaTrader 5端末で再現できるようにデータをスケーリングする必要があります。各列から列の平均を減算し、列の標準偏差で割ります。これにより、データが異なるスケールにあるため、モデルが効果的に学習することが保証されます。また、平均値と標準偏差値をCSV形式でエクスポートして、後で取得できるようにします。
#Preparing to export to ONNX
#Let's scale our data
scaling_factors = pd.DataFrame(columns=predictors,index=['mean','standard deviation'])
X = boom_1000.loc[:,predictors]
y = boom_1000.loc[:,"RSI Target"]

各列をスケーリングします。

#Let's fill each column
for i in np.arange(0,len(predictors)):
        scaling_factors.iloc[0,i] = X.iloc[:,i].mean()
        scaling_factors.iloc[1,i] = X.iloc[:,i].std()
        X.iloc[:,i] = ( ( X.iloc[:,i] - scaling_factors.iloc[0,i] ) / scaling_factors.iloc[1,i])

スケーリン係数をCSV形式で保存します。

#Save the scaling factors as a CSV
scaling_factors.to_csv("/home/volatily/.wine/drive_c/Program Files/MetaTrader 5/MQL5/Files/boom_1000_scaling_factors.csv")


ONNXへのエクスポート

Open Neural Network Exchange (ONNX)は、オープンソースの相互運用可能な機械学習フレームワークであり、開発者は、ONNX APIをサポートする任意のプログラミング言語で機械学習モデルを構築、共有、使用できます。これにより、Pythonで機械学習モデルを構築し、本番環境でMQL5に展開できます。

まず、必要なライブラリをインポートします。
#Exporting to ONNX
import onnx
import netron
import skl2onnx
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType

ライブラリのバージョンを表示します。

#Display the library versions
print(f"Onnx version {onnx.__version__}")
print(f"Netron version {netron.__version__}")
print(f"Skl2onnx version {skl2onnx.__version__}")
Onnx version 1.15.0
Netron version 7.8.0
Skl2onnxバージョン1.16.0

モデルの入力タイプを定義します。

#Define the model input types
initial_types = [("float_input",FloatTensorType([1,5]))]

保有するすべてのデータにモデルを適合させます。

#Fit the model on all the data we have
default_model = MLPRegressor(hidden_layer_sizes=(30,10),max_iter=200)
default_model.fit(X,y)

モデルをONNX表現に変換します。

#Convert the model to an ONNX representation
onnx_model = convert_sklearn(default_model,initial_types=initial_types,target_opset=12)

ONNX表現をファイルに保存します。

#Save the ONNX representation
onnx_name = "Boom 1000 Neural Network.onnx"
onnx.save(onnx_model,onnx_name)

netronを使用してモデルを表示します。

#View the onnx model
netron.start(onnx_name)


図12:ディープニューラルネットワークの可視化

モデルの入力と出力を可視化する

図13:モデルの入力と出力を可視化する


MQL5での実装

統合されたAIシステムを備えた取引アプリケーションを構築するには、まずPythonでエクスポートしたONNXモデルが必要になります。

//+------------------------------------------------------------------+
//|                                                    Boom 1000.mq5 |
//|                                        Gamuchirai Zororo Ndawana |
//|                          https://www.mql5.com/ja/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Zororo Ndawana"
#property link      "https://www.mql5.com/ja/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| ONNX Model                                                       |
//+------------------------------------------------------------------+
#resource "\\Files\\Boom 1000 Neural Network.onnx" as const uchar onnx_buffer[];

ポジションを管理するための取引ライブラリも読み込みます。

//+-----------------------------------------------------------------+
//| Libraries we need                                               |
//+-----------------------------------------------------------------+
#include <Trade/Trade.mqh>
CTrade Trade;

プログラム全体で使用するグローバル変数を定義します。

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
long onnx_model;
int rsi_handler,model_state,system_state;
double mean_values[5],std_values[5],rsi_buffer[],bid,ask;
vectorf model_outputs = vectorf::Zeros(1);
vectorf model_inputs = vectorf::Zeros(5);

ここで、ONNXモデルを準備するための関数を定義しましょう。この関数は、まずプログラムの先頭で定義したバッファからモデルを作成し、モデルが破損していないことを検証します。破損している場合、関数はfalseを返して初期化手順を終了します。その後、関数はONNXモデルの入力と出力の形状の設定に進みます。I/Oパラメータのいずれかを定義できなかった場合、関数は再びfalseを返して初期化手順を終了します。

//+------------------------------------------------------------------+
//| This function will prepare our ONNX model                        |
//+------------------------------------------------------------------+
bool load_onnx_model(void)
  {
//--- First create the ONNX model from the buffer we created earlier
   onnx_model = OnnxCreateFromBuffer(onnx_buffer,ONNX_DEFAULT);

//--- Validate the ONNX model
   if(onnx_model == INVALID_HANDLE)
     {
      Comment("[ERROR] Failed to create the ONNX model: ",GetLastError());
      return(false);
     }

//--- Set the input and output shapes of the model
   ulong input_shape[] = {1,5};
   ulong output_shape[] = {1,1};

//--- Validate the input and output shape
   if(!OnnxSetInputShape(onnx_model,0,input_shape))
     {
      Comment("Failed to set the ONNX model input shape: ",GetLastError());
      return(false);
     }

   if(!OnnxSetOutputShape(onnx_model,0,output_shape))
     {
      Comment("Failed to set the ONNX model output shape: ",GetLastError());
      return(false);
     }

   return(true);
  }

モデル入力をスケーリングせずにONNXモデルを使用することはできません。次の関数は、必要な平均値と標準偏差値を、簡単にアクセスできる配列に読み込みます。

//+-----------------------------------------------------------------+
//| Load the scaling values                                         |
//+-----------------------------------------------------------------+
void load_scaling_values(void)
  {
//--- BOOM 1000 OHLC + RSI Mean values
   mean_values[0] = 16799.87389394667;
   mean_values[1] = 16800.872890865994;
   mean_values[2] = 16798.91007345616;
   mean_values[3] = 16799.908906749482;
   mean_values[4] = 43.45867626462568;
//--- BOOM 1000 OHLC + RSI Mean std values
   std_values[0] = 864.3356132780019;
   std_values[1] = 864.3839684000297;
   std_values[2] = 864.2859346216392;
   std_values[3] = 864.3344430387272;
   std_values[4] = 20.593175501388043;
  }
//+------------------------------------------------------------------+

また、更新された市場価格と現在のテクニカル指標値を取得する関数を定義する必要があります。

//+------------------------------------------------------------------+
//| Fetch updated market prices and technical indicator values       |
//+------------------------------------------------------------------+
void update_market_data(void)
  {
//--- Market data
   bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
   ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
//--- Technical indicator values
   CopyBuffer(rsi_handler,0,0,1,rsi_buffer);
  }

最後に、モデルの入力を取得し、スケーリングして、モデルから予測を取得する関数が必要です。モデルの予測を記憶するためのフラグを保持します。これにより、モデルが反転を予測しているときに簡単に認識できるようになります。

//+------------------------------------------------------------------+
//| Fetch a prediction from our model                                |
//+------------------------------------------------------------------+
void model_predict(void)
  {
//--- Get the model inputs
   model_inputs[0] = iOpen(_Symbol,PERIOD_CURRENT,0);
   model_inputs[1] = iHigh(_Symbol,PERIOD_CURRENT,0);
   model_inputs[2] = iLow(_Symbol,PERIOD_CURRENT,0);
   model_inputs[3] = iClose(_Symbol,PERIOD_CURRENT,0);
   model_inputs[4] = rsi_buffer[0];

//--- Scale the model inputs
   for(int i = 0; i < 5; i++)
     {
      model_inputs[i] = ((model_inputs[i] - mean_values[i]) / std_values[i]);
     }

//--- Fetch a prediction from our model
   OnnxRun(onnx_model,ONNX_DEFAULT,model_inputs,model_outputs);
//--- Give user feedback
   Comment("Model RSI Forecast: ",model_outputs[0]);
//--- Store the model's state
   if(rsi_buffer[0] > model_outputs[0])
     {
      model_state = -1;
     }

   else
      if(rsi_buffer[0] < model_outputs[0])
        {
         model_state = 1;
        }
  }

ここで、モデルの初期化手順を定義します。まず、ONNXモデルを読み込み、スケーリング値を取得してRSI指標を設定します。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- This function will prepare our ONNX model and set the input and output shapes
   if(!load_onnx_model())
     {
      return(INIT_FAILED);
     }

//--- This function will prepare our scaling values
   load_scaling_values();

//--- Setup our technical indicatot
   rsi_handler = iRSI(Symbol(),PERIOD_CURRENT,20,PRICE_CLOSE);

//--- Everything went fine
   return(INIT_SUCCEEDED);
  }

アプリケーションがチャートから削除されるたびに、使用しなくなったリソースを解放し、ONNXモデル、RSI指標をリリースし、EAを削除します。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Release the resources we no longer need
   OnnxRelease(onnx_model);
   IndicatorRelease(rsi_handler);
   ExpertRemove();
  }

更新された価格を受け取るたびに、まず更新された市場データと技術データを取得します。これには、Bid価格とAsk価格、およびRSIの読み取り値が含まれます。これで、モデルから新しい予測を取得する準備が整いました。ポジションがない場合は、モデルの予測に従い、バイナリフラグを使用して現在のポジションを記憶します。そうでない場合、すでにポジションが建っている場合は、モデルの新しい予測がポジションに反するかどうかを確認し、反している場合はポジションをクローズします。そうでない場合は、利益確定を続けます。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Fetch updated market prices
   update_market_data();

//--- On every tick we need to fetch a prediction from our model
   model_predict();

//--- If we have no open positions, follow the model's prediction
   if(PositionsTotal() == 0)
     {
      //--- Our model detected a spike
      if(model_state == 1)
        {
         Trade.Buy(0.2,Symbol(),ask,0,0,"BOOM 1000 AI");
         system_state = 1;
        }

      //--- Our model detected a drop
      if(model_state == -1)
        {
         Trade.Sell(0.2,Symbol(),bid,0,0,"BOOM 1000 AI");
         system_state = -1;
        }
     }

//--- If we have open positiosn, our AI system will decide when to close them
   else
      if(PositionsTotal() > 0)
        {
         if(system_state != model_state)
           {
            //--- Close the positions we opened
            Alert("Reversal detected by the AI system,closing all positions now!");
            Trade.PositionClose(Symbol());
           }
        }
  }
//+------------------------------------------------------------------+

システム動作I

図14:Boom 1000システムがスパイクを検出

システム動作II

図15:Boom 1000システムが逆転を検出

結論

本日の記事では、最も難しい合成商品にも対応できる自己最適化EAを構築できることを実証しました。さらに、価格レベルを直接予測する従来のアプローチでは、今日のアルゴリズム市場では不十分であることも示しました。

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/15781

Connexus入門(第1回):WebRequest関数の使い方 Connexus入門(第1回):WebRequest関数の使い方
この記事は、MQL5でHTTPリクエストを容易にするための「Connexus」と呼ばれるライブラリの開発シリーズの始まりです。このプロジェクトの目標は、エンドユーザーにこの機会を提供し、このヘルパーライブラリーの使い方を示すことです。学習を容易にし、将来の発展の可能性を提供するために、できるだけシンプルにすることを意図しました。
MQL5で古典的な戦略を再構築する(後編):FTSE100と英国債 MQL5で古典的な戦略を再構築する(後編):FTSE100と英国債
この連載では、人気のある取引戦略を探り、AIを使ってその改善を試みます。今日の記事では、株式市場と債券市場の関係に基づく古典的な取引戦略を再考します。
MQL5で取引管理者パネルを作成する(第2回):応答性と迅速なメッセージングの強化 MQL5で取引管理者パネルを作成する(第2回):応答性と迅速なメッセージングの強化
この記事では、以前作成した管理パネルの応答性を強化します。さらに、取引シグナルの文脈におけるクイックメッセージングの重要性についても検討します。
知っておくべきMQL5ウィザードのテクニック(第37回):線形カーネルとMatérnカーネルによるガウス過程回帰 知っておくべきMQL5ウィザードのテクニック(第37回):線形カーネルとMatérnカーネルによるガウス過程回帰
線形カーネルは、線形回帰やサポートベクターマシンの機械学習で使用される、この種の行列の中で最も単純な行列です。一方、Matérnカーネルは、以前の記事で紹介したRBF (Radial Basis Function)をより汎用的にしたもので、RBFが想定するほど滑らかではない関数をマッピングするのに長けています。売買条件を予測する際に、両方のカーネルを利用するカスタムシグナルクラスを構築します。