English Русский Español Deutsch 日本語
preview
数据科学和机器学习(第 31 部分):利用 CatBoost AI 模型进行交易

数据科学和机器学习(第 31 部分):利用 CatBoost AI 模型进行交易

MetaTrader 5交易 |
44 5
Omega J Msigwa
Omega J Msigwa

 “CatBoost 是一个梯度增强库,它是如此与众不同,以高效、且可扩展的方式处理分类特征,为许多现世问题提供了显著的性能提升。”

— 安东尼·戈德布鲁姆(Anthony Goldbloom)。


什么是 CatBoost?

CatBoost 是一个基于决策树的梯度增强算法开源软件库,它专为解决机器学习中处理分类特征和数据的挑战而设计。

它由 Yandex 开发,并于 2017 年开源,阅读更多

尽管与线性回归或 SVM 等机器学习技术相比,CatBoost 是最近推出的,但它在 AI 社区中获得广泛欢迎,并在如 Kaggle 这样的平台上爬升到最常用机器学习模型的榜首。

CatBoost 之所以获得如此多的关注,是因其自动处理数据集中分类特征的能力,这对许多机器学习算法来说颇具挑战性。

  • 相比其它模型,Catboost 模型通常以最小的工作量提供更好的性能,即使按默认参数和设置,这些模型也能在准确性方面执行优良。
  • 不同于需要领域知识进行编码、并令其工作的神经网络,CatBoost 的实现很简单。

本文假设您对机器学习、决策树XGBoos、LightGBMtONNX 均有基本了解。


CatBoost 如何运作?

CatBoost 基于梯度增强算法,就像 “轻量梯度机器”(LightGBM)和 “极值梯度增强”(XGBoost)一样。它的工作原理是按顺序构建若干决策树模型,其中每个模型都建立在前一个模型的基础上,因其试图纠正之前模型的误差。

最终预测是把该过程中所涉及的全部模型所做预测的加权合计。

这些梯度增强决策树(GBDT)的意向是把损失函数最小化,而这是通过添加一个新模型来纠正之前模型的误差来实现的。


处理分类特征

正如我早前所解释,CatBoost 能处理分类特征,无需其它机器学习模型所需的显式编码,像是独热编码、或标签编码,这是因为 CatBoost 为分类特征引入了基于目标的编码。

计算每个分类特征值的目标条件分布来执行编码。

CatBoost 中的另一个关键创新就是在计算分类特征的统计数据时使用有序增强。这种有序增强可确保任何实例的编码都基于以前所观察数据点的信息。

这有助于避免数据泄漏,及避免过度拟合。


它使用对称决策树结构

不同于使用非对称树的 LightGBM 和 XGBoost,CatBoost 使用对称决策树进行决策过程。在对称树中,每个切分处的左右分支构造都是基于相同的切分规则,这种方式有若干优点,例如:

  • 它可以加快训练速度,因为两个切分是对称的。
  • 由于树结构更简单,因此内存使用效率更高。
  • 对称树针对数据中的小扰动更可靠。


CatBoost 对比 XGBoost 和 LightGBM,详细比较

我们了解 CatBoost 与其它梯度增强决策树有何不同。我们了解这三者之间的区别,这样我们就知道何时使用这三者之一,何时不用。


层面

CatBoostLightGBMXGBoost

分类特征处理

依据自动编码和有序增强,来处理分类变量。需要手工编码处理,诸如独热编码、标签编码、等等。需要手工编码处理,诸如独热编码、标签编码、等等。

决策树结构

它具有对称决策树,且是平衡的、并均匀增长。它们可确保更快的预测、和更低的过拟合风险。
它有一个逐叶生长策略(不对称),其专注于损失最大的叶片。这种结果出现在深度和不平衡的树,其能运营更高的精度,但过拟合的风险更大。


它有一个“级别增长策略”(非对称),树的增长基于每个节点的最佳切分。这会导致预测灵活,但速度较慢,且存在过度拟合的潜在风险。


模型准确性


在处理包含许多分类特征的数据集时,因为有序增强、并降低了对较小数据进行过度拟合的风险,它们提供了良好的准确性。

特别是在大型和高维数据集上,因为逐叶生长策略专注于提升高误差区域的性能,故它们提供了良好的准确性。它们能在大多数数据集上提供良好的准确性,但由于 LightGBM 的树生长策略不那么激进,因此在分类数据集上往往优于 CatBoost,在非常大的数据集上表现优于 LightGBM。

训练速度和准确性


通常比 LightGBM 训练慢,但在中小型数据集上效率更高,尤其是在涉及分类特征时。

通常是这三者中最快的,尤其是在大型数据集上,因为它的叶状树生长在高维数据中效率更高。通常是这三个中最慢的。它对于大型数据集非常有效。 


部署 CatBoost 模型

在我们为 CatBoost 编写代码之前,我们创建一个问题场景。我们想用数据来预测交易信号(Buy/Sell);开盘价、最高价、最低价、收盘价和几个分类特征,例如日期(即当前日期)、星期几(从星期一到星期日)、一年中的日期(从 1 到 365)、和月份(从 1 月到 12 月)。

OHLC(开盘价,最高价,最低价,收盘价)值是连续特征,而其余值是分类特征。收集数据的脚本位于附件之中

在安装之后,我们首先导入 CatBoost 模型。

安装

命令行

pip install catboost

导入。

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

我们能可视化数据,看看它的全部内容。

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

df.head()

成果

开盘价最高价最低价收盘价月内日期周内日期年内日期
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

在 MQL5 脚本中收集数据时,我把获取的 DayofWeek(周一至周日)、和 Month(1 月 - 12 月)值以整数取代字符,因为我要把它们存储在矩阵中,故不允许我添加字符串,尽管它们本质上是分类变量,但现在它们还不是,故我们要再次将它们转换为分类,看看 CatBoost 如何处理它们。

我们准备目标变量。

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

成果

开盘价最高价最低价收盘价月内日期周内日期年内日期target_closetarget_open信号
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

现在我们有了可以预测的信号,我们将数据拆分为训练样本和测试样本。

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)

我们定义了一份列表,列举数据集中存在的分类特征。

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

然后,我们能用该列表将这些分类特征转换为字符串格式,因为分类变量通常采用字符串格式。

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

成果

<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

现在,我们有了含有对象数据类型的分类变量,其数据类型是字符串数据类型。如果您尝试将此数据用于 CatBoost 以外的机器学习模型,则会出现错误,因为典型的机器学习训练数据中不允许使用这样的对象数据类型。


训练 CatBoost 模型

在调用负责训练 CatBoost 模型的 fit 方法之前,我们先了解一些该模型正常运行的众多参数。


参数

说明

Iterations迭代


这是要构建的决策树迭代数。

迭代次数越多,性能越好,但也存在过度拟合的风险。


learning_rate               
  

该因子控制每棵树到最终预测的分布。

较小的学习率需要更多的迭代才能令树收敛,但通常会产生更好的模型。


depth


这是树的最大深度。

较深的树可在数据中捕获更复杂的形态,但通常可能导致过度拟合。


cat_features


这是分类索引的列表。
尽管 CatBoost 模型能够检测分类特征,但最好明确指示模型的哪些特征是分类特征。
这有助于模型从人类的角度理解分类特征,因为自动检测分类变量的方法有时会失败。

l2_leaf_reg


这是 L2 正则化系数。

它有助于通过惩罚较大的叶片权重来防止过度拟合。


border_count
 


这是每个分类特征的切分数量。该数字越高,性能越好,并增加计算时间。



eval_metric


这是训练期间将用到的评估量值。

它有助于有效地监控模型性能。


early_stopping_rounds


当往模型提供验证数据时,如果在该轮数内未观察到模型准确性的提高,则训练进度将停止。

该参数有助于减少过拟合,并可节省大量训练时间。

我们可为上述参数定义一个字典。

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
)

最后,我们可在 Sklearn 管道中定义 Catboost 模型,然后调用 fit 方法进行训练,给出该方法评估数据和分类特征列表。

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)

输出

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.


评估模型

我们可用 Sklearn 量值来评估该模型的性能。

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

输出

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

成果是一个性能一般的模型。我意识到,在删除分类特征列表后,模型准确率在训练集上上升到 60%,但在测试集中保持不变。

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

成果

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

为了更详尽地理解这个模型,我们来创建特征重要性图板。

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

输出:

上面的 “特征重要性图板” 说出了模型如何做出决策的整个故事。CatBoost 模型似乎认为分类变量比连续变量对模型的最终预测结果影响最大。


将 CatBoost 模型保存为 ONNX

为了在 MetaTrader 5 中使用该模型,我们必须将其保存为 ONNX 格式。现在保存 CatBoost 模型可能有点棘手,不像 Sklearn 和 Keras 模型,它们的方法更容易转换。

按照文档中的说明操作,我已能够保存它。我对理解大部分代码没有耐心。

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"]},
)

下面是模型的最终转换,并将其保存为 .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())

在 Netron 中可视化时,模型结构看起来与 XGBoost 和 LightGBM 相同。

netron 中的 catboost 模型

这是有意义的,因为它只是另一棵梯度增强决策树。它们的核心结构相似。

当我尝试将管道中的 CatBoost 模型转换为具有分类变量的 ONNX 时,该过程失败,并引发错误。

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

我必须确保分类变量是 float64(双精度)类型,就像我们最初在 MetaTrader 5 中收集它们一样,这可修复错误,并有助于在 MQL5 中与该模型打交道,我们不必担心将双精度或浮点值与整数混合。

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

尽管如此修改,但 CatBoost 模型并未受到影响(提供相同的准确率值),因为它可处理各种性质的数据集。


创建 CatBoost 智能交易系统(交易机器人)

我们可先将 ONNX 模型作为资源嵌入到主智能系统当中。

MQL5 代码

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

我们导入加载 CatBoost 模型的函数库。

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

我们必须按照在 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
...
...
...     
  }

然后,我们最终可获得预测信号和熊市信号类别 0、与牛市信号类别 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);

我们可据从模型获得的预测编写交易策略来包装这个 EA。 

策略很简单,当模型预测到看涨信号时,我们买入并把做空交易(如果存在)平仓。当模型预测看跌信号时,我们反向行事。

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

我在策略测试器上据 2021.01.01 到 2024.10.8、H4 时间帧进行了测试,建模设置为“仅开盘价”。下面是成果

'

EA 做得很好,至少可以说,提供了 55% 的盈利交易,提供了总共 96 美元的净盈利。对于简单的数据集、简单的模型、和最小的交易量设置来说,这还不错。


后记

当您在有限的资源环境中工作,并寻找一个 “正常工作 ”的模型时,CatBoost 和其它梯度增强决策树是首选解决方案,无需被迫去做很多无聊的、有时是不必要的工作,而这些涉及我们在处理大量机器模型时经常面临的特征工程和模型配置。 

尽管它们简单、且入场门槛最小,但它们是在许多实际应用中能用的最好、及最有效的 AI 模型之一。

此致敬意。


跟踪机器学习模型的开发,在 GitHub 存储库 上有更多本系列文章的讨论内容。

附件表


文件名

文件类型说明/用法

Experts\CatBoost EA.mq5

 智能系统交易机器人,以 ONNX 格式加载 Catboost 模型,并在 MetaTrader 5 中测试交易策略。

Include\CatBoost.mqh

 包含文件


包含加载和部署 CatBoost 模型的代码。


Files\ CatBoost.EURUSD.OHLC.D1.onnx


 ONNX 模型


以 ONNX 格式保存的经训练的 CatBoost 模型。

 
MQL5 脚本

 MQL5 脚本收集训练数据的脚本。 

Jupyter Notebook\CatBoost-4-trading.ipynb 

 Python/Jupyter 笔记簿  本文中讨论的所有 Python 代码都可在该笔记簿中找到。


源码 & 参考

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/16017

附加的文件 |
Attachments.zip (40.07 KB)
最近评论 | 前往讨论 (5)
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 15 10月 2024 在 20:28
你的文章发人深省。

我在想,如果我们也能追踪我们所处的交易时段,会发生什么呢?
Omega J Msigwa
Omega J Msigwa | 16 10月 2024 在 08:23
是的,交易时段 是训练数据中的一个重要变量
Maxim Dmitrievsky
Maxim Dmitrievsky | 27 5月 2025 在 13:01
所有分类器(包括catboost)只有在属性归一化的情况下才能正常工作。价格作为属性并不合适。
Aliaksandr Kazunka
Aliaksandr Kazunka | 27 5月 2025 在 18:52

还有将分类器模型导出到 ONNX 的问题


注释

二进制分类的标签推断不正确。这是 onnxruntime 实现中的一个已知错误。如果是二进制分类,请忽略此参数的值。

zhainan
zhainan | 11 7月 2025 在 10:05
价格是不能做为训练数据的,去年年初我采用黄金的价格训练模型,当黄金的价格不断创新高时,输入给模型这些创新高的价格数据时,模型不认识这些数据,无论给怎样变化且超出训练数据最高价格的价格数据,给出的都是恒定概率值
开发回放系统(第 68 部分):取得正确的时间(一) 开发回放系统(第 68 部分):取得正确的时间(一)
今天,我们将继续努力,让鼠标指针告诉我们在流动性较低期间,一根柱形上还剩下多少时间。尽管乍一看似乎很简单,但实际上这项任务要困难得多。这涉及一些我们必须克服的障碍。因此,为了理解以下部分,您必须很好地理解子系列第一部分的材料。
价格行为分析工具包开发(第二部分):分析注释脚本 价格行为分析工具包开发(第二部分):分析注释脚本
秉承我们简化价格行为分析的核心理念,我们很高兴推出又一款可显著提升市场分析能力、助力您做出精准决策的工具。该工具可展示关键技术指标(如前一日价格、重要支撑阻力位、成交量),并在图表上自动生成可视化标记。
将互信息作为渐进特征选择的准则 将互信息作为渐进特征选择的准则
在本文中,我们展示了基于最优预测变量集与目标变量之间互信息渐进特征选择的MQL5实现。
DoEasy.服务函数(第 3 部分):外包线形态 DoEasy.服务函数(第 3 部分):外包线形态
在本文中,我们将开发 DoEasy 库中的外包线(Outside Bar)价格行为形态,并优化访问价格形态管理的方法。此外,我们将修复在库测试中发现的错误和缺点。