feat: strategy builder
This commit is contained in:
@@ -1515,3 +1515,152 @@ export const useRejectAiTradeProposal = () => {
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['ai-trade-proposals'] }),
|
||||
})
|
||||
}
|
||||
|
||||
// ── Strategy Builder ────────────────────────────────────────────────────────
|
||||
|
||||
export type ChainRow = { strike: number; bid: number; ask: number; mid: number; last: number; iv: number; open_interest: number; volume: number }
|
||||
export type ChainExpiry = { expiry_date: string; days_to_expiry: number; calls: ChainRow[]; puts: ChainRow[] }
|
||||
export type ChainSlice = { symbol: string; proxy: string; spot: number; expiries: ChainExpiry[] }
|
||||
|
||||
export type ManualGridCell = { days_to_expiry: number; strike_pct: number; iv: number | null }
|
||||
|
||||
export type StrategyScenario = {
|
||||
symbol: string
|
||||
horizon_days: number
|
||||
spot_shock_pct: number
|
||||
iv_level_shift: number
|
||||
skew_tilt: number
|
||||
term_shift: number
|
||||
manual_grid?: ManualGridCell[]
|
||||
rate?: number
|
||||
n_expiries?: number
|
||||
}
|
||||
|
||||
export type StrategyLeg = {
|
||||
expiry_date: string
|
||||
days_to_expiry: number
|
||||
strike: number
|
||||
option_type: 'call' | 'put'
|
||||
position: 'long' | 'short'
|
||||
quantity: number
|
||||
}
|
||||
|
||||
export type Greeks = { delta: number; gamma: number; theta: number; vega: number }
|
||||
export type PayoffPoint = { underlying: number; pnl: number }
|
||||
|
||||
export type PriceCombo = {
|
||||
entry_cost: number
|
||||
entry_cost_mid: number
|
||||
scenario_value: number
|
||||
scenario_value_mid: number
|
||||
net_pnl: number
|
||||
broker_spread_cost: number
|
||||
max_gain: number | null
|
||||
max_loss: number | null
|
||||
bounded_risk: boolean
|
||||
greeks_now: Greeks
|
||||
greeks_scenario: Greeks
|
||||
net_delta_now: number
|
||||
net_delta_scenario: number
|
||||
at_expiry: PayoffPoint[]
|
||||
at_scenario: PayoffPoint[]
|
||||
spot: number
|
||||
scenario_spot: number
|
||||
proxy: string
|
||||
}
|
||||
|
||||
export type StrategyCandidate = {
|
||||
template_name: string
|
||||
legs: StrategyLeg[]
|
||||
score: number
|
||||
objective: string
|
||||
} & PriceCombo
|
||||
|
||||
export const useOptionChainSlice = (symbol: string, horizonDays: number, nExpiries = 3, enabled = true) =>
|
||||
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),
|
||||
enabled: enabled && !!symbol,
|
||||
staleTime: 30_000,
|
||||
retry: 1,
|
||||
})
|
||||
|
||||
export const usePriceStrategy = () =>
|
||||
useMutation({
|
||||
mutationFn: (body: { scenario: StrategyScenario; legs: StrategyLeg[] }) =>
|
||||
api.post<PriceCombo>('/strategy-builder/price', body).then(r => r.data),
|
||||
})
|
||||
|
||||
export type OptimizeConstraints = {
|
||||
max_legs: number
|
||||
delta_threshold: number
|
||||
max_loss_cap?: number | null
|
||||
objective: 'net_pnl' | 'return_on_risk' | 'prob_weighted'
|
||||
top_n?: number
|
||||
}
|
||||
|
||||
export const useOptimizeStrategy = () =>
|
||||
useMutation({
|
||||
mutationFn: (body: { scenario: StrategyScenario; constraints: OptimizeConstraints }) =>
|
||||
api.post<StrategyCandidate[]>('/strategy-builder/optimize', body).then(r => r.data),
|
||||
})
|
||||
|
||||
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
|
||||
manual_grid: ManualGridCell[]; created_at: string
|
||||
}
|
||||
|
||||
export type SavedStrategyRecord = {
|
||||
id: string; scenario_id: string | null; symbol: string; template_name: string; objective: string
|
||||
legs: StrategyLeg[]; entry_cost: number | null; max_gain: number | null; max_loss: number | null
|
||||
net_pnl_scenario: number | null; net_delta: number | null; notes: string; created_at: string
|
||||
}
|
||||
|
||||
export const useScenarios = (symbol?: string) =>
|
||||
useQuery<SavedScenario[]>({
|
||||
queryKey: ['strategy-scenarios', symbol],
|
||||
queryFn: () => api.get('/strategy-builder/scenarios', { params: symbol ? { symbol } : {} }).then(r => r.data),
|
||||
})
|
||||
|
||||
export const useSaveScenario = () => {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (body: StrategyScenario & { label?: string }) => api.post('/strategy-builder/scenarios', body).then(r => r.data),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['strategy-scenarios'] }),
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteScenario = () => {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => api.delete(`/strategy-builder/scenarios/${id}`).then(r => r.data),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['strategy-scenarios'] }),
|
||||
})
|
||||
}
|
||||
|
||||
export const useSavedStrategies = (symbol?: string) =>
|
||||
useQuery<SavedStrategyRecord[]>({
|
||||
queryKey: ['saved-strategies', symbol],
|
||||
queryFn: () => api.get('/strategy-builder/saved', { params: symbol ? { symbol } : {} }).then(r => r.data),
|
||||
})
|
||||
|
||||
export const useSaveStrategyRecord = () => {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (body: {
|
||||
scenario_id?: string | null; symbol: string; template_name?: string; objective?: string
|
||||
legs: StrategyLeg[]; entry_cost?: number | null; max_gain?: number | null; max_loss?: number | null
|
||||
net_pnl_scenario?: number | null; net_delta?: number | null; notes?: string
|
||||
}) => api.post('/strategy-builder/saved', body).then(r => r.data),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['saved-strategies'] }),
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteSavedStrategy = () => {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => api.delete(`/strategy-builder/saved/${id}`).then(r => r.data),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['saved-strategies'] }),
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user