English Русский 日本語
preview
Bauen Sie Ihr erstes Modell einer Glass-Box mit Python und MQL5

Bauen Sie Ihr erstes Modell einer Glass-Box mit Python und MQL5

MetaTrader 5Handelssysteme | 10 April 2024, 11:32
227 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Einführung

Die Algorithmen der Glass-Box (Glaskasten) sind Algorithmen des maschinellen Lernens, die völlig transparent und von Natur aus verständlich sind. Sie widerlegen die gängige Meinung, dass es beim maschinellen Lernen einen Kompromiss zwischen Vorhersagegenauigkeit und Interpretierbarkeit gibt, da sie ein unvergleichliches Maß an Genauigkeit und Transparenz bieten. Das bedeutet, dass sie im Vergleich zu den uns vertrauten Black-Box-Alternativen exponentiell einfacher zu debuggen, zu warten und iterativ zu verbessern sind. Black-Box-Modelle sind alle Modelle des maschinellen Lernens, deren Innenleben komplex und nicht leicht zu interpretieren ist. Diese Modelle können hochdimensionale und nichtlineare Beziehungen darstellen, die für uns Menschen nicht leicht zu verstehen sind.

Als Faustregel gilt, dass Black-Box-Modelle nur in Szenarien verwendet werden sollten, in denen ein Glass-Box-Modell nicht das gleiche Maß an Genauigkeit liefern kann. In diesem Artikel werden wir ein Glass-Box-Modell erstellen und die potenziellen Vorteile des Einsatzes von Glass-Boxen verstehen. Wir werden 2 Möglichkeiten zur Steuerung unseres MetaTrader 5-Terminals mit unserem Glass-Box-Modell untersuchen:

  1. Legacy-Ansatz: Dies ist der einfachste Weg. Wir verbinden einfach unser Glass-Box-Modell mit unserem MetaTrader 5 Terminal, indem wir die integrierte Python-Bibliothek in MetaTrader 5 verwenden. Von dort aus werden wir einen Expert Advisor in MetaQuotes Language 5 erstellen, um unser Glass-Box-Modell zu unterstützen und unsere Effektivität zu maximieren.
  2. Heutiger Ansatz: Dies ist die empfohlene Methode zur Integration von Machine Learning-Modellen in Ihren Expert Advisor. Wir werden unser Glass-Box-Modell in das Open Neural Network Exchange-Format exportieren und das Modell dann direkt in unseren Expert Advisor als Ressource laden, sodass wir alle nützlichen Funktionen des MetaTrader 5 nutzen und mit der Leistungsfähigkeit unseres Glass-Box-Modells verbinden können.

AI

Abbildung 1: Das menschliche Gehirn mit künstlicher Intelligenz imitieren


Black-Box-Modelle vs. Glass-Box-Modelle

Wie bereits erwähnt, sind die meisten herkömmlichen Modelle für maschinelles Lernen schwer zu interpretieren oder zu erklären. Diese Klasse von Modellen wird als Black-Box-Modelle bezeichnet. Black-Box-Modelle umfassen alle Modelle, deren Innenleben komplex und nicht leicht zu interpretieren ist. Dies stellt ein großes Problem für uns dar, da wir versuchen, die wichtigsten Leistungskennzahlen unseres Modells zu verbessern. Glass-Box-Modelle hingegen sind eine Reihe von Modellen des maschinellen Lernens, deren innere Funktionsweise transparent und verständlich ist und deren Vorhersagegenauigkeit ebenfalls hoch und zuverlässig ist. 

Forscher, Entwickler und eine Gruppe von Domänenexperten bei Microsoft Research haben ein Python-Paket namens Interpret ML entwickelt und pflegen es zum Zeitpunkt der Erstellung dieses Artikels aktiv. Das Paket enthält eine umfassende Reihe von Black-Box-Erklärern und Glass-Box-Modellen. Black-Box-Erklärer sind eine Reihe von Algorithmen, die versuchen, Einblicke in das Innenleben eines Black-Box-Modells zu gewinnen. Die meisten Black-Box-Erklärungsalgorithmen in Interpret ML sind modellunabhängig, d. h. sie können auf jedes Black-Box-Modell angewendet werden. Diese Black-Box-Erklärer können jedoch nur Schätzungen der Black-Box-Modelle liefern. Wir werden im nächsten Abschnitt dieses Artikels untersuchen, warum dies problematisch sein kann. Interpret ML umfasst auch eine Reihe von Glass-Box-Modellen, die mit der Vorhersagegenauigkeit von Black-Box-Modellen konkurrieren und dabei eine noch nie dagewesene Transparenz aufweisen. Dies ist perfekt für jeden, der mit maschinellem Lernen arbeitet, egal ob Anfänger oder Experte, der Wert der Modellinterpretierbarkeit geht über den Bereich und das Erfahrungsniveau hinaus.

Für weitere Informationen:

1. Wenn Sie daran interessiert sind, können Sie die Dokumentation von Interpret ML lesen.

2. Außerdem können Sie das „White Paper“ von Interpret ML durchlesen. 

Wir werden Interpret ML in diesem Papier verwenden, um ein Glass-Box-Modell in Python zu erstellen. Wir werden sehen, wie unser Glass-Box-Modell uns entscheidende Einblicke geben kann, um unseren Feature-Engineering-Prozess zu steuern und unser Verständnis für die innere Funktionsweise unseres Modells zu verbessern.

Die Herausforderung der Black-Box-Modelle: Das Problem der Meinungsverschiedenheiten

Einer der Gründe, warum wir keine Black-Box-Modelle mehr verwenden sollten, ist das so genannte „Disagreement-Problem“. Kurz gesagt, verschiedene Erklärungsmethoden können sehr unterschiedliche Erklärungen für ein Modell liefern, selbst wenn sie dasselbe Modell bewerten. Mit Hilfe von Erklärungstechniken wird versucht, Einblick in die zugrunde liegende Struktur eines Black-Box-Modells zu gewinnen. Es gibt viele verschiedene Denkschulen, die sich mit der Erklärung von Modellen befassen, und deshalb kann sich jede Erklärungstechnik auf unterschiedliche Aspekte des Modellverhaltens konzentrieren und daher unterschiedliche Metriken über das zugrunde liegende Black-Box-Modell ableiten. Das Problem der Meinungsverschiedenheiten ist ein offenes Forschungsgebiet, das erkannt und proaktiv entschärft werden muss.

In diesem Beitrag wird das Problem der Uneinigkeit anhand einer realen Situation demonstriert, falls der Leser dieses Phänomen noch nicht selbst beobachtet hat.

Für weitere Informationen:

1. Wenn Sie mehr über das Disagreement-Problem erfahren möchten, empfehle ich Ihnen die Lektüre dieses ausgezeichneten Artikels, das von einem klugen Kollektiv von Absolventen der Universitäten Harvard, MIT, Drexel und Carnegie Mellon stammt.

Lassen Sie uns das Problem der Meinungsverschiedenheiten einmal in Aktion sehen:

Zunächst importieren wir Python-Pakete, die uns bei der Durchführung unserer Analyse helfen.

#Import MetaTrader5 Python package
#pip install --upgrade MetaTrader5, if you don't have it installed
import MetaTrader5 as mt5

#Import datetime for selecting data
#Standard python package, no installation required
from datetime import datetime

#Plotting Data
#pip install --upgrade matplotlib, if you don't have it installed
import matplotlib.pyplot as plt

#Import pandas for handling data
#pip install --upgrade pandas, if you don't have it installed
import pandas as pd

#Import library for calculating technical indicators
#pip install --upgrade pandas-ta, if you don't have it installed
import pandas_ta as ta

#Scoring metric to assess model accuracy
#pip install --upgrade scikit-learn, if you don't have it installed
from sklearn.metrics import precision_score

#Import mutual information, a black-box explanation technique
from sklearn.feature_selection import mutual_info_classif

#Import permutation importance, another black-box explanation technique
from sklearn.inspection import permutation_importance

#Import our model
#pip install --upgrade xgboost, if you don't have it installed
from xgboost import XGBClassifier

#Plotting model importance
from xgboost import plot_importance

Von dort aus können wir uns mit unserem MetaTrader 5-Terminal verbinden, aber vorher müssen wir unsere Anmeldedaten angeben.

#Enter your account number
login = 123456789

#Enter your password
password = '_enter_your_password_'

#Enter your Broker's server
server = 'Deriv-Demo'

Jetzt können wir das MetaTrader 5-Terminal initialisieren und uns im selben Schritt bei unserem Handelskonto anmelden.

#We can initialize the MT5 terminal and login to our account in the same step
if mt5.initialize(login=login,password=password,server=server):
    print('Logged in successfully')
else:
    print('Failed To Log in')

Erfolgreich eingeloggt.

Wir haben nun vollen Zugriff auf das MetaTrader 5 Terminal und können Chartdaten, Tickdaten, aktuelle Kurse und vieles mehr abfragen.

#To view all available symbols from your broker
symbols = mt5.symbols_get()

for index,value in enumerate(symbols):
    print(value.name)

Volatility 10 Index

Volatility 25 Index

Volatility 50 Index

Volatility 75 Index

Volatility 100 Index

Volatility 10 (1s) Index

Boom 1000 Index

Boom 500 Index

Crash 1000 Index

Crash 500 Index

Step Index

...

Sobald wir wissen, welches Symbol wir modellieren wollen, können wir Chartdaten zu diesem Symbol anfordern, aber zuerst müssen wir den Datumsbereich angeben, den wir abrufen wollen.

#We need to specify the dates we want to use in our dataset
date_from = datetime(2019,4,17)
date_to = datetime.now()

Jetzt können wir Chartdaten zu diesem Symbol anfordern.
#Fetching historical data
data = pd.DataFrame(mt5.copy_rates_range('Boom 1000 Index',mt5.TIMEFRAME_D1,date_from,date_to))

Wir müssen die Zeitspalte in unserem Datenrahmen für Darstellungszwecke formatieren.

#Let's convert the time from seconds to year-month-date
data['time'] = pd.to_datetime(data['time'],unit='s')

data

DataFrame nach der Konvertierung der Zeit

Abb. 2: Unser DataFrame zeigt jetzt die Zeit in einem für Menschen lesbaren Format an. Beachten Sie, dass die Spalte „real_volume“ mit Nullen gefüllt ist.

Nun müssen wir eine Hilfsfunktion erstellen, die uns dabei hilft, neue Funktionen zu unserem Datenrahmen hinzuzufügen, technische Indikatoren zu berechnen und unseren Datenrahmen zu bereinigen.

#Let's create a function to preprocess our data
def preprocess(df):
    #All values of real_volume are 0 in this dataset, we can drop the column
    df.drop(columns={'real_volume'},inplace=True) 
    #Calculating 14 period ATR
    df.ta.atr(length=14,append=True)
    #Calculating the growth in the value of the ATR, the second difference
    df['ATR Growth'] = df['ATRr_14'].diff().diff()
    #Calculating 14 period RSI
    df.ta.rsi(length=14,append=True)    
    #Calculating the rolling standard deviation of the RSI
    df['RSI Stdv'] = df['RSI_14'].rolling(window=14).std()
    #Calculating the mid point of the high and low price
    df['mid_point'] = ( ( df['high'] + df['low'] ) / 2 )  
    #We will keep track of the midpoint value of the previous day
    df['mid_point - 1'] = df['mid_point'].shift(1) 
    #How far is our price from the midpoint?
    df['height'] = df['close'] - df['mid_point']  
    #Drop any rows that have missing values
    df.dropna(axis=0,inplace=True)

Rufen wir die Vorverarbeitungsfunktion für unseren Datenrahmen auf.

preprocess(data)

data

Unser Datenrahmen nach der Vorverarbeitung

Abb. 3: Unser Datenrahmen wurde nun vorverarbeitet.

Unser Ziel (target) wird sein, ob der nächste Schlusskurs größer ist als der heutige Schlusskurs. Wenn der morgige Schlusskurs höher als der heutige Schlusskurs ist, wird unser Ziel 1 sein. Andernfalls wird unser Ziel 0 sein.

#We want to predict whether tomorrow's close will be greater than today's close
#We can encode a dummy variable for that: 
#1 means tomorrow's close will be greater.
#0 means today's close will be greater than tomorrow's.

data['target'] = (data['close'].shift(-1) > data['close']).astype(int)

data

#The first date is 2019-05-14, and the first close price is 9029.486, the close on the next day 2019-05-15 was 8944.461
#So therefore, on the first day, 2019-05-14, the correct forecast is 0 because the close price fell the following day.


Ziel-Kodierung

Abb. 4: Erstellen unseres Ziels

Als Nächstes definieren wir explizit unser Ziel und unsere Prädiktoren. Dann teilen wir unsere Daten in Trainings- und Testsätze auf. Beachten Sie, dass es sich um Zeitreihendaten handelt und wir daher nicht zufällig in 2 Gruppen aufteilen können.

#Seperating predictors and target
predictors = ['open','high','low','close','tick_volume','spread','ATRr_14','ATR Growth','RSI_14','RSI Stdv','mid_point','mid_point - 1','height']
target     = ['target']

#The training and testing split definition
train_start = 27
train_end = 1000

test_start = 1001

Nun erstellen wir die Trainings- und Testmengen.

#Train set
train_x = data.loc[train_start:train_end,predictors]
train_y = data.loc[train_start:train_end,target]

#Test set
test_x = data.loc[test_start:,predictors]
test_y = data.loc[test_start:,target]

Jetzt können wir unser Black-Box-Modell anpassen.

#Let us fit our model
black_box = XGBClassifier()
black_box.fit(train_x,train_y)

Schauen wir uns die Vorhersagen unseres Modells für den Testsatz an.

#Let's see our model predictions
black_box_predictions = pd.DataFrame(black_box.predict(test_x),index=test_x.index)

Lassen Sie uns die Genauigkeit unseres Modells bewerten.

#Assesing model prediction accuracy
black_box_score = precision_score(test_y,black_box_predictions)

#Model precision score
black_box_score

0.4594594594594595

Unser Modell ist zu 45 % genau. Welche Merkmale tragen dazu bei, dass wir dies erreichen, und welche nicht? Glücklicherweise verfügt XGBoost über eine eingebaute Funktion zur Messung der Bedeutung von Funktionen, die uns das Leben leichter macht. Dies ist jedoch spezifisch für diese Implementierung von XGBoost, und nicht alle Black-Boxen enthalten nützliche Funktionen, um die Bedeutung von Merkmalen auf diese Art und Weise zu zeigen.  Neuronale Netze und Support-Vektor-Maschinen haben zum Beispiel keine entsprechende Funktion. Sie müssen die Modellgewichte selbst nüchtern analysieren und sorgfältig interpretieren, um Ihr Modell besser zu verstehen. Die Funktion plot_importance in XGBoost ermöglicht es uns, einen Blick in unser Modell zu werfen.

plot_importance(black_box)

XGBoost-Merkmal Bedeutung

Abb. 5: Die Bedeutung der Funktion unseres XGBClassifier. Beachten Sie, dass die Tabelle keine Interaktionsterme enthält. Bedeutet das, dass es keine gibt? Nicht unbedingt!

Nachdem wir nun die Basiswahrheit festgelegt haben, wollen wir uns unsere erste Black-Box-Erklärungstechnik namens „Permutationsbedeutung“ ansehen. Mit der Permutationsbedeutung wird versucht, die Bedeutung jedes Merkmals zu schätzen, indem die Werte in jedem Merkmal zufällig gemischt werden und dann die Änderung der Verlustfunktion des Modells gemessen wird. Der Grund dafür ist, dass die Leistung eines Modells umso schlechter wird, je mehr es sich auf dieses Merkmal verlässt, wenn wir diese Werte zufällig mischen. Lassen Sie uns einige der Vor- und Nachteile der Permutationsbedeutung diskutieren

Vorteile

  1. Modellunabhängig: Die Permutationsbedeutung kann auf jedes Black-Box-Modell angewendet werden, ohne dass eine Vorverarbeitung des Modells oder der Permutationsbedeutungsfunktion erforderlich ist, was eine einfache Integration in Ihren bestehenden Arbeitsablauf für maschinelles Lernen ermöglicht. 
  2. Interpretierbarkeit: Die Ergebnisse der Permutationsbedeutung sind leicht zu interpretieren und werden unabhängig von dem zugrunde liegenden Modell, das bewertet wird, einheitlich interpretiert. Das macht es zu einem einfach zu bedienenden Werkzeug.
  3. Behandelt Nicht-Linearität: Die Permutationsbedeutung ist robust und eignet sich zur Erfassung nichtlinearer Beziehungen zwischen den Prädiktoren und der Antwort. 
  4. Behandelt Ausreißer: Die Permutationsbedeutung stützt sich nicht auf die Rohwerte der Prädiktoren, sondern befasst sich mit den Auswirkungen der Merkmale auf die Leistung des Modells. Dieser Ansatz macht ihn robust gegenüber Ausreißern, die in den Rohdaten enthalten sein können.

Nachteile

  1. Berechnungsaufwand: Bei großen Datensätzen mit vielen Merkmalen kann die Berechnung der Permutationsbedeutung sehr rechenintensiv sein, da wir jedes Merkmal iterieren, permutieren und das Modell bewerten müssen, um dann zum nächsten Merkmal überzugehen und den Vorgang zu wiederholen.
  2. Herausgefordert durch korrelierte Merkmale: Die Permutationsbedeutung kann zu verzerrten Ergebnissen führen, wenn Merkmale bewertet werden, die stark korreliert sind.
  3. Empfindlich gegenüber der Modellkomplexität: Obwohl die Permutationsbedeutung modellunabhängig ist, ist es möglich, dass ein zu komplexes Modell eine hohe Varianz aufweist, wenn seine Merkmale permutiert werden, was es schwierig macht, zuverlässige Schlussfolgerungen zu ziehen.
  4. Eigenständigkeit: Bei der Permutationsbedeutung wird davon ausgegangen, dass die Merkmale im Datensatz unabhängig sind und nach dem Zufallsprinzip ohne Folgen vertauscht werden können. Das macht die Berechnungen einfacher, aber in der realen Welt sind die meisten Merkmale voneinander abhängig und haben Wechselwirkungen, die von der Permutationsbedeutung nicht erfasst werden. 

Berechnen wir die Permutationswichtigkeit für unseren Black-Box-Klassifikator.

#Now let us observe the disagreement problem
black_box_pi = permutation_importance(black_box,train_x,train_y)

# Get feature importances and standard deviations
perm_importances = black_box_pi.importances_mean
perm_std = black_box_pi.importances_std

# Sort features based on importance
sorted_idx = perm_importances.argsort()

Stellen wir unsere berechneten Werte für die Bedeutung (importance) der Permutationen dar.

#We're going to utilize a bar histogram
plt.barh(range(train_x.shape[1]), perm_importances[sorted_idx], xerr=perm_std[sorted_idx])
plt.yticks(range(train_x.shape[1]), train_x.columns[sorted_idx])
plt.xlabel('Permutation Importance')
plt.title('Permutation Importances')
plt.show()

Bedeutung der Permutation

Abb. 6: Permutationsbedeutung für unsere Black-Box

Nach den Berechnungen des Permutationsbedeutungs-Algorithmus ist der ATR-Wert das informativste Merkmal, das wir entwickelt haben. Aber wir wissen aus der Praxis, dass das nicht der Fall ist, denn die ATR liegt auf dem sechsten Platz. Das ATR-Wachstum ist das wichtigste Merkmal! Das zweitwichtigste Merkmal war die Höhe, aber die Permutationsbedeutung ergab, dass das ATR-Wachstum wichtiger war. Das drittwichtigste Merkmal war der RSI-Wert, aber unsere Permutationsberechnung ergab, dass die Höhe wichtiger ist.

Das ist das Problem mit Black-Box-Erklärungstechniken, sie sind sehr gute Schätzungen der Merkmalsbedeutung, aber sie sind anfällig für Fehler, weil sie bestenfalls nur Schätzungen sind. Und nicht nur das, sie können auch bei der Bewertung ein und desselben Modells unterschiedlicher Meinung sein. Überzeugen wir uns selbst davon.

Wir werden den Algorithmus der gegenseitigen Information als zweite Black-Box-Erklärungstechnik verwenden. Die gegenseitige Information misst die Verringerung der Unsicherheit, die sich aus der Kenntnis des Wertes eines Merkmals ergibt.

#Let's see if our black-box explainers will disagree with each other by calculating mutual information
black_box_mi = mutual_info_classif(train_x,train_y)
black_box_mi = pd.Series(black_box_mi, name="MI Scores", index=train_x.columns)
black_box_mi = black_box_mi.sort_values(ascending=False)

black_box_mi

RSI_14:              0.014579

open:                0.010044

low:                  0.005544

mid_point - 1:    0.005514

close:                0.002428

tick_volume :    0.001402

high:                 0.000000

spread:             0.000000

ATRr_14:           0.000000

ATR Growth:     0.000000

RSI Stdv:          0.000000

mid_point:       0.000000

height:             0.000000

Name: MI Scores, dtype: float64

Wie Sie sehen können, haben wir sehr unterschiedliche Rangstufen der Bedeutung. Die gegenseitige Information ordnet die Merkmale in fast umgekehrter Reihenfolge im Vergleich zu unserer Basiswahrheit und der Berechnung der Permutationsbedeutung. Auf welchen Erklärer würden Sie sich mehr verlassen, wenn Sie nicht die Grundwahrheit hätten, die wir in diesem Beispiel haben?  Und was wäre, wenn Sie 5 verschiedene Erklärungsmethoden verwenden würden und jede von ihnen Ihnen eine andere Bedeutungseinstufung geben würde, was dann? Wählen Sie die Rankings aus, die mit Ihren Überzeugungen über die Funktionsweise der realen Welt übereinstimmen, öffnet dies die Tür zu einem weiteren Problem, dem so genannten Confirmation Bias. Bestätigungsvoreingenommenheit bedeutet, dass Sie alle Beweise ignorieren, die Ihren bestehenden Überzeugungen widersprechen. Sie versuchen aktiv, das zu bestätigen, was Sie für die Wahrheit halten, auch wenn es nicht wahr ist!

Die Vorteile von Glass-Box-Modellen

Glass-Box-Modelle sind ein perfekter Ersatz für Black-Box-Erklärungstechniken, da sie völlig transparent und sehr verständlich sind. Sie haben das Potenzial, das Problem der Uneinigkeit in vielen Bereichen zu lösen, auch in unserem Finanzbereich. Als ob das nicht schon Grund genug wäre, ist das Debuggen eines Glass-Box-Modells exponentiell einfacher als das Debuggen eines Black-Box-Modells mit demselben Maß an Flexibilität. Das spart unsere wichtigste Ressource, nämlich Zeit! Und das Beste daran ist, dass die Modellgenauigkeit nicht dadurch beeinträchtigt wird, dass es sich um eine Glass-Box handelt, sodass wir das Beste aus beiden Welten haben. Als Faustregel gilt, dass Black-Boxen nur in Szenarien eingesetzt werden sollten, in denen eine Glass-Box nicht den gleichen Grad an Genauigkeit erreichen kann. 

Nachdem das geklärt ist, wollen wir uns nun dem Bau unseres ersten Glass-Box-Modells zuwenden, seine Leistung analysieren und versuchen, seine Genauigkeit zu verbessern. Danach werden wir uns damit befassen, wie wir unser Glass-Box-Modell mit unserem MetaTrader 5 Terminal verbinden und den Handel mit Glass-Box-Modellen beginnen. Dann werden wir einen Expert Advisor erstellen, der unser Glass-Box-Modell mit MetaQuotes Language 5 unterstützt. Und schließlich werden wir unser Glass-Box-Modell in das Open Neural Network Exchange Format exportieren, damit wir das volle Potenzial von MetaTrader 5 und unserem Glass-Box-Modell ausschöpfen können.

Das Erstellern eines ersten Glass-Box-Modells mit Python ist einfach

Um den Code leicht lesbar zu halten, werden wir unsere Glass-Box in einem separaten Python-Skript erstellen, das sich von dem Python-Skript unterscheidet, das wir für die Erstellung des Black-Box-Modells verwendet haben. Daher werden wir diese Schritte nicht noch einmal durchgehen, sondern uns nur auf die Schritte konzentrieren, die für das Glass-Box-Modell typisch sind.

Um zu beginnen, müssen wir zunächst Interpret ML installieren

#Installing Interpret ML
pip install --upgrade interpret

Dann laden wir unsere Abhängigkeiten. In diesem Artikel werden wir uns auf 3 Module des Pakets interpret konzentrieren. Das erste ist das Glass-Box-Modell selbst, das zweite ist ein nützliches Modul, das es uns ermöglicht, in das Modell hineinzuschauen und diese Informationen in einem interaktiven GUI-Dashboard darzustellen, und das letzte Paket ermöglicht es uns, die Leistung unseres Modells in einem Diagramm zu visualisieren. Die anderen Pakete wurden bereits besprochen.

#Import MetaTrader5 package
import MetaTrader5 as mt5

#Import datetime for selecting data
from datetime import datetime

#Import matplotlib for plotting
import matplotlib.pyplot as plt

#Intepret glass-box model for classification
from interpret.glassbox import ExplainableBoostingClassifier

#Intepret GUI dashboard utility
from interpret import show

#Visualising our model's performance in one graph
from interpret.perf import ROC

#Pandas for handling data
import pandas as pd

#Pandas-ta for calculating technical indicators
import pandas_ta as ta

#Scoring metric to assess model accuracy
from sklearn.metrics import precision_score

Anschließend erstellen wir unsere Anmeldedaten und melden uns wie zuvor an unserem MT5-Terminal an. Dieser Schritt entfällt.

Wählen Sie dort das Symbol aus, das Sie modellieren möchten, wie wir es zuvor getan haben. Dieser Schritt entfällt.

Dann geben wir den Datumsbereich für die Daten an, die wir modellieren wollen, wie wir es zuvor getan haben. Dieser Schritt entfällt.

Dann können wir die historischen Daten abrufen, wie wir es zuvor getan haben. Dieser Schritt entfällt.

Von dort aus folgen wir den gleichen Vorverarbeitungsschritten wie oben beschrieben. Dieser Schritt entfällt.

Sobald die Daten vorverarbeitet sind, fügen wir unser Ziel wie zuvor hinzu. Dieser Schritt entfällt.

Anschließend führen wir unseren Zugtest wie zuvor durch. Dieser Schritt entfällt. Vergewissern Sie sich, dass die Aufteilung Ihres Zugversuchs nicht zufällig erfolgt. Behalten Sie die natürliche zeitliche Abfolge bei, da Ihre Ergebnisse sonst beeinträchtigt werden und ein zu optimistisches Bild der künftigen Leistung zeichnen.

Jetzt passen wir unser Glass-Box-Modell an.

#Let us fit our glass-box model
#Please note this step can take a while, depending on your computational resources
glass_box = ExplainableBoostingClassifier()
glass_box.fit(train_x,train_y)

Wir können nun einen Blick in unser Glass-Box-Modell werfen

#The show function provides an interactive GUI dashboard for us to interface with out model
#The explain_global() function helps us find what our model found important and allows us to identify potential bias or unintended flaws
show(glass_box.explain_global())


Globaler Zustand der Glass-Box

Abb. 7: Glass-Box Global State

Die Interpretation der zusammenfassenden Statistiken ist sehr wichtig. Doch bevor wir dazu kommen, wollen wir zunächst einige wichtige Begriffe klären. „Global Term“ oder „Global State“ fasst den Zustand des gesamten Modells zusammen. Sie gibt uns einen Überblick darüber, welche Merkmale das Modell informativ fand. Dies ist nicht zu verwechseln mit „Lokalbegriff“ oder „Lokalstaat“. Lokale Zustände werden verwendet, um einzelne Modellvorhersagen zu erklären, damit wir verstehen, warum das Modell die Vorhersage getroffen hat und welche Merkmale einzelne Vorhersagen beeinflusst haben.

Zurück zum globalen Zustand unseres Glass-Box-Modells. Wie wir sehen können, fand das Modell den verzögerten Mittelwert sehr informativ, was unseren Erwartungen entspricht. Darüber hinaus wurde auch ein möglicher Interaktionsterm zwischen dem ATR-Wachstum und dem verzögerten Midpoint-Wert gefunden. Die Höhe war das drittwichtigste Merkmal, gefolgt von einem Interaktionsterm zwischen dem Schlusskurs und der Höhe. Es ist zu beachten, dass wir keinerlei zusätzliche Hilfsmittel benötigen, um unser Glass-Box-Modell zu verstehen, wodurch das Problem der Uneinigkeit und des Bestätigungs-Bias vollständig gelöst wird. Die Informationen über den globalen Zustand sind für die Entwicklung von Merkmalen von unschätzbarem Wert, da sie uns zeigen, worauf wir unsere künftigen Bemühungen zur Entwicklung besserer Merkmale richten können. Schauen wir uns nun an, wie unsere Glass-Box abschneidet.

Gewinnung von Glass-Box Vorhersagen

#Obtaining glass-box predictions
glass_box_predictions = pd.DataFrame(glass_box.predict(test_x))

Jetzt messen wir die Genauigkeit der Glass-Box.

glass_box_score = precision_score(test_y,glass_box_predictions)

glass_box_score

0.49095022624434387

Unsere Glass-Box hat eine Genauigkeit von 49 %. Es ist klar, dass unser Explainable Boosting Classifier (Erklärbarer Boosting-Klassifikator) im Vergleich zu unserem XGBClassifier sein eigenes Gewicht in die Waagschale werfen kann. Dies zeigt, wie gut Glass-Box-Modelle sind, die uns eine hohe Genauigkeit bieten, ohne die Verständlichkeit zu beeinträchtigen.

Wir können auch individuelle Erklärungen für jede Vorhersage von unserem Glass-Box-Modell erhalten, um zu verstehen, welche Merkmale die Vorhersage auf einer granularen Ebene beeinflusst haben. Diese werden als lokale Erklärungen bezeichnet und können von unserem Explainable Boosting Classifier ganz einfach ermittelt werden.

#We can also obtain individual explanations for each prediction
show(glass_box.explain_local(test_x,test_y))

Lokale Erklärungen

Abb. 8: Lokale Erklärungen von unserem Explainable Boosting Classifier

Im ersten Dropdown-Menü können wir durch die einzelnen Vorhersagen blättern und die Vorhersage auswählen, die wir besser verstehen wollen. 

Von dort aus können wir die tatsächliche Klasse im Vergleich zur vorhergesagten Klasse sehen. In diesem Fall war die tatsächliche Klasse 0, d. h. der Schlusskurs ist gefallen, aber wir haben ihn als 1 eingestuft. Es werden auch die geschätzten Wahrscheinlichkeiten der einzelnen Klassen angezeigt. Wie wir sehen können, schätzte unser Modell die Wahrscheinlichkeit, dass die nächste Kerze höher schließen würde, mit 53 % falsch ein. Außerdem wird aufgeschlüsselt, welchen Beitrag die einzelnen Merkmale zur geschätzten Wahrscheinlichkeit geleistet haben. Die Merkmale in Blau tragen zur Vorhersage unseres Modells bei, und die Merkmale in Orange waren für die Vorhersage unseres Modells verantwortlich. Das bedeutet also, dass der RSI am meisten zu dieser Fehlklassifizierung beigetragen hat, aber der Interaktionsterm zwischen der Streuung und der Höhe hat uns in die richtige Richtung gewiesen.

Wir werden nun die Leistung unseres Modells mit einem einzigen Diagramm, einem Receiver Operating Characteristic (ROC), untersuchen. Anhand des ROC-Diagramms können wir die Leistung unseres Klassifikators auf einfache Weise bewerten. Wir befassen uns mit der Fläche unter der Kurve oder der AUC (area under the curve). Theoretisch hat ein perfekter Klassifikator eine Gesamtfläche unter der Kurve von 1. Dies macht es einfach, unseren Klassifikator mit nur einem Graphen zu bewerten.

glass_box_performance = ROC(glass_box.predict_proba).explain_perf(test_x,test_y, name='Glass Box')
show(glass_box_performance)

ROC-Diagramm

Abb. 9: Das ROC-Diagramm unseres Glass-Box-Modells

Unser Glass-Box-Modell hat eine AUC von 0,49. Mit dieser einfachen Metrik können wir die Leistung unseres Modells anhand von Einheiten bewerten, die für uns als Menschen interpretierbar sind. Außerdem ist die Kurve modellunabhängig und kann zum Vergleich verschiedener Klassifizierungsverfahren verwendet werden, unabhängig von den zugrunde liegenden Klassifizierungstechniken.

Anschließen des Glass-Box-Modells an das MT5-Terminal

Jetzt geht es ans Eingemachte. Wir werden unser Glass-Box-Modell mit unserem MT5-Terminal verbinden, indem wir zunächst den einfacheren Ansatz wählen. 

Verfolgen wir zunächst den Stand unserer Leistungsbilanz.

#Fetching account Info
account_info = mt5.account_info()

# getting specific account data
initial_balance = account_info.balance
initial_equity = account_info.equity

print('balance: ', initial_balance)
print('equity: ', initial_equity)

balance: 912.11 equity: 912.11

Holen wir uns alle Symbole.

symbols = mt5.symbols_get()

Lassen Sie uns einige globale Variablen einrichten.

#Trading global variables
#The symbol we want to trade
MARKET_SYMBOL = 'Boom 1000 Index'

#This data frame will store the most recent price update
last_close = pd.DataFrame()

#We may not always enter at the price we want, how much deviation can we tolerate?
DEVIATION = 100

#For demonstrational purposes we will always enter at the minimum volume
#However,we will not hardcode the minimum volume, we will fetch it dynamically
VOLUME = 0
#How many times the minimum volume should our positions be
LOT_MUTLIPLE = 1

#What timeframe are we working on?
TIMEFRAME = mt5.TIMEFRAME_D1

Wir wollen das Handelsvolumen nicht fest einprogrammieren, sondern das minimal zulässige Handelsvolumen dynamisch vom Broker abrufen und dann mit einem Faktor multiplizieren, um sicherzustellen, dass wir keine ungültigen Aufträge senden. In diesem Papier werden wir also unsere Auftragsgrößen im Verhältnis zum Mindestvolumen betrachten.

In unserem Fall eröffnen wir jeden Handel mit dem Mindestvolumen oder mit einem Faktor von 1.

for index,symbol in enumerate(symbols):
    if symbol.name == MARKET_SYMBOL:
        print(f"{symbol.name} has minimum volume: {symbol.volume_min}")
        VOLUME = symbol.volume_min * LOT_MULTIPLE

Der Boom 1000 Index hat ein Mindestvolumen: 0.2

Jetzt definieren wir eine Hilfsfunktion zum Öffnen von Handelsgeschäften.

# function to send a market order
def market_order(symbol, volume, order_type, **kwargs):
    #Fetching the current bid and ask prices
    tick = mt5.symbol_info_tick(symbol)
    
    #Creating a dictionary to keep track of order direction
    order_dict = {'buy': 0, 'sell': 1}
    price_dict = {'buy': tick.ask, 'sell': tick.bid}

    request = {
        "action": mt5.TRADE_ACTION_DEAL,
        "symbol": symbol,
        "volume": volume,
        "type": order_dict[order_type],
        "price": price_dict[order_type],
        "deviation": DEVIATION,
        "magic": 100,
        "comment": "Glass Box Market Order",
        "type_time": mt5.ORDER_TIME_GTC,
        "type_filling": mt5.ORDER_FILLING_FOK,
    }

    order_result = mt5.order_send(request)
    print(order_result)
    return order_result

Als Nächstes definieren wir eine Hilfsfunktion zum Schließen von Handelsgeschäften anhand der Ticketnummer.

# Closing our order based on ticket id
def close_order(ticket):
    positions = mt5.positions_get()

    for pos in positions:
        tick = mt5.symbol_info_tick(pos.symbol) #validating that the order is for this symbol
        type_dict = {0: 1, 1: 0}  # 0 represents buy, 1 represents sell - inverting order_type to close the position
        price_dict = {0: tick.ask, 1: tick.bid} #bid ask prices

        if pos.ticket == ticket:
            request = {
                "action": mt5.TRADE_ACTION_DEAL,
                "position": pos.ticket,
                "symbol": pos.symbol,
                "volume": pos.volume,
                "type": type_dict[pos.type],
                "price": price_dict[pos.type],
                "deviation": DEVIATION,
                "magic": 100,
                "comment": "Glass Box Close Order",
                "type_time": mt5.ORDER_TIME_GTC,
                "type_filling": mt5.ORDER_FILLING_FOK,
            }

            order_result = mt5.order_send(request)
            print(order_result)
            return order_result

    return 'Ticket does not exist'

Da wir nicht ständig viele Daten vom Server abfragen müssen, aktualisieren wir auch unseren Datumsbereich.

#Update our date from and date to
date_from = datetime(2023,11,1)
date_to = datetime.now()

Wir brauchen auch eine Funktion, um eine Prognose von unserem Glass-Box-Modell zu erhalten und die Prognose als Handelssignale zu verwenden.

#Get signals from our glass-box model
def ai_signal():
    #Fetch OHLC data
    df = pd.DataFrame(mt5.copy_rates_range(market_symbol,TIMEFRAME,date_from,date_to))
    #Process the data
    df['time'] = pd.to_datetime(df['time'],unit='s')
    df['target'] = (df['close'].shift(-1) > df['close']).astype(int)
    preprocess(df)
    #Select the last row
    last_close = df.iloc[-1:,1:]
    #Remove the target column
    last_close.pop('target')
    #Use the last row to generate a forecast from our glass-box model
    #Remember 1 means buy and 0 means sell
    forecast = glass_box.predict(last_close)
    return forecast[0]

Jetzt definieren wir den Hauptteil unseres Python-Glass-Box-Handelsbots

#Now we define the main body of our Python Glass-box Trading Bot
if __name__ == '__main__':
    #We'll use an infinite loop to keep the program running
    while True:
        #Fetching model prediction
        signal = ai_signal()
        
        #Decoding model prediction into an action
        if signal == 1:
            direction = 'buy'
        elif signal == 0:
            direction = 'sell'
        
        print(f'AI Forecast: {direction}')
        
        #Opening A Buy Trade
        #But first we need to ensure there are no opposite trades open on the same symbol
        if direction == 'buy':
            #Close any sell positions
            for pos in mt5.positions_get():
                if pos.type == 1:
                    #This is an open sell order, and we need to close it
                    close_order(pos.ticket)
            
            if not mt5.positions_totoal():
                #We have no open positions
                market_order(MARKET_SYMBOL,VOLUME,direction)
        
        #Opening A Sell Trade
        elif direction == 'sell':
            #Close any buy positions
            for pos in mt5.positions_get():
                if pos.type == 0:
                    #This is an open buy order, and we need to close it
                    close_order(pos.ticket)
            
            if not mt5.positions_get():
                #We have no open positions
                market_order(MARKET_SYMBOL,VOLUME,direction)
        
        print('time: ', datetime.now())
        print('-------\n')
        time.sleep(60)

AI-Prognose: verkaufen

Zeit:  2023-12-04 15:31:31.569495

-------

Glass-Box-Handelsbot

Abb. 10: Unser in Python gebaute Glass-Box-Handelsroboter erwirtschaftet einen Gewinn.

Aufbau eines Expert Advisors zur Unterstützung des Glass-Box-Modells

Wir gehen nun dazu über, einen Assistenten für unser Glass-Box-Modell mit MQL5 zu erstellen. Wir möchten einen EA erstellen, der unseren Stop-Loss (SL) und Take-Profit (TP) basierend auf einem ATR-Wert verschiebt. Der nachstehende Code aktualisiert unsere TP- und SL-Werte bei jedem Tick. Die Durchführung dieser Aufgabe mit dem Python-Integrationsmodul wäre ein Alptraum, es sei denn, wir aktualisieren in geringeren Intervallen, z. B. pro Minute oder pro Stunde. Wir wollen straff geführt werden und unsere SL und TP bei jedem Tick aktualisieren, alles andere wird unseren strengen Anforderungen nicht gerecht. Wir benötigen zwei Eingaben von unserem Nutzer, die angeben, wie groß der Abstand zwischen dem Einstieg und dem SL/TP sein soll. Wir multiplizieren den ATR-Wert mit der Nutzereingabe, um die Höhe vom SL oder TP bis zum Einstiegspunkt zu berechnen. Und die zweite Eingabe ist einfach die Periode des ATR.

//Meta Properties 
#property copyright "Gamuchirai Ndawana"
#property link "https://twitter.com/Westwood267"

//Classes for managing Trades And Orders
#include  <Trade\Trade.mqh>
#include <Trade\OrderInfo.mqh>

//Instatiating the trade class and order manager
CTrade trade;
class COrderInfo;

//Input variables
input double atr_multiple =0.025;  //How many times the ATR should the SL & TP be?
input int atr_period = 200;      //ATR Period

//Global variables
double ask, bid,atr_stop; //We will use these variables to determine where we should place our ATR
double atr_reading[];     //We will store our ATR readings in this arrays
int    atr;               //This will be our indicator handle for our ATR indicator
int min_volume;

int OnInit(){     
                  //Check if we are authorized to use an EA on the terminal
                  if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)){
                           Comment("Press Ctrl + E To Give The Robot Permission To Trade And Reload The Program");
                           //Remove the EA from the terminal
                           ExpertRemove();
                           return(INIT_FAILED);
                  }
                  
                  //Check if we are authorized to use an EA on the terminal
                  else if(!MQLInfoInteger(MQL_TRADE_ALLOWED)){
                            Comment("Reload The Program And Make Sure You Clicked Allow Algo Trading");
                            //Remove the EA from the terminal
                            ExpertRemove();
                            return(INIT_FAILED);
                  }
                  
                  //If we arrive here then we are allowed to trade using an EA on the Terminal                
                  else{
                        //Symbol information
                        //The smallest distance between our point of entry and the stop loss
                        min_volume = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);//SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN)
                        //Setting up our ATR indicator
                        atr = iATR(_Symbol,PERIOD_CURRENT,atr_period);
                        return(INIT_SUCCEEDED);
                  }                       
}

void OnDeinit(const int reason){

}

void OnTick(){
               //Get the current ask
               ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
               //Get the current bid
               bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
               //Copy the ATR reading our array for storing the ATR value
               CopyBuffer(atr,0,0,1,atr_reading);
               //Set the array as series so the natural time ordering is preserved
               ArraySetAsSeries(atr_reading,true); 
               
               //Calculating where to position our stop loss
               //For now we'll keep it simple, we'll add the minimum volume and the current ATR reading and multiply it by the ATR multiple
               atr_stop = ((min_volume + atr_reading[0]) * atr_multiple);

               //If we have open positions we should adjust the stop loss and take profit 
               if(PositionsTotal() > 0){
                        check_atr_stop();          
               }
}

//--- Functions
//This funciton will update our S/L & T/P based on our ATR reading
void check_atr_stop(){
      
      //First we iterate over the total number of open positions                      
      for(int i = PositionsTotal() -1; i >= 0; i--){
      
            //Then we fetch the name of the symbol of the open position
            string symbol = PositionGetSymbol(i);
            
            //Before going any furhter we need to ensure that the symbol of the position matches the symbol we're trading
                  if(_Symbol == symbol){
                           //Now we get information about the position
                           ulong ticket = PositionGetInteger(POSITION_TICKET); //Position Ticket
                           double position_price = PositionGetDouble(POSITION_PRICE_OPEN); //Position Open Price
                           double type = PositionGetInteger(POSITION_TYPE); //Position Type
                           double current_stop_loss = PositionGetDouble(POSITION_SL); //Current Stop loss value
                           
                           //If the position is a buy
                           if(type == POSITION_TYPE_BUY){
                                  //The new stop loss value is just the ask price minus the ATR stop we calculated above
                                  double atr_stop_loss = (ask - (atr_stop));
                                  //The new take profit is just the ask price plus the ATR stop we calculated above
                                  double atr_take_profit = (ask + (atr_stop));
                                  
                                  //If our current stop loss is less than our calculated ATR stop loss 
                                  //Or if our current stop loss is 0 then we will modify the stop loss and take profit
                                 if((current_stop_loss < atr_stop_loss) || (current_stop_loss == 0)){
                                       trade.PositionModify(ticket,atr_stop_loss,atr_take_profit);
                                 }  
                           }
                           
                            //If the position is a sell
                           else if(type == POSITION_TYPE_SELL){
                                     //The new stop loss value is just the bid price plus the ATR stop we calculated above
                                     double atr_stop_loss = (bid + (atr_stop));
                                     //The new take profit is just the bid price minus the ATR stop we calculated above
                                     double atr_take_profit = (bid - (atr_stop));
                                     
                                 //If our current stop loss is greater than our calculated ATR stop loss 
                                 //Or if our current stop loss is 0 then we will modify the stop loss and take profit 
                                 if((current_stop_loss > atr_stop_loss) || (current_stop_loss == 0)){
                                       trade.PositionModify(ticket,atr_stop_loss,atr_take_profit);
                                 }
                           }  
                  }  
            }
}

EA mit unserer Glass-Box

Abb. 11: Unser EA arbeitet Hand in Hand mit unserem Glass-Box Modell

Exportieren wir unser Glass-Box Modells in das Open Neural Network Exchange (ONNX) Format


ONNX-Logo

Abb. 12: Das Logo der offenen Börse für neuronale Netze

Open Neural Network Exchange (ONNX) ist ein Open-Source-Protokoll zur Darstellung beliebiger Modelle für maschinelles Lernen. Sie wird in großem Umfang von Unternehmen aus der ganzen Welt und aus verschiedenen Branchen unterstützt und gepflegt. Unternehmen wie Microsoft, Facebook, MATLAB, IBM, Qualcomm, Huawei, Intel, AMD, um nur einige zu nennen. Zum Zeitpunkt der Erstellung dieses Dokuments ist ONNX die universelle Standardform für die Darstellung jedes maschinellen Lernmodells, unabhängig davon, in welchem Framework es entwickelt wurde, und ermöglicht darüber hinaus die Entwicklung und den Einsatz von maschinellen Lernmodellen in verschiedenen Programmiersprachen und Umgebungen. Falls Sie sich fragen, wie das möglich ist: Der Kerngedanke ist, dass jedes Modell für maschinelles Lernen als ein Graph mit Knoten und Kanten dargestellt werden kann. Jeder Knoten steht für eine mathematische Operation, und jede Kante stellt den Datenfluss dar. Mit dieser einfachen Darstellung können wir jedes Modell des maschinellen Lernens darstellen, unabhängig von dem Framework, in dem es erstellt wurde.

Sobald wir ein ONNX-Modell haben, benötigen wir die Engine, die ONNX-Modelle ausführt. Dies ist die Aufgabe der ONNX Runtime. Die ONNX Runtime ist für die effiziente Ausführung und Bereitstellung von ONNX-Modellen auf einer Vielzahl von Geräten verantwortlich, vom Supercomputer in einem Rechenzentrum bis zum Mobiltelefon in Ihrer Tasche und allem, was dazwischen liegt.

In unserem Fall ermöglicht ONNX die Integration unseres maschinellen Lernmodells in unseren Expert Advisor und damit den Aufbau eines Advisors mit eigenem Gehirn. Das MetaTrader 5-Terminal stellt uns eine Reihe von Tools zur Verfügung, mit denen wir unseren Advisor sicher und zuverlässig anhand historischer Daten testen können, oder noch besser, um Walk-Forward-Tests durchzuführen, was die empfohlene Methode zum Testen jedes Expert Advisors ist. Beim Walk-Forward-Testing wird der Expert Advisor einfach in Echtzeit oder über einen beliebigen Zeitraum, der vor dem letzten Trainingsdatum des Modells liegt, ausgeführt. Dies ist der beste Test für die Robustheit unseres Modells im Umgang mit Daten, die es beim Training noch nicht gesehen hat, und verhindert außerdem, dass wir uns selbst etwas vormachen, indem wir unser Modell mit Daten zurücktesten, mit denen es trainiert wurde.

Wie zuvor werden wir den Code für den Export unseres ONNX-Modells vom restlichen Code, den wir bisher in diesem Artikel verwendet haben, trennen, damit der Code leicht lesbar bleibt. Außerdem werden wir die Anzahl der Parameter, die unser Modell als Eingabe benötigt, reduzieren, um seine praktische Umsetzung zu vereinfachen. Wir haben nur die folgenden Merkmale als Inputs für unser ONNX-Modell ausgewählt:

1. Verzögerte Höhe: Denken Sie daran, dass die Höhe in unserem Fall definiert ist als: (((High + Low) / 2) - Close), d.h. die verzögerte Höhe ist der vorherige Wert der Höhe.

2. Wachstum der Höhe: Das Höhenwachstum dient als Schätzung der zweiten Ableitung der Höhenmesswerte. Dies wird erreicht, indem die Differenz zwischen aufeinander folgenden historischen Höhenwerten zweimal ermittelt wird. Der resultierende Wert gibt Aufschluss über die Geschwindigkeit, mit der sich die Höhe verändert. Einfacher ausgedrückt, hilft sie uns zu verstehen, ob die Höhe im Laufe der Zeit ein beschleunigtes oder ein verlangsamtes Wachstum erfährt.

3. Mittelpunkt: Denken Sie daran, dass der Mittelwert in unserem Fall wie folgt definiert ist: ((Hoch + Tief) / 2)

4. Wachstum des Mittelpunktes: Das Wachstum des Mittelpunkts ist ein abgeleitetes Merkmal, das die zweite Ableitung der Mittelpunktsmesswerte darstellt. Dies wird erreicht, indem die Differenz zwischen aufeinanderfolgenden historischen Mittelwerten zweimal genommen wird. Der daraus resultierende Wert gibt Aufschluss über die Geschwindigkeit, mit der sich der Mittelpunkt verändert. Sie zeigt insbesondere an, ob der Mittelpunkt ein beschleunigtes oder ein verlangsamtes Wachstum erfährt. Einfacher und weniger technisch ausgedrückt, hilft sie uns zu verstehen, ob sich der Mittelpunkt mit zunehmender Geschwindigkeit von Null entfernt oder sich mit einer immer schneller werdenden Geschwindigkeit Null nähert.

Außerdem sollte der Leser wissen, dass wir die Symbole geändert haben. In der ersten Hälfte des Artikels haben wir das Symbol „Boom 1000 Index“ modelliert, und jetzt werden wir das Symbol „Volatility 75 Index“ modellieren.

Unser Expert Advisor wird auch automatisch SL/TP-Positionen dynamisch platzieren, indem er den ATR-Wert verwendet, wie wir es zuvor gesehen haben, und darüber hinaus werden wir ihm die Möglichkeit geben, automatisch eine weitere Position hinzuzufügen, sobald unsere Gewinne eine bestimmte Schwelle überschreiten.

Die meisten Importe bleiben unverändert, mit Ausnahme von 2 neuen Importen, ONNX und ebm2onnx. Mit diesen beiden Paketen können wir unsere Explainable Boosting Machine in das ONNX-Format konvertieren. 

#Import MetaTrader5 package
import MetaTrader5 as mt5

#Import datetime for selecting data
from datetime import datetime

#Keeping track of time
import time

#Import matplotlib
import matplotlib.pyplot as plt

#Intepret glass-box model
from interpret.glassbox import ExplainableBoostingClassifier

#Intepret GUI dashboard utility
from interpret import show

#Pandas for handling data
import pandas as pd

#Pandas-ta for calculating technical indicators
import pandas_ta as ta

#Scoring metric to assess model accuracy
from sklearn.metrics import precision_score

#ONNX
import onnx

#Import ebm2onnx
import ebm2onnx

#Path handling
from sys import argv

Von dort aus wiederholen wir dieselben Schritte wie oben beschrieben, um uns anzumelden und Daten abzurufen. Der einzige Unterschied besteht in den Schritten, die wir zur Vorbereitung unserer nutzerdefinierten Funktionen unternehmen.

#Let's create a function to preprocess our data
def preprocess(data):
    data['mid_point'] = ((data['high'] + data['low']) / 2)

    data['mid_point_growth'] = data['mid_point'].diff().diff()

    data['mid_point_growth_lag'] = data['mid_point_growth'].shift(1)

    data['height'] = (data['mid_point'] - data['close'])

    data['height - 1'] = data['height'].shift(1)

    data['height_growth'] = data['height'].diff().diff()
    
    data['height_growth_lag'] = data['height_growth'].shift(1)
    
    data['time'] = pd.to_datetime(data['time'],unit='s')
    
    data.dropna(axis=0,inplace=True)
    
    data['target'] = (data['close'].shift(-1) > data['close']).astype(int)

Nach der Datenerfassung sind die Schritte zur Aufteilung der Daten in einen Trainings- und einen Testsatz sowie die Schritte zur Anpassung des Glass-Box-Modells dieselben.

Wenn Sie Ihr Glass-Box-Modell angepasst haben, können wir nun mit dem Export in das ONNX-Format fortfahren.

Zunächst müssen wir den Pfad angeben, in dem wir das Modell speichern wollen. Jede Installation von MetaTrader 5 erstellt einen speziellen Ordner für Dateien, die in Ihrem Terminal verwendet werden können. Wir können den absoluten Pfad sehr einfach mit der Python-Bibliothek abrufen.

terminal_info=mt5.terminal_info()
print(terminal_info)
TerminalInfo(community_account=False, community_connection=False, connected=True, dlls_allowed=False, trade_allowed=True, tradeapi_disabled=False, email_enabled=False, ftp_enabled=False, notifications_enabled=False, mqid=True, build=4094, maxbars=100000, codepage=0, ping_last=222088, community_balance=0.0, retransmission=0.030435223698894183, company='MetaQuotes Software Corp.', name='MetaTrader 5', language='English', path='C:\\Program Files\\MetaTrader 5', data_path='C:\\Users\\Westwood\\AppData\\Roaming\\MetaQuotes\\Terminal\\D0E8209F77C8CF37AD8BF550E51FF075', commondata_path='C:\\Users\\Westwood\\AppData\\Roaming\\MetaQuotes\\Terminal\\Common')

Der gesuchte Pfad ist als „Datenpfad“ in dem oben erstellten terminal_info-Objekt gespeichert.

file_path=terminal_info.data_path+"\\MQL5\\Files\\"
print(file_path)

C:\Users\Westwood\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Files\

Der Code nimmt den Dateipfad auf, den wir von unserem Terminal erhalten haben, und isoliert das Verzeichnis des Pfades, indem er alle Dateinamen ausschließt.

data_path=argv[0]
last_index=data_path.rfind("\\")+1
data_path=data_path[0:last_index]
print("data path to save onnx model",data_path)

Datenpfad zum Speichern des Onnx-Modells C:\user\Westwood\AppData\Lokal\Programme\Python\Python311\Lib\site-packages\

Von dort aus verwenden wir das ebm2onnx-Paket, um unser Glass-Box-Modell für die Konvertierung in das ONNX-Format vorzubereiten. Beachten Sie, dass wir die Datentypen für jede unserer Eingaben explizit angeben müssen. Wir ziehen es vor, dies dynamisch mit der Funktion ebm2onnx.get_dtype_from_pandas zu tun, und wir übergeben ihr den zuvor verwendeten Trainingsdatenrahmen. 

onnx_model = ebm2onnx.to_onnx(glass_box,ebm2onnx.get_dtype_from_pandas(train_x))
#Save the ONNX model in python
output_path = data_path+"Volatility_75_EBM.onnx"
onnx.save_model(onnx_model,output_path)
#Save the ONNX model as a file to be imported in our MetaEditor
output_path = file_path+"Volatility_75_EBM.onnx"
onnx.save_model(onnx_model,output_path)

Wir sind nun bereit, mit unserer ONNX-Datei in unserem MetaEditor 5 zu arbeiten. MetaEditor ist eine integrierte Entwicklungsumgebung zum Schreiben von Code in der MetaQuotes-Sprache. 

Wenn wir zum ersten Mal unsere integrierte Entwicklungsumgebung MetaEditor 5 öffnen und auf den „Volatility Doctor 75 EBM“ doppelklicken, sehen wir das Folgende:

Erstmaliges Öffnen unseres ONNX-Modells

Abb. 13: Die Inputs und Outputs unseres ONNX-Modells.


Wir werden nun einen Expert Advisor erstellen und unser ONNX-Modell importieren.

Wir beginnen mit der Angabe von allgemeinen Dateiinformationen.

//+------------------------------------------------------------------+
//|                                                         ONNX.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
//Meta properties
#property copyright "Gamuchirai Zororo Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

Hier müssen wir einige globale Variablen deklarieren.

//Trade Library
#include <Trade\Trade.mqh>           //We will use this library to modify our positions

//Global variables
//Input variables
input double atr_multiple =0.025;    //How many times the ATR should the SL & TP be?
int input lot_mutliple = 1;          //How many time greater than minimum lot should we enter?
const int atr_period = 200;          //ATR Period

//Trading variables
double ask, bid,atr_stop;            //We will use these variables to determine where we should place our ATR
double atr_reading[];                //We will store our ATR readings in this arrays
int    atr;                          //This will be our indicator handle for our ATR indicator
long min_distance;                   //The smallest distance allowed between our entry position and the stop loss
double min_volume;                   //The smallest contract size allowed by the broker
static double initial_balance;       //Our initial trading balance at the beginning of the trading session
double current_balance;              //Our trading balance at every instance of trading
long     ExtHandle = INVALID_HANDLE; //This will be our model's handler
int      ExtPredictedClass = -1;     //This is where we will store our model's forecast
CTrade   ExtTrade;                   //This is the object we will call to open and modify our positions

//Reading our ONNX model and storing it into a data array
#resource "\\Files\\Volatility_75_EBM.onnx" as uchar ExtModel[] //This is our ONNX file being read into our expert advisor

//Custom keyword definitions
#define  PRICE_UP 1
#define  PRICE_DOWN 0

Jetzt beginnen wir hier mit der Funktion OnInit() Wir verwenden die Funktion OnInit, um unser ONNX-Modell einzurichten. Um ein ONNX-Modell einzurichten, müssen wir lediglich 3 einfache Schritte ausführen. Zunächst erstellen wir das ONNX-Modell aus dem Puffer, den wir oben in unseren globalen Variablen verwendet haben, als wir das ONNX-Modell als Ressource benötigten. Nach dem Einlesen müssen wir die Form jeder einzelnen Eingabe und dann die Form jeder einzelnen Ausgabe festlegen. Danach überprüfen wir, ob beim Versuch, die Eingabe- und Ausgabeform festzulegen, Fehler aufgetreten sind. Wenn alles gut gelaufen ist, holen wir uns auch das von unserem Broker erlaubte Mindestkontraktvolumen, den Mindestabstand zwischen dem Stop-Loss und der Einstiegsposition und wir richten unseren ATR-Indikator ein.

int OnInit()
  {
   //Check if the symbol and time frame conform to training conditions
   if(_Symbol != "Volatility 75 Index" || _Period != PERIOD_M1)
       {
            Comment("Model must be used with the Volatility 75 Index on the 1 Minute Chart");
            return(INIT_FAILED);
       }
    
    //Create an ONNX model from our data array
    ExtHandle = OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT);
    Print("ONNX Create from buffer status ",ExtHandle);
    
    //Checking if the handle is valid
    if(ExtHandle == INVALID_HANDLE)
      {
            Comment("ONNX create from buffer error ", GetLastError());
            return(INIT_FAILED);
      }
   
   //Set input shape
   long input_count = OnnxGetInputCount(ExtHandle);   
   const long input_shape[] = {1};
   Print("Total model inputs : ",input_count);
   
   //Setting the input shape of each input
   OnnxSetInputShape(ExtHandle,0,input_shape);
   OnnxSetInputShape(ExtHandle,1,input_shape);
   OnnxSetInputShape(ExtHandle,2,input_shape);
   OnnxSetInputShape(ExtHandle,3,input_shape);
   
   //Check if anything went wrong when setting the input shape
   if(!OnnxSetInputShape(ExtHandle,0,input_shape) || !OnnxSetInputShape(ExtHandle,1,input_shape) || !OnnxSetInputShape(ExtHandle,2,input_shape) || !OnnxSetInputShape(ExtHandle,3,input_shape))
      {
            Comment("ONNX set input shape error ", GetLastError());
            OnnxRelease(ExtHandle);
            return(INIT_FAILED);
      }
      
   //Set output shape
   long output_count = OnnxGetOutputCount(ExtHandle);
   const long output_shape[] = {1};
   Print("Total model outputs : ",output_count);
   //Setting the shape of each output
   OnnxSetOutputShape(ExtHandle,0,output_shape);
   //Checking if anything went wrong when setting the output shape
   if(!OnnxSetOutputShape(ExtHandle,0,output_shape))
      {
            Comment("ONNX set output shape error ", GetLastError());
            OnnxRelease(ExtHandle);
            return(INIT_FAILED);
      }
    //Get the minimum trading volume allowed  
    min_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);  
    //Symbol information
    //The smallest distance between our point of entry and the stop loss
    min_distance = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
    //Initial account balance
    initial_balance = AccountInfoDouble(ACCOUNT_BALANCE);
    //Setting up our ATR indicator
    atr = iATR(_Symbol,PERIOD_CURRENT,atr_period);
    return(INIT_SUCCEEDED);
//---
  }

Die DeInit-Funktion ist sehr einfach, sie entfernt den ONNX-Handler, damit wir keine Ressourcen binden, die wir nicht brauchen.

void OnDeinit(const int reason)
  {
//---
   if(ExtHandle != INVALID_HANDLE)
      {
         OnnxRelease(ExtHandle);
         ExtHandle = INVALID_HANDLE;
      }
  }

Die Funktion OnTick ist das Herzstück unseres Expert Advisors. Sie wird jedes Mal aufgerufen, wenn wir einen neuen Tick von unserem Broker erhalten. In unserem Fall fangen wir damit an, die Zeit zu verfolgen, was uns erlaubt, die Prozesse zu trennen, die wir bei jedem Tick ausführen wollen, und die Prozesse, die wir ausführen wollen, wenn sich eine neue Kerze gebildet hat. Wir wollen unsere Geld- und Briefkurse jeden Tick aktualisieren, wir wollen auch unsere Take-Profit- und Stop-Loss-Positionen bei jedem Tick aktualisieren, aber wir wollen nur dann eine Modellvorhersage machen, wenn sich eine neue Kerze gebildet hat, wenn wir keine offenen Positionen haben.

void OnTick()
  {
//---
   //Time trackers
   static datetime time_stamp;
   datetime time = iTime(_Symbol,PERIOD_M1,0);

   //Current bid price
   bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
   //Current ask price
   ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   
   //Copy the ATR reading our array for storing the ATR value
   CopyBuffer(atr,0,0,1,atr_reading);
   
   //Set the array as series so the natural time ordering is preserved
   ArraySetAsSeries(atr_reading,true); 
   
   //Calculating where to position our stop loss
   //For now we'll keep it simple, we'll add the minimum volume and the current ATR reading and multiply it by the ATR multiple
   atr_stop = ((min_distance + atr_reading[0]) * atr_multiple);
   
   //Current Session Profit and Loss Position
   current_balance = AccountInfoDouble(ACCOUNT_BALANCE);
   Comment("Current Session P/L: ",current_balance - initial_balance);
   
   //If we have a position open we need to update our stoploss
   if(PositionsTotal() > 0){
        check_atr_stop();          
   }
   
    //Check new bar
     if(time_stamp != time)
      {
         time_stamp = time;
         
         //If we have no open positions let's make a forecast and open a new position
         if(PositionsTotal() == 0){
            Print("No open positions making a forecast");
            PredictedPrice();
            CheckForOpen();
         }
      }
   
  }
Von dort aus definieren wir die Funktion, die unsere ATR-Take-Profit- und Stop-Loss-Position aktualisiert. Die Funktion durchläuft jede Position, die wir geöffnet haben, und prüft, ob die Position mit dem Symbol übereinstimmt, mit dem wir gerade handeln. Wenn dies der Fall ist, holt er weitere Informationen über die Position ein und passt den Stop-Loss und den Take-Profit der Position entsprechend an, je nach Richtung der Position. Beachten Sie, dass, wenn sich der Handel gegen unsere Position bewegt, Take-Profit und Stop-Loss unverändert bleiben.
//--- Functions
//This function will update our S/L & T/P based on our ATR reading
void check_atr_stop(){
      
      //First we iterate over the total number of open positions                      
      for(int i = PositionsTotal() -1; i >= 0; i--){
      
            //Then we fetch the name of the symbol of the open position
            string symbol = PositionGetSymbol(i);
            
            //Before going any further we need to ensure that the symbol of the position matches the symbol we're trading
                  if(_Symbol == symbol){
                           //Now we get information about the position
                           ulong ticket = PositionGetInteger(POSITION_TICKET); //Position Ticket
                           double position_price = PositionGetDouble(POSITION_PRICE_OPEN); //Position Open Price
                           long type = PositionGetInteger(POSITION_TYPE); //Position Type
                           double current_stop_loss = PositionGetDouble(POSITION_SL); //Current Stop loss value
                           
                           //If the position is a buy
                           if(type == POSITION_TYPE_BUY){
                                  //The new stop loss value is just the ask price minus the ATR stop we calculated above
                                  double atr_stop_loss = (ask - (atr_stop));
                                  //The new take profit is just the ask price plus the ATR stop we calculated above
                                  double atr_take_profit = (ask + (atr_stop));
                                  
                                  //If our current stop loss is less than our calculated ATR stop loss 
                                  //Or if our current stop loss is 0 then we will modify the stop loss and take profit
                                 if((current_stop_loss < atr_stop_loss) || (current_stop_loss == 0)){
                                       ExtTrade.PositionModify(ticket,atr_stop_loss,atr_take_profit);
                                 }  
                           }
                           
                            //If the position is a sell
                           else if(type == POSITION_TYPE_SELL){
                                     //The new stop loss value is just the bid price plus the ATR stop we calculated above
                                     double atr_stop_loss = (bid + (atr_stop));
                                     //The new take profit is just the bid price minus the ATR stop we calculated above
                                     double atr_take_profit = (bid - (atr_stop));
                                     
                                 //If our current stop loss is greater than our calculated ATR stop loss 
                                 //Or if our current stop loss is 0 then we will modify the stop loss and take profit 
                                 if((current_stop_loss > atr_stop_loss) || (current_stop_loss == 0)){
                                       ExtTrade.PositionModify(ticket,atr_stop_loss,atr_take_profit);
                                 }
                           }  
                  }  
            }
}
Wir brauchen auch eine weitere Funktion, um eine neue Position zu eröffnen. Beachten Sie, dass wir die oben deklarierten globalen Bid- und Ask-Variablen verwenden. Dadurch wird sichergestellt, dass das gesamte Programm den gleichen Preis hat. Außerdem setzen wir den Stop-Loss und den Take-Profit auf 0, da diese von unserer Funktion check_atr_stop verwaltet werden.
void CheckForOpen(void)
   {
      ENUM_ORDER_TYPE signal = WRONG_VALUE;
      
      //Check signals
      if(ExtPredictedClass == PRICE_DOWN)
         {
            signal = ORDER_TYPE_SELL;
         }
      else if(ExtPredictedClass == PRICE_UP)
         {
            signal = ORDER_TYPE_BUY;
         }
         
      if(signal != WRONG_VALUE && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
         {
            double price, sl = 0 , tp = 0;
            
            if(signal == ORDER_TYPE_SELL)
               {
                  price = bid;
               }
               
           else
               {
                  price = ask;
               }
               
            Print("Opening a new position: ",signal);  
            ExtTrade.PositionOpen(_Symbol,signal,min_volume,price,0,0,"ONNX Order");
         }
   }
   

Schließlich benötigen wir eine Funktion, um mit unserem ONNX-Modell innerhalb unseres Expert Advisors Vorhersagen zu treffen. Die Funktion wird auch für die Vorverarbeitung unserer Daten in der gleichen Weise verantwortlich sein, wie sie beim Training vorverarbeitet wurde. Es kann nicht oft genug betont werden, dass darauf geachtet werden muss, dass die Daten sowohl in der Ausbildung als auch in der Produktion auf einheitliche Weise verarbeitet werden. Beachten Sie, dass jede Eingabe für das Modell in einem eigenen Vektor gespeichert wird. Jeder Vektor wird dann in der gleichen Reihenfolge an die Funktion ONNX Run übergeben, in der er beim Training an das Modell übergeben wurde. Es ist von größter Bedeutung, dass wir die Konsistenz im gesamten Projekt aufrechterhalten, da sonst Laufzeitfehler auftreten können, die beim Kompilieren unseres Modells keine Ausnahmen auslösen. Stellen Sie sicher, dass der Datentyp jedes Eingabevektors mit dem Eingabetyp übereinstimmt, den das Modell erwartet, und dass der Ausgabetyp mit dem Ausgabetyp des Modells übereinstimmt.

void PredictedPrice(void)
   {
      long output_data[] = {1};
      
      double lag_2_open = double(iOpen(_Symbol,PERIOD_M1,3));
      double lag_2_high = double(iOpen(_Symbol,PERIOD_M1,3));
      double lag_2_close = double(iClose(_Symbol,PERIOD_M1,3));
      double lag_2_low = double(iLow(_Symbol,PERIOD_M1,3));
      double lag_2_mid_point = double((lag_2_high + lag_2_low) / 2);
      double lag_2_height = double(( lag_2_mid_point - lag_2_close));
      
      double lag_open = double(iOpen(_Symbol,PERIOD_M1,2));
      double lag_high = double(iOpen(_Symbol,PERIOD_M1,2));
      double lag_close = double(iClose(_Symbol,PERIOD_M1,2));
      double lag_low = double(iLow(_Symbol,PERIOD_M1,2));
      double lag_mid_point = double((lag_high + lag_low) / 2);
      double lag_height = double(( lag_mid_point - lag_close));
      
      double   open  =  double(iOpen(_Symbol,PERIOD_M1,1));
      double   high  = double(iHigh(_Symbol,PERIOD_M1,1));
      double   low   = double(iLow(_Symbol,PERIOD_M1,1));
      double   close = double(iClose(_Symbol,PERIOD_M1,1));
      double   mid_point = double( (high + low) / 2 );
      double   height =  double((mid_point - close)); 
      
      double first_height_delta = (height - lag_height);
      double second_height_delta = (lag_height - lag_2_height);
      double height_growth = first_height_delta - second_height_delta;
      
      double first_midpoint_delta = (mid_point - lag_mid_point);
      double second_midpoint_delta = (lag_mid_point - lag_2_mid_point);
      double mid_point_growth = first_midpoint_delta - second_midpoint_delta;
      
      vector input_data_lag_height = {lag_height};
      vector input_data_height_grwoth = {height_growth};
      vector input_data_midpoint_growth = {mid_point_growth};
      vector input_data_midpoint = {mid_point};
      
       if(OnnxRun(ExtHandle,ONNX_NO_CONVERSION,input_data_lag_height,input_data_height_grwoth,input_data_midpoint_growth,input_data_midpoint,output_data))
         {
            Print("Model Inference Completed Successfully");
            Print("Model forecast: ",output_data[0]);
         }
       else
       {
            Print("ONNX run error : ",GetLastError());
            OnnxRelease(ExtHandle);
       }
        
       long predicted = output_data[0];
       
       if(predicted == 1)
         {
            ExtPredictedClass = PRICE_UP;
         }
         
       else if(predicted == 0)
         {
            ExtPredictedClass = PRICE_DOWN;
         }
   }

Sobald das erledigt ist, können wir unser Modell kompilieren und es mit einem Demokonto auf unserem MetaTrader 5 Terminal testen.

Vorwärtsprüfung unseres ONNX-Modells

Abb. 14: Vorwärtstest unseres ONNX Expert Advisors aus der Glass-Box

Vergewissern Sie sich, dass Ihr Modell fehlerfrei läuft, indem Sie die Registerkarte Experten und die Registerkarte Journal überprüfen.

Auf Fehler prüfen

Abb. 15: Prüfen auf Fehler in der Registerkarte „Experts“

Prüfen auf Fehler in der Registerkarte „Journal

Abb. 16: Prüfen auf Fehler in der Registerkarte „Journal“

Wie wir sehen können, läuft das Modell einwandfrei. Denken Sie daran, dass wir die Einstellungen des Expert Advisors jederzeit ändern können.

Anpassen der Einstellungen des Modells

Abb. 17: Anpassen der Einstellungen des Expert Advisors

Häufig anzutreffende Herausforderungen

In diesem Abschnitt des Artikels werden wir einige der Fehler nachstellen, die bei der Ersteinrichtung auftreten können. Wir werden untersuchen, was die Fehlerursache ist, und schließlich eine Lösung für jedes Problem vorschlagen.

Falsche Einstellung von Eingabe- oder Ausgabeformen.

Das am häufigsten auftretende Problem wird dadurch verursacht, dass die Eingabe- und Ausgabeform nicht korrekt eingestellt wurde. Denken Sie daran, dass Sie die Eingabeform für jedes Feature definieren müssen, das Ihr Modell erwartet.  Stellen Sie sicher, dass Sie jeden Index durchlaufen und die Eingabeform für jedes Feature bei diesem Index definieren. Wenn Sie es versäumen, die Form für jedes Merkmal anzugeben, kann Ihr Modell trotzdem kompiliert werden, ohne dass Fehler auftreten, wie in der nachstehenden Demonstration gezeigt wird; wenn wir jedoch versuchen, mit dem Modell eine Inferenz durchzuführen, wird der Fehler aufgedeckt. Der Fehlercode ist 5808, und die MQL5-Dokumentation beschreibt ihn als „Tensor-Dimension nicht gesetzt oder ungültig“. Denken Sie daran, dass wir in diesem Beispiel 4 Eingänge haben, aber im folgenden Codebeispiel nur eine Eingangsform einstellen. 

Eingabeform kann nicht eingestellt werden

Abb. 18: Der Expert Advisor kompiliert, ohne Ausnahmen zu verursachen

Wir haben auch einen Screenshot beigefügt, der zeigt, wie der Fehler aussieht, wenn Sie Ihre „Experten“-Registerkarte inspizieren, und denken Sie daran, dass der richtige Code an den Artikel angehängt wurde.

Fehlermeldung 5808

Abb. 19: Fehlermeldung 5808

Falsches Typecasting

Eine fehlerhafte Eingabe kann manchmal zu einem totalen Datenverlust führen, oder der Expert Advisor stürzt einfach ab. Im folgenden Beispiel haben wir ein Integer-Array verwendet, um die Ausgabe unseres ONNX-Modells zu speichern. Zur Erinnerung: Unser ONNX-Modell hat eine Ausgabe vom Typ int64. Warum glauben Sie, dass dies zu einem Fehler führt?  Dies führt zu einem Fehler, da der Typ int nicht über genügend Speicher verfügt, um die Ausgabe unseres Modells zu speichern, was zu einem Fehlschlag des Modells führt. Unsere Modellausgabe benötigt 8 Bytes, aber unser int-Array liefert nur 4. Die Lösung ist einfach: Stellen Sie sicher, dass Sie den richtigen Datentyp verwenden, um Ihre Eingaben und Ausgaben zu speichern, und wenn Sie Typecasting verwenden müssen, stellen Sie sicher, dass Sie sich an die in der MQL5-Dokumentation angegebenen Typecasting-Regeln halten. Der Fehlercode lautet 5807 und die Beschreibung lautet „Ungültige Parametergröße“.

Typecasting-Fehler

Abb. 20: Falsches Typecasting

Fehlermeldung 5807

Abb. 21: Fehlermeldung 5807

ONNX-Lauf wird nicht aufgerufen

Die Funktion ONNX Run erwartet, dass jede der Modelleingaben separat in einem eigenen Array übergeben wird. Im folgenden Codebeispiel haben wir alle Eingaben in einem Array zusammengefasst und übergeben dieses Array an die ONNX Run-Funktion. Beim Kompilieren des Codes werden keine Ausnahmen ausgelöst, aber bei der Ausführung wird in der Registerkarte „Experte“ ein Fehler angezeigt. Der Fehlercode lautet 5804 und wird in der Dokumentation kurz und bündig als „Ungültige Anzahl von Parametern, die an OnnxRun übergeben wurden“ beschrieben.

Fehlerhafter Aufruf von ONNXRun

Abb. 22: Der Aufruf der Funktion ONNXRun ist fehlgeschlagen.

Fehlermeldung 5804

Abb. 23: Fehlermeldung 5804

Schlussfolgerung

Sie verstehen nun, warum Glass-Box-Modelle für uns Finanzingenieure nützlich sein können: Sie geben uns wertvolle Einblicke mit geringem Arbeitsaufwand im Vergleich zum Aufwand, der nötig gewesen wäre, um dieselben Informationen aus einem Black-Box-Modell zu extrahieren. Außerdem sind Glass-Box-Modelle einfacher zu debuggen, zu warten, zu interpretieren und zu erklären. Es reicht nicht aus, wenn wir davon ausgehen, dass sich unsere Modelle so verhalten, wie wir es uns vorgestellt haben, sondern wir müssen das auch überprüfen, indem wir sozusagen unter die Haube schauen. 

Es gibt einen großen Nachteil von Glass-Box-Modellen, den wir bisher noch nicht behandelt haben: Sie sind nicht so flexibel wie Black-Box-Modelle. Glass-Box-Modelle sind ein offenes Forschungsgebiet, und im Laufe der Zeit werden wir vielleicht flexiblere Glass-Box-Modelle in der Zukunft sehen, aber zum Zeitpunkt der Erstellung dieses Artikels sind sie nicht so flexibel, was bedeutet, dass es Beziehungen gibt, die besser durch ein Black-Box-Modell modelliert werden können. Darüber hinaus basieren die aktuellen Implementierungen von Glass-Box-Modellen auf Entscheidungsbäumen, sodass die aktuelle Implementierung von ExplainableBoostingClassifiers in InterpretML alle Unzulänglichkeiten von Entscheidungsbäumen erbt.

Bis wir uns wiedersehen, wünsche ich Ihnen Frieden, Liebe, Harmonie und profitable Geschäfte.

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

Neuronale Netze leicht gemacht (Teil 66): Explorationsprobleme beim Offline-Lernen Neuronale Netze leicht gemacht (Teil 66): Explorationsprobleme beim Offline-Lernen
Modelle werden offline mit Daten aus einem vorbereiteten Trainingsdatensatz trainiert. Dies bietet zwar gewisse Vorteile, hat aber den Nachteil, dass die Informationen über die Umgebung stark auf die Größe des Trainingsdatensatzes komprimiert werden. Das wiederum schränkt die Möglichkeiten der Erkundung ein. In diesem Artikel wird eine Methode vorgestellt, die es ermöglicht, einen Trainingsdatensatz mit möglichst unterschiedlichen Daten zu füllen.
Python, ONNX und MetaTrader 5: Erstellen eines RandomForest-Modells mit RobustScaler und PolynomialFeatures zur Datenvorverarbeitung Python, ONNX und MetaTrader 5: Erstellen eines RandomForest-Modells mit RobustScaler und PolynomialFeatures zur Datenvorverarbeitung
In diesem Artikel werden wir ein Random-Forest-Modell in Python erstellen, das Modell trainieren und es als ONNX-Pipeline mit Datenvorverarbeitung speichern. Danach werden wir das Modell im MetaTrader 5 Terminal verwenden.
Neuronale Netze leicht gemacht (Teil 67): Nutzung früherer Erfahrungen zur Lösung neuer Aufgaben Neuronale Netze leicht gemacht (Teil 67): Nutzung früherer Erfahrungen zur Lösung neuer Aufgaben
In diesem Artikel werden weitere Methoden zur Sammlung von Daten in einem Trainingssatz erörtert. Es liegt auf der Hand, dass der Lernprozess eine ständige Interaktion mit der Umgebung erfordert. Die Situationen können jedoch unterschiedlich sein.
Algorithmen zur Optimierung mit Populationen: Der Algorithmus intelligenter Wassertropfen (IWD) Algorithmen zur Optimierung mit Populationen: Der Algorithmus intelligenter Wassertropfen (IWD)
Der Artikel befasst sich mit einem interessanten, von der unbelebten Natur abgeleiteten Algorithmus - intelligente Wassertropfen (IWD), die den Prozess der Flussbettbildung simulieren. Die Ideen dieses Algorithmus ermöglichten es, den bisherigen Spitzenreiter der Bewertung - SDS - deutlich zu verbessern. Der neue Führende (modifizierter SDSm) befindet sich wie üblich im Anhang.