
QuaScript Documentation
Language guide, engine API, 601 builtins and the preset library · v3.0.0-phase1
Market structure
Darvas Box Breakouts
analysis · presets/analysis/smc/darvas_boxes.qs
Darvas boxes: a new high that holds for confirm bars sets the box top, the lowest low after it that holds sets the bottom. The box grows while price stays inside, a close above or below ends it with a breakout tag and the breakout's measured target (box height) is drawn as a dashed line.
analysis "Darvas Box Breakouts" {
overlay = true
max_objects = 300
}
/// Darvas boxes: a new high that holds for `confirm` bars sets the box top, the lowest low after
/// it that holds sets the bottom. The box grows while price stays inside, a close above or below
/// ends it with a breakout tag and the breakout's measured target (box height) is drawn as a
/// dashed line.
confirm = param.int(3, title = "Confirm bars", min = 1, max = 20)
look = param.int(20, title = "High lookback", min = 5, max = 200)
persist state = 0 // 0 searching top, 1 searching bottom, 2 box live
persist top = na
persist bot = na
persist since = 0
persist start = 0
persist zid = 0.0
hh = ta.highest(high, look)
a = ta.atr(14)
broke_up = false
broke_dn = false
if state == 0 {
if high >= hh {
top := high
since := 0
start := bar.index
} else if not math.is_na(top) {
since := since + 1
if since >= confirm {
state := 1
bot := low
since := 0
}
}
} else if state == 1 {
if high > top {
state := 0
top := high
since := 0
start := bar.index
} else if low < bot {
bot := low
since := 0
} else {
since := since + 1
if since >= confirm {
state := 2
zid := draw.zone.create(start, top, bar.index, bot, color = color.translucent(color.indigo.400, 85), border_color = color.indigo.300, text = "Darvas")
}
}
} else {
if close > top {
broke_up := true
} else if close < bot {
broke_dn := true
} else {
draw.zone.update(zid, right = bar.index)
}
if broke_up or broke_dn {
h = top - bot
draw.zone.update(zid, right = bar.index, color = color.translucent(broke_up ? color.emerald.500 : color.rose.500, 80))
draw.tag.create(bar.index, broke_up ? low - 0.3 * a : high + 0.3 * a, broke_up ? "Breakout" : "Breakdown", color = broke_up ? color.emerald.600 : color.rose.600, shape = broke_up ? shape.tag_up : shape.tag_down)
target = broke_up ? top + h : bot - h
draw.segment.create(bar.index, target, bar.index + 20, target, color = broke_up ? color.emerald.300 : color.rose.300, stroke = style.dashed)
state := 0
top := na
bot := na
}
}
render.step(state == 2 ? top : na, "Box top", color = color.translucent(color.indigo.300, 30), width = 1)
render.step(state == 2 ? bot : na, "Box bottom", color = color.translucent(color.indigo.300, 30), width = 1)
render.candle_color(broke_up ? color.emerald.300 : broke_dn ? color.rose.300 : na)
alert.rule(broke_up, "Darvas breakout", "Close above the top of the box")
alert.rule(broke_dn, "Darvas breakdown", "Close below the bottom of the box")FVG Imbalance Map
analysis · presets/analysis/smc/fvg_imbalance_map.qs · uses smc/zones
Fair-value gaps drawn as zones that shrink as price fills them, each with its consequent encroachment (the 50 % line). A gap is removed once price trades through it completely. A table counts open bullish and bearish gaps and the share filled so far.
analysis "FVG Imbalance Map" {
overlay = true
max_objects = 600
}
/// Fair-value gaps drawn as zones that shrink as price fills them, each with its consequent
/// encroachment (the 50 % line). A gap is removed once price trades through it completely. A table
/// counts open bullish and bearish gaps and the share filled so far.
use "smc/zones" as zn
min_gap = param.float(0.2, title = "Min gap (ATR)", min = 0, max = 3, step = 0.05)
max_live = param.int(16, title = "Gaps kept", min = 1, max = 60)
show_ce = param.bool(true, title = "50% line")
(has_gap, gap) = zn.fvg(min_gap)
persist gaps = list.create(zn.Zone)
persist zids = list.create(float)
persist cids = list.create(float)
persist filled_total = 0
persist made_total = 0
if has_gap {
made_total := made_total + 1
list.push(gaps, gap)
list.push(zids, draw.zone.create(gap.born, gap.top, bar.index, gap.bottom,
color = color.translucent(gap.bullish ? color.cyan.400 : color.fuchsia.500, 78),
border_color = color.translucent(gap.bullish ? color.cyan.300 : color.fuchsia.400, 50), text = gap.bullish ? "FVG+" : "FVG-"))
list.push(cids, show_ce ? draw.segment.create(gap.born, gap.mid(), bar.index, gap.mid(), color = color.translucent(color.slate.200, 50), stroke = style.dotted) : 0)
}
i = list.size(gaps) - 1
while i >= 0 {
z = list.get(gaps, i)
zid = list.get(zids, i)
// fill from the side price arrives at
top = z.bullish ? z.top : math.min(z.top, math.max(z.bottom, high))
bot = z.bullish ? math.max(z.bottom, math.min(z.top, low)) : z.bottom
closed = z.bullish ? low <= z.bottom : high >= z.top
if closed {
draw.zone.delete(zid)
if list.get(cids, i) > 0 { draw.segment.delete(list.get(cids, i)) }
list.remove(gaps, i)
list.remove(zids, i)
list.remove(cids, i)
filled_total := filled_total + 1
} else if top != z.top or bot != z.bottom {
list.set(gaps, i, zn.Zone(top = top, bottom = bot, born = z.born, bullish = z.bullish))
draw.zone.update(zid, top = top, bottom = bot, right = bar.index + 3)
if list.get(cids, i) > 0 { draw.segment.update(list.get(cids, i), x2 = bar.index + 3) }
} else {
draw.zone.update(zid, right = bar.index + 3)
if list.get(cids, i) > 0 { draw.segment.update(list.get(cids, i), x2 = bar.index + 3) }
}
i := i - 1
}
while list.size(gaps) > max_live {
draw.zone.delete(list.first(zids))
if list.first(cids) > 0 { draw.segment.delete(list.first(cids)) }
list.shift(gaps)
list.shift(zids)
list.shift(cids)
}
n_bull = 0
n_bear = 0
for z in gaps {
if z.bullish { n_bull := n_bull + 1 } else { n_bear := n_bear + 1 }
}
persist t = draw.table.create(anchor.top_right, 3, 2)
if bar.is_last {
draw.table.cell(t, 0, 0, "Open FVG+", text_color = color.cyan.300)
draw.table.cell(t, 0, 1, str.of(n_bull), bg_color = color.cyan.900)
draw.table.cell(t, 1, 0, "Open FVG-", text_color = color.fuchsia.300)
draw.table.cell(t, 1, 1, str.of(n_bear), bg_color = color.fuchsia.900)
draw.table.cell(t, 2, 0, "Fill rate", text_color = color.slate.400)
draw.table.cell(t, 2, 1, str.format("{:.0f}%", made_total > 0 ? filled_total * 100 / made_total : 0))
}
render.mark(has_gap and gap.bullish, shape = shape.circle, at = anchor.below_bar, color = color.cyan.300, size = 0.4, title = "Bullish gap")
render.mark(has_gap and not gap.bullish, shape = shape.circle, at = anchor.above_bar, color = color.fuchsia.400, size = 0.4, title = "Bearish gap")
alert.rule(has_gap, "New fair-value gap", "A three-bar imbalance formed")Liquidity Sweeps
analysis · presets/analysis/smc/liquidity_sweeps.qs · uses smc/structure, viz/palette
Equal highs / lows (pivots within a tolerance of each other) are liquidity pools. A sweep is a wick through a pool that closes back inside; pools are drawn as rays until swept.
analysis "Liquidity Sweeps" {
overlay = true
max_objects = 300
}
/// Equal highs / lows (pivots within a tolerance of each other) are liquidity pools. A sweep is a
/// wick through a pool that closes back inside; pools are drawn as rays until swept.
use "smc/structure" as st
use "viz/palette" as pal
legs = param.int(5, title = "Pivot legs", min = 2, max = 30)
tol = param.float(0.1, title = "Equal tolerance (ATR)", min = 0.01, max = 1, step = 0.01)
(ph, pl) = st.swings(legs)
a = ta.atr(14)
persist last_ph = na
persist last_pl = na
persist pool_hi = na
persist pool_lo = na
persist hi_id = 0.0
persist lo_id = 0.0
if not math.is_na(ph) {
if not math.is_na(last_ph) and math.abs(ph - last_ph) <= tol * a {
pool_hi := math.max(ph, last_ph)
hi_id := draw.segment.create(bar.index - legs, pool_hi, bar.index, pool_hi, color = pal.BEAR, stroke = style.dashed, ray = ray.right)
}
last_ph := ph
}
if not math.is_na(pl) {
if not math.is_na(last_pl) and math.abs(pl - last_pl) <= tol * a {
pool_lo := math.min(pl, last_pl)
lo_id := draw.segment.create(bar.index - legs, pool_lo, bar.index, pool_lo, color = pal.BULL, stroke = style.dashed, ray = ray.right)
}
last_pl := pl
}
sweep_hi = not math.is_na(pool_hi) and high > pool_hi and close < pool_hi
sweep_lo = not math.is_na(pool_lo) and low < pool_lo and close > pool_lo
if sweep_hi {
draw.segment.update(hi_id, x2 = bar.index)
draw.tag.create(bar.index, high, "Sweep", color = pal.BEAR, shape = shape.tag_down, size = 0.8)
pool_hi := na
}
if sweep_lo {
draw.segment.update(lo_id, x2 = bar.index)
draw.tag.create(bar.index, low, "Sweep", color = pal.BULL, shape = shape.tag_up, size = 0.8)
pool_lo := na
}
render.step(pool_hi, "Buy-side liquidity", color = color.translucent(pal.BEAR, 50), width = 1)
render.step(pool_lo, "Sell-side liquidity", color = color.translucent(pal.BULL, 50), width = 1)
alert.rule(sweep_hi, "Buy-side sweep", "Price swept equal highs and closed back below")
alert.rule(sweep_lo, "Sell-side sweep", "Price swept equal lows and closed back above")Liquidity Voids & Wicks
analysis · presets/analysis/smc/liquidity_voids.qs
Long-range, high-volume candles leave thin areas that price tends to revisit. Each void is drawn as a zone over the body of the impulse candle and shrinks as price trades back into it; unusually long wicks become rejection zones. A void disappears once fully filled.
analysis "Liquidity Voids & Wicks" {
overlay = true
max_objects = 500
}
/// Long-range, high-volume candles leave thin areas that price tends to revisit. Each void is
/// drawn as a zone over the body of the impulse candle and shrinks as price trades back into it;
/// unusually long wicks become rejection zones. A void disappears once fully filled.
mult = param.float(2.0, title = "Impulse range (ATR)", min = 1, max = 6, step = 0.1)
wick = param.float(0.6, title = "Wick share", min = 0.3, max = 0.95, step = 0.05)
keep = param.int(12, title = "Zones kept", min = 1, max = 50)
a = ta.atr(20)
rv = volume / math.max(ta.sma(volume, 50), 1e-12)
rng = high - low
impulse = rng > mult * a[1] and rv > 1.3 and math.abs(close - open) > 0.6 * rng
persist tops = list.create(float)
persist bots = list.create(float)
persist ups = list.create(float)
persist ids = list.create(float)
if impulse {
list.push(tops, math.max(open, close))
list.push(bots, math.min(open, close))
list.push(ups, close > open ? 1 : 0)
list.push(ids, draw.zone.create(bar.index, math.max(open, close), bar.index + 3, math.min(open, close),
color = color.translucent(close > open ? color.teal.400 : color.orange.400, 82), text = "Void"))
}
upper_w = high - math.max(open, close)
lower_w = math.min(open, close) - low
if rng > a and upper_w > wick * rng {
draw.zone.create(bar.index, high, bar.index + 8, math.max(open, close), color = color.translucent(color.rose.400, 85), text = "Rejection")
}
if rng > a and lower_w > wick * rng {
draw.zone.create(bar.index, math.min(open, close), bar.index + 8, low, color = color.translucent(color.emerald.400, 85), text = "Rejection")
}
i = list.size(ids) - 1
while i >= 0 {
t = list.get(tops, i)
b = list.get(bots, i)
bull = list.get(ups, i) > 0
id = list.get(ids, i)
// a bullish void fills from above, a bearish one from below
nt = bull ? t : math.min(t, math.max(b, high))
nb = bull ? math.max(b, math.min(t, low)) : b
if nt <= nb or (bull and low <= b) or (not bull and high >= t) {
draw.zone.delete(id)
list.remove(tops, i)
list.remove(bots, i)
list.remove(ups, i)
list.remove(ids, i)
} else {
list.set(tops, i, nt)
list.set(bots, i, nb)
draw.zone.update(id, top = nt, bottom = nb, right = bar.index + 3)
}
i := i - 1
}
while list.size(ids) > keep {
draw.zone.delete(list.first(ids))
list.shift(tops)
list.shift(bots)
list.shift(ups)
list.shift(ids)
}
render.mark(impulse and close > open, shape = shape.circle, at = anchor.below_bar, color = color.teal.300, size = 0.5, title = "Bullish impulse")
render.mark(impulse and close < open, shape = shape.circle, at = anchor.above_bar, color = color.orange.300, size = 0.5, title = "Bearish impulse")
alert.rule(impulse, "Impulse candle", "A wide, heavy candle left a liquidity void")Market Structure & Zones
analysis · presets/analysis/smc/market_structure_zones.qs · uses smc/structure, smc/zones, viz/palette
Swing structure (breaks up / down), order blocks and fair-value gaps drawn as zones that stop extending once price mitigates them. Uses three libraries; smc/zones itself uses smc/structure.
analysis "Market Structure & Zones" {
overlay = true
max_objects = 400
}
/// Swing structure (breaks up / down), order blocks and fair-value gaps drawn as zones that stop
/// extending once price mitigates them. Uses three libraries; smc/zones itself uses smc/structure.
use "smc/structure" as st
use "smc/zones" as zn
use "viz/palette" as pal
legs = param.int(5, title = "Swing legs", min = 2, max = 30)
min_gap = param.float(0.15, title = "Min gap (ATR)", min = 0, max = 2, step = 0.05)
max_live = param.int(12, title = "Zones kept", min = 1, max = 50)
(dir, broke_up, broke_down, hh, ll) = st.structure(legs)
(has_fvg, gap) = zn.fvg(min_gap)
(has_ob, block) = zn.order_block(legs)
persist zones = list.create(zn.Zone)
persist ids = list.create(float)
if has_fvg {
list.push(zones, gap)
list.push(ids, draw.zone.create(gap.born, gap.top, bar.index, gap.bottom, ray = ray.right,
color = color.translucent(gap.bullish ? pal.BULL : pal.BEAR, 80), text = "FVG"))
}
if has_ob {
list.push(zones, block)
list.push(ids, draw.zone.create(block.born, block.top, bar.index, block.bottom, ray = ray.right,
color = color.translucent(block.bullish ? color.sky.500 : color.orange.500, 75), text = "OB"))
}
// zones extend to the right until price mitigates them; keep the newest `max_live`
i = list.size(zones) - 1
while i >= 0 {
if list.get(zones, i).mitigated() or list.size(zones) > max_live {
draw.zone.update(list.get(ids, i), right = bar.index)
list.remove(zones, i)
list.remove(ids, i)
}
i := i - 1
}
render.step(hh, "Swing high", color = pal.BEAR, width = 1, stroke = style.dotted)
render.step(ll, "Swing low", color = pal.BULL, width = 1, stroke = style.dotted)
render.mark(broke_up, shape = shape.tag_up, at = anchor.below_bar, color = pal.BULL, text = "BOS")
render.mark(broke_down, shape = shape.tag_down, at = anchor.above_bar, color = pal.BEAR, text = "BOS")
render.candle_color(pal.trend_color(dir))
alert.rule(broke_up, "Structure break up", "Close above the last swing high")
alert.rule(broke_down, "Structure break down", "Close below the last swing low")Order Block Matrix
analysis · presets/analysis/smc/order_block_matrix.qs · uses smc/structure, smc/zones
Order blocks from structure breaks, graded by the relative volume of the break: the stronger the break, the more opaque the zone and the higher its score label. A mitigated block flips into a breaker block (dashed outline, opposite color) instead of disappearing, and a breaker is removed once price closes through it again. Live blocks extend to the newest bar; finished blocks stay on the chart where they ended.
analysis "Order Block Matrix" {
overlay = true
max_objects = 2000
}
/// Order blocks from structure breaks, graded by the relative volume of the break: the stronger
/// the break, the more opaque the zone and the higher its score label. A mitigated block flips
/// into a breaker block (dashed outline, opposite color) instead of disappearing, and a breaker
/// is removed once price closes through it again. Live blocks extend to the newest bar; finished
/// blocks stay on the chart where they ended.
use "smc/structure" as st
use "smc/zones" as zn
legs = param.int(6, title = "Swing legs", min = 2, max = 30)
max_live = param.int(10, title = "Blocks kept", min = 1, max = 40)
breakers = param.bool(true, title = "Breaker blocks")
(dir, broke_up, broke_down, hh, ll) = st.structure(legs)
(has_ob, block) = zn.order_block(legs)
rvol = volume / math.max(ta.sma(volume, 50), 1e-12)
score = math.clamp(rvol / 3, 0.05, 1)
persist zones = list.create(zn.Zone)
persist ids = list.create(float)
persist kinds = list.create(float) // 0 order block, 1 breaker
if has_ob {
base = block.bullish ? color.emerald.500 : color.rose.500
list.push(zones, block)
list.push(ids, draw.zone.create(block.born, block.top, bar.index, block.bottom,
color = color.translucent(base, 90 - score * 40), border_color = color.translucent(base, 40),
text = str.format("{} OB {:.0f}%", block.bullish ? "Bull" : "Bear", score * 100)))
list.push(kinds, 0)
}
i = list.size(zones) - 1
while i >= 0 {
z = list.get(zones, i)
id = list.get(ids, i)
kind = list.get(kinds, i)
if kind == 0 and z.mitigated() {
draw.zone.update(id, right = bar.index)
if breakers {
flipped = zn.Zone(top = z.top, bottom = z.bottom, born = bar.index, bullish = not z.bullish)
list.set(zones, i, flipped)
list.set(ids, i, draw.zone.create(bar.index, z.top, bar.index, z.bottom,
color = color.translucent(z.bullish ? color.orange.500 : color.sky.500, 88),
border_color = z.bullish ? color.orange.400 : color.sky.400, text = "Breaker"))
list.set(kinds, i, 1)
} else {
list.remove(zones, i)
list.remove(ids, i)
list.remove(kinds, i)
}
} else if kind == 1 and z.mitigated() {
draw.zone.update(id, right = bar.index)
list.remove(zones, i)
list.remove(ids, i)
list.remove(kinds, i)
} else {
draw.zone.update(id, right = bar.index + 3) // live blocks grow with the chart
}
i := i - 1
}
while list.size(zones) > max_live {
draw.zone.update(list.first(ids), right = bar.index)
list.shift(zones)
list.shift(ids)
list.shift(kinds)
}
render.step(hh, "Swing high", color = color.translucent(color.rose.300, 40), width = 1, stroke = style.dotted)
render.step(ll, "Swing low", color = color.translucent(color.emerald.300, 40), width = 1, stroke = style.dotted)
render.mark(broke_up and rvol > 1.5, shape = shape.tag_up, at = anchor.below_bar, color = color.emerald.500, text = "Strong BOS")
render.mark(broke_down and rvol > 1.5, shape = shape.tag_down, at = anchor.above_bar, color = color.rose.500, text = "Strong BOS")
alert.rule(has_ob and score > 0.6, "Strong order block", "A structure break on heavy volume formed an order block")Pivot Matrix
analysis · presets/analysis/smc/pivot_matrix.qs
Daily floor pivots in three systems at once from the previous day's high, low and close: classic (P, R1-R3, S1-S3), Fibonacci (0.382 / 0.618 of the range) and Camarilla (H3 / H4 / L3 / L4). Levels are step lines that restart every day; the Camarilla H3-L3 box is filled as the balance area and breaks of H4 / L4 are marked.
analysis "Pivot Matrix" {
overlay = true
}
/// Daily floor pivots in three systems at once from the previous day's high, low and close:
/// classic (P, R1-R3, S1-S3), Fibonacci (0.382 / 0.618 of the range) and Camarilla
/// (H3 / H4 / L3 / L4). Levels are step lines that restart every day; the Camarilla H3-L3 box is
/// filled as the balance area and breaks of H4 / L4 are marked.
system = param.choice("all", title = "Systems", options = ["all", "classic", "fibonacci", "camarilla"])
day = time.day()
new_day = day != day[1]
persist d_hi = na
persist d_lo = na
persist p_hi = na
persist p_lo = na
persist p_cl = na
if new_day {
p_hi := d_hi
p_lo := d_lo
p_cl := close[1]
d_hi := high
d_lo := low
} else {
d_hi := math.max(d_hi, high)
d_lo := math.min(d_lo, low)
}
rng = p_hi - p_lo
p = (p_hi + p_lo + p_cl) / 3
show_c = system == "all" or system == "classic"
show_f = system == "all" or system == "fibonacci"
show_m = system == "all" or system == "camarilla"
render.step(p, "Pivot", color = color.amber.300, width = 2)
render.step(show_c ? 2 * p - p_lo : na, "R1", color = color.rose.300, width = 1)
render.step(show_c ? p + rng : na, "R2", color = color.rose.400, width = 1)
render.step(show_c ? p_hi + 2 * (p - p_lo) : na, "R3", color = color.rose.500, width = 1.5)
render.step(show_c ? 2 * p - p_hi : na, "S1", color = color.emerald.300, width = 1)
render.step(show_c ? p - rng : na, "S2", color = color.emerald.400, width = 1)
render.step(show_c ? p_lo - 2 * (p_hi - p) : na, "S3", color = color.emerald.500, width = 1.5)
render.step(show_f ? p + 0.382 * rng : na, "Fib R 0.382", color = color.translucent(color.violet.300, 40), width = 1, stroke = style.dashed)
render.step(show_f ? p + 0.618 * rng : na, "Fib R 0.618", color = color.translucent(color.violet.300, 40), width = 1, stroke = style.dashed)
render.step(show_f ? p - 0.382 * rng : na, "Fib S 0.382", color = color.translucent(color.violet.300, 40), width = 1, stroke = style.dashed)
render.step(show_f ? p - 0.618 * rng : na, "Fib S 0.618", color = color.translucent(color.violet.300, 40), width = 1, stroke = style.dashed)
h3 = p_cl + rng * 1.1 / 4
l3 = p_cl - rng * 1.1 / 4
h4 = p_cl + rng * 1.1 / 2
l4 = p_cl - rng * 1.1 / 2
ph3 = render.step(show_m ? h3 : na, "Camarilla H3", color = color.sky.300, width = 1, stroke = style.dotted)
pl3 = render.step(show_m ? l3 : na, "Camarilla L3", color = color.sky.300, width = 1, stroke = style.dotted)
render.band(ph3, pl3, color = color.sky.500, opacity = 0.06, title = "Balance area")
render.step(show_m ? h4 : na, "Camarilla H4", color = color.sky.500, width = 1.5)
render.step(show_m ? l4 : na, "Camarilla L4", color = color.sky.500, width = 1.5)
brk_up = show_m and ta.cross_up(close, h4)
brk_dn = show_m and ta.cross_down(close, l4)
render.mark(brk_up, shape = shape.tag_up, at = anchor.below_bar, color = color.sky.500, text = "H4")
render.mark(brk_dn, shape = shape.tag_down, at = anchor.above_bar, color = color.sky.500, text = "L4")
render.time_mark(new_day, title = "New day", color = color.slate.700)
alert.rule(brk_up, "Camarilla H4 breakout", "Close above Camarilla H4")
alert.rule(brk_dn, "Camarilla L4 breakdown", "Close below Camarilla L4")Premium & Discount Zones
analysis · presets/analysis/smc/premium_discount_zones.qs · uses smc/structure
The dealing range between the last confirmed swing high and low, split into premium, equilibrium and discount zones. Fibonacci retracements are drawn across the range, the optimal trade entry (0.62 .. 0.79) is highlighted and the range is redrawn whenever a new swing forms.
analysis "Premium & Discount Zones" {
overlay = true
max_objects = 120
}
/// The dealing range between the last confirmed swing high and low, split into premium,
/// equilibrium and discount zones. Fibonacci retracements are drawn across the range, the optimal
/// trade entry (0.62 .. 0.79) is highlighted and the range is redrawn whenever a new swing forms.
use "smc/structure" as st
legs = param.int(8, title = "Swing legs", min = 2, max = 50)
show_fib = param.bool(true, title = "Fibonacci levels")
(ph, pl) = st.swings(legs)
persist hi = na
persist lo = na
persist hi_bar = 0
persist lo_bar = 0
changed = false
if not math.is_na(ph) {
hi := ph
hi_bar := bar.index - legs
changed := true
}
if not math.is_na(pl) {
lo := pl
lo_bar := bar.index - legs
changed := true
}
rng = hi - lo
eq = (hi + lo) / 2
persist zone_ids = list.create(float)
persist seg_ids = list.create(float)
persist tag_ids = list.create(float)
ready = not math.is_na(hi) and not math.is_na(lo) and rng > 0
if changed and ready {
// one dealing range at a time: the previous drawing set is removed
for id in zone_ids { draw.zone.delete(id) }
for id in seg_ids { draw.segment.delete(id) }
for id in tag_ids { draw.tag.delete(id) }
list.clear(zone_ids)
list.clear(seg_ids)
list.clear(tag_ids)
left = math.min(hi_bar, lo_bar)
list.push(zone_ids, draw.zone.create(left, hi, bar.index + 10, eq + rng * 0.05, color = color.translucent(color.rose.500, 82), text = "Premium", ray = ray.right))
list.push(zone_ids, draw.zone.create(left, eq + rng * 0.05, bar.index + 10, eq - rng * 0.05, color = color.translucent(color.slate.400, 85), text = "Equilibrium", ray = ray.right))
list.push(zone_ids, draw.zone.create(left, eq - rng * 0.05, bar.index + 10, lo, color = color.translucent(color.emerald.500, 82), text = "Discount", ray = ray.right))
bull_leg = lo_bar < hi_bar
ote_top = bull_leg ? hi - rng * 0.62 : lo + rng * 0.79
ote_bot = bull_leg ? hi - rng * 0.79 : lo + rng * 0.62
list.push(zone_ids, draw.zone.create(left, ote_top, bar.index + 10, ote_bot, color = color.translucent(color.amber.400, 78), border_color = color.amber.400, text = "OTE", ray = ray.right))
if show_fib {
for f in [0.236, 0.382, 0.5, 0.618, 0.786] {
y = bull_leg ? hi - rng * f : lo + rng * f
list.push(seg_ids, draw.segment.create(left, y, bar.index + 10, y, color = color.translucent(color.sky.300, 45), stroke = style.dotted, ray = ray.right))
list.push(tag_ids, draw.tag.create(left, y, str.format("{:.3f}", f), color = color.translucent(color.sky.700, 20), shape = shape.tag, size = 0.7))
}
}
}
render.step(ready ? hi : na, "Range high", color = color.rose.400, width = 2)
render.step(ready ? lo : na, "Range low", color = color.emerald.400, width = 2)
render.step(ready ? eq : na, "Equilibrium", color = color.slate.300, width = 1, stroke = style.dashed)
in_disc = ready and close < eq - rng * 0.25
in_prem = ready and close > eq + rng * 0.25
render.candle_color(in_disc ? color.emerald.500 : in_prem ? color.rose.500 : na)
alert.rule(in_disc and not in_disc[1], "Deep discount", "Price entered the lower quarter of the dealing range")
alert.rule(in_prem and not in_prem[1], "Deep premium", "Price entered the upper quarter of the dealing range")Support & Resistance Heat Levels
analysis · presets/analysis/smc/sr_heat_levels.qs
Swing pivots become horizontal levels that gain strength each time price touches them and from the volume traded at the touch. Each level is drawn as a zone whose opacity and label show its strength; levels merge when they are closer than a fraction of ATR and are removed once price closes decisively through them.
analysis "Support & Resistance Heat Levels" {
overlay = true
max_objects = 300
}
/// Swing pivots become horizontal levels that gain strength each time price touches them and
/// from the volume traded at the touch. Each level is drawn as a zone whose opacity and label show
/// its strength; levels merge when they are closer than a fraction of ATR and are removed once
/// price closes decisively through them.
legs = param.int(6, title = "Pivot legs", min = 2, max = 30)
merge = param.float(0.4, title = "Merge distance (ATR)", min = 0.05, max = 2, step = 0.05)
keep = param.int(12, title = "Levels kept", min = 2, max = 40)
ph = ta.pivot_high(legs, legs)
pl = ta.pivot_low(legs, legs)
a = ta.atr(50)
rvol = volume / math.max(ta.sma(volume, 50), 1e-12)
persist prices = list.create(float)
persist power = list.create(float)
persist ids = list.create(float)
fn add_level(price: float, w: float, half: float, x: int) {
// merge into an existing level when close enough
found = -1
k = 0
for p in prices {
if found < 0 and math.abs(p - price) <= merge * a { found := k }
k := k + 1
}
if found >= 0 {
list.set(power, found, list.get(power, found) + w)
} else {
list.push(prices, price)
list.push(power, w)
list.push(ids, draw.zone.create(x, price + half, bar.index, price - half, color = color.translucent(color.amber.400, 85)))
}
}
half = a * 0.12
if not math.is_na(ph) { add_level(ph, 1 + rvol[legs], half, bar.index - legs) }
if not math.is_na(pl) { add_level(pl, 1 + rvol[legs], half, bar.index - legs) }
i = list.size(prices) - 1
while i >= 0 {
p = list.get(prices, i)
id = list.get(ids, i)
// touches strengthen a level; a close more than one ATR beyond it breaks it
if low <= p + half and high >= p - half { list.set(power, i, list.get(power, i) + 0.25 * rvol) }
broken = math.abs(close - p) > a and ((close > p and open < p) or (close < p and open > p))
if broken {
draw.zone.update(id, right = bar.index)
list.remove(prices, i)
list.remove(power, i)
list.remove(ids, i)
} else {
s = math.clamp(list.get(power, i) / 8, 0, 1)
res = p > close
tone = res ? color.gradient(s, 0, 1, color.rose.900, color.rose.400) : color.gradient(s, 0, 1, color.emerald.900, color.emerald.400)
draw.zone.update(id, right = bar.index + 5, color = color.translucent(tone, 70 - s * 40), text = str.format("{} {:.1f}", res ? "R" : "S", list.get(power, i)))
}
i := i - 1
}
while list.size(prices) > keep {
draw.zone.update(list.first(ids), right = bar.index)
list.shift(prices)
list.shift(power)
list.shift(ids)
}Swing Structure Map
analysis · presets/analysis/smc/swing_structure_map.qs
Connects confirmed swing points with a polyline and labels each swing as a higher high, lower high, higher low or lower low. Each leg is drawn in the color of its direction, the latest swing range is shaded and candles take the structure bias.
analysis "Swing Structure Map" {
overlay = true
max_objects = 400
}
/// Connects confirmed swing points with a polyline and labels each swing as a higher high, lower
/// high, higher low or lower low. Each leg is drawn in the color of its direction, the latest
/// swing range is shaded and candles take the structure bias.
legs = param.int(5, title = "Swing legs", min = 2, max = 30)
labels = param.bool(true, title = "HH / HL / LH / LL labels")
ph = ta.pivot_high(legs, legs)
pl = ta.pivot_low(legs, legs)
persist last_x = na
persist last_y = na
persist last_hi = na
persist last_lo = na
persist bias = 0
persist rng_id = 0.0
x = bar.index - legs
a = ta.atr(14)
if not math.is_na(ph) {
if not math.is_na(last_x) {
draw.segment.create(last_x, last_y, x, ph, color = color.emerald.400, width = 2)
}
hh = math.is_na(last_hi) or ph > last_hi
if labels { draw.tag.create(x, ph + 0.2 * a, hh ? "HH" : "LH", color = hh ? color.emerald.600 : color.orange.600, shape = shape.tag_down, size = 0.8) }
if not hh { bias := -1 }
last_hi := ph
last_x := x
last_y := ph
}
if not math.is_na(pl) {
if not math.is_na(last_x) {
draw.segment.create(last_x, last_y, x, pl, color = color.rose.400, width = 2)
}
hl = math.is_na(last_lo) or pl > last_lo
if labels { draw.tag.create(x, pl - 0.2 * a, hl ? "HL" : "LL", color = hl ? color.teal.600 : color.rose.600, shape = shape.tag_up, size = 0.8) }
if hl { bias := 1 }
last_lo := pl
last_x := x
last_y := pl
}
if (not math.is_na(ph) or not math.is_na(pl)) and not math.is_na(last_hi) and not math.is_na(last_lo) {
if rng_id > 0 { draw.zone.delete(rng_id) }
rng_id := draw.zone.create(x, last_hi, bar.index + 15, last_lo, color = color.translucent(color.indigo.400, 90), border_color = color.translucent(color.indigo.300, 60), text = "Swing range")
}
render.step(last_hi, "Last swing high", color = color.translucent(color.emerald.300, 60), width = 1, stroke = style.dotted)
render.step(last_lo, "Last swing low", color = color.translucent(color.rose.300, 60), width = 1, stroke = style.dotted)
render.candle_color(bias > 0 ? color.teal.500 : bias < 0 ? color.rose.500 : na)
alert.rule(not math.is_na(pl) and bias > 0 and bias[1] <= 0, "Higher low", "Structure turned up with a higher low")
alert.rule(not math.is_na(ph) and bias < 0 and bias[1] >= 0, "Lower high", "Structure turned down with a lower high")