第 3 章 · 05 Kelly 凯利公式最优杠杆(对应 QS031)


文档摘要

第 3 章 · 05 Kelly 凯利公式最优杠杆(对应 QS031) 速查摘要:本节是本章最数学化的一节——用 Kelly 准则求"长期复利增长率最大"的最优杠杆 f。数据是 ^GSPC(标普 500)自 1950 年至今的年收益。流程: 抽年收益 → 算 25 年滚动均值/标准差 → 用 数值积分 (积分限 mean±3std)算"给定杠杆 f 时的负期望对数收益" → 用 最小化这个负值,得最优 f → 配合 画 Kelly 杠杆下的累计复利曲线。末尾用 m=0.058、s=0.216 单点算出 f≈0.6,演示"收益小、波动大"时最优杠杆的取值。

第 3 章 · 05 Kelly 凯利公式最优杠杆(对应 QS031)

速查摘要:本节是本章最数学化的一节——用 Kelly 准则求"长期复利增长率最大"的最优杠杆 f*。数据是 ^GSPC(标普 500)自 1950 年至今的年收益。流程:resample('YE').last().pct_change() 抽年收益 → rolling(25).agg(['mean','std']) 算 25 年滚动均值/标准差 → norm_integralscipy.integrate.quad 数值积分 -∫log(1+f·s)·pdf(s)ds(积分限 mean±3std)算"给定杠杆 f 时的负期望对数收益" → get_kellyscipy.optimize.minimize_scalar(method='bounded', bounds=[0,2]) 最小化这个负值,得最优 f → assign(kelly=return*f.shift()) 配合 cumprod() 画 Kelly 杠杆下的累计复利曲线。末尾用 m=0.058、s=0.216 单点算出 f*≈0.6,演示"收益小、波动大"时最优杠杆的取值。

涉及脚本:原项目 QS031-kelly/01_kelly.py(约 96 行,本章最数学化)

⚠️ 注意:Kelly 公式给出的是"理论最优"杠杆,但它假设你能精确知道收益分布、能承受任意回撤、交易无成本无约束。实战中通常用"半 Kelly"(f*/2)来降低破产风险和情绪痛苦。直接上满 Kelly 杠杆,你大概率会在某次回撤里被强制平仓或心理崩溃——这是 Kelly 在交易员圈子里"理论上完美、实战上致命"的名声来源。

学习目标

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

  1. 解释 Kelly 准则的目标:最大化长期复利增长率(对数财富期望)。
  2. 说清 norm_integral 数值积分的含义:-∫log(1+f·s)·pdf(s)ds
  3. scipy.optimize.minimize_scalar(method='bounded')[0,2] 上求最优 f。
  4. 解释为什么 f* 可能大于 1(需要加杠杆)。
  5. 理解 f.shift() 防止前视偏差的作用。

工具与原理

Kelly 准则的核心目标

John Kelly 1956 年在贝尔实验室研究信道容量时提出的公式,被赌徒和交易员发扬光大。它的目标是:找到一个下注比例 f,使长期复利增长率最大化

每一期财富乘以 (1 + f·s),其中 s 是本期收益(可正可负)。N 期后的总财富是:

W_N = W_0 · Π_{i=1}^{N} (1 + f·s_i)

取对数变成求和:

log(W_N / W_0) = Σ log(1 + f·s_i)

长期增长率(每期)的期望是 E[log(1 + f·s)]。Kelly 准则就是求使这个期望最大的 f。在连续时间、收益服从正态分布 N(μ, σ²) 的假设下,有解析解:

f* = μ / σ²

(最优杠杆等于"收益均值除以方差")。脚本里 m=0.058(均值 5.8%)、s=0.216(标准差 21.6%),f* = 0.058/0.216² ≈ 1.24——注意这是用 μ/σ² 而非 μ/σ,所以会比直觉上的"夏普"算法给出更大的杠杆。但本脚本不直接用解析解,而是用数值积分——这样支持任意分布,不只是正态。

数值积分的实现:为什么不用解析解

解析解 f* = μ/σ² 只在"收益严格正态"时成立。真实收益有偏度、肥尾、跳跃,正态假设站不住。脚本用数值积分更通用:

E[log(1 + f·s)] = ∫ log(1 + f·s) · pdf(s) ds

把收益 s 当作随机变量,对它的概率密度 pdf(s)(这里用正态 norm.pdf,但可以换任何分布)积分。负化后变成最小化问题:

val, er = quad( lambda s: np.log(1 + f * s) * norm.pdf(s, mean, std), mean - 3 * std, mean + 3 * std, ) return -val

mean ± 3·std 是积分限(取 ±3σ 覆盖 99.7% 概率质量,数值稳定)。quad 是 SciPy 的自适应数值积分,返回 (积分值, 误差估计)。返回 -val 是因为下一步要"最小化负期望对数收益"= 最大化期望对数收益。

为什么 f* 可能 > 1

f* = μ/σ² 在"收益小但波动更小"时可以远超 1。比如标普 500 长期年化 10%、年波动 16%,f* = 0.10/0.16² ≈ 3.9——意味着最优策略是 3.9 倍杠杆!

f > 1 的含义是:用 1 块钱本金 + 借 (f-1) 块钱,总共买入 f 块钱的资产。借的钱付融资利息(本脚本简化忽略),只要资产收益期望 > 融资成本,加杠杆就能提升长期复利。这是为什么 Kelly 在"低波动稳收益"资产(如债券套利)上给出的杠杆常常是 5-10 倍——长期资本管理公司(LTCM)就是这么做的,然后……炸了。Kelly 不约束路径上的破产风险,这是它的命门。

脚本精读

1. 抽年收益 + 25 年滚动参数

annual_returns = ( yf.download(["^GSPC"])[["Adj Close"]] .resample("YE") .last() .pct_change() .dropna() .rename(columns={"Adj Close": "^GSPC"}) ) return_params = annual_returns["^GSPC"].rolling(25).agg(["mean", "std"]).dropna()
  • .resample("YE").last():取每年最后一个交易日的收盘价。'YE' 是 Pandas 2.2+ 的新写法(旧的 'Y' 在 2.2 后有 deprecation warning,推荐 'YE' 表示 Year End)。注意不要混用。
  • .pct_change():年价差分得年收益。^GSPC 数据自 1950 年起,所以这里有 70 多个年收益。
  • .rolling(25).agg(["mean","std"]):在 25 年滚动窗口上同时算均值和标准差。结果是一个两列 DataFrame(mean/std),每个时点反映"过去 25 年的收益分布参数"。最早要 1950+25=1975 年才有值,所以 dropna() 把前 25 行扔掉。

2. norm_integral:负期望对数收益

def norm_integral(f, mean, std): val, er = quad( lambda s: np.log(1 + f * s) * norm.pdf(s, mean, std), mean - 3 * std, mean + 3 * std, ) return -val

参数是杠杆 f,固定 mean/std,返回"这个 f 下的负期望对数收益"。quad 做数值积分,被积函数是 log(1+f·s) · norm.pdf(s, mean, std)——"每个收益 s 出现的概率 × 这个收益下加杠杆 f 之后的对数财富增量",积分起来就是期望对数收益。返回负值是为了让下一步的最小化器实际是最大化期望对数。

注意 log(1 + f·s)1 + f·s ≤ 0(即 s ≤ -1/f)时无定义——这是"全损"边界。当 f 越大,这个边界越接近 0(一点小亏损就清零),所以 f 不能无限大。数值积分在 ±3σ 区间内若 mean>0、std 适中,这个边界一般不会被触发;但极端情况下需要小心。

3. get_kelly:对每个滚动窗口求最优 f

def get_kelly(data): solution = minimize_scalar( norm_integral, args=(data["mean"], data["std"]), bounds=[0, 2], method="bounded" ) return solution.x annual_returns["f"] = return_params.apply(get_kelly, axis=1)
  • minimize_scalar:scipy.optimize 的一维标量优化器。
  • method="bounded" + bounds=[0, 2]:在 [0, 2] 区间内有界优化(用 Brent 法的 bounded 变种)。f=0 是空仓、f=2 是两倍杠杆,搜索在这个范围。bounded 法比默认 'brent' 更稳,因为有界约束防止越界到无定义区。
  • args=(data["mean"], data["std"]):把 mean/std 作为额外参数传给 norm_integral——这是 scipy 优化器的标准传参方式。
  • apply(get_kelly, axis=1):对 return_params 的每一行(每个滚动时点)调 get_kelly,算那个时点的最优 f,存进 annual_returns["f"]

4. 画 Kelly 累计复利 + 防前视偏差

( annual_returns[["^GSPC"]] .assign(kelly=annual_returns["^GSPC"].mul(annual_returns.f.shift())) .dropna() .loc["1900":] .add(1) .cumprod() .sub(1) .plot(lw=2) )

关键技巧在 annual_returns.f.shift():用上一年的 f(刚算出来的最优杠杆)乘以今年的收益。如果不 shift,就是用"包含了今年数据的滚动窗口估出的 f"下注今年——这是赤裸裸的前视偏差(用了未来信息)。shift 一期后,每年的杠杆都是"基于到去年为止的数据"算的,严格无前视。

然后 .add(1).cumprod().sub(1) 是标准复利写法:(1+r_1)(1+r_2)...(1+r_N) - 1 即累计复利。^GSPC 那列是无杠杆纯持有,kelly 那列是按最优杠杆放大的——后者曲线波动更大、长期收益更高(理论最优),但也更接近"清零"的回撤。

5. 单点示例:f* 可大于 1

m = .058 s = .216 sol = minimize_scalar(norm_integral, args=(m, s), bounds=[0.0, 2.0], method="bounded") print("Optimal Kelly fraction: {:.4f}".format(sol.x))

m=0.058(年化均值 5.8%)、s=0.216(年化标准差 21.6%)是某个滚动窗口的样本值。sol.x 给出最优 f。代入解析解 f* = μ/σ² = 0.058/0.216² ≈ 1.24,但因为数值积分用了正态 pdf 应该接近这个值(差异来自 ±3σ 截断和数值误差)。

注释强调:"This formula can result in Kelly fractions higher than 1. In this case, it is theoretically advantageous to use leverage to purchase additional securities on margin."——即 f*>1 时,理论上是借杠杆加仓。注释里特意点出这点,因为新手常误以为"凯利比例一定 ≤ 1"。

关键技巧

  1. 数值积分 vs 解析解的取舍。解析解 f* = μ/σ² 只在严格正态下成立,快但不通用;数值积分 quad + minimize_scalar 慢一点,但 pdf 可以换成 t 分布(肥尾)、混合分布、经验分布——支持任意收益分布。研究阶段用数值积分更稳健,生产部署如果性能敏感可以推导解析解。

  2. .resample('YE') 是 Pandas 2.2+ 新写法。旧的 'Y'/'A' 在 Pandas 2.2 起有 deprecation warning:'Y' 被推荐改为 'YE'(Year End),'M''ME', 'Q''QE'。这是 Pandas 社区为消除"年/月"歧义(开始还是结束)做的大改,新代码应一律用新写法。

  3. f.shift() 防前视偏差。任何"用统计参数下注"的策略都要警惕这个坑:你估出的参数用了当期数据,下注当期就是用未来。shift(1) 把参数挪到下一期用,严格无前视。本脚本如果漏掉 .shift(),回测的 kelly 累计曲线会漂亮得离谱——但完全是假的。

  4. minimize_scalar(method='bounded', bounds=[0,2])'brent'。Brent 法无界,可能搜到 f<0(做空)或 f>2(超杠杆)的奇怪解;bounded 法保证在合理杠杆范围内,也避免 log(1+f·s) 在 f 极大时数值爆炸。

  5. 半 Kelly 是实战标准。直接上 f* 在回测里最优,但 f* 处的"几何增长率最大"对应的是"算术增长率方差也最大"——路径上的回撤极深(常超 50%)。交易员圈子的惯例是 f*/2f*/4,牺牲一点长期增长率换取回撤可控和情绪可承受。LTCM 满杠杆 Kelly 的下场就是前车之鉴。

  6. Kelly ↔ CVaR 的对偶。Kelly(本节)是"最大化长期复利增长",属于收益侧的优化;CVaR(上一节)是"度量尾部损失期望",属于风险侧的度量。两者互为对偶:Kelly 在 E[log(1+f·s)] 里通过 log 函数天然重罚"接近 -1/f 的极端亏损"(log 在 0 附近趋向负无穷),所以 Kelly 隐含了对尾部风险的厌恶——这跟 CVaR 显式惩罚尾部是同一件事的两面。把两者放一起理解,你对"风险与收益的权衡"会有更深的几何直觉。

💡 速查要点:Kelly 三件套——rolling(N).agg(['mean','std']) 估滚动参数 → norm_integralquad 数值积分 -∫log(1+f·s)·pdf(s)dsminimize_scalar(method='bounded', bounds=[0,2]) 最优 f。.resample('YE') 是 Pandas 2.2+ 新写法,f.shift() 防前视偏差,实战用半 Kelly。f*>1 时理论应加杠杆,但路径回撤可能致命。

本节要点

  1. Kelly 准则最大化长期复利增长率 E[log(1+f·s)],连续正态下有解析解 f* = μ/σ²
  2. 数值积分 quad + minimize_scalar(method='bounded') 更通用,支持任意收益分布(可换 t 分布/经验分布);解析解只在严格正态下成立。
  3. f 可大于 1*(需加杠杆),m=0.058, s=0.216 时 f*≈1.24;满杠杆路径回撤极深,LTCM 是反例。
  4. f.shift() 防前视偏差:用上一期估的参数下注当期,漏掉 shift 回测就是假阳性。
  5. .resample('YE') 是 Pandas 2.2+ 新写法(替代 'Y'),'ME'/'QE' 同理,新代码一律用新写法。
  6. Kelly(收益最大化)↔ CVaR(风险度量)对偶:Kelly 通过 log 隐式重罚尾部,CVaR 显式度量尾部,是同一权衡的两面。

至此第 3 章完结。五种方法(风险平价 / riskfolio 全功能 / HRP / CVaR / Kelly)从"风险均衡分配"到"最优杠杆"覆盖了组合优化的核心工具箱。下一章我们转入因子分析与策略研究,看怎么把第 2 章的因子信号和本章的组合方法结合起来。


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