English Русский 中文 Español 日本語
preview
PSAR, Heiken Ashi und Deep Learning gemeinsam für den Handel nutzen

PSAR, Heiken Ashi und Deep Learning gemeinsam für den Handel nutzen

MetaTrader 5Beispiele | 15 November 2024, 10:52
257 0
Javier Santiago Gaston De Iriarte Cabrera
Javier Santiago Gaston De Iriarte Cabrera

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:

  1.  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.
  2.  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.
  3.  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

Grafik Preis und Strategie

Heiken Ashi

Salden

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

Einstellungen

Eingaben

Backtesting

Saldenkurven

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

Beigefügte Dateien |
EURUSD_D1_2024.onnx (884.41 KB)
004.py (8.06 KB)
PHD_Final.mq5 (27.7 KB)
005.py (8.14 KB)
Neuronale Netze leicht gemacht (Teil 91): Vorhersage durch Frequenzbereiche (Frequency Domain Forecasting, FreDF) Neuronale Netze leicht gemacht (Teil 91): Vorhersage durch Frequenzbereiche (Frequency Domain Forecasting, FreDF)
Wir fahren fort mit der Analyse und Vorhersage von Zeitreihen im Frequenzbereich. In diesem Artikel machen wir uns mit einer neuen Methode zur Vorhersage von Daten im Frequenzbereich vertraut, die zu vielen der bisher untersuchten Algorithmen hinzugefügt werden kann.
Wie man die automatische Optimierung in MQL5 Expert Advisors implementiert Wie man die automatische Optimierung in MQL5 Expert Advisors implementiert
Schritt für Schritt Anleitung zur automatischen Optimierung in MQL5 für Expert Advisors. Wir werden eine robuste Optimierungslogik, bewährte Verfahren für die Parameterauswahl und die Rekonstruktion von Strategien mit Backtesting behandeln. Darüber hinaus werden übergeordnete Methoden wie die Walk-Forward-Optimierung erörtert, um Ihren Handelsansatz zu verbessern.
Entwicklung eines Replay Systems (Teil 51): Die Dinge werden kompliziert (III) Entwicklung eines Replay Systems (Teil 51): Die Dinge werden kompliziert (III)
In diesem Artikel werden wir uns mit einem der schwierigsten Probleme im Bereich der MQL5-Programmierung befassen: wie man eine Chart-ID korrekt erhält und warum Objekte manchmal nicht im Chart gezeichnet werden. Die hier vorgestellten Materialien sind ausschließlich für didaktische Zwecke bestimmt. Die Anwendung sollte unter keinen Umständen zu einem anderen Zweck als zum Erlernen und Beherrschen der vorgestellten Konzepte verwendet werden.
Beispiel für CNA (Causality Network Analysis), SMOC (Stochastic Model Optimal Control) und Nash Game Theory mit Deep Learning Beispiel für CNA (Causality Network Analysis), SMOC (Stochastic Model Optimal Control) und Nash Game Theory mit Deep Learning
Wir werden Deep Learning zu den drei Beispielen hinzufügen, die in früheren Artikeln veröffentlicht wurden, und die Ergebnisse mit den vorherigen vergleichen. Das Ziel ist es, zu lernen, wie man DL zu anderen EAs hinzufügt.