English Русский 中文 Español 日本語 Português
preview
Verschaffen Sie sich einen Vorteil auf jedem Markt (Teil III): Visa-Ausgabenindex

Verschaffen Sie sich einen Vorteil auf jedem Markt (Teil III): Visa-Ausgabenindex

MetaTrader 5Beispiele |
133 6
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Einführung

Im Zeitalter von Big Data stehen dem modernen Anleger nahezu unendlich viele alternative Datenquellen zur Verfügung. Jeder Datensatz birgt das Potenzial, bei der Vorhersage von Marktrenditen ein höheres Maß an Genauigkeit zu erreichen. Doch nur wenige Datensätze halten, was sie versprechen. In dieser Artikelserie werden wir Ihnen helfen, die weite Landschaft alternativer Daten zu erforschen, um Ihnen zu helfen, eine fundierte Entscheidung zu treffen, ob Sie diese Datensätze in Ihre Analyse einbeziehen sollten, andererseits, wenn diese Datensätze unbefriedigende Ergebnisse liefern, dann können wir Ihnen helfen, Zeit zu sparen.

Unser Grundgedanke ist, dass wir durch die Berücksichtigung alternativer Datensätze, die nicht direkt im MetaTrader 5-Terminal verfügbar sind, Variablen aufdecken können, die die Kursniveaus mit relativ höherer Genauigkeit vorhersagen als ein gelegentlicher Marktteilnehmer, der sich ausschließlich auf Marktnotierungen verlässt.


Überblick über die Handelsstrategie

VISA ist ein amerikanisches multinationales Zahlungsdienstleistungsunternehmen. Das Unternehmen wurde 1958 gegründet und betreibt heute eines der größten Transaktionsverarbeitungsnetzwerke der Welt. VISA ist gut positioniert, um eine Quelle für seriöse alternative Daten zu sein, da das Unternehmen fast alle Märkte in der entwickelten Welt durchdrungen hat. Darüber hinaus erhebt auch die Federal Reserve Bank of St. Louis einen Teil ihrer makroökonomischen Daten bei VISA.

In dieser Diskussion werden wir den VISA Spending Momentum Index (SMI) analysieren. Der Index ist ein makroökonomischer Indikator für das Ausgabeverhalten der Verbraucher. Die Daten werden von VISA unter Verwendung der firmeneigenen Netzwerke und der VISA-Debit- und -Kreditkarten gesammelt. Alle Daten sind entpersonalisiert und werden hauptsächlich in den Vereinigten Staaten erhoben. Da VISA weiterhin Daten aus verschiedenen Märkten sammelt, könnte dieser Index schließlich zu einem Maßstab für das weltweite Verbraucherverhalten werden.

Wir werden einen von der Federal Reserve Bank of St. Louis bereitgestellten API-Dienst nutzen, um die VISA SMI-Datensätze abzurufen. Die API der Federal Reserve Economic Database (FRED) ermöglicht uns den Zugang zu Hunderttausenden verschiedener wirtschaftlicher Zeitreihendaten, die auf der ganzen Welt gesammelt wurden.


Zusammenfassung der Methodik

Die SMI-Daten werden monatlich von VISA veröffentlicht und enthalten zum Zeitpunkt der Erstellung dieses Berichts weniger als 200 Zeilen. Daher benötigen wir eine Modellierungstechnik, die gleichzeitig resistent gegen eine Überanpassung und flexibel genug ist, um komplexe Beziehungen zu erfassen. Dies könnte eine ideale Aufgabe für ein neuronales Netz sein.

Wir haben 5 Parameter eines tiefen neuronalen Netzwerks optimiert, um die Veränderungen im EURUSD zu klassifizieren, wenn ein Satz von gewöhnlichen Eröffnungs-, Höchst-, Tiefst- und Schlusskursen mit 3 zusätzlichen Eingaben, nämlich den VISA-Datensätzen, vorliegt. Unser optimiertes Modell konnte bei der Validierung eine Genauigkeit von 71 % erreichen und übertraf damit das Standardmodell deutlich. Der Leser sollte jedoch bedenken, dass diese Genauigkeit auf monatlichen Daten beruht!

Wir setzten 1000 Iterationen eines randomisierten Suchalgorithmus ein, um das tiefe neuronale Netzwerk zu optimieren, und trainierten das Modell erfolgreich, ohne dass es sich zu stark an die Trainingsdaten anpasste. So beeindruckend diese Ergebnisse auch sein mögen, wir können nicht mit Sicherheit sagen, dass die beobachtete Beziehung zuverlässig ist. Unsere Algorithmen zur Merkmalsauswahl verwarfen alle 3 VISA-Datensätze bei der Auswahl der wichtigsten Merkmale auf nicht-parametrische Weise. Darüber hinaus weisen alle drei VISA-Datensätze relativ niedrige Werte für die gegenseitige Information auf, was für uns ein Hinweis darauf sein könnte, dass die Datensätze unabhängig sind oder dass es uns nicht gelungen ist, die Beziehung in einer für unser Modell sinnvollen Weise aufzudecken.


Datenextraktion

Um die benötigten Daten abzurufen, müssen Sie zunächst ein Konto auf der FRED-Website anlegen. Nachdem Sie ein Konto eingerichtet haben, können Sie Ihren FRED-API-Schlüssel verwenden, um auf die wirtschaftlichen Zeitreihendaten der Federal Reserve of St. Louis zuzugreifen und unserer Diskussion zu folgen. Unsere Marktdaten zu EURUSD-Kursen werden direkt vom Terminal über die MetaTrader 5 Python API abgerufen.

Um loszulegen, laden Sie zunächst die benötigten Bibliotheken.

#Import the libraries we need
import pandas as pd
import seaborn as sns
import numpy as np
from fredapi import Fred
import MetaTrader5 as mt5
from datetime import datetime
import time
import pytz

Richten Sie nun den FRED-API-Schlüssel ein und rufen Sie die benötigten Daten ab.

#Let's setup our FredAPI
fred = Fred(api_key="ENTER YOUR API KEY")
visa_discretionary = pd.DataFrame(fred.get_series("VISASMIDSA"),columns=["visa d"])
visa_headline   = pd.DataFrame(fred.get_series("VISASMIHSA"),columns=["visa h"])
visa_non_discretionary = pd.DataFrame(fred.get_series("VISASMINSA"),columns=["visa nd"])

Legen Sie den Prognosehorizont fest.

#Define how far ahead we want to forecast
look_ahead = 10


Visualisieren der Daten

Lassen Sie uns alle drei Datensätze visualisieren.

visa_discretionary.plot(title="VISA Spending Momentum Index: Discretionary")

Abb. 1: Der erste VISA-Datensatz

Lassen Sie uns nun den zweiten Datensatz visualisieren.

visa_headline.plot(title="VISA Spending Momentum Index: Headline")

Abb. 2: Der zweite VISA-Datensatz

Und schließlich unser dritter VISA-Datensatz.

visa_non_discretionary.plot(title="VISA Spending Momentum Index: Non-Discretionary")

Abb. 3: Der dritte VISA-Datensatz

Die ersten beiden Datensätze scheinen fast identisch zu sein, und wie wir später noch sehen werden, weisen sie Korrelationswerte von 0,89 auf, was bedeutet, dass sie möglicherweise dieselben Informationen enthalten. Dies legt uns nahe, dass wir das eine fallen lassen und das andere behalten können. Wir überlassen es jedoch unserem Algorithmus für die Merkmalsauswahl zu entscheiden, ob dies notwendig ist.


Daten von unserem MetaTrader 5 Terminal abrufen

Wir werden nun unser Terminal initialisieren.

#Initialize the terminal
mt5.initialize()

Jetzt müssen wir unsere Zeitzone angeben.

#Set timezone to UTC
timezone = pytz.timezone("Etc/UTC")

Erstellen wir ein Datetime-Objekt.

#Create a 'datetime' object in UTC
utc_from = datetime(2024,7,1,tzinfo=timezone)

Abrufen der Daten aus MetaTrader 5 und Einpacken in einen Pandas-Datenrahmen.

#Fetch the data
eurusd = pd.DataFrame(mt5.copy_rates_from("EURUSD",mt5.TIMEFRAME_MN1,utc_from,visa_headline.shape[0]))

Wir wollen die Daten beschriften und den Zeitstempel als Index verwenden.

#Label the data
eurusd["target"] = np.nan
eurusd.loc[eurusd["close"] > eurusd["close"].shift(-look_ahead),"target"] = 0
eurusd.loc[eurusd["close"] < eurusd["close"].shift(-look_ahead),"target"] = 1
eurusd.dropna(inplace=True)
eurusd.set_index("time",inplace=True)

Nun werden wir die Datensätze anhand der gemeinsamen Daten zusammenführen.

#Let's merge the datasets
merged_data = eurusd.merge(visa_headline,right_index=True,left_index=True)
merged_data = merged_data.merge(visa_discretionary,right_index=True,left_index=True)
merged_data = merged_data.merge(visa_non_discretionary,right_index=True,left_index=True)


Explorative Datenanalyse

Wir sind bereit, unsere Daten zu untersuchen. Streudiagramme sind hilfreich, um die Beziehung zwischen zwei Variablen zu visualisieren. Betrachten wir die Streudiagramme, die von jedem der VISA-Datensätze erstellt wurden, aufgetragen gegen den Schlusskurs. Die blauen Punkte fassen die Fälle zusammen, in denen der Kurs im Laufe der nächsten 10 Kerzen fiel, während die orangefarbenen Punkte den umgekehrten Fall wiedergeben.

Obwohl die Trennung in der Mitte des Diagramms verrauscht ist, scheint es, dass die VISA-Datensätze auf den extremen Ebenen Auf- und Abwärtsbewegungen recht gut trennen.

#Let's create scatter plots
sns.scatterplot(data=merged_data,y="close",x="visa h",hue="target").set(title="EURUSD Close Against VISA Momentum Index: Headline")

Abb. 4: Darstellung unseres nicht diskretionären VISA-Datensatzes gegen den EURUSD-Schlusskurs

Abb. 5: Aufzeichnung unseres Discretionary VISA-Datensatzes gegen den EURUSD-Schlusskurs

Abb. 6: Darstellung unseres Headline VISA-Datensatzes gegen den EURUSD-Schlusskurs

Die Korrelationsniveaus zwischen den VISA-Datensätzen und dem EURUSD-Markt sind moderat und alle positiv bewertet. Keines der Korrelationsniveaus ist für uns besonders interessant. Es ist jedoch anzumerken, dass der positive Wert darauf hinweist, dass die beiden Variablen dazu neigen, gemeinsam zu steigen und zu fallen. Das entspricht unserem Verständnis von Makroökonomie: Die Verbraucherausgaben in den USA haben einen gewissen Einfluss auf die Wechselkurse. Wenn sich die Verbraucher kollektiv dafür entscheiden, nichts auszugeben, verringert sich die Gesamtwährung im Umlauf, was zu einer Aufwertung des Dollars führen kann.


Abb. 7: Korrelationsanalyse unseres Datensatzes


Auswahl der Merkmale

Wie wichtig ist die Beziehung zwischen unserem Ziel und unseren neuen Funktionen? Lassen Sie uns beobachten, ob die neuen Merkmale durch unseren Algorithmus zur Merkmalsauswahl eliminiert werden. Wenn unser Algorithmus keine der neuen Variablen auswählt, kann dies ein Hinweis darauf sein, dass die Beziehung nicht zuverlässig ist.

Der Vorwärtsauswahlalgorithmus beginnt mit einem Nullmodell und fügt ein Merkmal nach dem anderen hinzu, wählt dann das beste Einzelvariablenmodell aus und beginnt dann mit der Suche nach einer zweiten Variablen usw. Er wird das beste Modell, das er gebaut hat, an uns zurückschicken. In unserer Studie wurde nur der Eröffnungskurs vom Algorithmus ausgewählt, was uns zeigt, dass die Beziehung möglicherweise nicht stabil ist.

Importieren wir die benötigten Bibliotheken.

#Let's see which features are the most important
from mlxtend.feature_selection import SequentialFeatureSelector as SFS
from mlxtend.plotting import plot_sequential_feature_selection as plot_sfs
import matplotlib.pyplot as plt

Wir erstellen das Objekt für die Vorwärtsselektion.

#Create the forward selection object
sfs = SFS(
        MLPClassifier(hidden_layer_sizes=(20,10,4),shuffle=False,activation=tuner.best_params_["activation"],solver=tuner.best_params_["solver"],alpha=tuner.best_params_["alpha"],learning_rate=tuner.best_params_["learning_rate"],learning_rate_init=tuner.best_params_["learning_rate_init"]),
        k_features=(1,train_X.shape[1]),
        forward=False,
        scoring="accuracy",
        cv=5
).fit(train_X,train_y)

Stellen wir die Ergebnisse grafisch dar.

fig1 = plot_sfs(sfs.get_metric_dict(),kind="std_dev")
plt.title("Neural Network Backward Feature Selection")
plt.grid()

Abb. 8: Als wir die Anzahl der Merkmale im Modell erhöhten, verschlechterte sich unsere Leistung

Leider sank unsere Genauigkeit immer weiter, je mehr Funktionen wir hinzufügten. Das kann entweder bedeuten, dass die Assoziation einfach nicht so stark ist, oder wir legen die Assoziation nicht in einer Weise offen, die unser Modell interpretieren kann. Es sieht also so aus, als ob ein Modell mit 1 Merkmal immer noch in der Lage sein könnte, die Arbeit zu erledigen.

Das beste Merkmal, das wir gefunden haben.

sfs.k_feature_names_
('open',)

Betrachten wir nun unsere Werte für die gegenseitige (mutual) Information (MI). Die MI-Werte sind positiv bewertet und reichen theoretisch von 0 bis unendlich, aber in der Praxis beobachten wir selten MI-Werte über 2 und ein MI-Wert über 1 ist gut.

Importieren wir den MI-Klassifikator aus scikit-learn.

#Mutual information
from sklearn.feature_selection import mutual_info_classif

Der MI-Score für den Headline-Datensatz.

#Mutual information from the headline visa dataset, 
print(f"VISA Headline dataset has a mutual info score of: {mutual_info_classif(train_X.loc[:,['visa h']],train_y)[0]}")
Der VISA Headline-Datensatz hat eine gegenseitige Informationsbewertung von: 0.06069528690724346

Der MI-Score für den Datensatz Discretionary.

#Mutual information from the second visa dataset, 
print(f"VISA Discretionary dataset has a mutual info score of: {mutual_info_classif(train_X.loc[:,['visa d']],train_y)[0]}")
Der VISA Discretionary-Datensatz hat einen gegenseitigen Informationswert von: 0.1277119388376886

Alle unsere Datensätze wiesen schlechte MI-Werte auf. Dies könnte ein zwingender Grund dafür sein, verschiedene Transformationen auf den VISA-Datensatz anzuwenden, um hoffentlich eine stärkere Assoziation aufzudecken.


Einstellen der Parameter

Versuchen wir nun, unser tiefes neuronales Netzwerk so einzustellen, dass es den EURUSD prognostiziert. Zuvor müssen wir unsere Daten skalieren. Zunächst setzen wir den Index des zusammengeführten Datensatzes zurück

#Reset the index
merged_data.reset_index(inplace=True)

und definieren das Ziel und die Prädiktoren.

#Define the target
target = "target"
ohlc_predictors = ["open","high","low","close","tick_volume"]
visa_predictors = ["visa d","visa h","visa nd"]
all_predictors = ohlc_predictors + visa_predictors

Jetzt werden wir unsere Daten skalieren und transformieren. Von jedem Wert in unserem Datensatz subtrahieren wir den Mittelwert und teilen ihn durch die Standardabweichung der jeweiligen Spalte. Es ist zu beachten, dass diese Transformation empfindlich auf Ausreißer reagiert.

#Let's scale the data
scale_factors = pd.DataFrame(index=["mean","standard deviation"],columns=all_predictors)

for i in np.arange(0,len(all_predictors)):
        #Store the mean and standard deviation for each column
        scale_factors.iloc[0,i] = merged_data.loc[:,all_predictors[i]].mean()
        scale_factors.iloc[1,i] = merged_data.loc[:,all_predictors[i]].std()
        merged_data.loc[:,all_predictors[i]] = ((merged_data.loc[:,all_predictors[i]] - scale_factors.iloc[0,i]) / scale_factors.iloc[1,i])

scale_factors

Ein Blick auf die skalierten Daten.

#Let's see the normalized data
merged_data

Importieren der Standardbibliotheken.

#Lets try to train a deep neural network to uncover relationships in the data
from sklearn.neural_network import MLPClassifier
from sklearn.model_selection import RandomizedSearchCV
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

Erstellen einer Aufteilung in Trainings und Test.

#Create train test partitions for our alternative data
train_X,test_X,train_y,test_y = train_test_split(merged_data.loc[:,all_predictors],merged_data.loc[:,"target"],test_size=0.5,shuffle=False)

Anpassung des Modells an die verfügbaren Eingaben. Erinnern Sie sich daran, dass wir zuerst das Modell übergeben müssen, das wir abstimmen wollen, und dann die Parameter des Modells angeben, an dem wir interessiert sind. Danach müssen wir angeben, wie viele Faltungen wir für die Kreuzvalidierung verwenden wollen.

tuner = RandomizedSearchCV(MLPClassifier(hidden_layer_sizes=(20,10,4),shuffle=False),
                        {
                                "activation": ["relu","identity","logistic","tanh"],
                                "solver": ["lbfgs","adam","sgd"],
                                "alpha": [0.1,0.01,0.001,(10.0 ** -4),(10.0 ** -5),(10.0 ** -6),(10.0 ** -7),(10.0 ** -8),(10.0 ** -9)],
                                "learning_rate": ["constant", "invscaling", "adaptive"],
                                "learning_rate_init": [0.1,0.01,0.001,(10.0 ** -4),(10.0 ** -5),(10.0 ** -6),(10.0 ** -7),(10.0 ** -8),(10.0 ** -9)],
                        },
                        cv=5,
                        n_iter=1000,
                        scoring="accuracy",
                        return_train_score=False
                        )

Einbau des „tuners“.

tuner.fit(train_X,train_y)

Sehen wir uns die Ergebnisse an, die mit den Trainingsdaten erzielt wurden, in der Reihenfolge vom besten zum schlechtesten Ergebnis.

tuner_results = pd.DataFrame(tuner.cv_results_)
params = ["param_activation","param_solver","param_alpha","param_learning_rate","param_learning_rate_init","mean_test_score"]
tuner_results.loc[:,params].sort_values(by="mean_test_score",ascending=False)

Ergebnisse der Optimierung

Abb. 9: Unsere Optimierungsergebnisse

Unsere höchste Genauigkeit betrug 88 % bei den Trainingsdaten. Beachten Sie, dass es aufgrund der stochastischen Natur des von uns gewählten Optimierungsalgorithmus schwierig sein kann, die in dieser Demonstration erzielten Ergebnisse zu reproduzieren.


Testen auf Überanpassung

Vergleichen wir nun unsere Standard- und angepassten Modelle, um zu sehen, ob wir die Trainingsdaten zu gut anpassen. Bei einer Überanpassung wird das Standardmodell unser angepasstes Modell in der Validierungsmenge übertreffen, andernfalls wird unser angepasstes Modell besser abschneiden.

Bereiten wir die 2 Modelle vor.

#Let's compare the default model and our customized model on the hold out set
default_model = MLPClassifier(hidden_layer_sizes=(20,10,4),shuffle=False)
customized_model = MLPClassifier(hidden_layer_sizes=(20,10,4),shuffle=False,activation=tuner.best_params_["activation"],solver=tuner.best_params_["solver"],alpha=tuner.best_params_["alpha"],learning_rate=tuner.best_params_["learning_rate"],learning_rate_init=tuner.best_params_["learning_rate_init"])

Wir messen die Genauigkeit des Standardmodells.

#The accuracy of the defualt model
default_model.fit(train_X,train_y)
accuracy_score(test_y,default_model.predict(test_X))
0.5423728813559322

Die Genauigkeit unseres angepassten Modells.

#The accuracy of the defualt model
customized_model.fit(train_X,train_y)
accuracy_score(test_y,customized_model.predict(test_X))
0.7457627118644068

Es scheint, dass wir das Modell ohne Überanpassung an die Trainingsdaten trainiert haben. Beachten Sie auch, dass unser Trainingsfehler in der Regel immer höher ist als unser Testfehler, die Diskrepanz zwischen ihnen sollte jedoch nicht zu groß sein. Unser Trainingsfehler lag bei 88 % und der Testfehler bei 74 %, was angemessen ist. Eine große Diskrepanz zwischen dem Trainings- und dem Testfehler wäre alarmierend, denn sie könnte darauf hindeuten, dass wir uns zu sehr angepasst haben!


Umsetzung der Strategie

Zunächst definieren wir globale Variablen, die wir verwenden werden.

#Let us now start building our trading strategy
SYMBOL = 'EURUSD'
TIMEFRAME = mt5.TIMEFRAME_MN1
DEVIATION = 1000
VOLUME = 0
LOT_MULTIPLE = 1

Initialisieren wir nun unser MetaTrader 5 Terminal.

#Get the system up
if not mt5.initialize():
        print('Failed To Log in')

Jetzt müssen wir mehr Details über den Markt wissen.

#Let's fetch the trading volume
for index,symbol in enumerate(mt5.symbols_get()):
        if symbol.name == SYMBOL:
        print(f"{symbol.name} has minimum volume: {symbol.volume_min}")
        VOLUME = symbol.volume_min * LOT_MULTIPLE

Diese Funktion ermittelt den aktuellen Marktpreis für uns.

#A function to get current prices
def get_prices():
        start = datetime(2024,1,1)
        end   = datetime.now()
        data  = pd.DataFrame(mt5.copy_rates_range(SYMBOL,TIMEFRAME,start,end))
        data['time'] = pd.to_datetime(data['time'],unit='s')
        data.set_index('time',inplace=True)
        return(data.iloc[-1,:])

Erstellen wir auch eine Funktion, um die neuesten alternativen Daten von der FRED-API abzurufen.

#A function to get our alternative data
def get_alternative_data():
        visa_d = fred.get_series_as_of_date("VISASMIDSA",datetime.now())
        visa_d = visa_d.iloc[-1,-1]
        visa_h = fred.get_series_as_of_date("VISASMIHSA",datetime.now())
        visa_h = visa_h.iloc[-1,-1]
        visa_n = fred.get_series_as_of_date("VISASMINSA",datetime.now())
        visa_n = visa_n.iloc[-1,-1]
        return(visa_d,visa_h,visa_n)

Wir brauchen eine Funktion, die für die Normalisierung und Skalierung unserer Eingaben verantwortlich ist.

#A function to prepare the inputs for our model
def get_model_inputs():
        LAST_OHLC = get_prices()
        visa_d , visa_h , visa_n = get_alternative_data()
        return(
        np.array([[
                        ((LAST_OHLC['open'] - scale_factors.iloc[0,0]) / scale_factors.iloc[1,0]),
                        ((LAST_OHLC['high']  - scale_factors.iloc[0,1]) / scale_factors.iloc[1,1]),
                        ((LAST_OHLC['low']  - scale_factors.iloc[0,2]) / scale_factors.iloc[1,2]),
                        ((LAST_OHLC['close']  - scale_factors.iloc[0,3]) / scale_factors.iloc[1,3]),
                        ((LAST_OHLC['tick_volume']  - scale_factors.iloc[0,4]) / scale_factors.iloc[1,4]),
                        ((visa_d  - scale_factors.iloc[0,5]) / scale_factors.iloc[1,5]),
                        ((visa_h  - scale_factors.iloc[0,6]) / scale_factors.iloc[1,6]),
                        ((visa_n  - scale_factors.iloc[0,7]) / scale_factors.iloc[1,7])
                ]])
        )

Wir wollen unser Modell mit allen Daten trainieren, die wir haben.

#Let's train our model on all the data we have
model = MLPClassifier(hidden_layer_sizes=(20,10,4),shuffle=False,activation="logistic",solver="lbfgs",alpha=0.00001,learning_rate="constant",learning_rate_init=0.00001)
model.fit(merged_data.loc[:,all_predictors],merged_data.loc[:,"target"])

Diese Funktion liefert eine Vorhersage aus unserem Modell.

#A function to get a prediction from our model
def ai_forecast():
        model_inputs = get_model_inputs()
        prediction = model.predict(model_inputs)
        return(prediction[0])

Damit sind wir beim Kernstück unseres Algorithmus angelangt. Zunächst prüfen wir, wie viele Stellen wir offen haben. Dann erhalten wir eine Vorhersage von unserem Modell. Wenn wir keine offenen Positionen haben, werden wir die Prognose unseres Modells nutzen, um eine Position zu eröffnen. Andernfalls werden wir die Prognose unseres Modells als Ausstiegssignal verwenden, wenn wir Positionen offen haben.

while True:
        #Get data on the current state of our terminal and our portfolio
        positions = mt5.positions_total()
        forecast  = ai_forecast()
        BUY_STATE , SELL_STATE = False , False

        #Interpret the model's forecast
        if(forecast == 0.0):
        SELL_STATE = True
        BUY_STATE  = False

        elif(forecast == 1.0):
        SELL_STATE = False
        BUY_STATE  = True

        print(f"Our forecast is {forecast}")

        #If we have no open positions let's open them
        if(positions == 0):
        print(f"We have {positions} open trade(s)")
        if(SELL_STATE):
                print("Opening a sell position")
                mt5.Sell(SYMBOL,VOLUME)
        elif(BUY_STATE):
                print("Opening a buy position")
                mt5.Buy(SYMBOL,VOLUME)

        #If we have open positions let's manage them
        if(positions > 0):
        print(f"We have {positions} open trade(s)")
        for pos in mt5.positions_get():
                if(pos.type == 1):
                if(BUY_STATE):
                        print("Closing all sell positions")
                        mt5.Close(SYMBOL)
                if(pos.type == 0):
                if(SELL_STATE):
                        print("Closing all buy positions")
                        mt5.Close(SYMBOL)
        #If we have finished all checks then we can wait for one day before checking our positions again
        time.sleep(24 * 60 * 60)
Our forecast is 0.0
We have 0 open trade(s)
Opening a sell position


Implementation in MQL5

Um unsere Strategie in MQL5 zu implementieren, müssen wir zunächst unsere Modelle in das Format Open Neural Network Exchange (ONNX) exportieren. ONNX ist ein Protokoll zur Darstellung von Modellen des maschinellen Lernens als eine Kombination aus Graph und Kanten. Dieses standardisierte Protokoll ermöglicht es Entwicklern, Modelle für maschinelles Lernen mit verschiedenen Programmiersprachen zu erstellen und einzusetzen. Leider werden nicht alle Modelle und Frameworks für maschinelles Lernen vollständig von der aktuellen ONNX-API unterstützt. 

Für den Anfang werden wir einige Bibliotheken importieren.

#Import the libraries we need
import pandas as pd
import numpy as np
from fredapi import Fred
import MetaTrader5 as mt5
from datetime import datetime
import time
import pytz

Dann müssen wir unseren FRED-API-Schlüssel eingeben, um Zugriff auf die benötigten Daten zu erhalten.

#Let's setup our FredAPI
fred = Fred(api_key="")
visa_discretionary = pd.DataFrame(fred.get_series("VISASMIDSA"),columns=["visa d"])
visa_headline      = pd.DataFrame(fred.get_series("VISASMIHSA"),columns=["visa h"])
visa_non_discretionary = pd.DataFrame(fred.get_series("VISASMINSA"),columns=["visa nd"])

Beachten Sie, dass wir nach dem Abruf der Daten eine Skalierung nach dem gleichen Format wie oben beschrieben vorgenommen haben. Wir haben diese Schritte weggelassen, um Wiederholungen derselben Informationen zu vermeiden. Der einzige kleine Unterschied besteht darin, dass wir das Modell jetzt so trainieren, dass es den tatsächlichen Schlusskurs vorhersagt und nicht nur ein binäres Ziel.

Nachdem wir die Daten skaliert haben, wollen wir nun versuchen, die Parameter unseres Modells abzustimmen.

#A few more libraries we need
from sklearn.neural_network import MLPRegressor
from sklearn.model_selection import RandomizedSearchCV
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

Wir müssen unsere Daten so aufteilen, dass wir einen Trainingssatz für die Optimierung des Modells und einen Validierungssatz haben, mit dem wir auf Überanpassung testen.

#Create train test partitions for our alternative data
train_X,test_X,train_y,test_y = train_test_split(merged_data.loc[:,all_predictors],merged_data.loc[:,"close target"],test_size=0.5,shuffle=False)

Wir führen nun die Abstimmung der Hyperparameter durch. Beachten Sie, dass wir die Bewertungsmetrik auf „neg mean squared error“ (negierter mittlerer quadratischer Fehler) eingestellt haben, diese Bewertungsmetrik identifiziert das Modell mit dem niedrigsten MSE als das leistungsstärkste Modell.

tuner = RandomizedSearchCV(MLPRegressor(hidden_layer_sizes=(20,10,4),shuffle=False,early_stopping=True),
                           {
                               "activation": ["relu","identity","logistic","tanh"],
                               "solver": ["lbfgs","adam","sgd"],
                               "alpha": [0.1,0.01,0.001,(10.0 ** -4),(10.0 ** -5),(10.0 ** -6),(10.0 ** -7),(10.0 ** -8),(10.0 ** -9)],
                               "learning_rate": ["constant", "invscaling", "adaptive"],
                               "learning_rate_init": [0.1,0.01,0.001,(10.0 ** -4),(10.0 ** -5),(10.0 ** -6),(10.0 ** -7),(10.0 ** -8),(10.0 ** -9)],
                           },
                           cv=5,
                           n_iter=1000,
                           scoring="neg_mean_squared_error",
                           return_train_score=False,
                           n_jobs=-1
                          )

Anpassen des „tuner“-Objekts.

tuner.fit(train_X,train_y)

Testen wir nun auf Überanpassung.

#Let's compare the default model and our customized model on the hold out set
default_model = MLPRegressor(hidden_layer_sizes=(20,10,4),shuffle=False)
customized_model = MLPRegressor(hidden_layer_sizes=(20,10,4),shuffle=False,activation=tuner.best_params_["activation"],solver=tuner.best_params_["solver"],alpha=tuner.best_params_["alpha"],learning_rate=tuner.best_params_["learning_rate"],learning_rate_init=tuner.best_params_["learning_rate_init"])

Die Genauigkeit unseres Standardmodells.

#The accuracy of the defualt model
default_model.fit(train_X,train_y)
mean_squared_error(test_y,default_model.predict(test_X))
0.19334261927379248

Es ist uns gelungen, unser Standardmodell auf der ausgeklammerten Validierungsmenge zu übertreffen, was ein gutes Zeichen dafür ist, dass wir nicht überangepasst sind.

#The accuracy of the defualt model
default_model.fit(train_X,train_y)
mean_squared_error(test_y,default_model.predict(test_X))

0.006138795093781729

Passen wir das angepasste Modell an alle Daten an, die wir haben, bevor wir es ins ONNX-Format exportieren.

#Fit the model on all the data we have
customized_model.fit(test_X,test_y)

Importieren von ONNX-Konvertierungsbibliotheken.

#Convert to ONNX
from skl2onnx.common.data_types import FloatTensorType
from skl2onnx import convert_sklearn
import netron
import onnx

Definieren wir den Eingabetyp und die Form unseres Modells,

#Define the initial types
initial_types = [("float_input",FloatTensorType([1,train_X.shape[1]]))]

erstellen eine ONNX-Darstellung des Modells im Speicher,

#Create the onnx representation
onnx_model = convert_sklearn(customized_model,initial_types=initial_types,target_opset=12)

speichern die ONNX-Darstellung auf der Festplatte,

#Save the ONNX model
onnx_model_name = "EURUSD VISA MN1 FLOAT.onnx"
onnx.save(onnx_model,onnx_model_name)

und schauen uns das ONNX-Modell mit netron an.

#View the ONNX model
netron.start(onnx_model_name)


Unsere ONNX-Darstellung des Neuronalen Netzes

Abb. 10: Unser tiefes neuronales Netz im ONNX-Format

ONNX DNN

Abb. 11: Metadetails unseres ONNX-Modells

Wir sind fast bereit, mit dem Aufbau unseres Expert Advisors zu beginnen. Zunächst müssen wir jedoch einen Python-Hintergrunddienst erstellen, der die Daten von FRED abruft und an unser Programm weiterleitet. 

Zunächst importieren wir die benötigten Bibliotheken.

#Import the libraries we need
import pandas as pd
import numpy as np
from fredapi import Fred
from datetime import datetime

Dann melden wir uns mit unseren FRED-Anmeldedaten an.

#Let's setup our FredAPI
fred = Fred(api_key="")

Wir müssen eine Funktion definieren, die die Daten für uns abruft und sie in CSV ausgibt.

#A function to write out our alternative data to CSV
def write_out_alternative_data():
        visa_d = fred.get_series_as_of_date("VISASMIDSA",datetime.now())
        visa_d = visa_d.iloc[-1,-1]
        visa_h = fred.get_series_as_of_date("VISASMIHSA",datetime.now())
        visa_h = visa_h.iloc[-1,-1]
        visa_n = fred.get_series_as_of_date("VISASMINSA",datetime.now())
        visa_n = visa_n.iloc[-1,-1] 
        data = pd.DataFrame(np.array([visa_d,visa_h,visa_n]),columns=["Data"],index=["Discretionary","Headline","Non-Discretionary"])
        data.to_csv("C:\\Users\\Westwood\\AppData\\Roaming\\MetaQuotes\\Terminal\\D0E8209F77C8CF37AD8BF550E51FF075\\MQL5\\Files\\fred_visa.csv")

Nun müssen wir eine Schleife schreiben, die einmal am Tag nach neuen Daten sucht und unsere CSV-Datei aktualisiert.

while True:
        #Update the fred data for our MT5 EA
        write_out_alternative_data()
        #If we have finished all checks then we can wait for one day before checking for new data
        time.sleep(24 * 60 * 60)
Da wir nun Zugang zu den neuesten FRED-Daten haben, können wir mit dem Aufbau unseres Expert Advisors beginnen. 
We will first load our ONNX model as a resource into our application.
//+------------------------------------------------------------------+
//|                                                      VISA EA.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Resorces                                                         |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD VISA MN1 FLOAT.onnx" as const uchar onnx_buffer[];

Dann laden wir die Handelsbibliothek, die uns bei der Eröffnung und Verwaltung unserer Positionen hilft.

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

So weit, unsere Anwendung kommt gut zusammen, lassen Sie uns globale Variablen erstellen, die wir in verschiedenen Blöcken unserer Anwendung verwenden werden.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
long   onnx_model;
double mean_values[8],std_values[8];
float visa_data[3];
vector model_forecast = vector::Zeros(1);
double trading_volume = 0.3;
int state = 0;

Bevor wir mit unserem ONNX-Modell beginnen können, müssen wir zunächst das ONNX-Modell aus der Ressource erstellen, die wir zu Beginn des Programms benötigt haben. Danach müssen wir die Eingangs- und Ausgangsformen des Modells definieren. 

//+------------------------------------------------------------------+
//| Load the ONNX model                                              |
//+------------------------------------------------------------------+
bool load_onnx_model(void)
  {
//--- Try create the ONNX model from the buffer we have
   onnx_model = OnnxCreateFromBuffer(onnx_buffer,ONNX_DEFAULT);

//--- Validate the model
   if(onnx_model == INVALID_HANDLE)
     {
      Comment("Failed to create the ONNX model. ",GetLastError());
      return(false);
     }

//--- Set the I/O shape
   ulong input_shape[] = {1,8};
   ulong output_shape[] = {1,1};

//--- Validate the I/O shapes
   if(!OnnxSetInputShape(onnx_model,0,input_shape))
     {
      Comment("Failed to set the ONNX model input shape. ",GetLastError());
      return(false);
     }

   if(!OnnxSetOutputShape(onnx_model,0,output_shape))
     {
      Comment("Failed to set the ONNX model output shape. ",GetLastError());
      return(false);
     }

   return(true);
  }

Erinnern Sie sich daran, dass wir unsere Daten standardisiert haben, indem wir den Spaltenmittelwert abgezogen und durch die Standardabweichung der einzelnen Spalten geteilt haben. Wir müssen diese Werte im Speicher ablegen. Da sich diese Werte nie ändern werden, habe ich sie einfach fest in das Programm programmiert.

//+------------------------------------------------------------------+
//| Mean & Standard deviation values                                 |
//+------------------------------------------------------------------+
void load_scaling_values(void)
  {
//--- Mean & standard deviation values for the EURUSD OHLCV
   mean_values[0] = 1.146552;
   std_values[0]  = 0.08293;
   mean_values[1] = 1.165568;
   std_values[1]  = 0.079657;
   mean_values[2] = 1.125744;
   std_values[2]  = 0.083896;
   mean_values[3] = 1.143834;
   std_values[3]  = 0.080655;
   mean_values[4] = 1883520.051282;
   std_values[4]  = 826680.767222;
//--- Mean & standard deviation values for the VISA datasets
   mean_values[5] = 101.271017;
   std_values[5]  = 3.981438;
   mean_values[6] = 100.848506;
   std_values[6]  = 6.565229;
   mean_values[7] = 100.477269;
   std_values[7]  = 2.367663;
  }

Der Python-Hintergrunddienst, den wir erstellt haben, wird uns immer die neuesten verfügbaren Daten liefern. Lassen Sie uns eine Funktion zum Lesen dieser CSV-Datei erstellen und die Werte in einem Array für uns speichern.

//+-------------------------------------------------------------------+
//| Read in the VISA data                                             |
//+-------------------------------------------------------------------+
void read_visa_data(void)
  {
//--- Read in the file
   string file_name = "fred_visa.csv";

//--- Try open the file
   int result = FileOpen(file_name,FILE_READ|FILE_CSV|FILE_ANSI,","); //Strings of ANSI type (one byte symbols).

//--- Check the result
   if(result != INVALID_HANDLE)
     {
      Print("Opened the file");
      //--- Store the values of the file

      int counter = 0;
      string value = "";
      while(!FileIsEnding(result) && !IsStopped()) //read the entire csv file to the end
        {
         if(counter > 10)  //if you aim to read 10 values set a break point after 10 elements have been read
            break;          //stop the reading progress

         value = FileReadString(result);
         Print("Trying to read string: ",value);

         if(counter == 3)
           {
            Print("Discretionary data: ",value);
            visa_data[0] = (float) value;
           }

         if(counter == 5)
           {
            Print("Headline data: ",value);
            visa_data[1] = (float) value;
           }

         if(counter == 7)
           {
            Print("Non-Discretionary data: ",value);
            visa_data[2] = (float) value;
           }

         if(FileIsLineEnding(result))
           {
            Print("row++");
           }

         counter++;
        }

      //--- Show the VISA data
      Print("VISA DATA: ");
      ArrayPrint(visa_data);

      //---Close the file
      FileClose(result);
     }
  }

Schließlich müssen wir eine Funktion definieren, die für die Vorhersagen unseres Modells verantwortlich ist. Zunächst speichern wir die aktuellen Eingaben in einem Float-Vektor, da unser Modell den Eingabetyp float hat, den wir bei der Erstellung der ONNX-Ausgangstypen definiert haben.

Erinnern Sie sich daran, dass wir jeden Eingabewert skalieren müssen, indem wir den Spaltenmittelwert subtrahieren und durch die Spaltenstandardabweichung dividieren, bevor wir die Eingaben an unser Modell weitergeben.

//+--------------------------------------------------------------+
//| Get a prediction from our model                              |
//+--------------------------------------------------------------+
void model_predict(void)
  {
//--- Fetch input data
   read_visa_data();
   vectorf input_data =  {(float)iOpen("EURUSD",PERIOD_MN1,0),
                          (float)iHigh("EURUSD",PERIOD_MN1,0),
                          (float)iLow("EURUSD",PERIOD_MN1,0),
                          (float)iClose("EURUSD",PERIOD_MN1,0),
                          (float)iTickVolume("EURUSD",PERIOD_MN1,0),
                          (float)visa_data[0],
                          (float)visa_data[1],
                          (float)visa_data[2]
                         };
//--- Scale the data
   for(int i =0; i < 8;i++)
     {
      input_data[i] = (float)((input_data[i] - mean_values[i])/std_values[i]);
     }

//--- Show the input data
   Print("Input data: ",input_data);

//--- Obtain a forecast
   OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT|ONNX_DEFAULT,input_data,model_forecast);
  }
//+------------------------------------------------------------------+

Definieren wir nun die Initialisierungsprozedur. Wir beginnen mit dem Laden unseres ONNX-Modells, lesen dann den VISA-Datensatz ein und laden schließlich unsere Skalierungswerte.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Load the ONNX file
   if(!load_onnx_model())
     {
      //--- We failed to load the ONNX model
      return(INIT_FAILED);
     }

//--- Read the VISA data
   read_visa_data();

//--- Load scaling values
   load_scaling_values();

//--- We were successful
   return(INIT_SUCCEEDED);
  }

Wenn unser Programm nicht mehr genutzt wird, sollten wir die nicht mehr benötigten Ressourcen freigeben.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Free up the resources we don't need
   OnnxRelease(onnx_model);
   ExpertRemove();
  }

Immer wenn neue Preisdaten verfügbar sind, holen wir zunächst eine Vorhersage aus unserem Modell. Wenn wir keine offenen Positionen haben, folgen wir dem von unserem Modell generierten Einstieg. Wenn wir offene Positionen haben, nutzen wir unser KI-Modell, um potenzielle Kursumschwünge im Voraus zu erkennen.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {

//--- Get a prediction from our model
   model_predict();
   Comment("Model forecast: ",model_forecast[0]);
//--- Check if we have any positions
   if(PositionsTotal() == 0)
     {
      //--- Note that we have no trades open
      state = 0;

      //--- Find an entry and take note
      if(model_forecast[0] < iClose(_Symbol,PERIOD_CURRENT,0))
        {
         Trade.Sell(trading_volume,_Symbol,SymbolInfoDouble(_Symbol,SYMBOL_BID),0,0,"Gain an Edge VISA");
         state = 1;
        }

      if(model_forecast[0] > iClose(_Symbol,PERIOD_CURRENT,0))
        {
         Trade.Buy(trading_volume,_Symbol,SymbolInfoDouble(_Symbol,SYMBOL_ASK),0,0,"Gain an Edge VISA");
         state = 2;
        }
     }

//--- If we have positions open, check for reversals
   if(PositionsTotal() > 0)
     {
      if(((state == 1) && (model_forecast[0] > iClose(_Symbol,PERIOD_CURRENT,0))) ||
         ((state == 2) && (model_forecast[0] < iClose(_Symbol,PERIOD_CURRENT,0))))
        {
         Alert("Reversal detected, closing positions now");
         Trade.PositionClose(_Symbol);
        }

     }
  }
//+------------------------------------------------------------------+

Unser EA

Abb. 12: Unser VISA-Experten-Ratgeber

Output aus unserem EA

Abb. 13: Beispielhafte Ausgabe unseres Programms

Unser EA in Aktion.

Abb. 14: Unser Programm in Aktion


Schlussfolgerung

In diesem Artikel haben wir gezeigt, wie Sie Daten auswählen können, die für Ihre Handelsstrategie hilfreich sein können. Wir haben erörtert, wie Sie die potenzielle Stärke Ihrer alternativen Daten messen und wie Sie Ihre Modelle optimieren können, um so viel Leistung wie möglich zu erzielen, ohne sich zu sehr anzupassen. Es gibt potenziell Hunderttausende von Datensätzen, die erforscht werden können, und wir sind bestrebt, Ihnen dabei zu helfen, die informativsten zu finden.

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

Letzte Kommentare | Zur Diskussion im Händlerforum (6)
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 26 Aug. 2024 in 12:01
Clemence Benjamin #:
Vielen Dank, Gamu
Gern geschehen, Clemence.
Leandro de Araujo Souza
Leandro de Araujo Souza | 26 Aug. 2024 in 15:38
Toller Artikel, vielen Dank für den Austausch!
linfo2
linfo2 | 26 Aug. 2024 in 21:31
Nochmals vielen Dank, Gamu. Gut geschrieben wie immer. Eine großartige kommentierte Vorlage, wie man visualisiert, skaliert, testet, auf Überanpassung prüft, einen Datafeed implementiert, ein Handelssystem aus einem Datensatz vorhersagt und implementiert. Fantastisch, sehr geschätzt
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 26 Aug. 2024 in 22:46
Leandro Souza #:
Toller Artikel, danke fürs Teilen!!
Mein Vergnügen Leandro, ich bin hier zu helfen 💯
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 26 Aug. 2024 in 22:55
linfo2 #:
Nochmals vielen Dank, Gamu. Gut geschrieben wie immer. Eine großartig kommentierte Vorlage, wie man visualisiert, skaliert, testet, auf Überanpassung prüft, einen Datafeed implementiert, ein Handelssystem aus einem Datensatz vorhersagt und implementiert. Fantastisch, sehr geschätzt
Vielen Dank, Neil, für dein Feedback, es ist schön, so freundliche Worte zu hören.

Das Verrückte daran ist, dass es jeden Tag neue Forschungsergebnisse gibt, die alles in Frage stellen, was wir zu wissen glaubten. Ich habe vor kurzem von dem Phänomen des doppelten Abstiegs erfahren.

Wenn die Theorie stimmt, gibt es so etwas wie Überanpassung nicht. Dem Phänomen zufolge sinkt der Validierungsfehler immer weiter, wenn wir größere tiefe neuronale Netze über längere Zeiträume mit demselben Trainingssatz trainieren.

Das Bild, das ich unten angehängt habe, verdeutlicht das Phänomen visuell. Der Haken an der Sache ist, dass das Training eines so großen Modells über einen so langen Zeitraum hinweg teuer ist, und wenn die Daten zudem verrauscht sind, dauert das Phänomen noch länger. Ich war nicht in der Lage, die Ergebnisse auf meinem Computer zu reproduzieren, aber dieses Papier macht die Runde
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 32): Regularisierung MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 32): Regularisierung
Die Regularisierung ist eine Form der Bestrafung der Verlustfunktion im Verhältnis zur diskreten Gewichtung, die in den verschiedenen Schichten eines neuronalen Netzes angewendet wird. Wir sehen uns an, welche Bedeutung dies für einige der verschiedenen Regularisierungsformen in Testläufen mit einem vom Assistenten zusammengestellten Expert Advisor haben kann.
Klassische Strategien neu interpretieren (Teil V): Analyse mehrerer Symbole für USDZAR Klassische Strategien neu interpretieren (Teil V): Analyse mehrerer Symbole für USDZAR
In dieser Artikelserie überprüfen wir klassische Strategien, um herauszufinden, ob wir die Strategie mithilfe von KI verbessern können. Im heutigen Artikel werden wir eine beliebte Strategie der Mehrfachsymbolanalyse anhand eines Korbs korrelierter Wertpapiere untersuchen, wobei wir uns auf das exotische Währungspaar USDZAR konzentrieren werden.
Klassische Strategien neu interpretieren (Teil VI): Analyse mehrerer Zeitrahmen Klassische Strategien neu interpretieren (Teil VI): Analyse mehrerer Zeitrahmen
In dieser Artikelserie nehmen wir klassische Strategien unter die Lupe, um zu sehen, ob wir sie mithilfe von KI verbessern können. Im heutigen Artikel werden wir die beliebte Strategie der Analyse mehrerer Zeitrahmen untersuchen, um zu beurteilen, ob die Strategie durch KI verbessert werden kann.
Mustererkennung mit dynamischer Zeitnormierung in MQL5 Mustererkennung mit dynamischer Zeitnormierung in MQL5
In diesem Artikel erörtern wir das Konzept der dynamischen Zeitnormierung als Mittel zur Ermittlung von Vorhersagemustern in Finanzzeitreihen. Wir werden uns ansehen, wie es funktioniert, und seine Implementierung in reinem MQL5 vorstellen.