Files
fxhnt/tests/integration/test_regime_execution.py
jgrusewski ae9f9965ad feat: regime-conditional execution (factory layer 5) — the loop closes
application/regime_execution.RegimeConditionalExecutor: reads specialists from the store, detects TODAY's
regime per market, activates only those whose regime is live, nets their latest positions into per-market
target weights (gross-capped). CLI book. The multi-strategy book is now regime-adaptive — trend specialists
run in calm uptrends, mean-reversion in panic, automatically in/out by regime. Live demo: as of 2026-06-08
all 3 discovered specialists are DORMANT (NQ in trend_up|HIGH_vol not its calm-trend regime; GC in calm
downtrend not its panic regime) -> book correctly FLAT. Discipline: don't trade out-of-regime. 21/21 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:40:47 +02:00

48 lines
2.7 KiB
Python

"""Regime-conditional execution: only specialists whose regime is live today are activated and netted."""
from __future__ import annotations
import datetime as dt
import numpy as np
from fxhnt.adapters.persistence import DuckDbAnalyticalStore, SqlStrategyStore
from fxhnt.application import RegimeConditionalExecutor
from fxhnt.config import Settings
from fxhnt.domain.factory import RegimeFit, StrategyRecord, StrategyStatus, strategy_id
from fxhnt.domain.models import AssetClass, Market, PriceSeries, StrategySpec
_NOW = dt.datetime(2026, 6, 9, tzinfo=dt.timezone.utc)
class FakeData:
name = "fake"
def fetch(self, market: Market, start=None, end=None) -> PriceSeries:
close = 100.0 * np.cumprod(1.0 + np.full(600, 0.0015)) # steady uptrend -> today's regime is trend_up
return PriceSeries(market=market, dates=tuple(f"2026-{i}" for i in range(600)), close=close)
def _spec(kind: str) -> StrategySpec:
return StrategySpec(kind=kind, params={"window": 100.0} if kind == "trend" else {"window": 20.0, "z": 1.0})
def test_only_live_regime_specialists_are_activated(tmp_path) -> None:
settings = Settings(analytical_path=str(tmp_path / "an.duckdb"), operational_dsn=f"sqlite:///{tmp_path / 's.db'}")
store = SqlStrategyStore(settings.operational_dsn)
t, m = _spec("trend"), _spec("mean_reversion")
# trend specialist for trend-up regimes (matches today); mean-rev specialist for a panic regime (does not)
store.upsert(StrategyRecord(strategy_id=strategy_id(["AAA"], AssetClass.FUTURE, t), markets=["AAA"],
asset_class=AssetClass.FUTURE, spec=t, discovered_at=_NOW, updated_at=_NOW,
regime_fits=[RegimeFit(regime="trend_up|low_vol", dsr=0.97, is_sharpe=1.3, oos_sharpe=0.9, passed=True),
RegimeFit(regime="trend_up|high_vol", dsr=0.96, is_sharpe=1.2, oos_sharpe=0.8, passed=True)]))
store.upsert(StrategyRecord(strategy_id=strategy_id(["AAA"], AssetClass.FUTURE, m), markets=["AAA"],
asset_class=AssetClass.FUTURE, spec=m, discovered_at=_NOW, updated_at=_NOW,
regime_fits=[RegimeFit(regime="trend_down|high_vol", dsr=0.96, is_sharpe=1.2, oos_sharpe=0.8, passed=True)]))
ex = RegimeConditionalExecutor(FakeData(), DuckDbAnalyticalStore(settings.analytical_path), store, settings)
rb = ex.assemble(status=StrategyStatus.DISCOVERED)
assert any("trend" in a.spec and a.market == "AAA" for a in rb.active) # trend is live
assert any(d.strategy_id.startswith("mean_reversion") for d in rb.dormant) # mean-rev is dormant
assert rb.target_weights.get("AAA", 0.0) > 0 # long in the uptrend