
Aprendizaje automático y Data Science (Parte 31): Uso de los modelos de inteligencia artificial CatBoost
«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 | CatBoost | LightGBM | XGBoost |
---|---|---|---|
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
Open | High | Low | Close | Day | DayofWeek | DayofYear | Month | |
---|---|---|---|---|---|---|---|---|
0 | 1.09381 | 1.09548 | 1.09003 | 1.09373 | 21.0 | 3.0 | 264.0 | 9.0 |
1 | 1.09678 | 1.09810 | 1.09361 | 1.09399 | 22.0 | 4.0 | 265.0 | 9.0 |
2 | 1.09701 | 1.09973 | 1.09606 | 1.09805 | 23.0 | 5.0 | 266.0 | 9.0 |
3 | 1.09639 | 1.09869 | 1.09542 | 1.09742 | 26.0 | 1.0 | 269.0 | 9.0 |
4 | 1.10302 | 1.10396 | 1.09513 | 1.09757 | 27.0 | 2.0 | 270.0 | 9.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
Open | High | Low | Close | Day | DayofWeek | DayofYear | Month | target_close | target_open | signal | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1.09381 | 1.09548 | 1.09003 | 1.09373 | 21.0 | 3.0 | 264.0 | 9.0 | 1.09399 | 1.09678 | 0 |
1 | 1.09678 | 1.09810 | 1.09361 | 1.09399 | 22.0 | 4.0 | 265.0 | 9.0 | 1.09805 | 1.09701 | 1 |
2 | 1.09701 | 1.09973 | 1.09606 | 1.09805 | 23.0 | 5.0 | 266.0 | 9.0 | 1.09742 | 1.09639 | 1 |
3 | 1.09639 | 1.09869 | 1.09542 | 1.09742 | 26.0 | 1.0 | 269.0 | 9.0 | 1.09757 | 1.10302 | 0 |
4 | 1.10302 | 1.10396 | 1.09513 | 1.09757 | 27.0 | 2.0 | 270.0 | 9.0 | 1.10297 | 1.10431 | 0 |
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.
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 archivo | Descripción y uso |
---|---|---|
Experts\CatBoost EA.mq5 | Asesor experto | Robot comercial para cargar el modelo Catboost en formato ONNX y probar la estrategia comercial en MetaTrader 5. |
Include\CatBoost.mqh | Archivo de inclusión |
|
Files\ CatBoost.EURUSD.OHLC.D1.onnx | Modelo ONNX | Modelo CatBoost entrenado guardado en formato ONNX. |
Scripts\CollectData.mq5 | Script MQL5 | Un 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
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
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.
Tengo una pequeña pregunta o preocupación que espero compartir.
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.