Files
QuantumTerminal/backend/regime_routes.py
T

109 lines
4.3 KiB
Python

# version: v2
"""
regime_routes.py — Consumer terminal HMM regime routes.
v2 — Lit up from stub. Serves the DC HMM-regime cache files produced by the
Data Center and synced into %APPDATA%\\QuantumTerminal\\cache\\:
/api/regime/macro → GLOBAL_macro_regime.json
(4-state macro HMM, daily, no ticker dim)
/api/regime/transitions → GLOBAL_regime_transitions.json
(macro transition analytics — aux, optional)
/api/regime/{ticker} → GLOBAL_<lower>_regime.json
(per-ticker slow D1 + fast M15; 404 for
tickers where DC's hmm_regime flag is off)
Pattern mirrors /api/historical_cones in data_server.py (v17): try the
sync client's in-memory store first so periodic-sync populated stores
are reused, then fall back to direct disk reads for both `{TICKER}_*`
and `GLOBAL_<lower>_*` naming.
Rule C1: display-only. No computation here — just reads cache JSON and
returns it unmodified.
"""
import json as _json
from fastapi import APIRouter, HTTPException
from pathlib import Path as _Path
def create_regime_router(cfg_manager=None, app=None) -> APIRouter:
router = APIRouter(tags=["regime"])
def _read_disk(*candidates):
"""Return first existing JSON file from candidates, else None."""
for p in candidates:
if p and p.is_file():
try:
return _json.loads(p.read_text(encoding="utf-8"))
except Exception as e:
# Corrupt file — surface as 500 to the caller.
raise HTTPException(500, f"corrupt regime file: {p.name} ({e})")
return None
def _cache_dir() -> _Path:
try:
from data_sync_client import _get_cache_dir
return _get_cache_dir()
except Exception:
return None
def _try_memory_store(canonical: str, data_type: str):
"""Ask the sync client's in-memory store. Returns None if absent."""
try:
from data_sync_client import get_sync_client
client = get_sync_client()
return client._get_data(canonical, data_type)
except Exception:
return None
@router.get("/api/regime/macro")
async def get_macro_regime():
"""Macro HMM regime — 4-state daily (GOLDILOCKS / REFLATION / STAGFLATION / DEFLATION)."""
cache = _cache_dir()
data = _read_disk(
cache / "GLOBAL_macro_regime.json" if cache else None,
cache / "macro_regime.json" if cache else None,
)
if data is None:
raise HTTPException(404, "macro regime not available")
return data
@router.get("/api/regime/transitions")
async def get_regime_transitions():
"""Macro regime transition analytics (7d/30d/90d probs, duration stats)."""
cache = _cache_dir()
data = _read_disk(
cache / "GLOBAL_regime_transitions.json" if cache else None,
cache / "regime_transitions.json" if cache else None,
)
if data is None:
raise HTTPException(404, "regime transitions not available")
return data
@router.get("/api/regime/{ticker}")
async def get_ticker_regime(ticker: str):
"""Per-ticker HMM regime — slow D1 (palette-adaptive) + fast M15 hazard.
404 is the normal response when the ticker's `hmm_regime` flag is off
in the Data Center's universe config — the consumer should treat 404
as "not enabled for this ticker" rather than an error.
"""
canonical = ticker.upper()
# (1) sync-client in-memory store (populated by sync_all / _load_all_from_cache)
mem = _try_memory_store(canonical, "regime")
if mem:
return mem
# (2) direct-disk fallback — handles both naming conventions.
cache = _cache_dir()
if cache is None:
raise HTTPException(503, "cache directory unavailable")
data = _read_disk(
cache / f"{canonical}_regime.json",
cache / f"GLOBAL_{canonical.lower()}_regime.json",
)
if data is None:
raise HTTPException(404, f"no regime data for {canonical}")
return data
return router