第 3 章 · 01 skfolio 风险平价与回测(对应 QS011)


文档摘要

第 3 章 · 01 skfolio 风险平价与回测(对应 QS011) 速查摘要:本节精读 skfolio 的"风险预算"优化。skfolio 把组合优化包装成 sklearn 估计器,核心三步: 把价格转收益; 切训练/测试(注意训练只占 25%,而且绝不能 shuffle,时序一旦打乱就泄漏未来); 拟合后用 在测试集上回测,与 (权重∝1/σ)基准对比年化夏普。最后用 把多个组合打包,一键画权重构成、累计收益、风险贡献。 涉及脚本:原项目 (约 72 行) ⚠️ 注意:skfolio 与 riskfolio(QS014)是两种范式。

第 3 章 · 01 skfolio 风险平价与回测(对应 QS011)

速查摘要:本节精读 skfolio 的"风险预算"优化。skfolio 把组合优化包装成 sklearn 估计器,核心三步:prices_to_returns 把价格转收益;train_test_split(test_size=0.75, shuffle=False) 切训练/测试(注意训练只占 25%,而且绝不能 shuffle,时序一旦打乱就泄漏未来);RiskBudgeting(risk_measure=VARIANCE) 拟合后用 predict 在测试集上回测,与 InverseVolatility(权重∝1/σ)基准对比年化夏普。最后用 Population 把多个组合打包,一键画权重构成、累计收益、风险贡献。

涉及脚本:原项目 QS011-skfolio-risk-parity/01_risk_parity.py(约 72 行)

⚠️ 注意:skfolio 与 riskfolio(QS014)是两种范式。skfolio 是 sklearn 风格(fit/predict,可塞进 Pipeline、可交叉验证),riskfolio 是命令式(一步一调用、画图丰富)。理解这种差异,选型时就不会混淆 API。

学习目标

阅读完本节,你应当能够:

  1. 说清风险预算(每类资产贡献等量风险)与反波动率(权重∝1/σ)的区别。
  2. 解释为什么时序回测必须 shuffle=False,以及为什么这里训练集只占 25%。
  3. 把 skfolio 的 RiskBudgeting 当 sklearn 估计器使用(fit/predict)。
  4. Population 把多个组合打包对比、画构成与累计收益。

工具与原理

风险预算(Risk Budgeting)

传统马科维茨按"总方差最小"分配权重,结果常把大部分仓位压在一两只低波动资产上。风险预算反其道而行:让每类资产对组合总风险的贡献相等(或按预算比例分配)。

资产 i 的边际风险贡献为:

MRC_i = (Σ w)_i / sqrt(w'Σw)

资产 i 的总风险贡献为 RC_i = w_i · MRC_i。风险平价(等风险预算)即求解 RC_1 = RC_2 = ... = RC_n,等价于让每类资产"贡献等量风险"。结果是不管某只股票波动多大,它对组合风险的拖累都被压成与其他资产相同——天然分散。

反波动率(Inverse Volatility)

更简单的基准:权重与波动率成反比。

w_i = (1/σ_i) / Σ_j (1/σ_j)

只用各资产自己的标准差,不考虑相关性。所以它是"朴素的"风险均衡:作为 RiskBudgeting 的对照基准非常合适——能看出"考虑协方差"比"只看自己波动"好多少。

skfolio 的 sklearn 范式

skfolio 的核心设计:所有优化器(RiskBudgetingInverseVolatilityMeanRiskMaximumSharpe...)都继承 sklearn 的 BaseEstimator,统一接口:

  • fit(X_train):在训练收益上估协方差、解优化、存 weights_
  • predict(X_test):用 weights_ 在测试集上算组合收益,返回一个 MultiPeriodPortfolio 对象(自带累计收益、夏普、回撤等指标)。
  • score(X_test):默认返回夏普比率,可塞进 GridSearchCV 调参。

这意味着你可以把"数据标准化 + 优化器 + 回测"串成 Pipeline,跟任何 sklearn 工作流一样——这是 riskfolio 做不到的。

脚本精读

1. 取数据、转收益

prices = yf.download(['AAPL', 'MSFT', 'GOOG', 'AMZN', 'META', 'TSLA', 'NVDA'], start='2015-01-01', end='2024-12-31', progress=False) prices = prices['Adj Close'] X = prices_to_returns(prices)

7 只科技股 2015-2024 共 10 年。prices_to_returns 是 skfolio 提供的预处理函数,内部就是 prices.pct_change().dropna(),但封装成 sklearn 风格便于流水线复用。

2. 时序切分(关键!)

X_train, X_test = train_test_split(X, test_size=0.75, shuffle=False)

两个细节决定成败:

  • shuffle=False:时序数据绝不能随机打乱。如果打乱,训练集里就混进了 2024 年的收益、测试集里出现 2015 年的——直接泄漏未来。sklearn 默认 shuffle=True(给横截面数据用),时序场景必须显式关掉。
  • test_size=0.75:训练只占 25%(约 2.5 年),测试占 75%(约 7.5 年)。这是 Quant Science 的故意设计:用短窗口估参数、长窗口验稳定性,防止过拟合被短测试期掩盖。常规做法是反过来(训练长测试短),这里反着来,是为了"压力测试"模型在数据有限时的外推能力。

3. 风险预算优化

model = RiskBudgeting( risk_measure=RiskMeasure.VARIANCE, portfolio_params=dict(name="Risk Parity - Variance"), ) model.fit(X_train) model.weights_

RiskMeasure.VARIANCE 表示用方差度量风险(也可换成 SEMIVARIANCECVARWORST_REALIZATION 等)。RiskBudgeting 默认预算是等权(每类 1/n),即"风险平价"。fitmodel.weights_ 就是优化出的权重向量。

4. 反波动率基准

benchmark = InverseVolatility( portfolio_params=dict(name="Inverse Volatility") ) benchmark.fit(X_train) benchmark.weights_

InverseVolatility 不解优化、只用 w_i ∝ 1/σ_i 算权重。同样是 sklearn 接口(fitweights_),便于和 RiskBudgeting 放进同一个对比流程。

5. 预测与对比

pred_model = model.predict(X_test) pred_bench = benchmark.predict(X_test) print(pred_model.annualized_sharpe_ratio) print(pred_bench.annualized_sharpe_ratio)

predict 返回的 MultiPeriodPortfolio 对象自带一堆指标:annualized_sharpe_ratiocumulative_returnsmax_drawdown 等。直接读属性即可,不用自己算。

6. Population 打包可视化

population = Population([pred_model, pred_bench]) population.plot_composition() # 权重堆叠条 population.plot_cumulative_returns() # 累计收益对比曲线 population.summary() # 绩效指标表 pred_model.plot_contribution(RiskMeasure.ANNUALIZED_VARIANCE) # 风险贡献 pred_bench.plot_contribution(RiskMeasure.ANNUALIZED_VARIANCE)

Population 是 skfolio 的"组合集合"容器,把多个组合放进去就能统一画图、统一对比。plot_contribution 画各资产对总风险的贡献——风险平价的图应该接近一条平线(每类资产贡献相等),这就是"风险预算达成"的直观证据;反波动率的图则不一定平,因为它没考虑相关性。

关键技巧

  1. shuffle=False 是时序回测的生死线。忘了写这一句,所有"漂亮的回测夏普"都要打折扣——因为你用了未来信息。一个识别窍门:任何 sklearn 切分函数用在金融时序上,第一反应就是问"shuffle 关了吗"。

  2. skfolio 可塞进 Pipeline。因为优化器是 BaseEstimator,你可以写 Pipeline([('returns', ReturnsTransformer()), ('opt', RiskBudgeting())]),再用 GridSearchCV 调预算向量、风险度量。这是 riskfolio 完全不具备的能力。

  3. skfolio vs riskfolio 范式差异。选型时记住一句话:做产品/工程选 skfolio(可流水线、可交叉验证、sklearn 生态),做研究报告选 riskfolio(画图丰富、有效前沿、Excel 报告一键导出)。两者目标用户不同,没有绝对优劣。

  4. predict 不重新优化fit 出来的 weights_ 在整个测试期保持不变,predict 只是用这个固定权重乘以测试期收益。所以本节是"单期优化 + 多期回测",不是滚动再平衡。要做滚动再平衡得用 skfolio 的 cross_val_predict 或自己写循环。

  5. 风险度量可换。把 RiskMeasure.VARIANCE 换成 RiskMeasure.CVAR,优化器就从"方差风险平价"变成"CVaR 风险平价",自动聚焦尾部风险。一个参数切换两种风格。

💡 速查要点:skfolio 三件套——prices_to_returns 转收益、train_test_split(shuffle=False) 时序切分、RiskBudgeting.fit/predict 优化回测。Population 打包对比,InverseVolatility 当朴素基准。整个流程跟 sklearn 一模一样,这是它相对 riskfolio 的核心优势。

本节要点

  1. 风险预算让每类资产对组合总风险的贡献相等(等 RC_i),比马科维茨更分散;反波动率只用 w_i ∝ 1/σ_i 不考虑相关性,作基准。
  2. 时序切分必须 shuffle=False,否则未来泄漏进训练集;本节训练只占 25% 是有意的压力测试。
  3. skfolio 是 sklearn 范式:fit 估参数、predict 在新数据上算组合收益、score 返回夏普——可塞进 Pipeline 和 GridSearchCV。
  4. Population 把多个组合打包,一键画权重构成、累计收益、风险贡献,对比极方便。
  5. skfolio(工程/流水线) vs riskfolio(报告/可视化) 是两种范式,选型看场景。

下一节,我们看 riskfolio 怎么在一个脚本里完成"优化 + 有效前沿 + 6 种可视化 + Excel 报告"——研究演示的瑞士军刀。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U