Files
Chan/ChanMacro/tests/test_regime.py
T
jackyu66gitandClaude 71951019fb chanmacro: Signal Expectancy Engine V1 — Market Memory System
Phase A-C complete: 4 core factors, regime detection, signal tracking, Bayesian expectancy.

chanmacro/ (32 files, ~4000 lines):
- models: 12 enums + 15 Pydantic v2 models (DateAwareModel, MarketStateVector, etc.)
- fetchers: OHLCV + Breadth (from data_provider) + Derivatives (new endpoint)
- scoring: Price Structure / Breadth (quantile buckets) / OI Matrix (5 discrete states) / Volatility Regime
- regime_detector: 3-state (TREND/RANGE/PANIC), factor-locked (Price+Breadth+Vol), versioned, 2-day confirmation
- expectancy: SignalTracker (record+outcomes), TimeDecay (half-life=180d), BayesianExpectancyEngine (Empirical Bayes, Leveled, SufficiencyGuard)
- validation: FactorValidator (IC/ICIR/Hit Ratio), RegimeValidator (MI/KL/ANOVA), TransitionValidator (stability)
- CLI: fetch|score|regime|track|backfill|expectancy|validate|serve
- tests: 52 passing (models, scoring, regime, expectancy)

data_provider:
- /api/derivatives endpoint: funding rate, OI, OI change, basis
- _derivatives storage: same persist pattern as K-line (merge→lock→snapshot→atomic write)
- background refresh every 60s

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-24 17:44:55 +08:00

110 lines
4.3 KiB
Python

"""Test regime detector and validation."""
import pytest
from datetime import date
import pandas as pd
import numpy as np
class TestRegimeDetector:
def test_detects_trend(self):
from regime_detector import RegimeDetector
from models import MarketRegime
d = RegimeDetector()
r = d.detect(75.0, 80.0, "NORMAL_VOL", date(2026, 6, 24))
assert r.regime == MarketRegime.TREND
assert r.confidence > 0.5
def test_detects_range(self):
from regime_detector import RegimeDetector
from models import MarketRegime
d = RegimeDetector()
r = d.detect(50.0, 50.0, "LOW_VOL", date(2026, 6, 24))
assert r.regime in (MarketRegime.RANGE, MarketRegime.TREND)
def test_detects_panic(self):
from regime_detector import RegimeDetector
from models import MarketRegime
d = RegimeDetector()
r = d.detect(15.0, 10.0, "EXPLOSIVE_VOL", date(2026, 6, 24))
assert r.regime == MarketRegime.PANIC
def test_2day_confirmation(self):
from regime_detector import RegimeDetector
from models import MarketRegime
d = RegimeDetector()
# Day 1: RANGE
r1 = d.detect(50.0, 50.0, "LOW_VOL", date(2026, 6, 24))
assert r1.regime == MarketRegime.RANGE # first run, no confirmation needed
# Day 2: still RANGE
r2 = d.detect(50.0, 50.0, "LOW_VOL", date(2026, 6, 25))
assert r2.regime == MarketRegime.RANGE
assert r2.confirmation_days == 2
def test_transition_needs_confirmation(self):
from regime_detector import RegimeDetector
from models import MarketRegime
d = RegimeDetector()
# Establish TREND
d.detect(75.0, 80.0, "NORMAL_VOL", date(2026, 6, 24))
d.detect(75.0, 80.0, "NORMAL_VOL", date(2026, 6, 25))
# Day 3: weak scores → raw best = RANGE, but TREND should persist
r3 = d.detect(35.0, 40.0, "NORMAL_VOL", date(2026, 6, 26))
# First day of pending transition — should still be TREND
assert r3.regime == MarketRegime.TREND
assert d.pending_regime is not None
def test_version_is_stored(self):
from regime_detector import RegimeDetector
d = RegimeDetector(regime_version="v1_price_breadth_vol")
r = d.detect(75.0, 80.0, "NORMAL_VOL", date(2026, 6, 24))
assert r.regime_version == "v1_price_breadth_vol"
def test_load_state(self, db_path):
from regime_detector import RegimeDetector
from models import MarketRegime
d = RegimeDetector()
d.load_state(db_path)
# DB has TREND for first 100 days, so most recent should load
assert d.current_regime is not None
def test_confidence_for_confirmed_regime(self):
"""Confidence should be for the confirmed regime, not raw best."""
from regime_detector import RegimeDetector
from models import MarketRegime
d = RegimeDetector()
# Establish TREND
d.detect(75.0, 80.0, "NORMAL_VOL", date(2026, 6, 24))
d.detect(75.0, 80.0, "NORMAL_VOL", date(2026, 6, 25))
# Now feed weak scores → raw best would be PANIC or RANGE
r = d.detect(15.0, 10.0, "EXPLOSIVE_VOL", date(2026, 6, 26))
# Should still report TREND (need 2 confirmations to switch)
assert r.regime == MarketRegime.TREND
class TestTransitionValidator:
def test_stable_regime_passes(self):
from validation.transition_validator import TransitionValidator
# Create stable regime sequence: long periods
seq = pd.Series(
["TREND"] * 50 + ["RANGE"] * 50 + ["PANIC"] * 40,
index=pd.date_range("2026-01-01", periods=140),
)
tv = TransitionValidator()
report = tv.validate(seq)
assert report.is_stable
assert report.avg_duration > 20
assert report.flip_rate < 0.05
def test_unstable_regime_fails(self):
from validation.transition_validator import TransitionValidator
# Create unstable sequence: flips every 2 days
seq = pd.Series(
["TREND", "TREND", "RANGE", "RANGE", "TREND", "TREND",
"PANIC", "PANIC", "RANGE", "RANGE"] * 5,
index=pd.date_range("2026-01-01", periods=50),
)
tv = TransitionValidator()
report = tv.validate(seq)
assert not report.is_stable
assert report.flip_rate > 0.15