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

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.

© 2026 Quatick Technologies. All rights reserved.