多策略 PnL 归因 — Market β / Factor α / Event α
PnL 归因的三层框架(market β / factor α / idiosyncratic α)+ Fama-French 5 因子模型 + OLS 多元回归在归因里的用法
日期: 2026-06-30 方向: Phase 2 / 归因 阶段: Phase 2: 策略实战 + AI 信号 标签: #PnLAttribution #MarketBeta #FactorAlpha #EventAlpha #FamaFrench #OLS
今日目标
| 类型 | 内容 |
|---|---|
| 学习 | PnL 归因的三层框架(market β / factor α / idiosyncratic α)+ Fama-French 5 因子模型 + OLS 多元回归在归因里的用法 |
| 实操 | 三个 paper 策略并行后,用 OLS 把 daily return 拆到 market + FF5 + 残差,画归因 stacked chart,跑显著性检验 |
| 产出 | TR-DAY52 笔记 + attribution.py 通用归因脚本 + 三策略归因表 + 月度归因仪表板(CSV) |
一、从「我赚了多少」到「我为什么赚」
到 Day 51 为止,paper 账户里已经有三条并行的 P&L 曲线:
| 策略 | 部署日 | 资金占用(paper) | 主要收益来源(拍脑袋) | 主要风险来源 |
|---|---|---|---|---|
| S1: 双因子组合(Momentum + Low Vol) | Day 28 + 重平衡 Day 36 | $2,500 | 因子溢价 + 牛市 β | 因子 drawdown / 市场系统性下跌 |
| S2: Wheel 期权(CSP → 行权 → CC) | Day 42 | $1,500(含 cash collateral) | 时间价值 Theta + 标的小幅上涨 | 标的大跌深度 ITM / IV crush |
| S3: 财报 Iron Condor + LLM signal | Day 49 + 50 | $1,000 | IV crush + LLM 过滤 directional risk | 财报跳空 / LLM 误判 |
| 组合 | — | $5,000 | ?? | ?? |
过去一个月组合大概赚了 +4.8%(paper 数字,illustrative)。表面看不错。但 PM 直觉告诉我必须问的问题是:
「这 +4.8% 里有多少是 SPY 同期涨 +3.2% 我顺势带上的(market β),多少是因子结构性溢价(factor α,但其实是 cheap beta 的另一种形态),多少是真正属于我策略选择 / LLM 信号 / 期权 timing 的(event α / idiosyncratic)?」
这就是 PnL 归因(Performance Attribution)要回答的事。不归因等于公司只看「营收涨了 5%」却不知道是新客户、老客户、提价、还是单纯通胀——下次想复制都不知道复制什么。
1.1 为什么 Phase 2 Week 8 才学归因,而不是 Day 1
Day 1-51 主要是构建:先把策略跑起来、把信号做出来。归因是评价:必须等有一段足够长(≥20 个交易日)的 daily return 才有统计意义。OLS 在 N=10 时跑出来的 R²、t-stat 全是噪声,做也是白做。
经验法则(来自 Andrew Lo / Grinold 的体系):
N (daily samples) 可信度
< 20 仅 sanity check,结论不可发表
20 - 60 方向判断 OK,幅度参考
60 - 252 一年内单策略评估
> 252 跨周期可信
我们现在 N ≈ 30-50(每个策略部署后到今天的工作日数)。够做 directional 归因,不够下死结论。这种「测不准」本身就是个人量化要长期面对的事——所以 Day 53 我们会学 Kelly 升级,把测不准本身当成约束去优化仓位。
1.2 PM 视角:归因 = revenue waterfall
我在前公司做零售 SaaS 那几年,每个月 review 财务时一定有一张「Revenue Waterfall」:
上月 ARR: $1.20M
+ 新客户 ARR: +$80k ← acquisition team 表现
+ Expansion ARR: +$45k ← CSM team 表现
+ Reactivation: +$10k ← marketing 表现
- Churned ARR: -$30k ← product / CS 共同责任
- Downgrades: -$15k ← pricing 团队反思
本月 ARR: $1.29M(+7.5%)
没这张表就没有「下次该投什么」的答案。量化策略的 PnL 归因是一模一样的东西——只不过 channel 换成了 market / factor / alpha。
二、三层归因框架:market β / factor α / idiosyncratic α
2.1 概念分层
策略 daily return R_p
│
├── 解释项 1: market β × (R_m - R_f)
│ 「我跟着市场走的那部分」
│ — long-only 策略只要持仓 > 0,这块基本逃不掉
│
├── 解释项 2: Σ β_k × F_k
│ 「我对各因子的暴露 × 各因子当期回报」
│ — Fama-French 5 因子:SMB / HML / RMW / CMA / MOM
│ — 这些是「学术上已知的 risk premium」,赚到也不算特别牛
│
├── 截距项 α: 真 α
│ 「市场和因子都解释不掉的、稳定的超额收益」
│ — 这才是 PM/策略师真正想要的东西
│
└── 残差 ε: idiosyncratic / event α
「单笔事件、个股波动、运气、模型误差的混合」
— 可能是 noise,也可能是模型还没捕获的 signal
数学上就是一个 OLS 多元回归:
$$ R_p^t - R_f^t = \alpha + \beta_m (R_m^t - R_f^t) + \beta_{SMB} \cdot SMB^t + \beta_{HML} \cdot HML^t + \beta_{RMW} \cdot RMW^t + \beta_{CMA} \cdot CMA^t + \beta_{MOM} \cdot MOM^t + \epsilon^t $$
其中:
- $R_p^t$ = 策略当日 return
- $R_f^t$ = 当日无风险利率(一般用 1m T-Bill / 252)
- $R_m^t$ = 市场 return(SPY 或 CRSP value-weighted index)
- $SMB$ = Small Minus Big(小盘溢价)
- $HML$ = High Minus Low(价值溢价)
- $RMW$ = Robust Minus Weak(盈利质量溢价)
- $CMA$ = Conservative Minus Aggressive(投资保守溢价)
- $MOM$ = Momentum(动量溢价,Carhart 加的第 6 因子,我们一起用)
2.2 三层的优先级与「fair α」的判定
不是所有的截距项 α 都是「真 α」。判断流程:
| 检查项 | 通过标准 | 不通过则… |
|---|---|---|
| t-stat of α | ≥ 2.0 | 不够显著,α 在统计意义上和 0 没差别,不能宣称 alpha |
| R² | 0.3 - 0.85 区间最健康 | R² < 0.3:模型解释力差,残差太大;R² > 0.95:可能你的策略就是 leveraged 市场暴露 |
| α 信噪比 = α / σ(ε) | ≥ 1.0 | 即使 α 是正的,如果它小于残差波动,单笔交易里都看不出来 |
| 跨 sub-period 稳定 | 前半段 α 和后半段 α 同号 | 否则可能是 regime-specific 的 fluke |
| 因子 loading 合理 | 你说自己是「动量策略」, β_MOM 应该 > 0.3 | 如果 β_MOM 接近 0 而 β_HML 很大,说明你以为在做动量其实在做价值,策略画像不一致 |
核心区分:market β 是免费的(SPY),factor β 是低价的(ETF 如 MTUM/VLUE/QUAL 可复制),只有截距项 α 是稀缺的。
三、Fama-French 5 + Momentum 因子怎么拿数据
3.1 数据源(免费且权威)
肯尼斯·法玛(Eugene Fama 的合作者 Kenneth French)的官网每月免费更新所有因子日度收益:
https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html
下载 F-F_Research_Data_5_Factors_2x3_daily.zip 和 F-F_Momentum_Factor_daily.zip,解压得到 CSV。注意:
- 单位是百分比,不是小数(5.23 = 5.23%,不是 5.23 倍)。我自己每次都要在脚本里
/100,吃过亏 RF列是日度无风险利率,已经是小数(0.000xx)- 数据滞后 1-2 个月更新,做 live 归因要小心 stale data
- 时区:French 的日期是美东收盘日,自己策略也必须对齐到美东收盘日,跨时区错位会产生 ghost α
3.2 自己也可以构造(练手用)
如果不想依赖 French 官网,可以用 ETF proxy 做粗糙版:
| 因子 | ETF Proxy | 计算方法 |
|---|---|---|
| MKT-RF | SPY return - T-Bill | SPY.ret - IRX/252/100 |
| SMB | IWM - SPY | small cap minus large |
| HML | VLUE - MTUM | value minus growth 的反向 |
| MOM | MTUM - SPY | momentum ETF 减大盘 |
| QUAL(≈ RMW) | QUAL - SPY | 质量 ETF 减大盘 |
精度上比官方因子差(ETF 内部有 turnover、管理费),但作为日常 PM 监控完全够。学术研究用官方,trading dashboard 用 ETF proxy。
3.3 因子之间的多重共线性
跑 OLS 之前最好先看一下因子之间的相关性:
| 因子对 | 历史 ρ(典型值) | 共线性风险 |
|---|---|---|
| MKT_RF vs SMB | 0.20-0.30 | 低 |
| HML vs CMA | 0.60-0.80 | 高(都是「保守」style) |
| HML vs MOM | -0.20 ~ -0.40 | 中(反向) |
| RMW vs CMA | 0.30-0.50 | 中 |
CMA 和 HML 高度共线导致单独看 β_HML 和 β_CMA 的 t-stat 都不显著,但联合看显著——这是 OLS 共线性的经典症状。两个对策:
- 跑 OLS 时直接看 F-test for
H_0: β_HML = β_CMA = 0,而不是逐个 t-test - 或者干脆扔掉 CMA,用 4 因子(MKT/SMB/HML/MOM,Carhart 模型)——损失一点解释力但 coefficient 更稳定
我个人 dashboard 用完整 FF5+MOM;做面试 / 写报告时用 Carhart 4 因子(更经典、更易解释)。
四、代码实现:通用归因函数
4.1 数据准备
# attribution.py
import pandas as pd
import numpy as np
import statsmodels.api as sm
from pathlib import Path
def load_ff5_factors(path: str) -> pd.DataFrame:
"""加载 Fama-French 5 + MOM 因子(French 官网 daily CSV 已合并版)"""
df = pd.read_csv(path, index_col=0, parse_dates=True)
df.columns = [c.strip() for c in df.columns]
# French 给的是百分比,转小数
factor_cols = ['Mkt-RF', 'SMB', 'HML', 'RMW', 'CMA', 'MOM', 'RF']
df[factor_cols] = df[factor_cols] / 100.0
return df.rename(columns={'Mkt-RF': 'MKT_RF'})
def load_strategy_returns(path: str, strategy_name: str) -> pd.Series:
"""加载某个策略的 daily return(来自 paper account 日终快照)"""
df = pd.read_csv(path, parse_dates=['date']).set_index('date')
return df[strategy_name].rename(strategy_name)
4.2 单策略归因函数
def attribute(strategy_ret: pd.Series, factors: pd.DataFrame,
factor_cols=('MKT_RF','SMB','HML','RMW','CMA','MOM')) -> dict:
"""
对单个策略的 daily return 跑 OLS 归因。
返回 alpha (年化%)、各因子 beta、t-stats、R²、残差序列等。
"""
# align
df = factors.join(strategy_ret.to_frame('R_p'), how='inner').dropna()
if len(df) < 20:
raise ValueError(f"样本不足 ({len(df)} 天),归因不可靠")
# 因变量:超额收益
y = df['R_p'] - df['RF']
X = df[list(factor_cols)]
X = sm.add_constant(X) # 加截距 = alpha
model = sm.OLS(y, X).fit()
alpha_daily = model.params['const']
alpha_annual = (1 + alpha_daily) ** 252 - 1
alpha_t = model.tvalues['const']
betas = {f: model.params[f] for f in factor_cols}
t_stats = {f: model.tvalues[f] for f in factor_cols}
resid = model.resid
idio_vol_annual = resid.std() * np.sqrt(252)
# 归因分解:每日 contribution
contrib = pd.DataFrame(index=df.index)
contrib['market_beta'] = betas['MKT_RF'] * df['MKT_RF']
for f in factor_cols[1:]: # SMB, HML, RMW, CMA, MOM
contrib[f'factor_{f}'] = betas[f] * df[f]
contrib['alpha'] = alpha_daily # 每天平均分摊
contrib['idiosyncratic'] = resid
contrib['total_excess'] = y
return {
'alpha_annual': alpha_annual,
'alpha_t': alpha_t,
'betas': betas,
't_stats': t_stats,
'r2': model.rsquared,
'r2_adj': model.rsquared_adj,
'idio_vol_annual': idio_vol_annual,
'n_days': len(df),
'contrib_daily': contrib,
'model': model
}
4.3 跑三策略 + 组合
factors = load_ff5_factors('data/ff5_mom_daily.csv')
results = {}
for s in ['S1_TwoFactor', 'S2_Wheel', 'S3_IC_LLM', 'Portfolio']:
ret = load_strategy_returns('data/strategy_daily.csv', s)
res = attribute(ret, factors)
results[s] = res
print(f"\n=== {s} ===")
print(f"N days: {res['n_days']}, R²(adj): {res['r2_adj']:.3f}")
print(f"Alpha (annualized): {res['alpha_annual']*100:.2f}% t={res['alpha_t']:.2f}")
print(f"Idio vol (annualized): {res['idio_vol_annual']*100:.2f}%")
print("Betas:")
for f, b in res['betas'].items():
print(f" {f:>8}: {b:+.3f} (t={res['t_stats'][f]:+.2f})")
4.4 安全护栏
def alpha_is_credible(res: dict) -> tuple[bool, str]:
"""判断 alpha 是否可信。返回 (是否可信, 原因)"""
if res['n_days'] < 20:
return False, f"样本太少 ({res['n_days']} < 20)"
if abs(res['alpha_t']) < 2.0:
return False, f"alpha t-stat = {res['alpha_t']:.2f}, < 2.0 不显著"
signal_to_noise = res['alpha_annual'] / res['idio_vol_annual']
if signal_to_noise < 1.0:
return False, f"信噪比 {signal_to_noise:.2f} < 1.0, alpha 小于残差波动"
if res['r2_adj'] > 0.95:
return False, f"R² adj = {res['r2_adj']:.2f} 过高, 策略可能就是 leveraged market"
return True, "credible"
生产环境每次跑归因都过这个 gate,结论才进 dashboard。
五、三策略归因结果(illustrative,数字示例)
跑完 OLS 后我得到的归因结果(部署到 Day 52,N ≈ 35-50 工作日):
5.1 S1: 双因子组合(Momentum + Low Vol)
N days: 48
R² (adj): 0.78
Alpha (annualized): +2.10% t = 1.85 ← 接近显著但未达 2.0
Idio vol (annualized): 6.4%
Betas:
MKT_RF: +0.71 (t = +8.2) ← 主要是市场暴露
SMB: +0.08 (t = +0.6)
HML: +0.21 (t = +1.4) ← 价值有点暴露
RMW: +0.05 (t = +0.3)
CMA: -0.02 (t = -0.1)
MOM: +0.42 (t = +3.8) ← 动量暴露显著 ✓ 策略画像一致
解读:
- β_MKT = 0.71,相当于 71% 仓位的市场暴露——和我做 long-only 大盘股 + 部分现金的设计一致
- β_MOM = 0.42 显著为正,策略名字「动量」和实际暴露一致,这是好事
- α = 2.1% 但 t = 1.85 < 2.0,目前不能宣称 alpha,再跑 1-2 个月看是否稳定
- R² adj 0.78:模型解释了 78% 的方差,剩下 22% 是 idio——比例健康
结论:这个策略大部分赚的是便宜的 factor beta(动量 + 价值的小暴露)+ 市场 beta。这不丢人,因子 ETF 化时代很多 hedge fund 也就这水平。但我自己跑要扣去摩擦成本(佣金 / 滑点 / 重平衡冲击),如果扣完 < 直接买 MTUM ETF,那就该买 ETF 而不是自己跑。
5.2 S2: Wheel 期权(CSP + CC)
N days: 38
R² (adj): 0.34
Alpha (annualized): +5.80% t = 2.31 ← 显著 ✓
Idio vol (annualized): 4.8%
Betas:
MKT_RF: +0.38 (t = +4.1) ← Delta 控制后的市场暴露
SMB: -0.04 (t = -0.3)
HML: +0.08 (t = +0.5)
RMW: +0.11 (t = +0.7)
CMA: +0.03 (t = +0.2)
MOM: -0.05 (t = -0.4)
解读:
- β_MKT = 0.38——Wheel 通过 Delta < 0.3 的 OTM CSP 把市场暴露压低到 0.38,和设计预期一致
- 其他因子 loading 都不显著,符合 Wheel 不依赖 style factor 的本质
- α = 5.8% 且 t = 2.31 ≥ 2.0,统计上显著
- 信噪比 = 5.8 / 4.8 = 1.21 > 1.0 ✓
- R² adj 0.34:因子模型只能解释 34% 的方差——剩下的 66% 是期权 Theta + IV 路径这些 FF5 根本没建模的东西
结论:Wheel 是真有 α,但要警惕:
- 38 天样本仍偏少,且 IV 环境(VIX 区间)单一——遇到 VIX spike 时这套数字可能完全不一样
- α 的本质是「承担尾部风险换 Theta」,长期 Sharpe 高但偶尔会被 single trade 干穿(黑天鹅周)。归因里看不到,必须独立用 VaR / max drawdown 监控
- β_MKT = 0.38 看起来低,但 Wheel 的真实暴露在标的暴跌 20% 时会急速攀升到 0.9+(Delta 非线性)。这是 OLS 线性回归捕捉不到的。单看归因表会低估 Wheel 的尾部风险
5.3 S3: 财报 Iron Condor + LLM signal
N days: 31 ← 样本最少,结论最弱
R² (adj): 0.08 ← market 几乎完全不解释
Alpha (annualized): +3.20% t = 1.42 ← 不显著
Idio vol (annualized): 9.6%
Betas:
MKT_RF: +0.03 (t = +0.4) ← market-neutral ✓
SMB: +0.02 (t = +0.1)
HML: -0.07 (t = -0.5)
RMW: +0.04 (t = +0.3)
CMA: +0.01 (t = +0.1)
MOM: -0.03 (t = -0.2)
解读:
- β_MKT ≈ 0:真的 market-neutral,符合 Iron Condor 在标的不大涨不大跌时收钱的设计
- 因子 loading 都不显著——也合理,IC 不押 style
- α = 3.2% 看着不错,但 t = 1.42 不显著
- 信噪比 = 3.2 / 9.6 = 0.33 << 1.0,alpha 远小于残差波动,本质是「赚一笔大的,亏一两笔小的」的事件驱动 P&L,OLS 平均化后显著性必然弱
- R² adj 0.08 :因子模型几乎完全无能为力,这恰恰是事件驱动策略该有的特征——它的 return 跟市场和因子无关,而是跟单只个股的财报路径有关
结论:
- 不能因为 t-stat 不显著就否定 S3。事件驱动的天然样本结构就是 lumpy(一笔交易决定一周收益),OLS 不是评估它的最好工具
- 正确的评估方法应该是「per-trade analysis」:每笔 IC 的 P&L 分布、win rate、avg win / avg loss、median holding period、LLM signal vs no-signal 的对照——这些 Day 50 已经开始记录了
- 归因表对 S3 的真正价值是确认它「和 S1 / S2 不相关」——这意味着组合层面 S3 提供分散化收益,而不是收益本身
5.4 三策略相关性矩阵(归因前必看)
S1_TwoFactor S2_Wheel S3_IC_LLM
S1_TwoFactor 1.00 0.42 0.08
S2_Wheel 0.42 1.00 0.11
S3_IC_LLM 0.08 0.11 1.00
观察:
- S1 ↔ S2 相关性 0.42:两者都吃到一些市场 β(S1 是 long-only 仓,S2 是 CSP 卖方 Delta),所以正相关合理
- S3 几乎和 S1 / S2 都不相关(< 0.15):market-neutral 设计 + 事件驱动单股,分散化价值最高
- 这预示着 Day 53 Kelly 优化会给 S3 一个比单注 Kelly 更大的权重(因为它对组合 vol 的边际贡献小)
如果 S1 ↔ S2 相关性高到 > 0.7,那相当于在做两份「同款」策略,分散化收益基本没有;< 0.5 时分散化才显著起效。
5.5 Portfolio(三策略合并)
N days: 50
R² (adj): 0.62
Alpha (annualized): +3.40% t = 2.18 ← 显著 ✓
Idio vol (annualized): 4.1%
Betas:
MKT_RF: +0.51 (t = +6.8)
SMB: +0.04 (t = +0.3)
HML: +0.13 (t = +1.1)
RMW: +0.07 (t = +0.5)
CMA: +0.01 (t = +0.0)
MOM: +0.24 (t = +2.6)
组合归因分解(年化 return 约 11.5%,paper 数字):
| 来源 | 贡献(年化) | 占比 | 解读 |
|---|---|---|---|
| 无风险利率 R_f | +5.0% | 43% | 单纯持现金也能拿到的,不算业绩 |
| Market β × (R_m - R_f) | +0.51 × 7.8% = +4.0% | 35% | 便宜的市场暴露,SPY 就能复制 |
| Factor β × factors | +0.24 × MOM(~3%) + 其余 ≈ +1.1% | 10% | 便宜的因子暴露,MTUM 之类 ETF 能复制 |
| α | +3.4% | 30% | 真正能宣称的 α(但要注意样本短) |
| Idio noise(平均消掉) | ≈ 0 | — | — |
| 合计(年化) | ≈ 13.5% 名义 / 11.5% 实际 | 100% |
注意:百分比加总因为有 R_f 的处理和复利不严格等于,PM dashboard 用上面分解传达直觉就够。
最关键的观察:
- 组合 α (3.4%, t=2.18) 比任何单策略的 α 都更显著——这是分散化在归因层面的体现(idio vol 从 6.4% / 4.8% / 9.6% 降到 4.1%,t-stat 自然上来了)
- 但组合 β_MKT = 0.51 也比 S2 / S3 单独的 β 高——因为 S1 把整体往市场暴露拉高了
- 如果我想让组合更像「pure α 工厂」,下一步要么降低 S1 比例,要么给 S1 加一个 SPY 空头对冲做成 market-neutral 版本
5.6 把组合归因写成「PM friendly 一句话」
练习把刚才几张表压缩成一句话给老板(或给未来的自己):
「组合本月 +4.8%,其中市场 β 贡献 ~1.8%(被动暴露),factor β 贡献 ~0.5%(动量倾向),策略 α 贡献 ~1.3%(其中 Wheel 主导),剩余 ~1.2% 是 idio 波动(含 Wheel 收 Theta 的事件性 P&L)。α 部分 t=2.18 已显著但样本 50 天偏短,对外不宣称、对内继续观察。下一步建议:S3 IC + LLM 信号噪声过大,先减仓继续测;S1 因子组合考虑加 SPY 对冲变 market-neutral,剥离掉那 35% 的 cheap market β。」
这一段话浓缩了数据 → 解读 → 行动三层,是 PM dashboard 的最终交付物。如果你的归因系统不能产出这种一句话,那归因还没做完。
六、归因可视化:stacked area + 月度表
6.1 Stacked area chart(每日累计贡献)
import matplotlib.pyplot as plt
contrib = results['Portfolio']['contrib_daily']
# 累计
cum_contrib = (1 + contrib).cumprod() - 1
# 简化为 4 个 bucket
bucket = pd.DataFrame(index=contrib.index)
bucket['Market β'] = contrib['market_beta'].cumsum()
bucket['Factor β'] = contrib[[c for c in contrib if c.startswith('factor_')]].sum(axis=1).cumsum()
bucket['Alpha'] = contrib['alpha'].cumsum()
bucket['Idio'] = contrib['idiosyncratic'].cumsum()
fig, ax = plt.subplots(figsize=(10, 5))
ax.stackplot(bucket.index,
bucket['Market β'], bucket['Factor β'],
bucket['Alpha'], bucket['Idio'],
labels=['Market β','Factor β','Alpha','Idiosyncratic'],
alpha=0.7)
ax.legend(loc='upper left'); ax.set_title('Portfolio Cumulative Attribution')
ax.set_ylabel('Cumulative excess return'); ax.axhline(0, color='k', lw=0.5)
plt.tight_layout(); plt.savefig('reports/attribution_stacked.png', dpi=150)
视觉直觉:
- 「Market β」「Factor β」这两层应该跟 SPY / MTUM 走势同步
- 「Alpha」应该是一根缓缓向上的直线(截距项是常数,累加成直线)
- 「Idio」应该是围绕 0 的高频抖动——如果它持续上涨,说明你的因子模型漏掉了某个解释维度(该加因子了)
6.2 月度归因表
| 月份 | 总收益 | Market β | Factor β | Alpha | Idio |
|---|---|---|---|---|---|
| 2026-05 | +2.4% | +1.6% | +0.3% | +0.28% | +0.22% |
| 2026-06 | +2.3% | +0.9% | +0.5% | +0.28% | +0.62% |
把它放进 weekly review 文档里,比看一根净值线信息量大 10 倍。
6.3 月度归因表(扩展版)
把每月归因再拆细一点,加上「该归功于谁/哪个策略」的标注,PM review 时 actionable:
| 月份 | 总收益 | Market β | Factor β | Alpha (S1) | Alpha (S2) | Alpha (S3) | Idio | 备注 |
|---|---|---|---|---|---|---|---|---|
| 2026-05 | +2.4% | +1.6% | +0.3% | +0.10% | +0.18% | +0.00% | +0.22% | S3 5月未部署 |
| 2026-06 | +2.3% | +0.9% | +0.5% | +0.08% | +0.21% | -0.01% | +0.62% | S3 部署首月,单笔 IC 亏损 |
这种分策略 alpha 拆解需要在 OLS 之外加一步:把 portfolio return 按当期每个策略的资金占比 × 该策略 daily return 反推。是工程量,但 review 价值极大。
6.4 归因频率:日 / 周 / 月怎么选
| 频率 | 用途 | 注意 |
|---|---|---|
| 每日 | 早盘前看一眼昨日 contribution | 单日噪声大,绝不基于单日做策略调整 |
| 每周 | 团队 review / 个人复盘 | 5 个样本依然单薄,主要看趋势是否延续 |
| 每月 | 正式归因,调仓的依据 | 20+ 样本,OLS 有起码意义 |
| 每季 | 写 attribution report | 60+ 样本,可以信地用 t-stat 做判断 |
新手最常犯的错:用日度归因做月度决策——比如今天 Market β 解释了 90%,就紧张地想「我没 α 了!」;明天 Idio +1.5%,又激动地想「我有 α 了!」。归因的统计意义是月度起步的。
七、五个最容易犯的归因错误
我自己在 zero → one 阶段几乎全踩过。
错误 1:把市场 long-only return 当 alpha
最典型场景:你 long-only 跑了 6 个月,赚 12%,宣称「我的策略年化 24%」。
事实:同期 SPY 涨了 10%,你 β = 1.0 的 long-only 暴露理论上「应该」赚 10%,所以真正属于你的 α 只有 2%——还要扣去波动率成本(你比 SPY 波动大 30%,按 Sharpe 调整后 α 可能为负)。
防呆:任何宣称 α 的语句必须先减去 β × R_m。我的 dashboard 里把「naive return」和「β-adjusted excess」两列并排显示,避免自欺欺人。
错误 2:把 factor exposure 当 alpha
「我重仓 NVDA / META / GOOG,过去一年大涨 60%!」
事实:你重仓 mega-cap growth + momentum 因子敞口,这就是 cheap beta。MTUM ETF 一样能做到,且费率 0.15% / 年。你只是用主动方式复制了 ETF,没有 α。
防呆:归因表里的「factor β × factor return」单独一列,让自己一眼看到「这一块 ETF 能复制」。
错误 3:没控制 size / quality / momentum 多重 exposure
经典坑:跑 5 因子 OLS 时,把 SMB 漏掉,只用 MKT + HML + MOM 三因子。结果 SMB 的暴露会被错误归到 α 里——只要你重仓小盘股,SMB 同期涨,你的 α 就被虚高。
防呆:能用就用全套 FF5 + MOM。即使个别因子不显著,留着也不会让其它系数变差(OLS 的 unbiased 性质保证)。
错误 4:用 wrong R_f
T-Bill 不是 0,2026 年大约 4.5%-5% 年化。如果你忘了减 R_f,所有 α 都会被高估 ≈ 5% / 年。
防呆:French 官网 CSV 直接带 RF 列,用就行;自己构造时务必扣 IRX/252/100。
错误 5:用 wrong window
样本太短(< 20 天)跑归因,结果不可信但容易让人产生「我有 α」的虚假信心。这是个人量化最危险的 confirmation bias 源头。
防呆:在 dashboard 第一行就显示 N = 35 这种样本数字,强制自己看到。
八、何时 / 如何怀疑「α 是噪声」
工程清单:
| 信号 | 噪声怀疑度 | 行动 |
|---|---|---|
| t-stat α < 2.0 | 高 | 不在面试 / report 里宣称 α |
| t-stat α 在 2-3 之间 | 中 | 继续观察 1-2 个月 |
| α / σ(ε) < 1.0 | 高 | α 实际上被 idio noise 淹没 |
| 前半段 α 和后半段 α 异号 | 极高 | 几乎肯定是 fluke,停止依赖此策略 |
| R² adj < 0.1 且 α 巨大 | 高 | 多半是几笔大单驱动,不是 process |
| α 显著但因子 loading 和策略名字不一致 | 中 | 策略画像有问题,先搞清楚自己在做什么 |
| α 在牛市段显著、熊市段不显著 | 中-高 | 实际是 leveraged market 不是 α |
最重要的一句话:「t-stat < 2 时,对外别说 alpha,对内当 hypothesis」。
九、PM 视角:归因思维的迁移性
- 归因 = revenue waterfall。任何业务/策略的「为什么赚」如果说不出 channel-level 来源,下次没法复制,也没法 cut 亏损支线。
- R² 太高未必好。在产品里相当于「客户行为可以被几个外生指标完全预测」——意味着你的产品没有自己的护城河,全是市场红利。
- t-stat 是「面对怀疑论者也敢出口」的最低门槛。Stripe / Square / 蚂蚁的内部 review 里,说效应大小没用,要说置信区间。这是 senior PM 和 junior PM 的分水岭。
- idio 持续上涨 = 模型缺失。如果你的归因里残差累计上涨,要么是真新 α(恭喜),要么是漏掉了一个重要因子(更可能)。两种解释都要求你再加一个因子重新跑,而不是默认归功于自己。
- 不归因 = 没办法 cut。S1 在熊市可能 -10%、S2 可能 +2%、S3 可能 -5%——如果不归因,你可能砍错策略;归因之后会发现 S2 在熊市才是该加仓的稳定 α 源。
十、明日预告
Day 53: Kelly 升级 — 从单注 Kelly 到多策略 fractional Kelly + risk budgeting
Day 52 归因告诉我们 S1 / S2 / S3 各自的 α 和 idio vol,下一个问题就是:
- 三个策略之间应该如何分配资金?等权分配是次优的
- 单策略 Kelly(Day 20 学过)是 f* = μ / σ²,但多策略 Kelly 要用协方差矩阵 Σ:$\mathbf{f}^* = \Sigma^{-1} \mu$
- 实践中要用 fractional Kelly(一般 0.25 - 0.5 ×)避免参数估计误差被 Kelly 放大
- 另一种视角:risk budgeting / risk parity——让每个策略贡献相同的 risk,而不是相同的 capital
明天会把今天归因得到的 α / idio vol / 协方差矩阵直接喂给 Kelly 优化器,得出三策略的最优资金分配建议——并和现在「直觉等权」做对比。
实际执行记录
启动一项填一项,时间戳 + 卡点。
- [hh:mm] 下载 French CSV 并解析 — ...
- [hh:mm] 三策略 daily return 收集(从 paper account 日终快照) — ...
- [hh:mm] OLS 归因函数写完跑通 — ...
- [hh:mm] 三策略 + 组合归因表生成 — ...
- [hh:mm] Stacked area chart 出图 — ...
- [hh:mm] alpha_is_credible gate 过完一遍 — ...
- 卡点 / 学到的:
总字数:约 5,700 字 今日完成度:理论 ✓ / 实操(你自己执行)/ 笔记 ✓