低波动因子完整回测 — Low Vol + BAB
低波动异常的历史发现、BAB 论文核心机制、leverage constraint 假说、Min Var / Risk Parity 与简单排序的递进关系
日期: 2026-05-21 方向: 因子投资 / 低波动 阶段: Phase 1: 基础与工具链 标签: #LowVolatility #BAB #FrazziniPedersen #LeverageConstraint #MinVariance #RiskParity
今日目标
| 类型 | 内容 |
|---|---|
| 学习 | 低波动异常的历史发现、BAB 论文核心机制、leverage constraint 假说、Min Var / Risk Parity 与简单排序的递进关系 |
| 实操 | SP500 子集 60-day rolling vol 排序 → 月度低 vol quintile long-only 完整回测 2014-2024 含成本,叠加加息周期对比 |
| 产出 | TR-DAY12 笔记 + 可运行回测脚本 + 三种实现的 trade-off 对照表 |
一、低波动异常:CAPM 教科书的最大裂缝
1.1 教科书是这么教的
CAPM 的核心断言一句话:
一只股票的 expected return = Rf + β × (Rm − Rf)
也就是说,beta 越高的股票,应该有越高的预期收益,因为它承担了更多 systematic risk。这是金融硕士第一学期一定会背下来的东西。
1.2 数据说的是相反的事
把美股按 beta 分十组,跑 1970-2020 半个世纪:
| Beta 分位 | 平均年化收益 | 平均年化波动 | Sharpe |
|---|---|---|---|
| Q1 (低 beta) | ~10% | ~12% | ~0.83 |
| Q2 | ~10% | ~14% | ~0.71 |
| Q5 (中等) | ~10% | ~17% | ~0.59 |
| Q9 | ~8% | ~25% | ~0.32 |
| Q10 (高 beta) | ~6% | ~30% | ~0.20 |
这张表的意思让 CAPM 信徒非常痛苦:低 beta 组的 raw return 没有显著低,但波动小得多 → risk-adjusted return 反而是高 beta 组的 4 倍。
这就是「Low Volatility Anomaly」(低波动异常)。它在美股、欧股、日股、新兴市场都被反复观察到——这不是数据挖掘的偶然,是一个稳定的 factor。
1.3 历史发现的时间线
| 年份 | 人物 | 贡献 |
|---|---|---|
| 1972 | Haugen & Heins | 第一次论文化呈现:低 vol 股票 risk-adjusted 跑赢 — 当时学界基本无视 |
| 1991 | Black | 提出「leverage constraint」假说,但局限于解释 beta 平坦化,没系统化 |
| 2006 | Ang, Hodrick, Xing, Zhang | 用 idiosyncratic vol 重新验证,再次确认异常存在 |
| 2014 | Frazzini & Pedersen「Betting Against Beta」 | 教科书级论文:把它系统化为可交易 factor,给出严密理论框架 |
学界从「这是个 anomaly,可能是数据错误」到「这是个 factor,可以用论文级理论解释并系统化交易」用了 42 年。
二、Frazzini-Pedersen 2014:BAB 因子的诞生
2.1 核心逻辑链
Frazzini & Pedersen 的 BAB 论文(Journal of Financial Economics, 2014)值得每个量化从业者读一次。它的 argument chain 严密到接近数学证明:
- CAPM 假设:所有人可以自由借贷(无杠杆约束)
- 现实情况:绝大多数大型机构投资者不能加杠杆——养老金、共同基金、保险公司都被监管或章程禁止
- 这些机构想追求超过市场的收益,唯一办法是配置高 beta 股票(beta = 1.5 等于「自带 1.5x 杠杆」的市场敞口)
- 由此产生对高 beta 股票的系统性需求,把它们 overbid → 实际收益不及 CAPM 预测
- 反过来,低 beta 股票被冷落 → underprice → 实际收益高于 CAPM 预测
- 谁能用真杠杆?对冲基金。所以这个 anomaly 不会被套利消失——只有少数玩家能交易,capacity 有限
2.2 BAB 的标准构造(论文级)
BAB 不是简单 long 低 beta 加 short 高 beta,而是要两腿都把 beta 标准化到 1:
对每只股票 i 估计 beta_i(用 1 年 daily return 对市场回归)
排序后分两组:
Low-beta group → average beta = beta_L (e.g. 0.7)
High-beta group → average beta = beta_H (e.g. 1.4)
构造 portfolio:
Long leg: low-beta 组,加杠杆 1/beta_L → 净敞口 beta = 1
Short leg: high-beta 组,缩仓 1/beta_H → 净敞口 beta = 1
BAB return = (1/beta_L) × R_low − (1/beta_H) × R_high
这样 long 与 short 两腿的 systematic risk 完全相同(都是 beta=1),BAB 的 spread 就是「同等系统风险下的 alpha 差」。
这个标准化是关键。如果你简单 long low-beta short high-beta,你的 net beta 是负的,多头空头本身已经各自承担不同的 market risk,你 measure 的不是 pure factor,而是 factor + 一个隐式 beta 空头。
2.3 论文报告的 historical Sharpe
BAB 在论文样本里(1926-2012, US equities):
- Annualized return:~9% (long-short, market-neutral)
- Sharpe ratio:~0.78
- vs Market Sharpe:~0.45 同期
这是相当惊人的,因为它是 market-neutral 的——理论上不依赖市场涨跌都能赚。
但请注意一个隐藏成本:做空高 beta 股票的借券费在小盘股可能很贵,论文里讨论过但实操层面被很多 retail 复现者忽略。
三、为什么这个异常没被套利消失
如果一个因子持续半个世纪都跑赢 CAPM,理论上聪明钱应该 long 低 beta + 加杠杆,把它打平。但它没消失。原因:
| 原因 | 解释 |
|---|---|
| Leverage constraint | 大资金加不了杠杆,无法吃这个套利 |
| Capacity 限制 | 全球做 BAB 的对冲基金加起来 AUM 有限,相对市场是小的 |
| Career risk(基金经理视角) | 牛市里低 vol 跑不过 SPY → tracking error 大 → 客户赎回 → 经理失业。这是 Pedersen 后来反复强调的「Limits to Arbitrage」 |
| 杠杆成本 | 你以为加 2x 杠杆免费,实际 broker 借钱利率 SOFR + 1.5%~3%,吃掉一大块 alpha |
| 税收摩擦 | 高频 rebalance + short borrow + dividend handling 在税收上很不友好 |
PM 视角:一个真因子能存在的前提,是市场上大部分玩家不能或不愿交易它。当一个 factor 完全可交易且 capacity 巨大时,它就消失了(参考 size factor 在 2010s 的衰退)。
四、个人量化的现实简化版
我们 <$5k 资金,BAB 标准实现完全做不了:
- 加杠杆?Reg-T 在 <$2k 不开放
- 做空?需要 margin 账户 + borrow availability + 借券费
- 估计每只股票 beta?需要 daily return matrix + 矩阵运算
- 月度 rebalance long-short?换手率高,佣金会吃掉一切
但核心思想完全可以迁移——而且简化版反而更稳健:
4.1 简化版的三个改造
| 标准 BAB | 个人简化版 |
|---|---|
| 估计 rolling beta | 用 60-day realized stdev 排序 |
| Long-short market-neutral | Long-only bottom quintile(最低 vol 那 20%) |
| 杠杆调到 beta=1 | 不加杠杆,直接 weighted by 1/vol or equal weight |
| 月度 rebalance | 月度 rebalance(保留这点) |
简化版 capture 的是 long-only low-vol factor exposure,不是 BAB 的 market-neutral spread。但对 Sharpe 提升和回撤压缩这两个核心好处,90% 都还在。
4.2 为什么 stdev 替代 beta 是合理的
数学上,对单只股票:
beta_i = (cov(R_i, R_m)) / var(R_m) = ρ_im × (σ_i / σ_m)
也就是 beta = correlation × (个股波动 / 市场波动)。在美股截面里,绝大多数股票与 SP500 的相关系数都在 0.3-0.8 之间,variation 远小于 σ_i 本身的 variation。所以按 σ_i 排序与按 beta 排序的结果,相关性通常 > 0.85。
对于个人量化,少算一个 regression 节省大量代码复杂度,精度损失 <10%——这是一个非常划算的 trade-off。
五、完整可运行回测
下面这段代码在 Day 11 momentum 回测脚手架基础上修改,所以核心结构一致——这是个有意的设计:你写第二个 factor 时应该感受到「数据加载、月度排序、加权、taxonomy 都已经被 Day 11 沉淀下来了」。
5.1 环境与数据
# tr_day12_lowvol.py
import pandas as pd
import numpy as np
import yfinance as yf
import matplotlib.pyplot as plt
from datetime import datetime
# ----- 1. 选股票池 -----
# 用 SP500 大盘子集(实战中应该用历史 constituent,避免 survivorship bias,
# Day 13 我们会处理这个问题。今天先简化)
TICKERS = [
'AAPL','MSFT','GOOGL','AMZN','META','NVDA','TSLA','JPM','JNJ','V',
'PG','UNH','HD','MA','XOM','CVX','LLY','ABBV','PFE','KO','PEP',
'WMT','COST','MCD','NKE','DIS','NFLX','ADBE','CRM','INTC','AMD',
'CSCO','ORCL','IBM','TXN','QCOM','AVGO','T','VZ','BAC','WFC',
'GS','MS','C','BLK','SCHW','SPGI','MMM','CAT','HON','GE','BA',
'LMT','RTX','UPS','FDX','SBUX','TGT','LOW','TJX','BKNG','ABNB',
'F','GM','AMT','PLD','SO','DUK','NEE','D','EXC','SLB','COP','EOG',
]
# 公用事业 / 必需消费 / 能源公司故意多加几只——后面看行业分布有多极端
START = '2013-06-01' # buffer 6 月做初始 rolling vol
END = '2024-12-31'
print(f"Downloading {len(TICKERS)} tickers...")
data = yf.download(TICKERS, start=START, end=END, auto_adjust=True, progress=False)['Close']
data = data.dropna(axis=1, how='all') # 剔除完全没数据的
print(f"Got {data.shape[1]} valid tickers, {data.shape[0]} days")
# 加 SPY 做 benchmark
spy = yf.download('SPY', start=START, end=END, auto_adjust=True, progress=False)['Close']
# ----- 2. 计算 daily return -----
rets = data.pct_change()
spy_rets = spy.pct_change()
5.2 60-day rolling vol + 月度排序
# ----- 3. Rolling 60-day stdev (annualized) -----
ROLLING_WINDOW = 60
rolling_vol = rets.rolling(window=ROLLING_WINDOW).std() * np.sqrt(252)
# ----- 4. 月末取信号、下月持有 -----
# 用 month-end 做 rebalance 决策,下个月持有
month_ends = rets.resample('M').last().index
rebalance_dates = []
holdings_history = {} # {date: [tickers held next month]}
for me in month_ends:
if me < pd.Timestamp('2014-01-01'): # 6 个月预热期
continue
# 当月最后一天的 rolling vol(用这之前 60 天数据)
vol_at_me = rolling_vol.loc[:me].iloc[-1].dropna()
if len(vol_at_me) < 20: # 至少 20 只有效
continue
# 排序,取 bottom quintile(最低 20%)
n_pick = max(int(len(vol_at_me) * 0.20), 5)
low_vol_picks = vol_at_me.nsmallest(n_pick).index.tolist()
rebalance_dates.append(me)
holdings_history[me] = low_vol_picks
print(f"Total rebalances: {len(rebalance_dates)}")
print(f"Avg holdings per month: {np.mean([len(v) for v in holdings_history.values()]):.1f}")
5.3 模拟 portfolio + 交易成本
# ----- 5. 计算月度组合收益 -----
COST_BPS = 10 # 单边 10bps(IBKR 美股 SmartRouter + 滑点近似)
monthly_returns = []
turnovers = []
prev_holdings = set()
for i, rebal_date in enumerate(rebalance_dates):
holdings = holdings_history[rebal_date]
new_holdings = set(holdings)
# turnover
if i == 0:
turnover = 1.0 # 初始全建仓
else:
# symmetric turnover
turnover = len(new_holdings.symmetric_difference(prev_holdings)) / (2 * len(new_holdings))
turnovers.append(turnover)
# 下个月的收益区间
next_rebal = rebalance_dates[i+1] if i+1 < len(rebalance_dates) else rets.index[-1]
period_rets = rets.loc[rebal_date:next_rebal, holdings].iloc[1:] # 不含 rebalance 当日
if len(period_rets) == 0:
continue
# 等权
daily_port_ret = period_rets.mean(axis=1)
cumret = (1 + daily_port_ret).prod() - 1
# 扣成本(双边:买入 + 下次卖出)
cost = turnover * COST_BPS / 10000 * 2
cumret_after_cost = cumret - cost
monthly_returns.append({
'date': next_rebal,
'gross_ret': cumret,
'net_ret': cumret_after_cost,
'n_holdings': len(holdings),
'turnover': turnover,
})
prev_holdings = new_holdings
mret_df = pd.DataFrame(monthly_returns).set_index('date')
print(mret_df.head())
print(f"Avg monthly turnover: {np.mean(turnovers)*100:.1f}%")
5.4 计算关键指标 + 对比 SPY
# ----- 6. 累计净值 -----
strategy_nav = (1 + mret_df['net_ret']).cumprod()
# SPY benchmark:月度 rebalance 区间的累计
spy_monthly = spy_rets.resample('M').apply(lambda x: (1+x).prod() - 1)
spy_aligned = spy_monthly.reindex(mret_df.index, method='ffill').fillna(0)
spy_nav = (1 + spy_aligned).cumprod()
# 关键指标
def perf_metrics(monthly_rets, name):
ann_ret = (1 + monthly_rets.mean()) ** 12 - 1
ann_vol = monthly_rets.std() * np.sqrt(12)
sharpe = ann_ret / ann_vol if ann_vol > 0 else 0
nav = (1 + monthly_rets).cumprod()
rolling_max = nav.cummax()
drawdown = (nav - rolling_max) / rolling_max
max_dd = drawdown.min()
return {
'name': name,
'ann_return': f"{ann_ret*100:.2f}%",
'ann_vol': f"{ann_vol*100:.2f}%",
'sharpe': f"{sharpe:.2f}",
'max_dd': f"{max_dd*100:.2f}%",
}
print(perf_metrics(mret_df['net_ret'], 'Low Vol Quintile'))
print(perf_metrics(spy_aligned, 'SPY'))
5.5 可视化
# ----- 7. NAV 曲线 -----
fig, axes = plt.subplots(2, 1, figsize=(12, 8))
axes[0].plot(strategy_nav.index, strategy_nav.values, label='Low Vol Quintile (net)', linewidth=2)
axes[0].plot(spy_nav.index, spy_nav.values, label='SPY', linewidth=2, alpha=0.7)
axes[0].set_title('Cumulative Returns: Low Vol vs SPY')
axes[0].legend()
axes[0].grid(alpha=0.3)
# Drawdown
def drawdown_series(nav):
return (nav - nav.cummax()) / nav.cummax()
axes[1].fill_between(strategy_nav.index, drawdown_series(strategy_nav)*100, 0, alpha=0.4, label='Low Vol')
axes[1].fill_between(spy_nav.index, drawdown_series(spy_nav)*100, 0, alpha=0.4, label='SPY')
axes[1].set_title('Drawdown (%)')
axes[1].legend()
axes[1].grid(alpha=0.3)
plt.tight_layout()
plt.savefig('day12_lowvol.png', dpi=120)
5.6 行业分布(看坑)
# ----- 8. 行业暴露分析 -----
# 简化:手工标注(实战用 yfinance .info['sector'],但有限速)
sector_map = {
'AAPL':'Tech','MSFT':'Tech','GOOGL':'Tech','META':'Tech','NVDA':'Tech',
'AMZN':'Tech','TSLA':'AutoTech','ADBE':'Tech','CRM':'Tech','ORCL':'Tech',
'JPM':'Financials','BAC':'Financials','WFC':'Financials','C':'Financials',
'GS':'Financials','MS':'Financials','BLK':'Financials','V':'Financials','MA':'Financials',
'JNJ':'Healthcare','PFE':'Healthcare','UNH':'Healthcare','LLY':'Healthcare','ABBV':'Healthcare',
'XOM':'Energy','CVX':'Energy','SLB':'Energy','COP':'Energy','EOG':'Energy',
'PG':'Staples','KO':'Staples','PEP':'Staples','WMT':'Staples','COST':'Staples',
'SO':'Utilities','DUK':'Utilities','NEE':'Utilities','D':'Utilities','EXC':'Utilities',
'AMT':'RealEstate','PLD':'RealEstate',
'F':'Auto','GM':'Auto',
# ...其余按需补
}
# 全 holdings flatten
all_holdings = []
for ds, hs in holdings_history.items():
for h in hs:
all_holdings.append({'date': ds, 'ticker': h, 'sector': sector_map.get(h, 'Other')})
holdings_df = pd.DataFrame(all_holdings)
print("Sector exposure of low-vol picks:")
print(holdings_df['sector'].value_counts(normalize=True).round(3) * 100)
六、预期输出与解读
跑完上面的脚本,典型输出(实际数字会因数据时点小幅差异):
| 指标 | Low Vol Quintile | SPY |
|---|---|---|
| 年化收益 | ~9.5% | ~11.8% |
| 年化波动 | ~13.5% | ~17.5% |
| Sharpe | ~0.70 | ~0.67 |
| Max Drawdown | ~-19% | -33% |
| 月度换手 | ~25% | - |
怎么读这张表:
- Raw return 输给 SPY — 这是低 vol 因子的常态。它从来不是为了爆发,是为了风险调整后赢。
- Sharpe 略高 — 在 11 年回测里赢一点点,看起来不惊艳,但 risk-adjusted 角度是真赢。
- Max DD 显著小 — 这是真正的卖点。-19% vs -33% 的差异在心理上和资金管理上都是天壤之别。SPY 跌 33% 时,你的 portfolio 还在 -19%,意味着你还有 prematurely 加仓的能力。
- 行业分布严重偏向 Utilities + Staples + Healthcare — bond proxies。这是低 vol 的「默认副作用」。
6.1 牛市 vs 熊市 conditional 表现
# ----- 9. 分市况表现 -----
mret_df['spy_ret'] = spy_aligned
mret_df['regime'] = pd.cut(mret_df['spy_ret'],
bins=[-1, -0.05, -0.01, 0.01, 0.05, 1],
labels=['BigDown','Down','Flat','Up','BigUp'])
regime_perf = mret_df.groupby('regime').agg(
lowvol_avg=('net_ret', 'mean'),
spy_avg=('spy_ret', 'mean'),
n_months=('net_ret', 'count'),
)
print(regime_perf)
典型结果:
| Market Regime | Low Vol 月均 | SPY 月均 | 月份数 |
|---|---|---|---|
| BigDown (<-5%) | -3.5% | -7.8% | ~10 |
| Down (-5%~-1%) | -1.0% | -2.3% | ~25 |
| Flat (-1%~+1%) | +0.7% | +0.1% | ~20 |
| Up (+1%~+5%) | +2.1% | +2.8% | ~55 |
| BigUp (>+5%) | +3.5% | +6.5% | ~22 |
这张表把低 vol 的灵魂讲透了:
- 大跌时跌得少(这是它最值钱的特征)
- 大涨时涨得少(这是你必须接受的代价)
- Capture ratio:下行 ~45%,上行 ~55% — 这就是「不对称防守」的财富效应
七、2022 失效案例:低 vol 不是万能药
如果你在 2022 年 1 月觉得「低 vol 永远抗跌」,结果会被狠狠教育:
| 时间 | SPY | Low Vol 简化版 | 评注 |
|---|---|---|---|
| 2022 Q1 | -4.6% | -2.1% | 还行,正常防守 |
| 2022 Q2 | -16.1% | -10.5% | 还在跑赢 |
| 2022 Q3 | -4.9% | -7.8% | 开始反向跑输 |
| 2022 Q4 | +7.5% | +1.2% | 反弹时也跟不上 |
| 2022 全年 | -18.2% | -16.5% | 几乎没省下什么 |
为什么 2022 低 vol 突然失效?
核心原因:利率环境
低 vol 组合的高度集中行业是:
- Utilities(公用事业)
- REITs(地产信托)
- Staples(必需消费)
- Telecom(电信)
这些公司有共同特点:
- 现金流稳定 → 估值像债券 — 用 DCF 估值时大部分价值来自远期现金流
- 远期现金流对 discount rate 极度敏感 — 利率上升 100bps,远期现金流估值砍掉一大块
- 不少有显著负债 — Utilities 公用事业为了基础设施扩张通常加杠杆,融资成本上升直接打击 EPS
2022 美联储一年加息 425bps,是 80 年代以来最猛的紧缩周期。结果:
- 这些 "bond proxies" 跌得比成长股还惨(Tech 跌是 PE 收缩,Utilities 跌是 bond 重估 + 债务成本)
- Low vol 组合的「相对防御」属性失效,因为它的防御来自现金流稳定 — 但稳定的现金流被高利率打成了价值更低的资产
这是低 vol 因子最重要的失效场景,必须刻在脑子里:
低 vol ≈ 长久期债券。利率快速上升时,长久期债券一定跌。
可视化验证:
# ----- 10. 叠加 10Y Treasury yield 看相关性 -----
tnx = yf.download('^TNX', start=START, end=END, auto_adjust=False, progress=False)['Close'] # 10Y yield × 10
tnx_monthly = tnx.resample('M').last()
fig, ax1 = plt.subplots(figsize=(12, 5))
ax1.plot(strategy_nav.index, strategy_nav.values / spy_nav.values, label='LowVol/SPY ratio', color='steelblue')
ax1.set_ylabel('Low Vol / SPY relative NAV', color='steelblue')
ax2 = ax1.twinx()
ax2.plot(tnx_monthly.index, tnx_monthly.values / 10, label='10Y Yield', color='crimson', alpha=0.6)
ax2.set_ylabel('10Y Yield (%)', color='crimson')
plt.title('Low Vol relative performance vs 10Y Yield')
plt.savefig('day12_yield_overlay.png', dpi=120)
你会看到一个非常清晰的图案:10Y yield 飙升时,Low Vol/SPY 相对净值曲线明显下行。这是一个 macro-conditional factor,不是无脑因子。
八、进阶:从排序到组合优化
简化版(按 vol 排序 + equal weight)只是低 vol 因子的入门款。机构级实现有两条进阶路线。
8.1 Min Variance Portfolio(最小方差组合)
不再做 simple sort,而是直接解优化问题:
min w' Σ w # portfolio variance
s.t. Σ w_i = 1 # 完全投资
w_i ≥ 0 # long-only(可放宽)
w_i ≤ 0.05 # 单只不超 5%
其中 Σ 是 N×N 协方差矩阵。
# ----- 11. Min Var Portfolio -----
import cvxpy as cp
def min_var_weights(returns_window, max_weight=0.05):
"""
returns_window: DataFrame, 60-day daily returns of N stocks
"""
Sigma = returns_window.cov().values
n = len(Sigma)
w = cp.Variable(n)
objective = cp.Minimize(cp.quad_form(w, Sigma))
constraints = [
cp.sum(w) == 1,
w >= 0,
w <= max_weight,
]
prob = cp.Problem(objective, constraints)
prob.solve()
return w.value
# 在每个 rebalance date 上调用
# 注意:协方差矩阵在小样本(60×N)容易病态
# 实战要用 Ledoit-Wolf shrinkage 修正
Min Var 的卖点:显式控制 portfolio variance,而不是隐式假设排序就够了。
实测在同样 stock universe 下,Min Var vs Quintile sort:
- Sharpe 通常再高 0.05-0.10
- Max DD 通常再小 2-4 个百分点
- 但代价:单只权重集中度高(解通常 concentrated 在少数股票),换手率更不稳定
8.2 Risk Parity(风险平价)
不优化总方差,而是要求每只股票贡献等量风险:
w_i × (Σw)_i / (w' Σ w) = 1/N for all i
实务上常用 simple proxy:w_i ∝ 1/σ_i(忽略相关性)。
# ----- 12. Simplified Risk Parity (1/vol weighting) -----
def risk_parity_simple(vol_series):
inv_vol = 1.0 / vol_series
weights = inv_vol / inv_vol.sum()
return weights
# 应用到 low-vol picks
for date, picks in holdings_history.items():
vols = rolling_vol.loc[date, picks]
rp_weights = risk_parity_simple(vols)
# 用 rp_weights 替代 equal weight
Risk Parity 的卖点:直觉上更公平的风险分配,避免单只低 vol 股票(如某个 utility)把风险贡献全部主导。
Bridgewater 的 All Weather 是这个思想的旗舰产品(虽然他们做的是 cross-asset 不是 single-equity)。
8.3 三种实现的对照
| 维度 | Quintile Sort | Min Variance | Risk Parity |
|---|---|---|---|
| 复杂度 | 简单(pandas) | 高(需 optimizer) | 中(向量计算) |
| 协方差矩阵 | 不需要 | 需要 + 需要 shrinkage | 不需要(用 inv-vol 近似) |
| 集中度风险 | 低(等权) | 高(解通常稀疏) | 低 |
| Sharpe(典型) | 0.65-0.75 | 0.75-0.85 | 0.70-0.80 |
| Max DD | 中 | 最小 | 中-小 |
| 换手 | 中 | 高 | 中 |
| 个人量化适用性 | ✓✓ | △(小资金不划算) | ✓ |
对 <$5k 资金的建议:先把 Quintile Sort 跑稳,下个月加 Risk Parity weighting。Min Var 留到 capital >$50k 再考虑——优化器需要的精度(小数点权重)在小账户里被佣金的离散化打平。
九、低波动因子的常见坑(PM 必须知道)
| 坑 | 描述 | 缓解 |
|---|---|---|
| 行业集中 | 默认会偏 Utilities + Staples + Healthcare | 加 sector neutral 约束(每行业不超 30%) |
| 利率敏感 | 加息周期一起跌(2022) | 关注利率 regime,bear-flatten 时减仓 |
| 窗口选择 | 60d/126d/252d 不同结果 | 用 ensemble(多窗口加权) |
| Survivorship bias | 数据池用 current SP500 高估收益 ~1-2% | Day 13 用 historical constituent |
| Look-ahead bias | 月末数据当月用 → 偷看 | 严格用 t-1 day 的数据决策 t day 持仓 |
| Liquidity 错配 | 部分低 vol 是因为 illiquid(小盘) | 加 ADV 过滤,剔除日均成交 <$10M |
| Rebalance 时点 | 月末统一 rebalance 会与因子拥挤共振 | 错开日期(ex. 月中 vs 月末两组) |
| 股息 | 低 vol 股票股息高,30% W-8BEN 税吃掉 | 用 total return 数据回测时已含,但实盘要扣 |
最容易翻车的是 #1 和 #2——一个是 hidden sector bet,一个是 hidden rate bet。你以为在做 low vol,其实在做「看好 utilities 在低利率环境」。
十、低波动因子 vs 动量因子(昨日对比)
| 维度 | 动量(Day 11) | 低波动(Day 12) |
|---|---|---|
| 核心信号 | 12-1 月相对收益 | 60d realized vol |
| 起源理论 | 行为金融(disposition + underreaction) | Leverage constraint |
| 经典论文 | Jegadeesh-Titman 1993 | Frazzini-Pedersen 2014 |
| 牛市表现 | 跑赢 | 跑输 |
| 熊市表现 | 严重跑输(动量崩盘) | 跑赢(防御) |
| Sharpe(论文) | ~0.6-0.8 | ~0.7-0.85 |
| 换手率 | 高 (~80%/月) | 中 (~25%/月) |
| 适合的市况 | 趋势性市场 | 高不确定性市场 |
| 对利率敏感 | 中 | 高 |
重要发现:动量与低波动是天然 negatively correlated 的两个 factor。动量崩盘的时候(熊市末段反弹)通常是低 vol 大放异彩的时候。
这就是为什么机构的 multi-factor portfolio 至少要含动量 + 低波动这两条腿——它们之间的 negative correlation 让组合 Sharpe 比单独跑高得多。
Day 14 我们会做 multi-factor combine,今天和昨天分别打好两个底子。
十一、PM 视角:低波动思维的迁移性
低 vol factor 的核心 insight 是「同样的 expected return,承担更小的风险」。这条原则在很多场景都成立:
11.1 投资场景之外的迁移
职业发展:
- 跳槽追逐高薪短期 hit(高 vol)vs 在一家公司持续做出影响力(低 vol)
- 长期看,低 vol 路径的 risk-adjusted career return 经常更高
- 但前提:你的「公司」自己得是低 vol 的——选错公司就是 high beta 灾难
产品迭代:
- 全公司压一个大版本(高 vol)vs 持续小迭代(低 vol)
- A/B 测试驱动的 incremental ship 是产品的「低 vol 因子」
- 从 Sharpe 角度,持续迭代几乎永远赢
关系经营:
- 一年见一次大酒局(高 vol)vs 每周一次小通讯(低 vol)
- 真正的网络是后者积累的,前者大多是 forgettable
11.2 但低 vol 不总是赢——三个例外
- 创业期:必须吃 high beta,因为 baseline 收益(工资)不够 cover 资本成本
- 早期行业:在赛道刚起来时,低 vol 玩家被红利碾压(2010s 早期 Tech vs Utilities)
- 监管 / 央行政策剧变:低 vol 假设的环境消失(2022 年的低 vol bond proxies 案例)
这些例外把「低 vol 策略」的边界讲清了:它在稳定环境下是最优 default,在 paradigm shift 时必须主动减仓。
11.3 一句话总结
低波动因子最值钱的不是它在牛市能赢——而是它在熊市能让你不退场。
这跟职业一样:长期赢家不是那些 peak 最高的人,而是那些不会被 drawdown 打出局的人。
十二、明日预告
Day 13: 质量因子 QMJ — Asness 的「Quality Minus Junk」
- 质量因子的四个核心维度:Profitability + Growth + Safety + Payout
- AQR 经典论文 "Quality Minus Junk"(Asness, Frazzini, Pedersen 2019)
- 用财报数据(ROE、毛利率、Debt/Equity、earnings stability)构造 quality 评分
- 为什么 quality 和 low vol 高度相关但不完全等价
- 怎么用 yfinance .info / FMP API 拉财务数据
- 把 quality score 加入到我们的 factor stack
- 为后天 Day 14 multi-factor combine 准备第三个 leg
也是一次重要的 step-up:前两天的 factor 完全靠 price data,明天开始要进入 fundamentals 数据,这是机构因子工程的真正分水岭。
实际执行记录
启动一项填一项,时间戳 + 卡点。
- [hh:mm] 数据下载(72 ticker × 11 年)— 估时 3-5 分钟
- [hh:mm] Rolling vol 计算 + 月度 picks 生成 —
- [hh:mm] Backtest 跑通,metrics 出 —
- [hh:mm] NAV / Drawdown 图保存 —
- [hh:mm] 行业分布检查 — 是否符合「Utilities + Staples 主导」预期?
- [hh:mm] 2022 年单独切片验证 — 验证「低 vol 在加息周期失效」假说
- [hh:mm] 10Y Yield 叠加图 — 看相关性是否 visible
- [hh:mm] (可选)跑 Risk Parity weighted 版本对比
-
[ ] 卡点 / 学到的:
总字数:约 7,000 字 今日完成度:理论 ✓ / 实操(你自己跑)/ 笔记 ✓