English
preview
Die Grenzen des maschinellen Lernens überwinden (Teil 2): Mangelnde Reproduzierbarkeit

Die Grenzen des maschinellen Lernens überwinden (Teil 2): Mangelnde Reproduzierbarkeit

MetaTrader 5Beispiele |
22 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Ich erhalte oft ermutigende Rückmeldungen von unseren Lesern, aber ein immer wiederkehrendes Thema in privaten Nachrichten und Kommentaren sind die Schwierigkeiten, die manche haben, wenn sie versuchen, die in unseren Artikeln vorgestellten Ergebnisse zu reproduzieren. Das hat mich zunächst verwundert, aber nach einigem Nachdenken ergab sich eine mögliche Erklärung.

Der globale Finanzmarkt funktioniert wie ein riesiges, dezentrales Netzwerk. Es gibt weltweit viele Makler, und täglich werden neue registriert, aber es gibt keine einzige internationale Behörde, die diese Makler reguliert oder ihre Kursdaten koordiniert. Jedem Makler steht es frei, die Kurse von seinen bevorzugten eigenen Feeds oder Datendiensten wie Reuters zu beziehen. 

Wenn Sie also die EURUSD-Performance bei zwei Brokern - nennen wir sie Broker A und Broker B - vergleichen, können Sie feststellen, dass sich dasselbe Paar im selben Moment in entgegengesetzte Richtungen bewegt. So kann Broker A beispielsweise melden, dass der EURUSD an einem Tag um 0,12 % gestiegen ist, während Broker B für denselben Tag einen Wertverlust von -0,65 % verzeichnet.


Der Kern des Themas: Datendiskrepanz zwischen Brokern

Für diese Diskussion habe ich nach dem Zufallsprinzip zwei Makler ausgewählt, die ich persönlich für den unabhängigen Handel verwende. Im Einklang mit unseren Gemeinschaftsrichtlinien, die Maklerwerbung untersagen, wurden ihre Namen geschwärzt und durch „Makler A“ und „Makler B“ ersetzt.

Mit Hilfe der MetaTrader 5 Python-Bibliothek habe ich vier Jahre lang täglich historische EURUSD-Daten von beiden Brokern angefordert. Bei der Überprüfung stellte ich fest, dass die Zeitstempel nicht übereinstimmten: Die Daten des einen Brokers reichten bis September 2019 zurück, die des anderen nur bis August 2020. Beide lieferten jedoch genau 1.460 Zeilen täglicher Daten und erfüllten damit unsere Anfrage.

Da die Makler dezentralisiert arbeiten, ist zu erwarten, dass sie in unterschiedlichen Zeitzonen arbeiten. Weniger offensichtlich sind jedoch die Auswirkungen der Sommerzeit, anerkannter Feiertage und anderer subtiler Diskrepanzen, die den Zeitstempelabgleich noch weiter verfälschen können.

Anschließend berechneten wir die 10-Tage-Rendite des EURUSD bei beiden Brokern und stellten fest, dass die numerischen Eigenschaften des EURUSD-Symbols nicht miteinander vereinbar waren. Die durchschnittliche 10-Tage-Rendite für EURUSD lag bei Broker A bei 0,000267, während die durchschnittliche 10-Tage-Rendite bei Broker B -0,000352 betrug. Dies entspricht einem Unterschied von etwa 232 % bei der erwarteten Rendite desselben Basiswerts.

Erschwerend kommt hinzu, dass die erwarteten Renditen von Broker A offenbar mit einem um 21 % höheren Risiko behaftet sind als die erwarteten Renditen von Broker B. Dies wurde uns durch die Tatsache suggeriert, dass die Varianz der Renditen zwischen den Brokern um denselben Betrag, nämlich 21 %, zunahm. 

Anmerkung für Anfänger: Der Leser soll verstehen, dass Renditeschwankungen als finanzielles Risiko angesehen werden. Jedes einführende Lehrbuch zur Finanzportfoliotheorie kann dies den Lesern demonstrieren, die sich dieses Prinzips vielleicht noch nicht bewusst waren.

In der Statistik kann man feststellen, ob sich zwei Variablen gemeinsam oder unabhängig voneinander bewegen, indem man ihren Korrelationsgrad misst. Die standardisierten Korrelationsmaße reichen von 1 bis -1. Ein Wert von 1 bedeutet, dass sich die Variablen perfekt in dieselbe Richtung bewegen, während ein Wert von -1 bedeutet, dass sich die Variablen perfekt in entgegengesetzte Richtungen bewegen. Als wir die Koeffizienten der Pearson-Korrelationsmetrik zwischen den beiden Maklern verglichen, erwartete ich, der Verfasser, ehrlich gesagt Korrelationskoeffizienten nahe bei 1. Die Daten wiesen jedoch nur Korrelationswerte von 0,41 auf. 

Dies deutet darauf hin, dass der Glaube, dass sich die Kursniveaus des EURUSD-Symbols bei verschiedenen Brokern im Einklang bewegen, mathematisch unbegründet zu sein scheint. Vielmehr deuten die Ergebnisse unseres Tests darauf hin, dass sich der EURUSD-Markt in mehr als der Hälfte der Fälle bei den verschiedenen Brokern in unterschiedliche Richtungen bewegt.

Andere wichtige numerische Eigenschaften der Angebote der beiden Makler haben die Tiefe der Probleme, die dieser Artikel dem Leser vor Augen führt, nur noch verstärkt. In der vorangegangenen Diskussion über die Grenzen der künstlichen Intelligenz haben wir dem Leser einige der Fallstricke aufgezeigt, die mit den üblicherweise zur Erstellung von Regressionsmodellen verwendeten Metriken wie dem RMSE verbunden sind. Der Leser findet den Artikel hier verlinkt.

Kurz gesagt, wir raten dem Leser, den RMSE nicht als eigenständige Kennzahl zu betrachten, sondern diese Kennzahl mit einer Prise Salz zu interpretieren, indem man das Verhältnis der Leistung des Modells, das man verwenden möchte (Residual Sum of Squares, RSS), mit dem Fehler vergleicht, den ein einfaches Modell erzeugt, das immer die durchschnittliche Marktrendite vorhersagt (Total Sum of Squares, TSS). Der Punkt war, dass die Leser überrascht sein könnten, wie schwierig es sein kann, das einfachere Modell zu übertreffen. Das Verhältnis von RSS geteilt durch TSS gibt Aufschluss darüber, wie effizient wir das einfache Modell übertreffen.

Man würde erwarten, dass dieses Verhältnis für ein und dasselbe Symbol nahezu konstant bleibt, auch bei verschiedenen Brokern. Unsere Fähigkeit, ein Modell, das die durchschnittliche Marktrendite vorhersagt, zu übertreffen, verbesserte sich jedoch allein durch den Wechsel des Brokers um 7 %. Dies bedeutet, dass die 10-Tage-Rendite des EURUSD bei Broker B um ca. 7 % leichter zu prognostizieren ist als bei Broker A!

Statistiker vergleichen häufig die Mitte einer Verteilung mit ihrer Standardabweichung, um mehr über die Merkmale des Endes einer bestimmten Verteilung zu erfahren. Wenn diese Operation auf die 10-Tage-Renditen des EURUSD umgedeutet wird, erhalten wir eine numerische Methode, um zu vergleichen, welcher Broker dazu neigt, überdurchschnittliche Renditen zu erzielen. Mit dieser Argumentation erscheinen die 10-Tage-EURUSD-Renditen von Broker B um 147 % überhöht.

Das Problem, mit dem wir konfrontiert sind, sollte inzwischen klar sein: Wichtige numerische Merkmale desselben Symbols sind nicht garantiert über alle Makler hinweg einheitlich. Infolgedessen kann die Rentabilität einer bestimmten Handelsstrategie nicht immer zuverlässig zwischen verschiedenen Brokern reproduziert werden.

Handelsstrategien, die KI-Modelle integrieren, die mit der ONNX-API oder sogar nativ in MQL5 erstellt wurden, werden die Erwartungen der Anleger möglicherweise nicht erfüllen, es sei denn, der zusätzliche Zeitaufwand, der erforderlich ist, um die KI individuell auf den beabsichtigten Broker zuzuschneiden, wird zur gängigen Praxis. Diese Arbeit ist zwar zeitaufwändig, aber sie ist eindeutig von entscheidender Bedeutung.

Während Sie diesen Artikel lesen, werden wir den Produktionszyklus, den die meisten MQL5-Entwickler befolgen, Schritt für Schritt nachstellen. Mit diesem Artikel wollen wir veranschaulichen, dass, wenn ein Entwickler seine Anwendung mit seinem privaten Broker, in unserem Fall Broker B, erstellt und optimiert, sein Kunde diese Anwendung aber mit einem anderen Broker, Broker A, einsetzt, die Probleme nicht so weit vom Entwickler und seinem Kunden entfernt sind. Jeder Entwickler, der einem solchen Produktionszyklus folgt, wird höchstwahrscheinlich mit gemischten Kritiken für seine Produkte zurückgelassen.

Um solche unbefriedigenden Leistungsniveaus zu vermeiden, müssen MQL5-Entwickler, die zuverlässige Dienste anbieten wollen, erkennen, dass ihren Kunden am besten mit Strategien und Anwendungen gedient ist, die auf bestimmte Makler zugeschnitten sind, um die Sicherheit der Verbraucher zu gewährleisten, die wir auf dem Marktplatz bedienen wollen.


Die ersten Schritte

Zunächst müssen wir unsere numerischen Standardbibliotheken importieren.

#Load our libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import MetaTrader5 as mt5

Legen Sie fest, auf welchen Zeitrahmen und welches Währungspaar wir uns konzentrieren wollen, und wie viele Datenzeilen wir benötigen.

#Let us define certain constants
TF = mt5.TIMEFRAME_D1
DATA = (365 * 4)
START = 1
PAIR = "EURUSD"

Starten Sie Ihr Terminal.

#Log in to the terminal
if mt5.initialize():
   print('Logged in successfully')

else:
   print('Failed To Log In')

Erfolgreich eingeloggt.

Analysieren wir die Schwierigkeit der Vorhersage des EURUSD mit Broker A.

EURUSD_BROKER_A = pd.DataFrame(mt5.copy_rates_from_pos(PAIR,TF,START,DATA))
#Store the data we retrieved from broker A
EURUSD_BROKER_A.to_csv("EURUSD BROKER A.csv")

Nun wiederholen wir das gleiche Verfahren für Makler B.

#I have manually changed brokers using the MT5 terminal, you should also do the same on your side
EURUSD_BROKER_B = pd.DataFrame(mt5.copy_rates_from_pos(PAIR,TF,START,DATA))
#Store the data we retrieved from broker B
EURUSD_BROKER_B.to_csv("EURUSD BROKER B.csv")

Großartig! Nachdem wir nun historische EURUSD-Daten von beiden Brokern gesammelt haben, wollen wir nun die empirischen Eigenschaften dieser Datensätze untersuchen, um zu sehen, ob das EURUSD-Symbol bei den verschiedenen Brokern konsistent ist. Wir müssen festlegen, wie weit wir in die Zukunft schauen wollen.

#Our forecasting horizon
HORIZON = 10

Lesen beider Datensätze.

EURUSD_BROKER_A = pd.read_csv("EURUSD BROKER A.csv")
EURUSD_BROKER_B = pd.read_csv("EURUSD BROKER B.csv")

Die Zeitspalte in den Datensätzen wird derzeit in Sekunden aufgezeichnet, wir hätten lieber eine für Menschen lesbare Zeitspalte im Format Datum-Monat-Jahr. Lassen Sie uns eine Methode dafür entwickeln.

def format_data(f_data):
    #First make a copy of the data, so we always preserve the original data
    f_data_copy = f_data.copy()
    #Format the time correctly, form seconds to human readable formats
    f_data_copy['time'] = pd.to_datetime(f_data_copy['time'],unit='s')
    return(f_data_copy)

Formatieren wir unsere Datensätze.

A = format_data(EURUSD_BROKER_A)
B = format_data(EURUSD_BROKER_B)

Benennen wir jetzt alle Spalten entsprechend um, sodass dem Namen jeder Spalte der Buchstabe des Brokers, der uns die Daten zur Verfügung gestellt hat, angehängt wird. Alle Spalten von Broker A oder B enden mit einem A bzw. B. Lassen Sie uns nun die historischen EURUSD-Daten, die wir von beiden Brokern erhalten haben, sorgfältig prüfen. Beachten Sie die Tatsache, dass beide Sätze genau 1 460 Zeilen täglicher Daten enthalten, was bedeutet, dass jeder Broker genau 4 Jahre täglicher Daten korrekt zurückgegeben hat. Welche weiteren Unterschiede kann der Leser feststellen? Haben Sie einen Blick auf das Tickvolumen geworfen?

# Rename all columns (except the join key)
B = B.rename(columns=lambda col: col + ' B' if col != 'id' else col)
A = A.rename(columns=lambda col: col + ' A' if col != 'id' else col)

Abb. 1: Die historischen täglichen EURUSD-Daten, die wir von Broker A erhalten haben, mit Zeitstempeln, die bis zum September 2019 zurückreichen


Abb. 2: Die historischen täglichen EURUSD-Daten, die wir von Broker B erhalten haben, stimmen nicht mit den Zeitstempeln in Abb. 1 überein, aber beide sind genau 4 Jahre alt

Verbinden wir nun die 2 Datensätze.

combined = pd.concat([A,B],axis=1)

Wir erstellen eine Spalte, die nur Nullen enthält.

combined['Null'] = 0

Definieren der Eingabeparameter.

inputs = ['open A','high A','low A','close A','tick_volume A','spread A','open B','high B','low B','close B','tick_volume B','spread B']

Berechnen wir die 10-Tage-Rendite des EURUSD.

#Label the data
combined['A Target'] = combined['close A'].shift(-HORIZON) - combined['close A']
combined['B Target'] = combined['close B'].shift(-HORIZON) - combined['close B']

#Drop the last HORIZON rows of data
combined = combined.iloc[:-HORIZON,:]

Das Tick-Volumen gibt Aufschluss darüber, wie viele Kursänderungen wir in regelmäßigen Abständen beobachtet haben. Zeiträume mit intensivem Handel werden durch ein hohes Tick-Volumen verraten, was auf eine rege Marktaktivität hinweist, während das Gegenteil bedeutet, dass der Markt relativ ruhig und wenig aktiv war. Broker A scheint einen langfristigen Aufwärtstrend in seinen Tick-Volumen-Daten zu haben, was darauf hindeutet, dass das offene Interesse der Anleger im Laufe der Zeit zu wachsen scheint. Gelegentlich gibt es große Ausschläge in der Grafik, die möglicherweise mit besonders geschäftigen Zeiten korrespondieren, in denen sich das offene Interesse am EURUSD einem Maximum nähert.

plt.title('Broker A Daily EURUSD Tick Volume')
plt.plot(combined['tick_volume A'],color='black')
plt.ylabel('Tick Volume')
plt.xlabel('Historical Day')
plt.grid()

Abb. 3: Das EURUSD-Tickvolumen, das wir von Broker A erhalten haben

Vergleicht man das uns von Broker B vorgelegte Tick-Volumen in Abb. 4 mit Abb. 3, kann man deutlich große Unterschiede in den gemeldeten Aktivitätsniveaus erkennen. Broker B hat fast keinen Trend in seinem Tick-Volumen, wenn man es mit Broker A vergleicht. Abb. 4 ist dicht, mit zufälligen Spikes, die nicht so periodisch erscheinen wie die Spikes, die wir in Abb. 3 beobachtet haben. 

plt.title('Broker B Daily EURUSD Tick Volume')
plt.plot(combined['tick_volume B'],color='black')
plt.ylabel('Tick Volume')
plt.xlabel('Historical Day')
plt.grid()

Abb. 4: Das tägliche EURUSD-Tickvolumen, das wir von Broker B erhalten haben

Wenn wir die durchschnittliche Rendite betrachten, die ein Anleger erwarten kann, wenn er den EURUSD bei jedem Broker hält, stellen wir fest, dass beide Broker unterschiedliche Varianten desselben Symbols anbieten. Andernfalls, wenn diese 2 Makler uns identische Versionen desselben Symbols anbieten würden, sollten wir dann nicht die gleichen Renditeerwartungen haben?

#What's the average 10-Day EURUSD return from both brokers
delta_return = str(((combined.iloc[:,-2:].mean()[0]-combined.iloc[:,-2:].mean()[1]) / combined.iloc[:,-2:].mean()[0]) * 100)

t = 'The Expected 10-Day EURUSD Return Differes by ' + delta_return[:5] + '% Between Our Brokers'

sns.barplot(combined.iloc[:,-2:].mean(),color='black')
plt.axhline(0,color='grey',linestyle='--')
plt.title(t)
plt.ylabel('Return')

Abb. 5: Die durchschnittliche Marktrendite zwischen den Brokern liegt auf beiden Seiten von 0

Stellen wir die Renditen der beiden Märkte übereinander dar. Ich werde jede der Renditen so skalieren, dass beide Linien bei 0 zentriert sind, und die Verschiebung von 0 stellt dar, wie viele Standardabweichungen wir von der durchschnittlichen Marktrendite abgewichen sind. Wir können sofort erkennen, dass es viele Momente gibt, in denen die beiden Linien auf entgegengesetzten Seiten der 0-Linie liegen, und dass es andere Zeiten gibt, in denen die beiden Linien einander folgen. Es sei daran erinnert, dass wir, salopp gesagt, dazu neigen anzunehmen, dass diese beiden Linien immer aufeinander folgen, aber Abb. 6 zeigt uns, dass dies nur manchmal der Fall ist.

plt.plot(((combined.iloc[:,-1]-combined.iloc[:,-1].mean())/combined.iloc[:,-1].std()),color='red')
plt.plot(((combined.iloc[:,-2]-combined.iloc[:,-2].mean())/combined.iloc[:,-2].std()),color='black')
plt.grid()
plt.axhline(0,color='black',linestyle='--')
plt.ylabel('Std. Deviations From Expected 10-Day EURUSD Return')
plt.xlabel('Historical Days')
plt.title('EURUSD Returns from Different Brokers May Not Always Allign')
plt.legend(['Broker A','Broker B'])

Abb. 6: Visualisierung der 10-tägigen EURUSD-Renditen, die von 2 verschiedenen Brokern generiert werden

Der Vergleich der Renditeunterschiede zwischen den Brokern ermöglicht es uns zu beurteilen, welcher Broker risikoreicher ist und welcher Broker uns eine sicherere Rendite bietet. Nach diesem Maßstab ist die Version des EURUSD von Broker A im Vergleich zu Broker B mit einem höheren Risiko verbunden.

#The variance of returns is not the same across both brokers, broker A is riskier
delta_var = str(((combined.iloc[:,-2:].var()[0]-combined.iloc[:,-2:].var()[1]) / combined.iloc[:,-2:].var()[0]) * 100)

t = 'Broker A EURUSD Returns Appear to Carry '+ delta_var[:5]+'% Additional Risk.'

sns.barplot(combined.iloc[:,-2:].var(),color='black')
plt.axhline(np.min(combined.iloc[:,-2:].var()),color='red',linestyle=':')
plt.title(t)
plt.ylabel('Vriance of Returns')

Abb. 7: Die Renditen von Broker A sind mit einem um 21 % höheren Risiko verbunden als die Renditen von Broker B. Würden Sie diese Symbole zu diesem Zeitpunkt immer noch als „gleich“ betrachten?

Wenn wir uns der Betrachtung des größten Drawdowns zuwenden, der von beiden Brokern verzeichnet wurde, können wir immer noch keine kohärenten Beobachtungen machen. Der größte Drawdown, den beide Märkte aufwiesen, unterschied sich zwischen unseren beiden Brokern um etwa 37 %. All dies scheint darauf hinzudeuten, dass Broker B seine Kunden auf intelligente Weise von der Volatilität des EURUSD-Marktes abschirmt, indem er eine verkürzte Perspektive des Devisenmarktes bietet.

#Broker A also demonstrated the largest drawdown ever in our 4 year sample window
delta = (((combined.iloc[:,-2:].min()[0]-combined.iloc[:,-2:].min()[1]) / combined.iloc[:,-2:].min()[0]) *100)
delta_s = str(delta)

t = 'The Largest Negative 10-Day EURUSD Return Grew By: ' + delta_s[:5] + ' %'

sns.barplot(combined.iloc[:,-2:].min(),color='black')
plt.axhline(np.max(combined.iloc[:,-2:].min()),color='red',linestyle=':')
plt.title(t)
plt.ylabel('Return')

Abb. 8: Broker A wies mit 36,79 % den größten Renditeverlust auf und lag damit deutlich vor dem größten Verlust von Broker B

Die Überlagerung der Verteilung der 10-Tage-Rendite des EURUSD, die von beiden Brokern generiert wurde, zeigt, dass beide Broker wirklich nicht die gleiche Sicht auf den Markt bieten. Wie wir in der Einleitung unserer Diskussion erläutert haben, steht es jedem Makler frei, seine Kursdaten aus der von ihm gewählten Quelle zu beziehen. Diese dezentralisierte Preisgestaltung bedeutet, dass jeder Broker willkürlich unterschiedliche Perspektiven für einen bestimmten Markt anbieten kann.

sns.histplot(((combined.iloc[:,-2]-combined.iloc[:,-2].mean())/combined.iloc[:,-2].std()),color='black')
sns.histplot(((combined.iloc[:,-1]-combined.iloc[:,-1].mean())/combined.iloc[:,-1].std()),color='red')
plt.xlabel('Std. Deviations From The Expected Return')
plt.ylabel('Frequency')
plt.title('Comparing The Distribution of 10-Day EURUSD Returns Between 2 Brokers')
plt.grid()
plt.legend(['Broker A','Broker B'])

Abb. 9: Vergleich der Verteilung der von den 2 Märkten erzielten Renditen

Wenn wir außerdem die Korrelationsniveaus zwischen den Brokern analysieren, stellen wir fest, dass die Marktpreise kaum miteinander korreliert sind. Das heißt, wie wir bereits sagten, mehr als die Hälfte der Zeit könnten sich die Kurse zwischen diesen 2 bestimmten Brokern in entgegengesetzte Richtungen entwickeln.

sns.heatmap(combined.loc[:,inputs].corr(),annot=True)

Abb. 10: Die Visualisierung der Korrelationsniveaus zeigt uns, dass sich die Symbole der beiden Broker die meiste Zeit fast unabhängig voneinander bewegen

Nun wollen wir sehen, ob unsere Vorhersagefähigkeiten bei allen Brokern gleich bleiben.

from sklearn.model_selection import train_test_split,TimeSeriesSplit,cross_val_score
from sklearn.linear_model import Ridge

Wir erstellen ein Zeitreihenvalidierungsobjekt

tscv = TimeSeriesSplit(n_splits=5,gap=HORIZON)

und schreiben eine Methode, die uns ein neues Modell zurückgibt, das wir verwenden können.

def get_model():
    return(Ridge())

Wir teilen die Daten auf, und achten darauf, dass sie nicht gemischt werden.
train , test = train_test_split(combined,shuffle=False,test_size=0.5)

Wir erfassen die Fehlerquoten, wenn wir die Spalte verwenden, die wir absichtlich nur mit Nullen gefüllt haben. Dadurch wird das Modell gezwungen, immer den Durchschnittswert des Ziels vorherzusagen. Es sei daran erinnert, dass ein lineares Modell den Achsenabschnitt vorhersagt, wenn alle Eingaben 0 sind. Oder einfach ausgedrückt: Dieses Modell gibt uns Auskunft darüber, wie gut wir auf diesem Markt abschneiden können, wenn wir immer die durchschnittliche Marktrendite vorhersagen. Wenn es uns nicht gelingt, dieses Modell zu schlagen, zeigt uns das, dass wir keine Fähigkeiten haben.

Dieser Benchmark wird als TSS bezeichnet. Wir haben die TSS in der Einleitung unserer Diskussion definiert. Unser Ziel ist es, den TSS bei beiden Brokern zu messen und dann zu sehen, ob wir in der Lage sind, diese Benchmark bei allen Brokern zu übertreffen.

broker_a_tss = np.mean(np.abs(cross_val_score(get_model(),train.loc[:,['Null']],train.loc[:,'A Target'],scoring='neg_mean_squared_error',n_jobs=-1,cv=tscv)))
broker_a_rss = np.mean(np.abs(cross_val_score(get_model(),train.loc[:,inputs[0:(len(inputs)//2)]],train.loc[:,'A Target'],scoring='neg_mean_squared_error',n_jobs=-1,cv=tscv)))

broker_b_tss = np.mean(np.abs(cross_val_score(get_model(),train.loc[:,['Null']],train.loc[:,'B Target'],scoring='neg_mean_squared_error',n_jobs=-1,cv=tscv)))
broker_b_rss = np.mean(np.abs(cross_val_score(get_model(),train.loc[:,inputs[(len(inputs)//2):]],train.loc[:,'B Target'],scoring='neg_mean_squared_error',n_jobs=-1,cv=tscv)))

Überraschenderweise ist es für uns einfacher, den TSS bei Broker B zu übertreffen als bei Broker A! Das bedeutet, dass die zukünftige 10-Tage-Rendite des EURUSD nicht immer effizient ist, wenn wir von Broker zu Broker wechseln.

res = [(broker_a_rss/broker_a_tss),(broker_b_rss/broker_b_tss)]

eff = str(((res[0] - res[1])/res[1]) * 100)

t = 'The EURUSD Appears ' + eff[0:4] + '% Easier To Forecast With Broker B'

sns.barplot(res,color='black')
plt.axhline(np.min(res),color='red',linestyle=':')
plt.ylabel('5-Fold Cross Valiated Ratio of RSS/TSS ')
plt.title(t)
plt.xticks([0,1],['Broker A','Broker B'])

Abb. 11: Die 10-Tage-Rendite des EURUSD-Futures lässt sich mit Broker B leichter vorhersagen.

Da wir festgestellt haben, auf welchen Broker wir uns konzentrieren wollen, wählen wir die Eingabedaten aus, die wir von Broker B erhalten haben.

b_inputs = inputs[len(inputs)//2:]

Lassen Sie uns nun ein völlig neues Modell entwickeln.

from sklearn.ensemble import GradientBoostingRegressor

model = GradientBoostingRegressor()

Passen Sie das Modell an alle Daten an, die wir von Broker B haben.

model.fit(train.loc[:,b_inputs[:-2]],train['B Target'])
Lassen Sie uns nun unser Modell in das ONNX-Format exportieren, damit wir unser KI-Modell einfach in unsere MQL5-Anwendung integrieren können.
import skl2onnx,onnx
Definieren wir die Anzahl der Eingaben, die unser ONNX-Modell akzeptiert.
initial_types = [('float_input',skl2onnx.common.data_types.FloatTensorType([1,4]))]
Jetzt konvertieren wir das ONNX-Modell in einen ONNX-Prototyp
onnx_proto = skl2onnx.convert_sklearn(model,initial_types=initial_types,target_opset=12)

und speichern den ONNX-Prototyp auf der Festplatte.

onnx.save(onnx_proto,"EURUSD GBR D1.onnx")


Erste Schritte in MQL5

Nun, da wir unser ONNX-Modell fertig haben, können wir mit der Erstellung unserer MQL5-Anwendung beginnen. Laden wir zunächst die benötigten Bibliotheken
//+------------------------------------------------------------------+
//|                                                       EURUSD.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"

//+------------------------------------------------------------------+
//| System Constants Definitions                                     |
//+------------------------------------------------------------------+
#include  <Trade\Trade.mqh>
CTrade Trade;

Wir werden auch Systemkonstanten benötigen, um sicherzustellen, dass unsere Anwendung die wichtigen Parameter widerspiegelt, die wir zuvor definiert haben, wie z. B. die 10-tägige Wiederkehrperiode.

//+------------------------------------------------------------------+
//| System Constants Definitions                                     |
//+------------------------------------------------------------------+
#define  ONNX_INPUT_SHAPE 4
#define  ONNX_OUTPUT_SHAPE 1
#define  SYSTEM_TIME_FRAME PERIOD_D1
#define  RETURN_PERIOD 10
#define  TRADING_VOLUME SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN)
Wir laden die ONNX-Datei als Systemressource, damit sie mit unserer Anwendung kompiliert wird.
//+------------------------------------------------------------------+
//| System Resources                                                 |
//+------------------------------------------------------------------+
#resource "\\Files\\Broker Manipulation\\EURUSD GBR D1.onnx" as const uchar onnx_proto[];

Für die Umsetzung unserer Handelsstrategie werden einige globale Variablen benötigt.

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
long   model;
int    position_timer;
double bid,ask;
double o,h,l,c;
bool   bullish;
double sl_width;

Wenn unser System zum ersten Mal initialisiert wird, werden wir unser ONNX-Modell einrichten und dann wichtige globale Variablen für unsere Handelsstrategie zurücksetzen.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   model = OnnxCreateFromBuffer(onnx_proto,ONNX_DATA_TYPE_FLOAT);

   ulong input_shape[] = {1,ONNX_INPUT_SHAPE};
   ulong output_shape[] = {1,ONNX_OUTPUT_SHAPE};

   if(model == INVALID_HANDLE)
     {
      Comment("Failed To Load EURUSD Auto-Encoder-Decoder: ",GetLastError());
      return(INIT_FAILED);
     }

   if(!OnnxSetInputShape(model,0,input_shape))
     {
      Comment("Failed To Set EURUSD Auto-Encoder-Decoder Input Shape: ",GetLastError());
      return(INIT_FAILED);
     }

   else
      if(!OnnxSetOutputShape(model,0,output_shape))
        {
         Comment("Failed To Set EURUSD Auto-Encoder-Decoder Output Shape: ",GetLastError());
         return(INIT_FAILED);
        }

   position_timer = 0;
   sl_width = 30;
//---
   return(INIT_SUCCEEDED);
  }

Wenn wir unsere Handelsstrategie nicht mehr verwenden, geben wir die Ressourcen frei, die von unserem ONNX-Modell verbraucht wurden.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   OnnxRelease(model);
  }

Immer wenn wir aktualisierte Kurse erhalten, speichern wir die neuen Kursniveaus einmal am Tag, und wenn wir dann keine offenen Positionen haben, erhalten wir eine Prognose von unserem Modell und handeln dann entsprechend. Andernfalls, wenn wir bereits einen offenen Handel haben, werden wir versuchen, unseren Stop-Loss möglichst nachzuziehen, während wir unsere 10-tägige Halteperiode für jeden Handel herunterzählen.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),SYSTEM_TIME_FRAME,0);

   if(time_stamp != current_time)
     {
      time_stamp = current_time;
      ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
      bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
      o = iOpen(Symbol(),SYSTEM_TIME_FRAME,1);
      h = iHigh(Symbol(),SYSTEM_TIME_FRAME,1);
      l = iLow(Symbol(),SYSTEM_TIME_FRAME,1);
      c = iClose(Symbol(),SYSTEM_TIME_FRAME,1);
      bullish = (o < c) && (c > iClose(Symbol(),SYSTEM_TIME_FRAME,2));

      if(PositionsTotal() == 0)
        {
         position_timer = 0;
         find_setup();
        }

      else
         if(PositionsTotal() > 0)
           {
            if(PositionSelect(Symbol()))
              {
               long position_type = PositionGetInteger(POSITION_TYPE);
               double current_sl = PositionGetDouble(POSITION_SL);
               double new_sl;

               //--- Buy Trades
               if(position_type == POSITION_TYPE_BUY)
                 {
                  new_sl = bid - ((h-l)*sl_width);
                  if(new_sl > current_sl)
                     Trade.PositionModify(Symbol(),new_sl,0);
                 }

               //--- Sell Trades
               else
                  if(position_type == POSITION_TYPE_SELL)
                    {
                     new_sl = ask + ((h-l)*sl_width);
                     if(new_sl < current_sl)
                        Trade.PositionModify(Symbol(),new_sl,0);
                    }
              }

            if(position_timer < RETURN_PERIOD)
               position_timer+=1;

            else
               Trade.PositionClose(Symbol());
           }
     }
  }

Schließlich erhält diese Funktion eine Prognose von unserem Modell und prüft dann, ob eine gültige Handelsmöglichkeit besteht.

//+------------------------------------------------------------------+
//| Find A Trading Setup                                             |
//+------------------------------------------------------------------+
void find_setup(void)
  {
   vectorf model_inputs(ONNX_INPUT_SHAPE);

   model_inputs[0] = (float) iOpen(Symbol(),SYSTEM_TIME_FRAME,0);
   model_inputs[1] = (float) iHigh(Symbol(),SYSTEM_TIME_FRAME,0);
   model_inputs[2] = (float) iLow(Symbol(),SYSTEM_TIME_FRAME,0);
   model_inputs[3] = (float) iClose(Symbol(),SYSTEM_TIME_FRAME,0);

   vectorf model_output(ONNX_OUTPUT_SHAPE);

   if(!OnnxRun(model,ONNX_DATA_TYPE_FLOAT,model_inputs,model_output))
     {
      Comment("Failed To Get A Prediction From Our Model: ",GetLastError());
      return;
     }

   else
     {
      Comment("Prediction: ",model_output[0]);

      vector open,close;

      open.CopyRates(Symbol(),SYSTEM_TIME_FRAME,COPY_RATES_OPEN,1,2);
      close.CopyRates(Symbol(),SYSTEM_TIME_FRAME,COPY_RATES_CLOSE,1,2);

      if(open.Mean() < close.Mean())
        {
         if((model_output[0] > 0) && (bullish))
            Trade.Buy(TRADING_VOLUME,Symbol(),ask,(bid - ((h-l) * sl_width)),0);
        }


      else
         if(open.Mean() > close.Mean())
           {
            if((model_output[0] < 0) && (!bullish))
               Trade.Sell(TRADING_VOLUME,Symbol(),bid,(ask + ((h-l) * sl_width)),0);
           }
     }
  }

Vergessen wir nicht, alle Systemkonstanten, die wir in Ihrer Anwendung erstellen, freizugeben.

//+------------------------------------------------------------------+
//| Undefine System Constants                                        |
//+------------------------------------------------------------------+
#undef  ONNX_INPUT_SHAPE
#undef  ONNX_OUTPUT_SHAPE
#undef  SYSTEM_TIME_FRAME
#undef  TRADING_VOLUME
#undef  RETURN_PERIOD
//+------------------------------------------------------------------+

Die Datumsperioden, die wir für unseren Backtest verwenden, liegen außerhalb der Stichprobe des Trainingszeitraums unseres Modells. Diese Daten werden sowohl für unsere Tests auf Broker A als auch auf Broker B konstant gehalten. Erinnern wir uns, dass Broker B den Broker symbolisiert, den ein MQL5-Entwickler zur Erstellung seiner Anwendung verwendet, während Broker A den Broker symbolisiert, mit dem seine Kunden die Anwendung letztendlich bereitstellen werden.

Abb. 12: Wählen der Eingabedaten unseres Testzeitraum

Beide Einstellungen, die in Abb. 12 oben und Abb. 13 unten angegeben sind, werden für beide Tests, die wir durchführen, beibehalten.

Abb. 13: Wir werden auch anspruchsvolle Modellierungseinstellungen wählen, um eine realistische Erwartung an die Fähigkeit unserer Strategie zu erhalten

Wie in Abb. 14 zu sehen ist, scheint unsere Strategie vielversprechend zu sein, wenn wir sie mit Broker B testen. Sie kommt mit Daten, die außerhalb der Stichprobe liegen, gut zurecht und ermutigt uns dazu, mehr Zeit auf die Verfeinerung der Strategie zu verwenden, um die beste Leistung zu erzielen. Der Punkt, den wir dem Leser vermitteln wollen, ist jedoch, dass es naiv ist, zu glauben, dass Verbesserungen, die bei einem Makler vorgenommen werden, auch bei anderen Maklern sinnvoll sind.

Abb. 14: Die von unserer Strategie erzeugte Kapitalkurve weist einen positiven Trend auf, wenn sie auf den vorgesehenen Makler angewendet wird.

Nachdem wir jedoch dieselbe Strategie bei Broker A angewandt hatten, konnten wir den positiven Aufwärtstrend beim Kontostand, den wir bei Broker B beobachtet hatten, nicht mehr feststellen. Die Entwickler müssen verstehen, dass dies nicht immer ihre Schuld ist. Es ist eine Herausforderung für jeden Entwickler, seine Modelle für jeden Makler anzupassen, der unter der Sonne existiert.

Dies ist jedoch eine visuelle Art, sich das Problem vorzustellen. Entwickler und ihre Kunden können auf völlig unterschiedlichen Seiten stehen, wenn ihre Beziehungen nicht sorgfältig genug definiert sind. 

Abb. 15: Bei der Umsetzung unserer Strategie mit Broker A gelingt es uns nicht, den von uns hart erarbeiteten Aufwärtstrend des Kontostands zu reproduzieren

Wir können uns auch eine detailliertere Analyse unserer Leistung mit Makler B in Abb. 16 ansehen und diese mit der Leistung unseres Modells mit Makler A in Abb. 17 vergleichen. 

Abb. 16: Eine detaillierte Analyse unserer Handelsleistung, wenn wir uns auf den gewünschten Broker konzentrieren

Es ist deutlich zu erkennen, dass die Entwicklung einer Strategie, die über mehrere Broker hinweg eine sinnvolle Leistung erbringt, sicherlich keine triviale Angelegenheit ist. Da die Modelle für maschinelles Lernen immer komplexer werden, reagieren sie auch empfindlicher auf geringfügige Änderungen ihrer Eingaben. Diese Abweichungen in den numerischen Eigenschaften des Symbols können verheerende Auswirkungen auf unsere Fähigkeit haben, Handelsstrategien zu teilen und sinnvoll zu reproduzieren. 

Abb. 17: Eine detaillierte Analyse der Strategie, die versucht, mit Maklern zu arbeiten, mit denen sie nicht trainiert wurde


Schlussfolgerung

Die dezentralisierte Natur der globalen Finanzmärkte führt zu realen Einschränkungen, die es unserer Gemeinschaft erschweren, die Ergebnisse der anderen zu reproduzieren. Die Broker bieten keine Garantie, dass ihre Kurse übereinstimmen, was bedeutet, dass Ineffizienzen, die Sie bei Ihrem Broker ausnutzen, bei meinem möglicherweise nicht bestehen, selbst wenn Sie dieselbe Strategie für dasselbe Symbol verwenden.

Je nachdem, welche Rolle Sie in unserer Gemeinschaft einnehmen möchten, haben diese Erkenntnisse praktische Auswirkungen:

  • Wenn Sie den Bereich „Freelance“ auf der MQL5-Website nutzen möchten, geben Sie bei der Anfrage Ihren Broker an und bitten Sie die Entwickler, Demokonten bei Ihrem Broker einzurichten, damit Sie maßgeschneiderte Lösungen erhalten. Vermeiden Sie beiläufige und allgemein gehaltene Anfragen wie „EURUSD Trading Application Wanted“, denn wie wir gesehen haben, ist es sicherer für Sie, wenn Sie so detailliert wie möglich sind.
  • Nutzer, die häufig Anwendungen auf dem Marktplatz kaufen, verstehen jetzt, warum maklerspezifische Produkte einen größeren Wert haben können als solche, die einen universellen Nutzen versprechen.
  • Signalabonnenten können ihre Zufriedenheit maximieren, indem sie selektiv Signalanbieter auswählen, die denselben Broker verwenden, um sicherzustellen, dass die gemeldeten und die erzielten Erträge immer übereinstimmen.
  • Schließlich erhalten meine MQL5-Entwicklerkollegen ein klareres Verständnis davon, was wir brauchen, um konsistente Produkte und zuverlässige Dienstleistungen zu liefern, die unsere Kunden zufrieden stellen.

Wenn wir diese Herausforderungen erkennen, können wir auf reproduzierbare, maklerspezifische Lösungen hinarbeiten, die allen in unserer vielfältigen und integrativen Gemeinschaft zugute kommen. Dieser Artikel sollte die Gefahren veranschaulichen, die mit dem Versuch verbunden sind, ein ONNX-Modell für verschiedene Broker zu verwenden. Als MQL5-Entwickler bin ich der Meinung, dass wir uns an höhere Praxisstandards halten und vermeiden sollten, unsere Kunden solchen Gefahren auszusetzen.

Dateiname Beschreibung der Datei
Requesting Broker Data.ipynb Das Jupyter-Notebook, das wir verwendet haben, um historische tägliche EURUSD-Daten von unseren 2 Brokern abzurufen.
Analyzing Broker Data.ipynb Das Jupyter-Notebook, das wir verwendet haben, um die Konsistenz der historischen täglichen EURUSD-Daten von unseren 2 Brokern zu testen.
EURUSD.mq5 Der Expert Advisor, den wir gebaut haben, um unsere Rentabilität nach dem gleichen Modell auf 2 verschiedenen Brokern zu bewerten.

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

Beigefügte Dateien |
EURUSD.mq5 (6.94 KB)
Handel mit dem MQL5 Wirtschaftskalender (Teil 9): Bessere Interaktion mit Nachrichten durch eine dynamische Bildlaufleiste und eine optimierte Anzeige Handel mit dem MQL5 Wirtschaftskalender (Teil 9): Bessere Interaktion mit Nachrichten durch eine dynamische Bildlaufleiste und eine optimierte Anzeige
In diesem Artikel erweitern wir den MQL5-Wirtschaftskalender um eine dynamische Bildlaufleiste für eine intuitive Nachrichtennavigation. Wir sorgen für eine reibungslose Darstellung der Ereignisse und eine effiziente Aktualisierungen. Wir validieren die reaktionsschnelle Bildlaufleiste und das ausgefeilte Dashboard durch Tests.
Datenwissenschaft und ML (Teil 39): News + Künstliche Intelligenz, würden Sie darauf wetten? Datenwissenschaft und ML (Teil 39): News + Künstliche Intelligenz, würden Sie darauf wetten?
Nachrichten treiben die Finanzmärkte an, insbesondere wichtige Veröffentlichungen wie die Non-Farm Payrolls (NFP, Beschäftigung außerhalb der Landwirtschaft). Wir alle haben schon erlebt, wie eine einzige Schlagzeile starke Kursbewegungen auslösen kann. In diesem Artikel befassen wir uns mit der leistungsstarken Schnittmenge von Nachrichtendaten und künstlicher Intelligenz.
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.
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 65): Verwendung von FrAMA-Mustern und des Force Index MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 65): Verwendung von FrAMA-Mustern und des Force Index
Der Fractal Adaptive Moving Average (FrAMA) und der Oszillator Force Index sind ein weiteres Paar von Indikatoren, die in Verbindung mit einem MQL5 Expert Advisor verwendet werden können. Diese beiden Indikatoren ergänzen sich ein wenig, denn der FrAMA ist ein Trendfolgeindikator, während der Force Index ein volumenbasierter Oszillator ist. Wie immer verwenden wir den MQL5-Assistenten, um das Potenzial der beiden schnell zu erkunden.