
QuaScript Documentation
Language guide, engine API, 601 builtins and the preset library · v3.0.0-phase1
Volume
Anchored VWAP Swings
analysis · presets/analysis/volume/anchored_vwap_swings.qs
Two VWAPs re-anchored automatically: one from the last confirmed swing high and one from the last swing low, each with a one-sigma band. The space between them is a gradient cloud, the anchors are tagged where they start and candles are colored by which side of both VWAPs price trades on.
analysis "Anchored VWAP Swings" {
overlay = true
max_objects = 200
}
/// Two VWAPs re-anchored automatically: one from the last confirmed swing high and one from the
/// last swing low, each with a one-sigma band. The space between them is a gradient cloud, the
/// anchors are tagged where they start and candles are colored by which side of both VWAPs price
/// trades on.
legs = param.int(10, title = "Swing legs", min = 2, max = 60)
band = param.float(1.0, title = "Band (sigma)", min = 0.2, max = 4, step = 0.1)
ph = ta.pivot_high(legs, legs)
pl = ta.pivot_low(legs, legs)
tp = bar.typical
v = math.max(volume, 1e-9)
persist hi_pv = na
persist hi_v = na
persist hi_pv2 = na
persist lo_pv = na
persist lo_v = na
persist lo_pv2 = na
a = ta.atr(14)
if not math.is_na(ph) {
// re-anchor at the swing bar: sum the bars from the pivot up to now
s_pv = 0.0
s_v = 0.0
s_pv2 = 0.0
for k in 0..=legs {
s_pv := s_pv + tp[k] * math.max(volume[k], 1e-9)
s_v := s_v + math.max(volume[k], 1e-9)
s_pv2 := s_pv2 + tp[k] * tp[k] * math.max(volume[k], 1e-9)
}
hi_pv := s_pv
hi_v := s_v
hi_pv2 := s_pv2
draw.tag.create(bar.index - legs, ph + 0.3 * a, "AVWAP high", color = color.rose.600, shape = shape.tag_down, size = 0.8)
} else if not math.is_na(hi_v) {
hi_pv := hi_pv + tp * v
hi_v := hi_v + v
hi_pv2 := hi_pv2 + tp * tp * v
}
if not math.is_na(pl) {
s_pv = 0.0
s_v = 0.0
s_pv2 = 0.0
for k in 0..=legs {
s_pv := s_pv + tp[k] * math.max(volume[k], 1e-9)
s_v := s_v + math.max(volume[k], 1e-9)
s_pv2 := s_pv2 + tp[k] * tp[k] * math.max(volume[k], 1e-9)
}
lo_pv := s_pv
lo_v := s_v
lo_pv2 := s_pv2
draw.tag.create(bar.index - legs, pl - 0.3 * a, "AVWAP low", color = color.emerald.600, shape = shape.tag_up, size = 0.8)
} else if not math.is_na(lo_v) {
lo_pv := lo_pv + tp * v
lo_v := lo_v + v
lo_pv2 := lo_pv2 + tp * tp * v
}
vh = hi_pv / hi_v
vl = lo_pv / lo_v
sd_h = math.sqrt(math.max(hi_pv2 / hi_v - vh * vh, 0))
sd_l = math.sqrt(math.max(lo_pv2 / lo_v - vl * vl, 0))
p_h = render.line(vh, "VWAP from high", color = color.rose.400, width = 2)
p_l = render.line(vl, "VWAP from low", color = color.emerald.400, width = 2)
render.band(p_h, p_l, texture = "gradient", upper_color = color.rose.500, lower_color = color.emerald.500, opacity = 0.16, title = "Anchor cloud")
hu = render.line(vh + band * sd_h, "High band", color = color.translucent(color.rose.300, 60), width = 1, stroke = style.dotted)
hd = render.line(vh - band * sd_h, "High band low", color = color.translucent(color.rose.300, 60), width = 1, stroke = style.dotted)
lu = render.line(vl + band * sd_l, "Low band high", color = color.translucent(color.emerald.300, 60), width = 1, stroke = style.dotted)
ld = render.line(vl - band * sd_l, "Low band", color = color.translucent(color.emerald.300, 60), width = 1, stroke = style.dotted)
render.band(hu, hd, color = color.rose.500, opacity = 0.05, title = "High anchor band")
render.band(lu, ld, color = color.emerald.500, opacity = 0.05, title = "Low anchor band")
above = close > math.max(vh, vl)
below = close < math.min(vh, vl)
render.candle_color(above ? color.emerald.400 : below ? color.rose.400 : color.slate.500)
render.mark(above and not above[1], shape = shape.triangle_up, at = anchor.below_bar, color = color.emerald.300, title = "Above both")
render.mark(below and not below[1], shape = shape.triangle_down, at = anchor.above_bar, color = color.rose.300, title = "Below both")
alert.rule(above and not above[1], "Above both anchored VWAPs", "Close above the VWAPs from the last swing high and low")
alert.rule(below and not below[1], "Below both anchored VWAPs", "Close below the VWAPs from the last swing high and low")Cumulative Volume Delta
analysis · presets/analysis/volume/cumulative_delta.qs · uses viz/palette
Buy / sell volume split by where each bar closes inside its range (or the host's real buy / sell volume when supplied), accumulated per session as candles, with a delta EMA and price / delta divergence marks.
analysis "Cumulative Volume Delta" {
overlay = false
}
/// Buy / sell volume split by where each bar closes inside its range (or the host's real
/// buy / sell volume when supplied), accumulated per session as candles, with a delta EMA and
/// price / delta divergence marks.
use "viz/palette" as pal
len = param.int(20, title = "Signal length", min = 2)
range = high - low
buy_share = range > 0 ? (close - low) / range : 0.5
delta = volume * (2 * buy_share - 1)
new_day = time.day() != time.day(time[1])
persist cvd = 0.0
open_cvd = new_day ? 0.0 : cvd
cvd := open_cvd + delta
hi = math.max(open_cvd, cvd)
lo = math.min(open_cvd, cvd)
render.price.candles(o = open_cvd, h = hi, l = lo, c = cvd, up_color = pal.BULL, down_color = pal.BEAR, title = "CVD")
render.line(ta.ema(cvd, len), "CVD EMA", color = pal.ACCENT, width = 1)
render.level(0, "Zero", color = color.slate.600)
price_up = close > ta.highest(close[1], len)
cvd_weak = cvd < ta.highest(cvd[1], len)
price_dn = close < ta.lowest(close[1], len)
cvd_strong = cvd > ta.lowest(cvd[1], len)
render.mark(price_up and cvd_weak, shape = shape.x, at = anchor.top, color = pal.BEAR, text = "Absorption")
render.mark(price_dn and cvd_strong, shape = shape.x, at = anchor.bottom, color = pal.BULL, text = "Absorption")Cumulative Delta Candles
analysis · presets/analysis/volume/delta_candles.qs
Cumulative volume delta drawn as candles: each bar opens at the previous total, its wicks reach the buy and sell volume and it closes at the new total. Buy / sell volume is the chart's own order flow when supplied, else bulk volume classification. An EMA ribbon of the delta and divergences against price show absorption.
analysis "Cumulative Delta Candles" {
pane = hpane
height = 0.3
}
/// Cumulative volume delta drawn as candles: each bar opens at the previous total, its wicks
/// reach the buy and sell volume and it closes at the new total. Buy / sell volume is the chart's
/// own order flow when supplied, else bulk volume classification. An EMA ribbon of the delta and
/// divergences against price show absorption.
win = param.int(20, title = "Classification window", min = 5, max = 200)
fast = param.int(9, title = "Fast EMA", min = 2, max = 200)
slow = param.int(34, title = "Slow EMA", min = 5, max = 500)
reset = param.bool(true, title = "Reset every day")
(buy, sell, imb) = hft.flow(win)
day = time.day()
new_day = day != day[1]
persist cvd = 0.0
prev = (reset and new_day) ? 0.0 : cvd
cvd := prev + buy - sell
c = cvd
h = math.max(prev + buy, math.max(prev, c))
l = math.min(prev - sell, math.min(prev, c))
render.price.candles(prev, h, l, c, up_color = color.teal.400, down_color = color.rose.500, title = "Delta candles")
ef = ta.ema(cvd, fast)
es = ta.ema(cvd, slow)
p_f = render.line(ef, "Fast EMA", color = ef >= es ? color.teal.300 : color.rose.300, width = 1.5)
p_s = render.line(es, "Slow EMA", color = color.slate.400, width = 1)
render.band(p_f, p_s, texture = "gradient", upper_color = color.teal.500, lower_color = color.rose.500, opacity = 0.2, title = "Delta ribbon")
(bull_div, bear_div) = ta.divergence(cvd, 5, 5, 60)
render.mark(bull_div, shape = shape.tag_up, at = anchor.bottom, color = color.teal.500, text = "Absorb")
render.mark(bear_div, shape = shape.tag_down, at = anchor.top, color = color.rose.500, text = "Absorb")
render.time_mark(reset and new_day, title = "Session reset", color = color.slate.600)
alert.rule(bull_div, "Bullish delta divergence", "Price made a lower low while delta made a higher low")
alert.rule(bear_div, "Bearish delta divergence", "Price made a higher high while delta made a lower high")Money Flow Heat
analysis · presets/analysis/volume/money_flow_heat.qs · uses viz/palette
Money-flow index and Chaikin money flow on one scale (CMF rescaled to 0..100), filled between them, with an on-balance-volume trend tint in the background.
analysis "Money Flow Heat" {
overlay = false
}
/// Money-flow index and Chaikin money flow on one scale (CMF rescaled to 0..100), filled between
/// them, with an on-balance-volume trend tint in the background.
use "viz/palette" as pal
len = param.int(14, title = "MFI length", min = 2)
cmf_len = param.int(20, title = "CMF length", min = 2)
mfi = ta.money_flow(len)
cmf = ta.chaikin_mf(cmf_len) * 50 + 50
obv = ta.obv()
pm = render.line(mfi, "MFI", color = pal.osc_heat(mfi), width = 2)
pc = render.line(cmf, "CMF (scaled)", color = color.violet.400, width = 1)
render.band(pm, pc, color = color.violet.500, opacity = 0.1)
render.level(80, "Distribution", color = color.rose.700, stroke = style.dashed)
render.level(20, "Accumulation", color = color.teal.700, stroke = style.dashed)
render.background(obv > ta.ema(obv, 50) ? color.translucent(pal.BULL, 94) : color.translucent(pal.BEAR, 94))Untitled
analysis · presets/analysis/volume/obv.qs
On-Balance Volume (OBV)
analysis "Untitled" {
render = canvas
overlay = false
}
// On-Balance Volume (OBV)
val = ta.obv()
render.line(val, "OBV", color=color.cyan.500, width=2)Auction Profile Suite
analysis · presets/analysis/volume/profile_suite.qs
A complete auction view: the visible-range volume profile on the chart, the developing session point of control and value area as step lines with a cloud between the value-area edges, and two vertical panes beside the price axis (time at price and the buy / sell split). Candles outside value are highlighted.
analysis "Auction Profile Suite" {
overlay = true
width = 130
}
/// A complete auction view: the visible-range volume profile on the chart, the developing session
/// point of control and value area as step lines with a cloud between the value-area edges, and
/// two vertical panes beside the price axis (time at price and the buy / sell split). Candles
/// outside value are highlighted.
rows = param.int(48, title = "Rows", min = 12, max = 200)
va = param.float(0.7, title = "Value area", min = 0.5, max = 0.95, step = 0.05)
(poc, vah, val) = ta.volume_profile(rows, va, "session")
render.profile.volume(rows = 32, value_area = 0.7, width = 160, position = "right", period = "visible", title = "Visible profile")
p_vah = render.step(vah, "Value high", color = color.sky.400, width = 1.5)
p_val = render.step(val, "Value low", color = color.sky.400, width = 1.5)
render.band(p_vah, p_val, texture = "cloud", color = color.sky.500, opacity = 0.1, title = "Value area")
render.step(poc, "Session POC", color = color.amber.400, width = 2.5)
render.vpane.profile(kind = "tpo", title = "Time at price", rows = 60)
render.vpane.profile(kind = "buy_sell", title = "Buy | Sell", rows = 60)
above = close > vah
below = close < val
render.candle_color(above ? color.sky.300 : below ? color.orange.400 : na)
render.mark(ta.cross_up(close, vah), shape = shape.triangle_up, at = anchor.below_bar, color = color.sky.300, title = "Acceptance above value")
render.mark(ta.cross_down(close, val), shape = shape.triangle_down, at = anchor.above_bar, color = color.orange.400, title = "Acceptance below value")
alert.rule(ta.cross_up(close, vah), "Breakout above value", "Close above the session value-area high")
alert.rule(ta.cross_down(close, val), "Breakdown below value", "Close below the session value-area low")Relative Volume
analysis · presets/analysis/volume/relative_volume.qs
Volume divided by its average for the same time of day over the last N sessions (time-of-day RVOL), computed with a map keyed by the minute of the day. Bars above the threshold glow.
analysis "Relative Volume" {
overlay = false
}
/// Volume divided by its average for the same time of day over the last N sessions (time-of-day
/// RVOL), computed with a map keyed by the minute of the day. Bars above the threshold glow.
days = param.int(10, title = "Sessions", min = 1, max = 60)
thr = param.float(2.0, title = "Highlight above", min = 1, max = 10, step = 0.1)
persist avgs = map.create(float, float)
persist seen = map.create(float, float)
slot = math.floor(time.of_day() / 60)
avg = map.get(avgs, slot)
rvol = math.is_na(avg) or avg <= 0 ? na : volume / avg
// running mean of this minute-of-day slot over (at most) the last `days` sessions
n = math.min(map.get(seen, slot, 0) + 1, days)
map.set(avgs, slot, math.is_na(avg) ? volume : avg + (volume - avg) / n)
map.set(seen, slot, n)
render.histogram(rvol, "RVOL", color = rvol >= thr ? color.amber.400 : rvol >= 1 ? color.teal.600 : color.slate.600)
render.level(1, "Average", color = color.slate.500, stroke = style.dotted)
render.level(thr, "Threshold", color = color.amber.700, stroke = style.dashed)
alert.rule(ta.cross_up(rvol, thr), "Unusual volume", "Volume is well above its usual level for this time of day")Session Levels & Opening Range
analysis · presets/analysis/volume/session_levels.qs
Key intraday reference levels: the previous day's high, low and close, today's open, and the opening range of the first N bars drawn as a zone with breakout targets at one and two range heights. Levels start at the session and end with it; breakouts are tagged.
analysis "Session Levels & Opening Range" {
overlay = true
max_objects = 400
}
/// Key intraday reference levels: the previous day's high, low and close, today's open, and the
/// opening range of the first N bars drawn as a zone with breakout targets at one and two range
/// heights. Levels start at the session and end with it; breakouts are tagged.
or_bars = param.int(6, title = "Opening range bars", min = 1, max = 60)
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
persist d_open = na
persist n = 0
persist or_hi = na
persist or_lo = na
persist or_id = 0.0
persist broke = 0
if new_day {
p_hi := d_hi
p_lo := d_lo
p_cl := close[1]
d_hi := high
d_lo := low
d_open := open
n := 1
or_hi := high
or_lo := low
broke := 0
or_id := 0
} else {
d_hi := math.max(d_hi, high)
d_lo := math.min(d_lo, low)
n := n + 1
if n <= or_bars {
or_hi := math.max(or_hi, high)
or_lo := math.min(or_lo, low)
}
}
or_done = n == or_bars
if or_done {
or_id := draw.zone.create(bar.index - or_bars + 1, or_hi, bar.index, or_lo, color = color.translucent(color.amber.400, 85), border_color = color.amber.400, text = "Opening range")
}
if n > or_bars and or_id > 0 { draw.zone.update(or_id, right = bar.index) }
h = or_hi - or_lo
a = ta.atr(14)
up_break = n > or_bars and broke == 0 and close > or_hi
dn_break = n > or_bars and broke == 0 and close < or_lo
if up_break {
broke := 1
draw.tag.create(bar.index, low - 0.3 * a, "OR breakout", color = color.emerald.600, shape = shape.tag_up)
}
if dn_break {
broke := -1
draw.tag.create(bar.index, high + 0.3 * a, "OR breakdown", color = color.rose.600, shape = shape.tag_down)
}
render.step(p_hi, "Previous high", color = color.rose.400, width = 1.5, stroke = style.dashed)
render.step(p_lo, "Previous low", color = color.emerald.400, width = 1.5, stroke = style.dashed)
render.step(p_cl, "Previous close", color = color.slate.300, width = 1, stroke = style.dotted)
render.step(d_open, "Today's open", color = color.sky.300, width = 1.5)
render.step(n > or_bars and broke >= 0 ? or_hi + h : na, "Target +1R", color = color.translucent(color.emerald.300, 40), width = 1)
render.step(n > or_bars and broke >= 0 ? or_hi + 2 * h : na, "Target +2R", color = color.translucent(color.emerald.300, 60), width = 1)
render.step(n > or_bars and broke <= 0 ? or_lo - h : na, "Target -1R", color = color.translucent(color.rose.300, 40), width = 1)
render.step(n > or_bars and broke <= 0 ? or_lo - 2 * h : na, "Target -2R", color = color.translucent(color.rose.300, 60), width = 1)
render.time_mark(new_day, title = "Session start", color = color.slate.700)
alert.rule(up_break, "Opening range breakout", "Close above the opening range")
alert.rule(dn_break, "Opening range breakdown", "Close below the opening range")Session Volume Profile
analysis · presets/analysis/volume/session_volume_profile.qs
Per-session volume profile drawn on the GPU, plus the developing session POC / value area as lines so they can be traded against.
analysis "Session Volume Profile" {
overlay = true
render = webgl
}
// Per-session volume profile drawn on the GPU, plus the developing session POC / value area
// as lines so they can be traded against.
rows = param.int(80, title = "Price rows", min = 8, max = 400)
area = param.float(0.70, title = "Value area", min = 0.5, max = 0.95, step = 0.05)
width = param.float(180, title = "Width (px)", min = 40, max = 500)
render.profile.session(rows = rows, value_area = area, width = width, up_color = color.emerald.500, down_color = color.rose.500, poc_color = color.amber.400, title = "Session profile")
(poc, vah, val) = ta.volume_profile(rows = 24, value_area = 0.7, period = "session")
render.step(poc, "Developing POC", color = color.amber.500, width = 2)
p_vah = render.step(vah, "Value area high", color = color.rose.400, width = 1)
p_val = render.step(val, "Value area low", color = color.emerald.400, width = 1)
render.band(p_vah, p_val, color = color.blue.500, opacity = 0.06, title = "Value area")
render.mark(ta.cross_up(close, vah), shape = shape.triangle_up, at = anchor.below_bar, color = color.cyan.500, text = "VAH break")
render.mark(ta.cross_down(close, val), shape = shape.triangle_down, at = anchor.above_bar, color = color.rose.500, text = "VAL break")
alert.rule(ta.cross_up(close, vah), "Value area breakout", "Price broke above the value area high")
alert.rule(ta.cross_down(close, val), "Value area breakdown", "Price broke below the value area low")Smart Money Flow Cloud
analysis · presets/analysis/volume/smart_money_flow.qs
Chaikin money flow, the slope of on-balance volume and the order-flow imbalance combined into one flow score (-1 .. +1). The score paints a volatility cloud around a volume-weighted average, widens it while flow is strong and colors the candles; marks show flow flipping against price (distribution in a rally, accumulation in a sell-off).
analysis "Smart Money Flow Cloud" {
overlay = true
}
/// Chaikin money flow, the slope of on-balance volume and the order-flow imbalance combined into
/// one flow score (-1 .. +1). The score paints a volatility cloud around a volume-weighted
/// average, widens it while flow is strong and colors the candles; marks show flow flipping
/// against price (distribution in a rally, accumulation in a sell-off).
len = param.int(21, title = "Flow length", min = 5, max = 200)
blen = param.int(34, title = "Basis length", min = 5, max = 300)
cmf = ta.chaikin_mf(len)
obv = ta.obv()
obv_s = math.clamp((obv - obv[len]) / math.max(stat.sum(volume, len), 1e-12) * 2, -1, 1)
(buy, sell, imb) = hft.flow(len)
flow = ta.ema(math.clamp(cmf * 3, -1, 1) * 0.4 + obv_s * 0.3 + imb * 0.3, 5)
basis = ta.vwma(close, blen)
a = ta.atr(blen)
w = a * (1 + math.abs(flow) * 1.5)
heat = flow >= 0 ? color.gradient(flow, 0, 0.6, color.slate.400, color.teal.300) : color.gradient(flow, -0.6, 0, color.pink.400, color.slate.400)
pb = render.line(basis, "Flow basis", color = heat, width = 3)
pu = render.line(basis + w, "Cloud top", color = color.translucent(color.teal.300, 70), width = 1)
pl = render.line(basis - w, "Cloud bottom", color = color.translucent(color.pink.300, 70), width = 1)
render.band(pu, pb, texture = "gradient", upper_color = color.teal.400, lower_color = color.slate.800, opacity = 0.25, title = "Buy pressure")
render.band(pb, pl, texture = "gradient", upper_color = color.slate.800, lower_color = color.pink.500, opacity = 0.25, title = "Sell pressure")
render.candle_color(color.gradient(flow, -0.6, 0.6, color.pink.500, color.teal.400))
distribution = flow < -0.15 and close > basis and ta.rising(close, 3)
accumulation = flow > 0.15 and close < basis and ta.falling(close, 3)
render.mark(distribution and not distribution[1], shape = shape.tag_down, at = anchor.above_bar, color = color.pink.500, text = "Dist")
render.mark(accumulation and not accumulation[1], shape = shape.tag_up, at = anchor.below_bar, color = color.teal.500, text = "Accum")
alert.rule(distribution and not distribution[1], "Distribution", "Money flows out while price rises")
alert.rule(accumulation and not accumulation[1], "Accumulation", "Money flows in while price falls")Value Area Migration
analysis · presets/analysis/volume/value_area_migration.qs
The developing session value area (POC, VAH, VAL) and the previous session's value area carried forward as a zone. How today's value relates to yesterday's (higher, lower, inside, outside or overlapping) classifies the auction; the class is shown in a table and colors the candles, and acceptance outside the prior value is marked.
analysis "Value Area Migration" {
overlay = true
max_objects = 300
}
/// The developing session value area (POC, VAH, VAL) and the previous session's value area
/// carried forward as a zone. How today's value relates to yesterday's (higher, lower, inside,
/// outside or overlapping) classifies the auction; the class is shown in a table and colors the
/// candles, and acceptance outside the prior value is marked.
rows = param.int(48, title = "Rows", min = 12, max = 200)
va = param.float(0.7, title = "Value area", min = 0.5, max = 0.95, step = 0.05)
(poc, vah, val) = ta.volume_profile(rows, va, "session")
day = time.day()
new_day = day != day[1]
persist p_vah = na
persist p_val = na
persist p_poc = na
persist zid = 0.0
if new_day {
p_vah := vah[1]
p_val := val[1]
p_poc := poc[1]
if not math.is_na(p_vah) {
zid := draw.zone.create(bar.index, p_vah, bar.index, p_val, color = color.translucent(color.slate.400, 88), border_color = color.slate.500, text = "Prior value")
}
} else if zid > 0 {
draw.zone.update(zid, right = bar.index)
}
higher = val > p_vah
lower = vah < p_val
inside = vah <= p_vah and val >= p_val
outside = vah > p_vah and val < p_val
cls = higher ? 2 : lower ? -2 : inside ? 0 : outside ? 3 : (poc > p_poc ? 1 : -1)
tone = match cls {
case 2 { color.emerald.400 }
case 1 { color.teal.500 }
case -1 { color.orange.500 }
case -2 { color.rose.400 }
case 3 { color.violet.400 }
else { color.slate.400 }
}
pu = render.step(vah, "Value high", color = color.sky.400, width = 1.5)
pl = render.step(val, "Value low", color = color.sky.400, width = 1.5)
render.band(pu, pl, texture = "gradient", upper_color = color.sky.500, lower_color = color.indigo.500, opacity = 0.12, title = "Developing value")
render.step(poc, "Developing POC", color = color.amber.400, width = 2.5)
render.step(p_poc, "Prior POC", color = color.translucent(color.amber.300, 50), width = 1, stroke = style.dashed)
render.candle_color(tone)
acc_up = close > p_vah and close[1] > p_vah and close[2] <= p_vah
acc_dn = close < p_val and close[1] < p_val and close[2] >= p_val
render.mark(acc_up, shape = shape.tag_up, at = anchor.below_bar, color = color.emerald.500, text = "Accepted")
render.mark(acc_dn, shape = shape.tag_down, at = anchor.above_bar, color = color.rose.500, text = "Accepted")
persist t = draw.table.create(anchor.top_right, 2, 2)
if bar.is_last {
name = match cls {
case 2 { "Higher value" }
case 1 { "Overlap, higher" }
case -1 { "Overlap, lower" }
case -2 { "Lower value" }
case 3 { "Outside day" }
else { "Inside day" }
}
draw.table.cell(t, 0, 0, "Auction", text_color = color.slate.400)
draw.table.cell(t, 0, 1, name, bg_color = tone, text_color = color.readable(tone))
draw.table.cell(t, 1, 0, "POC shift", text_color = color.slate.400)
draw.table.cell(t, 1, 1, str.format("{:+.2f}", poc - p_poc))
}
alert.rule(acc_up, "Accepted above prior value", "Two closes above yesterday's value-area high")
alert.rule(acc_dn, "Accepted below prior value", "Two closes below yesterday's value-area low")Volume Heat Candles
analysis · presets/analysis/volume/volume_heat_candles.qs
Candles colored by relative volume (cold = thin, white-hot = climactic) with a volume-weighted average cloud and markers for climax bars, stopping volume (heavy volume with a small body at an extreme) and no-demand / no-supply bars.
analysis "Volume Heat Candles" {
overlay = true
}
/// Candles colored by relative volume (cold = thin, white-hot = climactic) with a volume-weighted
/// average cloud and markers for climax bars, stopping volume (heavy volume with a small body at
/// an extreme) and no-demand / no-supply bars.
len = param.int(50, title = "Volume average", min = 5, max = 500)
climax = param.float(2.5, title = "Climax multiple", min = 1.5, max = 10, step = 0.1)
rv = volume / math.max(ta.sma(volume, len), 1e-12)
heat = rv < 1 ? color.gradient(rv, 0, 1, color.slate.700, color.sky.500) : rv < 2 ? color.gradient(rv, 1, 2, color.sky.500, color.amber.400) : color.gradient(rv, 2, climax, color.amber.400, color.yellow.100)
render.candle_color(heat)
vw = ta.vwma(close, 20)
em = ta.ema(close, 20)
p_v = render.line(vw, "VWMA", color = color.amber.300, width = 2)
p_e = render.line(em, "EMA", color = color.sky.300, width = 1)
render.band(p_v, p_e, texture = "gradient", upper_color = color.amber.500, lower_color = color.sky.500, opacity = 0.2, title = "Participation cloud")
body = math.abs(close - open) / math.max(high - low, 1e-12)
hi_ext = high >= ta.highest(high, 20)
lo_ext = low <= ta.lowest(low, 20)
is_climax = rv >= climax
stopping = rv > 2 and body < 0.3 and (hi_ext or lo_ext)
no_demand = close > open and rv < 0.5 and high > high[1]
no_supply = close < open and rv < 0.5 and low < low[1]
render.mark(is_climax and close > open, shape = shape.star, at = anchor.above_bar, color = color.yellow.200, title = "Buying climax")
render.mark(is_climax and close < open, shape = shape.star, at = anchor.below_bar, color = color.yellow.200, title = "Selling climax")
render.mark(stopping and lo_ext, shape = shape.square, at = anchor.below_bar, color = color.emerald.400, size = 0.7, title = "Stopping volume")
render.mark(stopping and hi_ext, shape = shape.square, at = anchor.above_bar, color = color.rose.400, size = 0.7, title = "Stopping volume high")
render.mark(no_demand, shape = shape.x, at = anchor.above_bar, color = color.slate.400, size = 0.5, title = "No demand")
render.mark(no_supply, shape = shape.x, at = anchor.below_bar, color = color.slate.400, size = 0.5, title = "No supply")
alert.rule(is_climax, "Volume climax", "Volume above the climax multiple of its average")Volume Oscillator
analysis · presets/analysis/volume/volume_osc.qs
Percent difference between a short and a long volume EMA.
analysis "Volume Oscillator" {
overlay = false
}
// Percent difference between a short and a long volume EMA.
short_len = param.int(5, title = "Short", min = 1, max = 50)
long_len = param.int(10, title = "Long", min = 1, max = 100)
osc = ta.volume_osc(short_len, long_len)
render.histogram(osc, "Volume oscillator", color = osc >= 0 ? color.teal.500 : color.rose.500)
render.level(0, "Zero", color = color.slate.500)Volume Profile
analysis · presets/analysis/volume/volume_profile.qs
Visible-range volume profile with POC and value area, computed by the engine.
analysis "Volume Profile" {
overlay = true
render = webgl
}
// Visible-range volume profile with POC and value area, computed by the engine.
rows = param.int(70, title = "Price rows", min = 8, max = 400)
area = param.float(0.70, title = "Value area", min = 0.5, max = 0.95, step = 0.05)
width = param.float(160, title = "Width (px)", min = 40, max = 400)
render.profile.volume(rows = rows, value_area = area, width = width, up_color = color.teal.500, down_color = color.rose.500, poc_color = color.amber.400)VWAP Constellation
analysis · presets/analysis/volume/vwap_constellation.qs
Daily and weekly VWAP with three deviation bands each. The daily bands are cloud-textured, the VWAP line is colored by its slope, the weekly VWAP acts as the anchor and marks show rejections at the outer bands.
analysis "VWAP Constellation" {
overlay = true
}
/// Daily and weekly VWAP with three deviation bands each. The daily bands are cloud-textured,
/// the VWAP line is colored by its slope, the weekly VWAP acts as the anchor and marks show
/// rejections at the outer bands.
mult1 = param.float(1.0, title = "Band 1", min = 0.1, max = 5, step = 0.1)
mult2 = param.float(2.0, title = "Band 2", min = 0.1, max = 5, step = 0.1)
mult3 = param.float(3.0, title = "Band 3", min = 0.1, max = 6, step = 0.1)
show_week = param.bool(true, title = "Weekly VWAP")
(v1, u1, l1) = ta.vwap(bar.typical, "day", mult1)
(v2, u2, l2) = ta.vwap(bar.typical, "day", mult2)
(v3, u3, l3) = ta.vwap(bar.typical, "day", mult3)
(w, wu, wl) = ta.vwap(bar.typical, "week", 1.0)
slope = v1 - v1[1]
pv = render.line(v1, "Daily VWAP", color = slope >= 0 ? color.amber.300 : color.orange.500, width = 3)
pu1 = render.line(u1, "+1", color = color.translucent(color.sky.300, 50), width = 1)
pl1 = render.line(l1, "-1", color = color.translucent(color.sky.300, 50), width = 1)
pu2 = render.line(u2, "+2", color = color.translucent(color.indigo.300, 45), width = 1)
pl2 = render.line(l2, "-2", color = color.translucent(color.indigo.300, 45), width = 1)
pu3 = render.line(u3, "+3", color = color.translucent(color.fuchsia.400, 30), width = 1.5)
pl3 = render.line(l3, "-3", color = color.translucent(color.fuchsia.400, 30), width = 1.5)
render.band(pu1, pl1, texture = "cloud", color = color.sky.500, opacity = 0.12, title = "Core value")
render.band(pu2, pu1, texture = "gradient", upper_color = color.indigo.500, lower_color = color.sky.500, opacity = 0.14, title = "Upper extension")
render.band(pl1, pl2, texture = "gradient", upper_color = color.sky.500, lower_color = color.indigo.500, opacity = 0.14, title = "Lower extension")
render.band(pu3, pu2, color = color.fuchsia.500, opacity = 0.08, title = "Upper extreme")
render.band(pl2, pl3, color = color.fuchsia.500, opacity = 0.08, title = "Lower extreme")
render.line(show_week ? w : na, "Weekly VWAP", color = color.lime.300, width = 2, stroke = style.dashed)
rej_hi = high > u2 and close < u2
rej_lo = low < l2 and close > l2
render.mark(rej_hi, shape = shape.triangle_down, at = anchor.above_bar, color = color.fuchsia.400, title = "Upper rejection")
render.mark(rej_lo, shape = shape.triangle_up, at = anchor.below_bar, color = color.sky.300, title = "Lower rejection")
render.candle_color(close > u2 ? color.fuchsia.400 : close < l2 ? color.sky.400 : na)
alert.rule(rej_hi, "Upper band rejection", "Wick above the +2 band closed back inside")
alert.rule(rej_lo, "Lower band rejection", "Wick below the -2 band closed back inside")VWAP Deviation Bands
analysis · presets/analysis/volume/vwap_deviation_bands.qs
Session-anchored VWAP with 1, 2 and 3 standard-deviation bands (volume weighted), a stepped previous-session VWAP and band-touch marks.
analysis "VWAP Deviation Bands" {
overlay = true
}
/// Session-anchored VWAP with 1, 2 and 3 standard-deviation bands (volume weighted), a
/// stepped previous-session VWAP and band-touch marks.
period = param.choice("day", title = "Anchor", options = ["day", "week", "month"])
(vw, u1, l1) = ta.vwap(bar.typical, period, 1)
(_, u2, l2) = ta.vwap(bar.typical, period, 2)
(_, u3, l3) = ta.vwap(bar.typical, period, 3)
render.line(vw, "VWAP", color = color.pink.400, width = 2)
a1 = render.line(u1, "+1 sd", color = color.slate.500, width = 1)
b1 = render.line(l1, "-1 sd", color = color.slate.500, width = 1)
a2 = render.line(u2, "+2 sd", color = color.amber.600, width = 1)
b2 = render.line(l2, "-2 sd", color = color.amber.600, width = 1)
a3 = render.line(u3, "+3 sd", color = color.rose.600, width = 1, stroke = style.dashed)
b3 = render.line(l3, "-3 sd", color = color.rose.600, width = 1, stroke = style.dashed)
render.band(a1, b1, color = color.pink.500, opacity = 0.06)
render.band(a3, a2, color = color.rose.500, opacity = 0.06)
render.band(b2, b3, color = color.teal.500, opacity = 0.06)
render.mark(ta.cross_up(high, u2), shape = shape.circle, at = anchor.above_bar, color = color.amber.500, size = 0.6)
render.mark(ta.cross_down(low, l2), shape = shape.circle, at = anchor.below_bar, color = color.amber.500, size = 0.6)
alert.rule(ta.cross_up(high, u3), "VWAP +3 sd", "Price reached the third upper band")
alert.rule(ta.cross_down(low, l3), "VWAP -3 sd", "Price reached the third lower band")VWAP Bands
analysis · presets/analysis/volume/vwap.qs
Session-anchored VWAP with volume-weighted deviation bands.
analysis "VWAP Bands" {
overlay = true
}
// Session-anchored VWAP with volume-weighted deviation bands.
mult = param.float(1.5, title = "Band multiple", min = 0.1, max = 5, step = 0.1)
(vwap, upper, lower) = ta.vwap(mult = mult)
render.line(vwap, "VWAP", color = color.pink.500, width = 2)
p_up = render.line(upper, "Upper band", color = color.blue.500, width = 1)
p_lo = render.line(lower, "Lower band", color = color.blue.500, width = 1)
render.band(p_up, p_lo, color = color.pink.500, opacity = 0.08, title = "VWAP envelope")
render.mark(ta.cross_up(close, vwap), shape = shape.arrow_up, at = anchor.below_bar, color = color.emerald.500, text = "Above")
render.mark(ta.cross_down(close, vwap), shape = shape.arrow_down, at = anchor.above_bar, color = color.rose.500, text = "Below")
alert.rule(ta.cross_up(close, vwap), "VWAP bullish cross", "Price crossed above VWAP")
alert.rule(ta.cross_down(close, vwap), "VWAP bearish cross", "Price crossed below VWAP")