
PythonとMQL5を使用した特徴量エンジニアリング(第1回):長期AIモデルの移動平均の予測
AIをあらゆるタスクに適用する際、モデルには現実世界に関する有益な情報をできる限り多く提供することが重要です。市場のさまざまな特性をAIモデルに伝えるには、入力データを適切に加工・変換する必要があります。このプロセスは特徴量エンジニアリングと呼ばれます。本連載では、市場データをどのように変換すればモデルの誤差を減らせるのかを詳しく説明していきます。今回は、移動平均を活用してAIモデルの予測範囲を拡大し、戦略の全体的な有効性を理解しながら制御する方法に焦点を当てます。
戦略の概要
前回の記事では、AIモデルが将来の価格レベルよりも移動平均の値を予測しやすいことを示す証拠を紹介しました。しかし、この結論が確かなものかどうかを検証するために、私は200を超える市場銘柄に対して、まったく同じ2つのAIモデルを訓練し、「価格予測」と「移動平均予測」の精度を比較しました。その結果、価格を直接予測する場合、移動平均を予測する場合と比べて平均34%も精度が低下することが分かりました。
具体的には、移動平均の予測精度は平均70%であるのに対し、価格予測の精度は52%にとどまりました。移動平均は、設定した期間によって価格の動きと必ずしも一致しないことはよく知られています。例えば、価格が20本以上のローソク足で下落していても、移動平均が同じ期間に上昇していることがあります。このような乖離があると、たとえ移動平均の未来の動きを正しく予測できても、実際の価格とは異なる方向に動く可能性があり、取引においては望ましくありません。しかし驚くべきことに、この乖離率はどの市場でも約31%で一定であり、さらに乖離を予測する能力は平均68%に達していました。
また、乖離の予測精度の分散は0.000041、乖離の発生自体の分散は0.000386という結果が得られました。これは、モデルが高い信頼性を持って自己修正できることを示しています。長期的な取引戦略にAIを活用しようと考えているトレーダーは、より長い時間軸でこのアプローチを検討する価値があるでしょう。現在の議論はM1(1分足)に限定していますが、これはすべての297市場において十分なデータを確保し、公平な比較をおこなうためです。
移動平均が価格よりも予測しやすい理由はいくつか考えられます。そのひとつは、移動平均の予測は、価格の予測よりも線形回帰の概念に適合しているという点です。線形回帰では、データが複数の入力の線形結合(加算)であることが前提となっています。移動平均は、過去の価格データを一定期間加算・平均したものなので、この線形仮定が成り立ちます。一方で、価格そのものは単純な数値の合計ではなく、複雑な市場要因が絡み合った結果として決定されるため、予測が難しくなります。
はじめに
まず、Pythonの科学計算用の標準ライブラリをインポートするところから始めましょう。
#Load the libraries we need import pandas as pd import numpy as np import MetaTrader5 as mt5 from sklearn.model_selection import TimeSeriesSplit,cross_val_score from sklearn.linear_model import LogisticRegression,LinearRegression import matplotlib.pyplot as plt
MetaTrader 5端末を初期化しましょう。
#Initialize the terminal
mt5.initialize()
利用できる銘柄はいくつあるでしょうか。
#The total number of symbols we have print(f"Total Symbols Available: ",mt5.symbols_total())
すべての銘柄の名前を取得します。
#Get the names of all pairs symbols = mt5.symbols_get() idx = [s.name for s in symbols]
すべての銘柄の精度レベルを保存するデータフレームを作成します。
global_params = pd.DataFrame(index=idx,columns=["OHLC Error","MAR Error","Noise Levels","Divergence Error"]) global_params
図1:端末のすべての市場における精度レベルを保存するデータフレーム
時系列分割オブジェクトを定義します。
#Define the time series split object tscv = TimeSeriesSplit(n_splits=5,gap=10)
すべての銘柄の精度を測定します。
#Iterate over all symbols for i in np.arange(global_params.dropna().shape[0],len(idx)): #Fetch M1 Data data = pd.DataFrame(mt5.copy_rates_from_pos(cols[i],mt5.TIMEFRAME_M1,0,50000)) data.rename(columns={"open":"Open","high":"High","low":"Low","close":"Close"},inplace=True) #Define our period period = 10 #Add the classical target data.loc[data["Close"].shift(-period) > data["Close"],"OHLC Target"] = 1 #Calculate the returns data.loc[:,["Open","High","Low","Close"]] = data.loc[:,["Open","High","Low","Close"]].diff(period) data["RMA"] = data["Close"].rolling(period).mean() #Calculate our new target data.dropna(inplace=True) data.reset_index(inplace=True,drop=True) data.loc[data["RMA"].shift(-period) > data["RMA"],"New Target"] = 1 data = data.iloc[0:-period,:] #Calculate the divergence target data.loc[data["OHLC Target"] != data["New Target"],"Divergence Target"] = 1 #Noise ratio global_params.iloc[i,2] = data.loc[data["New Target"] != data["OHLC Target"]].shape[0] / data.shape[0] #Test our accuracy predicting the future close price score = cross_val_score(LogisticRegression(),data.loc[:,["Open","High","Low","Close"]],data["OHLC Target"],cv=tscv) global_params.iloc[i,0] = score.mean() #Test our accuracy predicting the moving average of future returns score = cross_val_score(LogisticRegression(),data.loc[:,["Open","Close","RMA"]],data["New Target"],cv=tscv) global_params.iloc[i,1] = score.mean() #Test our accuracy predicting the future divergence between price and its moving average score = cross_val_score(LogisticRegression(),data.loc[:,["Open","Close","RMA"]],data["Divergence Target"],cv=tscv) global_params.iloc[i,3] = score.mean() print(f"{((i/len(idx)) * 100)}% complete") #We are done print("Done")
Done
結果の分析
市場データを取得し、2つのターゲットについてモデルを評価したので、すべての市場におけるテスト結果をまとめてみましょう。まず、将来の終値の変化をどれだけ正確に予測できたかを整理します。下の図2は、将来の終値の変化予測に関する結果を示しています。赤い水平線は50%の精度を示す基準値であり、青い水平線は本手法を用いた場合の平均精度を表しています。ご覧のとおり、平均精度は50%の閾値と大きくは変わらず、この結果はあまり心強いとは言えません。
しかし、公平に評価するために付け加えると、特定の市場では平均を大きく上回り、65%以上の精度で予測できるケースもあることが分かりました。これは非常に興味深い結果ですが、これらの精度が統計的に意味のあるものなのか、それとも単なる偶然なのかを見極めるために、さらなる検証が必要です。
global_params.iloc[:,0].plot() plt.title("OHLC Accuracy") plt.xlabel("Market") plt.ylabel("5-fold Accuracy %") plt.axhline(global_params.iloc[:,0].mean(),linestyle='--') plt.axhline(0.5,linestyle='--',color='red')
図2:価格予測の平均精度
次に、移動平均の変化を予測する精度に注目してみましょう。下の図3は、そのデータをまとめたものです。再度、赤い線は50%の閾値を表し、金色の線は価格レベルの変化を予測する平均精度、青い線は移動平均の変化を予測する平均精度を示しています。単に「私たちのモデルは移動平均の予測に優れている」と言うのは、控えめすぎる表現です。価格予測よりも特定の指標を予測するほうがモデルにとって有利だというのは、もはや議論の余地はなく、事実として受け入れるべきことだと思います。
global_params.iloc[:,1].plot() plt.title("Moving Average Returns Accuracy") plt.xlabel("Market") plt.ylabel("5-fold Accuracy %") plt.axhline(global_params.iloc[:,1].mean(),linestyle='--') plt.axhline(global_params.iloc[:,0].mean(),linestyle='--',color='orange') plt.axhline(0.5,linestyle='--',color='red')
図3:移動平均の変化を予測する精度
次に、価格と移動平均が乖離する速度を観察してみましょう。乖離レベルが50%に近い場合、価格と移動平均が同じ方向に動くのか、それとも反対方向に動くのかを合理的に予測できないため、好ましくありません。幸いなことに、私たちが評価したすべての市場で騒音レベルはほぼ一定でした。騒音レベルは35%から30%の間で変動していました。
global_params.iloc[:,2].plot() plt.title("Noise Level") plt.xlabel("Market") plt.ylabel("Percentage of Divergence:Price And Moving Average") plt.axhline(global_params.iloc[:,2].mean(),linestyle='--')
図4:すべての市場における騒音レベルの視覚化
もし2つの変数の比率がほぼ一定であれば、それはモデル化できる関係が存在する可能性があることを示唆しています。 価格と移動平均の乖離をどれだけ正確に予測できるかを見てみましょう。私たちのアプローチはシンプルです。もしモデルが移動平均の下落を予測した場合、価格が同じ方向に動くのか、それとも移動平均から乖離するのかを合理的に予測できるかを確認します。結果として、私たちは平均して70%近い高い精度で乖離を予測できることがわかりました。
global_params.iloc[:,3].plot() plt.title("Divergence Accuracy") plt.xlabel("Market") plt.ylabel("5-fold Accuracy %") plt.axhline(global_params.iloc[:,3].mean(),linestyle='--')
図5:価格と移動平均の乖離を予測する精度
調査結果を表形式でまとめることもできます。これにより、市場リターンとリターンの移動平均における精度レベルを簡単に比較できます。移動平均が価格に対して「遅れ」を取る場合でも、反転の予測精度は価格自体の予測精度よりも大幅に高いことに注意してください。
メトリック | 精度 |
---|---|
リターンの誤差 | 0.525353 |
リターンの移動平均誤差 | 0.705468 |
騒音レベル | 0.317187 |
発散誤差 | 0.682069 |
どの市場でモデルが最も優れたパフォーマンスを発揮したかを見てみましょう。
global_params.sort_values("MAR Error",ascending=False)
図6:最も好調な市場
最高のパフォーマンスを発揮する市場に向けた最適化
次に、パフォーマンスが最も高い市場の1つに合わせて移動平均インジケーターを設計し、カスタマイズしてみましょう。また、新しく設計した特徴量を従来の特徴量と視覚的に比較します。まずは、選択した市場を指定します。
symbol = "AUDJPY"
端末まで確実に到着できることを確認してください。
#Reach the terminal
mt5.initialize()
次に、市場データを取得します。
data = pd.DataFrame(mt5.copy_rates_from_pos(symbol,mt5.TIMEFRAME_D1,365*2,5000))
必要なライブラリをインポートします。
#Standard libraries import seaborn as sns from mpl_toolkits.mplot3d import Axes3D from sklearn.linear_model import LinearRegression from sklearn.neural_network import MLPRegressor from sklearn.metrics import mean_squared_error from sklearn.model_selection import cross_val_score,TimeSeriesSplit
期間計算と予測期間の開始点と終了点を定義します。両方の入力が同じ次元であることを確認してください。そうでない場合、コードが壊れます。
#Define the input range x_min , x_max = 2,100 #Look ahead y_min , y_max = 2,100 #Period
計算が詳細かつ安価に得られるように、入力ドメインを5ずつサンプリングします。
#Sample input range uniformly x_axis = np.arange(x_min,x_max,2) #Look ahead y_axis = np.arange(y_min,y_max,2) #Period
x_axisとy_axisを使用してメッシュ グリッドを作成します。メッシュグリッドは、評価する予測期間の可能なすべての組み合わせを定義する2つの2次元配列で構成されています。
#Create a meshgrid
x , y = np.meshgrid(x_axis,y_axis)
次に、市場データを取得して評価するためのラベルを付ける関数が必要です。
def clean_data(look_ahead,period): #Fetch the data from our terminal and clean it up data = pd.DataFrame(mt5.copy_rates_from_pos('AUDJPY',mt5.TIMEFRAME_D1,365*2,5000)) data['time'] = pd.to_datetime(data['time'],unit='s') data['MA'] = data['close'].rolling(period).mean() #Transform the data #Target data['Target'] = data['MA'].shift(-look_ahead) - data['MA'] #Change in price data['close'] = data['close'] - data['close'].shift(period) #Change in MA data['MA'] = data['MA'] - data['MA'].shift(period) data.dropna(inplace=True) data.reset_index(drop=True,inplace=True) return(data)
次の関数は、AIモデルに対して5段階の交差検証を実行します。
#Evaluate the objective function def evaluate(look_ahead,period): #Define the model model = LinearRegression() #Define our time series split tscv = TimeSeriesSplit(n_splits=5,gap=look_ahead) temp = clean_data(look_ahead,period) score = np.mean(cross_val_score(model,temp.loc[:,["Open","High","Low","Close"]],temp["Target"],cv=tscv)) return(score)
最後に、目的関数です。私たちの目的関数は、評価したい新しい設定におけるモデルの5倍交差検証誤差です。モデルが予測すべき将来の最適な距離を見つけること、さらに価格の変化を計算するために使用する期間を見つけることが目的であることを思い出してください。
#Define the objective def objective(x,y): #Define the output matrix results = np.zeros([x.shape[0],y.shape[0]]) #Fill in the output matrix for i in np.arange(0,x.shape[0]): #Select the rows look_ahead = x[i] period = y[i] for j in np.arange(0,y.shape[0]): results[i,j] = evaluate(look_ahead[j],period[j]) return(results)
価格レベルの変化を直接予測する際に、モデルと市場の関係を評価します。図7は、モデルと価格の変化との関係を示しており、図8はモデルと移動平均の変化との関係を示しています。両方のグラフにおける白い点は、最も低い誤差をもたらした入力の組み合わせを表しています。
res = objective(x,y) res = np.abs(res)
AUDJPYの日次リターンを予測するモデルの最高パフォーマンスをプロットします。データから、将来の価格変動を予測する際、最も良い予測結果は1ステップ先の未来に限られることが明らかになっています。しかし、人間のトレーダーは取引をおこなう際に単に1ステップ先を見るだけではありません。そのため、市場のリターンを直接予測するアプローチは限界があり、モデルが次のローソク足に固執してしまう結果となります。
plt.contourf(x,y,res,100,cmap="jet") plt.plot(x_axis[res.min(axis=0).argmin()],y_axis[res.min(axis=1).argmin()],'.',color='white') plt.ylabel("Differencing Period") plt.xlabel("Forecast Horizon") plt.title("Linear Regression Accuracy Forecasting AUDJPY Daily Return")
図7:将来の価格水準を予測するモデル能力の視覚化
価格の変化ではなく移動平均の変化を予測し始めると、最適な予測範囲が右にシフトすることがわかります。下の図8は、移動平均の変化を予測することで、価格の変化を予測する場合の1ステップ先ではなく、22ステップ先まで確実に予測できるようになったことを示しています。
plt.contourf(x,y,res,100,cmap="jet") plt.plot(x_axis[res.min(axis=0).argmin()],y_axis[res.min(axis=1).argmin()],'.',color='white') plt.ylabel("Differencing Period") plt.xlabel("Forecast Horizon") plt.title("Linear Regression Accuracy Forecasting AUDJPY Daily Moving Average Return")
図8:将来の移動平均レベルを予測するモデル能力の視覚化
さらに印象的なのは、最適なポイントでは、2つのターゲットにおける誤差レベルが同一であったことです。言い換えれば、モデルが価格の1ステップ先の変化を予測するのと同じくらい簡単に、移動平均の40ステップ先の変化を予測できるということです。したがって、移動平均を予測することで、予測の誤差を増やすことなく、より広い範囲の予測が可能になります。
テスト結果を3Dで視覚化すると、2つのターゲットの違いが明確にわかります。下の図9は、価格の変化とモデルの予測パラメータとの関係を示しています。データから明らかな傾向が見られ、将来を予測するほど結果が悪化することがわかります。したがって、このようにAIモデルを設計すると、モデルは「近視眼的」になり、20以上のステップ間隔を合理的に予測することができなくなります。
図10は、移動平均の変化を予測する際のモデルと誤差の関係を示したものです。表面プロットは望ましい特性を示しており、予測期間を長くし、移動平均の変化を計算する期間を増やすと、誤差率がスムーズに最小値に達した後、再び上昇し始めることがはっきりとわかります。この視覚的なデモンストレーションは、価格を予測するよりも移動平均を予測する方がモデルにとってどれほど簡単であるかを示しています。
#Create a surface plot fig , ax = plt.subplots(subplot_kw={"projection":"3d"}) fig.set_size_inches(8,8) ax.plot_surface(x,y,optimal_nn_res,cmap="jet")
図9:モデルとAUDJPYの日次価格の変化との関係の視覚化
図10:モデルとAUDJPYペアの移動平均値の変化との関係の視覚化
非線形変換:ウェーブレット雑音除去
これまではデータに線形変換のみを適用してきましたが、さらに深く掘り下げて、モデルの入力データに非線形変換を適用することもできます。特徴量エンジニアリングは、時には単なる試行錯誤のプロセスです。必ずしも良い結果が得られるわけではありません。そのため、これらの変換はアドホックに適用され、特定の瞬間に「最適な」変換を示す明確な公式はありません。
ウェーブレット変換は、データの周波数と時間を同時に表現するための数学的なツールです。これは、信号や画像処理のタスクで、処理しようとする信号から雑音を分離するためによく使用されます。変換を適用すると、データは周波数領域に変換されます。データ内の雑音は、変換によって特定された小さな周波数成分から発生すると考えられています。特定の閾値を下回るすべての値は、2つの方法のいずれかで0に縮小されます。これにより、元のデータのスパースな表現が得られます。
ウェーブレット雑音除去には、他の一般的な手法、例えば高速フーリエ変換(FFT)に比べていくつかの利点があります。フーリエ変換について知らない読者のために説明すると、フーリエ変換は任意の信号を正弦波と余弦波の和として表現します。しかし、フーリエ変換では高周波成分をフィルタリングしてしまいます。これは、特に信号が高周波領域にある場合には必ずしも望ましくありません。信号が高周波領域か低周波領域か不確かなため、私たちは教師なしで柔軟にこのタスクを処理できる変換が必要です。ウェーブレット変換は、データ内の信号を保持しつつ、できるだけ多くの雑音を除去します。
この処理を実行するには、scikit-learnの画像処理モジュールと、その依存ライブラリであるPyWaveletsがインストールされていることを確認してください。MQL5で完全な取引アプリケーションを作成したい読者にとって、変換を一から実装してデバッグするのはかなり手間がかかるかもしれません。そのため、私たちはそれなしで進めた方が効率的です。また、Pythonライブラリを使用して端末と連携させたい読者にとっては、この変換は有用なツールとして活用できるでしょう。
検証精度の変化を比較することで、変換がモデルに有効かどうかを確認できます。そして、それは確かに有効です。変換はモデルの入力にのみ適用され、ターゲットには適用されないことに注意してください。ターゲットはそのまま保持されます。実際に検証精度が低下したことがわかります。私たちはハード閾値を使ったウェーブレット変換を使用しており、その結果として雑音係数がすべて0に設定されます。逆に、ソフト閾値を使用することで雑音係数を0に近づけますが、正確に0にするわけではありません。
#Benchmark Score np.mean(cross_val_score(LinearRegression(),data.loc[:,["MA"]],data["Target"]))
#Wavelet denoising data["Denoised"] = denoise_wavelet( data["MA"], method='BayesShrink', mode='hard', rescale_sigma=True, wavelet_levels = 3, wavelet='sym5' ) np.mean(cross_val_score(LinearRegression(),np.sqrt(np.log(data.loc[:,["Denoised"]])),data["Target"]))
カスタマイズされたAIモデルの構築
これで、将来の予測範囲と最適な移動平均期間に関する理想的なパラメータが明確になりました。次に、AIモデルの学習に使用する市場データを、MetaTrader 5端末から直接取得します。こうすることで、実際の取引環境と同じインジケーター値を使用し、リアルな取引体験を可能な限り再現することができます。
スクリプト内の移動平均期間は、先ほど算出した理想的な値と一致させます。さらに、AI取引ボットの動作を安定させるため、RSI(相対力指数)のデータも端末から取得します。1つの指標だけでなく、2つの異なる指標の予測を組み合わせることで、AIモデルの長期的な安定性が向上する可能性があります。
//+------------------------------------------------------------------+ //| 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 ma_handler,rsi_handler; double ma_reading[],rsi_reading[]; //+------------------------------------------------------------------+ //| On start function | //+------------------------------------------------------------------+ void OnStart() { //--- Load indicator ma_handler = iMA(Symbol(),PERIOD_CURRENT,40,0,MODE_SMA,PRICE_CLOSE); rsi_handler = iRSI(Symbol(),PERIOD_CURRENT,30,PRICE_CLOSE); //--- Load the indicator values CopyBuffer(ma_handler,0,0,size,ma_reading); CopyBuffer(rsi_handler,0,0,size,rsi_reading); ArraySetAsSeries(ma_reading,true); ArraySetAsSeries(rsi_reading,true); //--- File name string file_name = "Market Data " + Symbol() +" MA RSI " + " As Series.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","MA","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), ma_reading[i], rsi_reading[i] ); } } //--- Close the file FileClose(file_handle); } //+------------------------------------------------------------------+
スクリプトを作成したので、目的の市場にドラッグアンド ドロップするだけで、市場データの操作を開始できます。バックテストの信頼性を高めるために、生成したCSVファイルから直近2年間の市場データを除外しました。これにより、2023年から2024年にかけて戦略をバックテストする際、AIモデルが未経験のデータに対してどのように機能するかを、より正確に評価できます。
#Read in the data data = pd.read_csv("Market Data AUDJPY MA RSI As Series.csv") #Let's drop the last two years of data. We'll use that to validate our model in the back test data = data.iloc[365:-(365 * 2),:] data
図11:2023年から2024年の期間を除く22年間の市場データを使用したモデルの訓練
次に、機械学習のためにデータにラベルを付与します。私たちの目標は、テクニカル指標の変化に応じた価格の動きを、モデルが学習できるようにすることです。そのために、入力データを変換し、各インジケーターの現在の状態を示す形式にします。たとえば、RSIインジケーターには以下の3つの状態があります。
- 70を超える
- 30未満
- 70未満で30を超える
#MA States data["MA 1"] = 0 data["MA 2"] = 0 data.loc[data["MA"] > data["MA"].shift(40),"MA 1"] = 1 data.loc[data["MA"] <= data["MA"].shift(40),"MA 2"] = 1 #RSI States data["RSI 1"] = 0 data["RSI 2"] = 0 data["RSI 3"] = 0 data.loc[data["RSI"] < 30,"RSI 1"] = 1 data.loc[data["RSI"] > 70,"RSI 2"] = 1 data.loc[(data["RSI"] >= 30) & (data["RSI"] <= 70),"RSI 3"] = 1 #Target data["Target"] = data["Close"].shift(-22) - data["Close"] data["MA Target"] = data["MA"].shift(-22) - data["MA"] #Clean up the data data = data.dropna() data = data.iloc[40:,:] data = data.reset_index(drop=True)
これで精度の測定を開始できます。
from sklearn.linear_model import Ridge from sklearn.model_selection import TimeSeriesSplit,cross_val_score
これらの変換を適用することで、RSIが指定した3つのゾーンを通過する際の平均的な価格変化を観察できるようになります。線形モデルの係数は、それぞれのRSIゾーンにおける平均的な価格変動として解釈できます。興味深いことに、この分析結果は、従来の指標の使い方とは異なる示唆を与えることもあります。たとえば、Ridgeモデルは、RSIの読み取り値が70を超えると価格レベルが下がる傾向があり、RSIの読み取り値が70 未満の場合は将来の価格レベルが上がる傾向があることを学習しました。
#Our model can suggest optimal ways of using the RSI indicator #Our model has learned that on average price tends to fall the RSI reading is less than 30 and increases otherwises model = Ridge() model.fit(data.loc[:,["RSI 1","RSI 2","RSI 3"]] , data["Target"]) model.coef_
Ridgeモデルは、RSIの現在の状態に基づいてのみ、将来の価格を適切に予測できます。
#RSI state np.mean(cross_val_score(Ridge(),data.loc[:,["RSI 1","RSI 2","RSI 3"]] , data["Target"],cv=tscv))
同様に、私たちのモデルも移動平均指標の変化から独自の取引ルールを学習しました。モデルの最初の係数は負であり、移動平均が40 本を超えて上昇すると、移動平均は下降する傾向があることを意味します。そして2番目の係数は正です。したがって、端末で取得した履歴データから、40 期間のAUDJPY日次移動平均が40 ローソク足を超えて下落すると、その後は上昇する傾向があります。私たちのモデルはデータから平均回帰戦略を学習しました。
#Our model can suggest optimal ways of using the RSI indicator #Our model has learned that on average price tends to fall the RSI reading is less than 30 and increases otherwises model = Ridge() model.fit(data.loc[:,["MA 1","MA 2"]] , data["Target"]) model.coef_
移動平均インジケーターの現在の状態を与えると、モデルのパフォーマンスはさらに向上します。
#MA state np.mean(cross_val_score(Ridge(),data.loc[:,["MA 1","MA 2"]] , data["Target"],cv=tscv))
ONNXへの変換
移動平均予測に最適な入力パラメータが見つかったので、モデルをONNX形式に変換する準備をしましょう。Open Neural Network Exchange (ONNX)を使用すると、言語に依存しないフレームワークで機械学習モデルを構築できます。ONNXプロトコルは、機械学習モデルのユニバーサル標準表現を作成するためのオープンソースの取り組みであり、ONNX APIを完全に採用している限り、どの言語でも機械学習モデルを構築および展開できます。
まず、見つけた最適な入力に従って、必要なデータを取得しましょう。
#Fetch clean data new_data = clean_data(140,130)
必要なライブラリをインポートします。
import onnx from skl2onnx import convert_sklearn from skl2onnx.common.data_types import FloatTensorType
保有するすべてのデータにRSIモデルを適合させます。
#First we will export the RSI model rsi_model = Ridge() rsi_model.fit(data.loc[:,['RSI 1','RSI 2','RSI 3']],data.loc[:,'Target'])
保有するすべてのデータに移動平均モデルを適合させます。
#Finally we will export the MA model ma_model = Ridge() ma_model.fit(data.loc[:,['MA 1','MA 2']],data.loc[:,'MA Target'])
モデルの入力形状を定義し、ディスクに保存します。
initial_types = [('float_input', FloatTensorType([1, 3]))] onnx.save(convert_sklearn(rsi_model,initial_types=initial_types,target_opset=12),"AUDJPY D1 RSI AI F22 P40.onnx") initial_types = [('float_input', FloatTensorType([1, 2]))] onnx.save(convert_sklearn(ma_model,initial_types=initial_types,target_opset=12),"AUDJPY D1 MA AI F22 P40.onnx")
MQL5での実装
これで、MQL5で取引アプリケーションの構築を開始する準備が整いました。移動平均に関する新たな知見を活用し、ポジションのエントリーとエグジットを自動化できるアプリケーションを開発します。さらに、取引シグナルを過度にアグレッシブにしないよう、より反応の遅いインジケーターを活用し、モデルの判断を補助します。これは、人間のトレーダーが常にポジションを持とうとするわけではない、という取引スタイルを模倣するためです。
また、健全なリスク管理を実現するために、トレーリングストップロスを導入します。具体的には、Average True Range (ATR)インジケーターを活用し、ストップロスとテイクプロフィットの水準を動的に調整します。私たちの戦略の基盤は、移動平均チャネルです。
この戦略では、40ステップ先の移動平均を予測し、エントリー前の確認材料とします。また、モデルの汎用性を検証するため、学習時に使用しなかった過去1年分の市場データでバックテストを実施しました。
まず、先ほど作成したONNXファイルをインポートすることから始めます。
//+------------------------------------------------------------------+ //| GBPUSD AI.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" //+------------------------------------------------------------------+ //| Load our resources | //+------------------------------------------------------------------+ #resource "\\Files\\AUDJPY D1 MA AI F22 P40.onnx" as const uchar onnx_buffer[]; #resource "\\Files\\AUDJPY D1 RSI AI F22 P40.onnx" as const uchar rsi_onnx_buffer[];
次に、必要なライブラリをインポートします。
//+------------------------------------------------------------------+ //| Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade; #include <Trade\OrderInfo.mqh> class COrderInfo;
ここで、必要なグローバル変数を定義します。
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ long onnx_model; int ma_handler,state; double bid,ask,vol; vectorf model_forecast = vectorf::Zeros(1); vectorf rsi_model_output = vectorf::Zeros(1); double min_volume,max_volume_increase, volume_step, buy_stop_loss, sell_stop_loss,atr_stop,risk_equity; double take_profit = 0; double close_price[3],atr_reading[],ma_buffer[]; long min_distance,login; int atr,close_average,ticket_1,ticket_2; bool authorized = false; double margin,lot_step; string currency,server; bool all_closed =true; int rsi_handler; long rsi_onnx_model; double indicator_reading[]; ENUM_ACCOUNT_TRADE_MODE account_type; const double stop_percent = 1;
入力変数を定義しましょう。
//+------------------------------------------------------------------+ //| Technical indicators | //+------------------------------------------------------------------+ input group "Money Management" input int lot_multiple = 10; // How big should the lot size be? input double profit_target = 0; // Profit Target input double loss_target = 0; // Max Loss Allowed input group "Money Management" const int atr_period = 200; //ATR Period input double atr_multiple =2.5; //ATR Multiple
ここで、取引アプリケーションを初期化する方法を正確に定義する必要があります。まず、ユーザーがアプリケーションに取引の許可を与えているかどうかを確認します。続行する許可が得られたら、テクニカル指標とONNXモデルを読み込みます。
int OnInit() { //Authorization if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { Comment("Press Ctrl + E To Give The Robot Permission To Trade And Reload The Program"); return(INIT_FAILED); } else if(!MQLInfoInteger(MQL_TRADE_ALLOWED)) { Comment("Reload The Program And Make Sure You Clicked Allow Algo Trading"); return(INIT_FAILED); } else { Comment("This License is Genuine"); setup(); } //Everything was okay return(INIT_SUCCEEDED); }
取引アプリケーションが使用されなくなったときはいつでも、エンドユーザーがアプリケーションを快適に使用できるように、使用しなくなったリソースを解放する必要があります。
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { OnnxRelease(onnx_model); OnnxRelease(rsi_onnx_model); IndicatorRelease(atr) }
価格の最新の更新を受け取るたびに、変数を更新し、新たな取引機会があるかを確認します。また、すでにポジションを保有している場合は、トレーリングストップロスを更新します。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //Update technical data update(); if(PositionsTotal() == 0) { check_setup(); } if(PositionsTotal() > 0) { check_atr_stop(); } }
モデルから予測を得るには、RSIと移動平均インジケーターの現在の状態を定義する必要があります。
//+------------------------------------------------------------------+ //| Get a prediction from our model | //+------------------------------------------------------------------+ int model_predict(void) { //MA Forecast vectorf model_inputs = vectorf::Zeros(2); vectorf rsi_model_inputs = vectorf::Zeros(3); CopyBuffer(ma_handler,0,0,40,ma_buffer); if(ma_buffer[0] > ma_buffer[39]) { model_inputs[0] = 1; model_inputs[1] = 0; } else if(ma_buffer[0] < ma_buffer[39]) { model_inputs[1] = 1; model_inputs[0] = 0; } //RSI Forecast CopyBuffer(rsi_handler,0,0,1,indicator_reading); if(indicator_reading[0] < 30) { rsi_model_inputs[0] = 1; rsi_model_inputs[1] = 0; rsi_model_inputs[2] = 0; } else if(indicator_reading[0] >70) { rsi_model_inputs[0] = 0; rsi_model_inputs[1] = 1; rsi_model_inputs[2] = 0; } else { rsi_model_inputs[0] = 0; rsi_model_inputs[1] = 0; rsi_model_inputs[2] = 1; } //Model predictions OnnxRun(onnx_model,ONNX_DEFAULT,model_inputs,model_forecast); OnnxRun(rsi_onnx_model,ONNX_DEFAULT,rsi_model_inputs,rsi_model_output); //Evaluate model output for buy setup if(((rsi_model_output[0] > 0) && (model_forecast[0] > 0))) { //AI Models forecast Comment("AI Forecast: UP"); return(1); } //Evaluate model output for a sell setup if((rsi_model_output[0] < 0) && (model_forecast[0] < 0)) { Comment("AI Forecast: DOWN"); return(-1); } //Otherwise no position was found return(0); }
グローバル変数を更新します。すべてのコードをOnTick()ハンドラで直接実行するのではなく、1回の関数呼び出しでこれらの更新を実行する方がクリーンです。
//+------------------------------------------------------------------+ //| Update our market data | //+------------------------------------------------------------------+ void update(void) { ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); buy_stop_loss = 0; sell_stop_loss = 0; static datetime time_stamp; datetime time = iTime(_Symbol,PERIOD_CURRENT,0); check_price(3); CopyBuffer(atr,0,0,1,atr_reading); CopyBuffer(ma_handler,0,0,1,ma_buffer); ArraySetAsSeries(atr_reading,true); atr_stop = ((min_volume + atr_reading[0]) * atr_multiple); //On Every Candle if(time_stamp != time) { //Mark the candle time_stamp = time; OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,min_volume,ask,margin); } }
テクニカル指標、口座および市場情報、その他の類似データなど、必要なリソースを読み込みます。
//+------------------------------------------------------------------+ //| Load resources | //+------------------------------------------------------------------+ bool setup(void) { //Account Info currency = AccountInfoString(ACCOUNT_CURRENCY); server = AccountInfoString(ACCOUNT_SERVER); login = AccountInfoInteger(ACCOUNT_LOGIN); //Indicators atr = iATR(_Symbol,PERIOD_CURRENT,atr_period); //Setup technical indicators ma_handler =iMA(Symbol(),PERIOD_CURRENT,40,0,MODE_SMA,PRICE_LOW); vol = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN) * lot_multiple; rsi_handler = iRSI(Symbol(),PERIOD_CURRENT,30,PRICE_CLOSE); //Market Information min_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); max_volume_increase = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX) / SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); min_distance = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); lot_step = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); //Define our ONNX model ulong ma_input_shape [] = {1,2}; ulong rsi_input_shape [] = {1,3}; ulong output_shape [] = {1,1}; //Create the model onnx_model = OnnxCreateFromBuffer(onnx_buffer,ONNX_DEFAULT); rsi_onnx_model = OnnxCreateFromBuffer(rsi_onnx_buffer,ONNX_DEFAULT); if((onnx_model == INVALID_HANDLE) || (rsi_onnx_model == INVALID_HANDLE)) { Comment("[ERROR] Failed to load AI module correctly"); return(false); } //Validate I/O if((!OnnxSetInputShape(onnx_model,0,ma_input_shape)) || (!OnnxSetInputShape(rsi_onnx_model,0,rsi_input_shape))) { Comment("[ERROR] Failed to set input shape correctly: ",GetLastError()); return(false); } if((!OnnxSetOutputShape(onnx_model,0,output_shape)) || (!OnnxSetOutputShape(rsi_onnx_model,0,output_shape))) { Comment("[ERROR] Failed to load AI module correctly: ",GetLastError()); return(false); } //Everything went fine return(true); }
すべてをまとめると、私たちの取引アプリケーションは以下のようになります。
//+------------------------------------------------------------------+ //| GBPUSD AI.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" //+------------------------------------------------------------------+ //| Load our resources | //+------------------------------------------------------------------+ #resource "\\Files\\AUDJPY D1 MA AI F22 P40.onnx" as const uchar onnx_buffer[]; #resource "\\Files\\AUDJPY D1 RSI AI F22 P40.onnx" as const uchar rsi_onnx_buffer[]; //+------------------------------------------------------------------+ //| Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade; #include <Trade\OrderInfo.mqh> class COrderInfo; //+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ long onnx_model; int ma_handler,state; double bid,ask,vol; vectorf model_forecast = vectorf::Zeros(1); vectorf rsi_model_output = vectorf::Zeros(1); double min_volume,max_volume_increase, volume_step, buy_stop_loss, sell_stop_loss,atr_stop,risk_equity; double take_profit = 0; double close_price[3],atr_reading[],ma_buffer[]; long min_distance,login; int atr,close_average,ticket_1,ticket_2; bool authorized = false; double margin,lot_step; string currency,server; bool all_closed =true; int rsi_handler; long rsi_onnx_model; double indicator_reading[]; ENUM_ACCOUNT_TRADE_MODE account_type; const double stop_percent = 1; //+------------------------------------------------------------------+ //| Technical indicators | //+------------------------------------------------------------------+ input group "Money Management" input int lot_multiple = 10; // How big should the lot size be? input double profit_target = 0; // Profit Target input double loss_target = 0; // Max Loss Allowed input group "Money Management" input int bb_period = 36; //Bollinger band period input int ma_period = 4; //Moving average period const int atr_period = 200; //ATR Period input double atr_multiple =2.5; //ATR Multiple //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //Authorization if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { Comment("Press Ctrl + E To Give The Robot Permission To Trade And Reload The Program"); return(INIT_FAILED); } else if(!MQLInfoInteger(MQL_TRADE_ALLOWED)) { Comment("Reload The Program And Make Sure You Clicked Allow Algo Trading"); return(INIT_FAILED); } else { Comment("This License is Genuine"); setup(); } //--- Everything was okay return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- OnnxRelease(onnx_model); OnnxRelease(rsi_onnx_model); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Update technical data update(); if(PositionsTotal() == 0) { check_setup(); } if(PositionsTotal() > 0) { check_atr_stop(); } } //+------------------------------------------------------------------+ //| Get a prediction from our model | //+------------------------------------------------------------------+ int model_predict(void) { //MA Forecast vectorf model_inputs = vectorf::Zeros(2); vectorf rsi_model_inputs = vectorf::Zeros(3); CopyBuffer(ma_handler,0,0,40,ma_buffer); if(ma_buffer[0] > ma_buffer[39]) { model_inputs[0] = 1; model_inputs[1] = 0; } else if(ma_buffer[0] < ma_buffer[39]) { model_inputs[1] = 1; model_inputs[0] = 0; } //RSI Forecast CopyBuffer(rsi_handler,0,0,1,indicator_reading); if(indicator_reading[0] < 30) { rsi_model_inputs[0] = 1; rsi_model_inputs[1] = 0; rsi_model_inputs[2] = 0; } else if(indicator_reading[0] >70) { rsi_model_inputs[0] = 0; rsi_model_inputs[1] = 1; rsi_model_inputs[2] = 0; } else { rsi_model_inputs[0] = 0; rsi_model_inputs[1] = 0; rsi_model_inputs[2] = 1; } //Model predictions OnnxRun(onnx_model,ONNX_DEFAULT,model_inputs,model_forecast); OnnxRun(rsi_onnx_model,ONNX_DEFAULT,rsi_model_inputs,rsi_model_output); //Evaluate model output for buy setup if(((rsi_model_output[0] > 0) && (model_forecast[0] > 0))) { //AI Models forecast Comment("AI Forecast: UP"); return(1); } //Evaluate model output for a sell setup if((rsi_model_output[0] < 0) && (model_forecast[0] < 0)) { Comment("AI Forecast: DOWN"); return(-1); } //Otherwise no position was found return(0); } //+------------------------------------------------------------------+ //| Check for valid trade setups | //+------------------------------------------------------------------+ void check_setup(void) { int res = model_predict(); if(res == -1) { Trade.Sell(vol,Symbol(),bid,0,0,"VD V75 AI"); state = -1; } else if(res == 1) { Trade.Buy(vol,Symbol(),ask,0,0,"VD V75 AI"); state = 1; } } //+------------------------------------------------------------------+ //| Update our market data | //+------------------------------------------------------------------+ void update(void) { ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); buy_stop_loss = 0; sell_stop_loss = 0; static datetime time_stamp; datetime time = iTime(_Symbol,PERIOD_CURRENT,0); check_price(3); CopyBuffer(atr,0,0,1,atr_reading); CopyBuffer(ma_handler,0,0,1,ma_buffer); ArraySetAsSeries(atr_reading,true); atr_stop = ((min_volume + atr_reading[0]) * atr_multiple); //On Every Candle if(time_stamp != time) { //Mark the candle time_stamp = time; OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,min_volume,ask,margin); } } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Load resources | //+------------------------------------------------------------------+ bool setup(void) { //Account Info currency = AccountInfoString(ACCOUNT_CURRENCY); server = AccountInfoString(ACCOUNT_SERVER); login = AccountInfoInteger(ACCOUNT_LOGIN); //Indicators atr = iATR(_Symbol,PERIOD_CURRENT,atr_period); //Setup technical indicators ma_handler =iMA(Symbol(),PERIOD_CURRENT,40,0,MODE_SMA,PRICE_LOW); vol = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN) * lot_multiple; rsi_handler = iRSI(Symbol(),PERIOD_CURRENT,30,PRICE_CLOSE); //Market Information min_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); max_volume_increase = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX) / SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); min_distance = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); lot_step = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); //Define our ONNX model ulong ma_input_shape [] = {1,2}; ulong rsi_input_shape [] = {1,3}; ulong output_shape [] = {1,1}; //Create the model onnx_model = OnnxCreateFromBuffer(onnx_buffer,ONNX_DEFAULT); rsi_onnx_model = OnnxCreateFromBuffer(rsi_onnx_buffer,ONNX_DEFAULT); if((onnx_model == INVALID_HANDLE) || (rsi_onnx_model == INVALID_HANDLE)) { Comment("[ERROR] Failed to load AI module correctly"); return(false); } //--- Validate I/O if((!OnnxSetInputShape(onnx_model,0,ma_input_shape)) || (!OnnxSetInputShape(rsi_onnx_model,0,rsi_input_shape))) { Comment("[ERROR] Failed to set input shape correctly: ",GetLastError()); return(false); } if((!OnnxSetOutputShape(onnx_model,0,output_shape)) || (!OnnxSetOutputShape(rsi_onnx_model,0,output_shape))) { Comment("[ERROR] Failed to load AI module correctly: ",GetLastError()); return(false); } //--- Everything went fine return(true); } //+------------------------------------------------------------------+ //| Close all our open positions | //+------------------------------------------------------------------+ void close_all() { if(PositionsTotal() > 0) { ulong ticket; for(int i =0;i < PositionsTotal();i++) { ticket = PositionGetTicket(i); Trade.PositionClose(ticket); } } } //+------------------------------------------------------------------+ //| Update our trailing ATR stop | //+------------------------------------------------------------------+ void check_atr_stop() { for(int i = PositionsTotal() -1; i >= 0; i--) { string symbol = PositionGetSymbol(i); if(_Symbol == symbol) { ulong ticket = PositionGetInteger(POSITION_TICKET); double position_price = PositionGetDouble(POSITION_PRICE_OPEN); double type = PositionGetInteger(POSITION_TYPE); double current_stop_loss = PositionGetDouble(POSITION_SL); if(type == POSITION_TYPE_BUY) { double atr_stop_loss = (ask - (atr_stop)); double atr_take_profit = (ask + (atr_stop)); if((current_stop_loss < atr_stop_loss) || (current_stop_loss == 0)) { Trade.PositionModify(ticket,atr_stop_loss,atr_take_profit); } } else if(type == POSITION_TYPE_SELL) { double atr_stop_loss = (bid + (atr_stop)); double atr_take_profit = (bid - (atr_stop)); if((current_stop_loss > atr_stop_loss) || (current_stop_loss == 0)) { Trade.PositionModify(ticket,atr_stop_loss,atr_take_profit); } } } } } //+------------------------------------------------------------------+ //| Close our open buy positions | //+------------------------------------------------------------------+ void close_buy() { ulong ticket; int type; if(PositionsTotal() > 0) { for(int i = 0; i < PositionsTotal();i++) { if(PositionGetSymbol(i) == _Symbol) { ticket = PositionGetTicket(i); type = (int)PositionGetInteger(POSITION_TYPE); if(type == POSITION_TYPE_BUY) { Trade.PositionClose(ticket); } } } } } //+------------------------------------------------------------------+ //| Close our open sell positions | //+------------------------------------------------------------------+ void close_sell() { ulong ticket; int type; if(PositionsTotal() > 0) { for(int i = 0; i < PositionsTotal();i++) { if(PositionGetSymbol(i) == _Symbol) { ticket = PositionGetTicket(i); type = (int)PositionGetInteger(POSITION_TYPE); if(type == POSITION_TYPE_SELL) { Trade.PositionClose(ticket); } } } } } //+------------------------------------------------------------------+ //| Get the most recent price values | //+------------------------------------------------------------------+ void check_price(int candles) { for(int i = 0; i < candles;i++) { close_price[i] = iClose(_Symbol,PERIOD_CURRENT,i); } } //+------------------------------------------------------------------+
ここで、アルゴリズムの訓練時には使用しなかったデータを用いて、取引アルゴリズムをバックテストしてみましょう。今回選択した期間は、AUD/JPYペアの日次市場データ(2023年1月初旬~2024年6月28日)です。また、モデルの訓練時にこの期間のデータが含まれていないことを確認済みのため、Forwardパラメータを「No」に設定します。
図12:取引戦略を評価するために使用する銘柄と時間枠
さらに、実際の取引条件を模倣するために、まずDelaysパラメータを「Random delay」に設定します。このパラメータは、注文が出された時点から実行されるまでの遅延時間を制御します。ランダムに設定することで、実際の取引環境のように、レイテンシーが常に一定ではない状態を再現できます。さらに、端末に実際のティックデータを使って市場をモデル化するように指示します。この設定により、バックテストがわずかに遅くなる可能性がありますが、その分、端末はインターネット経由でブローカーから詳細な市場データを取得する必要があるためです。
口座預金とレバレッジを制御する最後のパラメータは、取引設定に合わせて調整する必要があります。要求したすべてのデータを正常に取得できたと仮定すると、バックテストが開始されます。
図13:バックテストに使用するパラメータ
図14:モデルが訓練されなかったデータに対する戦略のパフォーマンス
図12:未知の市場データに関するバックテストの詳細
結論
本日分析した広範な市場データは、40ステップ未満の将来の間隔を予測したい場合は、価格を直接予測する方がおそらく良いことを示しています。ただし、40ステップ以上先の将来を予測したい場合は、価格の変化ではなく移動平均の変化を予測する方がおそらくよいでしょう。私たちが観察し、それがもたらす違いを実感できるさらなる機能強化が常に待っています。データの入力を変換するのにどれだけの時間を費やしても、その価値は十分にあることがはっきりとわかります。なぜなら、データの入力を変換することで、モデルの基礎となる関係をより意味のある方法で明らかにすることができるからです。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/16230





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