
Trendvorhersage mit LSTM für Trendfolgestrategien
Einführung
Long Short-Term Memory (LSTM) ist eine Art rekurrentes neuronales Netz (RNN), das für die Modellierung sequenzieller Daten entwickelt wurde, indem es langfristige Abhängigkeiten effektiv erfasst und das Problem des verschwindenden Gradienten löst. In diesem Artikel werden wir untersuchen, wie LSTM zur Vorhersage zukünftiger Trends eingesetzt werden kann, um die Leistung von Trendfolgestrategien zu verbessern. Der Artikel behandelt die Einführung von Schlüsselkonzepten und die Motivation hinter der Entwicklung, das Abrufen von Daten aus dem MetaTrader 5, die Verwendung dieser Daten zum Trainieren des Modells in Python, die Integration des maschinellen Lernmodells in MQL5 und die Reflexion der Ergebnisse und zukünftigen Bestrebungen auf der Grundlage von statistischem Backtesting.
Motivation
Intuitiv betrachtet, profitieren Trendfolgestrategien von Gewinnen in Märkten, die sich im Aufwärtstrend befinden, schneiden aber in unruhigen Märkten schlecht ab, in denen die Strategie mit einem Aufschlag gekauft und mit einem Abschlag verkauft wird. Wissenschaftliche Untersuchungen haben gezeigt, dass klassische Trendfolgestrategien, wie das Goldene Kreuz, über lange Zeiträume hinweg auf verschiedenen Märkten und Zeitrahmen funktionieren. Diese Strategien sind zwar nicht hochprofitabel, haben aber beständige Gewinne gezeigt. Trendfolgestrategien profitieren in der Regel von extremen Ausreißern, die deutlich höhere Gewinne als die durchschnittlichen Verluste erzielen. Der enge Stop-Loss und der Ansatz, Gewinne laufen zu lassen, führen zu einer niedrigen Gewinnrate, aber zu einem hohen Chance-Risiko-Verhältnis pro Handel.
LSTM (Long Short-Term Memory) ist ein spezieller Typ eines rekurrenten neuronalen Netzes (RNN), das für die Erfassung langfristiger Abhängigkeiten in sequentiellen Daten entwickelt wurde. Es nutzt Speicherzellen, die Informationen über lange Zeiträume hinweg aufrechterhalten können, und überwindet so das Problem des verschwindenden Gradienten, von dem herkömmliche RNNs betroffen sind. Diese Fähigkeit, Informationen aus einer früheren Sequenz zu speichern und darauf zuzugreifen, macht LSTM besonders effektiv für Aufgaben wie Zeitreihenprognosen und Trendvorhersagen. Bei Regressionsproblemen kann das LSTM die zeitlichen Beziehungen zwischen den Eingabemerkmalen modellieren und kontinuierliche Ausgaben mit hoher Genauigkeit vorhersagen, was es ideal für Vorhersageanwendungen macht.
Die Motivation für diesen Artikel ist es, die Leistungsfähigkeit von LSTM für die Trendregression zu nutzen, um zukünftige Handelsgeschäfte vorherzusagen und potenziell schlechte Trades herauszufiltern, die aus einer geringen Trendstärke resultieren. Diese Motivation beruht auf der Hypothese, dass Trendfolgestrategien auf trendigen Märkten besser abschneiden als auf Märkten mit einem abweichenden Trend.
Wir werden den ADX (Average Directional Index) verwenden, um die Trendstärke anzuzeigen, da er einer der beliebtesten Indikatoren für die Beurteilung der aktuellen Trendstärke ist. Wir versuchen, seinen zukünftigen Wert vorherzusagen, anstatt seinen aktuellen Wert zu verwenden, da ein hoher ADX in der Regel darauf hinweist, dass ein Trend bereits eingetreten ist oder zu Ende geht, sodass unser Einstiegspunkt zu spät ist, um davon zu profitieren.
Der ADX wird wie folgt berechnet:
Datenaufbereitung und Vorverarbeitung
Bevor wir Daten abrufen, müssen wir zunächst klären, welche Daten benötigt werden. Wir planen, mehrere Merkmale zu verwenden, um ein Regressionsmodell zu trainieren, das zukünftige ADX-Werte vorhersagt. Zu diesen Merkmalen gehören der RSI, der die aktuelle relative Stärke des Marktes anzeigt, der prozentuale Ertrag der letzten Kerze, der als stationärer Wert des Schlusskurses dient, und der ADX selbst, der für den Wert, den wir vorhersagen wollen, direkt relevant ist. Wir haben soeben die Intuition erklärt, die hinter der Auswahl dieser Merkmale steht. Sie können die Merkmale selbst bestimmen, aber achten Sie darauf, dass sie angemessen und stationär sind. Wir planen, das Modell mit stündlichen Daten von 2020.1.1 bis 2024.1.1 zu trainieren und die Leistung des Modells von 2024.1.1-2025.1.1 als Out-of-Sample-Test zu testen.
Nachdem wir nun geklärt haben, welche Daten wir abrufen wollen, können wir einen Expertenberater zum Abrufen dieser Daten erstellen.
Wir werden die in diesem Artikel vorgestellte Klasse CFileCSV verwenden, um das Array als String in einer CSV-Datei zu speichern. Der Code für diesen Vorgang ist recht einfach, wie unten gezeigt.
#include <FileCSV.mqh> CFileCSV csvFile; int barsTotal = 0; int handleRsi; int handleAdx; string headers[] = { "time", "ADX", "RSI", "Stationary" }; string data[1000000][4]; int indexx = 0; vector xx; input string fileName = "XAU-1h-2020-2024.csv"; input bool SaveData = true; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() {//Initialize model handleRsi = iRSI(_Symbol,PERIOD_CURRENT,14,PRICE_CLOSE); handleAdx = iADX(_Symbol,PERIOD_CURRENT,14); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if (!SaveData) return; if(csvFile.Open(fileName, FILE_WRITE|FILE_ANSI)) { //Write the header csvFile.WriteHeader(headers); //Write data rows csvFile.WriteLine(data); //Close the file csvFile.Close(); } else { Print("File opening error!"); } } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { int bars = iBars(_Symbol,PERIOD_CURRENT); if (barsTotal!= bars){ barsTotal = bars; double rsi[]; double adx[]; CopyBuffer(handleAdx,0,1,1,adx); CopyBuffer(handleRsi,0,1,1,rsi); data[indexx][0] =(string)TimeTradeServer(); data[indexx][1] = DoubleToString(adx[0], 2); data[indexx][2] = DoubleToString(rsi[0], 2); data[indexx][3] = DoubleToString((iClose(_Symbol,PERIOD_CURRENT,1)-iOpen(_Symbol,PERIOD_CURRENT,1))/iClose(_Symbol,PERIOD_CURRENT,1),3); indexx++; } }
Dieser Expert Advisor (EA) ist so konzipiert, dass er die Werte des Relative Strength Index (RSI) und des Average Directional Index (ADX) für ein bestimmtes Symbol verfolgt und aufzeichnet. Der EA verwendet die Funktionen iRSI und iADX, um die aktuellen Werte von RSI und ADX zu ermitteln und sie zusammen mit einem Zeitstempel in einer CSV-Datei zu speichern. Die CSV-Datei wird mit Überschriften für „time“, „ADX“, „RSI“ und „Stationary“ erstellt. Wenn die Option SaveData aktiviert ist, werden die Daten bei der Deinitialisierung in eine Datei (angegeben in fileName) geschrieben. Es erfasst neue Daten bei jedem Tick und speichert sie, wenn sich die Anzahl der Balken ändert.
Führen Sie den Expert Advisor (EA) im Strategy Tester mit einem einzigen Test aus. Nach der Durchführung des Tests sollte die Datei in folgendem Dateipfad gespeichert werden: /Tester/Agent-sth000/MQL5/Files.
Als Nächstes wechseln wir zu Python für die Datenvorverarbeitung als Vorbereitung für das Training unseres maschinellen Lernmodells.
Wir planen, einen überwachten Lernansatz zu verwenden, bei dem das Modell so trainiert wird, dass es ein gewünschtes Ergebnis auf der Grundlage markierter Daten vorhersagt. Der Trainingsprozess beinhaltet die Anpassung der Gewichte verschiedener Operationen, die auf die Merkmale angewendet werden, um den Fehlerverlust zu minimieren und die endgültige Ausgabe zu erzeugen.
Für die Bezeichnung schlage ich vor, den mittleren ADX-Wert der nächsten 10 ADX-Werte zu verwenden. Dieser Ansatz stellt sicher, dass der Trend nicht bereits vollständig etabliert ist, wenn wir in den Handel einsteigen, und verhindert gleichzeitig, dass der Trend zu weit vom aktuellen Signal entfernt ist. Die Verwendung des Mittelwerts der nächsten 10 ADX-Werte ist eine gute Möglichkeit, um sicherzustellen, dass der Trend in den nächsten Balken aktiv bleibt, sodass unser Einstieg die Gewinne aus den bevorstehenden Richtungswechseln mitnehmen kann.
import pandas as pd data = pd.read_csv('XAU-1h-2020-2024.csv', sep=';') data= data.dropna().set_index('time') data['output'] = data['ADX'].shift(-10) data = data[:-10] data['output']= data['output'].rolling(window=10).mean() data = data[9:]
Dieser Code liest die CSV-Datei und trennt die Daten in verschiedene Spalten, da die Werte durch ein Semikolon (;) gruppiert sind. Anschließend werden alle leeren Zeilen entfernt und die Spalte „time“ als Index festgelegt, um sicherzustellen, dass die Daten für das Training chronologisch geordnet sind. Anschließend wird eine neue Spalte mit der Bezeichnung „output“ erstellt, die den Mittelwert der nächsten 10 ADX-Werte berechnet. Danach lässt der Code alle verbleibenden leeren Zeilen fallen, da einige Zeilen möglicherweise nicht genügend zukünftige ADX-Werte aufweisen, um die Ausgabe zu berechnen.
Modelltraining
Dies Diagramm veranschaulicht, was das LSTM während unseres Trainingsprozesses zu erreichen versucht. Die Eingabe soll die Form (sample_amount, time_steps, feature_amount) haben, wobei time_step angibt, wie viele frühere Zeitpunkte der Daten wir zur Vorhersage des nächsten Wertes verwenden wollen. Wir könnten zum Beispiel Daten von Montag bis Donnerstag verwenden, um ein Ergebnis für Freitag vorherzusagen. LSTM nutzt Algorithmen, um Muster in den Zeitreihen und die Beziehung zwischen den Merkmalen und dem Ergebnis zu erkennen. Es erstellt eine oder mehrere Schichten neuronaler Netze, die jeweils aus vielen Gewichtungseinheiten (Neuronen) bestehen, die auf jedes Merkmal und jeden Zeitschritt Gewichte anwenden, um schließlich die endgültige Vorhersage auszugeben.
Der Einfachheit halber können Sie einfach den folgenden Code ausführen, der den Trainingsprozess für Sie übernimmt.
import numpy as np import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense # Assume data is your DataFrame already loaded with the specified columns and a time-based index # data.columns should include ['ADX', 'RSI', 'Stationary', 'output'] # --- Step 1: Data Preparation --- time_step = 5 # Select features and target features = ['ADX', 'RSI', 'Stationary'] target = 'output' # --- Step 2: Create sequences for LSTM input --- def create_sequences(data, target_col, time_step): """ Create sequences of length time_step from the DataFrame. data: DataFrame of input features and target. target_col: Name of the target column. Returns: X, y arrays suitable for LSTM. """ X, y = [], [] feature_cols = data.columns.drop(target_col) for i in range(len(data) - time_step): seq_x = data.iloc[i:i+time_step][feature_cols].values # predict target at the next time step after the sequence seq_y = data.iloc[i+time_step][target_col] X.append(seq_x) y.append(seq_y) return np.array(X), np.array(y) # Create sequences X, y = create_sequences(data, target_col=target, time_step=time_step) # --- Step 3: Split into training and evaluation sets --- # Use a simple 80/20 split for training and evaluation X_train, X_eval, y_train, y_eval = train_test_split(X, y, test_size=0.2, shuffle=False) # --- Step 4: Build the LSTM model --- n_features = len(features) # number of features per time step model = Sequential() model.add(LSTM(50, input_shape=(time_step, n_features))) # LSTM layer with 50 units model.add(Dense(1)) # output layer for regression model.compile(optimizer='adam', loss='mse') model.summary() # --- Step 5: Train the model --- epochs = 50 batch_size = 100 history = model.fit( X_train, y_train, epochs=epochs, batch_size=batch_size, validation_data=(X_eval, y_eval) )
Hier sind einige wichtige Überlegungen für die Ausbildung mit dem oben genannten Code:
-
Vorverarbeitung der Daten: Stellen Sie sicher, dass Ihre Daten in der Vorverarbeitungsphase in zeitlicher Reihenfolge vorliegen. Andernfalls kann es zu einer Verzerrung der Vorausschau kommen, wenn die Daten in Zeitschritte aufgeteilt werden.
-
Datenaufteilung in Training und Test: Wenn wir die Daten in Trainings- und Testsätze aufteilen, dürfen Sie die Daten nicht mischen. Die zeitliche Reihenfolge muss beibehalten werden, um eine Verzerrung durch die Vorausschau zu vermeiden.
-
Komplexität der Modelle: Für die Zeitreihenanalyse, insbesondere bei begrenzten Datenpunkten, ist es nicht notwendig, zu viele Schichten, Neuronen oder Epochen zu konstruieren. Eine Überkomplizierung des Modells könnte zu einer Überanpassung oder zu stark verzerrten Parametern führen. Die im Beispiel verwendeten Einstellungen sollten ausreichend sein.
# --- Step 6: Evaluate the model --- eval_loss = model.evaluate(X_eval, y_eval) print(f"Evaluation Loss: {eval_loss}") # --- Step 7: Generate Predictions and Plot --- # Generate predictions on the evaluation set predictions = model.predict(X_eval).flatten() # Create a plot for predictions vs actual values plt.figure(figsize=(12, 6)) plt.plot(predictions, label='Predicted Output', color='red') plt.plot(y_eval, label='Actual Output', color='blue') plt.title('LSTM Predictions vs Actual Output') plt.xlabel('Sample Index') plt.ylabel('Output Value') plt.legend() plt.show()
Dieser Code sollte den mittleren quadratischen Fehler der Auswertungsmenge im Vergleich zur Vorhersage des Modells wie folgt ausgeben.
Evaluation Loss: 57.405677795410156
Sie wird berechnet durch:
Dabei ist n der Stichprobenumfang, yi der vorausgesagte Wert für jede Stichprobe und y^i der tatsächliche Wert für jedes Bewertungsergebnis.
Wie Sie aus der Berechnung ersehen können, können Sie den Verlust des Modells mit dem Quadrat der Mittelwerte der Dinge, die Sie vorhersagen, vergleichen, um zu prüfen, ob der relative Verlust übermäßig hoch ist. Vergewissern Sie sich auch, dass der Verlust ähnlich wie bei den Trainingsdatensätzen ist, was darauf hindeutet, dass das Modell nicht zu stark an die Trainingsdaten angepasst ist.
Um das Modell schließlich mit MQL5 kompatibel zu machen, wollen wir es im ONNX-Format speichern. Da LSTM-Modelle ONNX-Übergänge nicht direkt unterstützen, müssen wir das Modell zunächst als funktionales Modell speichern und dabei das Format seiner Ein- und Ausgabe explizit festlegen. Danach können wir sie als ONNX-Datei speichern, sodass sie für die zukünftige Verwendung mit MQL5 geeignet ist.
import tensorflow as tf import tf2onnx # Define the input shape based on your LSTM requirements: (time_step, n_features) time_step = 5 n_features = 3 # Create a new Keras Input layer matching the shape of your data inputs = tf.keras.Input(shape=(time_step, n_features), name="input") # Pass the input through your existing sequential model outputs = model(inputs) functional_model = tf.keras.Model(inputs=inputs, outputs=outputs) # Create an input signature that matches the defined input shape input_signature = ( tf.TensorSpec((None, time_step, n_features), dtype=tf.float32, name="input"), ) output_path = "regression2024.onnx" # Convert the functional model to ONNX format onnx_model, _ = tf2onnx.convert.from_keras( functional_model, input_signature=input_signature, # matching the input signature opset=15, output_path=output_path ) print(f"Model successfully converted to ONNX at {output_path}")
Beachten Sie, dass „None“ als Eingabeformat hier bedeutet, dass das Modell eine beliebige Anzahl von Stichproben akzeptieren kann. Es gibt automatisch die entsprechenden Vorhersagen für jede Probe aus und ist damit flexibel für unterschiedliche Chargengrößen.
Aufbau eines Expert Advisors
Nachdem wir nun die ONNX-Modelldatei gespeichert haben, wollen wir sie zur späteren Verwendung in das Verzeichnis /MQL5/Files kopieren.
Wir kehren zum MetaEditor zurück. Wir werden auf einer klassischen Trendfolgestrategie aufbauen, die auf der Logik des goldenen Kreuzes basiert. Dies ist dieselbe, die ich in meinem vorherigen Artikel über maschinelles Lernen implementiert habe. Die grundlegende Logik beinhaltet zwei gleitende Durchschnitte: einen schnellen und einen langsamen. Ein Handelssignal wird generiert, wenn sich die beiden MAs kreuzen und die Handelsrichtung dem schnellen gleitenden Durchschnitt folgt, daher der Begriff „trendfolgend“. Das Ausstiegssignal tritt ein, wenn der Kurs den langsamen gleitenden Durchschnitt überschreitet, was mehr Spielraum für Trailing-Stops bietet. Der vollständige Code lautet wie folgt:
#include <Trade/Trade.mqh> //XAU - 1h. CTrade trade; input int MaPeriodsFast = 15; input int MaPeriodsSlow = 25; input int MaPeriods = 200; input double lott = 0.01; ulong buypos = 0, sellpos = 0; input int Magic = 0; int barsTotal = 0; int handleMaFast; int handleMaSlow; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { trade.SetExpertMagicNumber(Magic); handleMaFast =iMA(_Symbol,PERIOD_CURRENT,MaPeriodsFast,0,MODE_SMA,PRICE_CLOSE); handleMaSlow =iMA(_Symbol,PERIOD_CURRENT,MaPeriodsSlow,0,MODE_SMA,PRICE_CLOSE); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { int bars = iBars(_Symbol,PERIOD_CURRENT); //Beware, the last element of the buffer list is the most recent data, not [0] if (barsTotal!= bars){ barsTotal = bars; double maFast[]; double maSlow[]; CopyBuffer(handleMaFast,BASE_LINE,1,2,maFast); CopyBuffer(handleMaSlow,BASE_LINE,1,2,maSlow); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double lastClose = iClose(_Symbol, PERIOD_CURRENT, 1); //The order below matters if(buypos>0&& lastClose<maSlow[1]) trade.PositionClose(buypos); if(sellpos>0 &&lastClose>maSlow[1])trade.PositionClose(sellpos); if (maFast[1]>maSlow[1]&&maFast[0]<maSlow[0]&&buypos ==sellpos)executeBuy(); if(maFast[1]<maSlow[1]&&maFast[0]>maSlow[0]&&sellpos ==buypos) executeSell(); if(buypos>0&&(!PositionSelectByTicket(buypos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ buypos = 0; } if(sellpos>0&&(!PositionSelectByTicket(sellpos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ sellpos = 0; } } } //+------------------------------------------------------------------+ //| Expert trade transaction handling function | //+------------------------------------------------------------------+ void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) { if (trans.type == TRADE_TRANSACTION_ORDER_ADD) { COrderInfo order; if (order.Select(trans.order)) { if (order.Magic() == Magic) { if (order.OrderType() == ORDER_TYPE_BUY) { buypos = order.Ticket(); } else if (order.OrderType() == ORDER_TYPE_SELL) { sellpos = order.Ticket(); } } } } } //+------------------------------------------------------------------+ //| Execute sell trade function | //+------------------------------------------------------------------+ void executeSell() { double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); bid = NormalizeDouble(bid,_Digits); trade.Sell(lott,_Symbol,bid); sellpos = trade.ResultOrder(); } //+------------------------------------------------------------------+ //| Execute buy trade function | //+------------------------------------------------------------------+ void executeBuy() { double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); ask = NormalizeDouble(ask,_Digits); trade.Buy(lott,_Symbol,ask); buypos = trade.ResultOrder(); }
Ich werde nicht weiter auf die Validierung und die Vorschläge zur Auswahl Ihrer Backtest-Strategie eingehen. Weitere Einzelheiten finden Sie in meinem früheren Artikel über maschinelles Lernen, der hier verlinkt ist.
Nun werden wir versuchen, unser LSTM-Modell auf der Grundlage dieses Rahmens zu betreiben.
Zunächst deklarieren wir die globalen Variablen, die die Form unserer Ein- und Ausgabe spezifizieren, sowie zwei Multi-Arrays zum Speichern der Ein- und Ausgabedaten. Zusätzlich deklarieren wir ein Modell-Handle, das den Prozess des Abrufs von Daten in das Modell und die Extraktion von Vorhersagen aus diesem verwaltet. Dadurch werden ein ordnungsgemäßer Datenfluss und die Interaktion zwischen dem Modell und den Eingabe-/Ausgabevariablen gewährleistet.
#resource "\\Files\\regression2024.onnx" as uchar lstm_onnx[] float data[1][5][3]; float out[1][1]; long lstmHandle = INVALID_HANDLE; const long input_shape[] = {1,5,3}; const long output_shape[]={1,1};
Als Nächstes werden in der Funktion OnInit() die relevanten Indikatoren, wie RSI und ADX, sowie das ONNX-Modell initialisiert. Während dieser Initialisierung wird überprüft, ob die in MQL5 deklarierte Eingabe- und Ausgabeform mit den zuvor im Python-Funktionsmodell angegebenen übereinstimmt. Dieser Schritt gewährleistet die Konsistenz und verhindert Fehler bei der Modellinitialisierung, sodass das Modell die Daten im erwarteten Format korrekt verarbeiten kann.
int handleMaFast; int handleMaSlow; int handleAdx; // Average Directional Movement Index - 3 int handleRsi; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() {//Initialize model trade.SetExpertMagicNumber(Magic); handleMaFast =iMA(_Symbol,PERIOD_CURRENT,MaPeriodsFast,0,MODE_SMA,PRICE_CLOSE); handleMaSlow =iMA(_Symbol,PERIOD_CURRENT,MaPeriodsSlow,0,MODE_SMA,PRICE_CLOSE); handleAdx=iADX(_Symbol,PERIOD_CURRENT,14);//Average Directional Movement Index - 3 handleRsi = iRSI(_Symbol,PERIOD_CURRENT,14,PRICE_CLOSE); // Load the ONNX model lstmHandle = OnnxCreateFromBuffer(lstm_onnx, ONNX_DEFAULT); //--- specify the shape of the input data if(!OnnxSetInputShape(lstmHandle,0,input_shape)) { Print("OnnxSetInputShape failed, error ",GetLastError()); OnnxRelease(lstmHandle); return(-1); } //--- specify the shape of the output data if(!OnnxSetOutputShape(lstmHandle,0,output_shape)) { Print("OnnxSetOutputShape failed, error ",GetLastError()); OnnxRelease(lstmHandle); return(-1); } if (lstmHandle == INVALID_HANDLE) { Print("Error creating model OnnxCreateFromBuffer ", GetLastError()); return(INIT_FAILED); } return(INIT_SUCCEEDED); }
Als Nächstes deklarieren wir eine Funktion, die die Eingabedaten mit jedem neuen Balken aktualisiert. Diese Funktion führt eine Schleife durch den Zeitschritt (in diesem Fall 5) durch, um die entsprechenden Daten im globalen Multi-Array zu speichern. Er konvertiert die Daten in den Typ Float, um sicherzustellen, dass sie die vom ONNX-Modell erwartete 32-Bit-Anforderung erfüllen. Darüber hinaus stellt die Funktion sicher, dass die Reihenfolge des Multi-Arrays korrekt ist, d. h. ältere Daten zuerst und neuere Daten nacheinander hinzugefügt werden. Dadurch wird sichergestellt, dass die Daten in der richtigen zeitlichen Reihenfolge in das Modell eingespeist werden.
void getData(){ double rsi[]; double adx[]; CopyBuffer(handleAdx,0,1,5,adx); CopyBuffer(handleRsi,0,1,5,rsi); for (int i =0; i<5; i++){ data[0][i][0] = (float)adx[i]; data[0][i][1] = (float)rsi[i]; data[0][i][2] = (float)((iClose(_Symbol,PERIOD_CURRENT,5-i)-iOpen(_Symbol,PERIOD_CURRENT,5-i))/iClose(_Symbol,PERIOD_CURRENT,5-i)); } }
Schließlich wird in der Funktion OnTick() die Handelslogik implementiert.
Diese Funktion stellt sicher, dass die nachfolgende Handelslogik nur überprüft wird, wenn sich ein neuer Balken gebildet hat. Dies verhindert unnötige Neuberechnungen oder Handelsaktionen während desselben Taktes und stellt sicher, dass die Vorhersagen des Modells auf vollständigen Daten für jeden neuen Zeitschritt beruhen.
int bars = iBars(_Symbol,PERIOD_CURRENT); if (barsTotal!= bars){ barsTotal = bars;
Dieser Code setzt die Variablen buypos und sellpos auf 0 zurück, wenn keine Positionen mit der magischen Zahl des EA mehr vorhanden sind. Die Variablen buypos und sellpos werden verwendet, um sicherzustellen, dass sowohl Kauf- als auch Verkaufspositionen leer sind, bevor ein Einstiegssignal erzeugt wird. Indem wir diese Variablen zurücksetzen, wenn keine Positionen offen sind, stellen wir sicher, dass das System nicht versehentlich versucht, neue Positionen zu eröffnen, wenn bereits eine existiert.
if(buypos>0&&(!PositionSelectByTicket(buypos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ buypos = 0; } if(sellpos>0&&(!PositionSelectByTicket(sellpos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ sellpos = 0; }
Wir verwenden diese Codezeile, um das ONNX-Modell auszuführen, das die Eingabedaten aufnimmt und die Vorhersage in das Out-Array ausgibt. Dieser Vorgang wird nur ausgeführt, wenn das erste Einstiegssignal gebildet wird, und nicht bei jedem neuen Balken. Dieser Ansatz hilft, Rechenleistung zu sparen und macht den Backtest effizienter, da wir unnötige Modellevaluierungen in Zeiten vermeiden, in denen kein Einstiegssignal vorhanden ist.
OnnxRun(lstmHandle, ONNX_NO_CONVERSION, data, out);
Die Handelslogik sieht nun so aus, dass wir das Modell ausführen, um den vorhergesagten ADX-Wert zu erhalten, wenn das MA-Cross eintritt und keine aktuelle Position eröffnet ist. Wenn der Wert unter einem bestimmten Schwellenwert liegt, betrachten wir ihn als wenig trendig und vermeiden den Handel, und wenn er höher ist, steigen wir ein. Hier ist die gesamte Funktion OnTick():
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { int bars = iBars(_Symbol,PERIOD_CURRENT); if (barsTotal!= bars){ barsTotal = bars; double maFast[]; double maSlow[]; double adx[]; CopyBuffer(handleMaFast,BASE_LINE,1,2,maFast); CopyBuffer(handleMaSlow,BASE_LINE,1,2,maSlow); CopyBuffer(handleAdx,0,1,1,adx); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double lastClose = iClose(_Symbol, PERIOD_CURRENT, 1); //The order below matters if(buypos>0&& lastClose<maSlow[1]) trade.PositionClose(buypos); if(sellpos>0 &&lastClose>maSlow[1])trade.PositionClose(sellpos); if(maFast[1]<maSlow[1]&&maFast[0]>maSlow[0]&&sellpos == buypos){ getData(); OnnxRun(lstmHandle, ONNX_NO_CONVERSION, data, out); if(out[0][0]>threshold)executeSell();} if(maFast[1]>maSlow[1]&&maFast[0]<maSlow[0]&&sellpos == buypos){ getData(); OnnxRun(lstmHandle, ONNX_NO_CONVERSION, data, out); if(out[0][0]>threshold)executeBuy();} if(buypos>0&&(!PositionSelectByTicket(buypos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ buypos = 0; } if(sellpos>0&&(!PositionSelectByTicket(sellpos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ sellpos = 0; } } }
Statistischer Backtest
Nachdem wir alles implementiert haben, können wir nun den EA kompilieren und die Ergebnisse im Strategietester testen. Wir werden einen Out-of-Sample-Test für XAUUSD auf dem 1-Stunden-Zeitfenster vom 1. Januar 2024 bis zum 1. Januar 2025 durchführen. Zunächst lassen wir unsere ursprüngliche Backtest-Strategie als Ausgangsbasis laufen. Wir gehen davon aus, dass der EA mit der LSTM-Implementierung in diesem Zeitraum besser abschneidet als das Grundmodell.
Führen wir nun den Backtest für den EA mit der LSTM-Implementierung durch und verwenden dabei einen Schwellenwert von 30, da ein ADX von 30 allgemein als Indikator für eine starke Trendstärke gilt.
Der Vergleich der beiden Ergebnisse zeigt, dass die LSTM-Implementierung etwa 70 % der ursprünglichen Handelsgeschäfte herausfiltert und den Gewinnfaktor von 1,48 auf 1,52 verbessert. Sie wies auch eine höhere LR-Korrelation auf als die Grundlinie, was darauf hindeutet, dass sie zu einer stabileren Gesamtleistung beiträgt.
Bei den Backtests von Modellen des maschinellen Lernens ist es wichtig zu erkennen, dass die internen Parameter des Modells die entscheidenden Faktoren sind, im Gegensatz zu einfacheren Strategien, bei denen die Parameter weniger Einfluss haben. Folglich können unterschiedliche Trainingsdaten zu sehr unterschiedlichen Parameterergebnissen führen. Außerdem ist es nicht ideal, den gesamten historischen Datensatz auf einmal zu trainieren, da dies zu viele Stichproben zur Folge hätte, von denen die meisten nicht mehr aktuell sind. Aus diesem Grund empfehle ich in solchen Fällen die Verwendung der Methode der gleitenden Fenster für die Backtests. Wenn wir während der gesamten Backtest-Historie nur begrenzte Stichproben haben, wie in meinem vorherigen Artikel über das CatBoost-Modell beschrieben, ist ein Backtest mit expandierenden Fenster besser geeignet.
Hier sind die Bilder zur Demonstration:
Beim Backtest expandierender Fenster wird mit einem anfänglichen Datenfenster fester Größe begonnen. Sobald jedoch neue Datenpunkte zur Verfügung stehen, wird das Fenster um die neuen Daten erweitert, sodass die Strategie im Laufe der Zeit mit einem immer größeren Datensatz getestet wird.
Um den gleitenden Backtest durchzuführen, wiederholen wir einfach den in diesem Artikel beschriebenen Prozess und führen die Ergebnisse in einem einzigen Datensatz zusammen. Hier sehen Sie das Ergebnis des Backtests mit einem gleitenden Fenster vom 1. Januar 2015 bis zum 1. Januar 2025:
Kennzahlen:
Profit Factor: 1.24 Maximum Drawdown: -250.56 Average Win: 12.02 Average Loss: -5.20 Win Rate: 34.81%
Das Ergebnis ist beeindruckend, kann aber noch weiter verbessert werden.
Reflexion
Die Leistung des EA steht in direktem Zusammenhang mit der Vorhersagbarkeit des Modells. Um Ihren EA zu verbessern, sind einige Schlüsselfaktoren zu beachten:
- Das Herzstück Ihrer Backtest-Strategie: Letztendlich müssen die meisten Ihrer ursprünglichen Signale einen Vorteil haben, um eine weitere Filterung zu rechtfertigen.
- Die verwendeten Daten: Marktineffizienzen werden aufgedeckt, indem die Bedeutung der einzelnen Merkmale analysiert und weniger bekannte Merkmale identifiziert werden, die einen Vorteil bieten könnten.
- Das Modell, das Sie zum Trainieren verwenden: Überlegen Sie, ob es sich um ein Klassifizierungs- oder Regressionsproblem handelt, das Sie zu lösen versuchen. Und auch die Wahl der richtigen Trainingsparameter ist entscheidend.
- Die Dinge, die Sie vorhersagen wollen: Anstatt das Ergebnis eines Handels direkt vorherzusagen, sollten Sie sich auf etwas konzentrieren, das indirekt mit dem Endergebnis zusammenhängt, wie ich in diesem Artikel zeige.
In meinen früheren Artikeln habe ich mit verschiedenen Techniken des maschinellen Lernens experimentiert, die auch für Privatanleger zugänglich sind. Mein Ziel ist es, die Leser zu inspirieren, diese Ideen zu übernehmen und ihre eigenen innovativen Ansätze zu entwickeln, denn der Kreativität in diesem Bereich sind keine Grenzen gesetzt. Maschinelles Lernen ist nicht von Natur aus komplex oder unerreichbar - es ist eine Denkweise. Es geht darum, die Grenzen zu verstehen, Vorhersagemodelle zu erstellen und Hypothesen rigoros zu testen. Wenn Sie weiter experimentieren, wird dieses Verständnis allmählich klarer werden.
Schlussfolgerung
In diesem Artikel haben wir zunächst die Motivation für den Einsatz von LSTM zur Trendvorhersage erläutert und die Konzepte hinter ADX und LSTM erklärt. Als Nächstes haben wir Daten aus dem MetaTrader 5 geholt, sie verarbeitet und das Modell in Python trainiert. Anschließend gingen wir den Prozess der Erstellung des Expert Advisors durch und prüften die Backtest-Ergebnisse. Zum Schluss werden die Konzepte des Backtests mit gleitenden Fenstern und des Backtests mit expandierenden Fenstern vorgestellt, und der Artikel wird mit einigen Überlegungen abgeschlossen.
Datei-Tabelle
Datei Name | Dateiverwendung |
---|---|
FileCSV.mqh | Die Include-Datei zum Speichern von Daten in CSV |
LSTM_Demonstration.ipynb | Die Python-Datei für das Training des LSTM-Modells |
LSTM-TF-XAU.mq5 | Der Handels-EA mit LSTM-Implementierung |
OHLC Getter.mq5 | Der EA zum Abrufen von Daten |
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/16940
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.
Ich verstehe es nicht: wo ist das regression2024.onnx Modell selbst im Zip-Archiv?
Hallo an_tar.
Wie im Artikel erwähnt, soll diese Art von System durch einen Rolling-Window-Backtest validiert werden. Ich wollte nicht mein ganzes trainiertes Modell seit 2008 einbeziehen, um die Datei nicht zu schwer zu machen.
Es ist ratsam, das im Artikel vorgestellte Framework zu verwenden, um Ihr eigenes Modell zu trainieren, damit es mit Ihrer persönlichen Validierungsmethode kompatibel ist.