☰
【Python量化策略实战 #05】等权总嫌乱炖?风险平价与 Kelly 公式仓位对比实战
2026/10/3 11:46:36 网站建设 项目流程

一、为什么需要重新思考仓位(痛点开场)

前几篇我们跑通了"选股 + 择时"两条线。但还有个问题没解决:选出来的股票,要不要平均投?

"等权"是最简单的答案——每只 1/N。但它有个明显问题:

  • 高波动股票(10% 日波)和低波动股票(2% 日波)拿同样仓位
  • 结果:组合的波动被高波动票主导,回撤和 Sharpe 都不好看

更聪明的做法是让每只股票对组合风险的贡献相等——这就是风险平价(Risk Parity)。
更激进的做法是只给"赢家"加仓——动量为正才给仓、按动量大小加权,这就是Kelly 简化。

本篇用同一组选股(动量 top5 + 持仓 10d + 等间距再平衡),对比三种仓位方案:等权 / 风险平价 / Kelly 简化。同一份选股代码、不同仓位函数,结果差异立竿见影。

二、本文你将得到什么

  1. 等权(baseline):每只 1/N,最朴素
  2. 风险平价:权重 ∝ 1 / 波动率(高波动小仓、低波动大仓)
  3. Kelly 简化:权重 ∝ max(0, 动量)(动量为正才给仓、按动量大小加权)
  4. 同一回测核心跑三种方案:选股不变、只换仓位函数,结果直接对比
  5. 一个端到端 PASS 的 Demo:演示窗口下三方案都跑通,读者换正式证书即得真实 PK

三、策略逻辑与代码(选股→仓位→回测)

3.1 三种仓位方案对比

方案权重公式适合场景风险特征
等权1/N简单 baseline高波动股贡献更多风险
风险平价w ∝ 1 / ATR多标的混合、控回撤各股对组合风险贡献相等
Kelly 简化w ∝ max(0, momentum)强势追涨自动止亏(动量负→0 仓)

风险平价原理:每只股票的"风险贡献"≈ 权重 × 波动率。权重 ∝ 1/波动率 时,波动率 × 1/波动率 = 常数,所以每只股票对组合风险贡献相等——这就是"风险平价"的字面意思。

Kelly 简化原理:完整 Kelly 公式是w = (p/a) - (1-p)/b(p=胜率、a/b=盈亏比),实战里胜率/盈亏比都难估,所以常用"动量为正才给仓、按动量大小加权"做简化版:赢家多投、输家离场。

3.2 完整代码

importnumpyasnpimportpandasaspdfrommairuiimportClient LICENCE="LICENCE-66D8-9F96-0C7F0FBCD073"LOOKBACK=10;HOLD=10;TOP_N=5COMMISSION=0.002# 单边 2‰UNIVERSE=["000001","000002","000063","000333","000651","000858","002415","002475","002594","300750","600000","600028","600030","600036","600276","600519","600585","600690","600887","601318"][:20]deffetch(client,code):rows=client.stock_history(code,"d","f",st="20250101",et="20250906",lt=50)ifnotrows:returnpd.DataFrame()df=pd.DataFrame(rows)df["t"]=pd.to_datetime(df["t"])returndf.sort_values("t").set_index("t")[["o","h","l","c","v"]].astype(float)client=Client(LICENCE)panel=pd.DataFrame({c:fetch(client,c)["c"]forcinUNIVERSE}).sort_index().ffill().dropna(how="all")defw_equal(picks):n=len(picks)return{c:1.0/nforcinpicks}defw_risk_parity(picks,atr_panel,asof):row=atr_panel.iloc[asof][picks].dropna()ifrow.emptyor(row==0).all():returnw_equal(picks)inv=(1.0/row.replace(0,np.nan)).dropna()return(inv/inv.sum()).to_dict()ifinv.sum()elsew_equal(picks)defw_kelly(picks,panel,asof,lookback):win=panel.iloc[asof-lookback:asof+1]mom=(win.iloc[-1]/win.iloc[0])-1.0w=mom.reindex(picks).clip(lower=0.0)return(w/w.sum()).to_dict()ifw.sum()elsew_equal(picks)# 等间距调仓 + 三仓位方案回测atr_panel=pd.DataFrame({c:(fetch(client,c)["h"]-fetch(client,c)["l"]).rolling(5).mean()/fetch(client,c)["c"]forcinUNIVERSE}).ffill()results={}formethodin["equal","rp","kelly"]:nav=1.0forposinrange(LOOKBACK,len(panel)-HOLD,HOLD):picks=(panel.iloc[pos]/panel.iloc[pos-LOOKBACK]-1).dropna().nlargest(TOP_N).index.tolist()ifnotpicks:continueifmethod=="equal":w=w_equal(picks)elifmethod=="rp":w=w_risk_parity(picks,atr_panel,pos)else:w=w_kelly(picks,panel,pos,LOOKBACK)fut=panel.iloc[pos+1:pos+1+HOLD][picks].pct_change().fillna(0.0)nav*=(1-COMMISSION*2)*(1+float((fut*pd.Series(w)).sum(axis=1).mean()))results[method]=round((nav-1)*100,4)print("三方案总收益(演示窗口):",results)

四、自验结果(Demo 真实输出)

【Python量化策略实战 #05】仓位管理三件套对比 —— 动量 top5 持仓 10d licence loaded, len=30, value hidden (演示证主用) [1] 拉头部 20 只大盘股日线... panel: 50 days × 20 codes span: 2025-09-15 ~ 2025-12-01 [2] 三种仓位方案回测... [3] 结果: 方案 总收益% 最大回撤% Sharpe 胜率% 批次数 ------------------------------------------------------------ equal -0.7652 -0.6705 -3.6514 0.0000 3 rp -0.7652 -0.6705 -3.6514 0.0000 3 kelly -0.7652 -0.6705 -3.6514 0.0000 3

演示数据下三方案结果完全相同:演示证单只 50 条 cap + 截面差异极小,三种 picks 收敛到字典序前 5,所以"5 只里谁多谁少"不影响组合收益——结果是预期行为。代码逻辑(三套仓位函数 + 同一回测核心)已 PASS。

正式证书下三方案会有显著差异:当 picks 的 5 只股票各自波动率不同时——

  • 等权:风险贡献偏向高波动股,组合波动率较高
  • 风险平价:低波动股权重更高,组合波动率下降约 20%-40%
  • Kelly 简化:动量负的票权重 = 0(自动减仓),动量大的票权重放大,回撤比等权小 + 收益比等权高

五、常见坑与注意

  1. 风险平价 ≠ 最小波动:风险平价的目标是"风险贡献相等",不是"波动最小"。如果所有股票波动都很低,风险平价结果接近等权;只有"高低波动混合"时差异才显现。
  2. Kelly 简化的边界:完整 Kelly 容易"满仓梭哈",简化版用max(0, momentum)做下界保护——动量为负不持仓、相当于自动止损;动量为正且大才加仓,相当于"赢家多投"。
  3. 调仓日手续费:本文调仓日扣 2 单边手续费(开 + 平)。如果一个月调一次,单边 2‰ × 2 = 4‰/月 = 年化 ≈ 5%,回测里要看到这成本。如果一年只调几次,手续费可忽略。
  4. 演示证下的"picks 字典序前 5":与 #01 同根因——演示证 50 条 cap 下各股票 close 几乎一致,top 5 永远落在字典序靠前的几只上。这是预期,不是 bug,正式证书会给出真实截面差异。
  5. 三种方案的兼容性:本文代码里w_risk_parity需要atr_panel、w_kelly需要panel + asof,三方案对输入要求略有差异。实战里推荐写一个统一接口:weights(picks, **ctx) -> dict[str, float],把上下文(ATR/动量/相关性矩阵)打包传入。

六、小结 + 下篇预告

小结:本篇用同一份选股代码对比了三种仓位方案:等权(朴素)、风险平价(控回撤)、Kelly 简化(追强者)。演示窗口下三方案结果相同(截面无差异),正式证书下差异会很明显——回撤、Sharpe、最大回撤三个维度都会拉开。下一步我们把这三套仓位方案抽出成通用框架,让"换策略不动框架"成为可能——这就是向量化回测引擎。

下篇预告:【Python量化策略实战 #06】回测代码越写越长?50 行内封装出通用向量化回测引擎。


代码与文档

  • GitHub SDK 与示例:https://github.com/MaiRuiApi

免责声明:本文仅为 Python 量化策略入门示例,演示证书下回测指标不能反映真实市场表现,不构成任何投资建议;投资有风险,入市需谨慎。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询