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>
This commit is contained in:
OpenSquared
2026-06-20 06:36:55 +02:00
parent 67546d39af
commit 4a1dc76d26
5 changed files with 692 additions and 3 deletions

View File

@@ -3,7 +3,8 @@ 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_latest_pnl_snapshot, save_pnl_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,
@@ -64,10 +65,27 @@ def pnl_latest():
@router.get("/pnl/snapshots")
def pnl_snapshots(limit: int = Query(default=48, ge=1, le=200)):
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: