English Русский 中文 Deutsch 日本語 Português
preview
Aprendizaje automático y Data Science (Parte 31): Uso de los modelos de inteligencia artificial CatBoost

Aprendizaje automático y Data Science (Parte 31): Uso de los modelos de inteligencia artificial CatBoost

MetaTrader 5Trading |
171 7
Omega J Msigwa
Omega J Msigwa

 «CatBoost es una biblioteca de boosting de gradiente que se distingue por manejar características categóricas de una manera eficiente y escalable, proporcionando un aumento significativo del rendimiento para muchos problemas del mundo real.»

— Anthony Goldbloom.


¿Qué es CatBoost?

CatBoost es una biblioteca de software de código abierto con algoritmos de "potenciador de gradientes" sobre árboles de decisión, diseñada específicamente para abordar los desafíos del manejo de características categóricas y datos en el aprendizaje automático.

Fue desarrollado por Yandex y se convirtió en código abierto en 2017. Lee más.

A pesar de haberse introducido recientemente en comparación con técnicas de aprendizaje automático como la regresión lineal o SVM, CatBoost ganó una popularidad masiva entre las comunidades de IA y llegó a la cima de los modelos de aprendizaje automático más utilizados en plataformas como Kaggle.

Lo que hizo que CatBoost ganara tanta atención es su capacidad de manejar automáticamente características categóricas en el conjunto de datos, lo que puede ser un desafío para muchos algoritmos de aprendizaje automático.

  • Los modelos Catboost suelen proporcionar un mejor rendimiento en comparación con otros modelos con un esfuerzo mínimo, incluso con los parámetros y ajustes predeterminados, estos modelos pueden tener un buen rendimiento en cuanto a precisión.
  • A diferencia de las redes neuronales que requieren conocimiento del dominio para codificarlas y hacerlas funcionar, la implementación de CatBoost es sencilla.

Este artículo asume que usted tiene un conocimiento básico de aprendizaje automático, Árboles de Decisión, XGBoost, LightGBM, y ONNX.


¿Cómo funciona CatBoost?

CatBoost se basa en un algoritmo de aumento de gradiente al igual que Light Gradient Machine (LightGBM) y Extreme Gradient Boosting (XGBoost). Funciona construyendo varios modelos de árboles de decisión secuencialmente, donde cada modelo se basa en el anterior e intenta corregir el error de los modelos anteriores.

La predicción final es una suma ponderada de las predicciones realizadas por todos los modelos involucrados en el proceso.

El objetivo de estos árboles de decisión potenciados por gradiente (GBDT) es minimizar la función de pérdida, esto se hace agregando un nuevo modelo que corrige los errores del modelo anterior.


Manejo de características categóricas

Como expliqué anteriormente, CatBoost puede manejar características categóricas sin la necesidad de codificación explícita como la codificación One-hot o la codificación de etiquetas que se requiere para otros modelos de aprendizaje automático. Esto se debe a que CatBoost introduce una codificación basada en objetivos para características categóricas.

Esta codificación se realiza calculando una distribución condicional del objetivo para cada valor de característica categórica.

Otra innovación clave en CatBoost es el uso de un refuerzo ordenado al calcular las estadísticas para características categóricas. Este refuerzo ordenado garantiza que la codificación de cualquier instancia se base en la información de puntos de datos observados previamente.

Esto ayuda a evitar fugas de datos y a evitar el sobreajuste.


Utiliza estructuras de árboles de decisión simétricos

A diferencia de LightGBM y XGBoost, que utilizan árboles asimétricos, CatBoost utiliza árboles de decisión simétricos para el proceso de toma de decisiones. En un árbol simétrico, las ramas izquierda y derecha en cada división se construyen simétricamente según la misma regla de división, este enfoque tiene varias ventajas como:

  • Esto conduce a un entrenamiento más rápido ya que ambas divisiones son simétricas.
  • Uso eficiente de la memoria debido a la estructura de árbol más sencilla.
  • Los árboles simétricos son más robustos ante pequeñas perturbaciones en los datos.


CatBoost vs. XGBoost y LightGBM: una comparación detallada

Comprendamos en qué se diferencia CatBoost de otros árboles de decisión potenciados por gradiente. Entendamos las diferencias entre estos tres, de esta manera sabremos cuándo usar uno de estos tres y cuándo no.


Aspecto

CatBoostLightGBMXGBoost

Manejo de características categóricas

Equipado con codificación automática y refuerzo ordenado para el manejo de variables categóricas.Requiere procesamiento de codificación manual, como codificación One-Hot, codificación de etiquetas, etc.Requiere procesamiento de codificación manual, como codificación One-Hot, codificación de etiquetas, etc.

Estructura del árbol de decisiones

Tiene árboles de decisión simétricos, que están equilibrados y crecen de manera uniforme. Garantizan predicciones más rápidas y un menor riesgo de sobreajuste.
Tiene una estrategia de crecimiento por hojas (asimétrica) que se centra en las hojas con mayores pérdidas. Esto da lugar a árboles profundos y desequilibrados que pueden tener una mayor precisión pero, un mayor riesgo de sobreajuste.


Tiene una estrategia de crecimiento por niveles (asimétrica) que hace crecer el árbol basándose en la mejor división para cada nodo. Esto da lugar a predicciones flexibles pero más lentas y a un riesgo potencial de sobreajuste.


Precisión del modelo


Proporcionan una buena precisión cuando se trabaja con conjuntos de datos que contienen muchas características categóricas debido al refuerzo ordenado y al riesgo reducido de sobreajuste en datos más pequeños.

Proporcionan una buena precisión, especialmente en conjuntos de datos grandes y de alta dimensión, ya que la estrategia de crecimiento hoja por hoja se centra en mejorar el rendimiento en áreas de alto error.Proporcionan una buena precisión en la mayoría de los conjuntos de datos, pero tienden a ser superados por CatBoost en conjuntos de datos categóricos y por LightGBM en conjuntos de datos muy grandes debido a su estrategia de crecimiento de árboles menos agresiva.

Entrenamiento de velocidad y precisión


Generalmente es más lento de entrenar que LightGBM, pero más eficiente en conjuntos de datos pequeños a medianos, especialmente cuando hay características categóricas involucradas.

Generalmente es el más rápido de los tres, especialmente en conjuntos de datos grandes debido a su crecimiento de árbol hoja por hoja, que es más eficiente en datos de alta dimensión.A menudo, el más lento de los tres por un pequeño margen. Es muy eficiente para grandes conjuntos de datos. 


Implementación de un modelo CatBoost

Antes de que podamos escribir código para CatBoost, creemos un escenario de problema. Queremos predecir las señales de trading (Compra/Venta) utilizando los datos; Apertura, Máximo, Mínimo, Cierre y un par de características categóricas como el Día (que es la fecha actual), el día de la semana (de lunes a domingo), el Día del año (de 1 a 365), y el Mes (de enero a diciembre).

Los valores OHLC (Apertura, Máximo, Mínimo, Cierre) son características continuas, mientras que el resto son características categóricas. En los archivos adjuntos se puede encontrar un script para recopilar estos datos.

Comenzamos importando el modelo CatBoost después de instalarlo.

Instalación

Línea de comandos

pip install catboost

Importación.

Código Python

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")

Podemos visualizar los datos para ver de qué se trata.

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

df.head()

Resultado

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

Al recopilar datos dentro de un script MQL5, obtuve los valores Día de la semana (lunes a domingo) y Mes (enero - diciembre) como números enteros en lugar de cadenas, ya que los estaba almacenando en una matriz que no me permitía sumar cadenas, a pesar de que son variables categóricas por naturaleza, como está ahora no lo son, vamos a convertirlos nuevamente en categorías y ver cómo CatBoost puede manejarlos.

Preparemos la variable objetivo.

Código Python

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()

Resultado

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

Ahora que tenemos las señales que podemos predecir, dividamos los datos en muestras de entrenamiento y prueba.

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)

Definimos una lista de características categóricas presentes en nuestro conjunto de datos.

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

Luego podemos usar esta lista para convertir estas características categóricas en formato de cadenas, ya que las variables categóricas generalmente están en formato de cadenas.

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

Resultado

<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

Ahora tenemos variables categóricas con tipos de datos de objeto, que son tipos de datos de cadena. Si prueba estos datos con un modelo de aprendizaje automático que no sea CatBoost, obtendrá errores ya que los tipos de datos de objetos no están permitidos en los datos de entrenamiento de aprendizaje automático típicos.


Entrenamiento de un modelo CatBoost

Antes de llamar al método de ajuste, que es responsable de entrenar el modelo CatBoost, vamos a entender algunos de los muchos parámetros que hacen que este modelo funcione.


Parámetro

Descripción

Iterations


Este es el número de iteraciones de árboles de decisión a construir.

Más iteraciones dan como resultado un mejor rendimiento, pero también conllevan un riesgo de sobreajuste.


learning_rate               
  

Este factor controla la distribución de cada árbol hasta la predicción final.

Una tasa de aprendizaje menor requiere más iteraciones para que los árboles converjan, pero a menudo da como resultado mejores modelos.


depth


Esta es la profundidad máxima de los árboles.

Los árboles más profundos pueden capturar patrones más complejos en los datos pero, a menudo, pueden llevar a un sobreajuste.


cat_features


Esta es una lista de índices categóricos.
A pesar de que el modelo CatBoost es capaz de detectar las características categóricas, es una buena práctica instruir explícitamente al modelo sobre qué características son categóricas.
Esto ayuda al modelo a comprender las características categóricas desde una perspectiva humana, ya que los métodos para detectar automáticamente las variables categóricas a veces pueden fallar.

l2_leaf_reg


Se trata del coeficiente de regularización L2.

Ayuda a prevenir el sobreajuste al penalizar los pesos de hojas más grandes.


border_count
 


Es el número de divisiones para cada característica categórica. Cuanto mayor sea este número, mejor será el rendimiento y aumentará el tiempo de cálculo.



eval_metric


Esta es la métrica de evaluación que se utilizará durante el entrenamiento.

Ayuda a monitorear eficazmente el rendimiento del modelo.


early_stopping_rounds


Cuando se proporcionan datos de validación al modelo, el progreso del entrenamiento se detendrá si no se observa ninguna mejora en la precisión del modelo para esta cantidad de rondas.

Este parámetro ayuda a reducir el sobreajuste y puede ahorrar mucho tiempo de entrenamiento.

Podemos definir un diccionario para los parámetros anteriores.

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
)

Por último, podemos definir el modelo Catboost dentro de la pipeline de Sklearn, luego llamar al método fit para el entrenamiento dándole a este método datos de evaluación y una lista de características categóricas.

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)

Salidas:

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.


Evaluación del modelo

Podemos utilizar las métricas de Sklearn para evaluar el rendimiento de este modelo.

# 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))

Salidas:

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

El resultado es un modelo con un rendimiento promedio. Me di cuenta de que después de eliminar la lista de características categóricas, la precisión del modelo aumentó al 60 % en el conjunto de entrenamiento, pero se mantuvo igual en el conjunto de prueba.

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

Resultado

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

Para comprender este modelo con más detalle, creemos el gráfico de importancia de las características.

# 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()

Salida:

El «gráfico de importancia de las características» anterior cuenta toda la historia de cómo el modelo tomaba las decisiones. Parece que el modelo CatBoost consideró las variables categóricas como las características más impactantes en el resultado final previsto del modelo que las variables continuas.


Guardando el modelo CatBoost en ONNX

Para utilizar este modelo en MetaTrader 5 tenemos que guardarlo en formato ONNX. Ahora bien, guardar el modelo CatBoost puede ser un poco complicado, a diferencia de los modelos Sklearn y Keras, que vienen con métodos para una conversión más sencilla.

Pude guardarlo tras seguir las instrucciones de la documentación. No me molesté en entender la mayor parte del código.

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.
Esta función lo agrega al gráfico principal.
    """
    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"]},
)

A continuación se muestra la conversión del modelo final y su guardado en una extensión de archivo .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())

Cuando se visualiza en Netron, la estructura del modelo parece igual que XGBoost y LightGBM.

Modelo CatBoost en Netron

Tiene sentido ya que es simplemente otro árbol de decisiones potenciado por gradiente. Tienen estructuras similares en su núcleo.

Cuando intenté convertir el modelo CatBoost en un pipeline a ONNX con variables categóricas el proceso falló, arrojando un error.

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

Tuve que asegurarme de que las variables categóricas estuvieran en tipo float64 (double) tal y como las recogimos inicialmente en MetaTrader 5, Esto soluciona el error y ayuda a que cuando trabajemos con este modelo en MQL5 no tengamos que preocuparnos de mezclar valores double o float con enteros.

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

A pesar de este cambio, el modelo CatBoost no se vio afectado (siempre que mantenga los mismos valores de precisión) ya que puede trabajar con conjuntos de datos de diversa naturaleza.


Creación de un asesor experto CatBoost (robot comercial)

Podemos comenzar integrando el modelo ONNX dentro del Asesor Experto principal como recurso.

Código MQL5

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

Importamos la librería para cargar el modelo CatBoost.

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

Tenemos que recopilar datos de la misma manera que recopilamos los datos de entrenamiento dentro de la función OnTick.

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
...
...
...     
  }

Luego finalmente podemos obtener la señal prevista y el vector de probabilidad entre la clase de señal bajista 0 y la clase de señal alcista 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);

Podemos finalizar este EA codificando una estrategia comercial basada en las predicciones obtenidas del modelo. 

La estrategia es simple, cuando el modelo predice una señal alcista compramos y cerramos una operación de venta si existe. Hacemos lo contrario cuando el modelo predice una señal bajista.

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
        }
  }

Ejecuté una prueba en el Probador de estrategias desde el 01/01/2021 hasta el 08/10/2024 en un período de 4 horas. El modelado se configuró en "Solo precios de apertura". A continuación el resultado.

'

El EA lo hizo bien, por decir lo menos, proporcionando un 55% de operaciones rentables que proporcionaron un beneficio neto total de 96 USD. No está mal para un conjunto de datos simple, un modelo simple y una configuración de volumen comercial mínimo.


Reflexiones finales

CatBoost y otros árboles de decisión potenciados por gradiente son una solución ideal cuando se trabaja en un entorno de recursos limitados y se busca un modelo que "simplemente funcione" sin tener que hacer muchas de las tareas aburridas y a veces innecesarias que implica la ingeniería de características y la configuración del modelo que a menudo enfrentamos cuando trabajamos con numerosos modelos de máquinas. 

A pesar de su simplicidad y su mínima barrera de entrada, se encuentran entre los mejores y más efectivos modelos de IA utilizados en muchas aplicaciones del mundo real.

Saludos cordiales.


Siga el desarrollo de modelos de aprendizaje automático y mucho más discutido en esta serie de artículos en este repositorio de GitHub.

Tabla de archivos adjuntos


Nombre del archivo

Tipo de archivoDescripción y uso

Experts\CatBoost EA.mq5

 Asesor expertoRobot comercial para cargar el modelo Catboost en formato ONNX y probar la estrategia comercial en MetaTrader 5.

Include\CatBoost.mqh

 Archivo de inclusión


Contiene el código para cargar e implementar un modelo CatBoost.


Files\ CatBoost.EURUSD.OHLC.D1.onnx


 Modelo ONNX


Modelo CatBoost entrenado guardado en formato ONNX.

 
Scripts\CollectData.mq5

 Script MQL5Un script para recopilar los datos de entrenamiento. 

Jupyter Notebook\CatBoost-4-trading.ipynb 

 Notebook de Python/Jupyter   Todo el código en Python mencionado en este artículo se encuentra dentro de este notebook.


Fuentes y referencias

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/16017

Archivos adjuntos |
Attachments.zip (40.07 KB)
Maxim Dmitrievsky
Maxim Dmitrievsky | 27 may 2025 en 13:01
Todos los clasificadores (incluido catboost) funcionan correctamente sólo con atributos normalizados. Los precios como atributos no son adecuados.
Aliaksandr Kazunka
Aliaksandr Kazunka | 27 may 2025 en 18:52

y también existe el problema de exportar el modelo clasificador a ONNX


Nota

La etiqueta se infiere incorrectamente para la clasificación binaria. Se trata de un error conocido en la implementación de onnxruntime. Ignore el valor de este parámetro en caso de clasificación binaria.

zhai nan
zhai nan | 11 jul 2025 en 10:05
¡Precio no se puede utilizar como datos de entrenamiento, a principios del año pasado he utilizado el precio del oro para entrenar el modelo, cuando el precio del oro sigue alcanzando nuevos máximos, la entrada al modelo de estos nuevos datos de precios altos, el modelo no reconoce estos datos, no importa cómo dar cómo cambiar y superar el precio más alto del precio de los datos de datos de entrenamiento, dar una probabilidad constante de valor!
Yutaka Okamoto
Yutaka Okamoto | 12 ago 2025 en 05:45
Muchas gracias por este artículo tan útil.

Tengo una pequeña pregunta o preocupación que espero compartir.

When I tried to convert the CatBoost model in a pipeline to ONNX con variables categoricas el proceso falla, arrojando un error.


Creo que el problema subyacente puede estar relacionado con lo que se describe aquí:

https://catboost.ai/docs/en/concepts/apply-onnx-ml

Particularidades:

Actualmente sólo se soportan modelos entrenados en conjuntos de datos sin características categóricas.


En el Jupyter Notebook catboost-4-trading.ipynb que he descargado, el código de ajuste del pipeline está escrito como:

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

Parece que se ha omitido el parámetro"catboost__cat_features=categorical_features", por lo que es posible que el modelo se haya entrenado sin especificar características categóricas.

Esto podría explicar por qué el modelo pudo guardarse como ONNX sin ningún problema.

Si este es el caso, entonces tal vez el método nativo de CatBoost"save_model" podría ser utilizado directamente, así:

model = pipe.named_steps['catboost']

model_filename = "CatBoost.EURUSD.OHLC.D1.onnx"

model.save_model(model_filename, format='onnx')

Espero que esta observación pueda ser útil.

Aleksey Vyazmikin
Aleksey Vyazmikin | 12 ago 2025 en 08:04
border_count es una partición en bins (segmentos cuánticos) para cualquier característica, no sólo para características categóricas.
Del básico al intermedio: Plantilla y Typename (II) Del básico al intermedio: Plantilla y Typename (II)
En este artículo, mostraremos cómo lidiar con una de las situaciones más molestas y complicadas en términos de programación con las que tú podrías encontrarte: el uso de tipos diferentes en una misma plantilla de función o procedimiento. Aunque nos hemos enfocado casi todo el tiempo solo en funciones, todo lo que se ha visto aquí sirve y puede aplicarse a procedimientos.
Del básico al intermedio: Plantilla y Typename (I) Del básico al intermedio: Plantilla y Typename (I)
En este artículo, comenzaremos a tratar uno de los conceptos que muchos principiantes evitan. Esto se debe a que las plantillas no son un tema sencillo de entender y utilizar, ya que muchos no comprenden el principio básico detrás de lo que sería una plantilla: la sobrecarga de funciones y procedimientos.
Simulación de mercado (Parte 04): Creación de la clase C_Orders (I) Simulación de mercado (Parte 04): Creación de la clase C_Orders (I)
En este artículo comenzaremos a construir la clase C_Orders para poder enviar órdenes al servidor de negociación. Lo haremos poco a poco, ya que el objetivo es explicar detalladamente cómo se realizará esto a través del sistema de mensajería.
Características del Wizard MQL5 que debe conocer (Parte 42): Oscilador ADX Características del Wizard MQL5 que debe conocer (Parte 42): Oscilador ADX
El ADX es otro indicador técnico relativamente popular utilizado por algunos traders para medir la fuerza de una tendencia predominante. Actuando como una combinación de otros dos indicadores, se presenta como un oscilador cuyos patrones exploramos en este artículo con la ayuda del asistente de ensamblaje MQL5 y sus clases de soporte.