
Die Grenzen des maschinellen Lernens überwinden (Teil 2): Mangelnde Reproduzierbarkeit
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,onnxDefinieren 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
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.