Files
fxhnt/tests/unit/test_regime.py
jgrusewski 72cdbc9eec feat: regime layer — the foundation of the agentic strategy factory (ADR 0002)
ADR 0002 designs the real vision: a standing fleet of agents continuously breeding regime-specialist
strategies into an adaptively-allocated book, with multiple-testing-at-fleet-scale as the constraint the
whole architecture is built around (structured search + regime-conditioning + forward-validation +
global-N accounting = the moat). domain/regime: VolTrendClassifier (causal trend x volatility state) +
evaluate_by_regime (judge a strategy WITHIN each regime). Demo on real NQ: trend(200) is MARGINAL on the
full sample but a clear OOS-confirmed SPECIALIST in trend_up|low_vol (DSR 0.984, OOS +0.93), correctly
rejected in range/high-vol regimes — the architecture's thesis proven. 16/16 tests.

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

33 lines
1.5 KiB
Python

"""Regime layer: causal classification + regime-conditional evaluation."""
from __future__ import annotations
import numpy as np
from fxhnt.domain.models import AssetClass, Market, PriceSeries
from fxhnt.domain.regime import WARMUP, VolTrendClassifier, evaluate_by_regime
def _prices(close: np.ndarray) -> PriceSeries:
return PriceSeries(market=Market(symbol="X", asset_class=AssetClass.FUTURE),
dates=tuple(str(i) for i in range(len(close))), close=np.asarray(close, float))
def test_classifier_is_causal_and_labels_an_uptrend() -> None:
close = 100.0 * np.cumprod(1.0 + np.full(400, 0.002)) # steady uptrend
labels = VolTrendClassifier(trend_window=100).classify(_prices(close))
assert len(labels) == 400
assert all(lbl == WARMUP for lbl in labels[:100]) # warmup region is unlabelled
post = labels[150:]
assert sum(lbl.startswith("trend_up") for lbl in post) > 0.8 * len(post)
def test_evaluate_by_regime_finds_the_specialist_regime() -> None:
n = 600
labels = tuple("trend_up|low_vol" if i % 2 == 0 else "range|low_vol" for i in range(n))
drift = np.where(np.arange(n) % 2 == 0, 0.001, 0.0) # edge only in the trend regime
r = drift + np.random.default_rng(0).normal(0, 0.0005, n)
verdicts = evaluate_by_regime(r, labels, min_obs=100)
assert {"trend_up|low_vol", "range|low_vol"} <= set(verdicts)
# the regime that carries the drift is judged stronger than the flat one
assert verdicts["trend_up|low_vol"].dsr > verdicts["range|low_vol"].dsr