109 lines
4.3 KiB
Python
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
|