English Deutsch
preview
PythonとMQL5を使用した特徴量エンジニアリング(第1回):長期AIモデルの移動平均の予測

PythonとMQL5を使用した特徴量エンジニアリング(第1回):長期AIモデルの移動平均の予測

MetaTrader 5 | 13 2月 2025, 08:30
153 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

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()
True

利用できる銘柄はいくつあるでしょうか。

#The total number of symbols we have
print(f"Total Symbols Available: ",mt5.symbols_total())
Total Symbols Available:  297

すべての銘柄の名前を取得します。

#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")
99.66329966329967% complete
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()
True

次に、市場データを取得します。

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"]))
0.9935846835797412

#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"]))
0.9082244556297641


カスタマイズされた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つの状態があります。

  1. 70を超える
  2. 30未満
  3. 70未満で30を超える
現在のRSIの値が70を超えている場合、「RSI 1」列は1に設定され、その他の列は0になります。移動平均についても同様の手法を適用しますが、移動平均は「上昇している」または「下降している」の2つの状態のみを持ちます。このようなデータ変換をおこなうことで、モデルが人間のトレーダーのように、指標の重要な変化に着目できるようになります。
#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_
array([-0.19297857,  0.14816216,  0.04481641])

Ridgeモデルは、RSIの現在の状態に基づいてのみ、将来の価格を適切に予測できます。

#RSI state
np.mean(cross_val_score(Ridge(),data.loc[:,["RSI 1","RSI 2","RSI 3"]] , data["Target"],cv=tscv))
-0.025569914370219736

同様に、私たちのモデルも移動平均指標の変化から独自の取引ルールを学習しました。モデルの最初の係数は負であり、移動平均が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_
array([-0.15572796,  0.15572796])

移動平均インジケーターの現在の状態を与えると、モデルのパフォーマンスはさらに向上します。

#MA state
np.mean(cross_val_score(Ridge(),data.loc[:,["MA 1","MA 2"]] , data["Target"],cv=tscv))
-0.009645886983465935


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

PythonとMQL5による多銘柄分析(第2回):ポートフォリオ最適化のための主成分分析 PythonとMQL5による多銘柄分析(第2回):ポートフォリオ最適化のための主成分分析
取引口座のリスク管理は、すべてのトレーダーにとっての課題です。MetaTrader 5で、さまざまな銘柄に対して高リスク、中リスク、低リスクモードを動的に学習する取引アプリケーションを開発するにはどうすればよいでしょうか。PCA(主成分分析)を使用することで、ポートフォリオの分散をより効果的に管理できるようになります。MetaTrader 5から取得した市場データを基に、これら3つのリスクモードを学習するアプリケーションの作成方法を説明します。
知っておくべきMQL5ウィザードのテクニック(第45回):モンテカルロ法による強化学習 知っておくべきMQL5ウィザードのテクニック(第45回):モンテカルロ法による強化学習
モンテカルロは、ウィザードで組み立てられたエキスパートアドバイザー(EA)における実装を検討するために取り上げる、強化学習の4つ目の異なるアルゴリズムです。ランダムサンプリングに基づいていますが、多様なシミュレーション手法を活用できる点が特徴です。
リプレイシステムの開発(第57回):テストサービスについて リプレイシステムの開発(第57回):テストサービスについて
注意点が1つあります。この記事にはサービスコードは含まれておらず、次の記事でのみ提供されます。ただし、実際の開発の出発点として同じコードを使用するため、この記事ではその説明をおこないます。ですので、注意深く、そして忍耐強く読んでください。毎日、すべてがさらに面白くなっていきますので、次の記事を楽しみにお待ちください。
MQL5での暗号化の探索:ステップごとのアプローチ MQL5での暗号化の探索:ステップごとのアプローチ
この記事では、MQL5内での暗号化の統合について探り、取引アルゴリズムのセキュリティと機能を強化する方法を紹介します。主要な暗号化手法と、それらを自動取引に実際に実装する方法について説明します。