
QuaScript Documentation
Language guide, engine API, 601 builtins and the preset library · v3.0.0-phase1
Engine API
Three ways to run a script: the quas command line, the quas serve worker protocol, and the terminal's HTTP endpoints (which use quas serve behind the scenes). All three return the same result format, described at the end of this chapter.
Command line
| Command | Does |
|---|---|
quas run <script.qs> (<data.csv> | --synth N) | runs the script, JSON result to stdout (or --out FILE) |
quas check <script.qs> | compiles; prints the outputs, --json prints diagnostics + schema, --dis the bytecode |
quas bench <script.qs> [--synth N] [--runs R] | best-of-R timing in ns/bar; --keep-series also times the JSON export; --json for scripts |
quas sweep <script.qs> (<data.csv> | --synth N) --param id=from:to:step ... | parameter grid search over threads |
quas doc [--json] | lists every builtin; --json prints the full catalog (arguments, defaults, docs) |
quas serve | the long-lived worker described below |
| Option | Meaning |
|---|---|
--set id=value | parameter override (repeatable) |
--asset key=value | instrument: ticker exchange kind currency base_currency timezone tick_size point_value lot_size min_qty price_precision interval |
--interval 15 | shorthand for --asset interval=15 |
--live-from N | bars with index >= N are live (bot signals are emitted from there) |
--forming-last | the last bar is a forming bar |
--bot-id user:strategy | the identity used in bot signal ids |
--lib-dir DIR | extra library folder (repeatable) |
--no-bars | leave the input bars out of the JSON |
--seed S | seed of --synth data |
--threads N --top K --sort net|pf|win|dd|sharpe | sweep options |
Exit status: 0 success, 2 compile or run error (the message is on stderr; compile errors list line:col code: message and the fix when there is one).
The `quas serve` worker
quas serve reads one JSON request per line on stdin and writes one JSON response per line on stdout, in order. Compiled programs are cached by content (the script and its libraries), and sessions keep the engine state of one script on one chart alive, so new bars and price updates execute only the bars that changed.
Every response has id (echoed from the request), ok, op, ms (time in the worker) and timing: {parseMs, execMs, encodeMs}. A failure is {"ok": false, "error": {"code": "COMPILE" | "LIMIT_EXCEEDED" | "REQUEST", "message": ..., "diagnostics": [...]}}.
open
{"op": "open", "id": 1, "session": "chart-1:ema",
"script": "analysis \"EMA\" { overlay = true }\nrender.line(ta.ema(close, 20), \"EMA\")\n",
"libraries": [{"name": "viz/palette", "source": "library \"viz/palette\" ..."}],
"bars": {"time": [1700000000, 1700000060], "open": [100, 101], "high": [102, 103],
"low": [99, 100], "close": [101, 102], "volume": [500, 450]},
"params": {"length": 20}, "asset": {"ticker": "BTCUSDT", "timezone": "UTC", "tick_size": 0.1},
"formingLast": true, "encoding": "json", "view": {"from": 0, "to": 1}}| Field | Meaning |
|---|---|
session | the caller's id for this script on this chart; opening an existing id replaces it |
bars | columns of equal length; numbers or null; host holds extra columns ({"bar.delta": [...]}); book holds the order book (below) |
params, asset | as --set and --asset |
formingLast | the last bar is still forming (it can then be updated with update_last) |
liveFrom | first live bar (default: no history bar is live) |
botId | user:strategy for bot signal ids |
encoding | json (default) or base64: per-bar columns travel as base64 little-endian float64 (bars in, plots out), about 2x faster for large histories |
view | the visible bar range, for period = "visible" profiles |
includeBars | also return the bars (barData) |
bars.book carries the real order book of each bar, one snapshot (or null) per bar:
"book": [
{"bids": [[100.9, 12, 3], [100.8, 30]], "asks": [[101.1, 8, 1], [101.2, 25]]},
{"bids": [[101.0, 5]], "asks": [[101.2, 9]]},
{"orders": [["bid", 101.0, 2, 9001], ["ask", 101.3, 4, 9002]]},
null
]A level is [price, size, orders?] (or {"price", "size", "orders"}), best price first: one level a side is L1, more are L2. orders (L3) lists individual orders as [side, price, size, id?] (side -1 / 1 or "bid" / "ask" / "buy" / "sell"); when a snapshot has orders and no levels, the levels are aggregated from the orders. append takes the same field; update_last takes one snapshot as "book" on its bar and replaces the forming bar's book. schema.hostData lists the host data the script reads (order_book, order_flow, derivatives, fundamentals) and schema.bookDepth the depth it needs (1 L1, 2 L2, 3 L3), so a host sends the book only to scripts that use it.
The response has session, key (program hash), schema (the outputs, see below), bars, from (0), forming and the outputs of every bar.
append, update_last, view, close
{"op": "append", "session": "chart-1:ema", "formingLast": true,
"bars": {"time": [1700000120], "open": [102], "high": [102], "low": [102], "close": [102], "volume": [0]}}
{"op": "update_last", "session": "chart-1:ema",
"bar": {"time": 1700000120, "open": 102, "high": 103.5, "low": 101.8, "close": 103.1, "volume": 220}}
{"op": "view", "session": "chart-1:ema", "from": 400, "to": 900}
{"op": "close", "session": "chart-1:ema"}appendadds newer bars. A first bar whose time equals the last bar's time replaces it (that is how a forming bar is confirmed with its final prices).update_lastreplaces the prices of the forming bar and re-executes only that bar.- The response of
append/update_lastcontains the outputs of barsfrom..bars - 1only: replace your copy from indexfromon. Strategy trades come fromtradesFromon, the equity curve fromequityFromon, bot signals only for the new bars. viewrecomputes visible-range profiles only.- Other ops:
run(open + close in one, no session),check(compile only:valid,schema,symbols,diagnostics),stats(sessions, cached programs, cache hits),ping.
HTTP endpoints (terminal)
All endpoints take and return JSON. session and check require a signed-in user (bearer token or the session cookie) and answer 401 otherwise; session ids are private to the user.
POST /api/quascript/session
The worker protocol above, one operation per request. The body is the request object (op, session, ...); libraries named by use are added from the bundled library folder when the client does not send them. The response is the worker's response, unchanged.
| Status | When |
|---|---|
| 200 | ok: true |
| 400 | compile error, invalid request, unknown op |
| 401 | not signed in |
| 409 | SESSION_LOST: the session is unknown (expired or its worker restarted); send open again |
| 413 | more than 2,000,000 bars |
| 422 | LIMIT_EXCEEDED |
| 503 / 504 | worker exited / timed out |
POST /api/quascript/check
{ "script": "...", "libraries": [...] } → { ok, valid, key, schema, symbols, diagnostics }. Each diagnostic has code, severity, message, note, line, col, endLine, endCol and fixes: [{line, col, endLine, endCol, text}]. Used by the editor for underlines and quick fixes.
POST /api/quascript/run
One complete run in the chart's format: { script, bars: [{time, open, high, low, close, volume}], params, libraries, asset } → { success, data } where data has plots (with data: [{time, value, color?}]), markers + markSites, hlines, fills, candleColors, backgrounds, drawings, alerts, strategy and meta.timing. Settings are matched to the script's parameters by id or title. Use session for live charts; run recomputes everything.
Configuration
| Variable | Default | Meaning |
|---|---|---|
QUAS_BIN | packages/quascript/build/quas | engine binary |
QUAS_WORKERS | half the CPU cores (1 .. 8) | worker processes |
QUAS_REQUEST_TIMEOUT_MS | 60000 | per request; a stuck worker is restarted |
QUAS_LIBRARY_DIR | packages/quascript/presets/library | bundled libraries |
Result format (schema version 2)
A full result (quas run) is one JSON object:
| Key | Content |
|---|---|
schema | version, kind, title, overlay, render, maxObjects, params, plots, marks, levels, bands, backgrounds, profiles, alertRules, feeds, grid, warnings |
meta | bars, seconds, instructions, hasStrategy, runtimeWarnings |
bars | the input columns (unless --no-bars) |
plots | one array per plot, one value per bar (null = na) |
plotColors | {plotIndex: [color or null per bar]} for plots with per-bar colors |
marks | per mark site: {bars: [...], prices: [...]} |
backgrounds, candleColors | sparse [[bar, color], ...] |
prices | per render.price.*: {o, h, l, c} columns |
profiles | per profile: poc vah val rowStep totalVolume fromBar toBar and levels: [[price, volume, buy, sell, isPoc, inValueArea]] |
drawings | live objects: {id, kind, num, text, xs, ys, rows, cols, cells, merges} |
alerts | {bar, rule, message} |
strategy | capital, equity, drawdown, trades, stats (or null) |
bot | position, pending orders, signals |
Plot metadata: index title style color width stroke pane offset base upColor downColor shape size dynamicColor backend priceIndex profileIndex box. Styles: 0 line, 1 histogram, 2 area, 4 dots, 5 spline, 6 step, 7 baseline, 8 .. 17 price styles, 13 volume profile, 18 stepped area, 20 session profile, 21 TPO, 26 level. Strokes: 0 solid, 1 dashed, 2 dotted.
Colors are numbers 0xAARRGGBB: the low 24 bits are RGB and the high byte is the opacity, with 0 meaning fully opaque (so plain palette colors are just 0xRRGGBB). In per-bar color arrays null means "the plot's color" and 0 is black.