98 lines
3.6 KiB
Python
98 lines
3.6 KiB
Python
|
|
"""
|
|||
|
|
ScenarioLibrary — sweeps the state space for comprehensive Mode 1 coverage.
|
|||
|
|
|
|||
|
|
Unlike ScenarioFactory which builds 30 pre-defined scenario types per asset,
|
|||
|
|
ScenarioLibrary SWEEPS across parameter dimensions to ensure coverage of
|
|||
|
|
regions the tape never visited.
|
|||
|
|
|
|||
|
|
Sweep dimensions (orthogonal):
|
|||
|
|
- spread_bps: [0.1, 0.5, 1.0, 2.0, 5.0, 10.0]
|
|||
|
|
- depth_fraction: [0.01, 0.05, 0.1, 0.3, 0.5, 1.0]
|
|||
|
|
- toxicity: [0.0, 0.3, 0.7, 1.0]
|
|||
|
|
- regime: ["normal", "crisis", "recovery"]
|
|||
|
|
|
|||
|
|
Total: 6 × 6 × 4 × 4 = 576 grid points per asset.
|
|||
|
|
With 3 assets = 1,728 scenarios total.
|
|||
|
|
"""
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
from dataclasses import dataclass
|
|||
|
|
from typing import Dict, List, Optional, Sequence, Tuple
|
|||
|
|
|
|||
|
|
from malkhut.training.asset_classification import ASSET_PROFILES, get_asset_profile
|
|||
|
|
|
|||
|
|
|
|||
|
|
# Sweep dimension defaults
|
|||
|
|
DEFAULT_SPREAD_MULTS = [0.1, 0.5, 1.0, 2.0, 5.0, 10.0]
|
|||
|
|
DEFAULT_DEPTH_FRACS = [0.01, 0.05, 0.1, 0.3, 0.5, 1.0]
|
|||
|
|
DEFAULT_TOXICITY = [0.0, 0.3, 0.7, 1.0]
|
|||
|
|
DEFAULT_REGIMES = ["normal", "crisis", "recovery", "transition"]
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass(frozen=True, slots=True)
|
|||
|
|
class SweepPoint:
|
|||
|
|
"""A single point in the swept state space."""
|
|||
|
|
symbol: str
|
|||
|
|
spread_mult: float
|
|||
|
|
depth_fraction: float
|
|||
|
|
toxicity: float
|
|||
|
|
regime: str
|
|||
|
|
label: str # human-readable: "BTC_spread2x_depth30pct_tox0.3_crisis"
|
|||
|
|
|
|||
|
|
|
|||
|
|
class ScenarioLibrary:
|
|||
|
|
"""Sweeps the state space for Mode 1 (EXPLORE) coverage.
|
|||
|
|
|
|||
|
|
Produces grid points across spread × depth × toxicity × regime.
|
|||
|
|
These are ANCHORS that the ecology fills between and beyond.
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def __init__(
|
|||
|
|
self,
|
|||
|
|
symbols: Optional[Sequence[str]] = None,
|
|||
|
|
spread_mults: Optional[Sequence[float]] = None,
|
|||
|
|
depth_fracs: Optional[Sequence[float]] = None,
|
|||
|
|
tox_levels: Optional[Sequence[float]] = None,
|
|||
|
|
regimes: Optional[Sequence[str]] = None,
|
|||
|
|
) -> None:
|
|||
|
|
self.symbols = list(symbols or ASSET_PROFILES.keys())
|
|||
|
|
self.spread_mults = list(spread_mults or DEFAULT_SPREAD_MULTS)
|
|||
|
|
self.depth_fracs = list(depth_fracs or DEFAULT_DEPTH_FRACS)
|
|||
|
|
self.tox_levels = list(tox_levels or DEFAULT_TOXICITY)
|
|||
|
|
self.regimes = list(regimes or DEFAULT_REGIMES)
|
|||
|
|
|
|||
|
|
def sweep(self) -> List[SweepPoint]:
|
|||
|
|
"""Generate full grid sweep across all dimensions."""
|
|||
|
|
points = []
|
|||
|
|
for symbol in self.symbols:
|
|||
|
|
for spread in self.spread_mults:
|
|||
|
|
for depth in self.depth_fracs:
|
|||
|
|
for tox in self.tox_levels:
|
|||
|
|
for regime in self.regimes:
|
|||
|
|
label = f"{symbol}_s{spread}_d{depth}_t{tox}_r{regime}"
|
|||
|
|
points.append(SweepPoint(
|
|||
|
|
symbol=symbol,
|
|||
|
|
spread_mult=spread,
|
|||
|
|
depth_fraction=depth,
|
|||
|
|
toxicity=tox,
|
|||
|
|
regime=regime,
|
|||
|
|
label=label,
|
|||
|
|
))
|
|||
|
|
return points
|
|||
|
|
|
|||
|
|
def grid_size(self) -> int:
|
|||
|
|
return len(self.symbols) * len(self.spread_mults) * len(self.depth_fracs) * len(self.tox_levels) * len(self.regimes)
|
|||
|
|
|
|||
|
|
def summary(self) -> str:
|
|||
|
|
return (f"ScenarioLibrary: {len(self.symbols)} assets × "
|
|||
|
|
f"{len(self.spread_mults)} spreads × {len(self.depth_fracs)} depths × "
|
|||
|
|
f"{len(self.tox_levels)} tox × {len(self.regimes)} regimes = "
|
|||
|
|
f"{self.grid_size()} grid points")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def get_scenario_library(
|
|||
|
|
symbols: Optional[Sequence[str]] = None,
|
|||
|
|
) -> ScenarioLibrary:
|
|||
|
|
"""Get default scenario library for given symbols."""
|
|||
|
|
return ScenarioLibrary(symbols=symbols)
|