English Русский 日本語
preview
Datenwissenschaft und ML (Teil 31): CatBoost AI-Modelle für den Handel verwenden

Datenwissenschaft und ML (Teil 31): CatBoost AI-Modelle für den Handel verwenden

MetaTrader 5Handel | 19 Dezember 2024, 09:48
215 0
Omega J Msigwa
Omega J Msigwa

„CatBoost ist eine Gradient-Boosting-Bibliothek, die sich dadurch auszeichnet, dass sie kategoriale Merkmale auf effiziente und skalierbare Weise behandelt und so eine erhebliche Leistungssteigerung für viele reale Probleme bietet.“

— Anthony Goldbloom.


Was ist CatBoost?

CatBoost ist eine Open-Source-Softwarebibliothek mit Gradient-Boosting-Algorithmen für Entscheidungsbäume, die speziell für die Herausforderungen beim Umgang mit kategorialen Merkmalen und Daten beim maschinellen Lernen entwickelt wurde.

Es wurde von Yandex entwickelt und im Jahr 2017 als Open-Source-Version veröffentlicht, mehr dazu hier.

Obwohl CatBoost im Vergleich zu Techniken des maschinellen Lernens wie der linearen Regression oder SVM erst vor kurzem eingeführt wurde, erlangte es in der KI-Gemeinschaft große Popularität und stieg auf Plattformen wie Kaggle an die Spitze der am häufigsten verwendeten Modelle für maschinelles Lernen.

Was CatBoost so viel Aufmerksamkeit verschafft hat, ist seine Fähigkeit, automatisch mit kategorischen Merkmalen im Datensatz umzugehen, was für viele Algorithmen des maschinellen Lernens eine Herausforderung darstellen kann.

  • Catboost-Modelle bieten in der Regel eine bessere Leistung im Vergleich zu anderen Modellen mit minimalem Aufwand, selbst mit den Standardparametern und -einstellungen können diese Modelle in Bezug auf die Genauigkeit gute Ergebnisse erzielen.
  • Im Gegensatz zu neuronalen Netzen, für deren Programmierung und Funktionsweise Fachwissen erforderlich ist, ist die Implementierung von CatBoost unkompliziert.

Dieser Artikel geht davon aus, dass Sie ein grundlegendes Verständnis von maschinellem Lernen, Entscheidungsbäumen, XGBoost, LightGBM und ONNX haben.


Wie funktioniert CatBoost?

CatBoost basiert auf einem Gradient-Boosting-Algorithmus, genau wie Light Gradient Machine (LightGBM) und Extreme Gradient Boosting (XGBoost). Dabei werden mehrere Entscheidungsbaummodelle nacheinander erstellt, wobei jedes Modell auf dem vorherigen aufbaut und versucht, die Fehler der vorherigen Modelle zu korrigieren.

Die endgültige Vorhersage ist eine gewichtete Summe der Vorhersagen aller an dem Prozess beteiligten Modelle.

Das Ziel dieser Gradient Boosted Decision Trees (GBDTs) ist es, die Verlustfunktion zu minimieren. Dies geschieht durch Hinzufügen eines neuen Modells, das die Fehler des vorherigen Modells korrigiert.


Behandlung kategorischer Merkmale

Wie ich bereits erklärt habe, kann CatBoost kategoriale Merkmale verarbeiten, ohne dass eine explizite Kodierung wie One-hot-Kodierung oder Label-Kodierung erforderlich ist, die für andere maschinelle Lernmodelle erforderlich ist.

Diese Kodierung erfolgt durch Berechnung einer bedingten Verteilung des Ziels für jeden kategorialen Merkmalswert.

Eine weitere wichtige Neuerung in CatBoost ist die Verwendung von einer geordneten Verstärkung (ordered boosting) bei der Berechnung der Statistiken für kategoriale Merkmale. Dieses geordnete Verstärkung stellt sicher, dass die Kodierung für jede Instanz auf den Informationen der zuvor beobachteten Datenpunkte basiert.

Dies trägt dazu bei, Datenlecks zu vermeiden und eine Überanpassung zu verhindern.


Es verwendet symmetrische Entscheidungsbaumstrukturen

Im Gegensatz zu LightGBM und XGBoost, die asymmetrische Bäume verwenden, verwendet CatBoost symmetrische Entscheidungsbäume für den Entscheidungsprozess. Bei einem symmetrischen Baum werden sowohl der linke als auch der rechte Zweig bei jeder Teilung symmetrisch auf der Grundlage derselben Teilungsregel konstruiert; dieser Ansatz hat mehrere Vorteile, wie zum Beispiel:

  • Es führt zu einem schnelleren Training, da beide Splits symmetrisch sind.
  • Effiziente Speichernutzung aufgrund der einfacheren Baumstruktur.
  • Symmetrische Bäume sind robuster gegenüber kleinen Störungen in den Daten.


CatBoost vs. XGBoost und LightGBM, ein detaillierter Vergleich

Wir wollen verstehen, wie sich CatBoost von anderen gradientenverstärkte Entscheidungsbäumen unterscheidet. Verstehen wir die Unterschiede zwischen diesen drei, damit wir wissen, wann wir einen dieser drei verwenden sollten und wann nicht.


Aspekt

CatBoostLichtGBMXGBoost

Behandlung kategorischer Merkmale

Ausgestattet mit automatischer Kodierung und geordneter Verstärkung für den Umgang mit kategorialen Variablen.Erfordert eine manuelle Kodierung, z. B. One-Hot-Kodierung, Kennzeichnungskodierung usw.Erfordert eine manuelle Kodierung, z. B. One-Hot-Kodierung, Kennzeichnungskodierung usw.

Struktur des Entscheidungsbaums

Es verfügt über symmetrische Entscheidungsbäume, die ausgewogen sind und gleichmäßig wachsen. Sie gewährleisten schnellere Vorhersagen und ein geringeres Risiko der Überanpassung.
Sie hat eine blattweise Wachstumsstrategie (asymmetrisch), die sich auf die Blätter mit dem höchsten Verlust konzentriert. Dies führt zu tiefen und unausgewogenen Bäumen, die eine höhere Genauigkeit, aber auch ein größeres Risiko der Überanpassung aufweisen können.


Es hat eine stufenweise Wachstumsstrategie (asymmetrisch), die den Baum auf der Grundlage der besten Aufteilung für jeden Knoten wachsen lässt. Dies führt zu flexiblen, aber langsameren Vorhersagen und einem potenziellen Risiko der Überanpassung.


Modellgenauigkeit


Sie bieten eine gute Genauigkeit bei der Arbeit mit Datensätzen, die viele kategoriale Merkmale enthalten, aufgrund des geordneten Boostings und des geringeren Risikos einer Überanpassung bei kleineren Daten.

Sie bieten eine gute Genauigkeit, insbesondere bei großen und hochdimensionalen Datensätzen, da sich die blattweise Wachstumsstrategie auf die Verbesserung der Leistung in Bereichen mit hohen Fehlern konzentriert.Sie bieten bei den meisten Datensätzen eine gute Genauigkeit, werden aber von CatBoost bei kategorischen Datensätzen und von LightGBM bei sehr großen Datensätzen aufgrund seiner weniger aggressiven Baumwachstumsstrategie tendenziell übertroffen.

Geschwindigkeit und Genauigkeit trainieren


Ist in der Regel langsamer zu trainieren als LightGBM, aber effizienter bei kleinen bis mittelgroßen Datensätzen, insbesondere wenn kategoriale Merkmale beteiligt sind.

Ist in der Regel die schnellste dieser drei, vor allem bei großen Datensätzen, da der Baum blattweise wächst, was bei hochdimensionalen Daten effizienter ist.Oft ist er der mit Abstand langsamste der drei. Es ist sehr effizient für große Datensätze. 


Einsatz eines CatBoost-Modells

Bevor wir den Code für CatBoost schreiben können, müssen wir ein Problemszenario erstellen. Wir wollen die Handelssignale (Kauf/Verkauf) vorhersagen, indem wir die Daten Open, High, Low, Close und einige kategorische Merkmale wie Day (das aktuelle Datum), Day of Week (von Montag bis Sonntag), Day of Year (von 1 bis 365) und den Month (von Januar bis Dezember) verwenden.

Die OHLC-Werte (Open, High, Low, Close) sind kontinuierliche Merkmale, während der Rest kategorische Merkmale sind. Ein Skript zur Erfassung dieser Daten finden Sie in den Anhängen.

Wir beginnen damit, das CatBoost-Modell nach der Installation zu importieren.

Installation

Befehlszeile

pip install catboost

Importieren.

Python-Code

import numpy as np
import pandas as pd
import catboost
from catboost import CatBoostClassifier
from sklearn.pipeline import Pipeline 
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
import seaborn as sns
import matplotlib.pyplot as plt

sns.set_style("darkgrid")

Wir können die Daten visualisieren, um zu sehen, was es damit auf sich hat.

df = pd.read_csv("/kaggle/input/ohlc-eurusd/EURUSD.OHLC.PERIOD_D1.csv")

df.head()

Ergebnis:

OpenHighLowCloseDayDayofWeekDayofYearMonth
01.093811.095481.090031.0937321.03.0264.09.0
11.096781.098101.093611.0939922.04.0265.09.0
21.097011.099731.096061.0980523.05.0266.09.0
31.096391.098691.095421.0974226.01.0269.09.0
41.103021.103961.095131.0975727.02.0270.09.0

Beim Sammeln von Daten in einem MQL5-Skript habe ich die Werte DayofWeek (Montag bis Sonntag) und Month (Januar - Dezember) als Ganzzahlen anstelle von Zeichenketten abgerufen, da ich sie in einer Matrix speichere, die es mir nicht erlaubt, Zeichenketten hinzuzufügen, obwohl sie von Natur aus kategorische Variablen sind. So wie es jetzt aussieht, werden wir sie wieder in Kategorien umwandeln und sehen, wie CatBoost damit umgehen kann.

Lassen Sie uns die Zielvariable vorbereiten.

Python-Code

new_df = df.copy()  # Create a copy of the original DataFrame

# we Shift the 'Close' and 'open' columns by one row to ge the future close and open price values, then we add these new columns to the dataset

new_df["target_close"] = df["Close"].shift(-1)  
new_df["target_open"] = df["Open"].shift(-1)  

new_df = new_df.dropna()  # Drop the rows with NaN values resulting from the shift operation

open_values = new_df["target_open"]
close_values = new_df["target_close"]  

target = []
for i in range(len(open_values)):
    if close_values[i] > open_values[i]: 
        target.append(1) # buy signal 
    else:
        target.append(0) # sell signal

new_df["signal"] = target # we create the signal column and add the target variable we just prepared


print(new_df.shape)
new_df.head()

Ergebnis:

OpenHighLowCloseDayDayofWeekDayofYearMonthtarget_closetarget_opensignal
01.093811.095481.090031.0937321.03.0264.09.01.093991.096780
11.096781.098101.093611.0939922.04.0265.09.01.098051.097011
21.097011.099731.096061.0980523.05.0266.09.01.097421.096391
31.096391.098691.095421.0974226.01.0269.09.01.097571.103020
41.103021.103961.095131.0975727.02.0270.09.01.102971.104310

Da wir nun die Signale haben, die wir vorhersagen können, sollten wir die Daten in Trainings- und Testproben aufteilen.

X = new_df.drop(columns = ["target_close", "target_open", "signal"]) # we drop future values
y = new_df["signal"] # trading signals are the target variables we wanna predict 

X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, random_state=42)

Wir definieren eine Liste von kategorischen Merkmalen, die in unserem Datensatz vorhanden sind.

categorical_features = ["Day","DayofWeek", "DayofYear", "Month"]

Anhand dieser Liste können wir dann diese kategorischen Merkmale in das Format einer Zeichenkette konvertieren, da kategorische Variablen in der Regel als Zeichenkette vorliegen.

X_train[categorical_features] = X_train[categorical_features].astype(str)
X_test[categorical_features] = X_test[categorical_features].astype(str)

X_train.info() # we print the data types now

Ergebnis:

<class 'pandas.core.frame.DataFrame'>
Index: 6999 entries, 9068 to 7270
Data columns (total 8 columns):
 #   Column     Non-Null Count  Dtype  
---  ------     --------------  -----  
 0   Open       6999 non-null   float64
 1   High       6999 non-null   float64
 2   Low        6999 non-null   float64
 3   Close      6999 non-null   float64
 4   Day        6999 non-null   object 
 5   DayofWeek  6999 non-null   object 
 6   DayofYear  6999 non-null   object 
 7   Month      6999 non-null   object 
dtypes: float64(4), object(4)
memory usage: 492.1+ KB

Wir haben jetzt kategorische Variablen mit Objektdatentypen aus Zeichenketten. Wenn Sie diese Daten mit einem anderen maschinellen Lernmodell als CatBoost ausprobieren, werden Sie Fehler erhalten, da Objektdatentypen in den typischen Trainingsdaten für maschinelles Lernen nicht zulässig sind.


Trainieren eines CatBoost-Modells

Bevor wir die Fit-Methode aufrufen, die für das Training des CatBoost-Modells verantwortlich ist, sollten wir einige der vielen Parameter verstehen, die dieses Modell ausmachen.


Parameter

Beschreibung

Iterations


Dies ist die Anzahl der zu erstellenden Entscheidungsbaum-Iterationen.

Mehr Iterationen führen zu einer besseren Leistung, bergen aber auch das Risiko einer Überanpassung.


learning_rate               
  

Dieser Faktor steuert die Verteilung der einzelnen Bäume auf die endgültige Vorhersage.

Eine geringere Lernrate erfordert mehr Iterationen, bis die Bäume konvergieren, führt aber oft zu besseren Modellen.


depth


Dies ist die maximale Tiefe der Bäume.

Tiefere Bäume können komplexere Muster in den Daten erfassen, führen aber oft zu einer Überanpassung.


cat_features


Dies ist eine Liste von kategorischen Indizes.
Obwohl das CatBoost-Modell in der Lage ist, die kategorischen Merkmale zu erkennen, ist es eine gute Praxis, dem Modell ausdrücklich mitzuteilen, welche Merkmale kategorisch sind.
Dies hilft dem Modell, die kategorialen Merkmale aus menschlicher Sicht zu verstehen, da die Methoden zur automatischen Erkennung kategorialer Variablen manchmal versagen können.

l2_leaf_reg


Dies ist der Koeffizient für die L2 Regulierung.

Sie hilft, eine Überanpassung zu verhindern, indem größere Blattgewichte bestraft werden.


border_count
 


Dies ist die Anzahl der Splits für jedes kategoriale Merkmal. Je höher diese Zahl ist, desto besser ist die Leistung und desto länger ist die Berechnungszeit.



eval_metric


Dies ist der Bewertungsmaßstab, der beim Training verwendet wird.

Es hilft bei der effektiven Überwachung der Modellleistung.


early_stopping_rounds


Wenn dem Modell Validierungsdaten zur Verfügung gestellt werden, wird der Trainingsfortschritt gestoppt, wenn für diese Anzahl von Runden keine Verbesserung der Genauigkeit des Modells festgestellt wird.

Dieser Parameter trägt dazu bei, die Überanpassung zu reduzieren und kann viel Trainingszeit sparen.

Wir können ein Wörterbuch für die oben genannten Parameter definieren.

params = dict(
    iterations=100, 
    learning_rate=0.01,
    depth=10,
    l2_leaf_reg=5,  
    bagging_temperature=1,
    border_count=64,  # Number of splits for categorical features
    eval_metric='Logloss',
    random_seed=42,  # Seed for reproducibility
    verbose=1,  # Verbosity level
    # early_stopping_rounds=10  # Early stopping for validation
)

Schließlich können wir das Catboost-Modell innerhalb der  Sklearn-Pipeline definieren und dann die Fit-Methode für das Training aufrufen, indem wir dieser Methode Evaluierungsdaten und eine Liste von kategorialen Merkmalen übergeben.

pipe = Pipeline([
    ("catboost", CatBoostClassifier(**params))
])

# Fit the pipeline to the training data
pipe.fit(X_train, y_train, catboost__eval_set=(X_test, y_test), catboost__cat_features=categorical_features)

Outputs

90:     learn: 0.6880592        test: 0.6936112 best: 0.6931239 (3)     total: 523ms    remaining: 51.7ms
91:     learn: 0.6880397        test: 0.6936100 best: 0.6931239 (3)     total: 529ms    remaining: 46ms
92:     learn: 0.6880350        test: 0.6936051 best: 0.6931239 (3)     total: 532ms    remaining: 40ms
93:     learn: 0.6880280        test: 0.6936103 best: 0.6931239 (3)     total: 535ms    remaining: 34.1ms
94:     learn: 0.6879448        test: 0.6936110 best: 0.6931239 (3)     total: 541ms    remaining: 28.5ms
95:     learn: 0.6878328        test: 0.6936387 best: 0.6931239 (3)     total: 547ms    remaining: 22.8ms
96:     learn: 0.6877888        test: 0.6936473 best: 0.6931239 (3)     total: 553ms    remaining: 17.1ms
97:     learn: 0.6877408        test: 0.6936508 best: 0.6931239 (3)     total: 559ms    remaining: 11.4ms
98:     learn: 0.6876611        test: 0.6936708 best: 0.6931239 (3)     total: 565ms    remaining: 5.71ms
99:     learn: 0.6876230        test: 0.6936898 best: 0.6931239 (3)     total: 571ms    remaining: 0us

bestTest = 0.6931239281
bestIteration = 3

Shrink model to first 4 iterations.


Evaluierung des Modells

Wir können Sklearn-Metriken verwenden, um die Leistung dieses Modells zu bewerten.

# Make predicitons on training and testing sets
y_train_pred = pipe.predict(X_train)
y_test_pred = pipe.predict(X_test)

# Training set evaluation
print("Training Set Classification Report:")
print(classification_report(y_train, y_train_pred))

# Testing set evaluation
print("\nTesting Set Classification Report:")
print(classification_report(y_test, y_test_pred))

Outputs

Training Set Classification Report:
              precision    recall  f1-score   support

           0       0.55      0.44      0.49      3483
           1       0.54      0.64      0.58      3516

    accuracy                           0.54      6999
   macro avg       0.54      0.54      0.54      6999
weighted avg       0.54      0.54      0.54      6999


Testing Set Classification Report:
              precision    recall  f1-score   support

           0       0.53      0.41      0.46      1547
           1       0.49      0.61      0.54      1453

    accuracy                           0.51      3000
   macro avg       0.51      0.51      0.50      3000
weighted avg       0.51      0.51      0.50      3000

Das Ergebnis ist ein Modell mit durchschnittlicher Leistung. Nachdem ich die Liste der kategorischen Merkmale entfernt hatte, stieg die Modellgenauigkeit im Trainingssatz auf 60 %, im Testsatz blieb sie jedoch gleich.

pipe.fit(X_train, y_train, catboost__eval_set=(X_test, y_test))

Ergebnis:

91:     learn: 0.6844878        test: 0.6933503 best: 0.6930500 (30)    total: 395ms    remaining: 34.3ms
92:     learn: 0.6844035        test: 0.6933539 best: 0.6930500 (30)    total: 399ms    remaining: 30ms
93:     learn: 0.6843241        test: 0.6933791 best: 0.6930500 (30)    total: 404ms    remaining: 25.8ms
94:     learn: 0.6842277        test: 0.6933732 best: 0.6930500 (30)    total: 408ms    remaining: 21.5ms
95:     learn: 0.6841427        test: 0.6933758 best: 0.6930500 (30)    total: 412ms    remaining: 17.2ms
96:     learn: 0.6840422        test: 0.6933796 best: 0.6930500 (30)    total: 416ms    remaining: 12.9ms
97:     learn: 0.6839896        test: 0.6933825 best: 0.6930500 (30)    total: 420ms    remaining: 8.58ms
98:     learn: 0.6839040        test: 0.6934062 best: 0.6930500 (30)    total: 425ms    remaining: 4.29ms
99:     learn: 0.6838397        test: 0.6934259 best: 0.6930500 (30)    total: 429ms    remaining: 0us

bestTest = 0.6930499562
bestIteration = 30

Shrink model to first 31 iterations.
Training Set Classification Report:
              precision    recall  f1-score   support

           0       0.61      0.53      0.57      3483
           1       0.59      0.67      0.63      3516

    accuracy                           0.60      6999
   macro avg       0.60      0.60      0.60      6999
weighted avg       0.60      0.60      0.60      6999

Um dieses Modell im Detail zu verstehen, erstellen wir das Diagramm der Merkmalsbedeutung.

# Extract the trained CatBoostClassifier from the pipeline
catboost_model = pipe.named_steps['catboost']

# Get feature importances
feature_importances = catboost_model.get_feature_importance()

feature_im_df = pd.DataFrame({
    "feature": X.columns,
    "importance": feature_importances
})

feature_im_df = feature_im_df.sort_values(by="importance", ascending=False)

plt.figure(figsize=(10, 6))
sns.barplot(data = feature_im_df, x='importance', y='feature', palette="viridis")

plt.title("CatBoost feature importance")
plt.xlabel("Importance")
plt.ylabel("feature")
plt.show()

Output

Die obige „Merkmalsbedeutungsdarstellung“ gibt Aufschluss darüber, wie das Modell Entscheidungen getroffen hat. Es scheint, dass das CatBoost-Modell die kategorialen Variablen als die wichtigsten Merkmale für das endgültige vorhergesagte Ergebnis des Modells ansieht und nicht die kontinuierlichen Variablen.


Speichern des CatBoost-Modells in ONNX

Um dieses Modell im MetaTrader 5 zu verwenden, müssen wir es im ONNX-Format speichern. Das Speichern des CatBoost-Modells kann nun etwas kompliziert sein, im Gegensatz zu Sklearn- und Keras-Modellen, die über Methoden zur einfacheren Konvertierung verfügen.

Ich konnte sie speichern, nachdem ich den Instruktionen aus ihrer Dokumentation gefolgt war. Ich habe mir aber nicht die Mühe gemacht, den Großteil des Codes zu verstehen.

from onnx.helper import get_attribute_value
import onnxruntime as rt
from skl2onnx import convert_sklearn, update_registered_converter
from skl2onnx.common.shape_calculator import (
    calculate_linear_classifier_output_shapes,
)  # noqa
from skl2onnx.common.data_types import (
    FloatTensorType,
    Int64TensorType,
    guess_tensor_type,
)
from skl2onnx._parse import _apply_zipmap, _get_sklearn_operator_name
from catboost import CatBoostClassifier
from catboost.utils import convert_to_onnx_object

def skl2onnx_parser_castboost_classifier(scope, model, inputs, custom_parsers=None):
    
    options = scope.get_options(model, dict(zipmap=True))
    no_zipmap = isinstance(options["zipmap"], bool) and not options["zipmap"]

    alias = _get_sklearn_operator_name(type(model))
    this_operator = scope.declare_local_operator(alias, model)
    this_operator.inputs = inputs

    label_variable = scope.declare_local_variable("label", Int64TensorType())
    prob_dtype = guess_tensor_type(inputs[0].type)
    probability_tensor_variable = scope.declare_local_variable(
        "probabilities", prob_dtype
    )
    this_operator.outputs.append(label_variable)
    this_operator.outputs.append(probability_tensor_variable)
    probability_tensor = this_operator.outputs

    if no_zipmap:
        return probability_tensor

    return _apply_zipmap(
        options["zipmap"], scope, model, inputs[0].type, probability_tensor
    )


def skl2onnx_convert_catboost(scope, operator, container):
    """
    CatBoost returns an ONNX graph with a single node.
    This function adds it to the main graph.
    """
    onx = convert_to_onnx_object(operator.raw_operator)
    opsets = {d.domain: d.version for d in onx.opset_import}
    if "" in opsets and opsets[""] >= container.target_opset:
        raise RuntimeError("CatBoost uses an opset more recent than the target one.")
    if len(onx.graph.initializer) > 0 or len(onx.graph.sparse_initializer) > 0:
        raise NotImplementedError(
            "CatBoost returns a model initializers. This option is not implemented yet."
        )
    if (
        len(onx.graph.node) not in (1, 2)
        or not onx.graph.node[0].op_type.startswith("TreeEnsemble")
        or (len(onx.graph.node) == 2 and onx.graph.node[1].op_type != "ZipMap")
    ):
        types = ", ".join(map(lambda n: n.op_type, onx.graph.node))
        raise NotImplementedError(
            f"CatBoost returns {len(onx.graph.node)} != 1 (types={types}). "
            f"This option is not implemented yet."
        )
    node = onx.graph.node[0]
    atts = {}
    for att in node.attribute:
        atts[att.name] = get_attribute_value(att)
    container.add_node(
        node.op_type,
        [operator.inputs[0].full_name],
        [operator.outputs[0].full_name, operator.outputs[1].full_name],
        op_domain=node.domain,
        op_version=opsets.get(node.domain, None),
        **atts,
    )


update_registered_converter(
    CatBoostClassifier,
    "CatBoostCatBoostClassifier",
    calculate_linear_classifier_output_shapes,
    skl2onnx_convert_catboost,
    parser=skl2onnx_parser_castboost_classifier,
    options={"nocl": [True, False], "zipmap": [True, False, "columns"]},
)

Nachfolgend sehen Sie die Konvertierung des endgültigen Modells und das Speichern mit der Dateierweiterung .onnx.

model_onnx = convert_sklearn(
    pipe,
    "pipeline_catboost",
    [("input", FloatTensorType([None, X_train.shape[1]]))],
    target_opset={"": 12, "ai.onnx.ml": 2},
)

# And save.
with open("CatBoost.EURUSD.OHLC.D1.onnx", "wb") as f:
    f.write(model_onnx.SerializeToString())

Bei der Visualisierung in Netron sieht die Modellstruktur genauso aus wie bei XGBoost und LightGBM.

Catboost-Modell in Netron

Dies ist sinnvoll, da es sich um einen weiteren Gradient Boosted Decision Tree handelt. Sie haben im Wesentlichen ähnliche Strukturen.

Als ich versuchte, das CatBoost-Modell in einer Pipeline in ONNX mit kategorialen Variablen zu konvertieren, schlug der Prozess fehl und verursachte einen Fehler.

CatBoostError: catboost/libs/model/model_export/model_exporter.cpp:96: ONNX-ML format export does yet not support categorical features

Ich musste sicherstellen, dass die kategorischen Variablen im Typ float64 (double) waren, so wie wir sie ursprünglich in MetaTrader 5 gesammelt haben. Dies behebt den Fehler und hilft bei der Arbeit mit diesem Modell in MQL5, dass wir uns keine Sorgen über die Vermischung von Double- oder Float-Werten mit ganzen Zahlen machen müssen.

categorical_features = ["Day","DayofWeek", "DayofYear", "Month"]

# Remove these two lines of code operations

# X_train[categorical_features] = X_train[categorical_features].astype(str)
# X_test[categorical_features] = X_test[categorical_features].astype(str)

X_train.info() # we print the data types now

Trotz dieser Änderung war das CatBoost-Modell davon nicht betroffen (es lieferte die gleichen Genauigkeitswerte), da es mit Datensätzen unterschiedlicher Art arbeiten kann.


Erstellen eines CatBoost Expert Advisors (Handelsroboter)

Wir können damit beginnen, das ONNX-Modell als Ressource in den Haupt-Expert Advisor einzubetten.

MQL5-Code

#resource "\\Files\\CatBoost.EURUSD.OHLC.D1.onnx" as uchar catboost_onnx[]

Wir importieren die Bibliothek zum Laden des CatBoost-Modells.

#include <MALE5\Gradient Boosted Decision Trees(GBDTs)\CatBoost\CatBoost.mqh>
CCatBoost cat_boost;

Wir müssen die Daten auf die gleiche Weise sammeln, wie wir die Trainingsdaten in der OnTick-Funktion gesammelt haben.

void OnTick()
 {
...
...
...     

   if (CopyRates(Symbol(), timeframe, 1, 1, rates) < 0) //Copy information from the previous bar
    {
      printf("Failed to obtain OHLC price values error = ",GetLastError());
      return;
    }
   
   MqlDateTime time_struct;
   string time = (string)datetime(rates[0].time); //converting the date from seconds to datetime then to string
   TimeToStruct((datetime)StringToTime(time), time_struct); //converting the time in string format to date then assigning it to a structure
      
   vector x = {rates[0].open,
               rates[0].high, 
               rates[0].low, 
               rates[0].close, 
               time_struct.day, 
               time_struct.day_of_week, 
               time_struct.day_of_year, 
               time_struct.mon}; //input features from the previously closed bar
...
...
...     
  }

So erhalten wir schließlich das vorhergesagte Signal und den Wahrscheinlichkeitsvektor zwischen der bärischen Signalklasse 0 und der bullischen Signalklasse 1.

   vector proba = cat_boost.predict_proba(x); //predict the probability between the classes
   long signal = cat_boost.predict_bin(x); //predict the trading signal class
   
   Comment("Predicted Probability = ", proba,"\nSignal = ",signal);

Wir können diesen EA abschließen, indem wir eine Handelsstrategie kodieren, die auf den Vorhersagen des Modells basiert. 

Die Strategie ist einfach: Wenn das Modell ein Kaufsignal vorhersagt, kaufen wir und schließen einen Verkaufsposition, wenn es eine solche gibt. Wir tun das Gegenteil, wenn das Modell ein Abwärtssignal vorhersagt.

void OnTick()
  {
//---
   
   if (!NewBar())
    return;
   
//--- Trade at the opening of each bar

   if (CopyRates(Symbol(), timeframe, 1, 1, rates) < 0) //Copy information from the previous bar
    {
      printf("Failed to obtain OHLC price values error = ",GetLastError());
      return;
    }
   
   MqlDateTime time_struct;
   string time = (string)datetime(rates[0].time); //converting the date from seconds to datetime then to string
   TimeToStruct((datetime)StringToTime(time), time_struct); //converting the time in string format to date then assigning it to a structure
      
   vector x = {rates[0].open,
               rates[0].high, 
               rates[0].low, 
               rates[0].close, 
               time_struct.day, 
               time_struct.day_of_week, 
               time_struct.day_of_year, 
               time_struct.mon}; //input features from the previously closed bar
               
   vector proba = cat_boost.predict_proba(x); //predict the probability between the classes
   long signal = cat_boost.predict_bin(x); //predict the trading signal class
   
   Comment("Predicted Probability = ", proba,"\nSignal = ",signal);
   
//---
      
      MqlTick ticks;
      SymbolInfoTick(Symbol(), ticks);
      
      if (signal==1) //if the signal is bullish
       {
          if (!PosExists(POSITION_TYPE_BUY)) //There are no buy positions
            m_trade.Buy(min_lot, Symbol(), ticks.ask, 0, 0); //Open a buy trade
          
          ClosePosition(POSITION_TYPE_SELL); //close the opposite trade  
       }
      else //Bearish signal
        {
          if (!PosExists(POSITION_TYPE_SELL)) //There are no Sell positions
            m_trade.Sell(min_lot, Symbol(), ticks.bid, 0, 0); //open a sell trade
            
            
          ClosePosition(POSITION_TYPE_BUY); //close the opposite trade
        }
  }

Ich habe einen Test mit dem Strategy Tester vom 2021.01.01 bis 2024.10.8 auf einem 4-Stunden-Zeitrahmen durchgeführt, wobei die Modellierung auf „Nur offene Preise" eingestellt war. Nachstehend das Ergebnis.

'

Der EA schlug sich, gelinde gesagt, gut und lieferte 55% profitable Handelsgeschäfte, die einen Gesamtnettogewinn von $96 USD erbrachten. Nicht schlecht für einen einfachen Datensatz, ein einfaches Modell und ein minimales Handelsvolumen.


Abschließende Überlegungen

CatBoost und andere Gradient-Boost-Entscheidungsbäume sind die ideale Lösung, wenn Sie in einer Umgebung mit begrenzten Ressourcen arbeiten und ein Modell suchen, das „einfach funktioniert“, ohne dass Sie sich mit den langweiligen und manchmal unnötigen Dingen beschäftigen müssen, die beim Feature-Engineering und der Modellkonfiguration anfallen, wenn Sie mit zahlreichen Maschinenmodellen arbeiten. 

Trotz ihrer Einfachheit und der geringen Einstiegshürde gehören sie zu den besten und effektivsten KI-Modellen, die in vielen realen Anwendungen eingesetzt werden.

Mit freundlichen Grüßen.


Verfolgen Sie die Entwicklung von Modellen für maschinelles Lernen und vieles mehr in dieser Artikelserie auf diesem GitHub repo.

Tabelle der Anhänge


Dateiname

DateitypBeschreibung und Verwendung

Experts\CatBoost EA.mq5

 Expert AdvisorHandelsroboter zum Laden des Catboost-Modells im ONNX-Format und Testen der Handelsstrategie im MetaTrader 5.

Include\CatBoost.mqh

 #include-Datei


Enthält den Code zum Laden und Bereitstellen eines CatBoost-Modells.


Files\ CatBoost.EURUSD.OHLC.D1.onnx


 ONNX-Modell


Trainiertes CatBoost-Modell, gespeichert im ONNX-Format.

 
Skripte\CollectData.mq5

 MQL5-SkriptEin Skript zur Erfassung der Trainingsdaten. 

Jupyter Notebook\CatBoost-4-trading.ipynb 

 Python/Jupyter notebook  Der gesamte Python-Code, der in diesem Artikel besprochen wird, ist in diesem notebook zu finden.


Quellen und Referenzen

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

Beigefügte Dateien |
Attachments.zip (40.07 KB)
Entwicklung eines Handelsroboters in Python (Teil 3): Implementierung eines modellbasierten Handelsalgorithmus Entwicklung eines Handelsroboters in Python (Teil 3): Implementierung eines modellbasierten Handelsalgorithmus
Wir setzen die Serie von Artikeln über die Entwicklung eines Handelsroboters in Python und MQL5 fort. In diesem Artikel werden wir einen Handelsalgorithmus in Python erstellen.
Algorithmus zur chemischen Reaktionsoptimierung (CRO) (Teil II): Zusammenstellung und Ergebnisse Algorithmus zur chemischen Reaktionsoptimierung (CRO) (Teil II): Zusammenstellung und Ergebnisse
Im zweiten Teil werden wir die chemischen Operatoren in einem einzigen Algorithmus zusammenfassen und eine detaillierte Analyse seiner Ergebnisse präsentieren. Wir wollen herausfinden, wie die Methode der chemischen Reaktionsoptimierung (CRO) mit der Lösung komplexer Probleme bei Testfunktionen zurechtkommt.
Der Body im Connexus (Teil 4): Hinzufügen des HTTP-Hauptteils Der Body im Connexus (Teil 4): Hinzufügen des HTTP-Hauptteils
In diesem Artikel werden wir das Konzept des Body in HTTP-Anfragen untersuchen, das für das Senden von Daten wie JSON und Klartext unerlässlich ist. Wir besprechen und erklären, wie man es richtig mit den entsprechenden Kopfzeilen verwendet. Wir haben auch die Klasse ChttpBody eingeführt, die Teil der Connexus-Bibliothek ist und die Arbeit mit dem Body von Anfragen vereinfacht.
Neuronale Netze leicht gemacht (Teil 95): Reduzierung des Speicherverbrauchs in Transformermodellen Neuronale Netze leicht gemacht (Teil 95): Reduzierung des Speicherverbrauchs in Transformermodellen
Auf der Transformerarchitektur basierende Modelle weisen eine hohe Effizienz auf, aber ihre Verwendung wird durch hohe Ressourcenkosten sowohl in der Trainingsphase als auch während des Betriebs erschwert. In diesem Artikel schlage ich vor, sich mit Algorithmen vertraut zu machen, die es ermöglichen, den Speicherverbrauch solcher Modelle zu reduzieren.