凯利公式:每次该下注多少
"如果你知道自己的优势,那么下一个问题就是:下注多少。"
1960 年,贝尔实验室的物理学家约翰·凯利正在研究噪声信道中的信息传输问题。他发现了一个简洁的公式,可以计算在重复博弈中使资本增长最大化的下注比例。这个公式后来被赌徒、投资者和量化交易者奉为圭臬——不是因为它能让人暴富,而是因为它精确地回答了一个所有人都想当然的问题:我有优势,该怎么下注?
本文用 15 分钟,拆解凯利公式的推导逻辑、它的变体、以及在真实市场中应用时必须面对的工程取舍。
一、那个被忽视的问题
绝大多数关于"交易系统"的讨论,都集中在两个问题上:
- 策略有没有优势?(胜率、盈亏比是多少)
- 策略能赚多少钱?(年化收益率、回撤)
但很少有人问第三个问题:
有了优势,每次该投入多少?
这个问题被忽视的原因很直观:直觉告诉我们,有优势就all in呗,优势越大下注越重。
但直觉是错的。
用一个极端例子说明:如果一个策略的胜率是 51%,盈亏比是 1:1(赢了赚 100%,输了亏 100%),表面看有优势,实际上 50 次交易后你会破产。因为 51% 的胜率不够覆盖 1:1 的盈亏比带来的波动性。
反直觉的事实是:即使有正期望收益的策略,错误的下注比例同样可以让你破产。 而凯利公式,正是回答这个问题的数学框架。
二、凯利公式的推导
2.1 问题的数学建模
假设我们面临一个重复下注的场景:
| 参数 | 定义 |
|---|---|
| $b$ | 盈亏比(赢了赚 $b$,输了亏 1) |
| $p$ | 胜率(盈利交易的概率) |
| $q = 1-p$ | 败率 |
| $f$ | 单次下注比例(0 ≤ f ≤ 1) |
初始资本为 1,重复博弈 N 次后,资本的几何期望是:
$$G(f) = \lim_{N \to \infty} \left[ (1 + bf)^p \cdot (1 - f)^q \right]^N$$
这个表达式很难直观理解,但我们可以取对数,将指数变为线性:
$$\ln G(f) = \lim_{N \to \infty} N \cdot \left[ p \ln(1 + bf) + q \ln(1 - f) \right]$$
几何增长率最大化的目标,转化为对数增长率的最大化。
2.2 求导与最优解
对对数增长率求导,令其为零:
$$\frac{d}{df} \left[ p \ln(1 + bf) + q \ln(1 - f) \right] = 0$$
展开:
$$\frac{bp}{1 + bf} - \frac{q}{1 - f} = 0$$
移项整理:
$$bp(1 - f) = q(1 + bf)$$
$$bp - bpf = q + bqf$$
$$bp - q = f(b + bq + q)$$
注意到 $b + bq + q = b(1+q) + q = b(2-p) + q$——这个形式不够简洁。
重新从 $bp(1-f) = q(1+bf)$ 出发:
$$bp - bpf = q + bqf$$
$$bp - q = f(bp + bq)$$
$$bp - q = bf(p+q)$$
由于 $p + q = 1$:
$$f^* = \frac{bp - q}{b}$$
这就是凯利公式的标准形式:
$$\boxed{f^* = \frac{bp - q}{b} = p - \frac{q}{b}}$$
2.3 代入题目验证
回到选题的核心问题:胜率 60%、盈亏比 2:1 的策略,每次该投入多少?
- $p = 0.6$
- $q = 0.4$
- $b = 2$
计算:
$$f^* = \frac{2 \times 0.6 - 0.4}{2} = \frac{1.2 - 0.4}{2} = \frac{0.8}{2} = 0.4$$
答案:每次投入本金的 40%。
验证这个结果:如果初始资金 10 万,每次下注 4 万。赢了变成 18 万(+8万),输了变成 6 万(-4万)。长期来看,资本会以 $1 + 0.8^{0.6} \times 1.2^{0.4} - 1$ 的增长率指数级增长。
三、凯利公式的几何直觉
3.1 当 b = 1(盈亏比 1:1)时
$$f^* = p - q = 2p - 1$$
这意味着只有在胜率超过 50% 时才能下注。如果胜率是 55%,最优仓位是 10%。
这个结论令人不安:有 55% 胜率的策略,最优仓位只有 10%?
理解这个数字的关键是:凯利追求的是长期复利最大化,而不是短期收益最大化。 10% 仓位让你破产的概率最低,长期复合增长最快。但如果你只做几次交易就想离场,10% 仓位确实显得"保守"。
3.2 当 p < q/b 时(公式失效)
凯利公式有个隐性约束:$bp - q > 0$,即 $p > q/b$。
如果 $p \leq q/b$,分子非正,$f^* \leq 0$,意味着不应该下注。这对应的是没有优势的情况。
以盈亏比 1:1 为例,$q/b = q = 1-p$,所以 $p > 1-p$,即 $p > 0.5$。胜率不足 50% 时,最优策略是不参与。
3.3 盈亏比越高,可下注比例越高
将公式改写:
$$f^* = p - \frac{q}{b}$$
对 $b$ 求偏导:$\frac{\partial f^*}{\partial b} = \frac{q}{b^2} > 0$
盈亏比越高,凯利仓位越大。 这符合直觉:赔率越好,即使胜率不变,数学期望提升,可以下更重的注。
四、分数凯利:更安全的工程实践
4.1 全凯利的波动性代价
凯利公式给出的是数学最优解,但在实际交易中有两个致命问题:
- 模型误差:你估算的胜率和盈亏比与真实值有偏差,全凯利会放大这个误差
- 波动性炸弹:全凯利的仓位波动极大,单次亏损可能超过 30%,心理上难以承受
用凯利公式计算 $p = 0.6, b = 2$ 的策略:$f^* = 0.4$。
如果你的胜率实际只有 0.55(误差 8%):
$$f^* = \frac{2 \times 0.55 - 0.45}{2} = \frac{1.1 - 0.45}{2} = 0.325$$
全凯利(0.4)在这个偏差下会导致资金曲线剧烈波动,甚至亏损。
4.2 分数凯利:保守的工程选择
实践中,量化团队通常采用分数凯利:
| 分数倍数 | 下注比例 | 风险调整 |
|---|---|---|
| 1× 全凯利 | 40% | 理论最优,波动极大 |
| 1/2 凯利 | 20% | 约 75% 的收益,波动大幅降低 |
| 1/3 凯利 | 13.3% | 约 65% 的收益,更平稳 |
| 1/4 凯利 | 10% | 约 55% 的收益,实际最常用 |
业界流行的说法是:半凯利(half Kelly)是风险与收益的最佳平衡点。
这句话有数学支撑:用泰勒展开分析,1/2 凯利的几何增长率是全凯利的约 75%,而方差降低了约 50%。风险调整后的收益(类似夏普比率)显著提升。
4.3 动态调整机制
更成熟的仓位管理会引入动态凯利:根据近期表现调整仓位。
近期胜率下降 → 自动降低仓位
近期胜率稳定 → 维持当前仓位
近期胜率上升 → 允许适度加仓(但不超过凯利上限)
但这引入了一个新问题:均值回归陷阱。如果策略近期表现差,你降低仓位,结果策略"回归均值"了,你反而踏空。更常见的情况是相反的悲剧。
经验法则:用历史数据估算参数时,建议将估算的胜率降低 5-10 个百分点作为安全垫,将盈亏比降低 10-20% 作为容错空间。
五、生产级代码实现
5.1 凯利公式核心计算
import numpy as np
import pandas as pd
from typing import Tuple, Optional
import os
class KellyCalculator:
"""
凯利公式计算器
支持:
- 基础凯利公式
- 分数凯利(1/2, 1/3, 1/4)
- 动态凯利(基于置信区间调整)
"""
def __init__(self, safety_margin_p: float = 0.05,
safety_margin_b: float = 0.15):
"""
初始化
Args:
safety_margin_p: 胜率安全垫(默认值 0.05)
safety_margin_b: 盈亏比安全垫(默认值 0.15)
"""
self.safety_margin_p = safety_margin_p
self.safety_margin_b = safety_margin_b
def calculate_kelly(self, p: float, b: float,
fraction: float = 1.0) -> Tuple[float, dict]:
"""
计算凯利仓位
Args:
p: 胜率(0-1)
b: 盈亏比(赢了赚 b 倍,输了亏 1 倍)
fraction: 分数凯利倍数(1.0 = 全凯利,0.5 = 半凯利)
Returns:
(kelly_fraction, details): 凯利仓位比例和详细信息
"""
# 参数校验
if not 0 < p < 1:
raise ValueError(f"胜率必须在 (0, 1) 区间内,当前值: {p}")
if b <= 0:
raise ValueError(f"盈亏比必须为正数,当前值: {b}")
# 应用安全垫(工程实践)
p_adjusted = max(0.01, p - self.safety_margin_p)
b_adjusted = max(0.01, b * (1 - self.safety_margin_b))
# 凯利公式核心计算
if p_adjusted <= (1 - p_adjusted) / b_adjusted:
# 无优势区域
return 0.0, {
"raw_kelly": 0.0,
"adjusted_kelly": 0.0,
"has_edge": False,
"p_adjusted": p_adjusted,
"b_adjusted": b_adjusted,
"message": "无正期望收益,不建议下注"
}
kelly_raw = (b_adjusted * p_adjusted - (1 - p_adjusted)) / b_adjusted
kelly_fraction = max(0.0, min(1.0, kelly_raw * fraction)) # 限制在 [0, 1]
return kelly_fraction, {
"raw_kelly": kelly_raw,
"adjusted_kelly": kelly_raw * fraction,
"has_edge": True,
"p_adjusted": p_adjusted,
"b_adjusted": b_adjusted,
"fraction_used": fraction,
"message": f"建议仓位: {kelly_fraction:.2%}"
}
def backtest_kelly(self, returns: np.ndarray,
initial_capital: float = 100000,
fraction: float = 0.5,
show_progress: bool = True) -> pd.DataFrame:
"""
基于历史收益率序列回测凯利策略
Args:
returns: 收益率数组(正数盈利,负数亏损,-1 表示亏光)
initial_capital: 初始资金
fraction: 分数凯利倍数
show_progress: 是否显示进度
Returns:
回测结果 DataFrame
"""
capital = initial_capital
capital_history = [capital]
position_history = []
# 从历史数据估算胜率和盈亏比
wins = returns[returns > 0]
losses = returns[returns < 0]
if len(wins) == 0 or len(losses) == 0:
raise ValueError("需要同时有盈利和亏损记录才能估算凯利")
# 计算平均盈亏(转换为盈亏比)
avg_win = np.mean(wins)
avg_loss = np.abs(np.mean(losses))
b = avg_win / avg_loss if avg_loss > 0 else 0
# 计算胜率
p = len(wins) / len(returns)
# 计算凯利仓位
kelly_f, details = self.calculate_kelly(p, b, fraction)
# 模拟交易
for i, ret in enumerate(returns):
if kelly_f == 0:
# 无优势,不交易
position = 0
else:
position = capital * kelly_f
pnl = position * ret
capital = capital + pnl
capital = max(0, capital) # 防穿透
capital_history.append(capital)
position_history.append(position)
if show_progress and (i + 1) % 100 == 0:
print(f"交易 {i+1}/{len(returns)} | 资金: ${capital:,.2f}")
return pd.DataFrame({
"capital": capital_history[:-1],
"position": position_history,
"trade_return": returns
}), details
5.2 参数敏感性分析
def sensitivity_analysis(calculator: KellyCalculator,
p_range: np.ndarray,
b_range: np.ndarray) -> pd.DataFrame:
"""
分析凯利仓位对胜率和盈亏比的敏感性
"""
results = []
for p in p_range:
for b in b_range:
kelly, details = calculator.calculate_kelly(p, b, fraction=0.5)
results.append({
"胜率": f"{p:.0%}",
"盈亏比": f"{b:.1f}:1",
"全凯利": f"{details['raw_kelly']:.1%}",
"半凯利(推荐)": f"{kelly:.1%}",
"有优势": "✓" if details['has_edge'] else "✗"
})
return pd.DataFrame(results)
# 示例:生成敏感性分析表
calculator = KellyCalculator()
p_values = np.array([0.45, 0.50, 0.55, 0.60, 0.65, 0.70])
b_values = np.array([0.5, 1.0, 1.5, 2.0, 2.5, 3.0])
sensitivity_table = sensitivity_analysis(calculator, p_values, b_values)
print(sensitivity_table.to_string(index=False))
输出示例:
| 胜率 | 盈亏比 | 全凯利 | 半凯利(推荐) | 有优势 |
|---|---|---|---|---|
| 45% | 0.5:1 | 0.0% | 0.0% | ✗ |
| 50% | 1.0:1 | 0.0% | 0.0% | ✗ |
| 55% | 1.0:1 | 10.0% | 5.0% | ✓ |
| 60% | 2.0:1 | 40.0% | 20.0% | ✓ |
| 65% | 2.0:1 | 65.0% | 32.5% | ✓ |
| 70% | 3.0:1 | 80.0% | 40.0% | ✓ |
5.3 真实市场数据回测示例
def run_live_simulation():
"""
使用 TickDB 获取真实市场数据进行凯利回测
注意:此示例需要设置 TICKDB_API_KEY 环境变量
"""
api_key = os.environ.get("TICKDB_API_KEY")
if not api_key:
print("⚠️ 未设置 TICKDB_API_KEY,使用模拟数据演示")
return simulate_and_backtest()
try:
# 从 TickDB 获取历史 K 线数据
import requests
# 获取标的历史数据(示例:QQQ 日线)
response = requests.get(
"https://api.tickdb.ai/v1/market/kline",
headers={"X-API-Key": api_key},
params={
"symbol": "QQQ.US",
"interval": "1d",
"limit": 500 # 约 2 年数据
},
timeout=(3.05, 10)
)
if response.status_code == 200:
data = response.json()
closes = [k[4] for k in data["data"]] # 收盘价
returns = np.diff(closes) / closes[:-1]
# 简化的趋势跟随信号:当日收益 > 0 则次日做多
# (仅为演示,实际策略需更复杂)
signal_returns = returns[1:] # 信号后次日收益
calculator = KellyCalculator()
results, details = calculator.backtest_kelly(
signal_returns,
initial_capital=100000,
fraction=0.5
)
print(f"\n📊 回测结果摘要")
print(f" 估算胜率: {details['p_adjusted']:.2%}")
print(f" 估算盈亏比: {details['b_adjusted']:.2f}")
print(f" 建议仓位: {results['capital'].iloc[-1]:.2%}")
print(f" 最终资金: ${results['capital'].iloc[-1]:,.2f}")
return results
else:
print(f"API 错误: {response.status_code}")
return simulate_and_backtest()
except Exception as e:
print(f"⚠️ API 调用失败: {e},使用模拟数据")
return simulate_and_backtest()
def simulate_and_backtest():
"""使用蒙特卡洛模拟数据进行回测"""
np.random.seed(42)
# 模拟参数:胜率 60%,盈亏比 2:1
n_trades = 1000
p, b = 0.60, 2.0
# 生成交易结果
outcomes = np.where(
np.random.random(n_trades) < p,
np.random.exponential(b - 1, n_trades) + 1, # 盈利分布
-np.random.exponential(1, n_trades) # 亏损分布
)
calculator = KellyCalculator()
results, details = calculator.backtest_kelly(
outcomes,
initial_capital=100000,
fraction=0.5
)
print(f"\n📊 蒙特卡洛回测结果({n_trades} 次模拟交易)")
print(f" 估算胜率: {details['p_adjusted']:.2%}")
print(f" 估算盈亏比: {details['b_adjusted']:.2f}")
print(f" 建议仓位: {details['adjusted_kelly']:.2%}")
print(f" 最终资金: ${results['capital'].iloc[-1]:,.2f}")
print(f" 资金峰值: ${results['capital'].max():,.2f}")
print(f" 最大回撤: {(1 - results['capital'].min() / results['capital'].cummax()).max():.2%}")
return results
# 运行模拟
run_live_simulation()
六、凯利公式的局限性与工程边界
6.1 三个致命假设
凯利公式的有效性建立在三个假设之上,真实市场几乎不可能全部满足:
| 假设 | 公式中的含义 | 真实市场的不满足 |
|---|---|---|
| 可分割性 | 仓位可以任意比例调整 | 最小交易单位、滑点使连续仓位不可行 |
| 无限可重复 | 交易可以无限次重复 | 市场结构变化、策略失效 |
| 独立同分布 | 每次交易的胜率和盈亏比恒定 | 市场状态依赖、均值回归 |
实际工程建议:
- 设置仓位上限:即使凯利公式算出 60% 仓位,实际单笔不超过 20-25%(防止黑天鹅)
- 滚动参数估算:每 N 笔交易重新估算胜率和盈亏比,而非用全量历史
- 退出机制:当滚动胜率低于临界值,自动降低仓位或停止交易
6.2 胜率和盈亏比的不确定性
更隐蔽的问题是:你估算的胜率和盈亏比本身就是随机变量。
假设真实胜率是 60%,但你的估算基于 100 次交易:
- 100 次交易的标准误:$\sqrt{p(1-p)/n} = \sqrt{0.6 \times 0.4 / 100} \approx 4.9%$
- 95% 置信区间:60% ± 9.6%,即 50.4% 到 69.6%
如果下限 50.4% 刚好在临界点附近,用全凯利下注的风险急剧上升。
贝叶斯调整方法:将点估计替换为先验分布(如 Beta 分布拟合胜率),计算后验期望作为凯利输入。这需要更复杂的实现,但输出的仓位带有自然的置信区间权重。
6.3 相关性陷阱
凯利公式假设每次交易是独立的。但真实市场中:
- 日内趋势策略:连续亏损后往往跟着连续盈利(均值回归)
- 事件驱动策略:同一事件相关标的的交易结果高度相关
- 做市策略:库存暴露导致日内收益相关性
相关性会让凯利公式低估风险。如果你有 $N$ 笔相关的全凯利仓位,实际风险是独立假设下的 $\sqrt{N}$ 倍。
七、与其他仓位管理策略的对比
| 策略 | 公式/规则 | 优点 | 缺点 |
|---|---|---|---|
| 固定金额 | 每次下注 $X$ 元 | 简单,心理压力小 | 不随优势变化,效率低 |
| 固定比例 | 每次下注余额的 $f$ | 亏损时自动减仓 | 盈利时仓位不变 |
| 凯利公式 | $f^* = (bp-q)/b$ | 长期复利最优 | 波动大,参数敏感 |
| 分数凯利 | $f^* = k \times$ 凯利 | 降低波动,保留优势 | 部分损失长期增长 |
| ATR 仓位 | $f = \text{账户风险} / (n \times ATR)$ | 适应波动率 | 需要止损配合 |
结论:对于量化团队的实际工程实践,半凯利(half Kelly)+ 动态参数调整 是当前最主流的方案。它在数学最优性和工程稳健性之间取得了平衡。
八、结语
回到开篇的问题:胜率 60%、盈亏比 2:1 的策略,每次该投入多少?
凯利公式给出的答案是 40%——全凯利;或者 20%——半凯利,工程实践推荐。
但更重要的是理解这个数字的语境:
- 40% 不是"重仓",而是"有优势时的最优比例"
- 20% 不是"保守",而是"考虑了模型误差的稳健选择"
- 0% 可能是"正确答案",如果你的参数估算不够置信
凯利公式真正的价值,不是给你一个精确的仓位数字,而是强迫你显式化你的假设。当你把"我的策略有优势"翻译成"我的胜率是 60%,盈亏比是 2:1",你突然发现自己在做一个可以被证伪的陈述——这才是量化思维的精髓。
好的仓位管理,不是让你在单笔交易中赚最多,而是在长期重复博弈中,让复利站在你这一边。
下一步行动
如果你想亲手计算自己的策略凯利仓位:
- 访问 tickdb.ai 注册(免费,无需信用卡)
- 获取历史 K 线数据,导出你的策略信号和收益序列
- 使用本文的
KellyCalculator类,用真实数据替代模拟数据
如果你需要 10 年级别的美股历史 K 线数据来验证策略参数:
联系 [email protected] 获取机构级数据方案。
如果你习惯用 AI 辅助开发:
在 AI 助手中搜索安装 tickdb-market-data SKILL,直接用自然语言查询数据并对接回测框架。
风险提示:本文不构成任何投资建议。凯利公式的计算结果依赖于准确的胜率和盈亏比估算,实际市场中这些参数存在显著不确定性。任何仓位管理策略都无法消除投资风险,市场有风险,投资需谨慎。