fix: greedy模式仅在select_num=1时生效

问题:greedy在select_num>1时仓位分配取决于ETF池大小,而非动量强度
- 场景1: 有色金属(1ETF)>原油(3ETF)>黄金(4ETF) → 有色25%,原油75%,黄金0%
- 场景2: 黄金(4ETF)>创业板(4ETF)>纳指(4ETF) → 黄金100%,其他0%

修复:select_num>1时greedy退化为equal权重

回测对比:
- select_num=3, rank: 326.60%
- select_num=1, greedy: 730.61% (集中度更高,收益更好)
This commit is contained in:
2026-06-21 13:13:17 +08:00
parent adb83d8cd7
commit 0da0306894

View File

@@ -530,17 +530,18 @@ class SimpleRotationStrategy:
def _compute_greedy_weights(self, holdings: List[str], factors: Dict[str, float]) -> Dict[str, float]:
"""Compute greedy weights for signal-level holdings.
Greedy Algorithm:
1. Each index has absorption capacity = min(n_etfs, ceil(1/max_weight)) × max_weight
2. Iterate through holdings in order (sorted by momentum)
3. Each index absorbs up to its capacity
4. Remaining weight flows to next index
Greedy Algorithm (only for select_num=1):
1. The top-1 index has absorption capacity = min(n_etfs, ceil(1/max_weight)) × max_weight
2. If capacity < 100%, remaining weight flows to next index by momentum
3. Continue until 100% allocated
Example (select_num=1, max_weight=0.25):
- 有色金属(1 ETF): capacity=25%, absorbs 25%, remaining=75%
- 原油(3 ETFs): capacity=75%, absorbs 75%, remaining=0%
- Total: 100%
For select_num>1, falls back to equal weight (greedy not applicable).
Args:
holdings: List of signal codes (sorted by momentum desc)
factors: Dict of signal_code -> momentum score
@@ -551,6 +552,12 @@ class SimpleRotationStrategy:
if not holdings:
return {}
# Greedy only for select_num=1
if self.select_num > 1:
# Fall back to equal weight
w = 1.0 / len(holdings)
return {code: w for code in holdings}
signal_weights = {}
remaining_weight = 1.0