Files
OpenFin/backend/routers/var.py
OpenSquared 4a1dc76d26 feat: page Historique Positions + diff entre 2 snapshots PnL
Backend:
- get_pnl_snapshot(id) : détail complet d'un snapshot avec trades parsés
- diff_pnl_snapshots(a, b) : diff positions entre deux snapshots (nouvelles /
  fermées / évolution PnL par position + delta portfolio)
- GET /api/var/pnl/snapshots/{id} : détail snapshot
- GET /api/var/pnl/diff?a=&b= : calcul du diff

Frontend PositionHistory.tsx :
- Timeline scrollable des snapshots avec sparkline PnL
- Clic snapshot → détail des positions à ce moment (prix entrée, prix actuel,
  PnL %, PnL €, régime macro)
- Boutons A/B par snapshot → sélection de deux points à comparer
- Vue diff A→B : nouvelles positions, fermées, évolution PnL par trade,
  delta portfolio (capital, PnL %, PnL €)
- Route /position-history + nav sidebar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 06:36:55 +02:00

126 lines
4.1 KiB
Python

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.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()}