Charts
Concepts

Streams and history

Subscribe to bars, order flow, trades, the order book and your own data, with history.

subscribe returns named streams. The handler of the same name in on gets each event of that stream, and ctx.streams.<name> holds what it received so far.

subscribe: (ctx) => ({
  symbol: ctx.market.symbol(),
  h4: ctx.market.bars({ timeframe: "4h", warmup: 50 }),
  m1: ctx.market.bars({ timeframe: "1m" }),
  big: ctx.market.trades({ minSize: 50, history: "1d" }),
}),
on: {
  h4: (s, { bar, confirmed }) => { /* 4h bars */ },
  m1: (s, { bar }) => { /* 1m bars */ },
  big: (s, trade) => { /* trades of 50 and more */ },
},

Each stream has its own history, so one script can read confirmed 4h bars, 1m bars and every trade at once. Symbol and timeframe default to the chart's.

Streams

BuilderHandler getsctx.streams.<name>Notes
ctx.market.bars({ symbol, timeframe, warmup })BarEvent: { bar, confirmed }BarsView: typed arrays time, open, …, length
ctx.market.orderFlow({ symbol, timeframe, warmup })OrderFlowEvent: bid and ask volume per price of a barOrderFlowView: bars"1m" or coarser
ctx.market.trades({ symbol, minSize, history })Trade: time, price, size, aggressorTradesView: typed arraysminSize filters at the source
ctx.market.book({ symbol, history })BookSnapshot: bids and asksBookView: latest, simulatedSimulated for now, simulated is true
ctx.market.symbol({ symbol })SymbolInfo, onceSymbolView: infotick size, point value, sessionOf(time)
ctx.data.poll({ url, every, schema })DataEvent: Value or ErrorDataView: latestYour API, through our proxy
ctx.data.websocket({ url, onOpenSend, schema })DataEventDataView

Full types: Streams reference.

History

  • bars and orderFlow load the chart's time range: from the chart's first loaded bar to now.
  • warmup loads more before that: a bar count (warmup: 100), a duration (warmup: "2d") or "session", back to the open of the session the range starts in. Use it for indicators that need past bars, e.g. a 200 bar average.
  • When the chart loads older bars (scrolling left), each stream loads the same older range plus its warmup and sends it as backfill: a bar can arrive that is older than bars the handler already has. Insert by time, or skip older bars on purpose.
  • trades and book load history, a duration back from now. Without it they start live.
  • Missing history stays missing.

The forming bar

A bar that has not closed arrives again on every update with confirmed: false, then once with confirmed: true. Replace the last entry when the time repeats, and commit sums only on confirmed:

if (s.points.at(-1)?.time === bar.time) s.points[s.points.length - 1] = point;
else s.points.push(point);

if (confirmed) s.sums = sums;

Order flow works the same way, and a bar can arrive again with more levels: the last event of a time holds all its volume.

Status

Market views have a status:

  • Loading: nothing loaded yet.
  • Ready with from: data is complete from from to now.
  • LoadingOlder with from: complete from from, older history still loading.
  • Unavailable with error: the stream failed, e.g. NoAccess when the data feed has no order flow. Nothing more arrives.

Check status before trusting a range, e.g. show "Waiting for order flow" for a range that starts before from. A failed stream also shows in the indicator list. The other streams keep running.

Your own data (poll, websocket) has no status: errors come to the handler as { type: "Error", error }, so the script decides what to do.

Pull access

Handlers get events one by one. draw, legend and event handlers can look back without copying:

const { time, close, length } = ctx.streams.bars;

for (let i = 0; i < length; i += 1) {
  // close[i] is the close of bar i. Arrays can be longer than `length`: stop at `length`.
}

On this page