From 2b6b32958ddef0c44383ac17051644fad850efc1 Mon Sep 17 00:00:00 2001 From: OpenSquared Date: Sat, 18 Jul 2026 23:35:17 +0200 Subject: [PATCH] feat: strategy builder --- backend/routers/strategy_builder.py | 10 ++++++++++ frontend/src/pages/StrategyBuilder.tsx | 24 +++++++++++++++--------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/backend/routers/strategy_builder.py b/backend/routers/strategy_builder.py index 9931b4f..4549229 100644 --- a/backend/routers/strategy_builder.py +++ b/backend/routers/strategy_builder.py @@ -149,6 +149,16 @@ def optimize(req: OptimizeRequest): ) except ValueError as e: raise HTTPException(status_code=404, detail=str(e)) + except Exception as e: + import traceback + from services.database import log_system_event + tb = traceback.format_exc() + log_system_event( + level="ERROR", source="strategy_optimizer", + message=f"Optimize failed for {req.scenario.symbol}: {e}", + ticker=req.scenario.symbol, details={"error": str(e), "traceback": tb}, + ) + raise HTTPException(status_code=500, detail=f"Erreur optimiseur: {e} (détail complet dans System Logs)") return results diff --git a/frontend/src/pages/StrategyBuilder.tsx b/frontend/src/pages/StrategyBuilder.tsx index d43520b..57d3ac0 100644 --- a/frontend/src/pages/StrategyBuilder.tsx +++ b/frontend/src/pages/StrategyBuilder.tsx @@ -566,11 +566,17 @@ function SavedStrategiesLibrary({ symbol, onLoad }: { symbol: string; onLoad: (l // ── Page ────────────────────────────────────────────────────────────────────── export default function StrategyBuilder() { - const [symbol, setSymbol] = useState('SPY') + const [symbol, setSymbol] = useState('') + const [debouncedSymbol, setDebouncedSymbol] = useState('') const [horizonDays, setHorizonDays] = useState(8) const [scenario, setScenario] = useState({ - symbol: 'SPY', horizon_days: 8, spot_shock_pct: 0, iv_level_shift: 0, skew_tilt: 0, term_shift: 0, manual_grid: [], + symbol: '', horizon_days: 8, spot_shock_pct: 0, iv_level_shift: 0, skew_tilt: 0, term_shift: 0, manual_grid: [], }) + + useEffect(() => { + const t = setTimeout(() => setDebouncedSymbol(symbol.trim()), 500) + return () => clearTimeout(t) + }, [symbol]) const [legs, setLegs] = useState([]) const [constraints, setConstraints] = useState({ max_legs: 4, delta_threshold: 0.15, max_loss_cap: null, objective: 'net_pnl', top_n: 20, @@ -585,11 +591,11 @@ export default function StrategyBuilder() { ])).sort() const { data: chain, isLoading: chainLoading, isError: chainError, error: chainErrorObj, refetch: refetchChain, isFetching } = - useOptionChainSlice(symbol, horizonDays, 3) + useOptionChainSlice(debouncedSymbol, horizonDays, 3) useEffect(() => { - setScenario(s => ({ ...s, symbol, horizon_days: horizonDays })) - }, [symbol, horizonDays]) + setScenario(s => ({ ...s, symbol: debouncedSymbol, horizon_days: horizonDays })) + }, [debouncedSymbol, horizonDays]) useEffect(() => { if (chain && chain.expiries.length && legs.length === 0) { @@ -681,17 +687,17 @@ export default function StrategyBuilder() { {chainError && (
- {(chainErrorObj as any)?.response?.data?.detail ?? `Chaîne d'options indisponible pour ${symbol}. Essayez un autre symbole.`} + {(chainErrorObj as any)?.response?.data?.detail ?? `Chaîne d'options indisponible pour ${debouncedSymbol}. Essayez un autre symbole.`}
)} - - { setActiveTemplate(templateName); setLegs(legs) }} /> + + { setActiveTemplate(templateName); setLegs(legs) }} /> - {chainLoading &&
Chargement de la chaîne réelle ({symbol})…
} + {chainLoading &&
Chargement de la chaîne réelle ({debouncedSymbol})…
} {chain && } {chain && }