- DB: 9 new columns on trade_entry_prices (status, closed_at, close_reason,
close_note, pnl_realized, close_price, target_pct, stop_loss_pct, signal_threshold)
via ALTER TABLE migration; close_trade(), get_closed_trades(),
update_trade_exit_params() helpers; exit_defaults config key
- Backend: PATCH /trades/{id}/close, PATCH /trades/{id}/exit-params,
GET/PUT /exit-defaults, GET /closed-trades with win-rate/avg-PnL stats;
trade-mtm now computes alert_type (target_reached|stop_loss) per trade
- Journal: new "Fermés" tab with closed trades table + stats banner (win rate,
avg PnL, total PnL, best trade); open trades show Cible/Stop progress bar +
🎯/🛑 alert badges + 1-click close modal (price, reason, note)
- Config: new "Paramètres de sortie" panel — target_pct, stop_loss_pct,
signal_reversal_mode, signal_reversal_threshold with live sliders
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
816 lines
28 KiB
TypeScript
816 lines
28 KiB
TypeScript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
import { useEffect, useRef } from 'react'
|
|
import axios from 'axios'
|
|
import type {
|
|
Quote, GeoNews, GeoRiskScore, PatternMatch, TradeIdea,
|
|
EconomicEvent, HistoricalCandle, BacktestResult
|
|
} from '../types'
|
|
|
|
export const api = axios.create({ baseURL: '/api' })
|
|
|
|
// ── Market ────────────────────────────────────────────────────────────────────
|
|
export const useAllQuotes = () =>
|
|
useQuery<Record<string, Quote[]>>({
|
|
queryKey: ['quotes'],
|
|
queryFn: () => api.get('/market/quotes').then(r => r.data),
|
|
refetchInterval: 60_000,
|
|
})
|
|
|
|
export const useHistory = (symbol: string, period = '1y', interval = '1d') =>
|
|
useQuery<HistoricalCandle[]>({
|
|
queryKey: ['history', symbol, period, interval],
|
|
queryFn: () =>
|
|
api.get(`/market/history/${encodeURIComponent(symbol)}`, { params: { period, interval } }).then(r => r.data),
|
|
enabled: !!symbol,
|
|
})
|
|
|
|
// ── Geo ───────────────────────────────────────────────────────────────────────
|
|
export const useGeoNews = () =>
|
|
useQuery<GeoNews[]>({
|
|
queryKey: ['geo-news'],
|
|
queryFn: () => api.get('/geo/news').then(r => r.data),
|
|
refetchInterval: 3_600_000,
|
|
})
|
|
|
|
export const useGeoRiskScore = () =>
|
|
useQuery<GeoRiskScore>({
|
|
queryKey: ['geo-risk-score'],
|
|
queryFn: () => api.get('/geo/risk-score').then(r => r.data),
|
|
refetchInterval: 3_600_000,
|
|
})
|
|
|
|
export const usePatternMatches = () =>
|
|
useQuery<PatternMatch[]>({
|
|
queryKey: ['pattern-matches'],
|
|
queryFn: () => api.get('/geo/pattern-matches').then(r => r.data),
|
|
})
|
|
|
|
export const usePatternRelevance = (days: number) =>
|
|
useQuery({
|
|
queryKey: ['pattern-relevance', days],
|
|
queryFn: () => api.get('/geo/pattern-relevance', { params: { days } }).then(r => r.data),
|
|
staleTime: 5 * 60_000,
|
|
})
|
|
|
|
export const useTradeIdeas = () =>
|
|
useQuery<TradeIdea[]>({
|
|
queryKey: ['trade-ideas'],
|
|
queryFn: () => api.get('/geo/trade-ideas').then(r => r.data),
|
|
})
|
|
|
|
export const useCalendar = () =>
|
|
useQuery<EconomicEvent[]>({
|
|
queryKey: ['calendar'],
|
|
queryFn: () => api.get('/geo/calendar').then(r => r.data),
|
|
})
|
|
|
|
// ── Options ───────────────────────────────────────────────────────────────────
|
|
export const useIvSurface = (symbol: string) =>
|
|
useQuery({
|
|
queryKey: ['iv-surface', symbol],
|
|
queryFn: () => api.get('/options/iv-surface', { params: { symbol } }).then(r => r.data),
|
|
enabled: !!symbol,
|
|
})
|
|
|
|
export const usePnlCurve = (params: {
|
|
symbol: string; strike: number; expiry_days: number
|
|
option_type: string; quantity: number; premium_paid: number
|
|
}) =>
|
|
useQuery({
|
|
queryKey: ['pnl-curve', params],
|
|
queryFn: () => api.get('/options/pnl-curve', { params }).then(r => r.data),
|
|
enabled: !!params.symbol && !!params.strike && !!params.premium_paid,
|
|
})
|
|
|
|
// ── Backtest ──────────────────────────────────────────────────────────────────
|
|
export const useBacktest = () =>
|
|
useMutation<BacktestResult, Error, Record<string, unknown>>({
|
|
mutationFn: (data) => api.post('/backtest/run', data).then(r => r.data),
|
|
})
|
|
|
|
// ── AI ────────────────────────────────────────────────────────────────────────
|
|
export const useAiStatus = () =>
|
|
useQuery({
|
|
queryKey: ['ai-status'],
|
|
queryFn: () => api.get('/ai/status').then(r => r.data),
|
|
refetchInterval: 30_000,
|
|
})
|
|
|
|
export const useAiTopIdeas = () =>
|
|
useQuery({
|
|
queryKey: ['ai-top-ideas'],
|
|
queryFn: () => api.get('/ai/top-ideas').then(r => r.data),
|
|
enabled: false,
|
|
retry: false,
|
|
})
|
|
|
|
export const useAnalyzeSpeech = () =>
|
|
useMutation({
|
|
mutationFn: (data: { text: string; speaker?: string }) =>
|
|
api.post('/ai/analyze-speech', data).then(r => r.data),
|
|
})
|
|
|
|
export const useEvaluatePattern = () =>
|
|
useMutation({
|
|
mutationFn: (pattern: Record<string, unknown>) =>
|
|
api.post('/ai/evaluate-pattern', { pattern }).then(r => r.data),
|
|
})
|
|
|
|
export const useSuggestPattern = () =>
|
|
useMutation({
|
|
mutationFn: (context: string) =>
|
|
api.post('/ai/suggest-pattern', { context }).then(r => r.data),
|
|
})
|
|
|
|
export const useAiTopIdeasRefetch = () =>
|
|
useMutation({
|
|
mutationFn: () => api.get('/ai/top-ideas').then(r => r.data),
|
|
})
|
|
|
|
// ── Portfolio ─────────────────────────────────────────────────────────────────
|
|
export const usePortfolioPositions = (status = 'open') =>
|
|
useQuery({
|
|
queryKey: ['portfolio', status],
|
|
queryFn: () => api.get('/portfolio/positions', { params: { status } }).then(r => r.data),
|
|
refetchInterval: status === 'open' ? 60_000 : false,
|
|
})
|
|
|
|
export const usePortfolioSummary = () =>
|
|
useQuery({
|
|
queryKey: ['portfolio-summary'],
|
|
queryFn: () => api.get('/portfolio/summary').then(r => r.data),
|
|
refetchInterval: 60_000,
|
|
})
|
|
|
|
export const usePnlHistory = () =>
|
|
useQuery({
|
|
queryKey: ['pnl-history'],
|
|
queryFn: () => api.get('/portfolio/pnl-history').then(r => r.data),
|
|
})
|
|
|
|
export const useAddPosition = () => {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (data: Record<string, unknown>) =>
|
|
api.post('/portfolio/add', data).then(r => r.data),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ['portfolio'] })
|
|
qc.invalidateQueries({ queryKey: ['portfolio-summary'] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export const useClosePosition = () => {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: ({ id, close_value }: { id: string; close_value: number }) =>
|
|
api.post(`/portfolio/close/${id}`, { close_value }).then(r => r.data),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ['portfolio'] })
|
|
qc.invalidateQueries({ queryKey: ['portfolio-summary'] })
|
|
qc.invalidateQueries({ queryKey: ['pnl-history'] })
|
|
},
|
|
})
|
|
}
|
|
|
|
// ── Config ────────────────────────────────────────────────────────────────────
|
|
export const useConfig = () =>
|
|
useQuery({
|
|
queryKey: ['config'],
|
|
queryFn: () => api.get('/config/').then(r => r.data),
|
|
})
|
|
|
|
export const useSources = () =>
|
|
useQuery({
|
|
queryKey: ['sources'],
|
|
queryFn: () => api.get('/config/sources').then(r => r.data),
|
|
})
|
|
|
|
export const useUpdateSources = () => {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (sources: Record<string, unknown>) =>
|
|
api.put('/config/sources', { sources }).then(r => r.data),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['sources'] }),
|
|
})
|
|
}
|
|
|
|
export const useUpdateApiKeys = () => {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (keys: Record<string, string>) =>
|
|
api.put('/config/api-keys', keys).then(r => r.data),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ['config'] })
|
|
qc.invalidateQueries({ queryKey: ['ai-status'] })
|
|
},
|
|
})
|
|
}
|
|
|
|
// ── AI Pattern Scoring ────────────────────────────────────────────────────────
|
|
export const useScorePatterns = () => {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (params: { top_n?: number; category_filter?: string; template?: string }) =>
|
|
api.post('/ai/score-patterns', params).then(r => r.data),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['last-scores'] }),
|
|
})
|
|
}
|
|
|
|
export const useLastScores = () =>
|
|
useQuery({
|
|
queryKey: ['last-scores'],
|
|
queryFn: () => api.get('/ai/last-scores').then(r => r.data),
|
|
staleTime: Infinity,
|
|
})
|
|
|
|
export const useSuggestNewPatterns = () =>
|
|
useMutation({
|
|
mutationFn: () => api.post('/ai/suggest-new-patterns').then(r => r.data),
|
|
})
|
|
|
|
export const useAnalysisConfig = () =>
|
|
useQuery({
|
|
queryKey: ['analysis-config'],
|
|
queryFn: () => api.get('/config/analysis').then(r => r.data),
|
|
})
|
|
|
|
export const useSaveAnalysisConfig = () => {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (cfg: { top_n?: number; category_filter?: string; template?: string }) =>
|
|
api.put('/config/analysis', cfg).then(r => r.data),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['analysis-config'] }),
|
|
})
|
|
}
|
|
|
|
// ── Patterns ──────────────────────────────────────────────────────────────────
|
|
export const useAllPatterns = () =>
|
|
useQuery({
|
|
queryKey: ['all-patterns'],
|
|
queryFn: () => api.get('/patterns/all').then(r => r.data),
|
|
})
|
|
|
|
export const usePatternSimilarity = () =>
|
|
useQuery({
|
|
queryKey: ['pattern-similarity'],
|
|
queryFn: () => api.get('/ai/pattern-similarity').then(r => r.data),
|
|
staleTime: 5 * 60_000,
|
|
})
|
|
|
|
export const useMacroRegime = () => {
|
|
const qc = useQueryClient()
|
|
const query = useQuery({
|
|
queryKey: ['macro-regime'],
|
|
queryFn: () => api.get('/market/macro-regime').then(r => r.data),
|
|
staleTime: 10 * 60 * 1000,
|
|
refetchInterval: 15 * 60 * 1000,
|
|
})
|
|
const forceRefetch = async () => {
|
|
const fresh = await api.get('/market/macro-regime?force=true').then(r => r.data)
|
|
qc.setQueryData(['macro-regime'], fresh)
|
|
return fresh
|
|
}
|
|
return { ...query, forceRefetch }
|
|
}
|
|
|
|
export const useSavePattern = () => {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (pattern: Record<string, unknown>) =>
|
|
api.post('/patterns/custom', pattern).then(r => r.data),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['all-patterns'] }),
|
|
})
|
|
}
|
|
|
|
export const useDeletePattern = () => {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (id: string) =>
|
|
api.delete(`/patterns/custom/${id}`).then(r => r.data),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['all-patterns'] }),
|
|
})
|
|
}
|
|
|
|
export const useTogglePattern = () => {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (id: string) =>
|
|
api.put(`/patterns/toggle/${id}`).then(r => r.data),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['all-patterns'] }),
|
|
})
|
|
}
|
|
|
|
// ── Auto-Cycle ───────────────────────────────────────────────────────────────
|
|
|
|
export const useCycleStatus = () =>
|
|
useQuery({
|
|
queryKey: ['cycle-status'],
|
|
queryFn: () => api.get('/cycle/status').then(r => r.data),
|
|
staleTime: 5_000,
|
|
// Poll every 5s while a cycle is running, every 30s otherwise
|
|
refetchInterval: (query) => ((query.state.data as any)?.running ? 5_000 : 30_000),
|
|
})
|
|
|
|
export const useCycleHistory = (limit = 20) =>
|
|
useQuery({
|
|
queryKey: ['cycle-history', limit],
|
|
queryFn: () => api.get(`/cycle/history?limit=${limit}`).then(r => r.data),
|
|
staleTime: 60_000,
|
|
})
|
|
|
|
export const useUpdateCycleConfig = () => {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (cfg: { enabled?: boolean; interval_hours?: number; similarity_threshold?: number; min_ev_threshold?: number; min_score_threshold?: number; journal_retention_days?: number; maturity_threshold_pct?: number }) =>
|
|
api.post('/cycle/config', cfg).then(r => r.data),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['cycle-status'] }),
|
|
})
|
|
}
|
|
|
|
/** Keys to refresh once a cycle finishes — covers cockpit, macro, journal */
|
|
export const CYCLE_REFRESH_KEYS = [
|
|
['macro-regime'],
|
|
['last-scores'],
|
|
['all-patterns'],
|
|
['cycle-history'],
|
|
['cycle-status'],
|
|
['journal-summary'],
|
|
['journal-mtm'],
|
|
['journal-geo'],
|
|
['journal-macro'],
|
|
['geo-risk-score'],
|
|
['pattern-matches'],
|
|
]
|
|
|
|
export const useTriggerCycle = () => {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: () => api.post('/cycle/trigger').then(r => r.data),
|
|
onSuccess: () => {
|
|
// Immediate status refresh so the spinner shows
|
|
qc.invalidateQueries({ queryKey: ['cycle-status'] })
|
|
},
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Mount this hook once at a high level (e.g. CyclesSection or App).
|
|
* It watches cycle-status and, when a running cycle finishes, refreshes
|
|
* all cockpit + journal queries so the UI reflects the new scores/regime.
|
|
*/
|
|
export const useCycleWatcher = () => {
|
|
const qc = useQueryClient()
|
|
const { data: statusData } = useCycleStatus()
|
|
const wasRunning = useRef(false)
|
|
|
|
useEffect(() => {
|
|
const running = (statusData as any)?.running ?? false
|
|
if (running) {
|
|
wasRunning.current = true
|
|
} else if (wasRunning.current) {
|
|
// Transition: was running → now done → refresh everything
|
|
wasRunning.current = false
|
|
CYCLE_REFRESH_KEYS.forEach(key => qc.invalidateQueries({ queryKey: key }))
|
|
}
|
|
}, [(statusData as any)?.running])
|
|
}
|
|
|
|
// ── Journal de Bord ──────────────────────────────────────────────────────────
|
|
|
|
export const useJournalSummary = () =>
|
|
useQuery({
|
|
queryKey: ['journal-summary'],
|
|
queryFn: () => api.get('/journal/summary').then(r => r.data),
|
|
staleTime: 2 * 60_000,
|
|
})
|
|
|
|
export const useMacroHistory = (days = 15) =>
|
|
useQuery({
|
|
queryKey: ['journal-macro', days],
|
|
queryFn: () => api.get(`/journal/macro-history?days=${days}`).then(r => r.data),
|
|
staleTime: 5 * 60_000,
|
|
})
|
|
|
|
export const useGeoHistory = (days = 30) =>
|
|
useQuery({
|
|
queryKey: ['journal-geo', days],
|
|
queryFn: () => api.get(`/journal/geo-history?days=${days}`).then(r => r.data),
|
|
staleTime: 5 * 60_000,
|
|
})
|
|
|
|
export const useTradeMtm = (days = 30) =>
|
|
useQuery({
|
|
queryKey: ['journal-mtm', days],
|
|
queryFn: () => api.get(`/journal/trade-mtm?days=${days}`).then(r => r.data),
|
|
staleTime: 0,
|
|
refetchInterval: 5 * 60_000,
|
|
refetchIntervalInBackground: false,
|
|
})
|
|
|
|
export const useClosedTrades = (days = 180) =>
|
|
useQuery({
|
|
queryKey: ['journal-closed', days],
|
|
queryFn: () => api.get(`/journal/closed-trades?days=${days}`).then(r => r.data),
|
|
staleTime: 30_000,
|
|
})
|
|
|
|
export const useExitDefaults = () =>
|
|
useQuery({
|
|
queryKey: ['journal-exit-defaults'],
|
|
queryFn: () => api.get('/journal/exit-defaults').then(r => r.data),
|
|
staleTime: 60_000,
|
|
})
|
|
|
|
export const useCloseTrade = () => {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: ({ id, body }: { id: number; body: {
|
|
close_price: number; pnl_realized?: number;
|
|
close_reason: string; close_note: string
|
|
}}) => api.patch(`/journal/trades/${id}/close`, body).then(r => r.data),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ['journal-mtm'] })
|
|
qc.invalidateQueries({ queryKey: ['journal-closed'] })
|
|
qc.invalidateQueries({ queryKey: ['journal-summary'] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export const useUpdateExitParams = () => {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: ({ id, body }: { id: number; body: {
|
|
target_pct?: number; stop_loss_pct?: number; signal_threshold?: number
|
|
}}) => api.patch(`/journal/trades/${id}/exit-params`, body).then(r => r.data),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['journal-mtm'] }),
|
|
})
|
|
}
|
|
|
|
export const useSaveExitDefaults = () => {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (body: {
|
|
target_pct?: number; stop_loss_pct?: number;
|
|
signal_reversal_mode?: string; signal_reversal_threshold?: number
|
|
}) => api.put('/journal/exit-defaults', body).then(r => r.data),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['journal-exit-defaults'] }),
|
|
})
|
|
}
|
|
|
|
// ── Risk Profiles ─────────────────────────────────────────────────────────────
|
|
|
|
export const useRiskProfiles = () =>
|
|
useQuery({
|
|
queryKey: ['risk-profiles'],
|
|
queryFn: () => api.get('/profiles').then(r => r.data),
|
|
staleTime: 30_000,
|
|
})
|
|
|
|
export const useUpsertProfile = () => {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (profile: {
|
|
id?: number; name: string; min_score: number; min_gain_pct: number;
|
|
color?: string; enabled?: boolean; sort_order?: number
|
|
}) => {
|
|
if (profile.id) {
|
|
return api.put(`/profiles/${profile.id}`, profile).then(r => r.data)
|
|
}
|
|
return api.post('/profiles', profile).then(r => r.data)
|
|
},
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['risk-profiles'] }),
|
|
})
|
|
}
|
|
|
|
export const useDeleteProfile = () => {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (id: number) => api.delete(`/profiles/${id}`).then(r => r.data),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['risk-profiles'] }),
|
|
})
|
|
}
|
|
|
|
export const usePreviewTradeScore = (score: number, gainPct: number, enabled = true) =>
|
|
useQuery({
|
|
queryKey: ['profile-preview', score, gainPct],
|
|
queryFn: () => api.get(`/profiles/preview?score=${score}&gain_pct=${gainPct}`).then(r => r.data),
|
|
enabled,
|
|
staleTime: 0,
|
|
})
|
|
|
|
// ── Reasoning Traces / Post-mortem ────────────────────────────────────────────
|
|
|
|
export const useTradePostmortem = (tradeId: number | null) =>
|
|
useQuery({
|
|
queryKey: ['postmortem', tradeId],
|
|
queryFn: () => api.get(`/reasoning/postmortem/${tradeId}`).then(r => r.data),
|
|
enabled: tradeId !== null,
|
|
staleTime: 60_000,
|
|
})
|
|
|
|
export const useAnalyzePostmortem = () =>
|
|
useMutation({
|
|
mutationFn: (tradeId: number) =>
|
|
api.post(`/reasoning/postmortem/${tradeId}/analyze`).then(r => r.data),
|
|
})
|
|
|
|
export const usePortfolioReportData = (days: number) =>
|
|
useQuery({
|
|
queryKey: ['portfolio-report-data', days],
|
|
queryFn: () => api.get(`/reasoning/portfolio-report?days=${days}`).then(r => r.data),
|
|
staleTime: 120_000,
|
|
})
|
|
|
|
export const useGeneratePortfolioReport = () =>
|
|
useMutation({
|
|
mutationFn: (days: number) =>
|
|
api.post(`/reasoning/portfolio-report/generate?days=${days}`).then(r => r.data),
|
|
})
|
|
|
|
export const useAiReportsList = () =>
|
|
useQuery({
|
|
queryKey: ['ai-reports-list'],
|
|
queryFn: () => api.get('/reasoning/reports').then(r => r.data),
|
|
staleTime: 30_000,
|
|
})
|
|
|
|
export const useAiReport = (reportId: number | null) =>
|
|
useQuery({
|
|
queryKey: ['ai-report', reportId],
|
|
queryFn: () => api.get(`/reasoning/reports/${reportId}`).then(r => r.data),
|
|
enabled: reportId !== null,
|
|
staleTime: Infinity,
|
|
})
|
|
|
|
// ── Super Contexte / Knowledge Base ──────────────────────────────────────────
|
|
export const useKnowledgeState = () =>
|
|
useQuery({
|
|
queryKey: ['knowledge-state'],
|
|
queryFn: () => api.get('/knowledge/state').then(r => r.data),
|
|
staleTime: 5 * 60_000,
|
|
})
|
|
|
|
export const useKnowledgeHistory = () =>
|
|
useQuery({
|
|
queryKey: ['knowledge-history'],
|
|
queryFn: () => api.get('/knowledge/history').then(r => r.data),
|
|
staleTime: 5 * 60_000,
|
|
})
|
|
|
|
export const useKnowledgeStateVersion = (stateId: number | null) =>
|
|
useQuery({
|
|
queryKey: ['knowledge-state-version', stateId],
|
|
queryFn: () => api.get(`/knowledge/history/${stateId}`).then(r => r.data),
|
|
enabled: stateId !== null,
|
|
staleTime: Infinity,
|
|
})
|
|
|
|
export const useKnowledgeEntries = () =>
|
|
useQuery({
|
|
queryKey: ['knowledge-entries'],
|
|
queryFn: () => api.get('/knowledge/entries').then(r => r.data),
|
|
staleTime: 5 * 60_000,
|
|
})
|
|
|
|
export const useSynthesizeKnowledge = () => {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (force: boolean = false) =>
|
|
api.post(`/knowledge/synthesize${force ? '?force=true' : ''}`).then(r => r.data),
|
|
onSuccess: (_data) => {
|
|
if (!_data?.skipped) {
|
|
qc.invalidateQueries({ queryKey: ['knowledge-state'] })
|
|
qc.invalidateQueries({ queryKey: ['knowledge-history'] })
|
|
qc.invalidateQueries({ queryKey: ['knowledge-entries'] })
|
|
}
|
|
},
|
|
})
|
|
}
|
|
|
|
export const usePatchKbEntryStatus = () => {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: ({ id, status }: { id: number; status: string }) =>
|
|
api.patch(`/knowledge/entries/${id}/status`, { status }).then(r => r.data),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['knowledge-entries'] }),
|
|
})
|
|
}
|
|
|
|
export const useDeleteAiReport = () => {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (id: number) => api.delete(`/reasoning/reports/${id}`).then(r => r.data),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['ai-reports-list'] }),
|
|
})
|
|
}
|
|
|
|
export const useDeleteReasoningState = () => {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (id: number) => api.delete(`/knowledge/history/${id}`).then(r => r.data),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ['knowledge-history'] })
|
|
qc.invalidateQueries({ queryKey: ['knowledge-state'] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export const useDeleteKbEntry = () => {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (id: number) => api.delete(`/knowledge/entries/${id}`).then(r => r.data),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['knowledge-entries'] }),
|
|
})
|
|
}
|
|
|
|
// ── Options Volatility ────────────────────────────────────────────────────────
|
|
export const useIvSnapshot = (ticker: string) =>
|
|
useQuery({
|
|
queryKey: ['iv-snapshot', ticker],
|
|
queryFn: () => api.get(`/options-vol/snapshot/${encodeURIComponent(ticker)}`).then(r => r.data),
|
|
enabled: !!ticker,
|
|
staleTime: 60 * 60_000, // 1h — IV doesn't change that fast
|
|
})
|
|
|
|
export const useIvWatchlist = () =>
|
|
useQuery({
|
|
queryKey: ['iv-watchlist'],
|
|
queryFn: () => api.get('/options-vol/watchlist').then(r => r.data),
|
|
staleTime: 60 * 60_000,
|
|
})
|
|
|
|
export const useIvBatch = (tickers: string) =>
|
|
useQuery({
|
|
queryKey: ['iv-batch', tickers],
|
|
queryFn: () => api.get('/options-vol/batch', { params: { tickers } }).then(r => r.data),
|
|
enabled: !!tickers,
|
|
staleTime: 60 * 60_000,
|
|
})
|
|
|
|
export const useIvHistory = (ticker: string, days = 90) =>
|
|
useQuery({
|
|
queryKey: ['iv-history', ticker, days],
|
|
queryFn: () => api.get(`/options-vol/history/${encodeURIComponent(ticker)}`, { params: { days } }).then(r => r.data),
|
|
enabled: !!ticker,
|
|
staleTime: 60 * 60_000,
|
|
})
|
|
|
|
export const useIvForTrade = (underlying: string) =>
|
|
useQuery({
|
|
queryKey: ['iv-for-trade', underlying],
|
|
queryFn: () => api.get(`/options-vol/for-trade/${encodeURIComponent(underlying)}`).then(r => r.data),
|
|
enabled: !!underlying,
|
|
staleTime: 60 * 60_000,
|
|
})
|
|
|
|
// ── Analytics Phase 4 — Bayesian + Clustering + Embeddings ───────────────────
|
|
|
|
export const useBayesianPosteriors = () =>
|
|
useQuery({
|
|
queryKey: ['bayesian-posteriors'],
|
|
queryFn: () => api.get('/analytics/bayesian').then(r => r.data),
|
|
staleTime: 5 * 60_000,
|
|
})
|
|
|
|
export const useRegimeClusters = (days = 90) =>
|
|
useQuery({
|
|
queryKey: ['regime-clusters', days],
|
|
queryFn: () => api.get('/analytics/regime-clusters', { params: { days } }).then(r => r.data),
|
|
staleTime: 5 * 60_000,
|
|
})
|
|
|
|
export const useRegimeTransitions = (days = 180) =>
|
|
useQuery({
|
|
queryKey: ['regime-transitions', days],
|
|
queryFn: () => api.get('/analytics/regime-transitions', { params: { days } }).then(r => r.data),
|
|
staleTime: 10 * 60_000,
|
|
})
|
|
|
|
export const usePatternEmbeddings = () =>
|
|
useQuery({
|
|
queryKey: ['pattern-embeddings'],
|
|
queryFn: () => api.get('/analytics/embeddings').then(r => r.data),
|
|
staleTime: 10 * 60_000,
|
|
})
|
|
|
|
// ── Analytics (Phase 2) ───────────────────────────────────────────────────────
|
|
|
|
export const usePatternReliability = () =>
|
|
useQuery({
|
|
queryKey: ['pattern-reliability'],
|
|
queryFn: () => api.get('/analytics/reliability').then(r => r.data),
|
|
staleTime: 10 * 60_000,
|
|
})
|
|
|
|
export const useCalibration = (days = 365) =>
|
|
useQuery({
|
|
queryKey: ['calibration', days],
|
|
queryFn: () => api.get('/analytics/calibration', { params: { days } }).then(r => r.data),
|
|
staleTime: 10 * 60_000,
|
|
})
|
|
|
|
// ── Risk Engine (Phase 3) ─────────────────────────────────────────────────────
|
|
|
|
export const useRiskExposure = () =>
|
|
useQuery({
|
|
queryKey: ['risk-exposure'],
|
|
queryFn: () => api.get('/risk/exposure').then(r => r.data),
|
|
staleTime: 2 * 60_000,
|
|
})
|
|
|
|
export const usePnlTimeline = (days = 90) =>
|
|
useQuery({
|
|
queryKey: ['pnl-timeline', days],
|
|
queryFn: () => api.get('/risk/timeline', { params: { days } }).then(r => r.data),
|
|
staleTime: 5 * 60_000,
|
|
})
|
|
|
|
export const useRiskClusters = () =>
|
|
useQuery({
|
|
queryKey: ['risk-clusters'],
|
|
queryFn: () => api.get('/risk/clusters').then(r => r.data),
|
|
staleTime: 2 * 60_000,
|
|
})
|
|
|
|
export const usePatternCorrelations = () =>
|
|
useQuery({
|
|
queryKey: ['pattern-correlations'],
|
|
queryFn: () => api.get('/risk/correlations').then(r => r.data),
|
|
staleTime: 10 * 60_000,
|
|
})
|
|
|
|
export const useKellySizing = (patternId: string, capital = 10000) =>
|
|
useQuery({
|
|
queryKey: ['kelly', patternId, capital],
|
|
queryFn: () => api.get(`/risk/kelly/${encodeURIComponent(patternId)}`, { params: { capital } }).then(r => r.data),
|
|
enabled: !!patternId,
|
|
staleTime: 5 * 60_000,
|
|
})
|
|
|
|
export const useRiskDashboard = () =>
|
|
useQuery({
|
|
queryKey: ['risk-dashboard'],
|
|
queryFn: () => api.get('/risk/dashboard').then(r => r.data),
|
|
staleTime: 2 * 60_000,
|
|
refetchInterval: 5 * 60_000,
|
|
})
|
|
|
|
// ── System Logs ───────────────────────────────────────────────────────────────
|
|
|
|
export interface LogFilters {
|
|
level?: string
|
|
source?: string
|
|
cycle_id?: string
|
|
ticker?: string
|
|
date_from?: string
|
|
date_to?: string
|
|
limit?: number
|
|
}
|
|
|
|
export const useSystemLogs = (filters: LogFilters = {}) =>
|
|
useQuery({
|
|
queryKey: ['system-logs', filters],
|
|
queryFn: () => api.get('/logs', { params: filters }).then(r => r.data),
|
|
staleTime: 30_000,
|
|
refetchInterval: 30_000,
|
|
})
|
|
|
|
export const useLogSources = () =>
|
|
useQuery({
|
|
queryKey: ['log-sources'],
|
|
queryFn: () => api.get('/logs/sources').then(r => r.data),
|
|
staleTime: 60_000,
|
|
})
|
|
|
|
export const useLogCycles = () =>
|
|
useQuery({
|
|
queryKey: ['log-cycles'],
|
|
queryFn: () => api.get('/logs/cycles').then(r => r.data),
|
|
staleTime: 60_000,
|
|
})
|
|
|
|
export const useClearLogs = () =>
|
|
useMutation({
|
|
mutationFn: (days: number) => api.delete('/logs/clear', { params: { older_than_days: days } }).then(r => r.data),
|
|
})
|
|
|
|
// ── IV Watchlist Management ───────────────────────────────────────────────────
|
|
|
|
export const useWatchlistTickers = () =>
|
|
useQuery({
|
|
queryKey: ['watchlist-tickers'],
|
|
queryFn: () => api.get('/options-vol/watchlist-tickers').then(r => r.data),
|
|
staleTime: 60_000,
|
|
})
|
|
|
|
export const useAddWatchlistTicker = () =>
|
|
useMutation({
|
|
mutationFn: (ticker: string) => api.post(`/options-vol/watchlist-tickers/${encodeURIComponent(ticker)}`).then(r => r.data),
|
|
})
|
|
|
|
export const useRemoveWatchlistTicker = () =>
|
|
useMutation({
|
|
mutationFn: (ticker: string) => api.delete(`/options-vol/watchlist-tickers/${encodeURIComponent(ticker)}`).then(r => r.data),
|
|
})
|