Files
trade-frontend/apps/visualizer/src/features/market/DlobDepthBandsPanel.tsx

76 lines
2.9 KiB
TypeScript

import type { CSSProperties } from 'react';
import { useMemo } from 'react';
import type { DlobDepthBandRow } from './useDlobDepthBands';
function formatUsd(v: number | null | undefined): string {
if (v == null || !Number.isFinite(v)) return '—';
if (v >= 1_000_000) return `$${(v / 1_000_000).toFixed(2)}M`;
if (v >= 1000) return `$${(v / 1000).toFixed(0)}K`;
if (v >= 1) return `$${v.toFixed(2)}`;
return `$${v.toPrecision(4)}`;
}
function formatPct(v: number | null | undefined): string {
if (v == null || !Number.isFinite(v)) return '—';
return `${v.toFixed(0)}%`;
}
function bandRowStyle(askScale: number, bidScale: number): CSSProperties {
const a = Number.isFinite(askScale) && askScale > 0 ? Math.min(1, askScale) : 0;
const b = Number.isFinite(bidScale) && bidScale > 0 ? Math.min(1, bidScale) : 0;
return { ['--ask-scale' as any]: a, ['--bid-scale' as any]: b } as CSSProperties;
}
export default function DlobDepthBandsPanel({ rows }: { rows: DlobDepthBandRow[] }) {
const sorted = useMemo(() => rows.slice().sort((a, b) => a.bandBps - b.bandBps), [rows]);
const maxUsd = useMemo(() => {
let max = 0;
for (const r of sorted) {
if (r.askUsd != null && Number.isFinite(r.askUsd)) max = Math.max(max, r.askUsd);
if (r.bidUsd != null && Number.isFinite(r.bidUsd)) max = Math.max(max, r.bidUsd);
}
return max;
}, [sorted]);
return (
<div className="dlobDepth">
<div className="dlobDepth__head">
<div className="dlobDepth__title">Depth (bands)</div>
<div className="dlobDepth__meta">±bps around mid</div>
</div>
<div className="dlobDepth__table">
<div className="dlobDepthRow dlobDepthRow--head">
<span>Band</span>
<span className="dlobDepthRow__num">Ask USD</span>
<span className="dlobDepthRow__num">Bid USD</span>
<span className="dlobDepthRow__num">Bid %</span>
</div>
{sorted.length ? (
sorted.map((r) => (
<div
key={r.bandBps}
className="dlobDepthRow"
style={bandRowStyle(maxUsd > 0 ? (r.askUsd || 0) / maxUsd : 0, maxUsd > 0 ? (r.bidUsd || 0) / maxUsd : 0)}
title={`band=${r.bandBps}bps bid=${r.bidUsd ?? '—'} ask=${r.askUsd ?? '—'} imbalance=${r.imbalance ?? '—'}`}
>
<span className="dlobDepthRow__band">{r.bandBps} bps</span>
<span className="dlobDepthRow__num neg">{formatUsd(r.askUsd)}</span>
<span className="dlobDepthRow__num pos">{formatUsd(r.bidUsd)}</span>
<span className="dlobDepthRow__num muted">
{r.imbalance == null || !Number.isFinite(r.imbalance)
? '—'
: formatPct(((r.imbalance + 1) / 2) * 100)}
</span>
</div>
))
) : (
<div className="dlobDepth__empty muted">No depth band rows yet.</div>
)}
</div>
</div>
);
}