// The lightweight-charts chart: series per mode, the volume pane, rebasing lines on the visible range, // and New York time labels. import { CandlestickSeries, ColorType, CrosshairMode, HistogramSeries, LineSeries, createChart, type IChartApi, type ISeriesApi, type MouseEventParams, type SeriesType, type Time, } from 'lightweight-charts' import type { Bar } from './plan' import { crosshairLabel, tickLabel } from './time' export interface SeriesInput { symbol: string color: string bars: Bar[] } export interface ChartModel { mode: 'lines' | 'candles' series: SeriesInput[] active: string | null volume: boolean intraday: boolean /** A caption at the top of the volume pane, or null for none. */ volumeLabel?: string | null /** Changes when the interval changes: the chart fits all data again. */ dataKey: string } export interface LegendEntry { symbol: string color: string text: string } /** What the price pane holds after an update, as the view reports it. pane -1 means its series are split across panes. */ export interface PriceCheck { series: number points: number finite: boolean pane: number | null volumePane: number | null } const PRICE_PANE = 0 const VOLUME_PANE = 1 const UP = '#26a69a' const DOWN = '#ef5350' const pct = (v: number) => `${v >= 0 ? '+' : ''}${v.toFixed(2)}%` const price = (v: number) => (Math.abs(v) >= 1000 ? v.toFixed(0) : Math.abs(v) >= 1 ? v.toFixed(2) : v.toFixed(4)) const vol = (v: number) => v >= 1e9 ? `${(v / 1e9).toFixed(2)}B` : v >= 1e6 ? `${(v / 1e6).toFixed(2)}M` : v >= 1e3 ? `${(v / 1e3).toFixed(1)}K` : String(v) const t = (bar: Bar) => bar.time as Time /** Whether time a comes before b: seconds against seconds, YYYY-MM-DD against YYYY-MM-DD. */ const before = (a: number | string, b: number | string | { year: number; month: number; day: number }) => { if (typeof b === 'object') b = `${b.year}-${String(b.month).padStart(2, '0')}-${String(b.day).padStart(2, '0')}` return typeof a === 'number' && typeof b === 'number' ? a < b : String(a) < String(b) } export class PriceChart { private chart: IChartApi private model: ChartModel | null = null private lines = new Map>() private candles: ISeriesApi<'Candlestick'> | null = null private volumeSeries: ISeriesApi<'Histogram'> | null = null private hover: MouseEventParams