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