diff --git a/rotation/simple_rotation.py b/rotation/simple_rotation.py index 6743f72..81d500d 100644 --- a/rotation/simple_rotation.py +++ b/rotation/simple_rotation.py @@ -583,8 +583,10 @@ class SimpleRotationStrategy: n_slots = self.select_num - len(ranked_holdings) ranked_holdings.extend([self.bond_code] * n_slots) - # Compute position weights via configured scheme - self._position_weights = compute_position_weights( + # Compute position weights via configured scheme. + # These are *pending* weights; the caller (run) locks them in + # only when an actual rebalance occurs. + self._pending_weights = compute_position_weights( ranked_holdings, self.weight_type, ) @@ -708,6 +710,7 @@ class SimpleRotationStrategy: nav = 1.0 rebalance_count = 0 entry_info: Dict[str, dict] = {} # signal_code -> {entry_date, entry_price_etf, entry_price_idx} + active_weights: Dict[str, float] = {} # locked-in weights, updated only on rebalance for i, date in enumerate(self.trading_calendar): # Signal timing: 9:00 AM on day T @@ -732,6 +735,11 @@ class SimpleRotationStrategy: is_rebalance = (sorted(new_holdings) != sorted(current_holdings)) and len(current_holdings) > 0 + # Lock in position weights only on rebalance (or first day) + if is_rebalance or not current_holdings: + active_weights = dict(self._pending_weights) + self._position_weights = active_weights + # Return uses T's ETF prices (open for buy/sell, close for hold) daily_return = self._calculate_daily_return( current_holdings, new_holdings, date, is_rebalance @@ -774,7 +782,7 @@ class SimpleRotationStrategy: 'removed': sorted(removed), 'factors': {k: round(v, 6) for k, v in factors.items()}, 'threshold': threshold_val, - 'position_weights': {k: round(v, 6) for k, v in self._position_weights.items()}, + 'position_weights': {k: round(v, 6) for k, v in active_weights.items()}, }) current_holdings = new_holdings