Language guide, engine API, 601 builtins and the preset library · v3.0.0-phase1
Building complex analyses
This chapter builds a complete market-structure analysis step by step: swing points, structure breaks, order-block zones that disappear when price mitigates them, and a dashboard. Each step compiles on its own; the finished version is the smc/market_structure_zones preset.
Step 1: swings
Start with the smallest thing you can check on the chart.
QuaScriptengine result
Loading the engine result…
step1.qs
analysis "Structure, step 1" { overlay = true }
legs = param.int(5, title = "Swing legs", min = 2, max = 30)
ph = ta.pivot_high(legs, legs)
pl = ta.pivot_low(legs, legs)
render.dots(ph, "Swing high", color = color.rose.400, shape = shape.diamond, size = 4)
render.dots(pl, "Swing low", color = color.emerald.400, shape = shape.diamond, size = 4)
quatick-charts · QuaScript engine on synthetic 1-minute bars · drag to pan · scroll to zoom · Run executes the script on the engine (⌘/Ctrl+Enter while editing)
A pivot is confirmed legs bars after it happens, so the dot appears on the confirming bar.
Step 2: state across bars
Remember the last swing high and low with persist, and detect a close beyond them.
QuaScriptengine result
Loading the engine result…
step2.qs
analysis "Structure, step 2" { overlay = true }
legs = param.int(5, title = "Swing legs", min = 2, max = 30)
ph = ta.pivot_high(legs, legs)
pl = ta.pivot_low(legs, legs)
persist last_high = na
persist last_low = na
persist dir = 0
if not math.is_na(ph) { last_high := ph }
if not math.is_na(pl) { last_low := pl }
// compute the crosses on every bar, use them below
up = ta.cross_up(close, last_high)
down = ta.cross_down(close, last_low)
if up { dir := 1 }
if down { dir := -1 }
render.step(last_high, "Last swing high", color = color.rose.400, stroke = style.dotted)
render.step(last_low, "Last swing low", color = color.emerald.400, stroke = style.dotted)
render.mark(up, shape = shape.tag_up, at = anchor.below_bar, color = color.emerald.500, text = "BOS")
render.mark(down, shape = shape.tag_down, at = anchor.above_bar, color = color.rose.500, text = "BOS")
render.candle_color(dir > 0 ? color.emerald.600 : dir < 0 ? color.rose.600 : color.slate.500)
quatick-charts · QuaScript engine on synthetic 1-minute bars · drag to pan · scroll to zoom · Run executes the script on the engine (⌘/Ctrl+Enter while editing)
Note how ta.cross_up is called on every bar, outside any if: a stateful function must see every bar to know what happened on the previous one.
Step 3: a function and a struct
Wrap the logic so it can be reused, and describe zones with a struct.
QuaScriptengine result
Loading the engine result…
step3.qs
analysis "Structure, step 3" { overlay = true }
struct Zone {
top: float = na
bottom: float = na
bullish: bool = true
fn mitigated(self) -> bool { return self.bullish ? close < self.bottom : close > self.top }
}
/// (direction, broke_up, broke_down)
fn structure(legs: int) -> (float, bool, bool) {
ph = ta.pivot_high(legs, legs)
pl = ta.pivot_low(legs, legs)
persist hh = na
persist ll = na
persist dir = 0
if not math.is_na(ph) { hh := ph }
if not math.is_na(pl) { ll := pl }
up = ta.cross_up(close, hh)
down = ta.cross_down(close, ll)
if up { dir := 1 }
if down { dir := -1 }
return (dir, up, down)
}
(dir, up, down) = structure(5)
// the last opposite candle before the break becomes the zone
k_bull = ta.bars_since(close < open)
k_bear = ta.bars_since(close > open)
persist zone = Zone()
if up and k_bull <= 10 { zone := Zone(top = high[k_bull], bottom = low[k_bull], bullish = true) }
if down and k_bear <= 10 { zone := Zone(top = high[k_bear], bottom = low[k_bear], bullish = false) }
active = not math.is_na(zone.top) and not zone.mitigated()
render.step(active ? zone.top : na, "Zone top", color = color.sky.400)
render.step(active ? zone.bottom : na, "Zone bottom", color = color.sky.400)
quatick-charts · QuaScript engine on synthetic 1-minute bars · drag to pan · scroll to zoom · Run executes the script on the engine (⌘/Ctrl+Enter while editing)
persist inside a function belongs to that call of the function, exactly like the state of ta.cross_up.
Step 4: many zones, drawn as objects
Keep every zone in a list, draw each one as a zone object, and stop extending a zone once it is mitigated. Iterating a list is a runtime loop, so nothing stateful may be called inside it.
QuaScriptengine result
Loading the engine result…
step4.qs
analysis "Structure, step 4" { overlay = true, max_objects = 200 }
struct Zone {
top: float = na
bottom: float = na
bullish: bool = true
fn mitigated(self) -> bool { return self.bullish ? close < self.bottom : close > self.top }
}
ph = ta.pivot_high(5, 5)
pl = ta.pivot_low(5, 5)
persist hh = na
persist ll = na
if not math.is_na(ph) { hh := ph }
if not math.is_na(pl) { ll := pl }
up = ta.cross_up(close, hh)
down = ta.cross_down(close, ll)
k_bull = ta.bars_since(close < open)
k_bear = ta.bars_since(close > open)
persist zones = list.create(Zone)
persist ids = list.create(float)
if up and k_bull <= 10 {
list.push(zones, Zone(top = high[k_bull], bottom = low[k_bull], bullish = true))
list.push(ids, draw.zone.create(bar.index - k_bull, high[k_bull], bar.index, low[k_bull],
color = color.translucent(color.sky.500, 80), ray = ray.right))
}
if down and k_bear <= 10 {
list.push(zones, Zone(top = high[k_bear], bottom = low[k_bear], bullish = false))
list.push(ids, draw.zone.create(bar.index - k_bear, high[k_bear], bar.index, low[k_bear],
color = color.translucent(color.orange.500, 80), ray = ray.right))
}
// walk backwards so removing an element does not skip the next one
i = list.size(zones) - 1
while i >= 0 {
if list.get(zones, i).mitigated() {
draw.zone.update(list.get(ids, i), right = bar.index) // freeze the zone where it ended
list.remove(zones, i)
list.remove(ids, i)
}
i := i - 1
}
quatick-charts · QuaScript engine on synthetic 1-minute bars · drag to pan · scroll to zoom · Run executes the script on the engine (⌘/Ctrl+Enter while editing)
Step 5: move the building blocks into libraries
The bundled libraries already contain these pieces: smc/structure (swings and breaks) and smc/zones (the Zone struct, order_block() and fvg()). With them the analysis is short:
QuaScriptengine result
Loading the engine result…
step5.qs
analysis "Structure, step 5" { overlay = true, max_objects = 300 }
use "smc/structure" as st
use "smc/zones" as zn
use "viz/palette" as pal
(dir, up, down, hh, ll) = st.structure(5)
(has_gap, gap) = zn.fvg(0.15)
persist gaps = list.create(zn.Zone)
if has_gap { list.push(gaps, gap) }
if list.size(gaps) > 20 { list.shift(gaps) }
render.step(hh, "Swing high", color = pal.BEAR, stroke = style.dotted)
render.step(ll, "Swing low", color = pal.BULL, stroke = style.dotted)
render.mark(up, shape = shape.tag_up, at = anchor.below_bar, color = pal.BULL, text = "BOS")
render.mark(down, shape = shape.tag_down, at = anchor.above_bar, color = pal.BEAR, text = "BOS")
render.candle_color(pal.trend_color(dir))
quatick-charts · QuaScript engine on synthetic 1-minute bars · drag to pan · scroll to zoom · Run executes the script on the engine (⌘/Ctrl+Enter while editing)
Checklist for large scripts
Compute, then decide. Call stateful functions on every bar at the top level; use their values inside if, match and loops.
Constant loops for state.for len in [8, 13, 21] unrolls into separate call sites; a loop over a list or a runtime range cannot hold state.
`persist` for memory, plain variables for per-bar values.
Bound what grows. Shift old elements out of lists, rely on max_objects for drawings.
Write on the last bar. Tables and labels that only show the current state are filled inside if bar.is_last.
Split into libraries once two scripts need the same function or struct.
Check the numbers.quas run script.qs data.csv --out r.json gives every value of every bar; quas bench script.qs shows the cost per bar.