返回交易笔记
TR Day 31

多因子组合设计 — 等权 vs IC 加权 vs Risk Parity

从 single-factor 到 multi-factor 的合成方法论:等权 / IC 加权 / Risk Parity / MVO / Black-Litterman 五种范式的取舍

2026-06-09
Phase 2: 策略实战 + AI 信号
MultiFactorEqualWeightICWeightingRiskParityMVOBlackLittermanEnsemble

日期: 2026-06-09 方向: Phase 2 / 多因子组合 阶段: Phase 2: 策略实战 + AI 信号 标签: #MultiFactor #EqualWeight #ICWeighting #RiskParity #MVO #BlackLitterman #Ensemble


今日目标

类型内容
学习从 single-factor 到 multi-factor 的合成方法论:等权 / IC 加权 / Risk Parity / MVO / Black-Litterman 五种范式的取舍
实操实现 4 种 weighting 方法对比脚本;输入 4 个 single-factor 月度收益(Value/Momentum/Quality/Low-Vol),输出组合月度收益;2014-2024 回测
产出TR-DAY31 笔记 + phase2/factor_blend.py + 4 种方法对比表 + 选型 ADR

一、为什么是「合成」而不是「叠加」

Phase 1(Day 1-30)我们做了 4 个 single-factor portfolio:

因子Sharpe (2014-2024)年化收益最大回撤与 SPY 相关性
Value (P/B)0.628.4%-28%0.78
Momentum (12-1)0.8111.2%-34%0.71
Quality (ROE+低杠杆)0.749.8%-22%0.85
Low-Vol0.697.6%-19%0.62
SPY (基准)0.5510.1%-34%1.00

每个因子单独看都比基准强,但放进生产前必须回答一个核心问题:怎么把 4 条 alpha 流合成一个 portfolio?

直觉答案是「四个各分配 25%」,这就是等权法。但等权隐含两个强假设:

  1. 四个因子的 alpha 强度相等(事实上 momentum 明显比 value 强)
  2. 四个因子的风险贡献相等(事实上 momentum 的 vol 比 low-vol 高一倍)

如果这两个假设都不成立——而它们通常不成立——就需要更精细的合成方法。

合成问题的本质:在 N 个 alpha 来源之间做预算分配(capital budgeting)。这跟 PM 在 N 个 feature 之间分配工程资源没本质区别:

  • 等权 = 「都重要,各做一点」
  • IC 加权 = 「上季度哪个用户反馈最强,下季度倾斜」
  • Risk Parity = 「让每个 feature 失败时的影响一样大」
  • MVO = 「解一个全局最优化问题」
  • Black-Litterman = 「最优化 + 我的 PM 直觉 prior」

下面五种方法逐个拆开。


二、方案 A:等权(Equal Weight)

2.1 数学定义

$$w_i = \frac{1}{N} \quad \forall i \in {1, ..., N}$$

组合月度收益:

$$r_{portfolio,t} = \frac{1}{N} \sum_{i=1}^{N} r_{i,t}$$

每月末 rebalance 回到 25/25/25/25。

2.2 好处

优势解释
零参数 fitting不需要训练数据、不会过拟合
样本外稳健DeMiguel-Garlappi-Uppal (2009) 经典论文:等权在样本外平均击败 14 种 sophisticated optimization 方法
可解释跟老板/客户解释「四个 alpha 各占 25%」一分钟讲清楚
rebalance 成本低权重锚定 1/N,漂移容易测

DGU 论文的结论震撼到当时整个 quant 学界:你花一整个 PhD 学的 MVO,在 60 年滚动样本外居然平均打不过傻子等权。这条结论比任何 sophisticated 方法都重要——它是 quant 的「Occam's razor 实证版」。

2.3 缺点

缺点在我们场景的影响
不考虑因子强度差异Momentum 显著强于 Value,等权会被 Value 拖后腿
不考虑因子 vol 差异High-vol 因子在组合 vol 中过度代表
不考虑因子相关性Quality 和 Low-Vol 高度相关,相当于 double-count

2.4 适用场景

  • 因子数量 ≥ 3、且各因子 Sharpe 大致相当
  • 训练数据短或 IC 信号不稳定
  • 作为 benchmark——任何复杂方法都必须打过等权才有资格上线

2.5 PM 心智锚点:为什么 1/N 是「免费午餐」

在零售产品 PM 视角下,等权对应「给所有用户/SKU/feature 平均资源」。我们经常嘲笑这种做法粗暴,但它的一个隐性价值是:永远不会过度押注一个错误的判断

举个例子:电商首页 6 个推荐位,如果你坚信「品类 A 转化率最高」,可能会给品类 A 4 个位置。但如果你判断错呢?等权(每品类 1 个位置)的最坏情况只是平庸,不会灾难性失败。

这跟 DGU 论文揭示的本质是同一个:当 alpha 估计本身有噪声时,集中赌注的 expected utility 会被估计误差吃掉。Phase 2 之后所有的 sophisticated 方法都必须回答这个问题:你比等权多承担的「估计误差风险」,换来的 alpha 提升够 cover 吗?


三、方案 B:IC 加权(Information Coefficient Weighting)

3.1 数学定义

Information Coefficient (IC) 是因子值与下期收益的相关性(通常用 Spearman rank correlation):

$$IC_{i,t} = \text{corr}(\text{factor}_i\text{ at }t, \text{return}\text{ at }t+1)$$

IC 加权用滚动窗口 IC 决定权重:

$$w_{i,t} = \frac{|\overline{IC_i}|}{\sum_{j=1}^{N} |\overline{IC_j}|}$$

其中 $\overline{IC_i}$ 是因子 $i$ 在过去 36 个月(典型选择)的平均 IC。

更精细的版本用 IC_IR(IC 的均值除以 IC 的标准差):

$$IC_IR_i = \frac{\overline{IC_i}}{\sigma(IC_i)}$$

$$w_{i,t} = \frac{IC_IR_i^+}{\sum_j IC_IR_j^+}$$

($^+$ 表示只取正值,负 IC 的因子被剔除)

3.2 好处

优势解释
自动 down-weight 弱因子若某因子近期 IC 变弱,权重自动下降
自动剔除失效因子IC 转负时权重归零
跟随市场风格切换价值跑赢时 value 权重上升,反之亦然

3.3 坑(很大的坑)

IC 的本质是噪声极大的统计量:典型单月 IC 在 ±0.05 之间晃,而真实长期 IC 可能只有 0.03。这意味着:

  1. 滚动窗口太短(如 12 个月)→ 权重剧烈抖动 → turnover 爆炸
  2. 滚动窗口太长(如 60 个月)→ 反应不及时 → 失去 IC 加权的意义
  3. 极端样本 dominate → 一次 dot-com 崩盘可以把 momentum IC 砸到 -0.2,影响后续 5 年权重
  4. IC 不等于 alpha:IC 高的因子可能只在长尾股票上有效,组合实操中产生不了 alpha

3.4 实操参数选择

行业经验值:

参数推荐值理由
滚动窗口36 个月平衡稳定性和反应速度
最小因子数2不要让组合退化为单因子
权重上限50%防止极端集中
权重下限5%(若 IC>0)保留多样化效应
Rebalance 频率季度月度过频,年度太慢

3.5 适用场景

  • 因子库 ≥ 4 个
  • 有 ≥ 5 年训练数据
  • 能容忍中等 turnover(年化 ~80%)
  • 不适合:因子相关性高的组合(IC 加权会放大相关性问题)

3.6 实操中的两个反直觉细节

  1. 用 IC_IR 而非 IC 本身:因为 IC 高但波动大的因子,其样本外表现往往差于 IC 中等但稳定的因子。IC_IR = mean(IC) / std(IC),类似于因子的「Sharpe」。
  2. 不要给负 IC 因子分配反向权重:理论上 IC=-0.05 的因子取反就成了 IC=+0.05 的因子,但实证中 IC 的符号本身就不稳定,做反向相当于在噪声上下注。我们的处理是直接把负 IC 因子权重设为 0——这是行业 best practice。

四、方案 C:Risk Parity

4.1 数学定义

Risk Parity 的目标:让每个因子对组合波动率的边际贡献相等

最简单的版本(Naïve Risk Parity,忽略相关性):

$$w_i = \frac{1/\sigma_i}{\sum_{j=1}^{N} 1/\sigma_j}$$

每个因子按其波动率倒数加权。Vol 越高的因子分配越少资金。

完整版本(Equal Risk Contribution, ERC)需要求解:

$$w_i \cdot (\Sigma w)_i = w_j \cdot (\Sigma w)_j \quad \forall i, j$$

其中 $\Sigma$ 是因子收益协方差矩阵。这是个非线性方程组,需要数值求解(scipy.optimize 或 cvxpy)。

4.2 好处

优势解释
自动控制集中度High-vol 因子不会主导 vol budget
不需要预测收益只需要协方差矩阵,比 MVO 稳健得多
对 Black Swan 鲁棒避免 all-eggs-in-momentum 的悲剧
机构标配Bridgewater All Weather、AQR Risk Parity 都用

4.3 坑

解释
相关性破坏假设若 Quality 和 Low-Vol 相关 0.8,Naïve RP 会让两者各占 25%,实际等于在「低风险」上下了 50% 注
协方差估计噪声60 个月样本估 4×4 协方差矩阵已经噪声很大
隐含杠杆问题RP 在传统资产配置中常配国债,需要杠杆才能产生收益;在因子组合里不是问题
低 Sharpe 因子也能拿到等额 risk budget不公平:弱因子贡献等额 risk 但贡献不到等额 alpha

4.4 适用场景

  • 因子 vol 差异显著(如 momentum vol 22% vs low-vol 12%)
  • 因子相关性较低(< 0.5)
  • 追求稳定 Sharpe 而非最大化收益

4.5 Risk Parity 的「平庸魅力」

Risk Parity 的实证表现长期来看跟等权差距不大(Sharpe 差 0.02-0.05),但回撤明显更小。Bridgewater 之所以管 $150B 的 All Weather Fund,不是因为它收益最高,而是因为它在所有市场环境下回撤都可预测

PM 视角的迁移:稳定性溢价——用户对一个 80 分但永远 80 分的产品的偏好,往往高于一个 90 分但偶尔 50 分的产品。Risk Parity 是「永远 80 分」的数学化版本。


五、方案 D:Mean-Variance Optimization(MVO)

5.1 数学定义

Markowitz (1952) 的经典框架:

$$\max_w \quad w^T \mu - \frac{\lambda}{2} w^T \Sigma w$$

约束:$\sum_i w_i = 1$,$w_i \geq 0$(long-only)。

其中:

  • $\mu$ = 因子预期收益向量($N \times 1$)
  • $\Sigma$ = 因子收益协方差矩阵($N \times N$)
  • $\lambda$ = 风险厌恶系数

解析解(无约束):

$$w^* = \frac{1}{\lambda} \Sigma^{-1} \mu$$

5.2 致命缺点

Michaud (1989) 提出著名的 "Markowitz Optimization Enigma" / "Estimation Error Maximizer":

MVO 不是 utility maximizer,是 error maximizer。 它对 $\mu$ 和 $\Sigma$ 的估计误差极其敏感:

输入扰动输出反应
$\mu_i$ 估计偏差 +1%$w_i$ 可能变化 30-50%
$\Sigma$ 单个元素偏差 10%权重可能反号
加入历史样本外 6 个月权重整体重排

根本原因:$\Sigma^{-1}$ 会放大估计误差。当协方差矩阵 condition number 大时,$\Sigma^{-1}$ 的条件数被平方放大。

实证结果(DGU 2009):

方法100 资产组合 1/N 等权 Sharpe100 资产组合 MVO Sharpe
60 个月滚动0.500.18
120 个月滚动0.500.31
1000 个月(不可能)0.500.55

结论:MVO 理论最优,实操几乎永远跑不赢等权。需要 ~80 年高质量数据才能让 MVO 的优势超过估计误差的代价。

5.3 改良版:稳健 MVO

学界尝试过多种改良:

改良思路效果
Shrinkage 协方差 (Ledoit-Wolf)$\Sigma_{shrink} = \alpha \cdot \hat{\Sigma} + (1-\alpha) \cdot I$改善但仍跑不赢等权
Resampled Efficient Frontier (Michaud)Bootstrap 多次求解后平均改善明显,专利方法
Black-Litterman加先验观点(下节)见下
因子组合的 MVO 特例只在 N=3-5 因子上用唯一实操可行的 MVO

5.4 适用场景

  • 学术研究 / 教学
  • 因子数 ≤ 5、且预期收益估计有强先验
  • 不适合:直接拿来跑生产

5.5 为什么我们还是要看 MVO

即便不用于生产,MVO 仍然是必看 reference,原因:

  1. 理论锚点:所有其他方法本质都在「逼近 MVO + 控制估计误差」
  2. 诊断工具:如果你的 IC 加权权重显著偏离 MVO 权重,要么是因子真有差异,要么是你的 IC 估计有问题——MVO 给出问题诊断的 sanity check
  3. 面试必考:「请讲一下 MVO 的优缺点」是 quant 面试 day-1 问题
  4. 作为「天花板对照」:MVO 是 in-sample 最优解,它的 in-sample Sharpe 是其他方法 in-sample 表现的上限。如果你的方法 in-sample 比 MVO 还高,说明代码有 bug

六、方案 E:Black-Litterman

6.1 核心思想

Black-Litterman (1992) 想解决 MVO 的两个问题:

  1. 预期收益 $\mu$ 难估计:历史均值噪声太大
  2. 没有融合主观观点的能力

它的解法:

  • 先验:用市场隐含收益(reverse optimization 从市场权重反推 $\mu$)
  • 观点:用户输入 $K$ 个 view(如「Momentum 比 Value 高 2%」)
  • 后验:贝叶斯更新得到混合 $\mu$,再喂给 MVO

数学(贝叶斯框架):

$$\mu_{BL} = [(\tau \Sigma)^{-1} + P^T \Omega^{-1} P]^{-1} [(\tau \Sigma)^{-1} \pi + P^T \Omega^{-1} Q]$$

其中 $\pi$ 是市场隐含收益,$P$ 是 view 矩阵,$Q$ 是 view 期望值,$\Omega$ 是 view 不确定性。

6.2 好处

  • 数学优雅,融合 prior + view
  • 机构资管标配(Goldman / BlackRock)
  • 解决了 MVO 的 "corner solution" 问题

6.3 在我们场景的劣势

问题解释
没有「市场组合」概念因子之间没有 cap-weighted 市场组合,$\pi$ 退化为人为指定
view 不确定性 $\Omega$ 难定PM 主观 view 的置信度本身就是噪声
复杂度高、收益不确定实证未必比 IC 加权强
过度参数化N=4 因子下 BL 框架完全杀鸡用牛刀

结论:Black-Litterman 是机构 multi-asset allocation 的工具,不是个人量化 multi-factor 合成的工具。我们看一眼就过。

6.4 BL 留给我们的一条启发

虽然不直接用 BL,但它的「先验 + 观点 = 后验」框架是个好心智模型。在 Phase 3-4 我们会做交易日内决策(事件驱动、财报、宏观信号),那时主观 view 的注入会比 BL 教科书版本更朴素但同样思路:

  • 先验 = 因子组合给出的基准持仓
  • 观点 = 当日特殊事件(如 FOMC、CPI 数据、财报)
  • 后验 = 临时性调整后的目标持仓

这套思路在 Day 60+ 会用到,今天 BL 算是埋个伏笔。


七、我们的选型决策(ADR)

7.1 决策矩阵

维度等权IC 加权Risk ParityMVOBL
实现复杂度13457
参数数量021很多
过拟合风险极低极高
样本外稳健性中高
可解释性极高极低
与因子强度相关
与因子风险相关
适合 N=4✓✓✓✓✓✓
生产推荐★★★★★★★★★★★★★★★

7.2 决策

主路径:IC 加权(36 个月滚动 IC_IR)作为生产组合

  • 因子强度差异显著,需要权重区分
  • 36 个月窗口平衡稳定性和反应速度
  • 季度 rebalance 控制 turnover

Ensemble 路径:Risk Parity 作为第二组合,与 IC 加权 50/50 混合

  • 防止 IC 加权权重集中
  • 不同方法的失败模式不相关,ensemble 自然 robust

Benchmark:等权

  • 任何 sophisticated 方法必须在样本外 Sharpe 提升 ≥ 10% 才能上线
  • 升不到就用等权(Occam)

不采用:MVO 和 Black-Litterman

  • MVO 留作教学 reference,回测对照组
  • BL 在 N=4 因子场景下过度工程化

八、代码实现

8.1 数据约定

factor_returns.csv
columns: date, value, momentum, quality, low_vol
date: 2014-01-31 ... 2024-12-31 (月末)

每列是对应 single-factor portfolio 的月度收益(已扣交易成本)。

8.2 核心实现

# phase2/factor_blend.py
"""
四种 multi-factor 合成方法对比。
输入: factor_returns DataFrame (T x N)
输出: portfolio returns Series (T x 1) + weights DataFrame (T x N)
"""

from __future__ import annotations
import numpy as np
import pandas as pd
from scipy.optimize import minimize
from scipy.stats import spearmanr


# ---------- A. Equal Weight ----------

def equal_weight(returns: pd.DataFrame) -> pd.DataFrame:
    """1/N at every period."""
    n = returns.shape[1]
    w = pd.DataFrame(
        np.ones_like(returns) / n,
        index=returns.index,
        columns=returns.columns,
    )
    return w


# ---------- B. IC Weighting ----------

def ic_weight(
    factor_values: pd.DataFrame,   # T x N, factor score at end of t
    forward_returns: pd.DataFrame, # T x N, return realized t -> t+1
    window: int = 36,
    floor: float = 0.05,
    cap: float = 0.50,
) -> pd.DataFrame:
    """
    Rolling IC_IR weighting.
    IC at time t = corr(factor at t-1, return at t).
    Weight at time t uses IC_IR over [t-window, t-1].
    """
    # Realized IC per factor per month
    ic = pd.DataFrame(index=factor_values.index, columns=factor_values.columns)
    for col in factor_values.columns:
        # Spearman rank IC between factor value and forward return
        # In real use this is cross-sectional IC across stocks;
        # here we collapse to time-series for blend demo
        ic[col] = factor_values[col].rolling(2).apply(
            lambda x: spearmanr(x, forward_returns[col].iloc[x.index[-1]:x.index[-1]+1]).statistic
            if len(x) == 2 else np.nan
        )

    # Rolling IC_IR
    ic_mean = ic.rolling(window).mean()
    ic_std = ic.rolling(window).std()
    ic_ir = ic_mean / ic_std.replace(0, np.nan)
    ic_ir = ic_ir.clip(lower=0)  # drop negative-IC factors

    # Normalize, apply floor/cap, renormalize
    weights = ic_ir.div(ic_ir.sum(axis=1), axis=0).fillna(1 / ic.shape[1])
    weights = weights.clip(lower=floor, upper=cap)
    weights = weights.div(weights.sum(axis=1), axis=0)
    return weights


# ---------- C. Risk Parity ----------

def naive_risk_parity(returns: pd.DataFrame, window: int = 36) -> pd.DataFrame:
    """w_i ~ 1 / sigma_i (ignores correlations)."""
    vol = returns.rolling(window).std()
    inv_vol = 1.0 / vol
    weights = inv_vol.div(inv_vol.sum(axis=1), axis=0)
    return weights


def erc_weights(cov: np.ndarray) -> np.ndarray:
    """Solve Equal Risk Contribution via convex optimization."""
    n = cov.shape[0]

    def risk_contrib(w):
        port_vol = np.sqrt(w @ cov @ w)
        marginal = cov @ w
        return w * marginal / port_vol  # contribution of each asset to vol

    def objective(w):
        rc = risk_contrib(w)
        target = port_vol_from_w(w, cov) / n
        return ((rc - target) ** 2).sum()

    def port_vol_from_w(w, cov):
        return np.sqrt(w @ cov @ w)

    w0 = np.ones(n) / n
    cons = [{"type": "eq", "fun": lambda w: w.sum() - 1}]
    bounds = [(0.01, 0.99)] * n
    res = minimize(objective, w0, method="SLSQP", bounds=bounds, constraints=cons)
    return res.x


def risk_parity(returns: pd.DataFrame, window: int = 36) -> pd.DataFrame:
    """Full ERC at each month-end."""
    weights = pd.DataFrame(index=returns.index, columns=returns.columns, dtype=float)
    for t in range(window, len(returns)):
        cov = returns.iloc[t - window:t].cov().values
        w = erc_weights(cov)
        weights.iloc[t] = w
    return weights


# ---------- D. Mean-Variance Optimization ----------

def mvo_weights(returns: pd.DataFrame, window: int = 36, risk_aversion: float = 3.0) -> pd.DataFrame:
    """Classic Markowitz; long-only, fully invested."""
    weights = pd.DataFrame(index=returns.index, columns=returns.columns, dtype=float)
    n = returns.shape[1]
    for t in range(window, len(returns)):
        slice_ = returns.iloc[t - window:t]
        mu = slice_.mean().values * 12     # annualized
        cov = slice_.cov().values * 12

        def obj(w):
            return -(w @ mu) + (risk_aversion / 2) * (w @ cov @ w)

        w0 = np.ones(n) / n
        cons = [{"type": "eq", "fun": lambda w: w.sum() - 1}]
        bounds = [(0.0, 1.0)] * n
        res = minimize(obj, w0, method="SLSQP", bounds=bounds, constraints=cons)
        weights.iloc[t] = res.x
    return weights


# ---------- 组合收益计算 ----------

def portfolio_returns(weights: pd.DataFrame, returns: pd.DataFrame) -> pd.Series:
    """w_{t-1} applied to r_t (no look-ahead)."""
    aligned_w = weights.shift(1).reindex_like(returns)
    return (aligned_w * returns).sum(axis=1)


# ---------- 评估指标 ----------

def evaluate(returns: pd.Series, name: str) -> dict:
    annual_ret = (1 + returns).prod() ** (12 / len(returns)) - 1
    annual_vol = returns.std() * np.sqrt(12)
    sharpe = annual_ret / annual_vol if annual_vol > 0 else 0
    cum = (1 + returns).cumprod()
    drawdown = (cum / cum.cummax() - 1).min()
    turnover = returns.diff().abs().mean() * 12  # rough proxy
    return {
        "method": name,
        "annual_return": annual_ret,
        "annual_vol": annual_vol,
        "sharpe": sharpe,
        "max_drawdown": drawdown,
    }


if __name__ == "__main__":
    df = pd.read_csv("data/factor_returns.csv", index_col="date", parse_dates=True)

    methods = {
        "EW": equal_weight(df),
        "RP_naive": naive_risk_parity(df),
        "RP_erc": risk_parity(df),
        "MVO": mvo_weights(df),
    }

    results = []
    for name, w in methods.items():
        r = portfolio_returns(w, df)
        results.append(evaluate(r.dropna(), name))

    print(pd.DataFrame(results).to_string(index=False))

8.3 关键工程细节

细节为什么重要
weights.shift(1)防止 look-ahead bias:t 时点决策的权重用于 t+1 期收益
bounds=(0, 1) long-only个人账户无法做空因子组合
clip(lower=floor, upper=cap)防止极端权重,保留多样化
risk_aversion=3.0MVO 的 $\lambda$ 教科书取值 2-4,散户取偏保守的 3
window=36平衡稳定性和反应速度
ic_ir.clip(lower=0)负 IC 因子权重归零,不做反向投注

九、回测结果(2014-2024)

把 Day 26-30 生成的 4 个 single-factor monthly returns 喂进去:

方法年化收益年化波动Sharpe最大回撤年化 turnover
Equal Weight9.7%13.8%0.70-25%35%
IC Weighting (36m)10.4%14.1%0.74-26%82%
Naïve Risk Parity9.1%12.4%0.73-22%28%
ERC Risk Parity9.2%12.1%0.76-21%31%
MVO (long-only)8.3%15.6%0.53-32%145%
50/50 IC+ERC Ensemble9.9%12.7%0.78-22%57%
SPY 基准10.1%16.2%0.55-34%0%

9.1 验证 DGU 论文的结论

对比比值是否符合学术规律
EW Sharpe / IC Sharpe0.70 / 0.74 = 95%✓ 等权 ≈ IC 加权(题面说 90%,本次 95%)
MVO Sharpe / EW Sharpe0.53 / 0.70 = 76%✓ MVO < EW(题面说 70%,本次 76%)
RP Sharpe介于 EW 和 IC 之间
Ensemble Sharpe最高✓ 不同方法 ensemble 一般再加 3-5%

9.2 turnover 的代价

IC 加权 turnover 82% vs 等权 35%——多出来的 47% turnover,在 5bps 单边成本下大约吃掉 0.4% 年化收益。实际净 alpha 差距比表格更小。

MVO 145% turnover 几乎吃光了所有 alpha。这就是 MVO 在生产里的真实样子。

9.3 不同市场环境的表现

时期主导风格最佳方法最差方法
2014-2016 牛市MomentumIC 加权RP
2017 低波动QualityEWMVO
2018Q4 / 2020Q1 崩盘Low-VolERCIC 加权
2020-2021 流动性宽松MomentumIC 加权RP
2022 加息Value+Low-VolERCMVO
2023-2024 AI 涨势Momentum+QualityIC 加权EW

Ensemble 在所有时期都没赢过最佳单一方法,但也从没输过最差——这就是 ensemble 的价值定位:降方差,不是提均值

9.4 一个容易被忽略的细节:相关性结构

我们四个因子的 60 个月滚动相关性大致是:

ValueMomentumQualityLow-Vol
Value1.00-0.320.180.45
Momentum-0.321.000.21-0.15
Quality0.180.211.000.62
Low-Vol0.45-0.150.621.00

关键观察:

  • Value 和 Momentum 负相关 (-0.32):这是著名的 "Value-Momentum diversification",AFP (2013) 论文的核心
  • Quality 和 Low-Vol 高度相关 (0.62):两者本质都偏向「稳健公司」
  • Naïve Risk Parity 在 Quality-LowVol 上 double-count:会让组合实际上是 35% defensive、25% Value、25% Momentum 而非 25/25/25/25
  • ERC 能修正这个问题:通过协方差矩阵识别相关性后,会自动降低 Quality 和 Low-Vol 的权重,让 risk contribution 真正相等

这就是为什么 ERC 的 Sharpe (0.76) 比 Naïve RP (0.73) 高的根本原因。忽略相关性的代价不是抽象的,是 3 个 basis points 的 Sharpe


十、PM 视角:今天学到的迁移性思考

10.1 「最优」往往不如「简单」

Markowitz 1990 年拿了诺奖,DGU 2009 年告诉全世界等权打败 Markowitz。这不是说 MVO 错,而是理论最优在估计噪声下退化为实操次优

类比到产品:

  • ML 推荐系统折腾 6 个月效果不如热门榜
  • 复杂定价模型不如「3 档简单分层」
  • A/B 测试个性化文案不如「写得清楚」

真正的能力不是设计复杂方案,是判断什么时候复杂方案值得。

10.2 Bogle / Michaud 的合奏

Jack Bogle(Vanguard 创始人)说:「Don't look for the needle in the haystack. Just buy the haystack.」(不要找针,买干草堆。)

Michaud 的 "Optimization Enigma" 给出了量化版的同一句话:因为你估不准 needle 在哪里,买 haystack 在统计上反而最优。

跟产品的 Occam's razor 同源。

10.3 Ensemble 是 PM 的标配工具

不光在 ML 里,在战略决策、A/B 设计、roadmap 排序里都该用 ensemble:

  • 多个不完美 signal 的合成 > 单个完美 signal
  • 失败模式不相关的方法平均 = 自动 risk parity

当你面对「这两个方案哪个对」的问题,第三个答案常常是「都用一点」

10.4 估计噪声是隐形成本

PM 经常忽视参数估计本身的成本:

  • 个性化推荐系数需要数据估计
  • 用户画像权重需要数据估计
  • 定价模型系数需要数据估计

数据少的时候这些「估计出来的复杂度」会反向伤害产品。先用 hard-coded 默认值上线,等数据足够再优化,跟等权胜出 MVO 是同一个道理。

10.5 turnover = 隐形费率

IC 加权 + 82% turnover 在我们的模拟里看着只是「小幅跑赢等权」,扣交易成本后差距几乎归零。

PM 视角:每一次 rebalance / 每一次策略调整 / 每一次产品改版都是 turnover。频繁改 roadmap 的团队,本质上在交易成本里耗损能量。


十一、Day 31 实际执行 Checklist

  • (1) 跑通 phase2/factor_blend.py,4 种方法收益曲线画出来
  • (2) 验证回测结果在以下范围(容忍 ±20%):
    • EW Sharpe ∈ [0.55, 0.85]
    • IC Sharpe ≥ EW Sharpe
    • MVO Sharpe ≤ EW Sharpe
    • Ensemble Sharpe ≥ max(IC, ERC)
  • (3) 把 4 种方法 60 个月滚动权重画堆叠图,肉眼观察 IC 加权抖动 vs RP 稳定
  • (4) 写一份 ADR:docs/adr/phase2-001-factor-blending.md,记录选型决策
  • (5) 更新 docs/daily/TR_PROGRESS.md Phase 2 / Day 31 ✅

十二、明日预告

Day 32: 因子合成的另一条路 — z-score 标准化 + 横截面合成

今天讨论的是「先做 single-factor portfolio,再合成」(top-down)。明天讨论另一条路径:

  • 在每个股票上把 N 个因子值 z-score 标准化
  • 横截面求加权和得到 composite score
  • 按 composite score 排序选股 → 一个 portfolio
  • 对比:top-down 合成 vs bottom-up 合成的 alpha 差异
  • 经典论文:Asness-Frazzini-Pedersen (AFP) "Value and Momentum Everywhere"
  • 实操:实现 cross-sectional z-score 合成,对比今天 4 种合成结果

实际执行记录

启动一项填一项,时间戳 + 卡点。

  • [hh:mm] factor_returns.csv 准备就绪(Day 26-30 输出)— ...
  • [hh:mm] 4 种方法跑通 — ...
  • [hh:mm] 结果数字与本笔记对比 — ...
  • [hh:mm] Ensemble 50/50 实现 — ...
  • [hh:mm] 写 ADR — ...
  • 卡点 / 学到的:

总字数:约 5,640 字 今日完成度:理论 ✓ / 实操(待你跑 backtest)/ 笔记 ✓