From 9d79ef9ca0a4e18a4c1e7ba77313c56b2e931ed0 Mon Sep 17 00:00:00 2001 From: aszerW Date: Sun, 21 Jun 2026 13:59:01 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20greedy=E6=A8=A1=E5=BC=8F=E4=BB=8E?= =?UTF-8?q?=E6=89=80=E6=9C=89=E4=BF=A1=E5=8F=B7=E4=B8=AD=E9=A1=BA=E5=BB=B6?= =?UTF-8?q?=EF=BC=8C=E8=80=8C=E9=9D=9E=E4=BB=85holdings=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:select_num=1时,holdings只有1个元素,ETF池不足时仓位闲置 - 有色金属(1ETF): 仓位25%,75%闲置 - 德国DAX(2ETF): 仓位50%,50%闲置 - 原油(3ETF): 仓位75%,25%闲置 修复:从所有信号中按动量排序顺延 - 有色金属(25%) → 顺延到第2名信号(如原油75%) → 100%满配 回测对比 (select_num=1): - equal: 1053% 累计收益, 1.34 夏普 - greedy(修复前): 730% 累计收益, 1.31 夏普 - greedy(修复后): 1355% 累计收益, 1.69 夏普 --- rotation/simple_rotation.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/rotation/simple_rotation.py b/rotation/simple_rotation.py index 8262abc..9ae6d4f 100644 --- a/rotation/simple_rotation.py +++ b/rotation/simple_rotation.py @@ -531,9 +531,10 @@ class SimpleRotationStrategy: """Compute greedy weights for signal-level holdings. 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 + 1. Get ALL signals sorted by momentum (not just holdings) + 2. The top signal has absorption capacity = min(n_etfs, ceil(1/max_weight)) × max_weight + 3. If capacity < 100%, remaining weight flows to next signal by momentum + 4. Continue until 100% allocated Example (select_num=1, max_weight=0.25): - 有色金属(1 ETF): capacity=25%, absorbs 25%, remaining=75% @@ -558,10 +559,17 @@ class SimpleRotationStrategy: w = 1.0 / len(holdings) return {code: w for code in holdings} + # Get ALL signals sorted by momentum (for spill-over) + all_signals_sorted = sorted( + self.signal_codes, + key=lambda c: factors.get(c, 0), + reverse=True + ) + signal_weights = {} remaining_weight = 1.0 - for signal_code in holdings: + for signal_code in all_signals_sorted: if remaining_weight <= 0: break @@ -579,7 +587,8 @@ class SimpleRotationStrategy: remaining_weight -= absorb # Assign weight to this signal - signal_weights[signal_code] = absorb + if absorb > 0: + signal_weights[signal_code] = absorb return signal_weights