feat(chart): candle build indicator as direction line #1
123
apps/visualizer/src/features/market/useDlobStats.ts
Normal file
123
apps/visualizer/src/features/market/useDlobStats.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
import { subscribeGraphqlWs } from '../../lib/graphqlWs';
|
||||||
|
|
||||||
|
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?: string | null;
|
||||||
|
oracle_price?: string | null;
|
||||||
|
best_bid_price?: string | null;
|
||||||
|
best_ask_price?: string | null;
|
||||||
|
mid_price?: string | null;
|
||||||
|
spread_abs?: string | null;
|
||||||
|
spread_bps?: string | null;
|
||||||
|
depth_bid_base?: string | null;
|
||||||
|
depth_ask_base?: string | null;
|
||||||
|
depth_bid_usd?: string | null;
|
||||||
|
depth_ask_usd?: string | null;
|
||||||
|
imbalance?: string | null;
|
||||||
|
updated_at?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SubscriptionData = {
|
||||||
|
dlob_stats_latest: HasuraDlobStatsRow[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useDlobStats(marketName: string): { stats: DlobStats | null; connected: boolean; error: string | null } {
|
||||||
|
const [stats, setStats] = useState<DlobStats | null>(null);
|
||||||
|
const [connected, setConnected] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const normalizedMarket = useMemo(() => (marketName || '').trim(), [marketName]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!normalizedMarket) {
|
||||||
|
setStats(null);
|
||||||
|
setError(null);
|
||||||
|
setConnected(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
subscription DlobStats($market: String!) {
|
||||||
|
dlob_stats_latest(where: {market_name: {_eq: $market}}, limit: 1) {
|
||||||
|
market_name
|
||||||
|
mark_price
|
||||||
|
oracle_price
|
||||||
|
best_bid_price
|
||||||
|
best_ask_price
|
||||||
|
mid_price
|
||||||
|
spread_abs
|
||||||
|
spread_bps
|
||||||
|
depth_bid_base
|
||||||
|
depth_ask_base
|
||||||
|
depth_bid_usd
|
||||||
|
depth_ask_usd
|
||||||
|
imbalance
|
||||||
|
updated_at
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const sub = subscribeGraphqlWs<SubscriptionData>({
|
||||||
|
query,
|
||||||
|
variables: { market: normalizedMarket },
|
||||||
|
onStatus: ({ connected }) => setConnected(connected),
|
||||||
|
onError: (e) => setError(e),
|
||||||
|
onData: (data) => {
|
||||||
|
const row = data?.dlob_stats_latest?.[0];
|
||||||
|
if (!row?.market_name) 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_abs),
|
||||||
|
spreadBps: toNum(row.spread_bps),
|
||||||
|
depthBidBase: toNum(row.depth_bid_base),
|
||||||
|
depthAskBase: toNum(row.depth_ask_base),
|
||||||
|
depthBidUsd: toNum(row.depth_bid_usd),
|
||||||
|
depthAskUsd: toNum(row.depth_ask_usd),
|
||||||
|
imbalance: toNum(row.imbalance),
|
||||||
|
updatedAt: row.updated_at ?? null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => sub.unsubscribe();
|
||||||
|
}, [normalizedMarket]);
|
||||||
|
|
||||||
|
return { stats, connected, error };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user