feat(chart): candle build indicator as direction line #1

Open
u1 wants to merge 31 commits from feat/candle-build-indicator into main
Showing only changes of commit dff4d347ad - Show all commits

View 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 };
}