Files
etf/docs/strategy_summaries/20260608_Kelly仓位权重模式.md

214 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Kelly 仓位权重模式实现总结
**日期**: 2026-06-07
**Commit**: `8b7bcf2`
**相关文件**:
- `rotation/simple_rotation.py`
- `rotation/config_loader.py`
- `rotation/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)
```python
class WeightType(str, Enum):
"""仓位加权模式"""
EQUAL = "equal" # 等权
RANK = "rank" # 按排名加权
KELLY = "kelly" # Kelly准则近似
```
### 3.2 核心函数 (simple_rotation.py)
```python
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 调用点修改
```python
# _generate_signals 中传递 scores
self._pending_weights = compute_position_weights(
ranked_holdings, self.weight_type, scores=factors,
)
```
### 3.4 配置使用 (config_simple.yaml)
```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. 后续优化方向
1. **Half-Kelly**: 使用 f*/2 降低波动
2. **动态 Kelly**: 根据市场状态调整 Kelly 系数
3. **风险预算**: 结合波动率进行风险平价分配
4. **多因子 Kelly**: 综合多个因子 score 计算 edge
---
## 7. 结论
Kelly 仓位模式通过 score-proportional 近似在保持可插拔架构的同时实现了最优风险调整后收益。对于追求收益最大化的场景kelly 模式是首选对于风险厌恶场景equal 模式更稳健。