《金融大模型开发与应用实践》的背景,我将详细阐述如何使用 C++ 实现基于梯度提升树(Gradient Boosting Trees)的分类器列表,用于预测苹果公司(AAPL)股票周收益的正负方向(分类任务:上涨为 1,下跌为 0)。我将结合您提到的多种梯度提升树算法(AdaBoost、Gradient Boosting、Histogram-based Gradient Boosting、CatBoost、XGBoost),并确保设置相同的随机种子(random_state=123)以保证结果可重复性。由于您提到使用 ROC-AUC 作为评估指标,我将实现相应的评估逻辑,并结合特征工程、模型训练、预测和交易策略评估。


5.7 构建模型

5.7.1 梯度提升树分类器列表

目标:构建一个包含多种梯度提升树分类器的列表,预测 AAPL 股票周收益的涨跌方向。

  • 分类器
    • AdaBoost:通过迭代弱分类器(如决策树)加权组合,注重错误样本。
    • Gradient Boosting:经典梯度提升树,优化损失函数(如对数损失)。
    • Histogram-based Gradient Boosting:基于直方图的梯度提升(如 LightGBM),高效处理大规模数据。
    • CatBoost:优化处理类别特征,适合金融数据的复杂特征。
    • XGBoost:高性能梯度提升树,支持正则化和并行计算。
  • 随机种子:设置 random_state=123 以确保可重复性。
  • 评估指标:ROC-AUC 分数,衡量模型区分上涨和下跌的能力。
  • 数据:AAPL 30年历史数据,转换为周收益,特征包括滞后期、滚动统计、L-矩等。

C++ 的优势在于高效计算和低延迟,适合实时预测和交易场景。以下是具体实现步骤。


1. 数据准备

1.1 数据来源
  • AAPL 历史数据:包含 {Date, Open, High, Low, Close, Volume},转换为周收益 r_t = (Close_t - Close_{t-1}) / Close_{t-1}
  • 目标变量:将周收益二值化为分类标签:
    • y_t = 1(上涨):若 r_t > 0
    • y_t = 0(下跌):若 r_t <= 0
  • 时间范围:30年数据,预测最后 90 天(约 13 周)。
1.2 C++ 数据读取与标签生成
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
#include <Eigen/Dense>

struct StockData {
    std::string date;
    double open, high, low, close, volume;
};

std::vector<StockData> loadStockData(const std::string& filename) {
    std::vector<StockData> data;
    std::ifstream file(filename);
    std::string line;
    std::getline(file, line); // 跳过表头
    while (std::getline(file, line)) {
        std::stringstream ss(line);
        StockData row;
        std::string token;
        std::getline(ss, row.date, ',');
        std::getline(ss, token, ','); row.open = std::stod(token);
        std::getline(ss, token, ','); row.high = std::stod(token);
        std::getline(ss, token, ','); row.low = std::stod(token);
        std::getline(ss, token, ','); row.close = std::stod(token);
        std::getline(ss, token, ','); row.volume = std::stod(token);
        data.push_back(row);
    }
    file.close();
    return data;
}

// 计算周收益并生成分类标签
struct LabeledData {
    std::vector<double> returns;
    Eigen::VectorXd labels; // 1: 上涨, 0: 下跌
};

LabeledData calculateWeeklyReturnsAndLabels(const std::vector<StockData>& data, int days_per_week = 5) {
    LabeledData result;
    for (size_t i = days_per_week; i < data.size(); i += days_per_week) {
        double prev_close = data[i - days_per_week].close;
        double curr_close = data[i].close;
        double weekly_return = (curr_close - prev_close) / prev_close;
        result.returns.push_back(weekly_return);
        result.labels.conservativeResize(result.labels.size() + 1);
        result.labels(result.labels.size() - 1) = (weekly_return > 0) ? 1.0 : 0.0;
    }
    return result;
}
1.3 特征工程

基于前述问题,特征包括:

  • 滞后期:过去 1-4 周的收益、Open、Close、Volume、High。
  • 滚动统计:5 周均值、标准差。
  • L-矩:L-均值、L-方差。
  • 技术指标:RSI、MACD。
struct FeatureData {
    Eigen::VectorXd labels; // 分类标签
    Eigen::MatrixXd features; // 特征矩阵
};

FeatureData prepareData(const std::vector<StockData>& data, int lag = 4, int window = 5) {
    FeatureData result;
    LabeledData labeled = calculateWeeklyReturnsAndLabels(data);
    result.labels = labeled.labels;
    Eigen::VectorXd returns = Eigen::Map<Eigen::VectorXd>(labeled.returns.data(), labeled.returns.size());

    // 特征矩阵
    int num_features = lag + 2 + 2 + 4 * (lag + 2); // 滞后 + 滚动统计 + L-矩 + 其他列
    Eigen::MatrixXd features(returns.size(), num_features);
    features.setZero();

    // 滞后期
    for (int i = 0; i < lag; ++i) {
        for (size_t j = i + 1; j < returns.size(); ++j) {
            features(j, i) = returns[j - i - 1];
        }
    }

    // 滚动统计
    Eigen::VectorXd rolling_mean = returns;
    for (size_t i = window - 1; i < returns.size(); ++i) {
        rolling_mean(i) = returns.segment(i - window + 1, window).mean();
    }
    features.col(lag) = rolling_mean;
    features.col(lag + 1) = (returns - rolling_mean).array().square();

    // L-矩
    features.col(lag + 2).setConstant(returns.mean());
    features.col(lag + 3).setConstant((returns.array() - returns.mean()).square().mean());

    // 其他列(Open, Close, Volume, High)
    std::vector<Eigen::VectorXd> other_cols;
    for (const auto& col : {&StockData::open, &StockData::close, &StockData::volume, &StockData::high}) {
        Eigen::VectorXd col_data(data.size());
        for (size_t i = 0; i < data.size(); ++i) {
            col_data(i) = (data[i].*col);
        }
        col_data = normalize(col_data);
        other_cols.push_back(col_data);
        for (int i = 0; i < lag; ++i) {
            for (size_t j = i + 1; j < col_data.size(); ++j) {
                features(j, lag + 4 + i) = col_data[j - i - 1];
            }
        }
        Eigen::VectorXd col_mean = col_data;
        for (size_t i = window - 1; i < col_data.size(); ++i) {
            col_mean(i) = col_data.segment(i - window + 1, window).mean();
        }
        features.col(lag + 4 + lag) = col_mean;
        features.col(lag + 4 + lag + 1) = (col_data - col_mean).array().square();
    }

    // 删除缺失值
    int valid_rows = returns.size() - lag;
    result.features = features.bottomRows(valid_rows);
    result.labels = result.labels.tail(valid_rows);
    return result;
}

Eigen::VectorXd normalize(const Eigen::VectorXd& data) {
    double mean = data.mean();
    double stddev = std::sqrt((data.array() - mean).square().sum() / data.size());
    return (data.array() - mean) / stddev;
}

2. 梯度提升树分类器列表

我们将实现以下分类器:

  • AdaBoost:使用简单的决策树作为弱分类器。
  • Gradient Boosting:经典梯度提升,使用对数损失。
  • Histogram-based Gradient Boosting:使用 LightGBM。
  • XGBoost:高性能梯度提升。
  • CatBoost:需通过 Python 接口调用,C++ 可加载预训练模型。

由于 C++ 原生支持有限,CatBoost 训练建议在 Python 中完成,导出模型后用 C++ 推理。

2.1 设置随机种子

C++ 中通过 std::mt19937 设置随机种子,确保结果可重复。

#include <random>

void setRandomSeed(int seed) {
    std::mt19937 gen(seed);
    std::srand(seed);
}
2.2 AdaBoost 实现

AdaBoost 通过加权弱分类器组合预测。

#include <Eigen/Dense>

class DecisionStump {
    int feature_idx;
    double threshold;
    double polarity;

public:
    DecisionStump() : feature_idx(0), threshold(0.0), polarity(1.0) {}

    void fit(const Eigen::MatrixXd& X, const Eigen::VectorXd& y, const Eigen::VectorXd& weights) {
        double min_error = std::numeric_limits<double>::infinity();
        for (int i = 0; i < X.cols(); ++i) {
            for (int j = 0; j < X.rows(); ++j) {
                double thresh = X(j, i);
                for (double p : {1.0, -1.0}) {
                    double error = 0.0;
                    for (int k = 0; k < X.rows(); ++k) {
                        double pred = (p * X(k, i) < p * thresh) ? 1.0 : 0.0;
                        error += weights(k) * (pred != y(k));
                    }
                    if (error < min_error) {
                        min_error = error;
                        feature_idx = i;
                        threshold = thresh;
                        polarity = p;
                    }
                }
            }
        }
    }

    Eigen::VectorXd predict(const Eigen::MatrixXd& X) {
        Eigen::VectorXd preds(X.rows());
        for (int i = 0; i < X.rows(); ++i) {
            preds(i) = (polarity * X(i, feature_idx) < polarity * threshold) ? 1.0 : 0.0;
        }
        return preds;
    }
};

class AdaBoost {
    std::vector<DecisionStump> stumps;
    std::vector<double> alpha;

public:
    void fit(const Eigen::MatrixXd& X, const Eigen::VectorXd& y, int n_estimators) {
        Eigen::VectorXd weights = Eigen::VectorXd::Constant(X.rows(), 1.0 / X.rows());
        for (int i = 0; i < n_estimators; ++i) {
            DecisionStump stump;
            stump.fit(X, y, weights);
            Eigen::VectorXd preds = stump.predict(X);
            double error = ((preds.array() != y.array()).cast<double>() * weights).sum();
            alpha.push_back(0.5 * std::log((1.0 - error) / error));
            weights = weights.array() * (preds.array() == y.array()).cast<double>().exp().array() * std::exp(-alpha.back());
            weights /= weights.sum();
            stumps.push_back(stump);
        }
    }

    Eigen::VectorXd predict(const Eigen::MatrixXd& X) {
        Eigen::VectorXd preds = Eigen::VectorXd::Zero(X.rows());
        for (size_t i = 0; i < stumps.size(); ++i) {
            preds += alpha[i] * stumps[i].predict(X);
        }
        return (preds.array() > 0).cast<double>();
    }
};
2.3 XGBoost 实现

使用 XGBoost C++ API。

#include <xgboost/c_api.h>

void trainXGBoost(const Eigen::MatrixXd& X_train, const Eigen::VectorXd& y_train, const std::string& model_path) {
    DMatrixHandle dtrain;
    XGDMatrixCreateFromMat(X_train.data(), X_train.rows(), X_train.cols(), &dtrain);
    XGDMatrixSetFloatInfo(dtrain, "label", y_train.data(), y_train.size());

    BoosterHandle booster;
    XGBoosterCreate(&dtrain, 1, &booster);
    XGBoosterSetParam(booster, "objective", "binary:logistic");
    XGBoosterSetParam(booster, "max_depth", "6");
    XGBoosterSetParam(booster, "eta", "0.3");
    XGBoosterSetParam(booster, "seed", "123");

    for (int i = 0; i < 100; ++i) {
        XGBoosterUpdateOneIter(booster, i, dtrain);
    }

    XGBoosterSaveModel(booster, model_path.c_str());
    XGBoosterFree(booster);
    XGDMatrixFree(dtrain);
}

Eigen::VectorXd predictXGBoost(const Eigen::MatrixXd& X_test, const std::string& model_path) {
    DMatrixHandle dtest;
    XGDMatrixCreateFromMat(X_test.data(), X_test.rows(), X_test.cols(), &dtest);
    BoosterHandle booster;
    XGBoosterLoadModel(model_path.c_str(), &booster);

    bst_ulong out_len;
    const float* out_result;
    XGBoosterPredict(booster, dtest, 0, 0, &out_len, &out_result);
    Eigen::VectorXd preds(out_len);
    for (bst_ulong i = 0; i < out_len; ++i) {
        preds(i) = out_result[i] > 0.5 ? 1.0 : 0.0;
    }

    XGBoosterFree(booster);
    XGDMatrixFree(dtest);
    return preds;
}
2.4 LightGBM 实现(Histogram-based Gradient Boosting)
#include <lightgbm.h>

void trainLightGBM(const Eigen::MatrixXd& X_train, const Eigen::VectorXd& y_train, const std::string& model_path) {
    DatasetHandle dataset;
    LGBM_DatasetCreateFromMat(X_train.data(), C_API_DTYPE_FLOAT64, X_train.rows(), X_train.cols(), 1, nullptr, nullptr, &dataset);
    LGBM_DatasetSetField(dataset, "label", y_train.data(), y_train.size(), C_API_DTYPE_FLOAT64);

    BoosterHandle booster;
    LGBM_BoosterCreate(dataset, "objective=binary metric=auc seed=123 num_leaves=31 learning_rate=0.05 num_iterations=100", &booster);
    for (int i = 0; i < 100; ++i) {
        int is_finished;
        LGBM_BoosterUpdateOneIter(booster, &is_finished);
    }
    LGBM_BoosterSaveModel(booster, 0, -1, C_API_FEATURE_IMPORTANCE_GAIN, model_path.c_str());
    LGBM_BoosterFree(booster);
    LGBM_DatasetFree(dataset);
}

Eigen::VectorXd predictLightGBM(const Eigen::MatrixXd& X_test, const std::string& model_path) {
    BoosterHandle booster;
    LGBM_BoosterLoadModelFromFile(model_path.c_str(), nullptr, &booster);
    int64_t out_len;
    Eigen::VectorXd predictions(X_test.rows());
    LGBM_BoosterPredictForMat(booster, X_test.data(), C_API_DTYPE_FLOAT64, X_test.rows(), X_test.cols(), 1,
                              C_API_PREDICT_NORMAL, 0, "", &out_len, predictions.data());
    LGBM_BoosterFree(booster);
    return (predictions.array() > 0.5).cast<double>();
}
2.5 CatBoost(通过 Python 预训练)

CatBoost 训练需在 Python 中完成,导出模型后用 C++ 推理。

#include <catboost/c_api.h>

Eigen::VectorXd predictCatBoost(const Eigen::MatrixXd& X_test, const std::string& model_path) {
    ModelHandle model = CatBoostLoadModel(model_path.c_str());
    Eigen::VectorXd predictions(X_test.rows());
    CatBoostPredict(model, X_test.data(), X_test.rows(), X_test.cols(), predictions.data());
    CatBoostFreeModel(model);
    return (predictions.array() > 0.5).cast<double>();
}

3. 模型训练与评估

3.1 数据划分
  • 训练集:80% 数据。
  • 验证集:10% 数据,用于超参数调优。
  • 测试集:最后 90 天(约 13 周)。
FeatureData splitData(const FeatureData& data) {
    int train_size = static_cast<int>(data.features.rows() * 0.8);
    int val_size = static_cast<int>(data.features.rows() * 0.1);
    FeatureData split;
    split.features = data.features.topRows(train_size); // 训练集特征
    split.labels = data.labels.head(train_size); // 训练集标签
    split.features.conservativeResize(data.features.rows(), data.features.cols());
    split.features.middleRows(train_size, val_size) = data.features.middleRows(train_size, val_size); // 验证集
    split.labels.conservativeResize(data.labels.size());
    split.labels.segment(train_size, val_size) = data.labels.segment(train_size, val_size);
    split.features.bottomRows(data.features.rows() - train_size - val_size) = data.features.bottomRows(data.features.rows() - train_size - val_size); // 测试集
    split.labels.tail(data.labels.size() - train_size - val_size) = data.labels.tail(data.labels.size() - train_size - val_size);
    return split;
}
3.2 ROC-AUC 评估

计算 ROC-AUC 分数以评估分类器性能。

double calculateROCAUC(const Eigen::VectorXd& y_true, const Eigen::VectorXd& y_scores) {
    std::vector<std::pair<double, int>> pairs(y_true.size());
    for (int i = 0; i < y_true.size(); ++i) {
        pairs[i] = {y_scores(i), (int)y_true(i)};
    }
    std::sort(pairs.begin(), pairs.end(), std::greater<>());
    
    double auc = 0.0;
    int pos = 0, neg = 0;
    for (const auto& p : pairs) {
        if (p.second == 1) {
            pos++;
        } else {
            neg++;
            auc += pos;
        }
    }
    return auc / (pos * neg);
}
3.3 训练与预测
void trainAndEvaluate(const std::vector<StockData>& data) {
    setRandomSeed(123); // 设置随机种子
    FeatureData prepared = prepareData(data);
    FeatureData split = splitData(prepared);

    Eigen::MatrixXd X_train = split.features.topRows(split.features.rows() * 0.8);
    Eigen::VectorXd y_train = split.labels.head(split.labels.size() * 0.8);
    Eigen::MatrixXd X_val = split.features.middleRows(split.features.rows() * 0.8, split.features.rows() * 0.1);
    Eigen::VectorXd y_val = split.labels.segment(split.labels.size() * 0.8, split.labels.size() * 0.1);
    Eigen::MatrixXd X_test = split.features.bottomRows(split.features.rows() - split.features.rows() * 0.9);
    Eigen::VectorXd y_test = split.labels.tail(split.labels.size() - split.labels.size() * 0.9);

    // 训练 AdaBoost
    AdaBoost ada;
    ada.fit(X_train, y_train, 50);
    Eigen::VectorXd ada_preds = ada.predict(X_val);
    std::cout << "AdaBoost ROC-AUC: " << calculateROCAUC(y_val, ada_preds) << std::endl;

    // 训练 XGBoost
    trainXGBoost(X_train, y_train, "xgboost_model.bin");
    Eigen::VectorXd xgb_preds = predictXGBoost(X_val, "xgboost_model.bin");
    std::cout << "XGBoost ROC-AUC: " << calculateROCAUC(y_val, xgb_preds) << std::endl;

    // 训练 LightGBM
    trainLightGBM(X_train, y_train, "lightgbm_model.bin");
    Eigen::VectorXd lgb_preds = predictLightGBM(X_val, "lightgbm_model.bin");
    std::cout << "LightGBM ROC-AUC: " << calculateROCAUC(y_val, lgb_preds) << std::endl;

    // 测试集预测(以 LightGBM 为例)
    Eigen::VectorXd test_preds = predictLightGBM(X_test, "lightgbm_model.bin");
    std::cout << "Test ROC-AUC: " << calculateROCAUC(y_test, test_preds) << std::endl;
}

4. 交易策略

  • 策略 I:仅在预测上涨(y_pred = 1)时买入。
  • 策略 II:预测上涨时做多,预测下跌时做空。
double evaluateStrategy(const std::vector<double>& returns, const Eigen::VectorXd& predictions, int strategy) {
    double cum_return = 1.0;
    for (size_t i = 0; i < returns.size(); ++i) {
        if (strategy == 1) {
            if (predictions(i) == 1) {
                cum_return *= (1.0 + returns[i]);
            }
        } else {
            cum_return *= (predictions(i) == 1) ? (1.0 + returns[i]) : (1.0 - returns[i]);
        }
    }
    return cum_return - 1.0;
}

5. 与 Python 的结合

  • CatBoost 训练:在 Python 中使用 catboost 库训练,导出模型到 C++。
  • 数据预处理:Python(Pandas、NumPy)处理复杂特征,导出 CSV。
  • 推理:C++ 的 XGBoost 和 LightGBM API 实现高效预测。

6. 示例结果

基于 AAPL 最近 90 天数据:

  • 周收益(示例):
    • 2025-09-16:0.0068(上涨,标签 1)
    • 2025-09-23:0.0684(上涨,标签 1)
    • 2025-09-30:-0.0026(下跌,标签 0)
    • 2025-10-13:-0.0246(下跌,标签 0)
  • 模型性能(假设):
    • AdaBoost ROC-AUC:0.75
    • XGBoost ROC-AUC:0.82
    • LightGBM ROC-AUC:0.85
  • 交易策略:
    • 策略 I 累积回报:5%
    • 策略 II 累积回报:8%

7. 总结

C++ 实现梯度提升树分类器列表的流程包括:

  1. 数据准备:读取 AAPL 数据,计算周收益,生成分类标签。
  2. 特征工程:提取滞后、滚动统计、L-矩等特征。
  3. 模型训练:实现 AdaBoost、XGBoost、LightGBM,确保随机种子一致。
  4. 评估:使用 ROC-AUC 评估模型性能。
  5. 交易策略:基于预测结果执行策略 I 和 II。

C++ 的高性能适合实时预测,结合 Python 的训练生态可提升开发效率。如果需要更详细的代码(如 CatBoost 推理或策略优化),请进一步说明!

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐