# Chart Setup

> Wire TradingView lightweight-charts into a React app — candlestick + volume series, dark theme, dynamic resize. Use when the user wants to add a price chart, swap a Recharts/Chart.js chart for lightweight-charts, or build a custom indicator on top of an existing chart.

- Skill: `traderspost/chart-setup` (Agent Skill)
- Install (CLI): `npx skillmds@latest add traderspost/chart-setup`
- Raw SKILL.md: https://api.skillmd.com/api/skills/traderspost/chart-setup/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: traderspost (https://skillmd.com/u/traderspost)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/traderspost/chart-setup

---


# chart-setup

Add a production-quality price chart to a React app using TradingView's
[lightweight-charts](https://github.com/tradingview/lightweight-charts) library.
This is what powers the main `ChartCanvas` in command-dash.

## When to use

Trigger this skill whenever the user wants to:
- Show an OHLC / candlestick chart for a stock or crypto symbol
- Add volume bars under a price chart
- Theme an existing lightweight-charts instance to a dark dashboard palette
- Wire a chart to a `fetch('/api/...')` source that returns `{ time, open, high, low, close, volume }[]`

## Install

```bash
npm install lightweight-charts
```

`lightweight-charts` v5+ uses the modular series API (`addSeries(CandlestickSeries, ...)`) — older v4 code that calls `addCandlestickSeries(...)` will not work. If the project is on v4, either upgrade to v5 or use the v4 API throughout.

## Drop-in component

Create `src/components/ChartPanel.tsx`:

```tsx
import { useEffect, useRef, useState } from 'react'
import {
  createChart,
  CandlestickSeries,
  HistogramSeries,
  type IChartApi,
  type ISeriesApi
} from 'lightweight-charts'

interface Bar {
  time: number          // unix seconds (UTCTimestamp)
  open: number
  high: number
  low: number
  close: number
  volume: number
}

interface Props {
  symbol: string
  fetchBars: (symbol: string) => Promise<Bar[]>
}

export function ChartPanel({ symbol, fetchBars }: Props) {
  const containerRef = useRef<HTMLDivElement | null>(null)
  const chartRef = useRef<IChartApi | null>(null)
  const candleRef = useRef<ISeriesApi<'Candlestick'> | null>(null)
  const volRef = useRef<ISeriesApi<'Histogram'> | null>(null)

  useEffect(() => {
    if (!containerRef.current) return
    const chart = createChart(containerRef.current, {
      layout: { background: { color: '#1d2939' }, textColor: '#98a2b3' },
      grid: { vertLines: { color: '#293548' }, horzLines: { color: '#293548' } },
      rightPriceScale: { borderColor: '#344054' },
      timeScale: { borderColor: '#344054', timeVisible: true, secondsVisible: false },
      crosshair: { mode: 1 },
      autoSize: true
    })
    candleRef.current = chart.addSeries(CandlestickSeries, {
      upColor: '#12b76a', downColor: '#f04438',
      wickUpColor: '#12b76a', wickDownColor: '#f04438',
      borderVisible: false
    })
    volRef.current = chart.addSeries(HistogramSeries, {
      priceFormat: { type: 'volume' },
      priceScaleId: 'volume_scale',
      color: '#344054'
    })
    chart.priceScale('volume_scale').applyOptions({ scaleMargins: { top: 0.82, bottom: 0 } })
    chartRef.current = chart
    return () => chart.remove()
  }, [])

  useEffect(() => {
    let cancelled = false
    fetchBars(symbol).then((bars) => {
      if (cancelled || !candleRef.current || !volRef.current) return
      candleRef.current.setData(bars.map((b) => ({
        time: b.time as never, open: b.open, high: b.high, low: b.low, close: b.close
      })))
      volRef.current.setData(bars.map((b) => ({
        time: b.time as never,
        value: b.volume,
        color: b.close >= b.open ? 'rgba(18, 183, 106, 0.35)' : 'rgba(240, 68, 56, 0.35)'
      })))
      chartRef.current?.timeScale().fitContent()
    })
    return () => { cancelled = true }
  }, [symbol, fetchBars])

  return <div ref={containerRef} className="w-full h-full" />
}
```

## Key requirements

1. **`autoSize: true`** in `createChart` options — fills its container and re-fits on resize. No manual `resizeObserver` plumbing required.
2. **Container must have an explicit height** — `h-full` only works if every ancestor is also height-constrained (flexbox or grid).
3. **`time` is unix seconds (UTCTimestamp)** — not milliseconds, not ISO strings. Divide JS `Date.now()` by 1000.
4. **Sort bars ascending by `time`** before calling `setData`. Polygon and most providers return ascending if you pass `sort=asc`.
5. **Cast `time` with `as never`** in TS to avoid type friction with lightweight-charts' branded `Time` type.

## Color palette (TradersPost dark navy)

| Token              | Value     | Use                           |
|--------------------|-----------|-------------------------------|
| `bg-card`          | `#1d2939` | chart background              |
| `border`           | `#344054` | axis + scale borders          |
| `text-secondary`   | `#98a2b3` | axis text                     |
| grid lines         | `#293548` | both vert + horz              |
| up candle / bull   | `#12b76a` | wickUp, body up               |
| down candle / bear | `#f04438` | wickDown, body down           |
| accent / line      | `#2e90fa` | overlay lines, equity curves  |

## Reference

The canonical implementation in command-dash is `apps/web/src/components/dashboard/ChartCanvas.tsx`. It includes:
- Controlled props for symbol, timeframe, chart type (Candles / Bars / Line / Area / Heikin Ashi), and indicator overlays (SMA / EMA)
- Vite proxy to a FastAPI backend that returns Polygon OHLC
- A `requestAnimationFrame` size-wait pattern so charts created before the grid layout finalizes still render correctly

For equity-curve charts (single line series, no volume), see the `OverviewTab` inside `apps/web/src/components/dashboard/StrategyReport.tsx`.

