feat: strategy builder

This commit is contained in:
OpenSquared
2026-07-18 23:35:17 +02:00
parent ca30e37942
commit 2b6b32958d
2 changed files with 25 additions and 9 deletions

View File

@@ -149,6 +149,16 @@ def optimize(req: OptimizeRequest):
) )
except ValueError as e: except ValueError as e:
raise HTTPException(status_code=404, detail=str(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 return results

View File

@@ -566,11 +566,17 @@ function SavedStrategiesLibrary({ symbol, onLoad }: { symbol: string; onLoad: (l
// ── Page ────────────────────────────────────────────────────────────────────── // ── Page ──────────────────────────────────────────────────────────────────────
export default function StrategyBuilder() { export default function StrategyBuilder() {
const [symbol, setSymbol] = useState('SPY') const [symbol, setSymbol] = useState('')
const [debouncedSymbol, setDebouncedSymbol] = useState('')
const [horizonDays, setHorizonDays] = useState(8) const [horizonDays, setHorizonDays] = useState(8)
const [scenario, setScenario] = useState<StrategyScenario>({ const [scenario, setScenario] = useState<StrategyScenario>({
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<StrategyLeg[]>([]) const [legs, setLegs] = useState<StrategyLeg[]>([])
const [constraints, setConstraints] = useState<OptimizeConstraints>({ const [constraints, setConstraints] = useState<OptimizeConstraints>({
max_legs: 4, delta_threshold: 0.15, max_loss_cap: null, objective: 'net_pnl', top_n: 20, 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() ])).sort()
const { data: chain, isLoading: chainLoading, isError: chainError, error: chainErrorObj, refetch: refetchChain, isFetching } = const { data: chain, isLoading: chainLoading, isError: chainError, error: chainErrorObj, refetch: refetchChain, isFetching } =
useOptionChainSlice(symbol, horizonDays, 3) useOptionChainSlice(debouncedSymbol, horizonDays, 3)
useEffect(() => { useEffect(() => {
setScenario(s => ({ ...s, symbol, horizon_days: horizonDays })) setScenario(s => ({ ...s, symbol: debouncedSymbol, horizon_days: horizonDays }))
}, [symbol, horizonDays]) }, [debouncedSymbol, horizonDays])
useEffect(() => { useEffect(() => {
if (chain && chain.expiries.length && legs.length === 0) { if (chain && chain.expiries.length && legs.length === 0) {
@@ -681,17 +687,17 @@ export default function StrategyBuilder() {
{chainError && ( {chainError && (
<div className="px-4 py-3 rounded border border-red-700/40 bg-red-900/10 text-xs text-red-300 flex items-center gap-2"> <div className="px-4 py-3 rounded border border-red-700/40 bg-red-900/10 text-xs text-red-300 flex items-center gap-2">
<AlertTriangle className="w-4 h-4" /> <AlertTriangle className="w-4 h-4" />
{(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.`}
</div> </div>
)} )}
<ScenarioPanel symbol={symbol} setSymbol={setSymbol} horizonDays={horizonDays} setHorizonDays={setHorizonDays} <ScenarioPanel symbol={symbol} setSymbol={setSymbol} horizonDays={horizonDays} setHorizonDays={setHorizonDays}
scenario={scenario} setScenario={setScenario} watchlistTickers={watchlistTickers} /> scenario={scenario} setScenario={setScenario} watchlistTickers={watchlistTickers} />
<ScenarioLibrary symbol={symbol} scenario={scenario} onLoad={handleLoadScenario} /> <ScenarioLibrary symbol={debouncedSymbol} scenario={scenario} onLoad={handleLoadScenario} />
<SavedStrategiesLibrary symbol={symbol} onLoad={(legs, templateName) => { setActiveTemplate(templateName); setLegs(legs) }} /> <SavedStrategiesLibrary symbol={debouncedSymbol} onLoad={(legs, templateName) => { setActiveTemplate(templateName); setLegs(legs) }} />
{chainLoading && <div className="card-sm text-xs text-slate-500">Chargement de la chaîne réelle ({symbol})…</div>} {chainLoading && <div className="card-sm text-xs text-slate-500">Chargement de la chaîne réelle ({debouncedSymbol})…</div>}
{chain && <ScenarioGrid chain={chain} spot={chain.spot} scenario={scenario} setScenario={setScenario} />} {chain && <ScenarioGrid chain={chain} spot={chain.spot} scenario={scenario} setScenario={setScenario} />}
{chain && <VolSurfaceHeatmap chain={chain} spot={chain.spot} />} {chain && <VolSurfaceHeatmap chain={chain} spot={chain.spot} />}