Performance
How the chart renders and what it costs — canvas layers, indicator caching, request de-duplication, the caps that protect a long-running session, and which slowdowns are not the chart's fault.
Chart Engine draws every frame in the browser, on the same thread as the rest of the page. That is the shape of the thing, and every number below follows from it. Most installs never need to think about any of this; the ones that do are running many indicators on low-end hardware, or diagnosing a chart that has become sluggish after hours on the same tab.
How a frame is drawn
The chart stacks two canvases, each with a 2D context:
| Layer | Carries |
|---|---|
| Main | Grounds, grid, volume, candles, indicators, drawings, order markers, algo levels |
| Overlay | Crosshair, hover tooltips, drawing handles, and all pointer input |
Splitting them is what makes crosshair movement cheap: moving the mouse repaints the overlay only, and the candles underneath are untouched.
Redraws are driven by animation frames, and they happen on a change, not on a timer — a price tick, a pan, a zoom, a resize, a new indicator, or a change to the order list. An idle chart on an idle market is doing nothing at all.
Canvas memory is width × height × DPR² × 4 bytes, so a display reporting DPR 3 would cost more than twice as much memory as DPR 2 for a difference nobody can see on a candlestick. The chart clamps it. On a high-density tablet this is the single largest saving it makes.
Indicator calculation
Indicator maths runs on the main thread, in front of a cache tuned for streaming data:
| Property | Value |
|---|---|
| Cache entries | 200, least-recently-used evicted first |
| Entry lifetime | 2 minutes |
| Cache key | Indicator id, type and its full parameter set |
| Invalidation | A hash of the candle series |
The hash is the interesting part. It covers the series length, the first candle's timestamp, the last confirmed candle, and the newest candle's timestamp — and deliberately not the newest candle's price. A tick that only moves the current close therefore changes nothing, and indicators recalculate when a new candle forms or when history reloads, not on every frame.
The practical guidance:
- Overlay indicators are nearly free. They add lines to a canvas that is being drawn anyway.
- Panel indicators cost vertical space and a redraw each. Six or seven active indicators is comfortable on a laptop; twenty is not, and the symptom is a crosshair that lags the pointer.
- Heavy indicators are heavy. Volume profile, market profile and the harmonic pattern detectors examine far more of the series than an EMA does. One of them is fine; three at once on a 2,000-candle window is noticeable.
Network behaviour
The chart is deliberately conservative about how often it asks the server for anything.
| Guard | Effect |
|---|---|
| In-flight de-duplication | An identical request already running is not re-issued |
| 2-second window | The same symbol and timeframe requested twice inside 2s is skipped |
| 30-second window | After a successful fetch, the same pair is not refetched for 30s |
| Pan debounce | At most one history request every 300 ms |
| Client cache | 5-minute lifetime, 10 entries, keyed by symbol, timeframe and hour bucket |
Stale responses are discarded rather than drawn: if the symbol, the timeframe or the zoom anchor changed while a request was in flight, the answer is thrown away. Clicking rapidly through timeframes cannot leave you looking at the wrong market's candles, and it does not multiply the work either.
The chart can preload the timeframes either side of the one you are on. It does not, because the benefit — roughly 100 ms saved on a timeframe switch — did not justify doubling or tripling the chart requests every install makes on every page load. If your traders complain that switching timeframes has a beat of delay, that beat is this trade-off.
Memory over a long session
Three caps stop a tab that has been open all day from growing without bound:
| Cap | Value |
|---|---|
| Candles held in memory | 2,000 — older ones are dropped as newer arrive |
| Algo levels drawn | 240 |
| Algo trade legs drawn | 120 |
| Price alerts stored | 50 per browser |
| Chart templates stored | 20 per browser |
| Client candle cache | 10 entries, expiring after 5 minutes |
The 2,000-candle ceiling is why panning a long way back and then jumping to the present refetches rather than scrolling instantly — the old candles were dropped. That is the intended trade: a trader who pans through a year of 1-minute candles would otherwise be holding half a million objects.
The algo caps matter on the bot terminal. A grid bot that has been running for a month has thousands of fills, and drawing all of them would make the chart unusable at exactly the moment the operator most needs it responsive. Past 14 levels, the chart also stops labelling everything and labels only the band edges, the rungs holding inventory and the six nearest the price.
What each panel costs while it is open
| Panel | Cost |
|---|---|
| Indicators | One calculation per indicator per new candle, cached |
| Signals | Re-aggregates every active indicator's signals |
| Patterns | Scans the visible candle window |
| Divergence | Pivot detection against up to six indicators |
| Multi-timeframe | Six extra chart requests, refreshed every 5 seconds, cached 2 minutes with a 10-second floor |
| Heatmap | Buckets the visible volume — cost scales with the bucket count, default 20 |
| Replay | Drives a frame loop at the chosen speed; 10x is ten times the work of 1x |
Multi-timeframe is the one to watch on a busy install. It fetches only while open, but six timeframes is six endpoints, per trader, and the cache is the only thing between that and a request storm. It is a panel to open when you need it, not one to leave pinned.
Sizing follows the screen
Nothing is a fixed constant. The chart measures its own container and derives everything from a pixels-per-candle target:
- 8 px per candle fully zoomed out
- 12 px per candle at rest
- 25 px per candle fully zoomed in
From those it computes how many candles fit, fetches 1.5 times the zoomed-out count for the opening window, and requests half that count (minimum 30) each time you pan into unloaded history. A phone therefore downloads and draws far less than a 4K monitor, without any device detection.
Zooming out past the maximum candle count promotes the chart to the next timeframe up; zooming in past the minimum demotes it. That is what keeps candles readable instead of letting them collapse into a solid block.
Mobile
Touch support is enabled by the host page. Pinch-to-zoom and single-finger panning are handled by the chart, using incremental deltas rather than absolute positions so a drag stays smooth. Browser gesture handling is suppressed on the overlay canvas, which is why the page does not scroll while you pan the chart.
Two things help on a phone: fewer panel indicators, since vertical space is the binding constraint, and the compact layout the spot and futures modes already use.
Slowdowns that are not the chart
Historical candles arrive over HTTP and live updates over a WebSocket. If the socket never connects — a reverse proxy that does not forward the upgrade is the usual reason — the chart renders perfectly and then freezes. Nothing about rendering will fix it. See Data sources.
If the exchange provider has rate-limited or banned the install, the candle endpoint returns whatever is in its cache and no error. The chart is drawing exactly what it was given.
That is the server-side fetch, not the client. /api/exchange/chart abandons a
request after 20 seconds; a market that regularly approaches that is an upstream
problem. Once the disk cache holds 90% of the expected bars and its newest candle
is recent, the endpoint answers without touching the exchange at all.
The chart shares a main thread with everything else the page runs. An order book streaming at high frequency, or a table re-rendering on every tick, will slow the chart down and the chart will get the blame.
Next
- Data sources — the request behaviour behind these numbers.
- Troubleshooting — symptom-first diagnosis.