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

# Render a live chart

> Load candle history, then keep the last candle moving with the candles stream.

A live chart needs two things: history to draw, and updates to the bar that is still open. The OHLCV endpoint supplies the history; `spot.candles` supplies the live updates.

## Choose the series

| Parameter | Options | Pick |
| - | - | - |
| `timeframe` | `1s`, `5s`, `15s`, `30s`, `1m`, `3m`, `5m`, `15m`, `30m`, `1h`, `4h`, `6h`, `12h`, `1d`, `7d` | The bar size the user selected |
| `metric` | `marketCap` (default) or `price` | `marketCap` for new launchpad tokens, where supply is fixed and small prices are hard to read; `price` for established tokens |
| `denomination` | `usd` (default) or `native` | `usd`, unless your users trade in SOL, ETH or BNB terms |

Use the same three values for the history request and the subscription, or the stream updates will not line up with the bars you drew.

## 1. Subscribe, then load history

Subscribe first, and wait for the ack before you read history, so no update falls between the two.

```typescript theme={null}
import WebSocket from "ws";

type Candle = { time: number; open: number; high: number; low: number; close: number; volume: number };

const series = { chain: "solana", token: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263", timeframe: "1m", metric: "price", denomination: "usd" };
const headers = { Authorization: `Bearer ${process.env.API_KEY}` };

const socket = new WebSocket("wss://{{API_HOST}}/v1/stream", { headers });
const pending: Candle[] = [];
let bars: Candle[] | null = null;

socket.on("error", (error) => console.error("stream error:", error.message));

const subscribed = new Promise<void>((resolve) => {
  socket.on("message", (raw) => {
    const frame = JSON.parse(raw.toString());
    if (frame.type === "ack") resolve();
    if (frame.type !== "update") return;
    if (bars) upsert(bars, frame.data);
    else pending.push(frame.data);
  });
});

socket.on("open", () => {
  socket.send(JSON.stringify({ action: "subscribe", channel: "spot.candles", filters: series }));
});

await subscribed;

const params = new URLSearchParams({ timeframe: series.timeframe, metric: series.metric, denomination: series.denomination, countBack: "500" });
const response = await fetch(`https://{{API_HOST}}/v1/tokens/${series.chain}/${series.token}/ohlcv?${params}`, { headers });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const history = await response.json();

const loaded: Candle[] = history.candles;
for (const candle of pending) upsert(loaded, candle);
bars = loaded;
draw(bars);
```

## 2. Update the open bar

Each stream update is the current state of one bucket. The open bucket arrives again and again as trades land. Replace the bar with the same `time`, or append a new one:

```typescript theme={null}
function upsert(bars: Candle[], candle: Candle) {
  const last = bars[bars.length - 1];
  if (last && last.time === candle.time) bars[bars.length - 1] = candle;
  else if (!last || candle.time > last.time) bars.push(candle);
  draw(bars);
}
```

## Gaps are normal

A bucket with no trades is not sent, in history or on the stream. A quiet token has gaps between bars. Most charting libraries handle gaps; if yours needs a bar per bucket, fill each gap with a flat bar at the previous `close` and zero volume.

## Load older history

When the user scrolls left, request the bars before the oldest one you hold. `to` is exclusive, so the oldest bar is not repeated:

```typescript theme={null}
async function loadOlder(bars: Candle[]) {
  const params = new URLSearchParams({ timeframe: series.timeframe, metric: series.metric, denomination: series.denomination, countBack: "500", to: String(bars[0].time) });
  const response = await fetch(`https://{{API_HOST}}/v1/tokens/${series.chain}/${series.token}/ohlcv?${params}`, { headers });
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  const older = await response.json();
  bars.unshift(...older.candles);
}
```

An empty `candles` list means you reached the start of the token's history.

## TradingView datafeeds

The OHLCV endpoint follows the TradingView datafeed contract. In `getBars`, map `periodParams.countBack` to `countBack` and `periodParams.to` (seconds) to `to` in milliseconds. In `subscribeBars`, subscribe to `spot.candles` and pass each update to the `onTick` callback.

## Switching series

To change timeframe or metric, unsubscribe using the `subscriptionId` from the ack, subscribe with the new values, and reload history.


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