【Python量化策略实战 #05】等权总嫌乱炖?风险平价与 Kelly 公式仓位对比实战 一、为什么需要重新思考仓位痛点开场前几篇我们跑通了选股 择时两条线。但还有个问题没解决选出来的股票要不要平均投等权是最简单的答案——每只 1/N。但它有个明显问题高波动股票10% 日波和低波动股票2% 日波拿同样仓位结果组合的波动被高波动票主导回撤和 Sharpe 都不好看更聪明的做法是让每只股票对组合风险的贡献相等——这就是风险平价Risk Parity。更激进的做法是只给赢家加仓——动量为正才给仓、按动量大小加权这就是Kelly 简化。本篇用同一组选股动量 top5 持仓 10d 等间距再平衡对比三种仓位方案等权 / 风险平价 / Kelly 简化。同一份选股代码、不同仓位函数结果差异立竿见影。二、本文你将得到什么等权baseline每只 1/N最朴素风险平价权重 ∝ 1 / 波动率高波动小仓、低波动大仓Kelly 简化权重 ∝ max(0, 动量)动量为正才给仓、按动量大小加权同一回测核心跑三种方案选股不变、只换仓位函数结果直接对比一个端到端 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)/bp胜率、a/b盈亏比实战里胜率/盈亏比都难估所以常用动量为正才给仓、按动量大小加权做简化版赢家多投、输家离场。3.2 完整代码importnumpyasnpimportpandasaspdfrommairuiimportClient LICENCELICENCE-66D8-9F96-0C7F0FBCD073LOOKBACK10;HOLD10;TOP_N5COMMISSION0.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):rowsclient.stock_history(code,d,f,st20250101,et20250906,lt50)ifnotrows:returnpd.DataFrame()dfpd.DataFrame(rows)df[t]pd.to_datetime(df[t])returndf.sort_values(t).set_index(t)[[o,h,l,c,v]].astype(float)clientClient(LICENCE)panelpd.DataFrame({c:fetch(client,c)[c]forcinUNIVERSE}).sort_index().ffill().dropna(howall)defw_equal(picks):nlen(picks)return{c:1.0/nforcinpicks}defw_risk_parity(picks,atr_panel,asof):rowatr_panel.iloc[asof][picks].dropna()ifrow.emptyor(row0).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):winpanel.iloc[asof-lookback:asof1]mom(win.iloc[-1]/win.iloc[0])-1.0wmom.reindex(picks).clip(lower0.0)return(w/w.sum()).to_dict()ifw.sum()elsew_equal(picks)# 等间距调仓 三仓位方案回测atr_panelpd.DataFrame({c:(fetch(client,c)[h]-fetch(client,c)[l]).rolling(5).mean()/fetch(client,c)[c]forcinUNIVERSE}).ffill()results{}formethodin[equal,rp,kelly]:nav1.0forposinrange(LOOKBACK,len(panel)-HOLD,HOLD):picks(panel.iloc[pos]/panel.iloc[pos-LOOKBACK]-1).dropna().nlargest(TOP_N).index.tolist()ifnotpicks:continueifmethodequal:ww_equal(picks)elifmethodrp:ww_risk_parity(picks,atr_panel,pos)else:ww_kelly(picks,panel,pos,LOOKBACK)futpanel.iloc[pos1:pos1HOLD][picks].pct_change().fillna(0.0)nav*(1-COMMISSION*2)*(1float((fut*pd.Series(w)).sum(axis1).mean()))results[method]round((nav-1)*100,4)print(三方案总收益演示窗口:,results)四、自验结果Demo 真实输出【Python量化策略实战 #05】仓位管理三件套对比 —— 动量 top5 持仓 10d licence loaded, len30, 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自动减仓动量大的票权重放大回撤比等权小 收益比等权高五、常见坑与注意风险平价 ≠ 最小波动风险平价的目标是风险贡献相等不是波动最小。如果所有股票波动都很低风险平价结果接近等权只有高低波动混合时差异才显现。Kelly 简化的边界完整 Kelly 容易满仓梭哈简化版用max(0, momentum)做下界保护——动量为负不持仓、相当于自动止损动量为正且大才加仓相当于赢家多投。调仓日手续费本文调仓日扣 2 单边手续费开 平。如果一个月调一次单边 2‰ × 2 4‰/月 年化 ≈ 5%回测里要看到这成本。如果一年只调几次手续费可忽略。演示证下的picks 字典序前 5与 #01 同根因——演示证 50 条 cap 下各股票 close 几乎一致top 5 永远落在字典序靠前的几只上。这是预期不是 bug正式证书会给出真实截面差异。三种方案的兼容性本文代码里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 量化策略入门示例演示证书下回测指标不能反映真实市场表现不构成任何投资建议投资有风险入市需谨慎。