feat: strategy builder

This commit is contained in:
OpenSquared
2026-07-19 09:39:09 +02:00
parent e7247d4c4c
commit 3417bb6075
6 changed files with 142 additions and 62 deletions

View File

@@ -1534,6 +1534,7 @@ export type StrategyScenario = {
manual_grid?: ManualGridCell[]
rate?: number
n_expiries?: number
contract_size?: number
}
export type StrategyLeg = {

View File

@@ -584,6 +584,7 @@ export default function StrategyBuilder() {
const [horizonDays, setHorizonDays] = useState(8)
const [scenario, setScenario] = useState<StrategyScenario>({
symbol: '', horizon_days: 8, spot_shock_pct: 0, iv_level_shift: 0, skew_tilt: 0, term_shift: 0, manual_grid: [],
contract_size: 100_000,
})
// Chain lookup only commits on blur/Enter/datalist-pick, never mid-keystroke — typing
@@ -662,11 +663,11 @@ export default function StrategyBuilder() {
const handleLoadScenario = (s: SavedScenario) => {
setSymbol(s.symbol)
setHorizonDays(s.horizon_days)
setScenario({
setScenario(prev => ({
symbol: s.symbol, horizon_days: s.horizon_days, spot_shock_pct: s.spot_shock_pct,
iv_level_shift: s.iv_level_shift, skew_tilt: s.skew_tilt, term_shift: s.term_shift,
manual_grid: s.manual_grid,
})
manual_grid: s.manual_grid, contract_size: prev.contract_size,
}))
}
const handleSaveStrategy = () => {
@@ -721,13 +722,24 @@ export default function StrategyBuilder() {
<div className="card space-y-3">
<div className="flex items-center justify-between">
<div className="stat-label">Jambes (1-4) Spot {chain.spot}</div>
<button
onClick={addLeg}
disabled={legs.length >= 4}
className="flex items-center gap-1 text-xs bg-blue-600 hover:bg-blue-500 disabled:opacity-40 text-white px-2.5 py-1 rounded"
>
<Plus className="w-3.5 h-3.5" /> Ajouter une jambe
</button>
<div className="flex items-center gap-3">
<label className="flex items-center gap-1.5 text-xs text-slate-400" title="Notionnel par contrat (ex. 100 000 = 1 lot standard EURUSD). S'applique à chaque jambe, multiplié par sa quantité.">
Nominal (USD)
<input
type="number" step={1000} min={1}
value={scenario.contract_size ?? 100_000}
onChange={(e) => setScenario(s => ({ ...s, contract_size: parseFloat(e.target.value) || 100_000 }))}
className="w-28 bg-dark-700 border border-slate-700/50 rounded px-2 py-1 text-slate-200"
/>
</label>
<button
onClick={addLeg}
disabled={legs.length >= 4}
className="flex items-center gap-1 text-xs bg-blue-600 hover:bg-blue-500 disabled:opacity-40 text-white px-2.5 py-1 rounded"
>
<Plus className="w-3.5 h-3.5" /> Ajouter une jambe
</button>
</div>
</div>
<div className="space-y-2">
{legs.map((leg, i) => (