import { useEffect, useMemo, useState } from 'react'; import { subscribeGraphqlWs } from '../../lib/graphqlWs'; export type DlobDepthBandRow = { marketName: string; bandBps: number; midPrice: number | null; bestBid: number | null; bestAsk: number | null; bidUsd: number | null; askUsd: number | null; bidBase: number | null; askBase: number | null; imbalance: number | null; updatedAt: string | null; }; function toNum(v: unknown): number | null { if (v == null) return null; if (typeof v === 'number') return Number.isFinite(v) ? v : null; if (typeof v === 'string') { const s = v.trim(); if (!s) return null; const n = Number(s); return Number.isFinite(n) ? n : null; } return null; } function toInt(v: unknown): number | null { if (v == null) return null; if (typeof v === 'number') return Number.isFinite(v) ? Math.trunc(v) : null; if (typeof v === 'string') { const s = v.trim(); if (!s) return null; const n = Number.parseInt(s, 10); return Number.isFinite(n) ? n : null; } return null; } type HasuraRow = { market_name: string; band_bps: unknown; mid_price?: unknown; best_bid_price?: unknown; best_ask_price?: unknown; bid_usd?: unknown; ask_usd?: unknown; bid_base?: unknown; ask_base?: unknown; imbalance?: unknown; updated_at?: string | null; }; type SubscriptionData = { dlob_depth_bps_latest: HasuraRow[]; }; export function useDlobDepthBands( marketName: string ): { rows: DlobDepthBandRow[]; connected: boolean; error: string | null } { const [rows, setRows] = useState([]); const [connected, setConnected] = useState(false); const [error, setError] = useState(null); const normalizedMarket = useMemo(() => (marketName || '').trim(), [marketName]); useEffect(() => { if (!normalizedMarket) { setRows([]); setError(null); setConnected(false); return; } setError(null); const query = ` subscription DlobDepthBands($market: String!) { dlob_depth_bps_latest( where: { market_name: { _eq: $market } } order_by: [{ band_bps: asc }] ) { market_name band_bps mid_price best_bid_price best_ask_price bid_usd ask_usd bid_base ask_base imbalance updated_at } } `; const sub = subscribeGraphqlWs({ query, variables: { market: normalizedMarket }, onStatus: ({ connected }) => setConnected(connected), onError: (e) => setError(e), onData: (data) => { const out: DlobDepthBandRow[] = []; for (const r of data?.dlob_depth_bps_latest || []) { if (!r?.market_name) continue; const bandBps = toInt(r.band_bps); if (bandBps == null || bandBps <= 0) continue; out.push({ marketName: r.market_name, bandBps, midPrice: toNum(r.mid_price), bestBid: toNum(r.best_bid_price), bestAsk: toNum(r.best_ask_price), bidUsd: toNum(r.bid_usd), askUsd: toNum(r.ask_usd), bidBase: toNum(r.bid_base), askBase: toNum(r.ask_base), imbalance: toNum(r.imbalance), updatedAt: r.updated_at ?? null, }); } setRows(out); }, }); return () => sub.unsubscribe(); }, [normalizedMarket]); return { rows, connected, error }; }