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
from numba import njit
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) ────────────────────
@@ -195,9 +195,13 @@ class AlphaAssetSelector:
self,
lookback_horizon: int = IRP_LOOKBACK,
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.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(
self,
@@ -236,6 +240,15 @@ class AlphaAssetSelector:
best_eff = valid[ri, 4]
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"
orientation = "DIRECT" if trade_dir == regime_direction else "INVERSE"