# 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 模式更稳健。
