English Русский 中文 Español Deutsch Português
preview
どんな市場でも優位性を得る方法(第3回):VISA消費指数

どんな市場でも優位性を得る方法(第3回):VISA消費指数

MetaTrader 5 | 22 10月 2024, 09:52
156 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

はじめに

ビッグデータの時代、現代の投資家が利用できる代替データの情報源はほぼ無限にあります。各データセットは、市場リターンを予測する際に、より高い精度レベルをもたらす可能性を秘めています。しかし、この約束を果たすデータセットはほとんどありません。この連載では、代替データの広大な領域を探求するお手伝いをし、これらのデータセットを分析に含めるべきかどうかについて、十分な情報に基づいた意思決定をおこなうための支援をします。一方、これらのデータセットが満足のいく結果をもたらさない場合は、読者の時間を節約するお手伝いができます。

その根拠は、MetaTrader 5端末では直接利用できない代替データセットを考慮することで、市場相場だけに依存するカジュアルな市場参加者と比較して、比較的高い精度で価格水準を予測する変数を発見できる可能性があるということです。


取引戦略の概要

VISAはアメリカの多国籍決済サービス会社です。同社は1958年に設立され、今日では世界最大級の取引処理ネットワークを運営しています。VISAは、先進国のほぼすべての市場に浸透しているため、評判の高い代替データの供給源として有利な立場にあります。さらに、セントルイス連邦準備銀行もマクロ経済データの一部をVISAから収集しています。

今回は、VISA支出モメンタム指数(SMI: Spending Momentum Index)を分析します。この指数は、消費者の消費行動を示すマクロ経済指標です。このデータは、VISAが独自のネットワークとVISAブランドのデビットカードやクレジットカードを使って集計しています。データはすべて非個人化されており、ほとんどが米国で収集されたものです。VISAがさまざまな市場のデータを集計し続ければ、この指数はやがて世界の消費者行動のベンチマークとなるかもしれません。

VISA SMIデータセットの取得には、セントルイス連邦準備銀行が提供するAPIサービスを利用します。米連邦準備制度理事会経済データベース(FRED)のAPIを使えば、世界中から収集された数十万のさまざまな経済時系列データにアクセスできます。


方法論の概要

SMIデータはVISAが毎月発表しているもので、本稿執筆時点では200行に満たないものです。そのため、過剰適合に強く、複雑な関係を捉えるのに十分な柔軟性を持つモデリング技術が必要となります。これはニューラルネットワークにとって理想的な仕事かもしれません。

VISAデータセットである通常の始値、高値、安値、終値のセットと3つの追加入力が与えられたEURUSDの変化を分類するために、ディープニューラルネットワークの5つのパラメータを最適化しました。最適化したモデルは、検証で71%の精度を達成することができ、デフォルトのモデルを凌駕しました。ただし、この精度は月次データによるものであることを念頭に置いてください。

ディープニューラルネットワークを最適化するために、ランダムサーチアルゴリズムの1000回の反復を採用し、訓練データに過剰適合することなくモデルの訓練に成功しました。これらの結果は印象的に聞こえるかもしれませんが、観察された関係が信頼できるものであると自信を持って断言することはできません。私たちの特徴量選択アルゴリズムは、ノンパラメトリックな方法で最も重要な特徴量を選択する際、3つのVISAデータセットすべてを破棄しました。さらに、3つのVISAデータセットの相互情報スコアはいずれも比較的低く、これはデータセットが独立しているか、モデルにとって意味のある形で関係を明らかにできていないことを示しているのかもしれません。


データ抽出

必要なデータを取得するには、まずFREDのWebサイトでアカウントを作成する必要があります。アカウントを作成したら、私たちの議論に従って、FRED APIキーを使ってセントルイス連邦準備制度理事会(FRB)が保有する経済時系列データにアクセスできます。EURUSD相場に関する市場データは、MetaTrader 5 Python APIを使用して端末から直接取得されます。

始めるには、まず必要なライブラリをロードします。

#Import the libraries we need
import pandas as pd
import seaborn as sns
import numpy as np
from fredapi import Fred
import MetaTrader5 as mt5
from datetime import datetime
import time
import pytz

ここでFRED APIキーを設定し、必要なデータを取得します。

#Let's setup our FredAPI
fred = Fred(api_key="ENTER YOUR API KEY")
visa_discretionary = pd.DataFrame(fred.get_series("VISASMIDSA"),columns=["visa d"])
visa_headline   = pd.DataFrame(fred.get_series("VISASMIHSA"),columns=["visa h"])
visa_non_discretionary = pd.DataFrame(fred.get_series("VISASMINSA"),columns=["visa nd"])

予測期間を定義します。

#Define how far ahead we want to forecast
look_ahead = 10


データを可視化する

3つのデータセットを可視化してみましょう。

visa_discretionary.plot(title="VISA Spending Momentum Index: Discretionary")

図1:最初のVISAデータセット

では、2つ目のデータセットを可視化してみましょう。

visa_headline.plot(title="VISA Spending Momentum Index: Headline")

図2:第2のVISAデータセット

そして最後に、3つ目のVISAデータセットです。

visa_non_discretionary.plot(title="VISA Spending Momentum Index: Non-Discretionary")

図3:第3のVISAデータセット

最初の2つのデータセットはほとんど同じに見えるが、さらに後述するように、これらのデータセットの相関レベルは0.89であり、同じ情報が含まれている可能性があります。これは、片方を捨ててもう片方を残すことを示唆しています。しかし、それが必要かどうかは、特徴量選択アルゴリズムに判断させます。


MetaTrader 5端末からのデータ取得

これから端末を初期化します。

#Initialize the terminal
mt5.initialize()

ここでタイムゾーンを指定します。

#Set timezone to UTC
timezone = pytz.timezone("Etc/UTC")

datetimeオブジェクトを作成します。

#Create a 'datetime' object in UTC
utc_from = datetime(2024,7,1,tzinfo=timezone)

MetaTrader 5からデータを取得し、pandasデータフレームにラップします。

#Fetch the data
eurusd = pd.DataFrame(mt5.copy_rates_from("EURUSD",mt5.TIMEFRAME_MN1,utc_from,visa_headline.shape[0]))

データにラベルを付け、タイムスタンプをインデックスとして使います。

#Label the data
eurusd["target"] = np.nan
eurusd.loc[eurusd["close"] > eurusd["close"].shift(-look_ahead),"target"] = 0
eurusd.loc[eurusd["close"] < eurusd["close"].shift(-look_ahead),"target"] = 1
eurusd.dropna(inplace=True)
eurusd.set_index("time",inplace=True)

では、両データセットが共有している日付を使って、両データセットを統合しましょう。

#Let's merge the datasets
merged_data = eurusd.merge(visa_headline,right_index=True,left_index=True)
merged_data = merged_data.merge(visa_discretionary,right_index=True,left_index=True)
merged_data = merged_data.merge(visa_non_discretionary,right_index=True,left_index=True)


探索的データ分析

データを調べる準備はできています。散布図は、2つの変数の関係を可視化するのに役立ちます。各VISAデータセットを終値に対してプロットした散布図を見てみましょう。青い点は次の10本のローソク足で価格が下落した場合を、オレンジの点はその逆を表しています。

プロットの中央部ではノイズが多いが、極端なレベルではVISAデータセットは上下の動きを適度に分離しているように見えます。

#Let's create scatter plots
sns.scatterplot(data=merged_data,y="close",x="visa h",hue="target").set(title="EURUSD Close Against VISA Momentum Index: Headline")

図4:EURUSD終値に対する無裁量VISAデータセットのプロット

図5:EURUSD終値に対する裁量VISAデータセットのプロット

図6:EURUSD終値に対するヘッドラインVISAデータセットのプロット

VISAデータセットとEURUSD市場との相関レベルは中程度で、すべて正の値です。どの相関レベルも、私たちにとっては特に興味深いものではありません。しかし、正の値は2つの変数が共に上昇下降する傾向があることを示していることは注目に値します。これはマクロ経済学の理解に沿ったもので、アメリカの消費支出は為替レートにある程度の影響を与えます。消費者が一斉に消費しないことを選択した場合、彼らの行動は通貨流通総額を減らし、ドル高を引き起こす可能性があります。


図7:データセットの相関分析


特徴量の選択

ターゲットと新しい特徴量の関係はどの程度重要なのでしょうか。新しい特徴量が特徴量選択アルゴリズムによって除去されるかどうかを観察してみましょう。私たちのアルゴリズムが新しい変数を選択しない場合、これは関係が信頼できないことを示している可能性があります。

前方選択アルゴリズムは、帰無モデルから始まり、一度に1つの特徴を追加し、そこから最良の単一変数モデルを選択し、次に2つ目の変数の探索を開始する、といった具合です。そして、その最高のモデルを私たちに返却します。私たちの研究では、アルゴリズムによって選択されたのは始値だけであり、これは関係が安定していない可能性を示しています。

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

#Let's see which features are the most important
from mlxtend.feature_selection import SequentialFeatureSelector as SFS
from mlxtend.plotting import plot_sequential_feature_selection as plot_sfs
import matplotlib.pyplot as plt

前方選択オブジェクトを作成します。

#Create the forward selection object
sfs = SFS(
        MLPClassifier(hidden_layer_sizes=(20,10,4),shuffle=False,activation=tuner.best_params_["activation"],solver=tuner.best_params_["solver"],alpha=tuner.best_params_["alpha"],learning_rate=tuner.best_params_["learning_rate"],learning_rate_init=tuner.best_params_["learning_rate_init"]),
        k_features=(1,train_X.shape[1]),
        forward=False,
        scoring="accuracy",
        cv=5
).fit(train_X,train_y)

結果をプロットしてみましょう。

fig1 = plot_sfs(sfs.get_metric_dict(),kind="std_dev")
plt.title("Neural Network Backward Feature Selection")
plt.grid()

図8:モデルの特徴数の増加に伴うパフォーマンスの悪化

残念なことに、特徴を増やすにつれて精度は落ちていきました。これは単に関連性がそれほど強くないか、モデルが解釈できるような方法で関連性を明らかにしていないかのどちらかでしょう。そのため、1つの特徴を備えたモデルでも仕事をこなせる可能性があるようです。

私たちが確認した最高の特徴量。

sfs.k_feature_names_
('open',)

ここで、相互情報量(MI)スコアを観察してみましょう。MIスコアは正の値であり、理論的には0から無限大までの範囲であるが、実際には2以上のMIスコアが観測されることは稀であり、1以上のMIスコアは良好です。

scikit-learnからMI分類器をインポートします。

#Mutual information
from sklearn.feature_selection import mutual_info_classif

ヘッドラインデータセットのMIスコア。

#Mutual information from the headline visa dataset, 
print(f"VISA Headline dataset has a mutual info score of: {mutual_info_classif(train_X.loc[:,['visa h']],train_y)[0]}")
VISA Headlineデータセットの相互情報スコアは以下の通りです。0.06069528690724346

一任データセットのMIスコア。

#Mutual information from the second visa dataset, 
print(f"VISA Discretionary dataset has a mutual info score of: {mutual_info_classif(train_X.loc[:,['visa d']],train_y)[0]}")
VISA Discretionaryデータセットの相互情報スコアは以下の通りです。0.1277119388376886

このことは、VISAデータセットにさまざまな変換を施してみるべき説得力のある理由かもしれません。


パラメータチューニング

それでは、EURUSDを予測するためにディープニューラルネットワークをチューニングしてみましょう。その前に、データをスケーリングする必要があります。まず、結合したデータセットのインデックスをリセットします。

#Reset the index
merged_data.reset_index(inplace=True)

ターゲットと予測変数を定義します。

#Define the target
target = "target"
ohlc_predictors = ["open","high","low","close","tick_volume"]
visa_predictors = ["visa d","visa h","visa nd"]
all_predictors = ohlc_predictors + visa_predictors

では、データをスケーリングして変換してみましょう。データセットの各値から平均を引き、それぞれの列の標準偏差で割ります。この変換は外れ値に敏感であることは注目に値します。

#Let's scale the data
scale_factors = pd.DataFrame(index=["mean","standard deviation"],columns=all_predictors)

for i in np.arange(0,len(all_predictors)):
        #Store the mean and standard deviation for each column
        scale_factors.iloc[0,i] = merged_data.loc[:,all_predictors[i]].mean()
        scale_factors.iloc[1,i] = merged_data.loc[:,all_predictors[i]].std()
        merged_data.loc[:,all_predictors[i]] = ((merged_data.loc[:,all_predictors[i]] - scale_factors.iloc[0,i]) / scale_factors.iloc[1,i])

scale_factors

スケーリングされたデータを見てみましょう。

#Let's see the normalized data
merged_data

標準ライブラリのインポート

#Lets try to train a deep neural network to uncover relationships in the data
from sklearn.neural_network import MLPClassifier
from sklearn.model_selection import RandomizedSearchCV
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

訓練とテストの分割をおこないます。

#Create train test partitions for our alternative data
train_X,test_X,train_y,test_y = train_test_split(merged_data.loc[:,all_predictors],merged_data.loc[:,"target"],test_size=0.5,shuffle=False)

利用可能な入力に合わせてモデルを調整します。最初にチューニングしたいモデルを渡し、次に関心のあるモデルのパラメータを指定しなければならないことを思い出してください。その後、交差検証に使用するフォールド数を指定する必要があります。

tuner = RandomizedSearchCV(MLPClassifier(hidden_layer_sizes=(20,10,4),shuffle=False),
                        {
                                "activation": ["relu","identity","logistic","tanh"],
                                "solver": ["lbfgs","adam","sgd"],
                                "alpha": [0.1,0.01,0.001,(10.0 ** -4),(10.0 ** -5),(10.0 ** -6),(10.0 ** -7),(10.0 ** -8),(10.0 ** -9)],
                                "learning_rate": ["constant", "invscaling", "adaptive"],
                                "learning_rate_init": [0.1,0.01,0.001,(10.0 ** -4),(10.0 ** -5),(10.0 ** -6),(10.0 ** -7),(10.0 ** -8),(10.0 ** -9)],
                        },
                        cv=5,
                        n_iter=1000,
                        scoring="accuracy",
                        return_train_score=False
                        )

チューナーの適合

tuner.fit(train_X,train_y)

訓練データで得られた結果を、良いものから悪いものの順に見てみましょう。

tuner_results = pd.DataFrame(tuner.cv_results_)
params = ["param_activation","param_solver","param_alpha","param_learning_rate","param_learning_rate_init","mean_test_score"]
tuner_results.loc[:,params].sort_values(by="mean_test_score",ascending=False)

最適化結果

図9:最適化の結果

訓練データでの最高精度は88%でした。なお、今回選択した最適化アルゴリズムは確率的なものであるため、このデモで得られた結果を再現するのは難しいかもしれません。


過剰適合のテスト

ここで、デフォルトモデルとカスタマイズモデルを比較して、訓練データに過剰適合しているかどうかを確認してみましょう。もし過剰適合していれば、検証セット上ではデフォルトモデルがカスタマイズモデルを上回り、そうでなければカスタマイズモデルの方が良い結果を出す。

2つのモデルを用意しましょう。

#Let's compare the default model and our customized model on the hold out set
default_model = MLPClassifier(hidden_layer_sizes=(20,10,4),shuffle=False)
customized_model = MLPClassifier(hidden_layer_sizes=(20,10,4),shuffle=False,activation=tuner.best_params_["activation"],solver=tuner.best_params_["solver"],alpha=tuner.best_params_["alpha"],learning_rate=tuner.best_params_["learning_rate"],learning_rate_init=tuner.best_params_["learning_rate_init"])

デフォルトモデルの精度を測定します。

#The accuracy of the defualt model
default_model.fit(train_X,train_y)
accuracy_score(test_y,default_model.predict(test_X))
0.5423728813559322

次に、カスタマイズされたモデルの精度です。

#The accuracy of the defualt model
customized_model.fit(train_X,train_y)
accuracy_score(test_y,customized_model.predict(test_X))
0.7457627118644068

訓練データに過剰適合することなくモデルを訓練できたようです。また、訓練誤差は通常、テスト誤差よりも常に大きいことに注意。しかし、両者の差が大きすぎることはないはずです。訓練エラーは88%、テストエラーは74%で、これは妥当な結果です。訓練誤差とテスト誤差の間に大きなギャップがある場合は、過剰適合の可能性がある!


戦略の実施

まず、使用するグローバル変数を定義します。

#Let us now start building our trading strategy
SYMBOL = 'EURUSD'
TIMEFRAME = mt5.TIMEFRAME_MN1
DEVIATION = 1000
VOLUME = 0
LOT_MULTIPLE = 1

MetaTrader 5端末を初期化してみましょう。

#Get the system up
if not mt5.initialize():
        print('Failed To Log in')

市場についてもっと詳しく知る必要があります。

#Let's fetch the trading volume
for index,symbol in enumerate(mt5.symbols_get()):
        if symbol.name == SYMBOL:
        print(f"{symbol.name} has minimum volume: {symbol.volume_min}")
        VOLUME = symbol.volume_min * LOT_MULTIPLE

この関数は、現在の市場価格を取得します。

#A function to get current prices
def get_prices():
        start = datetime(2024,1,1)
        end   = datetime.now()
        data  = pd.DataFrame(mt5.copy_rates_range(SYMBOL,TIMEFRAME,start,end))
        data['time'] = pd.to_datetime(data['time'],unit='s')
        data.set_index('time',inplace=True)
        return(data.iloc[-1,:])

FRED APIから最新の代替データを取得する関数も作ります。

#A function to get our alternative data
def get_alternative_data():
        visa_d = fred.get_series_as_of_date("VISASMIDSA",datetime.now())
        visa_d = visa_d.iloc[-1,-1]
        visa_h = fred.get_series_as_of_date("VISASMIHSA",datetime.now())
        visa_h = visa_h.iloc[-1,-1]
        visa_n = fred.get_series_as_of_date("VISASMINSA",datetime.now())
        visa_n = visa_n.iloc[-1,-1]
        return(visa_d,visa_h,visa_n)

入力を正規化し、スケーリングする関数が必要です。

#A function to prepare the inputs for our model
def get_model_inputs():
        LAST_OHLC = get_prices()
        visa_d , visa_h , visa_n = get_alternative_data()
        return(
        np.array([[
                        ((LAST_OHLC['open'] - scale_factors.iloc[0,0]) / scale_factors.iloc[1,0]),
                        ((LAST_OHLC['high']  - scale_factors.iloc[0,1]) / scale_factors.iloc[1,1]),
                        ((LAST_OHLC['low']  - scale_factors.iloc[0,2]) / scale_factors.iloc[1,2]),
                        ((LAST_OHLC['close']  - scale_factors.iloc[0,3]) / scale_factors.iloc[1,3]),
                        ((LAST_OHLC['tick_volume']  - scale_factors.iloc[0,4]) / scale_factors.iloc[1,4]),
                        ((visa_d  - scale_factors.iloc[0,5]) / scale_factors.iloc[1,5]),
                        ((visa_h  - scale_factors.iloc[0,6]) / scale_factors.iloc[1,6]),
                        ((visa_n  - scale_factors.iloc[0,7]) / scale_factors.iloc[1,7])
                ]])
        )

手持ちのすべてのデータでモデルを訓練してみましょう。

#Let's train our model on all the data we have
model = MLPClassifier(hidden_layer_sizes=(20,10,4),shuffle=False,activation="logistic",solver="lbfgs",alpha=0.00001,learning_rate="constant",learning_rate_init=0.00001)
model.fit(merged_data.loc[:,all_predictors],merged_data.loc[:,"target"])

この関数はモデルから予測を得ます。

#A function to get a prediction from our model
def ai_forecast():
        model_inputs = get_model_inputs()
        prediction = model.predict(model_inputs)
        return(prediction[0])

これでアルゴリズムの核心にたどり着きました。まず、ポジションの数を確認します。次に、モデルから予測を得ます。ポジションがない場合は、モデルの予測を使ってポジションを建てます。そうでなければ、ポジションが建っている場合、モデルの予測をエグジットシグナルとして使用します。

while True:
        #Get data on the current state of our terminal and our portfolio
        positions = mt5.positions_total()
        forecast  = ai_forecast()
        BUY_STATE , SELL_STATE = False , False

        #Interpret the model's forecast
        if(forecast == 0.0):
        SELL_STATE = True
        BUY_STATE  = False

        elif(forecast == 1.0):
        SELL_STATE = False
        BUY_STATE  = True

        print(f"Our forecast is {forecast}")

        #If we have no open positions let's open them
        if(positions == 0):
        print(f"We have {positions} open trade(s)")
        if(SELL_STATE):
                print("Opening a sell position")
                mt5.Sell(SYMBOL,VOLUME)
        elif(BUY_STATE):
                print("Opening a buy position")
                mt5.Buy(SYMBOL,VOLUME)

        #If we have open positions let's manage them
        if(positions > 0):
        print(f"We have {positions} open trade(s)")
        for pos in mt5.positions_get():
                if(pos.type == 1):
                if(BUY_STATE):
                        print("Closing all sell positions")
                        mt5.Close(SYMBOL)
                if(pos.type == 0):
                if(SELL_STATE):
                        print("Closing all buy positions")
                        mt5.Close(SYMBOL)
        #If we have finished all checks then we can wait for one day before checking our positions again
        time.sleep(24 * 60 * 60)
Our forecast is 0.0
We have 0 open trade(s)
Opening a sell position


MQL5での実装

MQL5で戦略を実行するには、まずモデルをOpen Neural Network Exchange(ONNX)形式にエクスポートする必要があります。ONNXは、機械学習モデルをグラフとエッジの組み合わせで表現するためのプロトコルです。この標準化されたプロトコルにより、開発者は異なるプログラミング言語を使用して機械学習モデルを簡単に構築し、展開することができます。残念ながら、すべての機械学習モデルやフレームワークが現在のONNX APIで完全にサポートされているわけではありません。 

手始めに、いくつかのライブラリをインポートしましょう。

#Import the libraries we need
import pandas as pd
import numpy as np
from fredapi import Fred
import MetaTrader5 as mt5
from datetime import datetime
import time
import pytz

そして、必要なデータにアクセスするために、FRED APIキーを入力する必要があります。

#Let's setup our FredAPI
fred = Fred(api_key="")
visa_discretionary = pd.DataFrame(fred.get_series("VISASMIDSA"),columns=["visa d"])
visa_headline      = pd.DataFrame(fred.get_series("VISASMIHSA"),columns=["visa h"])
visa_non_discretionary = pd.DataFrame(fred.get_series("VISASMINSA"),columns=["visa nd"])

データを取得した後、上で説明したのと同じ形式でスケーリングしました。同じ情報の繰り返しを避けるため、これらの手順は省略しました。唯一のわずかな違いは、バイナリターゲットだけでなく、実際の終値を予測するためにモデルを訓練していることです。

データをスケーリングした後、モデルのパラメータを調整してみましょう。

#A few more libraries we need
from sklearn.neural_network import MLPRegressor
from sklearn.model_selection import RandomizedSearchCV
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

モデルを最適化するための訓練セットと、過剰適合をテストするための検証セットを持つように、データを分割する必要があります。

#Create train test partitions for our alternative data
train_X,test_X,train_y,test_y = train_test_split(merged_data.loc[:,all_predictors],merged_data.loc[:,"close target"],test_size=0.5,shuffle=False)

次にハイパーパラメータのチューニングをおこないます。スコアリングメトリックを「neg mean squared error」に設定していることに注意してください。このスコアリング メトリックは、最も低い MSE を生成するモデルを最高のパフォーマンスのモデルとして識別します。

tuner = RandomizedSearchCV(MLPRegressor(hidden_layer_sizes=(20,10,4),shuffle=False,early_stopping=True),
                           {
                               "activation": ["relu","identity","logistic","tanh"],
                               "solver": ["lbfgs","adam","sgd"],
                               "alpha": [0.1,0.01,0.001,(10.0 ** -4),(10.0 ** -5),(10.0 ** -6),(10.0 ** -7),(10.0 ** -8),(10.0 ** -9)],
                               "learning_rate": ["constant", "invscaling", "adaptive"],
                               "learning_rate_init": [0.1,0.01,0.001,(10.0 ** -4),(10.0 ** -5),(10.0 ** -6),(10.0 ** -7),(10.0 ** -8),(10.0 ** -9)],
                           },
                           cv=5,
                           n_iter=1000,
                           scoring="neg_mean_squared_error",
                           return_train_score=False,
                           n_jobs=-1
                          )

チューナーオブジェクトの適合

tuner.fit(train_X,train_y)

ここで過剰適合をテストしてみましょう。

#Let's compare the default model and our customized model on the hold out set
default_model = MLPRegressor(hidden_layer_sizes=(20,10,4),shuffle=False)
customized_model = MLPRegressor(hidden_layer_sizes=(20,10,4),shuffle=False,activation=tuner.best_params_["activation"],solver=tuner.best_params_["solver"],alpha=tuner.best_params_["alpha"],learning_rate=tuner.best_params_["learning_rate"],learning_rate_init=tuner.best_params_["learning_rate_init"])

以下は、デフォルトモデルの精度です。

#The accuracy of the defualt model
default_model.fit(train_X,train_y)
mean_squared_error(test_y,default_model.predict(test_X))
0.19334261927379248

保留中の検証セットでは、デフォルト モデルを上回るパフォーマンスを発揮できました。これは、過剰適合していないことを示す良い兆候です。

#The accuracy of the defualt model
default_model.fit(train_X,train_y)
mean_squared_error(test_y,default_model.predict(test_X))

0.006138795093781729

ONNX形式にエクスポートする前に、カスタマイズしたモデルをすべてのデータに適合してみましょう。

#Fit the model on all the data we have
customized_model.fit(test_X,test_y)

ONNX変換ライブラリのインポート

#Convert to ONNX
from skl2onnx.common.data_types import FloatTensorType
from skl2onnx import convert_sklearn
import netron
import onnx

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

#Define the initial types
initial_types = [("float_input",FloatTensorType([1,train_X.shape[1]]))]

メモリ内にモデルのONNX表現を作成します。

#Create the onnx representation
onnx_model = convert_sklearn(customized_model,initial_types=initial_types,target_opset=12)

ONNX表現をハードドライブに保存します。

#Save the ONNX model
onnx_model_name = "EURUSD VISA MN1 FLOAT.onnx"
onnx.save(onnx_model,onnx_model_name)

ONNXモデルをNetronで確認します。

#View the ONNX model
netron.start(onnx_model_name)


ニューラルネットワークのONNX表現

図10:ONNX形式のディープニューラルネットワーク

ONNX DNN

図11:ONNXモデルのメタデータ

EAの構築はほぼ完了しましたが、まずFREDからデータを取得し、それをプログラムに渡すバックグラウンドのPythonサービスを作成する必要があります。 

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

#Import the libraries we need
import pandas as pd
import numpy as np
from fredapi import Fred
from datetime import datetime

FREDの認証情報を使ってログインします。

#Let's setup our FredAPI
fred = Fred(api_key="")

データを取得してCSVに書き出す関数を定義します。

#A function to write out our alternative data to CSV
def write_out_alternative_data():
        visa_d = fred.get_series_as_of_date("VISASMIDSA",datetime.now())
        visa_d = visa_d.iloc[-1,-1]
        visa_h = fred.get_series_as_of_date("VISASMIHSA",datetime.now())
        visa_h = visa_h.iloc[-1,-1]
        visa_n = fred.get_series_as_of_date("VISASMINSA",datetime.now())
        visa_n = visa_n.iloc[-1,-1] 
        data = pd.DataFrame(np.array([visa_d,visa_h,visa_n]),columns=["Data"],index=["Discretionary","Headline","Non-Discretionary"])
        data.to_csv("C:\\Users\\Westwood\\AppData\\Roaming\\MetaQuotes\\Terminal\\D0E8209F77C8CF37AD8BF550E51FF075\\MQL5\\Files\\fred_visa.csv")

1日に1回、新しいデータを確認してCSVファイルを更新するループを書きます。

while True:
        #Update the fred data for our MT5 EA
        write_out_alternative_data()
        #If we have finished all checks then we can wait for one day before checking for new data
        time.sleep(24 * 60 * 60)
FREDの最新データにアクセスできるようになったので、EAを作り始めることができます。 
We will first load our ONNX model as a resource into our application.
//+------------------------------------------------------------------+
//|                                                      VISA EA.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Resorces                                                         |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD VISA MN1 FLOAT.onnx" as const uchar onnx_buffer[];

ポジションを建て、管理するのに役立つ取引ライブラリをロードします。

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

ここまでで、アプリケーションはうまくまとまりつつあるので、アプリケーションのさまざまなブロックで使うグローバル変数を作ってみましょう。

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
long   onnx_model;
double mean_values[8],std_values[8];
float visa_data[3];
vector model_forecast = vector::Zeros(1);
double trading_volume = 0.3;
int state = 0;

ONNXモデルを使い始める前に、まずプログラムの最初に必要なリソースからONNXモデルを作成する必要があります。その後、モデルの入出力の形状を定義します。 

//+------------------------------------------------------------------+
//| Load the ONNX model                                              |
//+------------------------------------------------------------------+
bool load_onnx_model(void)
  {
//--- Try create the ONNX model from the buffer we have
   onnx_model = OnnxCreateFromBuffer(onnx_buffer,ONNX_DEFAULT);

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

//--- Set the I/O shape
   ulong input_shape[] = {1,8};
   ulong output_shape[] = {1,1};

//--- Validate the I/O shapes
   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);
  }

列の平均を引き、各列の標準偏差で割ることによってデータを標準化したことを思い出してください。これらの値をメモリに保存する必要があります。これらの値が変わることはないので、単純にプログラムにハードコードしました。

//+------------------------------------------------------------------+
//| Mean & Standard deviation values                                 |
//+------------------------------------------------------------------+
void load_scaling_values(void)
  {
//--- Mean & standard deviation values for the EURUSD OHLCV
   mean_values[0] = 1.146552;
   std_values[0]  = 0.08293;
   mean_values[1] = 1.165568;
   std_values[1]  = 0.079657;
   mean_values[2] = 1.125744;
   std_values[2]  = 0.083896;
   mean_values[3] = 1.143834;
   std_values[3]  = 0.080655;
   mean_values[4] = 1883520.051282;
   std_values[4]  = 826680.767222;
//--- Mean & standard deviation values for the VISA datasets
   mean_values[5] = 101.271017;
   std_values[5]  = 3.981438;
   mean_values[6] = 100.848506;
   std_values[6]  = 6.565229;
   mean_values[7] = 100.477269;
   std_values[7]  = 2.367663;
  }

作成したPythonのバックグラウンドサービスは、常に最新のデータを提供してくれるので、そのCSVを読み込んで配列に格納する関数を作ってみましょう。

//+-------------------------------------------------------------------+
//| Read in the VISA data                                             |
//+-------------------------------------------------------------------+
void read_visa_data(void)
  {
//--- Read in the file
   string file_name = "fred_visa.csv";

//--- Try open the file
   int result = FileOpen(file_name,FILE_READ|FILE_CSV|FILE_ANSI,","); //Strings of ANSI type (one byte symbols).

//--- Check the result
   if(result != INVALID_HANDLE)
     {
      Print("Opened the file");
      //--- Store the values of the file

      int counter = 0;
      string value = "";
      while(!FileIsEnding(result) && !IsStopped()) //read the entire csv file to the end
        {
         if(counter > 10)  //if you aim to read 10 values set a break point after 10 elements have been read
            break;          //stop the reading progress

         value = FileReadString(result);
         Print("Trying to read string: ",value);

         if(counter == 3)
           {
            Print("Discretionary data: ",value);
            visa_data[0] = (float) value;
           }

         if(counter == 5)
           {
            Print("Headline data: ",value);
            visa_data[1] = (float) value;
           }

         if(counter == 7)
           {
            Print("Non-Discretionary data: ",value);
            visa_data[2] = (float) value;
           }

         if(FileIsLineEnding(result))
           {
            Print("row++");
           }

         counter++;
        }

      //--- Show the VISA data
      Print("VISA DATA: ");
      ArrayPrint(visa_data);

      //---Close the file
      FileClose(result);
     }
  }

最後に、モデルから予測を得るための関数を定義しなければなりません。まず、現在の入力をfloatベクトルに格納します。ONNXの初期型を作成するときに定義したように、このモデルにはfloat型の入力があるからです。

入力値をモデルに渡す前に、列の平均を引き、列の標準偏差で割ることによって、各入力値をスケーリングしなければならないことを思い出してください。

//+--------------------------------------------------------------+
//| Get a prediction from our model                              |
//+--------------------------------------------------------------+
void model_predict(void)
  {
//--- Fetch input data
   read_visa_data();
   vectorf input_data =  {(float)iOpen("EURUSD",PERIOD_MN1,0),
                          (float)iHigh("EURUSD",PERIOD_MN1,0),
                          (float)iLow("EURUSD",PERIOD_MN1,0),
                          (float)iClose("EURUSD",PERIOD_MN1,0),
                          (float)iTickVolume("EURUSD",PERIOD_MN1,0),
                          (float)visa_data[0],
                          (float)visa_data[1],
                          (float)visa_data[2]
                         };
//--- Scale the data
   for(int i =0; i < 8;i++)
     {
      input_data[i] = (float)((input_data[i] - mean_values[i])/std_values[i]);
     }

//--- Show the input data
   Print("Input data: ",input_data);

//--- Obtain a forecast
   OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT|ONNX_DEFAULT,input_data,model_forecast);
  }
//+------------------------------------------------------------------+

では、初期化手順を定義しましょう。まずONNXモデルをロードし、次にVISAデータセットを読み込み、最後にスケーリング値をロードします。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Load the ONNX file
   if(!load_onnx_model())
     {
      //--- We failed to load the ONNX model
      return(INIT_FAILED);
     }

//--- Read the VISA data
   read_visa_data();

//--- Load scaling values
   load_scaling_values();

//--- We were successful
   return(INIT_SUCCEEDED);
  }

プログラムが使われなくなったときはいつでも、もう必要のないリソースを解放します。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Free up the resources we don't need
   OnnxRelease(onnx_model);
   ExpertRemove();
  }

新しい価格データが入手可能になると、まずモデルから予測を取得します。ポジションがない場合は、モデルによって生成されたエントリに従います。最後に、ポジションがある場合は、AIモデルを使って価格の反転の可能性を事前に察知します。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {

//--- Get a prediction from our model
   model_predict();
   Comment("Model forecast: ",model_forecast[0]);
//--- Check if we have any positions
   if(PositionsTotal() == 0)
     {
      //--- Note that we have no trades open
      state = 0;

      //--- Find an entry and take note
      if(model_forecast[0] < iClose(_Symbol,PERIOD_CURRENT,0))
        {
         Trade.Sell(trading_volume,_Symbol,SymbolInfoDouble(_Symbol,SYMBOL_BID),0,0,"Gain an Edge VISA");
         state = 1;
        }

      if(model_forecast[0] > iClose(_Symbol,PERIOD_CURRENT,0))
        {
         Trade.Buy(trading_volume,_Symbol,SymbolInfoDouble(_Symbol,SYMBOL_ASK),0,0,"Gain an Edge VISA");
         state = 2;
        }
     }

//--- If we have positions open, check for reversals
   if(PositionsTotal() > 0)
     {
      if(((state == 1) && (model_forecast[0] > iClose(_Symbol,PERIOD_CURRENT,0))) ||
         ((state == 2) && (model_forecast[0] < iClose(_Symbol,PERIOD_CURRENT,0))))
        {
         Alert("Reversal detected, closing positions now");
         Trade.PositionClose(_Symbol);
        }

     }
  }
//+------------------------------------------------------------------+

EA

図12:VISA EA

EAからの出力

図13:プログラムのサンプル出力

EAの動作

図14:アプリケーションの動作


結論

この記事では、取引戦略に役立つデータを選択する方法を紹介しました。これまで、代替データの潜在的な強さを測定する方法や、過剰適合せずに可能な限りパフォーマンスを引き出せるようにモデルを最適化する方法について説明してきました。調査すべきデータセットは何十万にも及ぶ可能性があり、私たちは最も有益な情報を特定するお手伝いをすることに全力を尽くしています。

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

知っておくべきMQL5ウィザードのテクニック(第32回):正則化 知っておくべきMQL5ウィザードのテクニック(第32回):正則化
正則化とは、ニューラルネットワークのさまざまな層全体に適用される離散的な重み付けに比例して、損失関数にペナルティを与える形式です。様々な正則化形式について、ウィザードで組み立てたEAを使ったテスト実行で、この正則化が持つ重要性を見てみます。
古典的な戦略を再構築する(第6回):多時間枠分析 古典的な戦略を再構築する(第6回):多時間枠分析
この連載では、古典的な戦略を再検討し、AIを使って改善できるかどうかを検証します。本日の記事では、人気の高い多時間枠分析という戦略を検証し、AIによって戦略が強化されるかどうかを判断します。
MQL5とデータ処理パッケージの統合(第2回):機械学習と予測分析 MQL5とデータ処理パッケージの統合(第2回):機械学習と予測分析
本連載では、MQL5とデータ処理パッケージの統合について考察し、機械学習と予測分析の強力な組み合わせを深掘りします。MQL5と一般的な機械学習ライブラリをシームレスに接続することで、金融市場向けの高度な予測モデルを実現する方法を探ります。
古典的な戦略を再構築する(第5回):USDZARの多銘柄分析 古典的な戦略を再構築する(第5回):USDZARの多銘柄分析
この連載では、古典的な戦略を再検討し、AIを使って戦略を改善できるかどうかを検証します。今日の記事では、複数の相関する証券をまとめて分析するという一般的な戦略について検討し、エキゾチックな通貨ペアであるUSDZAR(米ドル/南アフリカランド)に焦点を当てます。