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>
125 lines
3.7 KiB
Python
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()),
|
|
},
|
|
}
|