Tick Stream
Standalone tick-by-tick view — every trade plotted on a high-resolution time × price grid, with optional VWAP line, large-trade markers, micro book heatmap, and depth lens. Its own canvas, its own engine.
The Tick Stream module renders individual trades as dots on a time × price grid — a "trade tape" view that lets traders read order-flow at sub-second resolution. Optional overlays add VWAP, large-trade emphasis, a micro book heatmap, and a depth lens around the latest price.
price ↑
▴ ▴ ● ← large buy
◦◦ ●◦
◦ ◦ ◦
━━━━━━━━━━━━━━ VWAP
◦◦◦ ◦
◦ ● ▾ ▾ ▾ ← large sell trail
t →
(1-5 min lookback, 50-100 ms cell)
Use this when:
- You want to see the order flow before the candle closes — the tick view shows every fill, not the OHLCV summary.
- You need a scalper / DOM-trader companion view alongside the main chart.
- You're building a replay tool for recorded tape data.
For aggregated bid×ask cells on top of the candle, use Footprint Chart on the main chart instead. Tick Stream is for the rawest view possible — every print as a discrete pixel.
Install & import
import { createTickBridge } from '@mrd/chart-engine'
Attach to a canvas
Dedicated canvas — not shared with any other engine.
<div class="tick-wrap" style="width: 480px; height: 360px;">
<canvas ref="tickCanvas" style="width: 100%; height: 100%;"></canvas>
</div>
The tick view works at any aspect ratio but a slightly wider-than-tall frame (4:3 or 16:9) reads best because time runs horizontally.
Mount
const tick = await createTickBridge(canvas, {
tickSize: 0.5,
pricePrecision: 2,
theme: 'dark',
lookbackMs: 5 * 60 * 1000, // 5 minutes of history
cellMs: 1_000, // 1-second cells
showTradeTape: true,
showBookHeatmap: true,
showVwap: true,
showGrid: true,
})
tick.start()
Options
| Option | Type | Default | Description |
|---|---|---|---|
tickSize | number | 1 | Price increment for the y-axis grid. |
pricePrecision | number | 2 | Decimals on the price axis. |
theme | 'dark' | 'light' | 'dark' | Initial theme. |
lookbackMs | number | 300_000 | How far back in time the view extends (5 min default). |
cellMs | number | 1_000 | Horizontal cell width in milliseconds. 100 for fast scalping, 1000-5000 for slower review. |
showTradeTape | boolean | true | Render individual trade dots. |
showBookHeatmap | boolean | true | Render the micro book heatmap behind the tape. |
showVwap | boolean | true | Render the VWAP line. |
showGrid | boolean | true | Render axis gridlines. |
isMobile | boolean | auto | Force-mobile rendering tuning (smaller fonts, reduced cell count). |
lowPower | boolean | false | Battery-saver mode — caps frame rate on backgrounded laptops. |
cellMsandlookbackMsare linked. The engine clampscellMs ≥ 20andlookbackMs ≥ cellMs. Pick a cell that matches your tape density — too small and the grid is noise, too large and individual trades pile up in a single cell.
Push data
Two streams: book snapshots (for the micro-heatmap and price-line) and trades (for the tape).
tick.pushBook(t_ms, bidsFlat, asksFlat, mid)
Push a coalesced book diff. The engine internally batches per render frame — calling at 100 Hz is safe, only the latest snapshot per frame is read by the renderer.
| Argument | Type | Description |
|---|---|---|
t_ms | number | Unix milliseconds of the snapshot. |
bidsFlat | Float64Array | [price, qty, price, qty, …] flat layout. |
asksFlat | Float64Array | Same flat layout for asks. |
mid | number | Mid price at snapshot time. |
const bidBuf = new Float64Array(40 * 2)
const askBuf = new Float64Array(40 * 2)
ws.on('depth', (d) => {
// ... fill bidBuf / askBuf from d ...
tick.pushBook(d.ts, bidBuf, askBuf, d.mid)
})
tick.pushTrade(t_ms, price, qty, side)
Append one aggressive trade — the most-called method on this module. The engine accumulates trades per frame into a single internal batch.
| Argument | Type | Description |
|---|---|---|
t_ms | number | Unix milliseconds. |
price | number | Trade price. |
qty | number | Trade size. |
side | number | 1 = aggressive buy (taker hit ask), -1 = aggressive sell. |
ws.on('trade', (t) => {
tick.pushTrade(
t.ts,
t.price,
t.qty,
t.isBuyerMaker ? -1 : 1,
)
})
tick.pushTradesFlat(flat)
Bulk-append from a flat array — useful when backfilling from a REST historical-fills endpoint at mount time, or when replaying recorded tape.
| Argument | Type | Description |
|---|---|---|
flat | Array<number> or typed array | [t_ms, price, qty, side, t_ms, price, qty, side, …] — quadruplets. |
const historicalTrades = await fetch(`/api/trades?symbol=${sym}&from=${start}`).then(r => r.json())
const flat = new Float64Array(historicalTrades.length * 4)
for (let i = 0; i < historicalTrades.length; i++) {
flat[i * 4] = historicalTrades[i].ts
flat[i * 4 + 1] = historicalTrades[i].price
flat[i * 4 + 2] = historicalTrades[i].qty
flat[i * 4 + 3] = historicalTrades[i].isBuyerMaker ? -1 : 1
}
tick.pushTradesFlat(flat)
Large bulk loads (≥ 10 k trades) hit the renderer in one frame — for very long backfills, slice into chunks of ~5 k and call across multiple animation frames if you see jank.
Display controls
| Method | Effect |
|---|---|
tick.setLookback(lookbackMs, cellMs) | Resize the time window and cell width. Re-binning the tape buffer is automatic. |
tick.setTickSize(t) | Y-axis grid spacing. |
tick.setPricePrecision(d) | Y-axis label decimals. |
tick.setTzOffsetSeconds(secs) | Shift the time axis to a non-UTC timezone (e.g. trader's local). |
tick.setShowTradeTape(v) | Trade-dots layer. |
tick.setShowBookHeatmap(v) | Micro book heatmap behind the tape. |
tick.setShowVwap(v) | VWAP line. |
tick.setShowGrid(v) | Grid lines. |
tick.setShowVolume(v) | Per-cell volume profile. |
tick.setShowPriceLine(v) | Last-price horizontal. |
tick.setShowDepthLens(v) | Local depth lens around the latest price. |
tick.setHeatmapPalette(idx) | Switch the micro-heatmap colormap (integer enum, 0-based). |
tick.setHeatmapIntensity(min, max) | Static intensity clamp; (0, 0) enables adaptive range. |
tick.setLargeTrade3d(enabled) | Emphasise large trades with a 3-D dot effect. |
tick.setLargeTradeScale(scale) | Scale factor for the large-trade emphasis. |
tick.setShowLatestProfile(v) | Render the latest-cell volume profile as a side histogram. |
tick.setFollowLive(v) | Auto-scroll to keep "now" at the right edge. |
tick.setAutoScaleY(v) | Auto-scale the y-axis to current price range. |
tick.setTheme('dark' | 'light') | Theme switch. |
Read state
| Method | Returns |
|---|---|
tick.getLastMid() | Last received mid price. |
tick.clearData() | Wipe the entire tape and book history. |
Lifecycle
const tick = await createTickBridge(canvas, options)
tick.start()
tick.resize() // call on parent CSS-size changes (ResizeObserver)
tick.stop()
tick.destroy() // permanent
A canvas owned by createTickBridge cannot be reused for another engine. To swap symbol, call clearData() + setTickSize() on the same instance.
Common pitfalls
Trades arrive but no dots appear.
Check the side argument convention — most exchanges ship isBuyerMaker: bool, NOT isBuy. Pass side = isBuyerMaker ? -1 : 1. Calling pushTrade(_, _, _, 0) is a no-op (the engine reads 0 as "unknown side, skip").
Tape and chart pan out of sync.setLookback actively rebins the tape — it is not the right method to call when the user pans. The user's pan is local view-state and the engine handles it internally. Call setLookback only when you change the time-resolution model (e.g. user picks "5m / 50ms" vs "30m / 500ms").
Backfill blanks out the live tape.pushTradesFlat is additive — trades append to the existing buffer. If you call it with old trades that fall outside the current lookbackMs window, they get dropped silently. Make sure the timestamps you backfill overlap the current window.
cellMs change wipes the chart.
Documented behavior — changing the time-resolution rebins the entire buffer. The "no-op when unchanged" guard inside setLookback covers spurious re-applies, but a real change clears the tape. Inform the user with a brief loading state.
Mobile rendering looks chunky.
The engine auto-detects mobile-class hardware and downscales. If you're on a desktop with a coarse pointer (e.g. touchscreen laptop), set isMobile: false at mount to force the desktop renderer.
Large-trade dots disappear at higher zoom.
The 3-D effect scales with the trade's relative size on screen — at very tight time/price zoom, even big trades occupy a few pixels and lose the emphasis. Bump setLargeTradeScale(1.5) for trader-feedback-heavy views.
Next
- Footprint Chart — same per-trade philosophy but aggregated into bid×ask cells inline on each candle.
- DOM Ladder — vertical price ladder, same data layer as tick stream, different view shape.
- Orderbook · Depth Profile — single-venue order-flow view with signal overlays, OR multi-exchange depth profile with horizontal liquidity bars + labeled walls.