feat: strategy builder
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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} />}
|
||||||
|
|||||||
Reference in New Issue
Block a user