From 62baa9700edbc65651cb053e171b92b257c09d0d Mon Sep 17 00:00:00 2001 From: u1 Date: Sat, 10 Jan 2026 22:46:20 +0000 Subject: [PATCH] feat(chart): overlay DLOB quotes + layers --- .../src/features/chart/ChartPanel.tsx | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/apps/visualizer/src/features/chart/ChartPanel.tsx b/apps/visualizer/src/features/chart/ChartPanel.tsx index 02b5557..f06a677 100644 --- a/apps/visualizer/src/features/chart/ChartPanel.tsx +++ b/apps/visualizer/src/features/chart/ChartPanel.tsx @@ -6,12 +6,13 @@ import ChartSideToolbar from './ChartSideToolbar'; import ChartToolbar from './ChartToolbar'; import TradingChart from './TradingChart'; import type { FibAnchor, FibRetracement } from './FibRetracementPrimitive'; -import type { IChartApi } from 'lightweight-charts'; +import { LineStyle, type IChartApi } from 'lightweight-charts'; import type { OverlayLayer } from './ChartPanel.types'; type Props = { candles: Candle[]; indicators: ChartIndicators; + dlobQuotes?: { bid: number | null; ask: number | null; mid: number | null } | null; timeframe: string; bucketSeconds: number; seriesKey: string; @@ -45,6 +46,7 @@ function isEditableTarget(t: EventTarget | null): boolean { export default function ChartPanel({ candles, indicators, + dlobQuotes, timeframe, bucketSeconds, seriesKey, @@ -61,6 +63,7 @@ export default function ChartPanel({ const [fib, setFib] = useState(null); const [fibDraft, setFibDraft] = useState(null); const [layers, setLayers] = useState([ + { id: 'dlob-quotes', name: 'DLOB Quotes', visible: true, locked: false, opacity: 0.9 }, { id: 'drawings', name: 'Drawings', visible: true, locked: false, opacity: 1 }, ]); const [layersOpen, setLayersOpen] = useState(false); @@ -196,6 +199,37 @@ export default function ChartPanel({ return Math.max(0, Math.min(1, v)); } + const quotesLayer = useMemo(() => layers.find((l) => l.id === 'dlob-quotes'), [layers]); + const quotesVisible = Boolean(quotesLayer?.visible); + const quotesOpacity = clamp01(quotesLayer?.opacity ?? 1); + + const priceLines = useMemo(() => { + if (!quotesVisible) return []; + return [ + { + id: 'dlob-bid', + title: 'DLOB Bid', + price: dlobQuotes?.bid ?? null, + color: `rgba(34,197,94,${quotesOpacity})`, + lineStyle: LineStyle.Dotted, + }, + { + id: 'dlob-mid', + title: 'DLOB Mid', + price: dlobQuotes?.mid ?? null, + color: `rgba(230,233,239,${quotesOpacity})`, + lineStyle: LineStyle.Dashed, + }, + { + id: 'dlob-ask', + title: 'DLOB Ask', + price: dlobQuotes?.ask ?? null, + color: `rgba(239,68,68,${quotesOpacity})`, + lineStyle: LineStyle.Dotted, + }, + ]; + }, [dlobQuotes?.ask, dlobQuotes?.bid, dlobQuotes?.mid, quotesOpacity, quotesVisible]); + function updateLayer(layerId: string, patch: Partial) { setLayers((prev) => prev.map((l) => (l.id === layerId ? { ...l, ...patch } : l))); } @@ -309,6 +343,7 @@ export default function ChartPanel({ showBuild={showBuild} bucketSeconds={bucketSeconds} seriesKey={seriesKey} + priceLines={priceLines} fib={fibRenderable} fibOpacity={fibEffectiveOpacity} fibSelected={fibSelected}