
QuaScript Documentation
Language guide, engine API, 601 builtins and the preset library · v3.0.0-phase1
Rendering
Outputs are declared by render.* calls. Each call is one output; calling it on every bar gives it one value per bar. The backend (canvas, WebGL or WebGPU) is never part of a name: it comes from the header's render setting or a call's backend argument.
Backends
analysis "Trend ribbon" { overlay = true, render = webgpu }
fast = ta.ema(close, 9)
slow = ta.ema(close, 34)
render.line(fast, "Fast", color = fast > slow ? color.emerald.400 : color.rose.400, width = 2)
render.line(slow, "Slow", color = color.translucent(color.amber.400, 40), width = 6)render / backend | Chart behaviour |
|---|---|
auto (default) | the chart chooses Canvas 2D or WebGL by data size |
canvas | Canvas 2D |
webgl | WebGL2 |
webgpu | WebGPU: lines, steps, splines, baselines, areas, bands, histograms and marks are drawn in one GPU pass (4x MSAA); clouds, labelled marks, levels and profiles stay on Canvas 2D in the same z-order. Until the browser's WebGPU device is ready, or where WebGPU is unavailable, the chart draws the script with WebGL / Canvas 2D. |
backend = "webgpu" on one call (for example render.line(x, backend = "webgpu")) asks for WebGPU for the whole script on the chart.
Plots
analysis "Plot styles" { overlay = false }
m = ta.rsi(close, 14) - 50
render.line(m, "Line", color = color.sky.400, width = 2)
render.spline(ta.ema(m, 5), "Spline", color = color.violet.400)
render.step(math.round(m / 10) * 10, "Step", color = color.amber.400)
render.histogram(m, "Histogram", color = m >= 0 ? color.teal.500 : color.rose.500)
render.area(m, "Area", base = 0, color = color.translucent(color.sky.500, 80))
render.baseline(m, "Baseline", base = 0, up_color = color.emerald.500, down_color = color.rose.500)
render.dots(ta.pivot_high(5, 5, m), "Dots", shape = shape.circle, size = 3, color = color.white)| Function | Draws |
|---|---|
render.line | a polyline |
render.spline | a smoothed curve |
render.step | a staircase line |
render.area / render.stepped_area | a filled area down to base |
render.histogram | columns from base |
render.baseline | a line colored above / below base |
render.dots | a shape per bar |
Shared arguments: title, color, width, stroke (style.solid, style.dashed, style.dotted), pane, offset (shift in bars), backend.
navalues are gaps.- Per-bar colors: when
coloris an expression (m >= 0 ? a : b), every bar gets its own color. Lines change color segment by segment; histograms and dots color each point. pane = 1puts the output in the script's lower pane,pane = 0on the price pane.
Panes
A chart has two kinds of panes, and a script can draw in both.
| Pane | Shares | x axis | y axis | Declared by |
|---|---|---|---|---|
| price pane | time | price | pane = price (default) | |
| H pane (horizontal) | the time axis | time | the script's own scale | pane = hpane |
| V pane (vertical) | the price scale | a value at each price | price | pane = vpane and render.vpane.* |
H panes stack under the price pane, one per script; height sets its share of the chart. Plots go to the script's pane by default; pane = pane.price (0) or pane = pane.lower (1) on a call overrides it.
analysis "Flow monitor" {
pane = hpane
height = 0.3
}
(buy, sell, imb) = hft.flow(50)
render.histogram(imb, "Imbalance", color = imb >= 0 ? color.emerald.500 : color.rose.500)
render.line(hft.vpin(50), "VPIN", color = color.fuchsia.400)V panes are columns beside the price axis. Each row is level with the same price on the chart, so a V pane reads as "how much of something happened at this price".
analysis "Delta at price" {
pane = vpane
width = 150
}
(buy, sell, imb) = hft.flow(20)
// buy volume right, sell volume left, spread over each bar's range
render.vpane.histogram(buy, value2 = sell, low = low, high = high, title = "Buy | Sell")
// the net delta at each close, averaged instead of summed
render.vpane.histogram(buy - sell, price = close, aggregate = "mean", title = "Mean delta")
// one of the chart's built-in V panes
render.vpane.profile(kind = "tpo", width = 110)render.vpane.histogram(value, ...) records one contribution per bar; the chart sums them by price:
| Argument | Default | Meaning |
|---|---|---|
price | close | the row the value lands in |
low, high | spread the value over [low, high] in proportion to the overlap (set both) | |
value2 | a second value drawn mirrored on the left (e.g. sell beside buy) | |
period | visible | visible bars, the current session, the last bars, or all history |
aggregate | sum | sum, mean, count, max, min or last per row |
rows, row_size | 60, 0 | rows across the visible price range, or a fixed price step |
color, color2 | positive / second (or negative) values | |
show_poc, poc_color, show_value_area, value_area, extend_poc | point of control and value area of the rows; extend_poc draws them across the chart | |
title, width, anchor, labels | pane header, width in px, bars grow from the axis (left) or the outer edge (right), row labels |
Negative values are drawn to the left of a centre line in color2. na values add nothing.
render.vpane.profile(kind = ...) adds one of the chart's own V panes, computed by the chart from its bars (and its order book for the book kinds): volume, buy_sell, delta, session, composite, tpo, time_at_price, close, wick, sr_strength, volatility, nodes, orderbook, depth, imbalance, liquidity.
V panes are incremental like everything else: on a live tick the chart re-adds only the forming bar, a new candle adds one bar, and panning moves the window by the bars that entered or left it. A script with pane = vpane may also draw plots; they go to the price pane.
Bands
analysis "Bands" { overlay = true }
(mid, up, dn) = ta.bollinger(close, 20, 2.0)
pu = render.line(up, "Upper", color = color.slate.500)
pd = render.line(dn, "Lower", color = color.slate.500)
render.band(pu, pd, color = color.sky.500, opacity = 0.1)
(conv, base, a, b, lag) = ta.ichimoku()
pa = render.line(a, "Span A", color = color.emerald.500, offset = 26)
pb = render.line(b, "Span B", color = color.rose.500, offset = 26)
render.band(pa, pb, texture = "cloud", upper_color = color.emerald.500, lower_color = color.rose.500, opacity = 0.25)render.band(top, bottom) fills between two plot handles (the value returned by a plot or a level). texture is flat (default), gradient (upper_color → lower_color, optional mid_color) or cloud (cloud_style, puff_scale).
Gradient bands
Three ways to color a band with the data:
color = <series>gives every bar its own flat fill color (heatmap rows).directionsets where a constant gradient runs:vertical(default) spans the band's whole height on screen,horizontalruns left to right, andedgesruns from each bar's own top edge to its own bottom edge, so a sloping or widening channel keeps its colors on its edges.upper_colorandlower_colormay be series. Each bar then fades from its own top color to its own bottom color (an edge-anchored gradient with per-bar colors), and the GPU blends neighbouring bars smoothly.
analysis "Gradient bands" {
overlay = true
render = webgpu
}
m = ta.ema(close, 34)
a = ta.atr(34)
z = (close - m) / a
pu = render.line(m + 2 * a, "Upper", color = color.slate.500, width = 1)
pl = render.line(m - 2 * a, "Lower", color = color.slate.500, width = 1)
// per-bar edge colors: the top edge shows the stretch on a perceptual palette
render.band(pu, pl, upper_color = color.ramp(z, -2, 2, "turbo"), lower_color = color.transparent, opacity = 0.45, title = "Stretch")
// constant colors anchored to each bar's edges
pi = render.line(m + a, "Inner", color = color.transparent, width = 0)
render.band(pu, pi, texture = "gradient", direction = "edges", upper_color = color.rose.400, lower_color = color.slate.900, opacity = 0.25, title = "Upper zone")Both the WebGPU and the Canvas 2D renderer draw these gradients the same way.
Levels, time marks, backgrounds, candle colors
analysis "Levels" { overlay = false }
r = ta.rsi(close, 14)
render.line(r, "RSI")
ob = render.level(70, "Overbought", color = color.rose.600)
os = render.level(30, "Oversold", color = color.teal.600)
render.band(ob, os, color = color.violet.500, opacity = 0.05)
render.time_mark(time.weekday() == 1 and time.weekday(time[1]) != 1, "Week start")
render.background(r > 70 ? color.translucent(color.rose.500, 90) : na)render.level(price)is a horizontal line at a constant (or parameter) price.render.time_mark(when)draws a vertical line on the bars wherewhenis true.render.background(color)tints the bar's background;naleaves it.render.candle_color(color)colors the price candle of the bar.
Marks
analysis "Marks" { overlay = true }
f = ta.ema(close, 9)
s = ta.ema(close, 21)
render.mark(ta.cross_up(f, s), shape = shape.tag_up, at = anchor.below_bar, color = color.emerald.500, text = "Buy")
render.mark(ta.cross_down(f, s), shape = shape.tag_down, at = anchor.above_bar, color = color.rose.500, text = "Sell")A mark is drawn on every bar where when is true. Shapes: arrow_up arrow_down circle cross diamond square tag_up tag_down triangle_up triangle_down star flag plus x pin tag. Positions (at): anchor.above_bar, anchor.below_bar, anchor.top, anchor.bottom, or anchor.price with price = ....
Price styles
render.price.candles, bars, hollow, heikin, renko, kagi, point_figure and line_break draw price bars computed by the script (or the chart's own bars by default):
analysis "Delta candles" { overlay = false }
d = volume * (close > open ? 1 : -1)
persist cum = 0.0
o = cum
cum := cum + d
render.price.candles(o = o, h = math.max(o, cum), l = math.min(o, cum), c = cum, up_color = color.teal.500, down_color = color.rose.500)Profiles
analysis "Volume profile" { overlay = true, render = webgl }
rows = param.int(70, title = "Rows", min = 8, max = 400)
render.profile.volume(rows = rows, value_area = 0.7, width = 160)render.profile.volume (visible range), render.profile.session (per session) and render.profile.tpo are computed by the engine, including the point of control and value area. ta.volume_profile() returns the developing POC / VAH / VAL as series for trading against.
Colors
See Colors for the palette and color functions.