6.4 KiB
6.4 KiB
Kelly 仓位权重模式实现总结
日期: 2026-06-07
Commit: 8b7bcf2
相关文件:
rotation/simple_rotation.pyrotation/config_loader.pyrotation/config_simple.yaml
1. 背景与动机
1.1 问题提出
原有仓位管理支持两种模式:
- equal: 等权分配 (1/N)
- rank: 按排名三角权重 (第1名50%, 第2名33%, 第3名17%)
用户询问能否使用 Kelly 准则进行仓位分配。
1.2 Kelly 准则简介
经典 Kelly 公式: f = W - (1-W)/R*
- W = 胜率(历史盈利交易占比)
- R = 盈亏比(平均盈利/平均亏损)
1.3 经典 Kelly 的挑战
| 问题 | 说明 |
|---|---|
| 样本量不足 | 每个标的被持有的天数有限,统计胜率/盈亏比不稳定 |
| 非平稳性 | 市场环境变化导致历史统计不代表未来 |
| 极端值敏感 | 一次大亏会剧烈改变 Kelly 比例 |
| 需要 expanding window | 回测中每天用截止当天的历史来估计,计算量大 |
2. 解决方案:Score-Proportional Kelly 近似
2.1 核心思路
利用当前动量分数 weighted_momentum_score = annualized_return × R² 作为 edge 代理,构造 Kelly 近似:
w_i = max(score_i, 0) / Σ max(score_j, 0)
2.2 设计优势
- 无需额外历史统计:每天从截面数据直接计算
- 天然支持 expanding window:每天用最新数据
- 负分自动排除:Kelly 原则 - 不下注负期望
- 可插拔设计:与现有 equal/rank 模式统一接口
2.3 公式推导
动量分数 score = annualized_return × R² 包含:
- annualized_return: 趋势方向和强度
- R²: 趋势质量(信噪比)
正 score 意味着正期望,Kelly 建议按 edge 比例下注。归一化后得到仓位权重。
3. 代码实现
3.1 枚举扩展 (config_loader.py)
class WeightType(str, Enum):
"""仓位加权模式"""
EQUAL = "equal" # 等权
RANK = "rank" # 按排名加权
KELLY = "kelly" # Kelly准则近似
3.2 核心函数 (simple_rotation.py)
def compute_position_weights(
ranked_holdings: List[str],
weight_type: str = 'equal',
scores: Dict[str, float] = None, # 新增参数
) -> Dict[str, float]:
"""
Schemes:
equal: each slot = 1/N, duplicates summed.
rank: slot i (0-indexed) = (N-i) / triangular(N), duplicates summed.
kelly: w_i = max(score_i, 0) / sum(max(score_j, 0)).
Score-proportional weighting as Kelly criterion proxy.
Negative scores excluded (Kelly: don't bet on negative edge).
"""
N = len(ranked_holdings)
if N == 0:
return {}
weights: Dict[str, float] = {}
if weight_type == 'kelly':
if not scores:
raise ValueError("Kelly weighting requires 'scores' parameter")
# Kelly proxy: weight proportional to positive scores
positive_scores = {c: max(scores.get(c, 0.0), 0.0) for c in set(ranked_holdings)}
total = sum(positive_scores.values())
if total <= 0:
# Fallback to equal if all scores non-positive
w = 1.0 / len(positive_scores)
for code in positive_scores:
weights[code] = w
else:
for code in ranked_holdings:
w = positive_scores.get(code, 0.0) / total
weights[code] = weights.get(code, 0.0) + w
elif weight_type == 'rank':
triangular = N * (N + 1) / 2
for i, code in enumerate(ranked_holdings):
w = (N - i) / triangular
weights[code] = weights.get(code, 0.0) + w
else:
# equal (default)
w = 1.0 / N
for code in ranked_holdings:
weights[code] = weights.get(code, 0.0) + w
return weights
3.3 调用点修改
# _generate_signals 中传递 scores
self._pending_weights = compute_position_weights(
ranked_holdings, self.weight_type, scores=factors,
)
3.4 配置使用 (config_simple.yaml)
rotation:
diversified: true
select_num: 3
weight: kelly # 可选: equal, rank, kelly
4. 回测结果对比
回测区间: 2020-01-10 ~ 2026-06-08 (1550 交易日)
| 指标 | equal | rank | kelly |
|---|---|---|---|
| 累计收益 | 204.97% | 255.45% | 405.23% |
| 年化收益 | 19.88% | 22.90% | 30.13% |
| 最大回撤 | -14.65% | -16.27% | -20.44% |
| 夏普比率 | 1.13 | 1.12 | 1.15 |
| Calmar比率 | 1.36 | 1.41 | 1.47 |
| 日胜率 | 54.07% | 53.75% | 54.10% |
| 调仓次数 | 392 | 392 | 392 |
4.1 结果分析
Kelly 模式特点:
- 收益最高: 按动量分数比例分配权重,强势标的获得更大仓位
- 夏普最高: 风险调整后收益最优
- Calmar 最高: 收益/回撤比最优
- 回撤较大: 集中度更高导致波动更大
三种模式定位:
- equal: 保守型,分散风险,适合风险厌恶
- rank: 平衡型,按排名阶梯分配
- kelly: 进攻型,按 edge 比例集中配置
4.2 为什么日胜率会变化?
虽然信号生成(调仓日期、持仓标的)完全相同,但仓位权重影响每日组合收益:
daily_return = Σ (weight_i × return_i)
当某天收益接近 0 时,权重分配的变化可能让它在正/负之间翻转。例如:
- 排名第1的标的大跌
- rank 模式给 50% 权重 → 组合收益可能变负
- equal 模式只给 33% → 影响较小,可能仍为正
5. 设计原则
5.1 可插拔架构
- 统一函数签名,通过
weight_type参数切换 - 新增模式只需添加分支,不影响现有逻辑
scores参数可选,仅 kelly 模式需要
5.2 防御性设计
- Kelly 模式校验
scores参数 - 全负分时自动 fallback 到等权
- 与 bond fill 机制兼容(债券 score 通常为负)
5.3 配置驱动
- 通过 YAML 配置切换,无需修改代码
- 支持环境变量覆盖
- 与现有配置体系一致
6. 后续优化方向
- Half-Kelly: 使用 f*/2 降低波动
- 动态 Kelly: 根据市场状态调整 Kelly 系数
- 风险预算: 结合波动率进行风险平价分配
- 多因子 Kelly: 综合多个因子 score 计算 edge
7. 结论
Kelly 仓位模式通过 score-proportional 近似,在保持可插拔架构的同时,实现了最优风险调整后收益。对于追求收益最大化的场景,kelly 模式是首选;对于风险厌恶场景,equal 模式更稳健。