Architecture

How it stays fast on an ordinary laptop.

Trading screens are heavy: long histories, ticks that never stop, many charts on one page. TradeCanvas does as little work as each frame needs, on the main thread, with no third-party code. This page shows how, and where it is still being improved.

Work in progress: the engine is fast enough to ship and not yet as fast as it will be. The last section lists what we know is left.

01

Small packages, one direction

Each package depends only on the ones below it. Nothing outside TradeCanvas runs in your page: the framework components take React, Vue or Svelte as peers, and the WebGL renderer is a separate chunk that loads only when a chart asks for it.

0 third-party runtime dependencies

02

The path of a tick

A price arrives from your feed and reaches the screen in one frame. Each stage does only the part that changed.

  1. 1Your feedDataAdapterWebSocket, REST, a broker: anything with history and a stream.
  2. 2Stream managerStreamManagerHistory first, then live. Answers from an old request are dropped; a silent feed is noticed within 30 s.
  3. 3Tick aggregatorTickAggregatorTicks go into a ring buffer of 1,000 and are folded into the bar of their timeframe.
  4. 4IndicatorsIndicatorEngineRecomputed from the last bar, not from the start of the series.
  5. 5ViewportViewportOnly the bars on screen are walked; the price scale comes from them alone.
  6. 6Render loopRenderLoopHowever many ticks arrive, the scene is drawn at most once per animation frame.
  7. 7Canvas 2D or WebGLRenderEngineTwo canvases, and a GPU canvas under them when WebGL is on.
03

A tick costs one bar, not the whole history

When a tick changes the last bar, each indicator resumes from that bar (and the few before it that its formula looks back on) instead of recomputing the series. If the bars it remembers no longer match, it falls back to a full recompute, so a correction is never missed.

About 20 indicators have their own incremental update, and 31 more are built on a base that resumes point by point. Indicators that read another indicator’s line resume too.

0 / 64 Measured on 100,000 bars with four indicators: about 0.001 ms a tick
04

Zoomed out: one column per pixel

When bars get narrower than a pixel, drawing each one wastes work the eye cannot see. Candles, bars, high-low and volume are drawn as one column per pixel, with the first open, the highest high, the lowest low and the last close of the bars under it. Lines and areas keep about two points per pixel (LTTB); indicator lines and bands draw one span per pixel column.

bars in view
313
bars per pixel
0.39
shapes drawn
313

A bar is never narrower than a quarter of a CSS pixel, so a chart holding a million bars shows at most about four bars per pixel when zoomed all the way out: about 6,400 on a 1,600 px chart. Panning through the rest is what the million-bar benchmark measures.

05

Two canvases, so a hover costs almost nothing

The scene (grid, series, indicators, drawings, orders) lives on one canvas; the crosshair, the legend, the axis labels and the countdown live on a thin canvas above it. Moving the mouse repaints only the top one. A data update repaints the scene once, in the next frame.

Every full-size canvas costs the browser one blend per frame, so the engine keeps them few: a back canvas for 2D-only drawing exists only while something needs it.

06

WebGL by recording, not by rewriting

The chart draws with Canvas 2D calls. With WebGL on, those calls go to a recorder that turns paths, fills and strokes into GPU commands in device pixels. Anything the GPU cannot draw the same stays on Canvas 2D, in order, so the picture is the same either way.

  • renderer: 'auto' asks the browser for a context without a major performance caveat, so a machine whose WebGL would run in software keeps Canvas 2D.
  • At most 8 charts on a page hold a WebGL context (setMaxWebGLCharts); the others wait in a queue and draw with Canvas 2D.
  • If the GPU drops the context, the chart carries on with Canvas 2D and rebuilds on WebGL when the context comes back.
  • Candles round to whole device pixels with the same rule in the shaders and on the CPU, so both renderers put them on the same pixels.
07

Built for weak machines

  • Canvas 2D by defaultWebGL is opt-in until it has been measured on more devices. On integrated graphics at a high pixel ratio the browser’s own compositing is the bottleneck, not the drawing.
  • One resize per frameResize events are batched into one per frame, and a size that did not change does nothing.
  • History in pagesOlder bars load 500 at a time as you scroll back, one request at a time, backing off after a failure.
  • No flash of loadingThe loading veil waits 200 ms, so a fast symbol switch never blinks.
  • Small to downloadThe headless chart is 176 KB gzipped, the full widget 315 KB, the WebGL renderer another 18 KB when used.
  • Boards that tick in placeThe bo-grid price board coalesces ticks to one patch per frame and repaints only the cells that changed.
08

What we are still improving

The engine is not finished. These are the costs we know of, from the source, and they are the next things to work on.

  1. Indicators update on every bar update, not once per frame: a feed that sends many ticks per frame recomputes more often than it draws.
  2. Bars are kept as an array of objects. A columnar store of typed arrays would use less memory and walk faster on very long histories.
  3. Everything runs on the main thread. A worker for indicator math exists in the code but is not wired into the chart yet.
  4. The depth heatmap keeps its snapshots in a plain array and rescans every level for its maximum on each frame; the volume profile allocates its buckets on each draw.
  5. Some objects are still allocated every frame (paths, small arrays), which gives the garbage collector work.
  6. At 2560×1400 and a pixel ratio of 2 on integrated graphics, compositing alone takes about 23 ms, so WebGL frames there average 29 ms.
Measure it on your machine →The audit runs these scenes in your browser: a million bars on Canvas 2D and WebGL, live ticks on 100,000 bars, and a 1,000-symbol board.