
QuaScript Documentation
Language guide, engine API, 601 builtins and the preset library · v3.0.0-phase1
Time, sessions and data
The bar
| Name | Value |
|---|---|
open high low close volume time | the bar's prices, volume and open time (epoch seconds) |
bar.index | 0 for the oldest bar |
bar.mid bar.typical bar.average bar.weighted | (h+l)/2, (h+l+c)/3, (o+h+l+c)/4, (h+l+2c)/4 |
bar.range bar.body bar.upper_wick bar.lower_wick | candle geometry |
bar.close_time | the time the bar closes |
bar.is_first bar.is_last | first / newest bar of the data |
bar.is_confirmed | false only for a forming (live, not yet closed) bar |
bar.is_live bar.is_new | the bar arrived after the chart opened / its first update |
The instrument and interval
asset.ticker exchange kind currency base_currency timezone tick_size point_value lot_size min_qty price_precision describe the instrument. interval.seconds multiplier unit label is_intraday is_daily ... describe the chart interval; interval.to_seconds("4h") converts a spec.
Interval specs are a number with an optional unit: 15 or 15m (minutes), 30s, 4h, 1D, 1W, 1M.
Time and sessions
analysis "Opening range" { overlay = true }
in_open = session.in_range("0930-1000", "America/New_York")
persist hi = na
persist lo = na
if session.is_opening_bar("0930-1600", "America/New_York") {
hi := high
lo := low
}
if in_open {
hi := math.max(hi, high)
lo := math.min(lo, low)
}
render.step(hi, "Opening range high", color = color.emerald.400)
render.step(lo, "Opening range low", color = color.rose.400)
render.background(in_open ? color.translucent(color.amber.500, 92) : na)time.year month day weekday hour minute second of_day(t, tz)split a time (default: the bar's open time in the instrument's time zone).time.timestamp(year, month, day, hour, minute, second, tz)builds one;time.format(t, "yyyy-MM-dd HH:mm", tz)prints one;time.shift(t, 3, "day")moves one.- Sessions are
"HHMM-HHMM"with optional weekdays"0930-1600:12345"(1 = Monday).session.is_open in_range is_opening_bar is_closing_bar start_time end_timetake a spec and a time zone.session.coreis the instrument's regular session. - Time zones are IANA names (
Europe/London,Asia/Kolkata) and follow daylight saving.
Other intervals and symbols (feeds)
analysis "Hourly trend" { overlay = true }
tf = param.interval("60", title = "Interval")
hourly = feed.series("", tf, ta.ema(close, 20))
render.step(hourly, "EMA 20 (higher interval)", color = color.violet.400, width = 2)
render.candle_color(close > hourly ? color.emerald.500 : color.rose.500)feed.series(symbol, interval, expression) evaluates expression on the bars of another interval ("" = the chart's symbol) and returns the value of the last closed bar of that interval. It never looks ahead: the value of an hourly bar becomes visible on the chart bar in which that hour closed. forming = true also shows the forming bar's value on live bars.
Higher intervals of the chart symbol are built from the chart's bars. Other symbols are supplied by the host; when they are missing the value is na with one W_FEED_MISSING warning.
Order book
Next to open, high, low, close and volume, every bar carries the real order book the chart recorded for it: book. On history it is the book at the bar's close (from the chart's recorded depth history), on the forming bar the live book, updated with every book tick, exactly like the forming bar's close. The engine never invents a book: a bar the chart has no book for has an empty one.
Feeds differ in depth, and book holds whatever the feed provides:
| Depth | Feed gives | book.depth | Read with |
|---|---|---|---|
| L1 | best bid / ask and their sizes | 1 | book.bid, book.ask, book.bid_size, book.ask_size, book.mid, book.spread, book.microprice |
| L2 | price levels, optionally with the order count of each level | 2 | book.bids, book.asks and the whole-book builtins below |
| L3 | every individual resting order (market by order) | 3 | book.orders, book.order_count, book.largest_order; L2 builtins work too (levels are aggregated from the orders) |
book.depth is per bar (0 = no book on this bar), so a script can check what it has. The terminal reports in each script's schema the depth it reads (schema.bookDepth).
Price levels: book.bids and book.asks
book.bids and book.asks hold every level of the bar's book, best price first, as a grid with one row per level:
| Column | Constant | Value |
|---|---|---|
| 0 | book.col_price | price of the level |
| 1 | book.col_size | resting size |
| 2 | book.col_total | cumulative size from the best price to this level |
| 3 | book.col_orders | orders at the level (na when the feed does not report it) |
analysis "Book walls" {
overlay = true
render = webgpu
}
/// The biggest bid and ask inside 1 % of the mid, and the side the book leans to.
bids = book.bids
asks = book.asks
mid = book.mid
bid_wall = na
ask_wall = na
bid_max = 0.0
ask_max = 0.0
for i in 0..grid.rows(bids) {
px = grid.get(bids, i, book.col_price)
sz = grid.get(bids, i, book.col_size)
if px >= mid * 0.99 and sz > bid_max {
bid_max := sz
bid_wall := px
}
}
for i in 0..grid.rows(asks) {
px = grid.get(asks, i, book.col_price)
sz = grid.get(asks, i, book.col_size)
if px <= mid * 1.01 and sz > ask_max {
ask_max := sz
ask_wall := px
}
}
render.step(bid_wall, "Bid wall", color = color.emerald.400, width = 2)
render.step(ask_wall, "Ask wall", color = color.rose.400, width = 2)
render.candle_color(book.pressure() > 0.2 ? color.emerald.500 : book.pressure() < -0.2 ? color.rose.500 : na)Bind book.bids / book.asks to a variable once and loop over the variable: each read of the builtin copies the side into its grid. A grid holds the book of the current bar only; keep what you need from older books in series (imb = book.imbalance(), then imb[1]).
Individual orders: book.orders
With an L3 feed, book.orders lists every resting order: columns book.order_side (-1 bid, 1 ask), book.order_price, book.order_size and book.order_id. Without L3 data it is empty.
o = book.orders
big = 0
for i in 0..grid.rows(o) {
if grid.get(o, i, book.order_size) > 10 * ta.sma(volume, 50) / 100 { big += 1 }
}Whole-book builtins
| Builtin | Value |
|---|---|
book.levels | price levels present on both sides (0 = no book on this bar) |
book.bid_volume(levels), book.ask_volume(levels) | resting size over the top levels (0 = all) |
book.imbalance(levels) | (bids - asks) / (bids + asks), -1 .. 1 |
book.pressure(decay) | imbalance with each level weighted by exp(-distance % / decay) |
book.liquidity(pct, side) | size within pct % of the mid (book.bid_side, book.ask_side or 0) |
book.vwap(side, levels) | size-weighted price of one side |
book.wall(side, levels) | (price, size) of the largest level |
book.impact(qty, side) | percent from the mid of the average fill of a market order of qty |
book.order_count(side) | resting orders (L3, or the order counts of L2 levels) |
book.largest_order(side) | (price, size) of the largest single order (L3) |
orderbook.ofi(levels) | Cont-Kukanov-Stoikov order flow imbalance between consecutive bars' books |
Bars without a book give empty grids and na; a chart that supplies no book at all also gives one W_HOST_DATA warning. Combine the book with bar data and fall back where history has no book:
clv = ((close - low) - (high - close)) / math.max(high - low, 1e-12)
lean = math.is_na(book.imbalance()) ? clv : book.imbalance()Supplying the book
The terminal does this for every script that reads the book: it normalises its feeds (Delta, Binance, Zerodha, Fyers, ...) into snapshots, attaches the recorded book of each history bar and streams the live book into the forming bar. Other hosts pass it themselves:
quas serve:bars.book, one snapshot per bar (see Engine API);- CSV files: columns
orderbook.bid.N,orderbook.bid_size.N,orderbook.bid_orders.N,orderbook.ask.N,orderbook.ask_size.N,orderbook.ask_orders.N(N = 0 = best).
For engine tests only, quas run --synth N --synth-book adds a generated L2 book to synthetic bars.
Host data
Order flow, order book, derivatives and fundamentals come from the host as extra columns:
| Domain | Values |
|---|---|
bar.buy_volume bar.sell_volume bar.delta bar.tick_count | order flow per bar |
book.bid book.bids book.orders book.imbalance(levels) ... | the order book (L1 / L2 / L3), see Order book |
orderbook.bid(level) ask bid_size ask_size spread spread_ticks mid microprice imbalance depth_ratio slippage | single levels of the same book |
derivative.funding_rate open_interest basis basis_pct put_call_ratio max_pain expiry_days | |
financial.pe pb eps market_cap revenue net_income roe debt_to_equity debt_ratio fii_net dii_net |
A chart without the data gives na and one W_HOST_DATA warning; a script can test math.is_na(bar.delta) and fall back to an estimate.