English Русский 中文 Español 日本語 Português
preview
Datenwissenschaft und ML (Teil 27): Convolutional Neural Networks (CNNs) in MetaTrader 5 Trading Bots — funktioniert das?

Datenwissenschaft und ML (Teil 27): Convolutional Neural Networks (CNNs) in MetaTrader 5 Trading Bots — funktioniert das?

MetaTrader 5Experten | 19 September 2024, 10:28
134 0
Omega J Msigwa
Omega J Msigwa

Die Pooling-Operation, die in einem faltenden neuronalen Netzwerk verwendet wird, ist ein großer Fehler, und die Tatsache, dass sie so gut funktioniert, ist eine Katastrophe.

Geoffrey Hinton

Inhalt


Ein grundlegendes Verständnis der Programmiersprache Python, künstlicher neuronaler Netze, maschinellem Lernen und ONNX in MQL5 ist erforderlich, um den Inhalt dieses Artikels vollständig zu verstehen.


Was sind faltende neuronale Netzwerke (Convolutional Neural Networks, CNNS)?

Die faltende neuronale Netzwerke (Convolutional Neural Networks, CNNs) sind eine Klasse von Deep-Learning-Algorithmen, die speziell für die Verarbeitung strukturierter gitterartiger Daten wie Bilder, Audiospektrogramme und Zeitreihendaten entwickelt wurden. Sie eignen sich besonders gut für visuelle Datenaufgaben, da sie automatisch und adaptiv räumliche Hierarchien von Merkmalen aus den Eingabedaten lernen können.

CNNs sind die erweiterte Version der künstlichen neuronalen Netze (ANN). Sie werden in erster Linie zur Extraktion der Merkmale aus dem gitterartigen Matrixdatensatz verwendet. Zum Beispiel visuelle Datensätze wie Bilder oder Videos, bei denen Datenmuster eine große Rolle spielen.

Die faltende neuronale Netzwerke haben mehrere Schlüsselkomponenten wie Faltungsschichten, Aktivierungsfunktionen, Pooling-Schichten, vollständig verbundene Schichten und Dropout-Schichten. Um CNNs im Detail zu verstehen, müssen wir die einzelnen Komponenten auseinander nehmen und sehen, worum es geht.

faltendes neuronales Netzwerk - Illustration


Faltungsschichten

Dies sind die Kernbausteine von CNNs, hier findet der Großteil der Berechnungen statt. Faltungsschichten sind für die Erkennung lokaler Muster in den Eingabedaten zuständig, wie z. B. Kanten in Bildern. Dies kann durch den Einsatz von Filtern (oder Kerneln) erreicht werden, die über die Eingabedaten gleiten, um Merkmalskarten zu erstellen.

Eine Faltungsschicht ist eine verborgene Schicht, die mehrere Faltungseinheiten in einem faltendes neuronales Netzwerk enthält, das zur Merkmalsextraktion verwendet wird.

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

Filter/Kerne

Filter (oder Kernel) sind kleine lernfähige quadratische Matrizen (in der Regel der Größe 3x3, 5x5 usw.), die über die Eingabedaten gleiten, um lokale Muster zu erkennen.

Wie funktionieren sie?

Sie arbeiten, indem sie sich über die Eingabedaten bewegen und dann eine elementweise Multiplikation zwischen den Filterwerten und den Eingabewerten innerhalb des aktuellen Empfangsfeldes des Filters durchführen, gefolgt von der Summierung der Ergebnisse. Dieser Vorgang wird als Faltung bezeichnet.

Während des Trainings lernt das Netz die optimalen Werte der Filter. In den ersten Schichten lernen die Filter in der Regel, einfache Merkmale wie Kanten und Texturen zu erkennen, während die Filter in den tieferen Schichten komplexere Muster wie Formen und Objekte erkennen können.

Nehmen wir einen einfachen 3x3-Filter und ein 5x5-Eingabebild. Der Filter gleitet über das Bild und berechnet die Faltungsoperation, um eine Merkmalskarte zu erstellen.

Conv-Netz-Kernel



Stride

Dies ist ein weiteres Merkmal, das in der Faltungsschicht zu finden ist. Der Stride ist die Schrittweite, mit der sich der Filter über die Eingabedaten bewegt. Er bestimmt, wie stark der Filter bei jedem Schritt während des Faltungsprozesses verschoben wird.

Wie funktionieren das?

Schrittweite von 1, Der Filter bewegt sich jeweils um eine Einheit, was zu einer stark überlappenden und detaillierten Merkmalskarte führt. Dies führt zu einer größeren Ausgabekarte.

Stride von 2 oder mehr, Der Filter überspringt Einheiten, was zu einer weniger detaillierten, aber kleineren Ausgabe-Merkmalskarte führt. Dadurch werden die räumlichen Dimensionen der Ausgabe reduziert und die Eingabe effektiv heruntergetastet.

Wenn Sie z. B. einen 3x3-Filter und ein 5x5-Eingabebild mit einer Schrittweite von 1 haben, bewegt sich der Filter jeweils um ein Pixel und erzeugt eine 3x3-Ausgabe-Merkmalskarte. Mit einer Schrittweite von 2 verschiebt der Filter jeweils zwei Pixel, wodurch eine 2x2-Ausgabe-Merkmalskarte entsteht.


Padding

Beim Auffüllen (Padding) werden zusätzliche Pixel (in der Regel Nullen) am Rand der Eingabedaten hinzugefügt. Dadurch wird sichergestellt, dass der Filter richtig passt und die räumlichen Dimensionen der ausgegebenen Merkmalskarte kontrolliert werden.

Arten des Padding

Laut Keras gibt es drei Arten von Paddings (Groß- und Kleinschreibung beachten).

  1. valid - es wird keine Padding vorgenommen,
  2. same - füllt die Eingabe auf, sodass die Ausgabegröße der Eingabegröße entspricht, wenn strides=1.
  3. causal - wird für zeitliche Daten verwendet, um sicherzustellen, dass die Ausgabe im Zeitschritt 𝑡 nicht von zukünftigen Eingaben abhängt.

Das Padding hilft dabei, die räumlichen Dimensionen der Eingabedaten zu erhalten. Ohne Padding schrumpft die Ausgabekarte mit jeder Faltungsschicht, was zum Verlust wichtiger Kanteninformationen führen kann.

Durch das Hinzufügen eines Paddings kann das Netz Kantenmerkmale effektiv lernen und die räumliche Auflösung der Eingabe beibehalten.

Nehmen wir einen 3x3-Filter und ein 5x5-Eingabebild. Bei einem gültigen Padding (kein Padding) ist die ausgegebene Merkmalskarte 3x3. Mit dem gleichen Padding könnten Sie einen Rahmen aus Nullen um die Eingabe hinzufügen, sodass sie 7x7 wird. Die ausgegebene Merkmalskarte ist dann 5x5, wobei die Eingabedimensionen erhalten bleiben.

Nachfolgend finden Sie den Code für eine Faltungsschicht in 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])
                )
         )


Aktivierungsfunktionen

Wie im Artikel Entmystifizierte Feed Forward Neurale Netzwerke beschrieben, ist eine Aktivierungsfunktion eine mathematische Funktion, die eine Eingabe annimmt und eine Ausgabe verarbeitet.

Die Aktivierungsfunktion wird elementweise angewendet, um Nichtlinearität in das Modell einzuführen. Zu den häufig verwendeten Aktivierungsfunktionen in CNNs gehören ReLU (Rectified Linear Unit), Sigmoid und TanH.


Pooling-Schichten

Diese auch als Desample-Schichten bezeichneten Schichten sind ein wesentlicher Bestandteil von CNNs, da sie für die Reduzierung der räumlichen Dimension der Eingabedaten in Bezug auf Breite und Höhe verantwortlich sind, während die wichtigsten Informationen erhalten bleiben.

Wie funktionieren sie?

Zunächst werden die Eingabedaten in sich überschneidende Regionen oder Fenster unterteilt, dann wird eine Aggregationsfunktion wie Max-Pooling oder Average-Pooling auf jedes Fenster angewendet, um einen einzigen Wert zu erhalten.

Beim Max-Pooling wird der maximale Wert aus einer Reihe von Werten innerhalb eines Filterbereichs ermittelt. Sie reduziert die räumliche Ausdehnung der Daten, was zu einer Verringerung des Rechenaufwands und der Anzahl der Parameter führt.

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)

Diese Ebene nimmt den Maximalwert aus jedem 2-Elemente-Fenster.

Max-Pooling

Weitere Informationen

Beim Durchschnitts-Pooling wird der Durchschnittswert aus einer Reihe von Werten innerhalb eines Filterbereichs ermittelt. Wird weniger häufig verwendet als Max-Pooling.

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)

Diese Ebene nimmt den Durchschnittswert aus jedem 2-Elemente-Fenster.

Durchschnitts-Pooling

Weitere Informationen

Warum eine 1D-Faltungsschicht?

Es gibt Conv1D-, Conv2D- und Conv3D-Schichten für CNNs. Die 1D-Faltungsschicht ist für diese Art von Problem geeignet, da sie für eindimensionale Daten konzipiert ist und sich daher für sequentielle Daten oder Zeitreihen eignet. Andere Faltungsschichten wie die Conv2D und die Conv3D sind für diese Art von Problem zu komplex.


Vollständig verbundene Schichten

Neuronen in einer vollständig verbundenen Schicht haben Verbindungen zu allen Aktivierungen in der vorherigen Schicht. Diese Schichten werden in der Regel gegen Ende des Netzes verwendet, um eine Klassifizierung oder Regression auf der Grundlage der von den Faltungsschichten und Pooling-Schichten extrahierten Merkmale durchzuführen.

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

Der Flatten-Layer wandelt die 1D-Pool-Merkmalskarte in einen 1D-Vektor um, sodass sie in die vollständig verknüpften (dichten) Schichten eingespeist werden kann.

Dichte Schichten (Dense) sind vollständig verknüpfte Schichten, die dazu dienen, endgültige Entscheidungen auf der Grundlage der von den Faltungsschichten und Pooling-Schichten extrahierten Merkmale zu treffen. Dichte Schichten sind im Wesentlichen der Kernbestandteil herkömmlicher künstlicher neuronaler Netze (ANN).

dichte Schicht in einem CNN hervorgehoben




Dropout-Schichten

Die Dropout-Schicht wirkt wie eine Maske, die die Beiträge einiger Neuronen zur nachfolgenden Schicht eliminiert, während die Funktionalität aller anderen Neuronen erhalten bleibt. Wenn wir eine Dropout-Schicht auf den Eingabevektor anwenden, werden einige seiner Merkmale eliminiert; wenn wir sie jedoch auf eine versteckte Schicht anwenden, werden einige versteckte Neuronen eliminiert.

Da sie eine Überanpassung der Trainingsdaten verhindern, sind Dropout-Schichten für das Training von CNNs entscheidend. Fehlen sie, hat der erste Satz von Trainingsmustern einen übermäßig großen Einfluss auf das Lernen. Dies hätte zur Folge, dass Merkmale, die erst in späteren Proben oder Chargen auftreten, nicht gelernt würden.

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


Warum sollte man faltende neuronale Netzwerke (Convolutional Neural Networks, CNNs) für Finanzanalysen und Handelsanwendungen verwenden?

CNNs sind in der Bild- und Videoverarbeitung weit verbreitet, da sie für diesen Zweck entwickelt wurden. Wenn Sie sich die obigen Erklärungen ansehen, werden Sie vielleicht feststellen, dass ich mich auf die Verwendung von CNNs beziehe, wenn ich mit Bildklassifizierungen und ähnlichem arbeite.

Die Verwendung von faltenden neuronalen Netzwerks (CNNs) für tabellarische Daten, wie z. B. Finanzanalysen, mag im Vergleich zur Verwendung anderer faltendes neuronales Netzwerkypen wie Feed Forward Neural Networks (FFNN), Recurrent Neural Networks (RNNs), Long Short-Term Memory (LSTMs) und Gated Recurrent Units (GRUs) unkonventionell erscheinen. Es gibt jedoch mehrere Gründe und potenzielle Vorteile, die im Folgenden für den Einsatz von CNNs in diesem Zusammenhang genannt werden.


01. CNNs sind hervorragend in der Lage, automatisch lokale Muster aus Daten zu extrahieren

Finanzdaten weisen häufig lokale zeitliche Muster auf, wie z. B. Trends und saisonale Schwankungen. Durch die Behandlung der Daten als Zeitreihe. CNNs können diese lokalen Abhängigkeiten und Wechselwirkungen zwischen Merkmalen lernen, die bei herkömmlichen Methoden möglicherweise übersehen werden.

02. Sie können hierarchische Merkmale lernen

Mehrere Schichten in CNNs ermöglichen es ihnen, komplexe Merkmalshierarchien zu lernen. Frühe Schichten können einfache Muster erkennen, während tiefere Schichten diese einfachen Muster zu komplexeren und abstrakteren Darstellungen kombinieren können. Dieses hierarchische Merkmalslernen kann bei der Erfassung komplizierter Muster in Finanzdaten von Vorteil sein.

03. Sie können robust gegenüber Rauschen und redundanten Merkmalen sein.

Wie wir wissen, enthalten Finanzdatensätze verrauschte und redundante Daten. Die Pooling-Schichten in CNNs tragen dazu bei, das Modell robust gegenüber leichten Schwankungen und Rauschen zu machen, da sie die Merkmalskarten nach unten abtasten und den Einfluss geringer Schwankungen verringern.

04: CNNs können multivariate Zeitreihen gut verarbeiten

Finanzdaten umfassen oft mehrere korrelierte Zeitreihen, z. B. unterschiedliche Aktienkurse und Handelsvolumina. CNNs können die Wechselwirkungen und Abhängigkeiten zwischen diesen mehreren Zeitreihen effektiv erfassen und eignen sich daher für multivariate Zeitreihenprognosen.

05. Sie sind rechnerisch effizient für hochdimensionale Daten

Finanzdaten können eine hohe Dimensionalität aufweisen (viele Merkmale). Durch Gewichtsteilung und lokale Konnektivität sind CNNs rechnerisch effizienter als vollständig verbundene neuronale Netze, wodurch sie für hochdimensionale Daten skalierbar sind.

06. CNNs ermöglichen durchgängiges Lernen

CNNs können direkt aus Rohdaten lernen und Vorhersagen treffen, ohne dass eine manuelle Bearbeitung der Merkmale erforderlich ist. Dieser durchgängige Lernansatz kann den Modellierungsprozess vereinfachen und potenziell zu einer besseren Leistung führen, da das Modell die relevantesten Merkmale ermitteln kann.

07. CNNs wenden Faltungsoperationen an, die bei bestimmten Datentypen von Vorteil sein können.

Durch Faltungsoperationen können wichtige Signale in den Daten, wie plötzliche Veränderungen oder bestimmte Muster, erkannt und verstärkt werden. Dies ist besonders bei der Finanzanalyse nützlich, wo das Erkennen plötzlicher Marktveränderungen oder -muster entscheidend sein kann.

Da wir nun stichhaltige Gründe für den Einsatz von CNNs in Handelsanwendungen haben, wollen wir ein CNN erstellen und trainieren und dann sehen, wie wir ein CNN in einem Meta Trader 5 Expert Advisor (EA) verwenden können.


Erstellung eines faltendes neuronales Netzwerks (CNN) in Python

Dies umfasst mehrere Schritte, nämlich.

  1. Sammeln der Daten
  2. Aufbereitung der Daten für ein CNN-Modell
  3. Training eines CNN-Modells
  4. Speichern eines CNN-Modells im ONNX-Format

01: Erfassen der Daten

Wir verwenden die Daten für die Zeitreihenprognose, die wir in den vorherigen Artikeln verwendet haben.

Datensatz für Zeitreihenprognosen

Da wir nun wissen, dass faltende neuronale Netzwerke (CNNs) gut darin sind, Muster in hochdimensionalen Daten zu erkennen, können wir, ohne das Modell zu verkomplizieren, einige der Merkmale auswählen, von denen ich glaube, dass sie viele Muster enthalten, die das CNN-Modell erkennen kann.

Python-Code

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)

Kurz nach der Vorbereitung der Zielvariablen auf der Grundlage von TARGET_OPEN und TARGET_CLOSE, die jeweils Eröffnungs- und Schließwerte sind, wird ein Balken vorwärts gesammelt. Wir haben eine Mini-Datensatzversion mit dem Namen new_df erstellt, die nur 4 unabhängige Variablen mit den Werten OPEN, HIGH und LOW und eine abhängige Variable mit dem Namen TARGET_VAR enthält.


02: Aufbereitung der Daten für ein CNN-Modell

Zunächst müssen wir die Eingabedaten vorverarbeiten, indem wir sie zu Fenstern umformen und ausrichten. Dies ist bei der Arbeit mit tabellarischen Daten in CNNs sehr wichtig, und zwar aus folgenden Gründen.

Da es sich bei den Handelsdaten um sequenzielle Daten handelt, ergeben sich die Muster oft über eine Reihe von Zeitschritten und nicht zu einem einzigen Zeitpunkt. Indem wir überlappende Datenfenster erstellen, können wir zeitliche Abhängigkeiten erfassen und dem CNN-Modell einen Kontext geben.

Außerdem erwarten CNNs, dass die Eingabedaten eine bestimmte Form haben. Für 1D-Faltungsschichten sollte die Eingabestruktur in der Regel über (Anzahl der Fenster, Fenstergröße, Anzahl der Merkmale) verfügen. Diese Form ähnelt derjenigen, die wir in der Zeitreihenanalyse mit rekurrenten neuronalen Netzen (RNNs) im vorherigen Artikel verwendet haben. Das nun folgende Vorverarbeitungsverfahren stellt sicher, dass die Daten in diesem Format vorliegen, sodass sie für die Eingabe in ein CNN-Modell geeignet sind.

# 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}")

Ausgabe

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

Da unsere Daten in einem täglichen Zeitrahmen gesammelt wurden, zeigt die Fenstergröße von 10 an, dass wir das CNN-Modell trainieren werden, um Muster innerhalb von 10 Tagen zu verstehen.


Dann müssen wir die Daten in Trainings- und Testproben aufteilen.

# 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}")

Ausgabe

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

y_train (792,) y_test (198,)


Schließlich müssen wir die Zielvariable für diese Klassifizierungsaufgabe einmalig kodieren.

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}")

Ausgabe

One hot encoded

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


03: Training eines CNN-Modells

Hier wird die meiste Arbeit erledigt.

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

Ausgabe

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 │
└─────────────────────────────────┴────────────────────────┴───────────────┘

Das Training wurde bei der 34. Epoche beendet.

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-Trainings-Validierungs-Verlustkurve

Das Modell war in etwa 57 % der Fälle bei Vorhersagen außerhalb der Stichprobe genau.

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

Ausgabe

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

Unser CNN-Modell ist gut genug für einen Expert Advisor. Bevor wir jedoch mit der Kodierung eines EA beginnen können, müssen wir das trainierte CNN-Modell im ONNX-Format speichern.


04: Speichern eines CNN-Modells im ONNX-Format.

Das Verfahren ist recht einfach. Wir müssen das CNN-Modell im .onnx-Format und die Parameter der Skalierungstechnik in Binärdateien speichern.

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


Erstellung eines Handelsroboters auf der Grundlage eines faltendes neuronales Netzwerks (CNN)

Innerhalb eines Expert Advisors müssen wir zunächst das ONNX-formatierte Modell und die Binärdateien des Standard Skalierer als Ressourcen einbinden.

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[]

Wir müssen sie beide initialisieren, den Skalierer und das Onnx-Modell.

#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);
  }

Das reicht aus, um das Modell zum Laufen zu bringen. Die Funktion zum Extrahieren von Daten soll so gestaltet werden, wie die unabhängigen Variablen beim Training verwendet wurden. Wir verwendeten vier Variablen OHLC-Werte von dem vorherigen geschlossenen Balken zu 10 Balken vorher, die die Fenstergröße und der Zeitrahmen muss beibehalten werden (der tägliche Zeitrahmen).

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;
 }

Da wir nun eine Funktion zur Erfassung der unabhängigen Variablen haben, können wir unsere Handelsstrategie fertigstellen.

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;
    }
  }

Die Strategie ist einfach. Wenn wir ein bestimmtes Signal erhalten, z. B. ein Kaufsignal, eröffnen wir eine Kaufposition ohne Stop-Loss- und Take-Profit-Werte und schließen dann das entgegengesetzte Signal und umgekehrt für ein Verkaufssignal.

Schließlich habe ich diese Strategie auf einem Symbol getestet, auf dem sie trainiert wurde, nämlich EURUSD, und zwar zehn Jahre lang. Vom 01.01.2014 bis zum 27.05.2024 auf einem 4-Stunden-Chart auf den Eröffnungskursen eines jeden Balkens.

convNet EA-Strategie-Tester-Konfiguration

Die Ergebnisse der Strategietester waren hervorragend.

ConvNet EA Strategie-Tester-Bericht

ConvNet EA-Tester-Grafik

Der EA traf in 58 % aller Fälle die richtigen Vorhersagen, sodass der auf CNN basierende EA einen Nettogewinn von 503 Dollar erzielte.


Die Quintessenz

Obwohl es speziell für die Bild- und Videoverarbeitung entwickelt wurde, kann es auch für die Verarbeitung von Tabellendaten, wie z. B. die von uns zur Verfügung gestellten Devisendaten, eingesetzt werden. Faltende neuronale Netzwerke (CNN) sind in der Lage, Muster zu erkennen und sie für Vorhersagen auf dem Devisenmarkt zu nutzen.

Das geht aus dem Bericht des Strategietesters hervor. Ich wette, dass viele traditionelle Modelle, die für Tabellendaten entwickelt wurden, wie z. B. lineare Regression, Support Vector Machine, Naive Bayes usw., diese Vorhersagegenauigkeit nicht erreichen können, wenn man bedenkt, dass dem CNN-Modell nur 4 unabhängige Variablen (OHLC) gegeben wurden. Meiner Erfahrung nach können nicht viele Modelle so gut werden, wenn man ein paar Variablen beachtet.

Mit freundlichen Grüßen.


Verfolgen Sie die Entwicklung von Modellen für maschinelles Lernen und vieles mehr in dieser Artikelserie auf GitHub repo.

Tabelle der Anhänge

Dateiname
Dateityp Beschreibung und Verwendung
 ConvNet EA.mq5
 Expert Advisor          Handelsroboter zum Laden des CNN-Modells im ONNX-Format und Testen der endgültigen Handelsstrategie im MetaTrader 5.
 cnn.EURUSD.D1.onnx
 ONNX  CNN-Modell im ONNX-Format.
 cnn.EURUSD.D1.standard_scaler_mean.bin  
 cnn.EURUSD.D1.standard_scaler_scale.bin
 Binärdateien   Binärdateien für den Normierungsscaler
 preprocessing.mqh
 Eine Include-Datei
 Eine Bibliothek, die aus dem Standardskalierer besteht
 ConvNet.mqh
 Eine Include-Datei   Eine Bibliothek zum Laden und Bereitstellen von CNN-Modellen im ONNX-Format 
 cnn-for-trading-applications-tutorial.ipynb
 Python-Skript/Jupyter-Notebook   Besteht aus dem gesamten Python-Code, der in diesem Artikel besprochen wird 


Quellen und Referenzen


Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/15259

Beigefügte Dateien |
Attachments.zip (132.38 KB)
MQL5 Handels-Toolkit (Teil 2): Erweiterung und Implementierung der Positionsmanagement EX5-Bibliothek MQL5 Handels-Toolkit (Teil 2): Erweiterung und Implementierung der Positionsmanagement EX5-Bibliothek
Erfahren Sie, wie Sie EX5-Bibliotheken in Ihren MQL5-Code oder Ihre Projekte importieren und verwenden können. In diesem Fortsetzungsartikel werden wir die EX5-Bibliothek erweitern, indem wir weitere Positionsmanagement-Funktionen zur bestehenden Bibliothek hinzufügen und zwei Expert Advisors erstellen. Im ersten Beispiel wird der Variable Index Dynamic Average Technical Indicator verwendet, um einen Expert Advisor für eine Trailing-Stop-Handelsstrategie zu entwickeln, während im zweiten Beispiel ein Handelspanel zum Überwachen, Öffnen, Schließen und Ändern von Positionen verwendet wird. Diese beiden Beispiele zeigen, wie die erweiterte EX5-Positionsmanagement-Bibliothek verwendet und implementiert werden kann.
Selbstoptimierende Expert Advisors mit MQL5 und Python erstellen Selbstoptimierende Expert Advisors mit MQL5 und Python erstellen
In diesem Artikel werden wir erörtern, wie wir Expert Advisors erstellen können, die in der Lage sind, Handelsstrategien auf der Grundlage der vorherrschenden Marktbedingungen eigenständig auszuwählen und zu ändern. Wir werden etwas über Markov-Ketten lernen und wie sie algorithmischen Händler helfen können.
Klassische Strategien neu interpretieren (Teil II): Bollinger-Bänder Ausbrüche Klassische Strategien neu interpretieren (Teil II): Bollinger-Bänder Ausbrüche
Dieser Artikel untersucht eine Handelsstrategie, die die lineare Diskriminanzanalyse (LDA) mit Bollinger-Bändern integriert und kategorische Zonenvorhersagen für strategische Markteinstiegssignale nutzt.
Aufbau des Kerzenmodells Trend-Constraint (Teil 7): Verfeinerung unseres Modells für die EA-Entwicklung Aufbau des Kerzenmodells Trend-Constraint (Teil 7): Verfeinerung unseres Modells für die EA-Entwicklung
In diesem Artikel werden wir uns mit der detaillierten Vorbereitung unseres Indikators für die Entwicklung von Expert Advisor (EA) befassen. Unsere Diskussion wird weitere Verfeinerungen der aktuellen Version des Indikators umfassen, um seine Genauigkeit und Funktionsweise zu verbessern. Außerdem werden wir neue Funktionen einführen, die Ausstiegspunkte markieren und damit eine Einschränkung der Vorgängerversion beheben, die nur Einstiegspunkte kennzeichnete.