Charts: Interactive price chart view with lines or candles and volume, backed by Yahoo, Massive, or Databento bar data.
| 1 | // Labels for the chart's times. Intraday times are New York wall-clock seconds read as UTC (see plan.ts), |
| 2 | // so formatting them in UTC shows New York time, daylight saving included. Dates are YYYY-MM-DD. |
| 3 | |
| 4 | const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] |
| 5 | |
| 6 | type AnyTime = number | string | { year: number; month: number; day: number } |
| 7 | |
| 8 | function parts(time: AnyTime) { |
| 9 | if (typeof time === 'number') { |
| 10 | const d = new Date(time * 1000) |
| 11 | return { y: d.getUTCFullYear(), m: d.getUTCMonth(), d: d.getUTCDate(), hh: d.getUTCHours(), mm: d.getUTCMinutes(), hasTime: true } |
| 12 | } |
| 13 | if (typeof time === 'string') { |
| 14 | const [y, m, d] = time.split('-').map(Number) |
| 15 | return { y, m: m - 1, d, hh: 0, mm: 0, hasTime: false } |
| 16 | } |
| 17 | return { y: time.year, m: time.month - 1, d: time.day, hh: 0, mm: 0, hasTime: false } |
| 18 | } |
| 19 | |
| 20 | const pad = (n: number) => String(n).padStart(2, '0') |
| 21 | |
| 22 | /** The crosshair's label: "Wed Oct 15 '26 09:30 ET" intraday, "Oct 15 '26" for a day. */ |
| 23 | export function crosshairLabel(time: unknown): string { |
| 24 | const p = parts(time as AnyTime) |
| 25 | const date = `${MONTHS[p.m]} ${p.d} '${String(p.y).slice(2)}` |
| 26 | if (!p.hasTime) return date |
| 27 | const weekday = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][new Date(Date.UTC(p.y, p.m, p.d)).getUTCDay()] |
| 28 | return `${weekday} ${date} ${pad(p.hh)}:${pad(p.mm)} ET` |
| 29 | } |
| 30 | |
| 31 | /** A tick on the time axis. markType is lightweight-charts' TickMarkType: 0 year, 1 month, 2 day, 3 and 4 time. */ |
| 32 | export function tickLabel(time: unknown, markType: number): string { |
| 33 | const p = parts(time as AnyTime) |
| 34 | if (markType === 0) return String(p.y) |
| 35 | if (markType === 1) return MONTHS[p.m] |
| 36 | if (markType === 2 || !p.hasTime) return `${MONTHS[p.m]} ${p.d}` |
| 37 | return `${pad(p.hh)}:${pad(p.mm)}` |
| 38 | } |