Files
OpenFin/backend/services/var_scheduler.py
OpenSquared b4f3089c58 feat: VaR/PnL schedulers + snapshots DB + page sur bouton
Backend:
- Tables var_snapshots + pnl_snapshots dans SQLite (contexte macro + prix tickers)
- var_service.py : save_var_snapshot, save_pnl_snapshot + fonctions get_*
- var_scheduler.py : threads APScheduler pour VaR (défaut 6h) et PnL (défaut 1h)
- router var.py : /run-now (POST compute+save), /latest, /snapshots, /pnl/run-now,
  /pnl/latest, /scheduler/status, /scheduler/config
- main.py : démarrage des deux schedulers au startup

Frontend:
- VaRAnalysis.tsx : plus d'auto-fetch ; charge le dernier snapshot DB au mount ;
  bouton "Calculer" → POST /run-now ; erreur backend = message clair ; historique
  de snapshots sélectionnables
- Config.tsx : section "Schedulers VaR & PnL" dans l'onglet cycle avec toggle
  enable/disable, intervalle, et boutons "Snapshot maintenant"

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

125 lines
3.7 KiB
Python

"""Periodic scheduler for VaR and PnL snapshots."""
from __future__ import annotations
import logging
import threading
from datetime import datetime, timedelta
logger = logging.getLogger(__name__)
_var_thread: threading.Thread | None = None
_pnl_thread: threading.Thread | None = None
_var_stop = threading.Event()
_pnl_stop = threading.Event()
def _var_loop(stop: threading.Event):
while not stop.wait(0):
from .database import get_config
try:
enabled = (get_config("var_scheduler_enabled") or "false").lower() == "true"
hours = float(get_config("var_scheduler_hours") or "6")
except Exception:
enabled, hours = False, 6.0
if not enabled:
stop.wait(timeout=300)
continue
# Run snapshot
try:
from .var_service import compute_var, save_var_snapshot
result = compute_var()
if "error" not in result:
save_var_snapshot(result, confidence=0.95, horizon_days=1,
lookback_days=252, default_iv=0.20)
logger.info("[VaR Scheduler] Snapshot saved")
else:
logger.warning(f"[VaR Scheduler] Compute failed: {result['error']}")
except Exception as e:
logger.error(f"[VaR Scheduler] Exception: {e}")
stop.wait(timeout=hours * 3600)
def _pnl_loop(stop: threading.Event):
while not stop.wait(0):
from .database import get_config
try:
enabled = (get_config("pnl_scheduler_enabled") or "false").lower() == "true"
hours = float(get_config("pnl_scheduler_hours") or "1")
except Exception:
enabled, hours = False, 1.0
if not enabled:
stop.wait(timeout=120)
continue
try:
from .var_service import save_pnl_snapshot
save_pnl_snapshot()
logger.info("[PnL Scheduler] Snapshot saved")
except Exception as e:
logger.error(f"[PnL Scheduler] Exception: {e}")
stop.wait(timeout=hours * 3600)
def start_var_scheduler():
global _var_thread, _var_stop
_var_stop.clear()
if _var_thread and _var_thread.is_alive():
return
_var_thread = threading.Thread(target=_var_loop, args=(_var_stop,),
name="var-scheduler", daemon=True)
_var_thread.start()
logger.info("[VaR Scheduler] Started")
def start_pnl_scheduler():
global _pnl_thread, _pnl_stop
_pnl_stop.clear()
if _pnl_thread and _pnl_thread.is_alive():
return
_pnl_thread = threading.Thread(target=_pnl_loop, args=(_pnl_stop,),
name="pnl-scheduler", daemon=True)
_pnl_thread.start()
logger.info("[PnL Scheduler] Started")
def stop_var_scheduler():
_var_stop.set()
def stop_pnl_scheduler():
_pnl_stop.set()
def restart_var_scheduler():
stop_var_scheduler()
import time; time.sleep(0.2)
start_var_scheduler()
def restart_pnl_scheduler():
stop_pnl_scheduler()
import time; time.sleep(0.2)
start_pnl_scheduler()
def get_scheduler_status() -> dict:
from .database import get_config
return {
"var": {
"enabled": (get_config("var_scheduler_enabled") or "false").lower() == "true",
"hours": float(get_config("var_scheduler_hours") or "6"),
"alive": bool(_var_thread and _var_thread.is_alive()),
},
"pnl": {
"enabled": (get_config("pnl_scheduler_enabled") or "false").lower() == "true",
"hours": float(get_config("pnl_scheduler_hours") or "1"),
"alive": bool(_pnl_thread and _pnl_thread.is_alive()),
},
}