
どんな市場でも優位性を得る方法(第2回):テクニカル指標の予測
はじめに
電子取引環境で機械学習を適用しようとする投資家は多くの課題に直面しており、現実には多くの投資家が期待する成果を達成できていません。この記事は、アルゴリズムトレーダーを目指す人が、その戦略の複雑さに比して満足のいくリターンを達成できないかもしれないと私が考えるいくつかの理由を明らかにすることを目的としています。金融証券の価格を予測しても、しばしば精度が50%を超えることができない理由と、その代わりにテクニカル指標の値を予測することに焦点を当てることで、精度を70%程度まで向上させることができる方法を示します。このガイドでは、時系列分析のベストプラクティスを段階的に説明します。
この記事を読み終わる頃には、PythonとMQL5を使用して機械学習モデルの精度を高め、市場変化の先行指標を他の市場参加者よりも効果的に発見する方法をしっかりと理解していることでしょう。
指標値の予測
MetaTrader 5端末から履歴データを取得し、標準的なPythonライブラリを使用して分析します。この分析により、指標値の変化を予測することは、証券価格の変化を予測することよりも効果的であることが示されます。証券価格に影響を与える要因を部分的にしか観察できないからです。現実には、銘柄の価格に影響を与えるすべての変数をモデル化することはできません。しかし、テクニカル指標の値に影響を与えるすべての要因を完全に観察することができます。
まず、その原理を示し、最後になぜこの方法がより効果的なのかを説明しましょう。原理を初めに実際に見てみることで、理論的な説明が理解しやすくなります。まずはチャートのすぐ上にあるメニューから銘柄リストのアイコンを選択しましょう。
ここでの目標は、データのフェッチに集中しています。
- MetaTrader 5端末を開きます。
- チャート上のメニューから銘柄リストのアイコンを選択します。
- 分析に必要な銘柄と時間枠を選択します。
- 履歴データをカンマ区切り値(csv)ファイルに書き出します。
図1:履歴データの取得
モデルにしたい銘柄を検索します。
図2:銘柄の選択
その後、メニューから「バー」タイルを選択し、できるだけ多くのデータをリクエストするようにします。
図3:履歴データのリクエスト
下のメニューでバーの書き出しを選択すると、Pythonでデータの分析を始めることができます。
図4:履歴データの書き出し
いつものように、まず必要となるライブラリを読み込むことから始めます。
#Load libraries import pandas as pd import numpy as np import matplotlib.pyplot as plt
次に、過去の市場データを読み込みます。MetaTrader 5端末はタブ区切りのcsvファイルを書き出すので、pandas read csvの呼び出しのseparatorパラメータにタブ表記を渡します。
#Read the data csv = pd.read_csv("/content/Volatility 75 Index_M1_20190101_20240131.csv",sep="\t") csv
履歴データを読み込むと、次のようになります。列のタイトルを少し整形し、テクニカル指標も追加する必要があります。
図5:MetaTrader 5端末からの履歴データ
次に列の名前を変更します。
#Format the data csv.rename(columns={"<DATE>":"date","<TIME>":"time","<TICKVOL>":"tickvol","<VOL>":"vol","<SPREAD>":"spread","<OPEN>":"open","<HIGH>":"high","<LOW>":"low","<CLOSE>":"close"},inplace=True) csv.ta.sma(length= 60,append=True) csv.dropna(inplace=True) csv
図6:データの書式設定
これで入力を定義できます。
#Define the inputs predictors = ["open","high","low","close","SMA_60"]
次に、モデルを十分に訓練できるように、データをスケールアップします。
#Scale the data
csv["open"] = csv["open"] /csv.loc[0,"open"]
csv["high"] = csv["high"] /csv.loc[0,"high"]
csv["low"] = csv["low"] /csv.loc[0,"low"]
csv["close"] = csv["close"] /csv.loc[0,"close"]
csv["SMA_60"] = csv["SMA_60"] /csv.loc[0,"SMA_60"]
このタスクを分類問題としてアプローチします。目標はカテゴリです。目標値1は、60本のローソク足で証券価格が上昇したことを意味し、目標値0は、同じ水平線で価格が下落したことを意味します。目標が2つあることに注目してください。一方の目標は終値の変化を監視するもので、もう一方は移動平均の変化を監視するものです。
移動平均の変化についても同じ符号化パターンを使用します。目標値が1であれば、今後60本のローソク足における将来の移動平均値が大きくなることを意味し、逆に目標値が0であれば、今後60本のローソク足における移動平均値が下落することを意味します。
#Define the close csv["Target Close"] = 0 csv["Target MA"] = 0
どのくらい先の未来を予測したいのかを明確にします。
#Define the forecast horizon look_ahead = 60
目標値をエンコードします。
#Set the targets
csv.loc[csv["close"] > csv["close"].shift(-look_ahead) ,"Target Close"] = 0
csv.loc[csv["close"] < csv["close"].shift(-look_ahead) ,"Target Close"] = 1
csv.loc[csv["SMA_60"] > csv["SMA_60"].shift(-look_ahead) ,"Target MA"] = 0
csv.loc[csv["SMA_60"] < csv["SMA_60"].shift(-look_ahead) ,"Target MA"] = 1
csv = csv[:-look_ahead]
唯一の違いは、1回目のテストではモデルが終値の変化を予測しようとするのに対し、2回目のテストでは代わりにテクニカル指標(この例では移動平均)の変化を予測しようとすることです。
目標を定義したら、分析に必要なモデルを読み込みます。
#Get ready from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.linear_model import LogisticRegression from xgboost import XGBClassifier from sklearn.neural_network import MLPClassifier from sklearn.metrics import accuracy_score from sklearn.decomposition import PCA from sklearn.model_selection import TimeSeriesSplit
時系列を分割したものを用意し、検証誤差が少ない場所を評価します。さらに、sklearnの主成分分析(PCA)機能を使用して入力データを変換します。このステップが必要なのは、入力列に相関関係がある可能性があり、モデルの学習プロセスを妨げる可能性があるからです。PCAを実行することで、データセットを入力間の相関がない形に変換し、モデルのパフォーマンスを向上させます。
#Time series split splits = 10 gap = look_ahead models_close = ["Logistic Regression","LDA","XGB","Nerual Net Simple","Nerual Net Large"] models_ma = ["Logistic Regression","LDA","XGB","Nerual Net Simple","Nerual Net Large"] #Prepare the data pca = PCA() csv_reduced = pd.DataFrame(pca.fit_transform(csv.loc[:,predictors]))
それでは、終値の変化を直接予測しようとするニューラルネットワークを使用して、精度のレベルを観察してみましょう。
#Fit the neural network predicting close price model_close = MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(5, 2), random_state=1) model_close.fit(csv_reduced.loc[0:300000,:],csv.loc[0:300000,"Target Close"]) print("Close accuracy: ",accuracy_score(csv.loc[300070:,"Target Close"], model_close.predict(csv_reduced.loc[300070:,:])))
終値の変化を予測したときの精度は49.9%でした。私たちが受け入れてきた複雑さの量を考えれば、これは印象的ではありません。保守と理解がより簡単な、より単純なモデルで同じレベルの精度を得ることができたはずです。さらに、もし49%の確率でしか正しく予測できなかったら、利益のない立場に留まることになります。移動平均指標の変化を予測したときの精度と対比してみましょう。
#Fit the model predicting the moving average model_ma = MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(5, 2), random_state=1) model_ma.fit(csv_reduced.loc[0:300000,:],csv.loc[0:300000,"Target MA"]) print("MA accuracy: ",accuracy_score(csv.loc[300070:,"Target MA"], model_ma.predict(csv_reduced.loc[300070:,:])))
モデルの精度は、移動平均の変化を予測した場合は68.8%であったのに対し、価格の変化を予測した場合は49.9%でした。これは、私たちが使用しているモデリング手法の複雑さに比べれば、許容できるレベルの精度です。
次に、さまざまなモデルを当てはめ、どのモデルが価格の変化を最もよく予測でき、どのモデルが移動平均の変化を最もよく予測できるかを見ていきます。
#Error metrics tscv = TimeSeriesSplit(n_splits=splits,gap=gap) error_close_df = pd.DataFrame(index=np.arange(0,splits),columns=models_close) error_ma_df = pd.DataFrame(index=np.arange(0,splits),columns=models_ma)
まず、選んだ各モデルの終値予測の精度を評価します。
#Training each model to predict changes in the close price for i,(train,test) in enumerate(tscv.split(csv)): model= MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(20, 10), random_state=1) model.fit(csv_reduced.loc[train[0]:train[-1],:],csv.loc[train[0]:train[-1],"Target Close"]) error_close_df.iloc[i,4] = accuracy_score(csv.loc[test[0]:test[-1],"Target Close"],model.predict(csv_reduced.loc[test[0]:test[-1],:]))
図7:価格の変化を分類しようとするさまざまなモデルの精度結果
図8:各モデルのパフォーマンスを視覚化したもの
各モデルが終値を予測する際に記録した最高精度を評価することができます。
for i in enumerate(np.arange(0,error_close_df.shape[1])): print(error_close_df.columns[i[0]]," ", error_close_df.iloc[:,i[0]].max())
LDA 0.5192457894678943
XGB 0.5119523008041539
ニューラルネットシンプル 0.5234700724948571
大型ニューラルネット 0.5186627504042771
見てわかるように、どのモデルも特別な成績は残していません。それらはすべて50%の範囲内でした。ただし、私たちの線形判別分析(LDA)モデルは、このグループの中で最も良い結果を出しました。
その一方で、特定のテクニカル指標の変化を予測する際には、私たちのモデルがより高い精度を発揮することが証明されました。次に、移動平均の変化を予測する際に、どのモデルが最もうまく機能するかを、候補群から決定します。
#Training each model to predict changes in a technical indicator (in this example simple moving average) instead of close price. for i,(train,test) in enumerate(tscv.split(csv)): model= MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(20, 10), random_state=1) model.fit(csv_reduced.loc[train[0]:train[-1],:],csv.loc[train[0]:train[-1],"Target MA"]) error_ma_df.iloc[i,4] = accuracy_score(csv.loc[test[0]:test[-1],"Target MA"],model.predict(csv_reduced.loc[test[0]:test[-1],:]))
図9:移動平均の変化を予測する際のモデルの精度
図10:移動平均の変化を予測する際のモデルの精度を視覚化したもの
各モデルが記録した最高精度を評価します。
for i in enumerate(np.arrange(0,error_ma_df.shape[1])): print(error_ma_df.columns[i[0]]," ", error_ma_df.iloc[:,i[0]].max())
ロジスティック回帰 0.6927054112625546
LDA 0.696401658911147
XGB 0.6932664488520731
ニューラルネットシンプル 0.6947955513019373
大型ニューラルネット 0.6965006655445914
なお、大型ニューラルネットワークが最高精度を達成したとはいえ、その性能は不安定であるため、本番では採用したくありません。このことは、大型ニューラルネットワークのプロットにおいて、2つの点が平均性能を大きく下回っていることからもわかります。したがって、現在のデータセットが与えられた場合、理想的なモデルは単純なロジスティック回帰よりも複雑で、大型ニューラルネットワークよりも複雑ではないはずだということが、結果から観察できます。
移動平均を売買シグナルとして、将来の動きを予測する売買戦略を構築します。私たちが選択するモデルは、より安定していると思われる小型ニューラルネットワークです。
まず、必要なライブラリを読み込みます。
#Import the libraries we need import MetaTrader5 as mt5 import pandas_ta as ta import pandas as pd
次に、取引環境を設定します。
#Trading global variables MARKET_SYMBOL = 'Volatility 75 Index' #This data frame will store the most recent price update last_close = pd.DataFrame() #We may not always enter at the price we want, how much deviation can we tolerate? DEVIATION = 10000 #We will always enter at the minimum volume VOLUME = 0 #How many times the minimum volume should our positions be LOT_MUTLIPLE = 1 #What timeframe are we working on? TIMEFRAME = mt5.TIMEFRAME_M1 #Which model have we decided to work with? neural_network_model= MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(5, 2), random_state=1)
取引したい銘柄で許容される最小出来高を決めましょう。
#Determine the minimum volume for index,symbol in enumerate(symbols): if symbol.name == MARKET_SYMBOL: print(f"{symbol.name} has minimum volume: {symbol.volume_min}") VOLUME = symbol.volume_min * LOT_MULTIPLE
これで、成り行き注文してくれる関数を作ることができます。
# function to send a market order def market_order(symbol, volume, order_type, **kwargs): #Fetching the current bid and ask prices tick = mt5.symbol_info_tick(symbol) #Creating a dictionary to keep track of order direction order_dict = {'buy': 0, 'sell': 1} price_dict = {'buy': tick.ask, 'sell': tick.bid} request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": volume, "type": order_dict[order_type], "price": price_dict[order_type], "deviation": DEVIATION, "magic": 100, "comment": "Indicator Forecast Market Order", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_FOK, } order_result = mt5.order_send(request) print(order_result) return order_result
さらに、成行注文を決済するための別の関数も必要です。
# Closing our order based on ticket id def close_order(ticket): positions = mt5.positions_get() for pos in positions: tick = mt5.symbol_info_tick(pos.symbol) #validating that the order is for this symbol type_dict = {0: 1, 1: 0} # 0 represents buy, 1 represents sell - inverting order_type to close the position price_dict = {0: tick.ask, 1: tick.bid} #bid ask prices if pos.ticket == ticket: request = { "action": mt5.TRADE_ACTION_DEAL, "position": pos.ticket, "symbol": pos.symbol, "volume": pos.volume, "type": type_dict[pos.type], "price": price_dict[pos.type], "deviation": DEVIATION, "magic": 10000, "comment": "Indicator Forecast Market Order", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_FOK, } order_result = mt5.order_send(request) print(order_result) return order_result return 'Ticket does not exist'
さらに、データをリクエストする日付範囲を定義しなければなりません。
#Update our date from and date to date_from = datetime(2024,1,1) date_to = datetime.now()
ブローカーからリクエストされたデータを渡す前に、まずデータを訓練中にモデルが観測したのと同じ形式に前処理しなければなりません。
#Let's create a function to preprocess our data def preprocess(df): #Calculating 60 period Simple Moving Average df.ta.sma(length=60,append=True) #Drop any rows that have missing values df.dropna(axis=0,inplace=True)
次に、ニューラルネットワークから予測を得て、その予測を売買シグナルとして解釈し、ロングまたはショートをおこなうことができなければなりません。
#Get signals from our model def ai_signal(): #Fetch OHLC data df = pd.DataFrame(mt5.copy_rates_range(market_symbol,TIMEFRAME,date_from,date_to)) #Process the data df['time'] = pd.to_datetime(df['time'],unit='s') df['target'] = (df['close'].shift(-1) > df['close']).astype(int) preprocess(df) #Select the last row last_close = df.iloc[-1:,1:] #Remove the target column last_close.pop('target') #Use the last row to generate a forecast from our moving average forecast model #Remember 1 means buy and 0 means sell forecast = neural_network_model.predict(last_close) return forecast[0]最後に、これらすべてを結びつけて取引戦略を作成します。
#Now we define the main body of our trading algorithm if __name__ == '__main__': #We'll use an infinite loop to keep the program running while True: #Fetching model prediction signal = ai_signal() #Decoding model prediction into an action if signal == 1: direction = 'buy' elif signal == 0: direction = 'sell' print(f'AI Forecast: {direction}') #Opening A Buy Trade #But first we need to ensure there are no opposite trades open on the same symbol if direction == 'buy': #Close any sell positions for pos in mt5.positions_get(): if pos.type == 1: #This is an open sell order, and we need to close it close_order(pos.ticket) if not mt5.positions_totoal(): #We have no open positions market_order(MARKET_SYMBOL,VOLUME,direction) #Opening A Sell Trade elif direction == 'sell': #Close any buy positions for pos in mt5.positions_get(): if pos.type == 0: #This is an open buy order, and we need to close it close_order(pos.ticket) if not mt5.positions_get(): #We have no open positions market_order(MARKET_SYMBOL,VOLUME,direction) print('time: ', datetime.now()) print('-------\n') time.sleep(60)
図11:モデルが実際に動いている
理論的説明
筆者の意見では、テクニカル指標の変化を予測する際の精度が向上している理由の1つは、証券価格に影響を与えるすべての変数を観察することはできないという事実にあります。しかし、テクニカル指標の変化を予測する場合、そのテクニカル指標に影響を与えたすべての入力を完全に把握することができます。どんなテクニカル指標でも、正確な計算式がわかっていることを思い出してください。
図12:すべてのテクニカル指標の数学的説明はわかっていますが、終値の数学的公式はありません。
例えば、Money Flow Multiplier (MFM)テクニカル指標は、上記の式で計算されます。したがって、MFMの変化を予測したいのであれば、その計算式の構成要素である終値、安値、高値があればよくなります。
一方、終値を予測する場合、どの入力が終値に影響を与えるかを示す具体的な計算式はありません。この結果、精度が低下することがよくありますが、これは現在の入力セットが有益でないか、あるいは貧弱な入力を選ぶことによってノイズが多すぎることを示唆しています。
基本的に言えば、機械学習の目的は、一連の入力によって決定される目標を見つけることです。テクニカル指標を目標として使用する場合、基本的に、テクニカル指標は始値、終値、安値、高値に影響されると述べていることになります。これは事実です。しかし、アルゴリズム開発者である私たちは、しばしばその逆の使い方をします。私たちは価格データとテクニカル指標を組み合わせて価格を予測しますが、テクニカル指標が価格に影響を与えるということを暗に示しています。
基礎となる関数が知られていない目標を学習しようとすると、いわゆる擬似相関の被害に遭う可能性があります。これについては前回のディスカッションで詳しく説明しました。簡単に言えば、現実には存在しない関係をモデルが学習することが可能なのです。さらに、この欠陥は、検証時に誤認させるような低いエラー率によって覆い隠され、モデルが十分に学習したかのように見えるが、実際には現実世界について何も学習していないことがあります。
擬似相関とは何かを説明するために、あなたと私が丘を歩いていて、地平線の向こうにぼんやりとした形が見えたとしましょう。あまりに遠すぎて何なのかはわかりませんが、自分が見たものに基づいて、私は「あそこに犬がいる」と叫びます。さて、到着して茂みを見つけると、茂みの向こうに犬がいるのです。
図13:犬を見たのか
問題はもうお分かりでしょうか。もちろん、この勝利は私の完璧な2.0の視力の証拠だと主張したいところですが、根本的に言えば、私がこの発言をしたとき私たちが立っていた場所から犬を見ることは不可能だったのです。私たちが立っていた場所からは遠すぎたし、犬の様子もぼんやりとしか見えなかったのです。
山の頂上で目にした入力と、私がたどり着いた結論の間には何の関係もなかったのです。つまり、入力データと出力データは互いに独立していたのです。あるモデルが、出力データとは何の関係もない入力データを見ているにもかかわらず、正しい答えを出すことができた場合、それを擬似相関と呼びます。擬似相関は常に発生します。
何が証券価格に影響を与えるかを示す技術的な公式がないため、終値という目標に影響を与えない入力を使用して擬似相関をおこないがちです。相関が擬似でないことを証明しようとするのは難しいかもしれません。入力と既知の関係を持つ目標を使用する方が簡単です。
結論
本稿では、終値を直接予測することをやめ、代わりにテクニカル指標の変化を予測することを支持する可能性がある理由を示しました。移動平均線よりも高い精度で予測できるテクニカル指標が他にあるかどうか、さらなる研究が必要です。ただし、移動平均の予測精度は比較的高いものの、移動平均の変化と価格の変化にはまだタイムラグがあることにも注意が必要です。
言い換えれば、価格が上昇している間に移動平均が下落している可能性があるということです。しかし、MQL5コミュニティが一丸となってこのアルゴリズムの改善に取り組めば、最終的に新たなレベルの精度に到達できると確信しています。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/14936




- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索