feat(s6): AlphaAssetSelector bucket-ban kwargs (BLUE no-op)

Add `asset_bucket_ban_set` and `asset_bucket_assignments` kwargs to
AlphaAssetSelector.__init__ (both default None → BLUE unchanged).

When active, assets whose KMeans bucket is in the ban set are skipped
inside rank_assets() so the next-ranked asset takes the slot — capital
is preserved rather than wasted by a 0× sizer multiplier.

Plan ref: Task 9 — ban ≠ 0× is the critical caveat from the sprint plan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hjnormey
2026-04-22 06:07:07 +02:00
parent 01c19662cb
commit ce7f3ce8ff

View File

@@ -10,7 +10,7 @@ import math
import numpy as np import numpy as np
from numba import njit from numba import njit
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple from typing import Dict, List, Optional, Set, Tuple
# ── Constants (matching dolphin_vbt_real.py lines 104-115) ──────────────────── # ── Constants (matching dolphin_vbt_real.py lines 104-115) ────────────────────
@@ -195,9 +195,13 @@ class AlphaAssetSelector:
self, self,
lookback_horizon: int = IRP_LOOKBACK, lookback_horizon: int = IRP_LOOKBACK,
ars_weights: Optional[List[float]] = None, ars_weights: Optional[List[float]] = None,
asset_bucket_ban_set: Optional[Set[int]] = None,
asset_bucket_assignments: Optional[Dict[str, int]] = None,
): ):
self.H = lookback_horizon self.H = lookback_horizon
self.weights = ars_weights or ARS_WEIGHTS self.weights = ars_weights or ARS_WEIGHTS
self.asset_bucket_ban_set = asset_bucket_ban_set
self.asset_bucket_assignments = asset_bucket_assignments or {}
def rank_assets( def rank_assets(
self, self,
@@ -236,6 +240,15 @@ class AlphaAssetSelector:
best_eff = valid[ri, 4] best_eff = valid[ri, 4]
asset = asset_names[asset_idx] asset = asset_names[asset_idx]
# GREEN-only bucket-ban filter. BLUE passes None → no-op.
# Skips banned-bucket assets so the next-ranked asset takes the slot
# (preserves capital; a sizer 0× would waste the slot).
if self.asset_bucket_ban_set:
bkt = self.asset_bucket_assignments.get(asset)
if bkt is not None and bkt in self.asset_bucket_ban_set:
continue
action = "SHORT" if trade_dir == -1 else "LONG" action = "SHORT" if trade_dir == -1 else "LONG"
orientation = "DIRECT" if trade_dir == regime_direction else "INVERSE" orientation = "DIRECT" if trade_dir == regime_direction else "INVERSE"