Data model
The chart does not read your database directly. It expects data in a few simple shapes. If your API returns these fields, everything else tends to work.
The four building blocks
| Concept | One-line meaning |
|---|---|
| Candle | One bar of history (open, high, low, close) |
| Tick | One live price update |
| Interval | How long each candle lasts (1h, 1d, …) |
| Instrument | Symbol info (name, decimals, available timeframes) |
You use candles + interval for history. Ticks for live updates. Instrument for labels and formatting.
Candle — one bar on the chart
A candle is a single time slice — like “what happened during this hour”:
type Candle = {
stamp: number; // when the bar opens (UTC, milliseconds)
o: number; // open
h: number; // high
l: number; // low
c: number; // close
v?: number; // volume (optional — use 0 if missing)
};
Example:
const candle = {
stamp: 1715472000000,
o: 101.2,
h: 103.1,
l: 100.9,
c: 102.8,
v: 3200,
};
Load many candles at once:
await chart.setMainSeriesData([candle, /* … */], interval);
Tip for your backend: return JSON in exactly this shape. Your frontend can pass it straight through.
Tick — one live price
When prices stream in (WebSocket, polling), you usually send ticks, not full candles:
type Tick = {
stamp: number;
price?: number; // simple last price
c?: number; // same thing, alternate field name
v?: number;
o?: number;
h?: number;
l?: number;
};
chart.appendTick({
stamp: Date.now(),
price: 102.45,
v: 120,
});
The chart merges ticks into the current candle or starts a new one based on stamp and your interval.
| Situation | Use |
|---|---|
| Initial page load | Candles → setMainSeriesData |
| Price just moved | Tick → appendTick |
| Bar fully closed | Candle → appendMainSeriesData |
Tutorial: Live data stream.
Interval — candle length
Tells the chart how wide each bar is in time:
const interval = {
symbol: "1h", // label for UI ("1 hour")
milis: 60 * 60 * 1000, // length in milliseconds
};
Pass it with your data:
await chart.setMainSeriesData(candles, interval);
When the user switches from 1h to 1d, fetch new candles and call setMainSeriesData again with the new interval.
Common values:
| UI label | symbol | milis |
|---|---|---|
| 5 minutes | "5m" | 5 * 60 * 1000 |
| 1 hour | "1h" | 60 * 60 * 1000 |
| 1 day | "1d" | 24 * 60 * 60 * 1000 |
Instrument — symbol metadata
Describes what you are charting — not the prices themselves:
const instrument = {
symbol: "BTCUSD",
currency: "USD",
precision: 2,
availableIntervals: [
{ symbol: "5m", milis: 5 * 60 * 1000 },
{ symbol: "1h", milis: 60 * 60 * 1000 },
],
};
Set at create time or later:
const chart = createChart({ container, instrument });
// or
chart.setInstrument(instrument);
precision controls decimal places on the price axis. availableIntervals can populate your timeframe dropdown.
Multiple symbols on one chart
Compare two assets (BTC vs ETH) on the same dates:
- First symbol = main series (usually candles).
- Others = overlays (usually lines).
Each symbol has its own id in getSeriesManager(). Style overlays with getChartInstrumentSettings() / applyChartInstrumentSettings().
Tutorial: Multi-instrument overlay.
Reference: Multi-instrument charts.
Scripts and series (when you add indicators)
Indicators and strategies read from series — computed lines derived from price. You do not manage those arrays by hand at first:
chart.addScript("EMA");
chart.addScript("RSI");
Under the hood, getScripts() lists available recipes and getSeriesManager() holds the data. Explore when you customize indicators: Customize a built-in indicator.
Data model cheat sheet
flowchart LR
API["Your API"]
Candles["Candle[]"]
Ticks["Tick"]
Chart["Chart runtime"]
API -->|"history"| Candles -->|"setMainSeriesData"| Chart
API -->|"live"| Ticks -->|"appendTick"| Chart
What is next?
- Rendering and scales — candles vs line, linear vs log scale
- Realtime updates — tick behavior in detail