feat: Specialist Desks v2 — COT, Forward Curves, Surprise Index, Hawk/Dove scorer
- COT Positioning: CFTC disaggregated + financial futures (19 markets) via Socrata free API net MM position % OI + weekly change stored in cot_data table - Forward Curves: yfinance front-month vs +3M slope (8 commodities) contango/backwardation/flat stored in forward_curve_data table - Surprise Index: consensus_estimate + actual_value on specialist_reports auto-computes surprise_score = actual - consensus on save - Hawk/Dove Text Scorer: GPT-4o-mini endpoint for CB statements score -1..+1, label, summary, key_phrases (forex/bonds: hawk/dove; commodities: bull/bear) - AI context injection: COT net positioning, forward curve structure, surprise scores, upcoming consensus estimates injected into all desk blocks - Frontend: COT panel (net% bars), Forward Curves panel, SurpriseInput on report cards, Hawk/Dove scorer in forex/bonds config tab - auto_cycle.py: non-blocking COT + curve refresh before each cycle Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1252,3 +1252,107 @@ export const useUnlinkReportDesk = () => {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ── COT / Forward Curves / Surprise Index / Hawk-Dove — new types ─────────────
|
||||
|
||||
export interface CotEntry {
|
||||
id: number
|
||||
market_name: string
|
||||
commodity: string
|
||||
asset_class: string
|
||||
report_date: string
|
||||
mm_long: number
|
||||
mm_short: number
|
||||
open_interest: number
|
||||
net_position: number
|
||||
net_pct_oi: number
|
||||
change_net: number
|
||||
fetched_at: string
|
||||
}
|
||||
|
||||
export interface ForwardCurveEntry {
|
||||
id: number
|
||||
asset: string
|
||||
asset_class: string
|
||||
front_price: number | null
|
||||
far_price: number | null
|
||||
slope_pct: number | null
|
||||
structure: 'contango' | 'backwardation' | 'flat' | 'unknown'
|
||||
months_spread: number
|
||||
fetched_at: string
|
||||
}
|
||||
|
||||
export interface TextSentimentResult {
|
||||
score: number
|
||||
label: string
|
||||
summary: string
|
||||
key_phrases: string[]
|
||||
confidence: 'high' | 'medium' | 'low'
|
||||
}
|
||||
|
||||
export interface ReportResultUpdate {
|
||||
actual_value?: number | null
|
||||
consensus_estimate?: number | null
|
||||
text_sentiment_score?: number | null
|
||||
text_sentiment_label?: string | null
|
||||
text_sentiment_summary?: string | null
|
||||
}
|
||||
|
||||
// ── COT Data ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export function useCotData() {
|
||||
return useQuery<CotEntry[]>({
|
||||
queryKey: ['specialist-desks', 'cot'],
|
||||
queryFn: () => api.get('/specialist-desks/cot').then(r => r.data),
|
||||
staleTime: 1000 * 60 * 60, // 1h
|
||||
})
|
||||
}
|
||||
|
||||
export function useFetchCot() {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: () => api.post('/specialist-desks/cot/fetch').then(r => r.data),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['specialist-desks', 'cot'] }),
|
||||
})
|
||||
}
|
||||
|
||||
// ── Forward Curves ────────────────────────────────────────────────────────────
|
||||
|
||||
export function useForwardCurves() {
|
||||
return useQuery<ForwardCurveEntry[]>({
|
||||
queryKey: ['specialist-desks', 'forward-curves'],
|
||||
queryFn: () => api.get('/specialist-desks/forward-curves').then(r => r.data),
|
||||
staleTime: 1000 * 60 * 60,
|
||||
})
|
||||
}
|
||||
|
||||
export function useFetchForwardCurves() {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: () => api.post('/specialist-desks/forward-curves/fetch').then(r => r.data),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['specialist-desks', 'forward-curves'] }),
|
||||
})
|
||||
}
|
||||
|
||||
// ── Surprise Index ────────────────────────────────────────────────────────────
|
||||
|
||||
export function useUpdateReportResult() {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: ({ id, body }: { id: string; body: ReportResultUpdate }) =>
|
||||
api.put(`/specialist-desks/reports/${id}/result`, body).then(r => r.data),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['specialist-reports'] })
|
||||
qc.invalidateQueries({ queryKey: ['desk-configs'] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ── Hawk/Dove Text Scorer ─────────────────────────────────────────────────────
|
||||
|
||||
export function useScoreText() {
|
||||
return useMutation({
|
||||
mutationFn: (body: { text: string; report_name: string; desk: string }) =>
|
||||
api.post('/specialist-desks/score-text', body).then(r => r.data) as Promise<TextSentimentResult>,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user