from fastapi import APIRouter, Query, HTTPException from pydantic import BaseModel from services.var_service import ( compute_var, save_var_snapshot, get_var_snapshots, get_var_snapshot, get_latest_var_snapshot, get_pnl_snapshots, get_pnl_snapshot, get_latest_pnl_snapshot, save_pnl_snapshot, diff_pnl_snapshots, ) from services.var_scheduler import ( get_scheduler_status, restart_var_scheduler, restart_pnl_scheduler, ) from services.database import set_config router = APIRouter(prefix="/api/var", tags=["var"]) @router.get("/latest") def var_latest(): """Get most recent saved VaR snapshot (no recompute).""" snap = get_latest_var_snapshot() return {"snapshot": snap} @router.get("/snapshots") def var_snapshots(limit: int = Query(default=20, ge=1, le=100)): """List recent VaR snapshot summaries.""" return {"snapshots": get_var_snapshots(limit)} @router.get("/snapshots/{snapshot_id}") def var_snapshot_detail(snapshot_id: int): snap = get_var_snapshot(snapshot_id) if not snap: raise HTTPException(404, "Snapshot not found") return snap @router.post("/run-now") def var_run_now( confidence: float = Query(default=0.95, ge=0.90, le=0.99), horizon_days: int = Query(default=1, ge=1, le=30), lookback_days: int = Query(default=252, ge=60, le=504), default_iv: float = Query(default=0.20, ge=0.05, le=0.80), ): """Compute VaR now and save to DB. Returns the full result.""" result = compute_var( confidence=confidence, horizon_days=horizon_days, lookback_days=lookback_days, default_iv=default_iv, ) if "error" in result: raise HTTPException(400, result["error"]) snapshot_id = save_var_snapshot( result, confidence, horizon_days, lookback_days, default_iv ) return {**result, "snapshot_id": snapshot_id} # ─── PnL endpoints ─────────────────────────────────────────────────────────── @router.get("/pnl/latest") def pnl_latest(): return {"snapshot": get_latest_pnl_snapshot()} @router.get("/pnl/snapshots") def pnl_snapshots(limit: int = Query(default=100, ge=1, le=500)): return {"snapshots": get_pnl_snapshots(limit)} @router.get("/pnl/snapshots/{snapshot_id}") def pnl_snapshot_detail(snapshot_id: int): snap = get_pnl_snapshot(snapshot_id) if not snap: raise HTTPException(404, "Snapshot not found") return snap @router.get("/pnl/diff") def pnl_diff(a: int = Query(..., description="ID snapshot A (earlier)"), b: int = Query(..., description="ID snapshot B (later)")): try: return diff_pnl_snapshots(a, b) except ValueError as e: raise HTTPException(404, str(e)) @router.post("/pnl/run-now") def pnl_run_now(): try: row_id = save_pnl_snapshot() except Exception as e: raise HTTPException(500, str(e)) return {"snapshot_id": row_id, "snapshot": get_latest_pnl_snapshot()} # ─── Scheduler status / config ─────────────────────────────────────────────── @router.get("/scheduler/status") def scheduler_status(): return get_scheduler_status() class SchedulerConfig(BaseModel): var_enabled: bool | None = None var_hours: float | None = None pnl_enabled: bool | None = None pnl_hours: float | None = None @router.delete("/purge-all") def purge_all_var(): """Delete all VaR snapshots and PnL snapshots.""" from services.database import get_conn conn = get_conn() conn.execute("DELETE FROM var_snapshots") conn.execute("DELETE FROM pnl_snapshots") n = conn.total_changes conn.commit() conn.close() return {"deleted": n} @router.post("/scheduler/config") def scheduler_config(cfg: SchedulerConfig): if cfg.var_enabled is not None: set_config("var_scheduler_enabled", "true" if cfg.var_enabled else "false") if cfg.var_hours is not None: set_config("var_scheduler_hours", str(cfg.var_hours)) if cfg.pnl_enabled is not None: set_config("pnl_scheduler_enabled", "true" if cfg.pnl_enabled else "false") if cfg.pnl_hours is not None: set_config("pnl_scheduler_hours", str(cfg.pnl_hours)) # Restart scheduler threads to pick up new config restart_var_scheduler() restart_pnl_scheduler() return {"ok": True, "status": get_scheduler_status()}