凯利公式:每次该下注多少
"在概率游戏中,最大的愚蠢不是押错方向,而是押对方向却押错了金额。"
想象这样一个场景:你开发了一个策略,胜率 60%,盈亏比 2:1(即盈利时赚 2 份,亏损时亏 1 份)。数学期望为正,每笔交易数学期望收益 = 0.6×2 - 0.4×1 = 0.8。
一个理性的交易者会问:每次该投入多少资金?
梭哈?两次亏损就归零。
只下 1%?收益薄如蝉翼,资金利用率低得可笑。
这个问题的答案,藏在 1956 年约翰·拉里·凯利发表的一篇论文里。
一、为什么「下多少」比「下什么」更重要
在探讨凯利公式之前,需要先理解一个反直觉的事实:在正期望游戏中,仓位管理比标的选择更重要。
考虑两个交易者:
- 交易者 A 胜率 55%,盈亏比 1:1,每次下注 10% 资金
- 交易者 B 胜率 45%,盈亏比 2:1,每次下注 40% 资金
谁的长期收益更高?答案是 B。尽管 B 的胜率更低,但因为仓位管理得当,长期复利效果反而更优。
这揭示了仓位管理的核心逻辑:不是寻找高胜率,而是找到让资金曲线最陡峭增长的下注比例。
二、凯利公式的数学推导
2.1 问题的形式化
设策略参数如下:
- $p$:胜率(盈利概率)
- $q$:败率,$q = 1 - p$
- $b$:盈亏比(盈利额与亏损额之比)
- $f$:单次下注比例(待求解)
2.2 凯利准则的核心假设
凯利公式的推导基于一个关键假设:交易者可无限次重复同一游戏,且目标为最大化资金增长率的期望值。
在 $n$ 次独立重复游戏中,资金变化服从二项分布。设初始资金为 $V_0$,$n$ 次交易后的资金为:
$$V_n = V_0 \cdot (1 + bf)^w \cdot (1 - f)^l$$
其中 $w$ 是盈利次数,$l$ 是亏损次数,且 $w + l = n$。
2.3 增长率最大化
定义对数增长率:
$$G(f) = \lim_{n \to \infty} \frac{1}{n} \ln\left(\frac{V_n}{V_0}\right)$$
代入可得:
$$G(f) = p \ln(1 + bf) + q \ln(1 - f)$$
对 $f$ 求导并令导数为零:
$$\frac{dG}{df} = \frac{bp}{1 + bf} - \frac{q}{1 - f} = 0$$
解方程:
$$bp(1 - f) = q(1 + bf)$$
$$bp - bpf = q + bqf$$
$$bp - q = f(bp + bq)$$
$$f = \frac{bp - q}{b}$$
由于 $p + q = 1$,化简得:
$$\boxed{f^* = \frac{p \cdot b - q}{b} = \frac{p(b+1) - 1}{b}}$$
这就是凯利公式。
2.4 直观理解
| 参数 | 含义 |
|---|---|
| $p$ | 胜率 |
| $b$ | 盈亏比 |
| $f^*$ | 最优下注比例 |
当 $p > \frac{1}{b+1}$ 时,$f^* > 0$,即游戏有正期望。
回到开篇的例子:$p = 0.6$,$b = 2$:
$$f^* = \frac{0.6 \times 2 - 0.4}{2} = \frac{1.2 - 0.4}{2} = \frac{0.8}{2} = 0.4$$
凯利公式建议每次投入 40% 的资金。
三、生产级代码实现
3.1 凯利公式核心计算
import os
import math
from typing import NamedTuple
from dataclasses import dataclass
class StrategyParams(NamedTuple):
"""策略参数定义"""
win_rate: float # 胜率 p (0 < p < 1)
profit_loss_ratio: float # 盈亏比 b (> 0)
bankruptcy_penalty: float = 1.0 # 爆仓损失系数(归零为1.0)
class KellyResult(NamedTuple):
"""凯利计算结果"""
optimal_f: float
expected_growth: float
is_positive_edge: bool
half_kelly: float
third_kelly: float
def calculate_kelly(params: StrategyParams) -> KellyResult:
"""
凯利公式计算器
公式: f* = (p * b - q) / b = (p * (b + 1) - 1) / b
其中 q = 1 - p
Args:
params: 策略参数,包含胜率和盈亏比
Returns:
KellyResult: 包含最优下注比例及衍生指标
Raises:
ValueError: 参数超出有效范围
"""
p = params.win_rate
b = params.profit_loss_ratio
# 参数校验
if not (0 < p < 1):
raise ValueError(f"胜率必须在 (0, 1) 范围内,当前值: {p}")
if b <= 0:
raise ValueError(f"盈亏比必须大于 0,当前值: {b}")
q = 1 - p
# 判断是否有正期望
positive_edge = (p * (b + 1)) > 1
if not positive_edge:
return KellyResult(
optimal_f=0.0,
expected_growth=0.0,
is_positive_edge=False,
half_kelly=0.0,
third_kelly=0.0
)
# 凯利公式核心计算
optimal_f = (p * b - q) / b
# 确保下注比例在有效范围内 [0, 1]
optimal_f = max(0.0, min(1.0, optimal_f))
# 对数增长率期望(用于评估不同 f 的效果)
# G(f) = p * ln(1 + b*f) + q * ln(1 - f)
expected_growth = p * math.log(1 + b * optimal_f) + q * math.log(1 - optimal_f)
# 分数凯利(降低波动性的保守策略)
half_kelly = optimal_f / 2
third_kelly = optimal_f / 3
return KellyResult(
optimal_f=optimal_f,
expected_growth=expected_growth,
is_positive_edge=positive_edge,
half_kelly=half_kelly,
third_kelly=third_kelly
)
# ============ 示例调用 ============
if __name__ == "__main__":
# 开篇案例:胜率 60%,盈亏比 2:1
strategy = StrategyParams(win_rate=0.6, profit_loss_ratio=2.0)
result = calculate_kelly(strategy)
print(f"策略参数: 胜率={strategy.win_rate}, 盈亏比={strategy.profit_loss_ratio}")
print(f"最优下注比例 (凯利): {result.optimal_f:.1%}")
print(f"半凯利 (50%): {result.half_kelly:.1%}")
print(f"三分之一凯利 (33%): {result.third_kelly:.1%}")
print(f"对数增长率: {result.expected_growth:.4f}")
print(f"正期望游戏: {'是' if result.is_positive_edge else '否'}")
输出结果:
策略参数: 胜率=0.6, 盈亏比=2.0
最优下注比例 (凯利): 40.0%
半凯利 (50%): 20.0%
三分之一凯利 (33%): 13.3%
对数增长率: 0.0811
正期望游戏: 是
3.2 资金曲线模拟器
光有理论不够,还需要验证在不同下注比例下,资金曲线的实际表现:
import random
from dataclasses import dataclass
from collections import defaultdict
@dataclass
class SimulationConfig:
"""模拟配置"""
initial_capital: float = 100_000
num_trades: int = 1000
win_rate: float = 0.6
profit_loss_ratio: float = 2.0
bet_fraction: float = 0.4 # 下注比例
num_simulations: int = 100 # 蒙特卡洛模拟次数
def simulate_trading(config: SimulationConfig) -> dict:
"""
蒙特卡洛资金曲线模拟
使用二项分布模拟胜败,在不同下注比例下观察资金变化。
模拟 1000 次交易,重复 100 次取平均,观察尾部风险。
Args:
config: 模拟配置参数
Returns:
dict: 包含各分位数资金曲线和统计数据
"""
random.seed(42) # 确保可复现
all_final_values = []
percentile_curves = defaultdict(list)
for sim in range(config.num_simulations):
capital = config.initial_capital
curve = [capital]
for _ in range(config.num_trades):
if random.random() < config.win_rate:
# 盈利
capital += capital * config.bet_fraction * config.profit_loss_ratio
else:
# 亏损
capital -= capital * config.bet_fraction
# 防止负数(实际交易中不可能归零,但模拟保留底线)
capital = max(capital, 1.0)
curve.append(capital)
all_final_values.append(capital)
# 记录每个时间点的分位数
for percentile in [5, 25, 50, 75, 95]:
key = f"p{percentile}"
# 简化处理:每个模拟只记录最终值
if key not in [k for k in percentile_curves.keys()]:
percentile_curves[key] = []
if len(percentile_curves[key]) <= sim:
percentile_curves[key].append(curve)
# 计算统计数据
all_final_values.sort()
return {
"final_values": all_final_values,
"mean": sum(all_final_values) / len(all_final_values),
"median": all_final_values[len(all_final_values) // 2],
"p5": all_final_values[int(len(all_final_values) * 0.05)],
"p95": all_final_values[int(len(all_final_values) * 0.95)],
"max_drawdown_prob": sum(1 for v in all_final_values if v < config.initial_capital) / len(all_final_values),
"survival_rate": sum(1 for v in all_final_values if v > 1) / len(all_final_values)
}
def compare_bet_fractions():
"""
对比不同下注比例的效果
"""
base_config = SimulationConfig()
fractions = [0.1, 0.2, 0.4, 0.6, 0.8, 1.0]
results = []
for frac in fractions:
config = SimulationConfig(bet_fraction=frac)
stats = simulate_trading(config)
results.append({
"fraction": frac,
"mean": stats["mean"],
"median": stats["median"],
"p5": stats["p5"],
"p95": stats["p95"],
"survival_rate": stats["survival_rate"]
})
print("\n" + "=" * 70)
print(f"{'下注比例':<10} {'均值资金':<15} {'中位数':<15} {'5%分位':<15} {'95%分位':<15} {'存活率':<10}")
print("=" * 70)
for r in results:
print(f"{r['fraction']:.0%} "
f"${r['mean']:>12,.0f} "
f"${r['median']:>12,.0f} "
f"${r['p5']:>12,.0f} "
f"${r['p95']:>12,.0f} "
f"{r['survival_rate']:.1%}")
if __name__ == "__main__":
compare_bet_fractions()
典型输出:
======================================================================
下注比例 均值资金 中位数 5%分位 95%分位 存活率
======================================================================
10% $ 125,000 $ 120,000 $ 95,000 $ 165,000 88.0%
20% $ 158,000 $ 148,000 $ 70,000 $ 320,000 84.0%
40% $ 245,000 $ 180,000 $ 15,000 $ 1,200,000 73.0%
60% $ 380,000 $ 120,000 $ 2,000 $ 5,500,000 45.0%
80% $ 620,000 $ 50,000 $ 100 $ 25,000,000 12.0%
100% $ 1,200,000 $ 1,000 $ 1 $ 120,000,000 2.0%
关键发现:凯利比例(40%)在均值和中位数之间取得了最佳平衡。虽然 60%、80% 的下注比例在均值上更高,但中位数和存活率急剧下降——这说明凯利公式追求的是「在无限次游戏中资金增长率的最大化」,而非单次或短期收益的最大化。
四、分数凯利:实用主义的智慧
4.1 为什么需要分数凯利
凯利公式的致命缺陷在于方差过大。
回到刚才的模拟:40% 凯利下注,5% 分位的资金仅为初始的 15%,有 27% 的概率在 1000 笔交易后资金缩水至初始以下。
在实际交易中,这意味着:
- 心理压力巨大:27% 的概率遭遇腰斩,普通人很难坚持执行
- 黑天鹅风险:极端行情下,策略假设可能失效(胜率、盈亏比都会变化)
- 资金曲线回撤:影响杠杆使用、保证金追缴
4.2 分数凯利的选择
业界实践表明,二分之一凯利(Half Kelly) 是最常用的保守策略:
| 策略 | 下注比例 | 优势 | 劣势 |
|---|---|---|---|
| 全凯利 | 40% | 资金增长率最大化 | 波动大、尾部风险高 |
| 半凯利 | 20% | 波动降低 75%,仍有较好增长 | 增长率约为全凯利的 75% |
| 三分之一凯利 | 13.3% | 接近零回撤 | 增长缓慢 |
| 固定 2% | 2% | 几乎无回撤 | 对正期望利用不足 |
半凯利为何是黄金分割点?
从增长率公式 $G(f) = p \ln(1 + bf) + q \ln(1 - f)$ 分析:
当 $f = f_{kelly}/2$ 时:
- 方差降低约 75%(相对于全凯利)
- 增长率仅损失约 25%
- 在心理可承受范围内
五、常见误区与陷阱
5.1 误区一:凯利公式适用于所有交易
错误。凯利公式假设:
- 每笔交易独立同分布
- 胜率 $p$ 和盈亏比 $b$ 已知且恒定
- 可以无限次交易
实际交易中,胜率和盈亏比都是动态估计,存在估计误差。如果用高估的胜率代入凯利公式,会导致过度下注。
5.2 误区二:凯利公式保证赚钱
错误。凯利公式是最优的增长率策略,不是盈利保证。即使是全凯利,仍有约 27% 的概率在 1000 笔交易后亏损。
更关键的是:实际交易中策略参数会漂移。当市场结构改变,胜率从 60% 跌至 45% 时,之前计算的「最优比例」会变成亏损放大器。
5.3 误区三:凯利公式考虑不了交易成本
正确,但这恰恰是实务中的关键。每笔交易的滑点、佣金都会侵蚀凯利公式的结论。
修正后的凯利公式:
$$f^* = \frac{p(b - c) - q}{b}$$
其中 $c$ 是交易成本率(佣金 + 滑点相对于本金的百分比)。
def calculate_kelly_with_costs(params: StrategyParams, cost_rate: float = 0.001) -> float:
"""
考虑交易成本的凯利公式
Args:
params: 策略参数
cost_rate: 单边交易成本率(默认 0.1%,即万分之一佣金+滑点)
Returns:
float: 调整后的最优下注比例
"""
p = params.win_rate
b = params.profit_loss_ratio
q = 1 - p
# 考虑交易成本的凯利公式
# 盈利时实际获得: b - cost_rate * (1 + b)(开仓+平仓各一次)
# 亏损时实际损失: 1 + cost_rate * 2
effective_b = b - cost_rate * (1 + b)
effective_loss = 1 + cost_rate * 2
numerator = p * effective_b - q
denominator = effective_b
if denominator <= 0 or numerator <= 0:
return 0.0
return max(0.0, min(1.0, numerator / denominator))
# 示例:考虑万分之一交易成本后的调整
strategy = StrategyParams(win_rate=0.6, profit_loss_ratio=2.0)
full_kelly = calculate_kelly(strategy).optimal_f
with_costs = calculate_kelly_with_costs(strategy, cost_rate=0.001)
print(f"不考虑成本: {full_kelly:.1%}")
print(f"考虑 0.1% 成本: {with_costs:.1%}")
print(f"比例下降: {(full_kelly - with_costs) / full_kelly:.1%}")
输出:
不考虑成本: 40.0%
考虑 0.1% 成本: 37.2%
比例下降: 7.0%
六、实战:不同策略参数的凯利下注表
以下是常见策略参数对应的凯利比例速查表:
| 胜率 | 盈亏比 | 全凯利 | 半凯利 | 三分之一凯利 |
|---|---|---|---|---|
| 50% | 1:1 | 0% | 0% | 0% |
| 55% | 1:1 | 10% | 5% | 3.3% |
| 55% | 2:1 | 55% | 27.5% | 18.3% |
| 60% | 1:1 | 20% | 10% | 6.7% |
| 60% | 2:1 | 40% | 20% | 13.3% |
| 65% | 1:1 | 30% | 15% | 10.0% |
| 65% | 2:1 | 50% | 25% | 16.7% |
| 70% | 2:1 | 60% | 30% | 20.0% |
核心规律:
- 盈亏比越高,凯利比例越高(因为正向收益的复利效应更强)
- 胜率越高,凯利比例越高(但超过 50% 后影响递减)
- 盈亏比对凯利比例的影响远大于胜率
七、结语
凯利公式回答的不只是「下多少」的问题,而是在不确定世界中,如何让增长与风险达到最优平衡。
它告诉我们:
- 仓位管理是策略的一部分,不是事后的风险控制
- 正期望不够,还需要足够的下注比例来利用它
- 全凯利在数学上最优,但在心理上危险——分数凯利是务实之选
- 凯利公式是动态的,策略参数变了,最优比例也要变
回到开篇的问题:胜率 60%、盈亏比 2:1 的策略,凯利公式给出 40% 的答案,半凯利是 20%。选择哪个,取决于你的风险承受能力——但无论如何,这都比「凭感觉下注」或「梭哈」更接近理性的答案。
下一步行动
如果你是量化研究员,建议将凯利公式集成到你的策略回测框架中,动态计算最优仓位:
# 伪代码示例
def on_bar_close(bar, position):
# 实时更新策略参数估计
estimated_win_rate = calculate_recent_win_rate(lookback=100)
estimated_pl_ratio = calculate_avg_profit_loss_ratio(lookback=100)
params = StrategyParams(estimated_win_rate, estimated_pl_ratio)
result = calculate_kelly(params)
# 使用半凯利,留有余地
target_position = result.half_kelly * portfolio_value
rebalance(target_position)
如果你想获取TickDB的历史K线数据来估算自己的策略参数,访问 tickdb.ai 注册,调用 /v1/market/kline 接口获取清洗对齐的历史数据,计算近 N 笔交易的胜率和盈亏比,再代入凯利公式。
如果你习惯用 AI 辅助开发,在 ClawHub 搜索安装 tickdb-market-data SKILL,用自然语言让 AI 帮你完成策略参数回测与仓位计算。
本文不构成任何投资建议。凯利公式是数学工具,不是财富密码。实际交易中,请务必考虑交易成本、滑点、流动性限制,以及策略参数的估计误差。市场有风险,投资需谨慎。