English Deutsch
preview
データサイエンスと機械学習(第27回):MetaTrader 5取引ボットにおける畳み込みニューラルネットワーク(CNN)に価値はあるか?

データサイエンスと機械学習(第27回):MetaTrader 5取引ボットにおける畳み込みニューラルネットワーク(CNN)に価値はあるか?

MetaTrader 5エキスパートアドバイザー | 24 9月 2024, 11:34
46 0
Omega J Msigwa
Omega J Msigwa

畳み込みニューラルネットワークで使われているプーリング演算は大きな間違いであり、それがうまく機能していることが災いしている。

ジェフリー・ヒントン

内容


この記事の内容を完全に理解するためには、プログラミング言語人工ニューラルネットワーク機械学習MQL5におけるONNXについての基本的な理解が必要です。


畳み込みニューラルネットワーク(CNN)とは?

畳み込みニューラルネットワーク(CNN)は、画像、音声スペクトログラム、時系列データなど、構造化されたグリッド状のデータを処理するために特別に設計された深層学習アルゴリズムのクラスです。入力データから特徴の空間階層を自動的かつ適応的に学習できるため、視覚データタスクに特に適しています。

CNNは人工ニューラルネットワーク(ANN)の拡張版です。これらは主にグリッド状の行列データセットから特徴を抽出するために使用されます。例えば、画像や動画のような視覚的データセットでは、データパターンが重要な役割を果たします。

畳み込みニューラルネットワークには、畳み込み層、活性化関数、プーリング層、全結合層、ドロップアウト層など、いくつかの重要な構成要素があります。CNNをより深く理解するために、各構成要素を分解して、その全体像を見てみましょう。

畳み込みニューラルネットワークのイラスト


畳み込み層

これらはCNNの核となる構成要素で、計算の大部分がここでおこなわれます。畳み込み層は、画像のエッジなど、入力データの局所的なパターンを検出する役割を担っています。これは、特徴量マップを生成するために入力データ上をスライドするフィルタ(またはカーネル)を使用することで実現できます。

畳み込み層は、畳み込みニューラルネットワークのいくつかの畳み込みユニットを含む隠れ層であり、特徴量の抽出に使用されます。

from tensorflow.keras.layers import Conv1D

model = Sequential()
model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(window_size, X_train.shape[2])))

フィルタ/カーネル

フィルタ(またはカーネル)とは、学習可能な小さな正方行列(通常は3x3、5x5などのサイズ)のことで、入力データ上をスライドして局所的なパターンを検出します。

どのように機能するのか?

入力データを横切って移動し、フィルタの値とフィルタの現在の受容野内の入力値との間で要素ごとの乗算をおこない、その結果を合計することで動作します。この操作が畳み込みと呼ばれるものです。

訓練中、ネットワークはフィルタの最適値を学習します。初期の層では、フィルタは通常、エッジやテクスチャのような単純な特徴量を検出するように学習します。一方、より深い層では、フィルタは形状やオブジェクトのような、より複雑なパターンを検出することができます。

単純な3x3フィルタと5x5の入力画像を考えてください。フィルタは画像上をスライドし、畳み込み演算をおこない、特徴量マップを生成します。

コンブネットカーネル



ストライド

これも畳み込み層に見られる特徴です。ストライドは、フィルタが入力データ上を移動する際のステップサイズです。これは、畳み込み処理中の各ステップでフィルタがどれだけシフトするかを決定します。

どのように機能するのか?

ストライドが1の場合、フィルタは一度に1ユニットずつ移動し、その結果、非常に重なり合った詳細な特徴量マップが得られます。これにより、より大きな特徴量マップが出力されます。

ストライドが2以上の場合、フィルタはユニットをスキップし、その結果、出力される特徴量マップはより詳細でなくなるが、より小さくなります。これにより、出力の空間次元が縮小され、事実上、入力がダウンサンプリングされます。

例えば、3x3のフィルタと5x5の入力画像があり、ストライドが1の場合、フィルタは一度に1ピクセルずつ移動し、3x3の出力特徴量マップを生成します。ストライドが2の場合、フィルタは一度に2つのピクセルを移動し、2x2の出力特徴量マップを生成します。


パディング

パディングは、入力データの境界の周りに余分なピクセル(通常はゼロ)を追加することです。これにより、フィルタが適切にフィットし、出力される特徴量マップの空間的な次元が制御されます。

パッドの種類

Kerasよると、パディングには3つのタイプがあります(大文字と小文字を区別する)。

  1. valid:パディングは適用されない
  2. same:strides=1のとき、出力サイズが入力サイズと一致するように入力をパッドする
  3. causal:時間ステップᵆでの出力が将来の入力に依存しないことを保証するために、時間データに使用される

パディングは、入力データの空間的な次元を保持するのに役立ちます。パディングをおこなわないと、畳み込み層ごとに出力特徴量マップが縮小し、重要なエッジ情報が失われる可能性があります。

パディングを追加することで、ネットワークはエッジ特徴量を効率的に学習し、入力の空間分解能を維持することができます。

3x3のフィルタと5x5の入力画像を考えます。有効なパディング(パディングなし)の場合、出力特徴量マップは3x3になります。同じパディングで、入力の周囲にゼロの境界を追加して7x7にすることもできます。出力特徴量マップは、入力次元を保持したまま5x5となります。

以下はPythonによる畳み込み層のコードです。

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv1D

model = Sequential()
model.add(Conv1D(filters=64, 
                 kernel_size=3, 
                 activation='relu', 
                 strides=2,
                 padding='causal',
                 input_shape=(window_size, X_train.shape[2])
                )
         )


活性化関数

ニューラルネットワークの謎を解くの記事で説明したように、活性化関数は入力を受けて出力を処理する数学的関数です。

活性化関数は、モデルに非線形性を導入するために要素ごとに適用されます。CNNでよく使われる活性化関数には、ReLU (Rectified Linear Unit)、SigmoidTanHなどがあります。


プーリング層

デサンプル層とも呼ばれるこれらの層は、最も重要な情報を保持しながら、入力データの幅と高さの空間次元を縮小する役割を担っているため、CNNにとって不可欠な部分です。

どのように機能するのか?

まず、入力データを重複する領域またはウィンドウに分割し、次に各ウィンドウに最大プーリングや平均プーリングのような集計関数を適用して単一の値を得ます。

最大プーリングは、フィルタ領域内の値の集合から最大値を取ります。データの空間的な次元を小さくすることで、計算負荷とパラメータの数を減らすことができます。

Python

from tensorflow.keras.layers import Conv1D, MaxPooling1D

model = Sequential()
model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(window_size, X_train.shape[2])))
model.add(MaxPooling1D(pool_size=2))
MaxPooling1D(pool_size=2)

この層は、各2要素ウィンドウから最大値を取ります。

最大プーリング

詳細.

平均プーリングは、フィルタ領域内の値の集合から平均値を取ります。最大プーリングよりも使用頻度は低くなります。

Python

from tensorflow.keras.layers import Conv1D, AveragePooling1D

model = Sequential()
model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(window_size, X_train.shape[2])))
model.add(AveragePooling1D(pool_size=2))
AveragePooling1D(pool_size=2)

この層は、各2要素ウィンドウの平均値を取ります。

平均プーリング

詳細.

なぜ1次元畳み込み層を使うのか?

CNNにはConv1D層、Conv2D層、Conv3D層があります。1次元畳み込み層は1次元データ用に設計されているため、この種の問題に適しており、逐次データや時系列データに適しています。Conv2DやConv3Dのような他の畳み込み層は、この種の問題には複雑すぎます。


全結合層

全結合層のニューロンは、前の層のすべての活性化に接続しています。これらの層は通常、畳み込み層とプーリング層によって抽出された特徴量に基づいて分類または回帰を実行するために、ネットワークの終盤で使用されます。

from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense, Dropout

model = Sequential()
model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(window_size, X_train.shape[2])))
model.add(MaxPooling1D(pool_size=2))
model.add(Flatten())
model.add(Dense(100, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(units=len(np.unique(y)), activation='sigmoid'))  # For binary classification (e.g., buy/sell signal)

model.summary()

平坦化層は、1次元プールされた特徴量マップを1次元ベクトルに変換し、全結合層(密な層)に供給できるようにします。

蜜な層(Dense)は、畳み込み層とプーリング層によって抽出された特徴量に基づいて最終的な決定を下すために使用される全結合層です。密な層は基本的に、従来の人工ニューラルネットワーク(ANN)の中核的な構成要素です。

CNNの密な層がハイライトされる




ドロップアウト層

ドロップアウト層はマスクの役割を果たし、他のすべてのニューロンの機能を維持したまま、一部のニューロンの後続層への寄与を排除します。ドロップアウト層を入力ベクトルに適用すると、その特徴の一部が除去されるが、隠れ層に適用すると、隠れニューロンの一部が除去されます。

学習データの過剰適合を避けるため、ドロップアウト層はCNNの学習において非常に重要です。もしそれらがない場合、最初の訓練サンプルセットは学習に過度に大きな影響を与えます。その結果、後のサンプルやバッチでしか見られない形質は学習されないことになります。

from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense, Dropout

model = Sequential()
model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(window_size, X_train.shape[2])))
model.add(MaxPooling1D(pool_size=2))
model.add(Flatten())
model.add(Dense(100, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(units=len(np.unique(y)), activation='sigmoid'))  # For binary classification (e.g., buy/sell signal)

model.summary()


なぜ畳み込みニューラルネットワーク(CNN)を金融分析や取引に使うのか?

CNNは画像やビデオの処理を目的として設計されているため、画像やビデオの処理アプリケーションで広く使用されています。上記の説明を見ていただければ、私が画像の分類などをおこなう際にCNNを使うことに言及していることにお気づきいただけるかもしれません。

金融分析のような表データに畳み込みニューラルネットワーク(CNN)を使用することは、順伝播型ニューラルネットワーク(FFNN: Feed Forward Neural Network)、回帰型ニューラルネットワーク(RNN: Recurrent Neural Network)長・短期記憶(LSTM: Long Short-Term Memory)、ゲート付き回帰型ユニット(GRU: Gated Recurrent Unit)のような他のニューラルネットワークタイプを使用することに比べて、型破りに見えるかもしれません。しかし、この文脈でCNNを採用するには、いくつかの理由と潜在的な利点があります。


01:CNNは、データから局所的なパターンを自動的に抽出することに優れている

金融データはしばしば、トレンドや季節性といった局所的な時間的パターンを示します。データを時系列として扱うことでCNNは、従来の手法では見逃されがちな、特徴間の局所的な依存関係や相互作用を学習することができます。

02:階層的な特徴量を学習できる

CNNの多層構造は、複雑な特徴量階層を学習することを可能にします。初期の層は単純なパターンを検出し、より深い層はこれらの単純なパターンをより複雑で抽象的な表現に組み合わせることができます。この階層的特徴量学習は、金融データの複雑なパターンを捉えるのに有効です。

03:ノイズや冗長な特徴に強い

周知のように、金融データセットにはノイズや冗長なデータが含まれています。CNNのプーリング層は、特徴量マップをダウンサンプリングし、些細な変動の影響を軽減するため、モデルをわずかな変動やノイズに頑健にするのに役立ちます。

04:CNNは多変量時系列をうまく扱える

金融データには、異なる株価や取引量のような複数の相関時系列が含まれることが多いです。CNNはこれらの複数の時系列間の相互作用や依存関係を効果的に捉えることができるため、多変量時系列予測に適しています。

05:高次元のデータに対して計算効率が高い

金融データは高次元(多くの特徴量)を持つことがあります。重みの共有と局所的な結合性によって、CNNは完全結合ニューラルネットワークよりも計算効率が高く、高次元データへの拡張性が高いです。

06:CNNはエンドツーエンドの学習を可能にする

CNNは生データから直接学習することができ、手作業による特徴量エンジニアリングを必要とせずに予測をおこなうことができます。このエンドツーエンドの学習アプローチは、モデル化プロセスを単純化し、モデルに最も関連性の高い特徴を発見させることで、より良いパフォーマンスをもたらす可能性があります。

07:CNNはある種のデータに対して有利な畳み込み演算を適用する

畳み込み演算は、急激な変化や特定のパターンなど、データ内の重要なシグナルを検出し、強調することができます。これは特に財務分析に有効で、市場の急激な変化やパターンを検出することが重要になります。

取引アプリケーションでCNNを使う正当な理由がわかったので、CNNを作り、訓練してみましょう。次に、Meta Trader 5のエキスパートアドバイザー(EA)でCNNを使用する方法を見ていきます。


Pythonで畳み込みニューラルネットワーク(CNN)を作る

これにはいくつかのステップがあります。

  1. データの収集
  2. CNNモデル用のデータ準備
  3. CNNモデルの訓練
  4. CNNモデルのONNX形式への保存

01:データの収集

前回の記事で使用した時系列予測のために作成したデータを使用します。

時系列予測データセット

畳み込みニューラルネットワーク(CNN)が高次元データ内のパターンを検出するのに適していることがわかったので、モデルを複雑にすることなく、CNNモデルが検出できるパターンをたくさん持っていると思われる特徴量をいくつか選ぶことができます。

Pythonコード

open_price = df['TARGET_OPEN']
close_price = df['TARGET_CLOSE']

# making the target variable

target_var = []
for i in range(len(open_price)):
    if close_price[i] > open_price[i]: # if the price closed above where it opened
        target_var.append(1) # bullish signal
    else:
        target_var.append(0) # bearish signal

        
new_df = pd.DataFrame({
    'OPEN': df['OPEN'],
    'HIGH': df['HIGH'],
    'LOW': df['LOW'],
    'CLOSE': df['CLOSE'],
    'TARGET_VAR': target_var
})

print(new_df.shape)

TARGET_OPENとTARGET_CLOSE(それぞれ始値と終値)に基づいて目標変数を準備した直後に、1バー先まで収集します。OPEN、HIGH、LOWの4つの独立変数と、TARGET_VARという1つの従属変数のみを持つ、new_dfというミニデータセットバージョンを作成しました。


02:CNNモデル用のデータ準備

まず、入力データを整形し、ウィンドウに整列させるという前処理をしなければなりません。CNNで表形式のデータを扱う場合、これは非常に重要です。

取引データは逐次的なものであるため、多くの場合、一時点ではなく、一連の時間ステップにわたってパターンが現れます。データのオーバーラップウィンドウを作ることで、時間的な依存関係をとらえ、CNNモデルにコンテキストを提供することができます。

また、CNNは入力データが特定の形をしていることを期待します。1次元畳み込み層の場合、入力形状は通常、(ウィンドウの数、ウィンドウのサイズ、特徴量の数)が必要です。 この形状は、前回の記事で紹介した回帰型ニューラルネットワーク(RNN)を使った時系列分析で使うものと似ています。これからおこなう前処理は、データがこの形式であることを保証し、CNNモデルの入力に適したものにします。

# Example data preprocessing function

def preprocess_data(df, window_size):
    X, y = [], []
    for i in range(len(df) - window_size):
        X.append(df.iloc[i:i+window_size, :-1].values)
        y.append(df.iloc[i+window_size, -1])
    return np.array(X), np.array(y)


window_size = 10


X, y = preprocess_data(new_df, window_size)
print(f"x_shape = {X.shape}\ny_shape = {y.shape}")

出力

x_shape = (990, 10, 4)
y_shape = (990,)

私たちのデータは1日単位で収集されているので、10というウィンドウサイズは、10日以内のパターンを理解するためにCNNモデルを訓練することを示しています。


次に、データを訓練用サンプルとテスト用サンプルに分ける必要があります。

# Split data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, shuffle=False)

# Standardize the data
scaler = StandardScaler()

X_train = scaler.fit_transform(X_train.reshape(-1, X_train.shape[-1])).reshape(X_train.shape)
X_test = scaler.transform(X_test.reshape(-1, X_test.shape[-1])).reshape(X_test.shape)

print(f"x_train\n{X_train.shape}\nx_test\n{X_test.shape}\n\ny_train {y_train.shape} y_test {y_test.shape}")

出力

x_train
(792, 10, 4)
x_test
(198, 10, 4)

y_train (792,) y_test (198,)


最後に、この分類問題タスクの目標変数をワンホットエンコードしなければなりません。

from tensorflow.keras.utils import to_categorical

y_train_encoded = to_categorical(y_train)
y_test_encoded = to_categorical(y_test)

print(f"One hot encoded\n\ny_train {y_train_encoded.shape}\ny_test {y_test_encoded.shape}")

出力

One hot encoded

y_train (792, 2)
y_test (198, 2)


03:CNNモデルの訓練

ほとんどの仕事はここでおこなわれます。

# Defining the CNN model

model = Sequential()
model.add(Conv1D(filters=64, 
                 kernel_size=3, 
                 activation='relu', 
                 strides=2,
                 padding='causal',
                 input_shape=(window_size, X_train.shape[2])
                )
         )

model.add(MaxPooling1D(pool_size=2))
model.add(Flatten()) 
model.add(Dense(100, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(units=len(np.unique(y)), activation='softmax'))  # For binary classification (buy/sell signal)

model.summary()

# Compiling the model

optimizer = Adam(learning_rate=0.001)

model.compile(optimizer=optimizer, loss='categorical_crossentropy', metrics=['accuracy'])

# Training the model

early_stopping = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True)

history = model.fit(X_train, y_train_encoded, epochs=100, batch_size=16, validation_split=0.2, callbacks=[early_stopping])

plt.figure(figsize=(7.5, 6))
plt.plot(history.history['loss'], label='Training Loss')
plt.plot(history.history['val_loss'], label='Validation Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.title('Training Loss Curve')
plt.legend()
plt.savefig("training loss cuver-cnn-clf.png")
plt.show()

出力

Model: "sequential_2"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv1d_2 (Conv1D)               │ (None, 5, 64)          │           832 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling1d_2 (MaxPooling1D)  │ (None, 2, 64)          │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ flatten_2 (Flatten)             │ (None, 128)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_4 (Dense)                 │ (None, 100)            │        12,900 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout_2 (Dropout)             │ (None, 100)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_5 (Dense)                 │ (None, 2)              │           202 │
└─────────────────────────────────┴────────────────────────┴───────────────┘

訓練は34番目のエポックで停止しました。

40/40 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - accuracy: 0.5105 - loss: 0.6875 - val_accuracy: 0.4843 - val_loss: 0.6955
Epoch 32/100
40/40 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - accuracy: 0.5099 - loss: 0.6888 - val_accuracy: 0.5283 - val_loss: 0.6933
Epoch 33/100
40/40 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - accuracy: 0.4636 - loss: 0.6933 - val_accuracy: 0.5283 - val_loss: 0.6926
Epoch 34/100
40/40 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - accuracy: 0.5070 - loss: 0.6876 - val_accuracy: 0.5346 - val_loss: 0.6963

CNN訓練検証損失曲線

このモデルは、サンプル外予測で約57%の精度を示しました。

y_pred = model.predict(X_test) 

classes_in_y = np.unique(y)
y_pred_binary = classes_in_y[np.argmax(y_pred, axis=1)]

# Confusion Matrix
cm = confusion_matrix(y_test, y_pred_binary)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.xlabel("Predicted Label")
plt.ylabel("True Label")
plt.title("Confusion Matrix")
plt.savefig("confusion-matrix CNN")  # Display the heatmap


print("Classification Report\n",
      classification_report(y_test, y_pred_binary))

出力

7/7 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step
Classification Report
               precision    recall  f1-score   support

           0       0.53      0.24      0.33        88
           1       0.58      0.83      0.68       110

    accuracy                           0.57       198
   macro avg       0.55      0.53      0.50       198
weighted avg       0.55      0.57      0.52       198

私たちのCNNモデルはEAとしては十分です。しかし、EAのコーディングを始める前に、訓練したCNNモデルをONNX形式で保存しておきましょう。


04:CNNモデルをONNX形式に保存する

CNNモデルを.onnx形式で保存し、スケーリング技術のパラメータをバイナリファイルで保存します。

import tf2onnx

onnx_file_name = "cnn.EURUSD.D1.onnx"

spec = (tf.TensorSpec((None, window_size, X_train.shape[2]), tf.float16, name="input"),)
model.output_names = ['outputs']

onnx_model, _ = tf2onnx.convert.from_keras(model, input_signature=spec, opset=13)

# Save the ONNX model to a file
with open(onnx_file_name, "wb") as f:
    f.write(onnx_model.SerializeToString())
    
    
# Save the mean and scale parameters to binary files
scaler.mean_.tofile(f"{onnx_file_name.replace('.onnx','')}.standard_scaler_mean.bin")
scaler.scale_.tofile(f"{onnx_file_name.replace('.onnx','')}.standard_scaler_scale.bin")


畳み込みニューラルネットワーク(CNN)ベースの取引ロボットの作成

EAの内部では、最初にONNX形式のモデルと標準スケーラーのバイナリファイルをリソースとしてインクルードする必要があります。

MQL5 | ConvNet EA.mq5

#resource "\\Files\\cnn.EURUSD.D1.onnx" as uchar onnx_model[]
#resource "\\Files\\cnn.EURUSD.D1.standard_scaler_scale.bin" as double scaler_stddev[]
#resource "\\Files\\cnn.EURUSD.D1.standard_scaler_mean.bin" as double scaler_mean[]

スケーラーとonnxモデルの両方を初期化します。

#include <MALE5\Convolutioal Neural Networks(CNNs)\Convnet.mqh>
#include <MALE5\preprocessing.mqh>

CConvNet cnn;
StandardizationScaler scaler;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+

input group "cnn";
input uint cnn_data_window = 10; 
//this value must be the same as the one used during training in a python script

vector classes_in_y = {0,1}; //we have to assign the classes manually | it is essential that their order is preserved as they can be seen in python code, HINT: They are usually in ascending order
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
   if (!cnn.Init(onnx_model)) //Initialize the ONNX model
     return INIT_FAILED;
     
//--- Initializing the scaler with values loaded from binary files 

   scaler = new StandardizationScaler(scaler_mean, scaler_stddev); //load the scaler
      
   return(INIT_SUCCEEDED);
  }

モデルを稼働させるにはこれで十分です。訓練時の独立変数の使い方と同じように、データを抽出する関数を作ってみましょう。時間枠が保存されなければならないウィンドウサイズである、直前のクローズバーから10本前のバーまでの4つの変数OHLC値を使用しました(日足時間枠)。

input group "cnn";
input uint cnn_data_window = 10; 
//this value must be the same as the one used during training in a python script

input ENUM_TIMEFRAMES timeframe = PERIOD_D1;
input int magic_number = 1945;
input int slippage = 50;
matrix GetXVars(int bars, int start_bar=1)
 {
   vector open(bars), 
          high(bars),
          low(bars), 
          close(bars);

//--- Getting OHLC values
   
   open.CopyRates(Symbol(), timeframe, COPY_RATES_OPEN, start_bar, bars);
   high.CopyRates(Symbol(), timeframe, COPY_RATES_HIGH, start_bar, bars);
   low.CopyRates(Symbol(), timeframe, COPY_RATES_LOW, start_bar, bars);
   close.CopyRates(Symbol(), timeframe, COPY_RATES_CLOSE, start_bar, bars);
   
//---

   matrix data(bars, 4); //we have 10 inputs from cnn | this value is fixed
   
//--- adding the features into a data matrix
   
   data.Col(open, 0);
   data.Col(high, 1);
   data.Col(low, 2);
   data.Col(close, 3);
   
   return data;
 }

独立変数を収集する関数ができたので、取引戦略を確定できます。

void OnTick()
  {
//---
   
   if (NewBar()) //Trade at the opening of a new candle
    {
      matrix input_data_matrix = GetXVars(cnn_data_window); //get data for the past 10 days(default)
      input_data_matrix = scaler.transform(input_data_matrix); //applying StandardSCaler to the input data
      
      int signal = cnn.predict_bin(input_data_matrix, classes_in_y); //getting trade signal from the RNN model
     
      Comment("Signal==",signal);
     
   //---
     
      MqlTick ticks;
      SymbolInfoTick(Symbol(), ticks);
      
      if (signal==1) //if the signal is bullish
       {
          if (!PosExists(POSITION_TYPE_BUY)) //There are no buy positions
           {
             if (!m_trade.Buy(lotsize, Symbol(), ticks.ask, 0, 0)) //Open a buy trade
               printf("Failed to open a buy position err=%d",GetLastError());

             ClosePosition(POSITION_TYPE_SELL); //close opposite trade
           }
       }
      else if (signal==0) //Bearish signal
        {
          if (!PosExists(POSITION_TYPE_SELL)) //There are no Sell positions
            if (!m_trade.Sell(lotsize, Symbol(), ticks.bid, 0, 0)) //open a sell trade
               printf("Failed to open a sell position err=%d",GetLastError());
               
               ClosePosition(POSITION_TYPE_BUY); 
        }
      else //There was an error
        return;
    }
  }

戦略は単純です。特定のシグナル、例えば買いシグナルを受信したら、ストップロスや利益確定値を設定せずに買い取引を開始し、その後反対側のシグナルを決済し、売りシグナルの場合はその逆となります。

最後に、この戦略をEURUSDという訓練済みの銘柄で10年間テストしてみました。2014.01.01から2024.05.27まで4時間足チャートで各バーの始値を表示します。

convNet EAストラテジーテスター設定

ストラテジーテスターの結果は傑出していました。

ConvNet EAストラテジーテスターレポート

ConvNet EA テスターグラフ

EAは全体の58%の確率で正確な予測をおこない、その結果、CNNベースのEAは503ドルの純利益を上げました。


結論

畳み込みニューラルネットワーク(CNN)は、本来画像や映像の処理に特化して設計されたにもかかわらず、為替データのような表形式のデータに対しても効果的に採用されています。CNNはパターンを検出し、それを基に外国為替市場での予測が可能です。

ストラテジーテスターのレポートが示すように、CNNベースのEAは正確な予測をおこないました。CNNモデルが4つの独立変数(OHLC)のみを入力としていることを考慮すると、線形回帰、サポートベクターマシン、ナイーブベイズなどの従来の表データ向けモデルでは、この精度の予測は困難であったと考えられます。私の経験から言えば、少数の変数しかない場合に、これほど優れた性能を発揮するモデルは多くはありません。

ご精読ありがとうございました。


機械学習モデルの開発を追跡し、本連載で議論されている多くのことは、このレポに掲載されています。

添付ファイルの表

ファイル名
ファイルタイプ 説明と使用法
 ConvNet EA.mq5
 EA          ONNX形式でCNNモデルをロードし、MetaTrader 5で最終的な取引戦略をテストするための取引ロボット
 cnn.EURUSD.D1.onnx
 ONNX  ONNX形式のCNNモデル
 cnn.EURUSD.D1.standard_scaler_mean.bin  
 cnn.EURUSD.D1.standard_scaler_scale.bin
 バイナリファイル   標準化スケーラー用バイナリファイル
 preprocessing.mqh
 インクルードファイル
 標準化スケーラーで構成されるライブラリ
 ConvNet.mqh
 インクルードファイル   CNNモデルをONNX形式で読み込み、配置するためのライブラリ 
 cnn-for-trading-applications-tutorial.ipynb
 Pythonスクリプト/Jupyterノートブック   この記事で取り上げたすべてのpythonコードで構成される 


情報源と参考文献


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

添付されたファイル |
Attachments.zip (132.38 KB)
MQL5取引ツールキット(第2回):ポジション管理EX5ライブラリの拡張と実装 MQL5取引ツールキット(第2回):ポジション管理EX5ライブラリの拡張と実装
MQL5コードやプロジェクトでEX5ライブラリをインポートして使用する方法をご紹介します。今回は、既存のライブラリにポジション管理関数を追加し、2つのエキスパートアドバイザー(EA)を作成することで、EX5ライブラリを拡張します。最初の例では、可変指数ダイナミック平均(VIDyA: Variable Index Dynamic Average)テクニカル指標を使用して、トレーリングストップ取引戦略EAを開発し、2番目の例では、取引パネルを使用して、ポジションの監視、オープン、クローズ、および修正をおこないます。この2つの例では、アップグレードされたEX5ポジション管理ライブラリの使用方法と実装方法を紹介します。
Candlestick Trend Constraintモデルの構築(第7回):EA開発モデルの改良 Candlestick Trend Constraintモデルの構築(第7回):EA開発モデルの改良
今回は、エキスパートアドバイザー(EA)開発のための指標の詳細な準備について掘り下げていきます。議論の中では、現行バージョンの指標にさらなる改良を加えることで、その精度と機能性の向上を図ります。さらに、前バージョンがエントリポイントの識別に限られていた制約に対応するため、新たにエグジットポイントを特定する機能を導入します。
古典的な戦略をPythonで再構築する(第2回):ボリンジャーバンドのブレイクアウト 古典的な戦略をPythonで再構築する(第2回):ボリンジャーバンドのブレイクアウト
本稿では、線形判別分析(LDA: Linear Discriminant Analysis)とボリンジャーバンドを統合し、戦略的市場参入シグナルの生成を目的としたカテゴリ別ゾーン予測を活用する取引戦略を考察します。
MQL5とPythonで自己最適化EAを構築する MQL5とPythonで自己最適化EAを構築する
この記事では、市況に基づいて取引戦略を自律的に選択変更できるエキスパートアドバイザー(EA)を構築する方法について解説します。マルコフ連鎖の基本を学び、それがアルゴリズムトレードにどのように役立つかを探っていきます。