English
preview
Selbstoptimierende Expert Advisors mit MQL5 und Python erstellen (Teil II): Abstimmung tiefer neuronaler Netze

Selbstoptimierende Expert Advisors mit MQL5 und Python erstellen (Teil II): Abstimmung tiefer neuronaler Netze

MetaTrader 5Beispiele | 27 September 2024, 11:18
18 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Einführung

Die Mitglieder unserer Community sind sehr daran interessiert, KI in ihre Handelsstrategien zu integrieren, was die Abstimmung von KI-Modellen auf bestimmte Märkte erfordert. Jedes KI-Modell verfügt über anpassbare Parameter, die seine Leistung erheblich beeinflussen; optimale Einstellungen für einen Markt funktionieren möglicherweise nicht für einen anderen. In diesem Artikel wird gezeigt, wie KI-Modelle mithilfe von Optimierungsalgorithmen, insbesondere dem Nelder-Mead-Algorithmus, so angepasst werden können, dass sie die Standardeinstellungen übertreffen. Wir werden diesen Algorithmus zur Feinabstimmung eines tiefen neuronalen Netzwerks mit Daten aus dem MetaTrader5-Terminal anwenden und dann das optimierte Modell im ONNX-Format zur Verwendung in einem Expert Advisor exportieren. Für diejenigen, die mit diesen Begriffen nicht vertraut sind, werden wir im Laufe des Artikels ausführliche Erläuterungen geben.


Nelder-Mead-Optimierungsalgorithmus

Der Nelder-Mead-Algorithmus ist eine beliebte Wahl für verrauschte, nicht-differenzierbare und nicht-lineare multimodale Optimierungsprobleme. Der nach seinen Erfindern John Nelder und Roger Mead benannte Algorithmus wurde 1965 in ihrem Papier „A Simplex Method for Function Minimization“ vorgestellt. Es kann sowohl für univariate als auch für multivariate Optimierungsprobleme verwendet werden.

Der Nelder-Mead-Algorithmus stützt sich nicht auf abgeleitete Informationen, sondern ist ein Optimierungsalgorithmus für die Mustersuche. Es erfordert, dass der Nutzer einen Ausgangspunkt angibt. Je nach gewähltem Startpunkt kann der Algorithmus in einem trügerischen lokalen Optimum stecken bleiben. Daher kann es von Vorteil sein, die Optimierung mehrmals mit unterschiedlichen Ausgangspunkten durchzuführen, um die Chancen auf ein globales Optimum zu erhöhen.

Der Algorithmus arbeitet mit einer geometrischen Form, die Simplex genannt wird. Das Simplex hat einen Scheitelpunkt für jede Eingangsvariable und einen zusätzlichen Scheitelpunkt. Die Punkte (Scheitelpunkte) des Simplex werden ausgewertet, und anhand einfacher Regeln werden die Punkte auf der Grundlage ihrer Auswertungen verschoben. Der Algorithmus hat bestimmte Abbruchbedingungen, wie z. B. das Erreichen der maximalen Anzahl von Iterationen oder das Erreichen einer minimalen Änderung der Bewertungswerte. Wenn keine Verbesserungen erzielt werden oder die zulässige Anzahl von Iterationen überschritten wird, wird das Optimierungsverfahren abgebrochen.

Roger Mead

Abb. 1: Roger Mead


John Nelder

Abb. 2: John Nelder


Fangen wir an

Wir beginnen damit, die benötigten Daten von unserem MetaTrader 5-Terminal zu holen. Zunächst öffnen wir unser MetaTrader 5-Terminal und klicken im Kontextmenü auf das Symbol-Symbol. Von dort aus wählen wir Balken aus und suchen nach dem gewünschten Symbol. Sobald wir die Daten angefordert haben, klicken wir einfach auf „Exportieren“ und die Daten stehen uns im CSV-Format zur Verfügung.

Suche nach den benötigten Daten

Abb. 3: Suche nach den benötigten Daten

Da unsere Daten fertig sind, können wir damit beginnen, die benötigten Bibliotheken zu importieren.

#import libraries we need
import pandas as pd
import numpy as np
from numpy.random import randn,rand
import seaborn as sns

Dann lesen wir die von uns vorbereiteten Daten ein.

#Read in our market data
brent = pd.read_csv("/home/volatily/market_data/Market Data UK Brent Oil.csv", sep="\t")

Wir müssen unsere Daten kennzeichnen.

#Preparing to label the data
look_ahead = 20

#Defining the target
brent["Target"] = brent["Close"].shift(-look_ahead)

#Drop missing values
brent.dropna(inplace=True)

Importieren wir nun die Bibliotheken, die wir für die Optimierung benötigen.

#In this article we will cover some techniques for hyper-parameter tuning 
from scipy.optimize import minimize
from sklearn.neural_network import MLPRegressor
from sklearn.model_selection import TimeSeriesSplit
from sklearn.metrics import root_mean_squared_error
import time

Wir werden nun unser Zeitreihen-Kreuzvalidierungsobjekt erstellen.

#Define the time series split parameters
splits = 5
gap = look_ahead

#Create the time series split object
tscv = TimeSeriesSplit(n_splits=splits,gap=gap)

#Create a dataframe to store our accuracy 
current_error_rate = pd.DataFrame(index = np.arange(0,splits),columns=["Current Error"])

Definieren wir die Prädiktoren und Ziele für unser Modell.

#Define the predictors and the target
predictors = ["Open","High","Low","Close"]
target = "Target"

Wir definieren nun die Funktion, die wir minimieren wollen: den Kreuzvalidierungsfehler des Modells. Bitte beachten Sie, dass dies nur zu Demonstrationszwecken dient. Idealerweise würden wir den Datensatz in zwei Hälften aufteilen, die Optimierung auf einer Hälfte durchführen und die Genauigkeit auf der anderen Hälfte messen. Für diese Demonstration optimieren wir jedoch das Modell und messen seine Genauigkeit anhand desselben Datensatzes.

#Define the objective function
def objective(x):
    #The parameter x represents a new value for our neural network's settings
    #In order to find optimal settings, we will perform 10 fold cross validation using the new setting
    #And return the average RMSE from all 10 tests
    #We will first turn the model's Alpha parameter, which controls the amount of L2 regularization
    model = MLPRegressor(hidden_layer_sizes=(5,2),alpha=x[0],early_stopping=True,shuffle=False,learning_rate_init=x[1],tol=x[2])
    #Now we will cross validate the model
    for i,(train,test) in enumerate(tscv.split(brent)):
        #The data
        X_train = brent.loc[train[0]:train[-1],predictors]
        y_train = brent.loc[train[0]:train[-1],target]
        X_test  = brent.loc[test[0]:test[-1],predictors]
        y_test  = brent.loc[test[0]:test[-1],target]
        #Train the model
        model.fit(X_train,y_train)
        #Measure the RMSE
        current_error_rate.iloc[i,0] = root_mean_squared_error(y_test,model.predict(X_test))
    #Return the Mean CV RMSE
    return(current_error_rate.iloc[:,0].mean())

Es sei daran erinnert, dass der Nelder-Mead-Algorithmus einen Ausgangspunkt erfordert. Um einen guten Ausgangspunkt zu finden, werden wir eine Zeilensuche über die fraglichen Parameter durchführen. Wir werden eine for-Schleife verwenden, um unsere Genauigkeit zu messen, wobei die Parameter auf 0,1, dann auf 0,01 und so weiter eingestellt werden. Dies wird uns helfen, einen potenziell guten Ausgangspunkt für den Algorithmus zu finden.

#Let us measure how much time this takes.
start = time.time()

#Create a dataframe to measure the error rates
starting_point_error = pd.DataFrame(index=np.arange(0,21),columns=["Average CV RMSE"])
starting_point_error["Iteration"] = np.arange(0,21)

#Let us first find a good starting point for our optimization algorithm
for i in np.arange(0,21):
    #Set a new starting point
    new_starting_point = (10.0 ** -i)
    #Store error rates
    starting_point_error.iloc[i,0] = objective([new_starting_point,new_starting_point,new_starting_point]) 

#Record the time stamp at the end
stop = time.time()

#Report the amount of time taken
print(f"Completed in {stop - start} seconds")
Beendet in 312.29402351379395 Sekunden

Betrachten wir nun unsere Fehlerquoten.

Durchschnittlicher CV RMSE
Iteration
0.91546
0
0.267167
1
14.846035
2
15.763264 3
56.820397 4
75.202923 5
72.562681
6
64.33746
7
88.980977
8
83.791834
9
82.871215
10
88.031151
11
65.532539
12
78.177191
13
85.063947
14
88.631589
15
74.369735
16
86.133656
17
90.482654
18
102.803612
19
74.636781
20

Wie wir sehen können, scheint es, dass wir zwischen Iteration 0 und 2 eine optimale Region überquert haben. Von da an wurde unser Fehler immer größer. Wir können die gleichen Informationen visuell beobachten.

sns.lineplot(data=starting_point_error,x="Iteration",y="Average CV RMSE")

Unsere Fehlerquote als

Abb. 4: Visualisierung der Ergebnisse unserer Zeilensuche

Da wir nun eine Vorstellung davon haben, was ein guter Ausgangspunkt sein könnte, wollen wir eine Funktion definieren, die uns zufällige Punkte innerhalb des Bereichs liefert, in dem wir das Optimum vermuten.

pt = abs(((10 ** -1) + rand(3) * ((10 ** 0) - (10 ** -1))))
pt
array([0.75747551, 0.34066536, 0.26214705])

Beachten Sie, dass wir ein Array mit 3 zufälligen Werten abrufen, weil wir 3 verschiedene Parameter für unser neuronales Netz optimieren. Führen wir nun die Abstimmung der Hyperparameter durch.

start = time.time()
result = minimize(objective,pt,method="nelder-mead")
stop = time.time()
print(f"Task completed in {stop - start} seconds")
Aufgabe abgeschlossen nach 1332.9911317825317 Sekunden

Lassen Sie uns das Ergebnis der Optimierung interpretieren

result
message: Maximum number of function evaluations has been exceeded.
       success: False
        status: 1
           fun: 0.12022686955703668
             x: [ 7.575e-01  3.577e-01  2.621e-01]
           nit: 225
          nfev: 600
 final_simplex: (array([[ 7.575e-01,  3.577e-01,  2.621e-01],
                       [ 7.575e-01,  3.577e-01,  2.621e-01],
                       [ 7.575e-01,  3.577e-01,  2.621e-01],
                       [ 7.575e-01,  3.577e-01,  2.621e-01]]), array([ 1.202e-01,  2.393e-01,  2.625e-01,  8.978e-01])

Achten Sie zunächst auf die nutzerfreundliche Meldung, die am oberen Rand angezeigt wird. Die Meldung zeigt an, dass der Algorithmus die maximale Anzahl von Funktionsbewertungen überschritten hat. Erinnern Sie sich an die Bedingungen, die wir zuvor in Bezug auf die Szenarien festgelegt haben, die zum Anhalten der Optimierung führen würden. Wir können zwar versuchen, die Anzahl der zulässigen Iterationen zu erhöhen, aber das ist keine Garantie für eine bessere Leistung.

Wir können den Schlüssel „fun“ sehen, der die optimale Ausgabe angibt, die der Algorithmus mit der Funktion erreicht hat. Danach folgt der Schlüssel „x“, der die Werte von x anzeigt, die zum optimalen Ergebnis geführt haben.

Wir können auch die Taste „nit“ beobachten, die uns die Anzahl der von der Funktion durchgeführten Iterationen angibt. Der Schlüssel „nfev“ schließlich gibt an, wie oft der Algorithmus die Zielfunktion aufgerufen hat, um ihre Ausgabe zu bewerten. Erinnern Sie sich daran, dass unsere Zielfunktion eine 5-fache Kreuzvalidierung durchführt und die durchschnittliche Fehlerrate liefert. Das bedeutet, dass wir bei jedem Aufruf der Funktion unser neuronales Netz 5 Mal anpassen. 600 Funktionsbewertungen bedeuten also, dass wir unser neuronales Netz 3000 Mal anpassen!

Lassen Sie uns nun das Standardmodell und das von uns erstellte angepasste Modell vergleichen.

#Let us compare our customised model and the defualt model
custom_model = MLPRegressor(hidden_layer_sizes=(5,2),alpha=result.x[0],early_stopping=True,shuffle=False,learning_rate_init=result.x[1],tol=result.x[2])

#Default model
default_model = MLPRegressor(hidden_layer_sizes=(5,2))

Wir bereiten das geteilte Zeitreihenobjekt vor.

#Define the time series split parameters
splits = 10
gap = look_ahead

#Create the time series split object
tscv = TimeSeriesSplit(n_splits=splits,gap=gap)

#Create a dataframe to store our accuracy 
model_error_rate = pd.DataFrame(index = np.arange(0,splits),columns=["Default Model","Custom Model"])

Wir werden nun jedes Modell einer Kreuzvalidierung unterziehen.

#Now we will cross validate the model
for i,(train,test) in enumerate(tscv.split(brent)):
    #The data
    X_train = brent.loc[train[0]:train[-1],predictors]
    y_train = brent.loc[train[0]:train[-1],target]
    X_test  = brent.loc[test[0]:test[-1],predictors]
    y_test  = brent.loc[test[0]:test[-1],target]
    #Our model
    model = MLPRegressor(hidden_layer_sizes=(5,2),alpha=result.x[0],early_stopping=True,shuffle=False,learning_rate_init=result.x[1],tol=result.x[2])
    #Train the model
    model.fit(X_train,y_train)
    #Measure the RMSE
    model_error_rate.iloc[i,1] = root_mean_squared_error(y_test,model.predict(X_test))

Betrachten wir unsere Fehlermetriken.

model_error_rate
Standardmodell
Kundenspezifisches Modell
0.153904    
0.550214
0.113818     
0.501043
82.188345    
0.52897
0.114108   
0.117466
0.114718    
0.112892
77.508403    
0.258558
0.109191    
0.304262
0.142143    
0.363774
0.163161    
0.153202
0.120068    
2.20102

Lassen Sie uns die Ergebnisse auch visualisieren.

model_error_rate["Default Model"].plot(legend=True)
model_error_rate["Custom Model"].plot(legend=True)

Visualisierung der Leistung unseres Modells

Abb. 5: Visualisierung der Leistung unseres angepassten Modells

Wie wir feststellen können, hat das angepasste Modell das Standardmodell übertroffen. Unser Test wäre jedoch überzeugender gewesen, wenn wir separate Datensätze für das Training der Modelle und die Bewertung ihrer Genauigkeit verwendet hätten. Die Verwendung desselben Datensatzes für beide Zwecke ist nicht das ideale Verfahren.

Als Nächstes bereiten wir die Konvertierung unseres tiefen neuronalen Netzes in seine ONNX-Darstellung vor. ONNX, die Abkürzung für Open Neural Network Exchange, ist ein standardisiertes Format, mit dem KI-Modelle, die in einem beliebigen konformen Framework trainiert wurden, in verschiedenen Programmen verwendet werden können. ONNX ermöglicht es uns zum Beispiel, ein KI-Modell in Python zu trainieren und es dann in MQL5 oder sogar in einem Java-Programm zu verwenden (vorausgesetzt, die Java-API unterstützt ONNX).

Zunächst importieren wir die benötigten Bibliotheken.

#Now we will prepare to export our neural network into ONNX format
from skl2onnx.common.data_types import FloatTensorType
from skl2onnx import convert_sklearn
import onnxruntime as ort
import netron

Definieren wir die Eingabeform für unser Modell, denn unser Modell benötigt 4 Eingaben.

#Define the input types
initial_type = [("float_input",FloatTensorType([1,4]))]

Anpassung an unser maßgeschneidertes Modell.

#Fit our custom model
custom_model.fit(brent.loc[:,["Open","High","Low","Close"]],brent.loc[:,"Target"])

Die Erstellung der ONNX-Darstellung unseres tiefen neuronalen Netzes ist dank der skl2onnx-Bibliothek einfach.

#Create the onnx represantation
onnx = convert_sklearn(custom_model,initial_types=initial_type,target_opset=12)

Definieren wir den Namen unserer ONNX-Datei.

#The name of our ONNX file
onnx_filename = "Brent_M1.onnx"

Jetzt werden wir die ONNX-Datei schreiben.

#Write out the ONNX file
with open(onnx_filename,"wb") as f:
    f.write(onnx.SerializeToString())

Schauen wir uns nun die Parameter unseres ONNX-Modells an.

#Now let us inspect our ONNX model
onnx_session = ort.InferenceSession(onnx_filename)
input_name   = onnx_session.get_inputs()[0].name
output_name = onnx_session.get_outputs()[0].name

Schauen wir uns die Eingabeform an.

for i, input_tensor in enumerate(onnx_session.get_inputs()):
    print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")
1. Name: float_input, Datentyp: tensor(float), Form: [1, 4]

Beobachten Sie die Form der Ausgabe unseres Modells.

for i, output_tensor in enumerate(onnx_session.get_outputs()):
    print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")
1. Name: variable, Datentyp: tensor(float), Form: [1, 1]

Jetzt können wir unser Modell mit netron visuell darstellen. Diese Schritte helfen uns sicherzustellen, dass unsere ONNX-Eingabe- und Ausgabeformen unseren Erwartungen entsprechen.

#We can also inspect our model visually using netron.
netron.start(onnx_filename)

Netron-Visualisierung

Abb. 6: Die ONNX-Darstellung unseres neuronalen Netzes


Unsere ONNX-Datei Metadaten

Abb. 7: Meta-Details zu unserem ONNX-Modell

Netron ist eine Open-Source-Python-Bibliothek, die es uns ermöglicht, ONNX-Modelle visuell zu inspizieren, ihre Parameter zu überprüfen und Metadaten zu kontrollieren. Für diejenigen, die mehr über die Verwendung von ONNX-Modellen in MetaTrader 5 erfahren möchten, gibt es viele gut geschriebene Artikel. Einer meiner Lieblingsautoren zu diesem Thema ist Omega.


Implementierung in MQL5

Nachdem die Konfiguration unseres ONNX-Modells abgeschlossen ist, können wir mit der Erstellung unseres Expert Advisors in MQL5 beginnen.

Schematisches Diagramm unserer MQL5-Anwendung

Abb. 8: Ein schematischer Plan unseres Expert Advisors

Unser Expert Advisor wird das angepasste ONNX-Modell verwenden, um Einstiegssignale zu generieren. Alle guten Händler lassen jedoch Vorsicht walten und führen nicht jedes Einstiegssignal aus, das sie erhalten. Um unserem Expert Advisor diese Disziplin beizubringen, werden wir ihn so programmieren, dass er auf die Bestätigung durch technische Indikatoren wartet, bevor er eine Position eröffnet.

Diese technischen Indikatoren helfen uns, den Zeitpunkt für unsere Einstiege effektiv zu bestimmen. Sobald Positionen geöffnet sind, werden sie durch nutzerdefinierte Stop-Loss-Levels geschlossen. Der erste Schritt besteht darin, das ONNX-Modell als Ressource für unsere Anwendung zu spezifizieren.

//+------------------------------------------------------------------+
//|                                   Custom Deep Neural Network.mq5 |
//|                                        Gamuchirai Zororo Ndawana |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Zororo Ndawana"
#property link      "https://www.mql5.com/en/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Load the ONNX model                                              |
//+------------------------------------------------------------------+
#resource "\\Files\\Brent_M1.onnx" as const uchar ModelBuffer[];

Als Nächstes werden wir die Handelsbibliothek laden, die für die Verwaltung unserer Positionen unerlässlich ist.

//+------------------------------------------------------------------+
//| Libraries we need                                                |
//+------------------------------------------------------------------+
#include <Trade/Trade.mqh>
CTrade Trade;

Nun können wir globale Variablen für unser Programm erstellen.

//+------------------------------------------------------------------+
//| Gloabal variables                                                |
//+------------------------------------------------------------------+
long model; //The handler for our ONNX model
vector forecast = vector::Zeros(1); //Our model's forecast

const int states = 3; //The total number of states the system can be in
vector state = vector::Zeros(states); //The state of our system

int mfi_handler,wpr_handler; //Handlers for our technical indicators
vector mfi_reading,wpr_reading; //The values of our indicators will be kept in vectors

double minimum_volume, trading_volume; //Smallest lot size allowed & our calculated lotsize
double ask_price, bid_price; //Market rates

Lassen Sie uns Nutzereingaben definieren, mit denen wir das Verhalten des Expert Advisors ändern können.

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input int mfi_period = 20; //Money Flow Index Period
input int wpr_period = 30; //Williams Percent Range Period

input int lot_multiple = 20; //How big should our lot sizes be?
input double sl_width = 2; //How tight should the stop loss be?

input double max_profit = 10; //Close the position when this profit level is reached.
input double max_loss = 10; //Close the position when this loss level is reached.

Unsere Anwendung benötigt Hilfsfunktionen, um bestimmte Routinen auszuführen. Wir beginnen mit der Definition einer Funktion, die den Zustand der Anwendung verwaltet. Diese Anwendung wird drei Zustände haben: Zustand 0 bedeutet, dass wir keine Positionen haben, während die Zustände 1 und 2 eine Kauf- bzw. Verkaufsposition anzeigen.

Je nach aktuellem Zustand hat die Anwendung Zugriff auf unterschiedliche Funktionen.

//+------------------------------------------------------------------+
//| This function is responsible for updating the system state       |
//+------------------------------------------------------------------+
void update_state(int index)
  {
//--- Reset the system state
   state = vector::Zeros(states);
//--- Now update the current state
   state[index] = 1;
  }

Als Nächstes benötigen wir eine Funktion, die für die Überprüfung der Nutzereingaben beim Starten der Anwendung verantwortlich ist. Diese Funktion stellt zum Beispiel sicher, dass alle Perioden der technischen Indikatoren größer als 0 sind.

//+------------------------------------------------------------------+
//| This function will ensure that user inputs are valid             |
//+------------------------------------------------------------------+
bool valid_inputs(void)
  {
//--- Let us validate the inputs the user passed
   return((mfi_period > 0)&&(wpr_period > 0) && (max_profit >= 0) && (max_loss >= 0) && (lot_multiple >= 0) && (sl_width >= 0));
  }

Unser Expert Advisor prüft laufend, ob die Gewinnniveaus den Vorgaben des Nutzers entsprechen. Wenn der Nutzer beispielsweise ein maximales Gewinnziel von $1 festlegt, wird die Position automatisch geschlossen, sobald sie einen Gewinn von $1 erreicht hat, auch wenn das Take-Profit-Niveau noch nicht erreicht wurde. Die gleiche Logik gilt für den Stop-Loss: Die Position wird geschlossen, je nachdem, welcher Schwellenwert zuerst erreicht wird, sei es das Stop-Loss-Niveau oder das maximale Verlustniveau. Diese Funktion soll Flexibilität bei der Festlegung akzeptabler Risikoniveaus bieten.

//+------------------------------------------------------------------+
//| This function will check our profit levels                       |
//+------------------------------------------------------------------+
void check_profit_level(void)
  {
//--- Let us check if the user set a max profit/loss limit
   if(max_loss > 0 || max_profit > 0)
     {
      //--- If true, let us inspect whether we have passed the limit.
      if((PositionGetDouble(POSITION_PROFIT) > max_profit) || (PositionGetDouble(POSITION_PROFIT) < (max_loss * -1)))
        {
         //--- Close the position
         Trade.PositionClose(Symbol());
        }
     }
  }

Da wir über ein KI-basiertes System verfügen, sollten wir eine Funktion entwickeln, die überprüft, ob unser Modell eine Marktbewegung vorhersagt, die sich negativ auf unsere offene Position auswirken könnte. Solche Signale können als Frühindikatoren für eine veränderte Marktstimmung dienen.

//+------------------------------------------------------------------+
//| If we predict a reversal, let's close our positions              |
//+------------------------------------------------------------------+
void find_reversal(void)
  {
//--- We have a position
   if(((state[1] == 1) && (forecast[0] < iClose(Symbol(),PERIOD_CURRENT,0))) || ((state[2] == 1) && (forecast[0] > iClose(Symbol(),PERIOD_CURRENT,0))))
     {
      Trade.PositionClose(Symbol());
     }
  }

Als Nächstes definieren wir eine Funktion, die auf gültige Einstiegssignale prüft. Ein Einstiegssignal wird als gültig angesehen, wenn es zwei Bedingungen erfüllt: Erstens muss es durch Kursänderungen auf höheren Zeitrahmen unterstützt werden; zweitens muss unser KI-Modell eine Kursbewegung vorhersagen, die mit diesem höheren Trend übereinstimmt. Wenn beide Bedingungen erfüllt sind, werden wir unsere technischen Indikatoren überprüfen, um die endgültige Bestätigung zu erhalten.

//+------------------------------------------------------------------+
//| This function will determine if we have a valid entry            |
//+------------------------------------------------------------------+
void find_entry(void)
  {
//--- First we want to know if the higher timeframes are moving in the same direction we want to go
   double higher_time_frame_trend = iClose(Symbol(),PERIOD_W1,16) - iClose(Symbol(),PERIOD_W1,0);

//--- If price levels appreciated, the difference will be negative
   if(higher_time_frame_trend < 0)
     {
      //--- We may be better off only taking buy opportunities
      //--- Buy opportunities are triggered when the model's prediction is greater than the current price
      if(forecast[0] > iClose(Symbol(),PERIOD_CURRENT,0))
        {
         //--- We will use technical indicators to time our entries
         bullish_sentiment();
        }
     }

//--- If price levels depreciated, the difference will be positive
   if(higher_time_frame_trend > 0)
     {
      //--- We may be better off only taking sell opportunities
      //--- Sell opportunities are triggered when the model's prediction is less than the current price
      if(forecast[0] < iClose(Symbol(),PERIOD_CURRENT,0))
        {
         //--- We will use technical indicators to time our entries
         bearish_sentiment();
        }
     }
  }

Nun sind wir bei der Funktion angelangt, die für die Interpretation unserer technischen Indikatoren zuständig ist. Es gibt verschiedene Möglichkeiten, diese Indikatoren zu interpretieren; ich ziehe es jedoch vor, sie auf 50 zu zentrieren. Dabei bestätigen Werte über 50 eine Aufwärts-Stimmung, während Werte unter 50 eine Abwärts-Stimmung anzeigen. Wir werden den Money Flow Index (MFI) als Volumenindikator und den Williams Percent Range (WPR) als Trendstärkeindikator verwenden.

//+------------------------------------------------------------------+
//| This function will interpret our indicators for buy signals      |
//+------------------------------------------------------------------+
void bullish_sentiment(void)
  {
//--- For bullish entries we want strong volume readings from our MFI
//--- And confirmation from our WPR indicator
   wpr_reading.CopyIndicatorBuffer(wpr_handler,0,0,1);
   mfi_reading.CopyIndicatorBuffer(mfi_handler,0,0,1);
   if((wpr_reading[0] > -50) && (mfi_reading[0] > 50))
     {
      //--- Get the ask price
      ask_price = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
      //--- Make sure we have no open positions
      if(PositionsTotal() == 0)
         Trade.Buy(trading_volume,Symbol(),ask_price,(ask_price - sl_width),(ask_price + sl_width),"Custom Deep Neural Network");
      update_state(1);
     }
  }

//+------------------------------------------------------------------+
//| This function will interpret our indicators for sell signals     |
//+------------------------------------------------------------------+
void bearish_sentiment(void)
  {
//--- For bearish entries we want strong volume readings from our MFI
//--- And confirmation from our WPR indicator
   wpr_reading.CopyIndicatorBuffer(wpr_handler,0,0,1);
   mfi_reading.CopyIndicatorBuffer(mfi_handler,0,0,1);
   if((wpr_reading[0] < -50) && (mfi_reading[0] < 50))
     {
      //--- Get the bid price
      bid_price = SymbolInfoDouble(Symbol(),SYMBOL_BID);
      if(PositionsTotal() == 0)
         Trade.Sell(trading_volume,Symbol(),bid_price,(bid_price + sl_width),(bid_price - sl_width),"Custom Deep Neural Network");
      //--- Update the state
      update_state(2);
     }
  }

Als Nächstes konzentrieren wir uns auf die Vorhersagen unseres ONNX-Modells. Zur Erinnerung: Unser Modell erwartet Eingaben der Form [1,4] und liefert Ausgaben der Form [1,1]. Wir definieren Vektoren, um die Eingaben und Ausgaben entsprechend zu speichern, und verwenden dann die Funktion OnnxRun, um die Prognose des Modells zu erhalten.

//+------------------------------------------------------------------+
//| This function will fetch forecasts from our model                |
//+------------------------------------------------------------------+
void model_predict(void)
  {
//--- First we get the input data ready
   vector input_data = {iOpen(_Symbol,PERIOD_CURRENT,0),iHigh(_Symbol,PERIOD_CURRENT,0),iLow(_Symbol,PERIOD_CURRENT,0),iClose(_Symbol,PERIOD_CURRENT,0)};
//--- Now we need to perform inferencing
   if(!OnnxRun(model,ONNX_DATA_TYPE_FLOAT,input_data,forecast))
     {
      Comment("Failed to obtain a forecast from the model: ",GetLastError());
      forecast[0] = 0;
      return;
     }
//--- We succeded!
   Comment("Model forecast: ",forecast[0]);
  }

Jetzt können wir mit der Erstellung einer Ereignisbehandlung für unsere Anwendung beginnen, der bei der Initialisierung des Expert Advisors aufgerufen wird. Unser Verfahren wird zunächst die Nutzereingaben validieren und dann die Eingabe- und Ausgabeformen unseres ONNX-Modells definieren. Als Nächstes werden wir unsere technischen Indikatoren einrichten, Marktdaten abrufen und schließlich den Status unseres Systems auf 0 setzen.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Make sure user inputs are valid
   if(!valid_inputs())
     {
      Comment("Invalid inputs were passed to the application.");
      return(INIT_FAILED);
     }

//--- Create the ONNX model from the buffer
   model = OnnxCreateFromBuffer(ModelBuffer,ONNX_DEFAULT);

//--- Check if we were succesfull
   if(model == INVALID_HANDLE)
     {
      Comment("[ERROR] Failed to create the ONNX model from the buffer: ",GetLastError());
      return(INIT_FAILED);
     }

//--- Set the input shape of the model
   ulong input_shape[] = {1,4};

//--- Check if we were succesfull
   if(!OnnxSetInputShape(model,0,input_shape))
     {
      Comment("[ERROR] Failed to set the ONNX model input shape: ",GetLastError());
      return(INIT_FAILED);
     }

//--- Set the output shape of the model
   ulong output_shape[] = {1,1};

//--- Check if we were succesfull
   if(!OnnxSetOutputShape(model,0,output_shape))
     {
      Comment("[ERROR] Failed to set the ONNX model output shape: ",GetLastError());
      return(INIT_FAILED);
     }

//--- Setup the technical indicators
   wpr_handler = iWPR(Symbol(),PERIOD_CURRENT,wpr_period);
   mfi_handler = iMFI(Symbol(),PERIOD_CURRENT,mfi_period,VOLUME_TICK);

//--- Fetch market data
   minimum_volume = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);
   trading_volume = minimum_volume * lot_multiple;

//--- Set the system to state 0, indicating we have no open positions
   update_state(0);

//--- Everything went fine
   return(INIT_SUCCEEDED);
  }

Ein entscheidender Teil unserer Anwendung ist die Deinitialisierungsprozedur. In dieser Ereignisbehandlung werden alle Ressourcen freigegeben, die nicht mehr benötigt werden, wenn der Expert Advisor nicht in Gebrauch ist.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Free the onnx resources
   OnnxRelease(model);

//--- Free the indicator resources
   IndicatorRelease(wpr_handler);
   IndicatorRelease(mfi_handler);

//--- Detach the expert advisor
   ExpertRemove();
  }

Schließlich müssen wir unsere Ereignishandler von OnTick definieren. Welche Maßnahmen ergriffen werden, hängt vom Zustand des Systems ab. Wenn wir keine offenen Positionen haben (Zustand 0), besteht unsere Priorität darin, eine Prognose von unserem Modell zu erhalten und einen möglichen Einstieg zu identifizieren. Wenn wir eine offene Position haben (Status 1 für Kauf oder Status 2 für Verkauf), liegt unser Schwerpunkt auf der Verwaltung der Position. Dazu gehören die Überwachung möglicher Umkehrungen und die Überprüfung von Risikoniveaus, Gewinnzielen und maximalen Gewinnhöhen.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Which state is the system in?
   if(state[0] == 1)
     {
      //--- Being in this state means we have no open positions, let's analyse the market to try find one
      model_predict();
      find_entry();
     }

   if((state[1] == 1) || (state[2] == 1))
     {
      //--- Being in this state means we have an position open, if our model forecasts a reversal move we will close
      model_predict();
      find_reversal();
      check_profit_level();
     }
  }
//+------------------------------------------------------------------+

Backtesting unseres EA

Abb. 9: Testen unseres Expert Advisors


Schlussfolgerung

Dieser Artikel bot eine leichte Einführung in die Verwendung von Optimierungsalgorithmen für die Auswahl von Modellhyperparametern. In künftigen Artikeln werden wir eine robustere Methodik anwenden und zwei spezielle Datensätze verwenden: einen für die Optimierung des Modells und den anderen für die Kreuzvalidierung und den Vergleich der Leistung mit einem Modell mit Standardeinstellungen.

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

Die Übertragung der Trading-Signale in einem universalen Expert Advisor. Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
In diesem Artikel wurden die verschiedenen Möglichkeiten beschrieben, um die Trading-Signale von einem Signalmodul des universalen EAs zum Steuermodul der Positionen und Orders zu übertragen. Es wurden die seriellen und parallelen Interfaces betrachtet.
Preisgesteuertes CGI-Modell: Erweiterte Datennachbearbeitung und Implementierung Preisgesteuertes CGI-Modell: Erweiterte Datennachbearbeitung und Implementierung
In diesem Artikel befassen wir uns mit der Entwicklung eines vollständig anpassbaren Skripts für den Preisdatenexport mit MQL5, das einen neuen Fortschritt in der Simulation des CGI-Modells Price Man darstellt. Wir haben fortschrittliche Verfeinerungstechniken implementiert, um sicherzustellen, dass die Daten nutzerfreundlich und für Animationszwecke optimiert sind. Außerdem werden wir die Möglichkeiten von Blender 3D bei der effektiven Arbeit mit und der Visualisierung von Preisdaten kennenlernen und sein Potenzial für die Erstellung dynamischer und ansprechender Animationen demonstrieren.
Eine alternative Log-datei mit der Verwendung der HTML und CSS Eine alternative Log-datei mit der Verwendung der HTML und CSS
In diesem Artikel werden wir eine sehr einfache, aber leistungsfähige Bibliothek zur Erstellung der HTML-Dateien schreiben, dabei lernen wir auch, wie man eine ihre Darstellung einstellen kann (nach seinem Geschmack) und sehen wir, wie man es leicht in seinem Expert Advisor oder Skript hinzufügen oder verwenden kann.
Ein Beispiel für automatisch optimierte Take-Profits und Indikatorparameter mit SMA und EMA Ein Beispiel für automatisch optimierte Take-Profits und Indikatorparameter mit SMA und EMA
Dieser Artikel stellt einen hochentwickelten Expert Advisor für den Devisenhandel vor, der maschinelles Lernen mit technischer Analyse kombiniert. Es konzentriert sich auf den Handel mit Apple-Aktien und bietet adaptive Optimierung, Risikomanagement und mehrere Strategien. Das Backtesting zeigt vielversprechende Ergebnisse mit hoher Rentabilität, aber auch erheblichen Drawdowns, was auf Potenzial für eine weitere Verfeinerung hinweist.