feat: strategy builder

This commit is contained in:
OpenSquared
2026-07-27 18:58:02 +02:00
parent ce09159bfb
commit 568414ca0c
18 changed files with 1315 additions and 63 deletions

View File

@@ -1677,11 +1677,14 @@ export type StrategyScenario = {
spot_shock_pct: number
iv_level_shift: number
skew_tilt: number
term_shift: number
term_slope_shift: number
rate_shock_bps?: number
manual_grid?: ManualGridCell[]
rate?: number
n_expiries?: number
contract_size?: number
dte_min?: number | null
dte_max?: number | null
}
export type StrategyLeg = {
@@ -1693,7 +1696,14 @@ export type StrategyLeg = {
quantity: number
}
export type Greeks = { delta: number; gamma: number; theta: number; vega: number }
export type Greeks = {
delta: number; gamma: number; theta: number; vega: number; rho: number
vanna: number; charm: number; vomma: number; veta: number; speed: number; color: number; zomma: number
}
export type VannaSimulation = {
spot_shock_pct: number; iv_shock_pts: number
delta_before: number; delta_after: number; delta_change: number
}
export type PayoffPoint = { underlying: number; pnl: number }
export type PriceCombo = {
@@ -1710,6 +1720,7 @@ export type PriceCombo = {
greeks_scenario: Greeks
net_delta_now: number
net_delta_scenario: number
vanna_simulation: VannaSimulation | null
at_expiry: PayoffPoint[]
at_scenario: PayoffPoint[]
spot: number
@@ -1722,12 +1733,30 @@ export type StrategyCandidate = {
legs: StrategyLeg[]
score: number
objective: string
greek_match_score?: number
final_rank_score?: number
} & PriceCombo
export const useOptionChainSlice = (symbol: string, horizonDays: number, nExpiries = 3, enabled = true) =>
export type GreekState = 'strong_negative' | 'negative' | 'neutral' | 'positive' | 'strong_positive' | 'free'
export type GreekTolerance = 'etroite' | 'normale' | 'large'
export type GreekTarget = { state: GreekState; tolerance: GreekTolerance; weight: number }
export type GreekProfile = { delta: GreekTarget; gamma: GreekTarget; theta: GreekTarget; vega: GreekTarget; rho: GreekTarget }
export const FREE_GREEK_TARGET: GreekTarget = { state: 'free', tolerance: 'normale', weight: 50 }
export const DEFAULT_GREEK_PROFILE: GreekProfile = {
delta: { ...FREE_GREEK_TARGET }, gamma: { ...FREE_GREEK_TARGET }, theta: { ...FREE_GREEK_TARGET },
vega: { ...FREE_GREEK_TARGET }, rho: { ...FREE_GREEK_TARGET },
}
export const useOptionChainSlice = (
symbol: string, horizonDays: number, nExpiries = 3, enabled = true,
dteMin?: number | null, dteMax?: number | null,
) =>
useQuery<ChainSlice>({
queryKey: ['strategy-builder-chain', symbol, horizonDays, nExpiries],
queryFn: () => api.get('/strategy-builder/chain', { params: { symbol, horizon_days: horizonDays, n_expiries: nExpiries } }).then(r => r.data),
queryKey: ['strategy-builder-chain', symbol, horizonDays, nExpiries, dteMin, dteMax],
queryFn: () => api.get('/strategy-builder/chain', {
params: { symbol, horizon_days: horizonDays, n_expiries: nExpiries, dte_min: dteMin ?? undefined, dte_max: dteMax ?? undefined },
}).then(r => r.data),
enabled: enabled && !!symbol,
staleTime: 30_000,
retry: 1,
@@ -1747,15 +1776,35 @@ export type OptimizeConstraints = {
top_n?: number
}
export type OptimizeResponse = { candidates: StrategyCandidate[]; warnings: string[] }
export const useOptimizeStrategy = () =>
useMutation({
mutationFn: (body: { scenario: StrategyScenario; constraints: OptimizeConstraints }) =>
api.post<StrategyCandidate[]>('/strategy-builder/optimize', body).then(r => r.data),
mutationFn: (body: { scenario: StrategyScenario; constraints: OptimizeConstraints; greek_profile?: GreekProfile }) =>
api.post<OptimizeResponse>('/strategy-builder/optimize', body).then(r => r.data),
})
// Mode 1 of the scenario/profile/constraints split — what Greek behavior the scenario
// alone already implies, before the user sets any explicit target (project memory:
// Strategy Builder Greeks plan, Phase 4).
export type SuggestedProfile = {
delta: GreekState; gamma: GreekState; theta: GreekState; vega: GreekState; rho: GreekState
rationale: string[]
reading: { spot_direction: string; spot_speed: string; iv_bucket: string }
}
export const useSuggestedProfile = (scenario: StrategyScenario, enabled: boolean) =>
useQuery<SuggestedProfile>({
queryKey: ['strategy-suggested-profile', scenario.symbol, scenario.spot_shock_pct, scenario.iv_level_shift, scenario.horizon_days],
queryFn: () => api.post('/strategy-builder/suggested-profile', scenario).then(r => r.data),
enabled: enabled && !!scenario.symbol,
staleTime: 30_000,
})
export type SavedScenario = {
id: string; symbol: string; label: string; horizon_days: number
spot_shock_pct: number; iv_level_shift: number; skew_tilt: number; term_shift: number
spot_shock_pct: number; iv_level_shift: number; skew_tilt: number; term_slope_shift: number
rate_shock_bps: number; dte_min: number | null; dte_max: number | null
manual_grid: ManualGridCell[]; created_at: string
}