跳到正文

因子组合:如何将多个 Alpha 信号合成为单一组合

真实的 alpha 策略很少依赖单一因子。因子组合覆盖如何将多个信号合成为具有更好风险调整收益的单一组合。

第 7 篇 · 共 10 篇 约 2 分钟

没有单一因子足以构成真实投资策略。动量有效但会在反转时崩溃。价值有效但很慢。质量稳定但单独使用时 Sharpe 低。构建稳健策略的关键是将多个因子合成为比任何单一因子都更好的组合。

为什么要组合因子?

Alpha 源多元化。 不同因子利用不同的市场无效性。动量利用行为偏差,价值利用均值回归,质量利用低风险股票的持久风险溢价。当一个因子表现不佳时,其他可能正在生效。

更稳定的收益。 单一因子可能有很长的表现不佳期。多因子组合平滑收益,因为因子循环轮换。

更好的风险调整收益。 组合负相关的 alpha 信号可以降低组合波动率而不降低预期收益——改善 Sharpe 比率。

组合因子的方法

方法 1:等权重(最简单)

给每个因子相等权重:

fcombined=1k∑i=1kfif_{combined} = \frac{1}{k} \sum_{i=1}^k f_i

等权重简单且稳健——避免对历史数据过拟合。主要缺点是它没有考虑因子质量差异(好因子与普通因子获得相同权重)。

方法 2:IC 加权组合

按因子信息系数加权:

wi=ICi∑jICjw_i = \frac{IC_i}{\sum_j IC_j}

这给预测力更高的因子更多权重。缺点是 IC 估计有噪声,你可能对近期 IC 过拟合。

方法 3:均值-方差优化(现代投资组合理论)

将每个因子视为资产,优化组合权重以最大化预期收益减去风险:

max⁡w wTμ−λ2wTΣw\max_w \ w^T \mu - \frac{\lambda}{2} w^T \Sigma w

其中 μ 是因子预期收益向量(通常用 IC 代理),Σ 是因子收益的协方差矩阵。

主要挑战:μ 和 Σ 从不精确已知。μ 的小误差导致最优权重的大误差。这是 Markowitz 优化陷阱。

方法 4:风险加权(稳健)

按风险贡献的倒数加权因子:

wi=1/σi∑j1/σjw_i = \frac{1 / \sigma_i}{\sum_j 1 / \sigma_j}

或按逆方差加权以最小化组合波动率,不管预期收益。

在 Python 中实现因子组合

import numpy as np
import pandas as pd
from scipy.optimize import minimize

def combine_factors_equal_weight(factor_dict):
    """
    简单等权重因子组合。
    """
    factor_names = list(factor_dict.keys())
    combined = sum(factor_dict[name] for name in factor_names) / len(factor_names)
    return combined


def combine_factors_ic_weighted(factor_dict, returns, lookback=252):
    """
    使用滚动 IC 加权的因子组合。

    Parameters
    ----------
    factor_dict : dict {name: Series}
        因子值(date index, asset columns)
    returns : DataFrame
        资产收益
    lookback : int
        IC 估计窗口

    Returns
    -------
    combined_factor : DataFrame
        组合因子值
    weights : DataFrame
        因子权重随时间变化
    """
    from scipy import stats

    factor_names = list(factor_dict.keys())
    n_factors = len(factor_names)

    dates = list(factor_dict[factor_names[0]].index)
    weights_history = []

    for i in range(lookback, len(dates) - 1):
        current_ics = []
        for name in factor_names:
            fv = factor_dict[name].iloc[i-lookback:i].dropna()
            ret = returns.iloc[i-lookback:i].dropna()
            common = fv.index.intersection(ret.index)

            if len(common) > 30:
                ic, _ = stats.spearmanr(fv[common].values, ret[common].values)
                current_ics.append(max(ic, 0))  # 丢弃负 IC
            else:
                current_ics.append(0.0)

        current_ics = np.array(current_ics)
        if current_ics.sum() > 0:
            weights = current_ics / current_ics.sum()
        else:
            weights = np.ones(n_factors) / n_factors

        weights_history.append(weights)

    weights_df = pd.DataFrame(weights_history, columns=factor_names, index=dates[lookback:])

    # 计算组合因子值
    combined_frames = []
    for name in factor_names:
        aligned = factor_dict[name].loc[weights_df.index]
        combined_frames.append(aligned * weights_df[name])

    combined = sum(combined_frames)

    return combined, weights_df


def combine_factors_mvo(factor_dict, returns, lookback=252, risk_aversion=1.0):
    """
    使用均值-方差优化的因子组合。

    Warning:容易过拟合。
    """
    from scipy import stats

    factor_names = list(factor_dict.keys())
    n_factors = len(factor_names)

    dates = list(factor_dict[factor_names[0]].index)

    # 估计 IC(作为预期收益代理)
    ic_history = {name: [] for name in factor_names}

    for i in range(lookback, len(dates) - 1):
        for name in factor_names:
            fv = factor_dict[name].iloc[i-lookback:i].dropna()
            ret = returns.iloc[i-lookback:i].dropna()
            common = fv.index.intersection(ret.index)

            if len(common) > 30:
                ic, _ = stats.spearmanr(fv[common].values, ret[common].values)
                ic_history[name].append(ic)
            else:
                ic_history[name].append(0.0)

    # 使用最新 IC 作为预期收益
    mu = np.array([ic_history[name][-1] for name in factor_names])
    mu = np.maximum(mu, 0)  # 丢弃负预期

    # 估计协方差矩阵
    ic_matrix = np.array([ic_history[name] for name in factor_names]).T
    cov_matrix = np.cov(ic_matrix)

    # 优化
    n = len(mu)
    weights = np.ones(n) / n  # 初始等权重

    def neg_sharpe(w):
        port_return = w @ mu
        port_vol = np.sqrt(w @ cov_matrix @ w)
        return -(port_return - risk_aversion * port_vol**2)

    result = minimize(neg_sharpe, weights, method='SLSQP',
                     bounds=[(0, 1) for _ in range(n)],
                     constraints={'type': 'eq', 'fun': lambda w: w.sum() - 1})

    return dict(zip(factor_names, result.x))

组合因子正交化

如果你的因子高度相关,组合它们几乎没有多元化优势。考虑组合前正交化因子(通过 PCA):

from sklearn.decomposition import PCA

def orthogonalize_factors(factor_matrix, n_components=None):
    """
    使用 PCA 正交化因子。

    返回不相关的因子主成分。
    """
    pca = PCA(n_components=n_components)
    orthogonalized = pca.fit_transform(factor_matrix)

    return orthogonalized, pca.explained_variance_ratio_

常见错误

对组合权重过拟合。 在历史上优化组合权重很诱人但危险。如果你使用 MVO,使用正则化或约束。等权重在样本外通常更稳健。

忽略因子相关性。 组合两个动量因子给你 2 倍动量敞口的组合,不是 2 倍多元化。组合真正互补的因子。

忘记交易成本。 每个因子需要再平衡。添加高换手的第五个因子可能增加总换手超过它添加的 alpha,导致净 Sharpe 更低。

组合快速衰减和慢速衰减因子时忽略衰减。 在月再平衡组合中,30 天半衰期的因子应比 5 天半衰期的因子获得更多权重。

在知识体系中的位置

因子组合是因子研究和组合构建之间的桥梁。Alpha 因子定义信号。IC 分析评估它们。因子 decay 通知再平衡频率。因子组合将它们合成为组合。

参考文献

  • López de Prado, Advances in Financial Machine Learning, 第 6 章. Wiley, 2018. ISBN: 978-1-119-48208-6.
  • Ang, Asset Management, 第 10 章. Oxford University Press, 2014. ISBN: 978-0-19-995932-3.
  • Kritzman, The Misfortunes of the Golden Ratio. Wiley, 2017.