Lumen ChartsLumen Charts
Connect data

Realtime subscriptions

Push live bar updates through subscribeBars and stop them on unsubscribe.

Download Markdown

subscribeBars hands you a subscriber ID and a callback. Keep separate state for each ID, because several charts can subscribe at the same time, to the same data or to different data.

Choose your data path

The chart works with any data provider and any connection type. Your adapter takes care of the socket, sign-in, reconnects, symbol mapping and cleanup. The chart only ever receives complete bars, never raw WebSocket messages.

Provider sendsAdapter doesChart receives
Raw trades or quotesNormalize ticks, then call createTickCandleAggregatorComplete updated OHLCV bar
Complete candles for the selected resolutionNormalize and validate candles; skip tick aggregationComplete updated OHLCV bar
Raw ticks → normalize → tick-to-candle helper → onRealtime(bar) → chart
Complete candles → normalize/validate → onRealtime(bar) → chart

Load the starting history with getBars before you send live updates. History and live updates need to agree on the symbol, the price basis, where intervals start and end, and Unix millisecond timestamps. Send each event to the subscription with the matching symbol and resolution, and keep separate state for each subscriber ID.

Example: raw ticks

This M5 (5-minute) example starts the current candle from the last history bar, then updates it on every accepted tick. transport.subscribe is an adapter method that supplies normalized ticks and returns a cleanup function. lastHistoryBar and onRealtime come from your datafeed's history and subscription lifecycle.

const candles = LumenChartingLibrary.createTickCandleAggregator({
  intervalMs: 5 * 60_000,
  volumeMode: 'incremental', // Each trade supplies a volume delta.
});
candles.reset({ ...lastHistoryBar, volume: lastHistoryBar.volume ?? 0 });

let active = true;
const stop = transport.subscribe(symbol, tick => {
  if (!active) return;
  // tick = { time: Unix milliseconds, price: trade price, volume: trade size }
  const update = candles.push(tick);
  if (update) onRealtime({ ...update.bar });
});

function unsubscribe() {
  active = false;
  stop();
}

If your feed only sends quotes, use volumeMode: 'none' and stick to one price basis, such as the bid/ask midpoint. Don't pass a running (cumulative) volume total as if it were the volume of a single tick.

With M5, a tick at 10:02 updates the candle that started at 10:00. The open stays the same, high and low stretch to the extremes, and close follows the latest price. The first tick at or after 10:05 starts a new candle and returns the finished one as closedBar. If no ticks arrive, no candle is created: there's no timer. M1 works the same way with intervalMs: 60_000.

See Build candles from ticks for the full subscribeBars implementation, provider normalization, calendar alignment, and reconciliation.

Example: complete candles

If your provider already streams complete candles for the timeframe the chart asked for, pass them straight to the callback. Don't turn their close prices into fake ticks, because you'd lose the open, high, low and volume.

In this example, the transport subscribes to the requested symbol and resolution. It checks that the OHLCV fields are numbers and converts timestamps before calling its handler. Keep provider-specific field names inside the transport.

let active = true;
let latestTime = lastHistoryBar.time;
const stop = candleTransport.subscribe(symbol, resolution, candle => {
  if (!active || candle.time < latestTime) return;
  latestTime = candle.time;
  onRealtime({
    time: candle.time, // Candle START in Unix milliseconds, not arrival time.
    open: candle.open,
    high: candle.high,
    low: candle.low,
    close: candle.close,
    volume: candle.volume,
  });
});

function unsubscribe() {
  active = false;
  stop();
}

Pass along every update to the candle that's still forming. Don't wait for the provider to mark it “closed”. If your feed only sends finished candles, the chart can't show price movement inside a bar this way. If the feed sends M1 candles but the chart asks for M5, combine them into M5 candles first, keeping the highs, lows and volume. Never pass M1 bars off as M5 bars.

Update the current bar

Whenever the price or volume changes, send the whole current OHLCV bar. A bar with the same timestamp replaces every OHLCV value of the last bar. It isn't a partial update of just the changed fields. A newer timestamp starts the next bar. Don't use the realtime callback to correct older history.

Unsubscribe reliably

const subscriptions = new Map();
function unsubscribeBars(id) {
  subscriptions.get(id)?.close();
  subscriptions.delete(id);
}

If several subscribers share a connection, count how many are using each underlying feed, but keep each subscriber's state separate. When the last one leaves, release the timers, sockets and listeners. Ignore any events that arrive after unsubscribing.

Historical corrections

When older data changes, use the documented cache reset and reload flow. After the provider reconnects, bring the current bar up to date before you resume updates. Never fill a gap with made-up market bars.

The example below updates a sample bar once per second. It only simulates live updates; it isn't connected to a real market.

Loading chart… · Fixed sample period ending 15 October 2026.

On this page