
PSAR, Heiken Ashi und Deep Learning gemeinsam für den Handel nutzen
Einführung
Einen Vorteil zu finden, kann den Unterschied zwischen Erfolg und Misserfolg ausmachen. Da so viele Daten zur Verfügung stehen, setzen Händler zunehmend auf fortschrittliche Technologien wie Deep Learning, um sich einen Vorteil zu verschaffen. Auf diesem Weg werden traditionelle technische Analysewerkzeuge mit modernen Modellen der künstlichen Intelligenz (KI) kombiniert, um die Marktbedingungen schnell beurteilen und anpassen zu können. Um dieses Potenzial zu erkunden, haben wir ein Python-Skript entwickelt, das die Vorhersagekraft von Deep Learning mit bewährten Handelsindikatoren verbindet. Dieses Skript ist nicht als vollwertiges Handelssystem gedacht. Es ist vielmehr ein Werkzeug zum schnellen Experimentieren - eine Möglichkeit, schnell zu testen, ob eine Handelsstrategie das Potenzial hat, profitabel zu sein. Parallel dazu nutzt ein MetaTrader 5-Skript ähnliche Prinzipien, um diesen Ansatz in eine Live-Handelsumgebung zu übertragen und so die Lücke zwischen theoretischem Backtesting und praktischer Anwendung zu schließen.
Erstellung des Deep Learning ONNX-Modells
In der schnelllebigen Welt des Devisenhandels ist es von entscheidender Bedeutung, der Entwicklung immer einen Schritt voraus zu sein. Deshalb haben wir ein Python-Skript entwickelt, das die Leistung des Deep Learning nutzt, um die Entwicklung von Währungspaaren vorherzusagen. Lassen Sie uns eine freundliche Tour durch dieses aufregende Stück Code machen und sehen, wie es seine Magie wirkt!
Unser Skript beginnt mit dem Import aller erforderlichen Werkzeuge - stellen Sie sich das vor wie das Sammeln von Zutaten für ein komplexes Rezept. Wir verwenden Bibliotheken wie TensorFlow und Keras, die die Kraftwerke hinter vielen modernen KI-Anwendungen sind. Wir verwenden auch MetaTrader 5, um echte Forex-Daten abzurufen.
import MetaTrader5 as mt5 import tensorflow as tf import numpy as np import pandas as pd import tf2onnx import keras
Als Nächstes legen wir einige wichtige Parameter fest. Wir betrachten 120 Tage historischer Daten für das EURUSD-Paar. Warum 120? Das ist ein optimaler Punkt, der uns genug Historie bietet, um Muster zu erkennen, ohne unser Modell zu überfordern.
Wir verwenden dann MetaTrader 5, um diese Daten abzurufen. Stellen Sie sich vor, Sie schicken einen Zeitreisenden zurück, um Preisinformationen der letzten 120 Tage zu sammeln. Sobald wir diese Daten haben, organisieren wir sie ordentlich in einem Format, das unsere KI versteht.
inp_history_size = 120 sample_size = inp_history_size*3*20 symbol = "EURUSD" optional = "D1_2024" inp_model_name = str(symbol)+"_"+str(optional)+".onnx" if not mt5.initialize(): print("initialize() failed, error code =",mt5.last_error()) quit() # we will save generated onnx-file near the our script to use as resource from sys import argv data_path=argv[0] last_index=data_path.rfind("\\")+1 data_path=data_path[0:last_index] print("data path to save onnx model",data_path) # and save to MQL5\Files folder to use as file terminal_info=mt5.terminal_info() file_path=terminal_info.data_path+"\\MQL5\\Files\\" print("file path to save onnx model",file_path) # set start and end dates for history data from datetime import timedelta, datetime #end_date = datetime.now() end_date = datetime(2024, 1, 1, 0) start_date = end_date - timedelta(days=inp_history_size*20*3) # print start and end dates print("data start date =",start_date) print("data end date =",end_date) # get rates eurusd_rates = mt5.copy_rates_from(symbol, mt5.TIMEFRAME_D1, end_date, sample_size)
Rohe Finanzdaten können unübersichtlich sein, deshalb bereinigen wir sie. Wir konzentrieren uns auf die Schlusskurse - das ist der Endpreis eines jeden Handelstages. Wir skalieren auch diese Daten so, dass sie zwischen 0 und 1 liegen. Dieser Schritt ist entscheidend, denn er hilft unserem KI-Modell, effektiver zu lernen, sozusagen alles in eine gemeinsame Sprache zu übersetzen.
from sklearn.preprocessing import MinMaxScaler scaler=MinMaxScaler(feature_range=(0,1)) scaled_data = scaler.fit_transform(data)
Wir wollen nicht, dass unser Modell die Daten auswendig lernt, also teilen wir sie in zwei Teile: Trainingsdaten (80 %) und Testdaten (20 %). Es ist, als würde man unserem KI-Schüler ein Lehrbuch zum Lernen geben (Trainingsdaten) und dann ein separates Quiz, um sein Wissen zu testen (Testdaten).
# scale data from sklearn.preprocessing import MinMaxScaler scaler=MinMaxScaler(feature_range=(0,1)) scaled_data = scaler.fit_transform(data) # training size is 80% of the data training_size = int(len(scaled_data)*0.80) print("Training_size:",training_size) train_data_initial = scaled_data[0:training_size,:] test_data_initial = scaled_data[training_size:,:1] # split a univariate sequence into samples def split_sequence(sequence, n_steps): X, y = list(), list() for i in range(len(sequence)): # find the end of this pattern end_ix = i + n_steps # check if we are beyond the sequence if end_ix > len(sequence)-1: break # gather input and output parts of the pattern seq_x, seq_y = sequence[i:end_ix], sequence[end_ix] X.append(seq_x) y.append(seq_y) return np.array(X), np.array(y) # split into samples time_step = inp_history_size x_train, y_train = split_sequence(train_data_initial, time_step) x_test, y_test = split_sequence(test_data_initial, time_step) # reshape input to be [samples, time steps, features] which is required for LSTM x_train =x_train.reshape(x_train.shape[0],x_train.shape[1],1) x_test = x_test.reshape(x_test.shape[0],x_test.shape[1],1)
Jetzt kommt der spannende Teil - der Aufbau unseres KI-Modells! Wir verwenden eine Kombination aus verschiedenen KI-Techniken:
- Neuronale Faltungsnetze (Convolutional Neural Networks, CNNs): Sie sind hervorragend in der Lage, Muster in Sequenzen zu erkennen, so wie unsere Augen Muster in Bildern erkennen.
- Netze mit langem Kurzzeitgedächtnis (Long Short-Term Memory, LSTM): Sie eignen sich hervorragend für das Verständnis langfristiger Abhängigkeiten in Daten und sind ideal für die Erkennung von Trends im Zeitverlauf.
- Dichte Schichten: Diese helfen unserem Modell, seine endgültigen Vorhersagen zu treffen.
Wir fügen auch einige Dropout-Schichten hinzu, um zu verhindern, dass unser Modell zu selbstbewusst wird und die Daten übermäßig gut anpasst (overfitting).
# define model from keras.models import Sequential from keras.layers import Dense, Activation, Conv1D, MaxPooling1D, Dropout, Flatten, LSTM from keras.metrics import RootMeanSquaredError as rmse from tensorflow.keras import callbacks model = Sequential() model.add(Conv1D(filters=256, kernel_size=2, strides=1, padding='same', activation='relu', input_shape=(inp_history_size,1))) model.add(MaxPooling1D(pool_size=2)) model.add(LSTM(100, return_sequences = True)) model.add(Dropout(0.3)) model.add(LSTM(100, return_sequences = False)) model.add(Dropout(0.3)) model.add(Dense(units=1, activation = 'sigmoid')) model.compile(optimizer='adam', loss= 'mse' , metrics = [rmse()]) # Set up early stopping early_stopping = callbacks.EarlyStopping( monitor='val_loss', patience=20, restore_best_weights=True, ) # model training for 300 epochs history = model.fit(x_train, y_train, epochs = 300 , validation_data = (x_test,y_test), batch_size=32, callbacks=[early_stopping], verbose=2)Das Training des Modells ist so, als würde man unsere KI in die Schule schicken. Wir zeigen ihm unsere Trainingsdaten viele Male (bis zu 300 Epochen), und es lernt allmählich, zukünftige Preise auf der Grundlage vergangener Muster vorherzusagen. Wir verwenden das so genannte „Early Stopping“, um sicherzustellen, dass unsere KI nicht zu lange lernt und anfängt, auswendig zu lernen, anstatt zu lernen.
Nach dem Training testen wir die Leistung unseres Modells. Wir verwenden Messgrößen wie den mittleren quadratischen Fehler RMSE (Root Mean Square Error, RMSE), um zu sehen, wie nahe unsere Vorhersagen an den tatsächlichen Preisen liegen. Das ist wie die Bewertung der Testergebnisse unserer KI.
# evaluate training data train_loss, train_rmse = model.evaluate(x_train,y_train, batch_size = 32) print(f"train_loss={train_loss:.3f}") print(f"train_rmse={train_rmse:.3f}") # evaluate testing data test_loss, test_rmse = model.evaluate(x_test,y_test, batch_size = 32) print(f"test_loss={test_loss:.3f}") print(f"test_rmse={test_rmse:.3f}")
Der letzte Schritt besteht darin, unser trainiertes Modell in das ONNX-Format zu konvertieren. ONNX ist wie eine universelle Sprache für KI-Modelle - es ermöglicht die Verwendung unseres Modells in verschiedenen Umgebungen, einschließlich unserer MetaTrader-Skripte.
# Define a function that represents your model @tf.function(input_signature=[tf.TensorSpec([None, inp_history_size, 1], tf.float32)]) def model_function(x): return model(x) output_path = data_path+inp_model_name # Convert the model to ONNX onnx_model, _ = tf2onnx.convert.from_function( model_function, input_signature=[tf.TensorSpec([None, inp_history_size, 1], tf.float32)], opset=13, output_path=output_path ) print(f"Saved ONNX model to {output_path}") # save model to ONNX output_path = data_path+inp_model_name onnx_model = tf2onnx.convert.from_keras(model, output_path=output_path) print(f"saved model to {output_path}") output_path = file_path+inp_model_name onnx_model = tf2onnx.convert.from_keras(model, output_path=output_path) print(f"saved model to {output_path}")Wir beschränken uns nicht nur auf Zahlen - wir erstellen mehrere Grafiken, um zu veranschaulichen, wie gut unser Modell funktioniert. Diese Diagramme zeigen z. B. den Lernfortschritt des Modells und den Vergleich zwischen den Vorhersagen und den tatsächlichen Preisen.
Am Ende dieses Skripts haben wir ein leistungsfähiges ONNX-Modell, das wir in unseren Forex-Handelsstrategien einsetzen können. Er wurde auf der Grundlage historischer EURUSD-Daten trainiert und kann helfen, zukünftige Kursbewegungen vorherzusagen.
Denken Sie daran, dass dieses Modell zwar ausgeklügelt ist, aber keine Kristallkugel darstellt. Der Devisenmarkt wird von vielen komplexen Faktoren beeinflusst. Nutzen Sie KI-Prognosen immer nur als eines von vielen Instrumenten in Ihrem Trading-Toolkit, und vergessen Sie nie die Bedeutung des Risikomanagements.
Python-Skript zum Testen einer Strategie
Dieses Python-Skript ist im Wesentlichen eine schnelle Möglichkeit zu testen, ob eine Handelsstrategie das Potenzial hat, profitabel zu sein. Es ist nicht als vollwertiges Handelssystem gedacht, sondern eher als ein Werkzeug zum schnellen Experimentieren. Durch die Kombination eines Deep-Learning-Modells mit der technischen Analyse soll festgestellt werden, ob es einen brauchbaren Vorteil auf dem Markt gibt, ohne dass man sich gleich in komplexe Optimierungen verstricken muss.
Das Skript stellt zunächst eine Verbindung zum MetaTrader 5 her, um historische Kursdaten für EUR/USD zu erhalten. Er verwendet stündliche Daten, um kurzfristige Bewegungen zu erfassen, und wendet Heikin-Ashi-Kerzen an, um das Marktrauschen zu glätten. Auf diese Weise lassen sich Trends deutlicher erkennen, was entscheidend ist, wenn man eine Strategie schnell testen will. Anschließend werden einige bekannte technische Indikatoren wie PSAR, SMA, RSI und ATR übereinander gelegt. Diese Indikatoren dienen als schnelle Überprüfung der Marktbedingungen und geben dem Modell den Kontext für Kauf- und Verkaufsentscheidungen.
import MetaTrader5 as mt5 import pandas as pd import numpy as np import onnxruntime as ort from sklearn.preprocessing import MinMaxScaler from ta.trend import PSARIndicator, SMAIndicator from ta.momentum import RSIIndicator from ta.volatility import AverageTrueRange import matplotlib.pyplot as plt # Inicializar conexión con MetaTrader5 if not mt5.initialize(): print("Inicialización fallida") mt5.shutdown() def get_historical_data(symbol, timeframe, start_date, end_date): rates = mt5.copy_rates_range(symbol, timeframe, start_date, end_date) df = pd.DataFrame(rates) df['time'] = pd.to_datetime(df['time'], unit='s') df.set_index('time', inplace=True) return df def calculate_heikin_ashi(df): ha_close = (df['open'] + df['high'] + df['low'] + df['close']) / 4 ha_open = (df['open'].shift(1) + df['close'].shift(1)) / 2 ha_high = df[['high', 'open', 'close']].max(axis=1) ha_low = df[['low', 'open', 'close']].min(axis=1) df['ha_close'] = ha_close df['ha_open'] = ha_open df['ha_high'] = ha_high df['ha_low'] = ha_low return df def add_indicators(df): # Calcular Heikin Ashi df = calculate_heikin_ashi(df) # PSAR con parámetros ajustados psar = PSARIndicator(df['high'], df['low'], df['close'], step=0.02, max_step=0.2) df['psar'] = psar.psar() # Añadir SMA sma = SMAIndicator(df['close'], window=50) df['sma'] = sma.sma_indicator() # Añadir RSI rsi = RSIIndicator(df['close'], window=14) df['rsi'] = rsi.rsi() # Añadir ATR para medir volatilidad atr = AverageTrueRange(df['high'], df['low'], df['close'], window=14) df['atr'] = atr.average_true_range() # Añadir filtro de tendencia simple df['trend'] = np.where(df['close'] > df['sma'], 1, -1) return df
Der Kern des Skripts liegt in der Verwendung eines vortrainierten ONNX Deep Learning-Modells. Durch Skalierung der Daten und Einspeisung in dieses Modell erstellt das Skript Vorhersagen darüber, wie sich der Markt als Nächstes entwickeln könnte. Diese Vorhersagen werden mit den technischen Indikatoren kombiniert, um ein Grundgerüst an Handelsregeln zu erstellen. Er sucht beispielsweise nach Kaufgelegenheiten, wenn bestimmte Bedingungen zusammentreffen, z. B. wenn der Kurs über dem gleitenden Durchschnitt liegt und der RSI anzeigt, dass der Markt nicht überkauft ist. Umgekehrt signalisiert er eine Verkaufsgelegenheiten, wenn die entgegengesetzten Bedingungen erfüllt sind. Das Skript fügt sogar adaptive Stop-Loss- und Take-Profit-Mechanismen zur Risikosteuerung hinzu, die jedoch relativ einfach gehalten sind, um den Schwerpunkt des Skripts auf schnelle Tests zu legen.
def prepare_data(df, window_size=120): scaler = MinMaxScaler(feature_range=(0, 1)) scaled_data = scaler.fit_transform(df[['close']]) X = [] for i in range(window_size, len(scaled_data)): X.append(scaled_data[i-window_size:i]) return np.array(X), scaler def load_onnx_model(model_path): return ort.InferenceSession(model_path) def predict_with_onnx(model, input_data): input_name = model.get_inputs()[0].name output_name = model.get_outputs()[0].name return model.run([output_name], {input_name: input_data})[0] def backtest(df, model, scaler, window_size=120, initial_balance=10000): scaled_data = scaler.transform(df[['close']]) predictions = [] for i in range(window_size, len(scaled_data)): X = scaled_data[i-window_size:i].reshape(1, window_size, 1) pred = predict_with_onnx(model, X.astype(np.float32)) predictions.append(scaler.inverse_transform(pred.reshape(-1, 1))[0, 0]) df['predictions'] = [np.nan]*window_size + predictions # Nueva lógica de trading df['position'] = 0 long_condition = ( (df['close'] > df['predictions']) & (df['close'] > df['psar']) & (df['close'] > df['sma']) & (df['rsi'] < 60) & # Condición RSI menos estricta (df['ha_close'] > df['ha_open']) & (df['ha_close'].shift(1) > df['ha_open'].shift(1)) & (df['trend'] == 1) # Solo comprar en tendencia alcista ) short_condition = ( (df['close'] < df['predictions']) & (df['close'] < df['psar']) & (df['close'] < df['sma']) & (df['rsi'] > 40) & # Condición RSI menos estricta (df['ha_close'] < df['ha_open']) & (df['ha_close'].shift(1) < df['ha_open'].shift(1)) & (df['trend'] == -1) # Solo vender en tendencia bajista ) df.loc[long_condition, 'position'] = 1 # Compra df.loc[short_condition, 'position'] = -1 # Venta # Implementar stop-loss y take-profit adaptativos sl_atr_multiple = 2 tp_atr_multiple = 3 for i in range(window_size, len(df)): if df['position'].iloc[i-1] != 0: entry_price = df['close'].iloc[i-1] current_atr = df['atr'].iloc[i-1] if df['position'].iloc[i-1] == 1: # Posición larga sl_price = entry_price - sl_atr_multiple * current_atr tp_price = entry_price + tp_atr_multiple * current_atr if df['low'].iloc[i] < sl_price or df['high'].iloc[i] > tp_price: df.loc[df.index[i], 'position'] = 0 else: # Posición corta sl_price = entry_price + sl_atr_multiple * current_atr tp_price = entry_price - tp_atr_multiple * current_atr if df['high'].iloc[i] > sl_price or df['low'].iloc[i] < tp_price: df.loc[df.index[i], 'position'] = 0 df['returns'] = df['close'].pct_change() df['strategy_returns'] = df['position'].shift(1) * df['returns'] # Calcular balance df['cumulative_returns'] = (1 + df['strategy_returns']).cumprod() df['balance'] = initial_balance * df['cumulative_returns'] return df
Was die Ergebnisse betrifft, so gibt das Skript einen schnellen Überblick über die Leistung der Strategie. In diesem Fall wurde eine Gesamtrendite von 1,35 % erzielt, d. h. aus 10.000 USD wurden im Testzeitraum 10.135,02 USD. Dies ist zwar kein spektakulärer Gewinn, aber ein positives Ergebnis, das darauf hindeutet, dass die Strategie einen gewissen Wert hat. Die Sharpe Ratio, ein gängiges Maß für die risikobereinigte Rendite, lag bei 0,39. Diese Zahl zeigt, dass es zwar Gewinne gab, diese aber mit einem gewissen Risiko verbunden waren. Für einen Schnelltest ist dieses Verhältnis ein nützliches Mittel, um zu beurteilen, ob es sich lohnt, die Strategie weiter zu verfolgen.
Retorno total: 1.35% Ratio de Sharpe: 0.39 Balance final: $10135.02
Die vom Skript generierten Visualisierungen bieten einen hilfreichen Überblick über die Leistung der Strategie. Das erste Chart zeigt das Zusammenspiel zwischen dem Marktpreis, den Vorhersagen des Modells und den Kauf- und Verkaufssignalen. Das Heikin-Ashi-Chart bietet einen klareren Blick auf die Markttrends und hilft Ihnen zu erkennen, ob die Strategie die richtigen Zeitpunkte für den Ein- und Ausstieg aus dem Handel erfasst. Die Saldenkurve schließlich gibt einen direkten Überblick über die Kontoentwicklung im Laufe der Zeit, wobei sowohl die Wachstumsperioden als auch die Rückgänge hervorgehoben werden.
Zusammenfassend lässt sich sagen, dass dieses Skript für schnelle Experimente gedacht ist. So können Sie schnell beurteilen, ob eine Strategie Potenzial hat, ohne sich zu sehr in die Feinabstimmung zu vertiefen. Dies zeigt zwar, dass die Strategie einen gewissen Gewinn erwirtschaften kann, aber die bescheidene Sharpe Ratio deutet darauf hin, dass noch einiges zu tun ist. Dies macht sie zu einem nützlichen Ausgangspunkt, um schnell festzustellen, ob eine Strategie eine eingehendere Entwicklung und Optimierung wert ist.
Expert Advisor
Dieses MetaTrader 5-Skript wurde entwickelt, um schnell zu testen, ob eine Handelsstrategie profitabel sein kann, indem eine Mischung aus technischer Analyse und einem Deep-Learning-Modell verwendet wird. Es geht hier nicht darum, ein narrensicheres Handelssystem zu entwickeln, sondern darum, traditionelle Handelsindikatoren mit den Möglichkeiten des maschinellen Lernens zu kombinieren, um zu sehen, ob es einen Vorteil auf dem Markt gibt.
Das Skript konzentriert sich auf das Währungspaar EUR/USD und verwendet den Zeitrahmen H6 (6 Stunden). Es beginnt mit der Einrichtung einer Handvoll bekannter technischer Indikatoren wie RSI, SMA, PSAR und ATR. Dies sind gängige Instrumente, die Händler verwenden, um die Marktdynamik, die Trendrichtung und die Volatilität einzuschätzen. Darüber hinaus verwendet er Heikin-Ashi-Kerzen, die dazu beitragen, das Kursgeschehen zu glätten und Trends leichter zu erkennen, indem sie das Marktrauschen reduzieren. Diese anfängliche Einrichtung legt den Grundstein für das, was das Skript zu erreichen versucht: eine systematische Art und Weise, nach Kauf- und Verkaufsmöglichkeiten zu suchen.
int handleRSI, handleSMA, handlePSAR, handleATR; double rsiBuffer[], smaBuffer[], psarBuffer[], atrBuffer[]; double haOpen[], haClose[], haHigh[], haLow[]; CTrade trade;
handleRSI = iRSI(Symbol, Timeframe, RSIPeriod, PRICE_CLOSE); handleSMA = iMA(Symbol, Timeframe, SMAPeriod, 0, MODE_SMA, PRICE_CLOSE); handlePSAR = iSAR(Symbol, Timeframe, PSARStep, PSARMaximum); handleATR = iATR(Symbol, Timeframe, ATRPeriod); if(handleRSI == INVALID_HANDLE || handleSMA == INVALID_HANDLE || handlePSAR == INVALID_HANDLE || handleATR == INVALID_HANDLE) { Print("Error creating indicators"); return(INIT_FAILED); } ArraySetAsSeries(rsiBuffer, true); ArraySetAsSeries(smaBuffer, true); ArraySetAsSeries(psarBuffer, true); ArraySetAsSeries(atrBuffer, true); ArrayResize(haOpen, 3); ArrayResize(haClose, 3); ArrayResize(haHigh, 3); ArrayResize(haLow, 3); ArraySetAsSeries(haOpen, true); ArraySetAsSeries(haClose, true); ArraySetAsSeries(haHigh, true); ArraySetAsSeries(haLow, true);
IndicatorRelease(handleRSI); IndicatorRelease(handleSMA); IndicatorRelease(handlePSAR); IndicatorRelease(handleATR);
Das Besondere an diesem Skript ist die Verwendung eines vortrainierten ONNX Deep Learning-Modells. Anstatt sich ausschließlich auf die traditionelle technische Analyse zu verlassen, nutzt das Skript die Vorhersagekraft des maschinellen Lernens, um Kursbewegungen vorherzusagen. Dieses Modell wird direkt in die Handelsumgebung geladen, wo es die jüngsten Marktdaten aufnimmt, sie normalisiert und vorhersagt, ob der Preis steigen, fallen oder gleich bleiben wird. Diese Vorhersage wird dann in eine von drei Kategorien eingeordnet: eine Aufwärtsbewegung, eine Abwärtsbewegung oder gar keine nennenswerte Bewegung. Durch die Kombination dieser Vorhersagen mit technischen Indikatoren zielt das Skript darauf ab, fundiertere Handelsentscheidungen zu treffen.
#define SAMPLE_SIZE 120 long ExtHandle=INVALID_HANDLE; int ExtPredictedClass=-1; datetime ExtNextBar=0; datetime ExtNextDay=0; float ExtMin=0.0; float ExtMax=0.0; CTrade ExtTrade; int dlsignal=-1; //--- price movement prediction #define PRICE_UP 0 #define PRICE_SAME 1 #define PRICE_DOWN 2 #resource "/Files/EURUSD_D1_2024.onnx" as uchar ExtModel[]
//--- create a model from static buffer ExtHandle=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(ExtHandle==INVALID_HANDLE) { Print("OnnxCreateFromBuffer error ",GetLastError()); return(INIT_FAILED); } //--- since not all sizes defined in the input tensor we must set them explicitly //--- first index - batch size, second index - series size, third index - number of series (only Close) const long input_shape[] = {1,SAMPLE_SIZE,1}; if(!OnnxSetInputShape(ExtHandle,ONNX_DEFAULT,input_shape)) { Print("OnnxSetInputShape error ",GetLastError()); return(INIT_FAILED); }
//--- check new day if(TimeCurrent() >= ExtNextDay) { GetMinMax(); //--- set next day time ExtNextDay = TimeCurrent(); ExtNextDay -= ExtNextDay % PeriodSeconds(PERIOD_D1); ExtNextDay += PeriodSeconds(PERIOD_D1); } //--- check new bar if(TimeCurrent() < ExtNextBar) return; //--- set next bar time ExtNextBar = TimeCurrent(); ExtNextBar -= ExtNextBar % PeriodSeconds(); ExtNextBar += PeriodSeconds(); //--- check min and max float close = (float)iClose(_Symbol, _Period, 0); if(ExtMin > close) ExtMin = close; if(ExtMax < close) ExtMax = close;
void PredictPrice(void) { static vectorf output_data(1); // vector to get result static vectorf x_norm(SAMPLE_SIZE); // vector for prices normalize //--- check for normalization possibility if(ExtMin>=ExtMax) { Print("ExtMin>=ExtMax"); ExtPredictedClass=-1; return; } //--- request last bars if(!x_norm.CopyRates(_Symbol,_Period,COPY_RATES_CLOSE,1,SAMPLE_SIZE)) { Print("CopyRates ",x_norm.Size()); ExtPredictedClass=-1; return; } float last_close=x_norm[SAMPLE_SIZE-1]; //--- normalize prices x_norm-=ExtMin; x_norm/=(ExtMax-ExtMin); //--- run the inference if(!OnnxRun(ExtHandle,ONNX_NO_CONVERSION,x_norm,output_data)) { Print("OnnxRun"); ExtPredictedClass=-1; return; } //--- denormalize the price from the output value float predicted=output_data[0]*(ExtMax-ExtMin)+ExtMin; //--- classify predicted price movement float delta=last_close-predicted; if(fabs(delta)<=0.00001) ExtPredictedClass=PRICE_SAME; else { if(delta<0) ExtPredictedClass=PRICE_UP; else ExtPredictedClass=PRICE_DOWN; } } //+------------------------------------------------------------------+ //| Gets Min and Max values | //+------------------------------------------------------------------+ void GetMinMax(void) { vectorf close; close.CopyRates(_Symbol,PERIOD_D1,COPY_RATES_CLOSE,0,SAMPLE_SIZE); ExtMin=close.Min(); ExtMax=close.Max(); }
Die Handelslogik ist recht einfach, aber anspruchsvoll. Für ein Kaufsignal (Long) müssen mehrere Bedingungen zusammentreffen: Der aktuelle Kurs sollte sowohl über dem PSAR als auch über dem SMA liegen, der RSI sollte unter 60 liegen (was darauf hindeutet, dass der Markt nicht überkauft ist), und die Heikin-Ashi-Kerzen sollten auf einen Aufwärtstrend hindeuten. Entscheidend ist, dass das Deep-Learning-Modell auch eine Aufwärtsbewegung der Kurse vorhersagen muss. Wenn alle diese Faktoren zusammentreffen, eröffnet das Skript eine Kaufposition. In ähnlicher Weise sucht er nach gegenteiligen Bedingungen, um eine Verkaufsposition (Short) zu eröffnen. Dieser mehrschichtige Ansatz soll Rauschen und falsche Signale herausfiltern und sicherstellen, dass nur dann gehandelt wird, wenn mehrere Indikatoren und das Modell sich über die Richtung einig sind.
void CalculateHeikinAshi() { double close[], open[], high[], low[]; ArraySetAsSeries(close, true); ArraySetAsSeries(open, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); int copied = CopyClose(Symbol(), Timeframe, 0, 3, close); copied = MathMin(copied, CopyOpen(Symbol(), Timeframe, 0, 3, open)); copied = MathMin(copied, CopyHigh(Symbol(), Timeframe, 0, 3, high)); copied = MathMin(copied, CopyLow(Symbol(), Timeframe, 0, 3, low)); if(copied < 3) { Print("Not enough data for Heikin Ashi calculation"); return; } // Calculate Heikin Ashi values for the last 3 candles for(int i = 2; i >= 0; i--) { haClose[i] = (open[i] + high[i] + low[i] + close[i]) / 4; if(i == 2) { haOpen[i] = (open[i] + close[i]) / 2; } else { haOpen[i] = (haOpen[i+1] + haClose[i+1]) / 2; } haHigh[i] = MathMax(high[i], MathMax(haOpen[i], haClose[i])); haLow[i] = MathMin(low[i], MathMin(haOpen[i], haClose[i])); } // Debug print Print("Heikin Ashi values:"); for(int i = 0; i < 3; i++) { Print("Candle ", i, ": Open=", haOpen[i], " High=", haHigh[i], " Low=", haLow[i], " Close=", haClose[i]); } }
// Copy indicator data if(CopyBuffer(handleRSI, 0, 0, 3, rsiBuffer) <= 0 || CopyBuffer(handleSMA, 0, 0, 3, smaBuffer) <= 0 || CopyBuffer(handlePSAR, 0, 0, 3, psarBuffer) <= 0 || CopyBuffer(handleATR, 0, 0, 3, atrBuffer) <= 0) { Print("Failed to copy indicator data"); return; } CalculateHeikinAshi(); if(ArraySize(haOpen) < 3 || ArraySize(haClose) < 3) { Print("Not enough Heikin Ashi data"); return; } PredictPrice(); int trend = GetTrend(); bool longCondition = close > psarBuffer[1] && close > smaBuffer[1] && rsiBuffer[1] < 60 && haClose[1] > haOpen[1] && haClose[2] > haOpen[2] && ExtPredictedClass == PRICE_UP && trend == 1; bool shortCondition = close < psarBuffer[1] && close < smaBuffer[1] && rsiBuffer[1] > 40 && haClose[1] < haOpen[1] && haClose[2] < haOpen[2] && ExtPredictedClass == PRICE_DOWN && trend == -1; // Debug printing Print("--- Debug Info ---"); Print("Close: ", close, " PSAR: ", psarBuffer[1], " SMA: ", smaBuffer[1]); Print("RSI: ", rsiBuffer[1], " HA Close[1]: ", haClose[1], " HA Open[1]: ", haOpen[1]); Print("HA Close[2]: ", haClose[2], " HA Open[2]: ", haOpen[2]); Print("ExtPredictedClass: ", ExtPredictedClass, " Trend: ", trend); Print("Long Condition: ", longCondition, " Short Condition: ", shortCondition); if(longCondition) { Print("Long Condition Met"); if(PositionsTotal() == 0) { double atrStopLoss = SLATRMultiple * atrBuffer[1]; double minStopLoss = GetMinimumStopLoss(); double sl = close - MathMax(atrStopLoss, minStopLoss); double tp = close + TPATRMultiple * atrBuffer[1]; bool result = trade.Buy(0.01, Symbol(), 0, sl, tp); if(result) Print("Buy order placed successfully. SL: ", sl, " TP: ", tp); else Print("Failed to place buy order. Error: ", GetLastError()); } else { Print("Position already open. Skipping buy order."); } } else if(shortCondition) { Print("Short Condition Met"); if(PositionsTotal() == 0) { double atrStopLoss = SLATRMultiple * atrBuffer[1]; double minStopLoss = GetMinimumStopLoss(); double sl = close + MathMax(atrStopLoss, minStopLoss); double tp = close - TPATRMultiple * atrBuffer[1]; bool result = trade.Sell(0.01, Symbol(), 0, sl, tp); if(result) Print("Sell order placed successfully. SL: ", sl, " TP: ", tp); else Print("Failed to place sell order. Error: ", GetLastError()); } else { Print("Position already open. Skipping sell order."); } } ManageTrailingStop(); }
Auch das Risikomanagement ist Teil des Skripts. Er verwendet die Average True Range (ATR), um dynamisch Stop-Loss und Take-Profit festzulegen. Da sich das Skript auf die Marktvolatilität stützt, passt es sich an die sich ändernden Marktbedingungen an, um sich vor erheblichen Verlusten zu schützen und gleichzeitig Gewinne zu sichern. Darüber hinaus verfügt es über einen Trailing-Stop-Mechanismus, der den Stop-Loss anpasst, wenn sich der Handel in eine günstige Richtung bewegt, und so die Gewinne weiter schützt.
double GetMinimumStopLoss() { double spread = SymbolInfoInteger(Symbol(), SYMBOL_SPREAD) * SymbolInfoDouble(Symbol(), SYMBOL_POINT); double minStopLevel = SymbolInfoInteger(Symbol(), SYMBOL_TRADE_STOPS_LEVEL) * SymbolInfoDouble(Symbol(), SYMBOL_POINT); return MathMax(spread * 2, minStopLevel); // Usamos el doble del spread como mínimo } // Agregar esta función para manejar el trailing stop void ManageTrailingStop() { for(int i = PositionsTotal() - 1; i >= 0; i--) { if(PositionSelectByTicket(PositionGetTicket(i))) { if(PositionGetString(POSITION_SYMBOL) == Symbol()) { double positionOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); double currentStopLoss = PositionGetDouble(POSITION_SL); double currentTakeProfit = PositionGetDouble(POSITION_TP); if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { double newStopLoss = NormalizeDouble(SymbolInfoDouble(Symbol(), SYMBOL_BID) - TrailingStop * SymbolInfoDouble(Symbol(), SYMBOL_POINT), Digits()); if(newStopLoss > currentStopLoss + TrailingStep * SymbolInfoDouble(Symbol(), SYMBOL_POINT)) { if(trade.PositionModify(PositionGetTicket(i), newStopLoss, currentTakeProfit)) { Print("Trailing stop ajustado para posición larga. Nuevo SL: ", newStopLoss); } } } else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { double newStopLoss = NormalizeDouble(SymbolInfoDouble(Symbol(), SYMBOL_ASK) + TrailingStop * SymbolInfoDouble(Symbol(), SYMBOL_POINT), Digits()); if(newStopLoss < currentStopLoss - TrailingStep * SymbolInfoDouble(Symbol(), SYMBOL_POINT)) { if(trade.PositionModify(PositionGetTicket(i), newStopLoss, currentTakeProfit)) { Print("Trailing stop ajustado para posición corta. Nuevo SL: ", newStopLoss); } } } } } } }
Kurz gesagt, dieses Skript dient als schnelle und experimentelle Methode, um die Durchführbarkeit einer Handelsstrategie zu testen. Es soll nicht die perfekte Lösung sein, sondern eher ein Testfeld, um zu sehen, ob die Kombination aus technischer Analyse und einem Deep-Learning-Modell profitable Handelsmöglichkeiten identifizieren kann. Sie garantiert zwar keine Gewinne, bietet aber einen differenzierteren und fundierteren Ansatz für Handelsentscheidungen, indem sie menschliche Intuition mit Erkenntnissen des maschinellen Lernens verbindet.
Ergebnisse
Die Ergebnisse des Expert Advisor (EA) geben einen aufschlussreichen Einblick in seine Leistung während des Backtesting-Zeitraums. Ausgehend von einer anfänglichen Einlage von 10.000 $ erzielte die Strategie einen bescheidenen Gesamtnettogewinn von 17 Einheiten, was darauf hindeutet, dass zwar Gewinne erzielt wurden, diese aber im Verhältnis zur anfänglichen Investition relativ gering waren. Die Saldenkurve spiegelt dieses allmähliche Wachstum wider und zeigt einen stetigen, wenn auch langsamen, Aufwärtstrend im Laufe der Zeit.
Eine der herausragenden Kennzahlen ist der Gewinnfaktor von 4,39. Dies ist eine solide Zahl, die darauf hindeutet, dass die Strategie für jede eingegangene Risikoeinheit 4,39 Einheiten an Gewinn einbrachte. Das bedeutet, dass der EA seine Gewinne im Vergleich zu seinen Verlusten effektiv maximieren konnte. Der Erholungsfaktor von 4,48 ist ein weiterer Beleg dafür, dass die Strategie in der Lage war, sich effektiv von Rückschlägen zu erholen, was ein positives Zeichen für ihre Robustheit ist. Bemerkenswert ist jedoch die Sharpe Ratio, die bei 5,25 liegt. Dieses Verhältnis ist zwar relativ hoch und deutet im Allgemeinen auf gute risikobereinigte Renditen hin, aber der insgesamt geringe Gewinn deutet darauf hin, dass die Strategie möglicherweise nur sehr geringe Risiken eingegangen ist, was zu dem begrenzten absoluten Gewinn geführt hat.
Wenn man sich die Handelsstatistiken anschaut, hat der EA insgesamt 16 Handelsgeschäfte ausgeführt, die sich gleichmäßig auf Kauf- und Verkaufs-Positionen verteilen. Die Gewinnquote für Kaufpositionen war mit 62,5 % höher, während Verkaufspositionen eine Gewinnquote von 37,5 % aufwiesen. Diese Diskrepanz deutet darauf hin, dass die Strategie erfolgreicher war, wenn es darum ging, die Aufwärtsbewegungen des Marktes zu erfassen. Interessanterweise machten die Gewinnpositionen genau 50 % der gesamten Trades aus, was deutlich macht, dass die Rentabilität des EA nicht auf eine hohe Gewinnrate zurückzuführen ist, sondern eher auf die Größe der Gewinner im Vergleich zu den Verlierern. Sowohl der größte als auch der durchschnittliche Gewinn waren positiv, was darauf hindeutet, dass es dem EA gelang, Gewinne zu sichern, wenn sich der Markt günstig entwickelte. Die Saldenkurve spiegelt diese Ergebnisse wider und zeigt Perioden des Rückgangs, gefolgt von Gewinnen, die auf einen vorsichtigen, aber stetigen Ansatz beim Handel hinweisen.
Die Drawdown-Metriken zeigen, dass der EA ein geringes Risiko aufrechterhalten hat. Der absolute Drawdown vom Saldo betrug 2 Einheiten, was recht gering ist, und der relative Abschlag betrug nur 0,02 %. Dieser sehr geringe Drawdown deutet darauf hin, dass die Strategie des EA konservativ war und der Kapitalerhaltung Vorrang vor aggressivem Gewinnstreben einräumte. Dieser konservative Ansatz kann in volatilen Märkten von Vorteil sein, erklärt aber auch den relativ bescheidenen Gesamtnettogewinn.
Zusammenfassend lässt sich sagen, dass der EA einen vorsichtigen und systematischen Handelsansatz verfolgte, der sich auf die Aufrechterhaltung eines hohen Gewinnfaktors und die Minimierung von Drawdowns konzentrierte. Der Gesamtgewinn war zwar relativ gering, aber der hohe Gewinnfaktor und die geringen Drawdowns deuten auf eine Strategie hin, bei der es mehr um Risikomanagement als um schnelle Gewinnerzielung geht. Dadurch eignet sich der EA für Händler, die ein stetiges, risikoarmes Wachstum gegenüber aggressiveren, risikoreicheren Strategien bevorzugen. Wer jedoch höhere absolute Renditen anstrebt, muss den EA möglicherweise weiter optimieren oder verbessern, um sein Gewinnpotenzial zu erhöhen und gleichzeitig sein solides Risikomanagement beizubehalten.
Schlussfolgerung
Die Erforschung der Integration von Deep-Learning-Modellen mit der traditionellen technischen Analyse hat vielversprechende Ergebnisse gezeigt. Anhand des Python-Skripts haben wir gesehen, wie ein systematischer Ansatz mit historischen Daten, Schlüsselindikatoren und maschinellem Lernen dazu beitragen kann, potenzielle Handelsmöglichkeiten zu identifizieren. Die Ergebnisse waren bescheiden, aber konsistent, was zeigt, dass eine solche Kombination zu einem positiven Ergebnis führen kann, wenn auch mit einigen Einschränkungen. Auch der MetaTrader 5 Expert Advisor (EA) bestätigt diesen vorsichtigen Ansatz. Während die Leistung des EA während des Backtestings ein kleines, aber stetiges Wachstum mit minimalem Drawdown zeigte, wurde eine Strategie deutlich, die sich eher auf das Risikomanagement als auf die Gewinnmaximierung konzentriert. Dieser konservative Charakter macht ihn für Händler interessant, die Wert auf stetiges, risikoarmes Wachstum legen. Für diejenigen, die höhere Renditen anstreben, ist jedoch eine weitere Verfeinerung und Optimierung erforderlich. Letztendlich dienen diese Tools als Grundlage - als Ausgangspunkt für Händler, um die menschliche Intuition mit der analytischen Leistung der KI zu verbinden und so einen nuancierten Ansatz zur Navigation in der komplexen Welt des Devisenhandels zu bieten.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/15868





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