feat(chart): candle build indicator as direction line #1
133
apps/visualizer/src/features/market/useDlobDepthBands.ts
Normal file
133
apps/visualizer/src/features/market/useDlobDepthBands.ts
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
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<DlobDepthBandRow[]>([]);
|
||||||
|
const [connected, setConnected] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(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<SubscriptionData>({
|
||||||
|
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 };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user