← 返回文章列表
August 3, 2026
5 分钟阅读

集成方法:结合弱学习者以获得稳健的 Alpha

集成方法:结合弱学习者以获得稳健的 Alpha
#machine-learning
#ensemble
#bagging
#stacking
#alpha

大多数有关交易集合的文章都认为模型越多越好。那是错误的轴。整体误差分解为三项,在交易中只有其中一项是具有约束力的约束。

本文是关于这个术语的,以及本博客其余部分未涵盖的它的两个后果:堆叠为明确的信号组合层,以及组合许多信号是否实际上会在交易到达市场之前降低成交量。

实际绑定的术语:偏差-方差-协方差

对于回归系综 NN 模型,平均预测的预期平方误差 fˉ\bar{f} 分解为:

E[(fˉy)2]=bias2+1Nvariance+(11N)covarianceE\left[\left(\bar{f} - y\right)^2\right] = \overline{\text{bias}^2} + \frac{1}{N}\overline{\text{variance}} + \left(1 - \frac{1}{N}\right)\overline{\text{covariance}}

三个术语,三个杠杆:

  • Bagging 通过对引导样本进行平均来攻击方差项。
  • Boosting 通过迭代修正残差来攻击偏差项。
  • 堆叠通过学习组合权重而不是假设相等来攻击协方差项。
  • 增加 NN 仅缩小方差贡献。它对协方差毫无作用—— (11/N)(1 - 1/N) 系数已经是 0.99 N=100N = 100

最后一点就是整个论点。在交易中,模型是在相同的工具、相同的历史上针对目标进行训练的,这些目标本身就是相同价格序列的几乎相同的转换。他们的错误并不接近独立。因此,协方差项很大,而方差项已经用尽,这意味着模型数量几乎什么也买不到,而结构差异却可以买到一切。

该主张的具体、可证伪的形式是:添加第 101 棵梯度增强树应该比添加一个结构不同的模型对集成度量的移动要小——LSTM、不相交特征域上的线性模型,任何具有不同归纳偏差的模型。边缘树几乎与书中已有的树共线;结构上不同的模型则不然。

相关性(而不是模型计数)决定了整体的有效广度 - 推导、按资产类别加密测量的相关因子以及针对真实货币对数据的模拟位于信号相关性:要监控多少对

不要将集合多样性减少到一个数字。 紧缩夏普比率 计算五个有效-NN 真实 640 个单元网格上的估计器(平均相关性、参与率、PCA-95%、Kaiser、Cheverud-Nyholt)显示它们跨越 NeffN_{\text{eff}} 从 1.6 到 370,并认为该范围(而不是任何单点估计)是可交付的。举报乐队。

堆叠作为信号组合层

bagging 和 boosting 在其他地方都有很好的介绍。 Spread Modeling with Machine Learning 提供了梯度提升设置、倾斜目标的损失选择、SHAP 重要性发现、特征分类法以及带有激发间隙的前向窗口重叠的清除前向分割。从那里开始;本节假设它。

没有涵盖的是上面的级别:元学习器将基本模型预测作为其输入特征并学习如何组合它们。

  • 0 级(基础学习者): 根据原始特征生成预测的多种模型。
  • 1 级(元学习器): 一个模型,用于学习何时信任哪些基础学习器。

确保这种安全性的机制是折叠元功能。元学习器绝不能看到基于基础模型训练数据进行的基础模型预测——这些预测存在乐观偏差,无法在实时交易中生存,元学习器将相应地对它们进行加权。

import numpy as np
import pandas as pd
from sklearn.base import clone
from sklearn.model_selection import TimeSeriesSplit


class TradingStackingEnsemble:
    """
    Two-level stacking ensemble for return prediction.

    Level 0: base learners, each on its own feature domain
    Level 1: meta-learner trained on out-of-fold base predictions
    """

    def __init__(self, base_models: list, meta_model, n_splits: int = 5):
        self.base_models = base_models  # list of (name, model, feature_cols)
        self.meta_model = meta_model
        self.n_splits = n_splits
        self.fitted_base_models_ = {}

    def _generate_meta_features(
        self,
        X: pd.DataFrame,
        y: pd.Series
    ) -> pd.DataFrame:
        """Out-of-fold predictions from each base model."""
        tscv = TimeSeriesSplit(n_splits=self.n_splits)
        meta_features = pd.DataFrame(index=X.index)

        for name, model, feature_cols in self.base_models:
            oof_preds = pd.Series(np.nan, index=X.index)

            for train_idx, val_idx in tscv.split(X):
                X_train = X.iloc[train_idx][feature_cols]
                y_train = y.iloc[train_idx]
                X_val = X.iloc[val_idx][feature_cols]

                fold_model = clone(model)
                fold_model.fit(X_train, y_train)

                if hasattr(fold_model, 'predict_proba'):
                    oof_preds.iloc[val_idx] = fold_model.predict_proba(X_val)[:, 1]
                else:
                    oof_preds.iloc[val_idx] = fold_model.predict(X_val)

            meta_features[name] = oof_preds

        return meta_features.dropna()

    def fit(self, X: pd.DataFrame, y: pd.Series):
        meta_features = self._generate_meta_features(X, y)
        y_meta = y.loc[meta_features.index]

        self.meta_model.fit(meta_features, y_meta)

        for name, model, feature_cols in self.base_models:
            model.fit(X[feature_cols], y)
            self.fitted_base_models_[name] = model

        return self

    def predict(self, X: pd.DataFrame) -> np.ndarray:
        meta_features = pd.DataFrame()

        for name, model, feature_cols in self.base_models:
            fitted = self.fitted_base_models_[name]
            if hasattr(fitted, 'predict_proba'):
                meta_features[name] = fitted.predict_proba(X[feature_cols])[:, 1]
            else:
                meta_features[name] = fitted.predict(X[feature_cols])

        return self.meta_model.predict(meta_features)

这里的分裂规则不是可选的,也不是新颖的——使用净化的、禁运的前进,并在相信这些数字之前了解每类泄漏在膨胀的夏普中的价值。两者都已被测量:前瞻偏差分类法量化了受控模拟器中的执行、指示器和标准化泄漏,而前行优化涵盖了锚定窗口与滚动窗口、CPCV、清除和WFER退化诊断。

三个设计决策是专门针对堆叠的,值得一提。

元学习器简单性。 使用线性模型——岭、套索、逻辑回归。基础学习器处理非线性;元学习器仅处理组合。复杂基础学习器之上的复杂元学习器会出现二阶过度拟合,而组合层的 L1 正则化具有将仅产生噪声的基础模型归零的有用副作用。

硬标签上的概率输出。 predict_proba 携带类标签丢弃的置信信息,并让元学习器更重视置信基础预测。

**特征域划分。**这是杠杆率最高的多样性杠杆,它直接来自协方差参数:无论算法如何,看到相同特征的模型都会产生相关错误。

FEATURE_GROUPS = {
    'momentum': ['ret_5d', 'ret_10d', 'ret_21d', 'ret_63d'],
    'volatility': ['vol_21d', 'vol_ratio', 'vol_skew', 'garch_vol'],
    'volume': ['volume_ratio', 'obv_slope', 'vwap_dist', 'adv_ratio'],
    'mean_reversion': ['dist_from_ma21', 'dist_from_ma63', 'rsi_14', 'bb_pct'],
    'microstructure': ['bid_ask_spread', 'kyle_lambda', 'amihud_illiq'],
    'fundamental': ['ep_ratio', 'bp_ratio', 'roe', 'accruals'],
}

base_models = []
for domain, features in FEATURE_GROUPS.items():
    model = GradientBoostingClassifier(
        n_estimators=200, max_depth=3,
        learning_rate=0.05, subsample=0.7
    )
    base_models.append((domain, model, features))

每个基础模型都成为领域专家:动量模型在不受音量噪声干扰的情况下学习动量模式,元学习器学习在哪种条件下值得信任哪个专家。

关于最后一条的两个警告。首先,状态条件加权是一个已解决并已发布的问题 - 使用 HMM 进行状态检测 通过 Forward/Viterbi/Baum-Welch 正确导出状态,并将它们作为特征提供给下游集成,并且动态组合策略 涵盖了状态加权均值回归与动量的报告结果。不要用临时的动量标志代理来替代其中任何一个。其次,根据样本内的表现进行选择,导致获胜者无法生存;通用机制和 CSCV 防御是在回测过度拟合的概率 中测量的。堆叠只是该问题的一个较小的例子。

对于加权返回流而不是模型预测,不要重新推导均值方差。 12种投资组合优化算法,比较 与 HRP、HERC、GHRP、MHRP、Black-Litterman、NCO、MVO、风险平价和等权重竞赛,并提供测量结果和开源代码; 加密马科维茨投资组合理论 具有 SLSQP 均值方差实现;并且 1/N 难以击败的结果已经在 HRP/CVaR 投资组合管道 中针对 HRP 和 CVaR 进行了测试。

净额营业额:值得检验的说法

这是 alpha-library 参数的一部分,本博客的执行成本集群当前未涵盖。

当 Alpha #4,721 在同一重新平衡中说“买入 MSFT”而 Alpha #8,392 说“卖出 MSFT”时,这些交易会在内部交叉。只有净头寸到达市场。据称,只要有足够的信号,预期总营业额的很大一部分就会在产生价差、费用或市场影响之前取消——这意味着一个整体的运行成本比其各个组成部分的总和更便宜,而不仅仅是更稳定。

这是与级联策略编排 已经处理的机制不同的机制。 Cascade解决了slot级别的冲突:同对、相反方向被禁止并通过分数解决,跨对驱逐通过优先级队列进行,文章通过经验证明了由于时间重叠和资金限制,每个策略的PnL不能简单地求和。净额结算是关于总头寸与净额头寸的算术减少交易数量本身。

伪造的成本也很低:当组合组件信号时,计算实际填充的总营业额与净营业额,然后通过现有成本机制进行定价 - maker-taker 费用和回扣实现缺口和 TCA滑点成本模型

关于规模的注释,因为阿尔法库框架邀请它。 WorldQuant 报告已经制作了一个非常大的 alpha 库,并且 Kakushadze 的 101 Formulaic Alphas记录各个信号的形状。但工业规模的阿尔法采矿也是可以想象到的最大的多重测试机器,本博客对此的立场已经确定:规模搜索 KK 必须与预期的最佳搜索规模相比进行缩减。 缩减的夏普比率 显示 640 个单元格的网格已经突破了朴素的显着性测试,而诚实的负数 显示了约 37,000 次试验搜索,产生了 +16.35% 的样本外获胜者,缩减至 DSR 0.00。库大小并不是优势的证据。这是您必须支付的分母。

将其放在一起

Raw Data
  |
  v
Feature Engineering
  |
  v
Feature Subsets (partitioned by domain -> decorrelated errors)
  |
  +---> Base Model 1: RF on momentum features
  +---> Base Model 2: GBM on volatility features
  +---> Base Model 3: LSTM on price sequences
  +---> Base Model 4: Lasso on fundamental features
  +---> Base Model 5: XGBoost on microstructure features
  |
  v
Out-of-Fold Predictions (purged walk-forward)
  |
  v
Meta-Learner (Ridge)
  |
  v
Combined Signal -> Net Position -> Execution
  |
  v
DSR >= 0.95 and PBO <= 0.2 gate, or it does not ship

最后一个盒子不是装饰品。堆叠起来 KK 基本模型是尺寸的选择过程 KK,而一篇不泄气的合奏文章是站不住脚的。

要点

  1. 结构差异胜过模型计数。 协方差项不会随着 NN。添加第六棵相关树并不是多样化;而是多样化。添加不同的模型类或不相交的特征域是。
  2. 报告有效-NN 带,而不是点。 五个估计器,五个答案,有时相差两个数量级。
  3. 保持元学习器线性。 组合层的复杂性几乎总是过度拟合,并且那里的 L1 确实有用的自动模型选择。
  4. **不合规或什么都没有。**接受过样本内基础预测训练的元学习器会以高置信度学习错误的权重。
  5. 缩小整体本身。 堆叠是一种搜索。定价就像一个。

进一步阅读:

免责声明:本文提供的信息仅用于教育和参考目的,不构成财务、投资或交易建议。加密货币交易涉及重大损失风险。

Authors

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

紧跟市场步伐

订阅我们的时事通讯,获取独家 AI 交易见解、市场分析和平台更新。

我们尊重您的隐私。您可以随时退订。