> ## Documentation Index
> Fetch the complete documentation index at: https://docs.serialized.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# TradingView Charts

> Wire Serialized candles into TradingView: a complete datafeed for the Advanced Charts (Charting Library) widget and the Lightweight Charts one-liner, with live bars from the stream, symbol search, and the pricescale trick for microcap tokens.

Every serious terminal renders its chart with TradingView, and our candles were shaped for it: `time` in unix seconds, TradingView-style interval keys accepted as aliases, a dense time axis with no gaps, and pagination that maps one-to-one to `getBars`. This guide wires [`GET /v1/token/ohlcv`](/endpoints/candles-ohlcv) into both TradingView products, then makes the bars live.

* **Advanced Charts** (the Charting Library): the full-featured widget behind Axiom, GMGN and every major terminal. Free, but TradingView grants the library access on request; you host the files, you feed the data.
* **Lightweight Charts**: TradingView's open-source npm package. A few KB, no approval process, perfect for list previews and compact charts.

## The symbol convention

A chart needs one string per market. Use the token's identity as the ticker, `<chain>:<address>`:

```typescript theme={null}
const ticker = 'solana:DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263';   // BONK
const toRef = (t: string) => { const i = t.lastIndexOf(':'); return { chain: t.slice(0, i), address: t.slice(i + 1) }; };
```

`chain` is a public chain id (`evm:8453`, `solana`), so the same datafeed serves all 18 EVM chains and Solana.

## Resolutions

Our intervals cover every resolution the widget offers, seconds included. The full map:

| TradingView resolution | `interval` |
| - | - |
| `1S` `5S` `15S` `30S` | `1s` `5s` `15s` `30s` |
| `1` `3` `5` `15` `30` | `1m` `3m` `5m` `15m` `30m` |
| `60` `120` `240` `360` `720` | `1h` `2h` `4h` `6h` `12h` |
| `1D` `1W` `1M` | `1d` `1w` `1M` |

```typescript theme={null}
const RES: Record<string, string> = {
  '1S': '1s', '5S': '5s', '15S': '15s', '30S': '30s',
  '1': '1m', '3': '3m', '5': '5m', '15': '15m', '30': '30m',
  '60': '1h', '120': '2h', '240': '4h', '360': '6h', '720': '12h',
  '1D': '1d', '1W': '1w', '1M': '1M',
};
const SUPPORTED = Object.keys(RES) as ResolutionString[];
```

## The datafeed

The whole integration is one object implementing four methods. `getBars` is a direct translation of the endpoint's paging contract: `endTime` is TradingView's `to` (both unix seconds), `limit` is `countBack`, and `noData` is true when a page comes back empty with `meta.hasMore` false.

```typescript datafeed.ts theme={null}
import { sd } from './serialized';   // the fetch helper from the terminal guide

// USD quoting: Solana candles come back in USD with quote=usd; on EVM
// multiply by the native spot, one cached call for every chart.
let spots: Record<string, number> = {}, spotsAt = 0;
async function usdFactor(chain: string) {
  if (chain === 'solana') return 1;
  if (Date.now() - spotsAt > 60_000) {
    spots = Object.fromEntries(Object.entries((await sd<any>('/prices/native')).data)
      .map(([c, v]: any) => [c, v.priceUsd]));
    spotsAt = Date.now();
  }
  return spots[chain] ?? 1;
}

// Integer pricescale sized to the token's price: the microcap essential.
// A $0.0000023 token needs 10+ decimals or every candle renders flat.
const priceScale = (p: number) =>
  10 ** (p >= 1 ? 4 : Math.min(16, 4 - Math.floor(Math.log10(p))));

export const datafeed = {
  onReady(cb: any) {
    setTimeout(() => cb({ supported_resolutions: SUPPORTED }), 0);
  },

  async searchSymbols(input: string, _ex: string, _type: string, onResult: any) {
    const { data } = await sd<any[]>('/search', { q: input });   // EVM + Solana, merged
    onResult(data.map((h) => ({
      ticker: `${h.chain}:${h.address}`, symbol: h.symbol, description: h.name,
      exchange: 'Serialized', type: 'crypto',
    })));
  },

  async resolveSymbol(ticker: string, onResolve: any, onError: any) {
    const { chain, address } = toRef(ticker);
    try {
      const { data } = await sd<any>('/token', { chain, address });
      const price = (data.priceUsd ?? 1) as number;
      onResolve({
        ticker, name: data.symbol, description: `${data.symbol}/USD`,
        type: 'crypto', exchange: 'Serialized', listed_exchange: 'Serialized',
        session: '24x7', timezone: 'Etc/UTC', format: 'price',
        minmov: 1, pricescale: priceScale(price),
        has_intraday: true, has_seconds: true, has_weekly_and_monthly: true,
        supported_resolutions: SUPPORTED, volume_precision: 2, data_status: 'streaming',
      });
    } catch (e: any) { onError(e.message); }
  },

  async getBars(symbolInfo: any, resolution: string, periodParams: any, onResult: any, onError: any) {
    const { chain, address } = toRef(symbolInfo.ticker);
    const { to, countBack, firstDataRequest } = periodParams;
    try {
      const [{ data, meta }, k] = await Promise.all([
        sd<any[]>('/token/ohlcv', {
          chain, address,
          interval: RES[resolution],
          endTime: firstDataRequest ? undefined : to,          // unix seconds
          limit: Math.min(Math.max(countBack || 300, 1), 2000),
          quote: chain === 'solana' ? 'usd' : undefined,
        }),
        usdFactor(chain),
      ]);
      onResult(
        data.map((c) => ({
          time: c.time * 1000,                                  // TV bars want ms
          open: c.open * k, high: c.high * k, low: c.low * k, close: c.close * k,
          volume: c.volumeToken,                                // base-asset volume
        })),
        { noData: data.length === 0 && !meta.hasMore },
      );
    } catch (e: any) { onError(e.message); }
  },

  subscribeBars, unsubscribeBars,                               // next section
};
```

Two behaviors you get without writing anything: the time axis is dense (a quiet bucket is a flat candle at the previous close, so thin tokens still chart), and a token that graduated from a bonding curve draws one continuous series across the migration, curve history stitched in server-side.

Mounting the widget is standard TradingView:

```typescript theme={null}
new TradingView.widget({
  container: 'chart', library_path: '/charting_library/', autosize: true, theme: 'dark',
  symbol: 'solana:DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263', interval: '5' as ResolutionString,
  datafeed,
});
```

## Live bars

`subscribeBars` folds the trades stream: subscribe the token on the [`trades` channel](/streams/trades-stream) and fold each event into the forming bar, exactly the pattern of the [terminal guide](/guides/build-a-trading-terminal#make-it-live):

```typescript theme={null}
const subs = new Map<string, string>();   // TV uid -> stream sub id

function subscribeBars(symbolInfo: any, resolution: string, onTick: any, uid: string, _reset: any) {
  const { chain, address } = toRef(symbolInfo.ticker);
  const sec = intervalSeconds(resolution);
  let bar: any = null;
  subs.set(uid, stream.subscribe('trades', { chain, address }, async (t) => {
    const price = (t.volumeUsd && t.amountToken) ? t.volumeUsd / t.amountToken : null;
    if (price == null) return;
    const time = Math.floor(t.at / 1000 / sec) * sec * 1000;
    if (!bar || time > bar.time)
      bar = { time, open: bar?.close ?? price, high: price, low: price, close: price, volume: 0 };
    bar.high = Math.max(bar.high, price); bar.low = Math.min(bar.low, price);
    bar.close = price; bar.volume += t.amountToken;
    onTick(bar);
  }));
}
function unsubscribeBars(uid: string) { stream.unsubscribe(subs.get(uid)); subs.delete(uid); }
```

On a WebSocket reconnect, delivery is at-most-once: call the widget's `onResetCacheNeededCallback` and `chart.resetData()` so `getBars` re-seeds the series from REST, then re-subscribe.

## Lightweight Charts

The open-source package expects `time` in unix seconds: our candles drop in with no conversion at all.

```typescript theme={null}
import { createChart, CandlestickSeries } from 'lightweight-charts';

const chart = createChart(el, { autoSize: true });
const series = chart.addSeries(CandlestickSeries);

const { data } = await sd<any[]>('/token/ohlcv',
  { chain, address, interval: '1m', limit: 500, quote: chain === 'solana' ? 'usd' : undefined });
series.setData(data.map(({ time, open, high, low, close }) => ({ time, open, high, low, close })));
// live: series.update(bar) with the forming bar from subscribeBars above (time in seconds here)
```

## The details that bite

* **Time units.** REST candles and Lightweight Charts use unix seconds; Advanced Charts bars want milliseconds. The `* 1000` in `getBars` is the whole difference.
* **`pricescale` must be an integer power of ten.** Size it from the token's price at `resolveSymbol` time (helper above) or microcap charts render as a flat line.
* **Volume.** `volumeToken` is the base-asset volume, the number a TradingView volume pane expects. `volume` is the quote-side value if you prefer notional.
* **Paging depth.** Up to 2000 candles per `getBars` call, `limit` honoured however quiet the token is; historical pages are immutable and cached, so scroll-back stays fast.
* **Interval aliases.** The API also accepts `1min`, `5min`, `15min`, `30min`, `60`, `7d`, `30d` directly and echoes the canonical key in `meta.interval`, so a partial map still works during a migration from another provider.
* **Credits.** A `getBars` page is 5 credits; live bars bill 1 credit per delivered event, and `maxUpdatesPerMinute` on the subscription caps the burn of a busy token. Full grid on [Authentication & Limits](/authentication).

<CardGroup cols={2}>
  <Card title="Build a Trading Terminal" icon="desktop" href="/guides/build-a-trading-terminal">The rest of the terminal around this chart: discovery, tape, panels, portfolio.</Card>
  <Card title="Candles (OHLCV) reference" icon="chart-candlestick" href="/endpoints/candles-ohlcv">Every parameter and semantic of the endpoint behind the datafeed.</Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.