import { useEffect, useMemo, useState } from 'react'; import { subscribeGraphqlWs } from '../../lib/graphqlWs'; import { getLatestDlobTable } from './dlobSource'; const ORDERBOOK_STATS_POLL_MS = 400; export type DlobStats = { marketName: string; markPrice: number | null; oraclePrice: number | null; bestBid: number | null; bestAsk: number | null; mid: number | null; spreadAbs: number | null; spreadBps: number | null; depthBidBase: number | null; depthAskBase: number | null; depthBidUsd: number | null; depthAskUsd: 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; } type HasuraDlobStatsRow = { market_name: string; mark_price?: unknown; oracle_price?: unknown; best_bid_price?: unknown; best_ask_price?: unknown; mid_price?: unknown; spread_quote?: unknown; spread_bps?: unknown; depth_bid_base?: unknown; depth_ask_base?: unknown; depth_bid_quote?: unknown; depth_ask_quote?: unknown; imbalance?: unknown; updated_at?: string | null; is_indicative?: boolean | null; }; type SubscriptionData = Record; export function useDlobStats(marketName: string): { stats: DlobStats | null; connected: boolean; error: string | null } { const [stats, setStats] = useState(null); const [connected, setConnected] = useState(false); const [error, setError] = useState(null); const normalizedMarket = useMemo(() => (marketName || '').trim(), [marketName]); useEffect(() => { if (!normalizedMarket) { setStats(null); setError(null); setConnected(false); return; } setError(null); const table = getLatestDlobTable(normalizedMarket); const query = ` subscription DlobStats($market: String!) { ${table}( where: {market_name: {_eq: $market}, is_indicative: {_eq: false}} limit: 1 ) { market_name mark_price oracle_price best_bid_price best_ask_price mid_price spread_quote spread_bps depth_bid_base depth_ask_base depth_bid_quote depth_ask_quote imbalance updated_at is_indicative } } `; const sub = subscribeGraphqlWs({ query, variables: { market: normalizedMarket }, pollMs: ORDERBOOK_STATS_POLL_MS, onStatus: ({ connected }) => setConnected(connected), onError: (e) => setError(e), onData: (data) => { const row = data?.[table]?.[0]; if (!row?.market_name) { setStats(null); return; } setStats({ marketName: row.market_name, markPrice: toNum(row.mark_price), oraclePrice: toNum(row.oracle_price), bestBid: toNum(row.best_bid_price), bestAsk: toNum(row.best_ask_price), mid: toNum(row.mid_price), spreadAbs: toNum(row.spread_quote), spreadBps: toNum(row.spread_bps), depthBidBase: toNum(row.depth_bid_base), depthAskBase: toNum(row.depth_ask_base), depthBidUsd: toNum(row.depth_bid_quote), depthAskUsd: toNum(row.depth_ask_quote), imbalance: toNum(row.imbalance), updatedAt: row.updated_at ?? null, }); }, }); return () => sub.unsubscribe(); }, [normalizedMarket]); return { stats, connected, error }; }