Appearance

Personalize your experience

Mode
Accent Color
Direction
All Docs/QuaScript//
QuaScript logo

QuaScript Documentation

Language guide, engine API, 601 builtins and the preset library · v3.0.0-phase1

Performance

The engine compiles a script once and then runs a tight loop over the bars: every builtin is a C++ kernel with its state kept per call site, series live in flat column arrays, and nothing is allocated per bar on the hot path. This chapter shows what that costs in practice, where the time goes, and how to keep a large script fast.

The bar loop
The bar loop

Measured numbers

All numbers below were measured on an Apple M4 with tests/perf/million.py (synthetic one-minute bars, release build). Run it on your own machine with:

terminal.sh
python3 tests/perf/million.py --sizes 1000000,5000000,10000000 --runs 3

Compute per bar

quas bench runs the script without exporting anything. The cost per bar does not grow with the length of the data: 1 M, 5 M and 10 M bars give the same nanoseconds per bar.

Scriptns / barMillion bars / s
volume/volume_profile (visible-range profile)6.5156
volume/obv12.284
volatility/atr28.435
ma/sma39.325
momentum/rsi47.721
trend/supertrend59.617
momentum/macd75.813
volatility/bollinger_bands86.812
trend/ichimoku_cloud95.810
strategy/trend_breakout_atr119.48.3
ma/ribbon_gradient_cloud (12 averages, gradient fills)195.65.0
momentum/momentum_matrix (8 oscillators, a table)223.44.4
volume/session_volume_profile429.32.3
smc/market_structure_zones (zones as objects)812.61.2
stats/hurst_regime (rescaled range over a window)1201.90.8

In other words: a typical analysis processes one million bars in 30 – 100 ms; the heaviest presets, which walk lists of zones or windows on every bar, take about one second.

Full runs with output

quas run --keep-series also keeps every output value of every bar, and the JSON writer exports them. At one million bars:

ScriptRun msJSON export msJSON MB
volume/obv13218.6
trend/supertrend6511046.9
momentum/macd8212552
trend/ichimoku_cloud10120862.7
strategy/trend_breakout_atr12618855.3
charts/candlesticks_and_bars (7 outputs)80382125

Exporting text costs more than computing. The serve protocol therefore sends columns as little-endian float64 in base64 (encoding: "base64"), and the terminal only asks for the visible range (view).

Live sessions

quas serve keeps a session per chart. A tick re-runs only the forming bar from a checkpoint taken when that bar opened; a new bar runs one bar.

The forming bar
The forming bar

Measured over one million bars, 2000 ticks with a new bar every ten:

ScriptOpen (parse / run ms)Tick, engine p50 / p99 µsTick, round trip p50 µsNew bar, engine p50 µs
ma/sma38 / 491.6 / 10.4143.6
momentum/macd38 / 932.0 / 6.5154.1
volume/vwap34 / 601.9 / 2.8153.8
trend/ichimoku_cloud34 / 1091.8 / 3.8144.3
momentum/momentum_matrix33 / 2406.8 / 9.62110.5
strategy/trend_breakout_atr32 / 1255.9 / 8.3197.7
bot/ema_cross_bot32 / 646.0 / 10.1207.7
volume/session_volume_profile34 / 39523.5 / 34.23825.7
smc/market_structure_zones34 / 8063.6 / 1971743.3

A tick costs a few microseconds of engine time; the round trip through the pipe and JSON is 15 – 40 µs. The p99 of market_structure_zones is a tick on which a zone is created or mitigated, so the drawings are sent again.

The session protocol
The session protocol

Chart rendering

The terminal's chart library draws one million candles with setData in 37 ms, the first frame in 93 ms (WebGL) or 226 ms (canvas), and pans in about 2 ms per frame. Outputs whose color changes per bar use the canvas renderer.

Where the time goes

Approximate costs, derived from the benchmarks above:

CostTypicalNotes
A builtin call (ta.ema, ta.rsi)3 – 10 nsO(1) per bar: running sums, Wilder smoothing, monotonic deques for highest / lowest
A window statistic (stat.stdev, stat.linreg)5 – 15 nsrolling sums, O(1) per bar
stat.percentile, stat.percent_rank, stat.mode20 – 60 nsa sorted window, O(log n) insert and remove
stat.hurst, stat.entropy, stat.fractal_dim100 – 800 nsO(window) per bar
A history read (close[5])~1 nsring buffers sized by the compiler from the largest offset
A while loop over a listper elementeach iteration is interpreted; bound the list
Creating a drawing~100 nsobjects are capped by max_objects
A per-bar color~2 nsconstant colors are folded at compile time

Keeping scripts fast

  • Prefer builtins to loops. ta.highest(high, 50) is O(1) per bar; a for loop over the last 50 bars is 50 interpreted iterations.
  • Bound what grows. Shift old elements out of lists (if list.size(zones) > 30 { list.shift(zones) }) and keep max_objects as small as the chart needs.
  • Write on the last bar. Fill tables and labels inside if bar.is_last; the values are computed on every bar anyway.
  • Use constant arguments. Colors, lengths and strings that are constants are folded by the compiler; param.* values are constants too.
  • Pick the right statistic. stat.stdev is O(1); stat.percentile is O(log n); stat.hurst is O(n) per bar. Use the heavy ones with short windows, or on a higher interval with feed.series.
  • Avoid exporting what you do not draw. Every render.* call is an output that the terminal receives; compute helper values without rendering them.
  • Measure. quas bench script.qs --synth 1000000 gives the cost per bar. To find the expensive part, comment out half of the script and bench again; quas check --dis shows the compiled instructions and how many kernel states the script holds.

Limits that protect the host

LimitValue
Instructions per runthe larger of 2 × 10⁹ and 4000 per bar
Drawing objects per kindmax_objects, default 500, at most 2000
On-screen table size100 rows × 20 columns
Libraries per program16, nested at most 4 levels
Serve request size1 GiB
HTTP route2 000 000 bars per request

A script that exceeds a limit stops with LIMIT_EXCEEDED (naming the limit: instructions, loop, memory or collection) instead of blocking the worker.


© 2026 Quatick Technologies. All rights reserved.