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

Momentum

Untitled

analysis · presets/analysis/momentum/cci.qs

Commodity Channel Index (CCI) with Arrow Reversals

QuaScriptengine result
Loading the engine result…
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)
cci.qs
analysis "Untitled" {
    render  = canvas
    overlay = false
}
// Commodity Channel Index (CCI) with Arrow Reversals
len = param.float(20, "length", min=1, max=100)

val = ta.cci(close, len)
render.line(val, "CCI", color=color.cyan.500, width=2)
render.level(100,  "Overbought (+100)", color=color.rose.500)
render.level(0,    "Zero Line",         color=color.slate.500)
render.level(-100, "Oversold (-100)",   color=color.teal.500)

render.mark(ta.cross_up(val, -100), shape=shape.arrow_up, text="REVERSAL BUY", color=color.emerald.500, at=anchor.below_bar)
render.mark(ta.cross_down(val, 100), shape=shape.arrow_down, text="REVERSAL SELL", color=color.rose.500, at=anchor.above_bar)

alert.rule(ta.cross_up(val, -100), "CCI Oversold Bounce", "CCI bounced out of oversold territory")
alert.rule(ta.cross_down(val, 100), "CCI Overbought Reject", "CCI rejected from overbought territory")

Channel Pressure Oscillator

analysis · presets/analysis/momentum/channel_pressure.qs

Where the close sits inside an adaptive volatility channel, as pressure from -100 to +100. The channel centre is a Kaufman adaptive average and its half-width is a multiple of ATR, so the reading means the same in quiet and wild markets. Pressure is smoothed into a fast and a slow line; the gap between them is drawn as a gradient histogram. Exhaustion marks fire when the fast line turns back from outside the channel, and pivot divergences between price and pressure are labelled.

QuaScriptengine result
Loading the engine result…
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)
channel_pressure.qs
analysis "Channel Pressure Oscillator" {
    overlay = false
}
/// Where the close sits inside an adaptive volatility channel, as pressure from -100 to +100.
/// The channel centre is a Kaufman adaptive average and its half-width is a multiple of ATR, so
/// the reading means the same in quiet and wild markets. Pressure is smoothed into a fast and a
/// slow line; the gap between them is drawn as a gradient histogram. Exhaustion marks fire when
/// the fast line turns back from outside the channel, and pivot divergences between price and
/// pressure are labelled.
ch_len   = param.int(30, title = "Channel length", min = 5, max = 300, group = "Channel")
ch_width = param.float(2.0, title = "Channel width (ATR)", min = 0.5, max = 6, step = 0.1, group = "Channel")
fast_len = param.int(5, title = "Fast smoothing", min = 1, max = 50, group = "Lines")
slow_len = param.int(13, title = "Slow smoothing", min = 2, max = 100, group = "Lines")
zone     = param.float(80, title = "Exhaustion zone", min = 40, max = 100, group = "Lines")
piv      = param.int(5, title = "Divergence pivot bars", min = 2, max = 20, group = "Divergence")
center = ta.kaufman(close, ch_len)
half = math.max(ch_width * ta.atr(ch_len), 1e-12)
raw = math.clamp(100 * (close - center) / half, -150, 150)
fast = ta.ema(raw, fast_len)
slow = ta.ema(fast, slow_len)
gap = fast - slow
t = math.clamp(math.abs(gap) / 40, 0, 1)
gap_col = gap >= 0 ? color.gradient(t, 0, 1, color.slate.700, color.teal.300) : color.gradient(t, 0, 1, color.slate.700, color.orange.400)
render.histogram(gap, "Pressure gap", color = gap_col)
p_fast = render.line(fast, "Fast pressure", color = fast >= slow ? color.teal.300 : color.orange.300, width = 2)
p_slow = render.line(slow, "Slow pressure", color = color.slate.400, width = 1)
render.band(p_fast, p_slow, color = fast >= slow ? color.teal.500 : color.orange.500, opacity = 0.16, title = "Pressure ribbon")
p_top = render.level(zone, "Upper zone", color = color.orange.800)
p_bot = render.level(-zone, "Lower zone", color = color.teal.800)
render.level(0, "Centre", color = color.slate.600, stroke = style.dotted)
fade_up = fast[1] > zone and fast < fast[1] and fast[1] >= fast[2]
fade_dn = fast[1] < -zone and fast > fast[1] and fast[1] <= fast[2]
render.mark(fade_dn, shape = shape.circle, at = anchor.bottom, color = color.teal.300, text = "Fade")
render.mark(fade_up, shape = shape.circle, at = anchor.top, color = color.orange.300, text = "Fade")
// divergences between price pivots and pressure, at confirmed pivots
(bull_div, bear_div) = ta.divergence(fast, piv, piv)
render.mark(bear_div, shape = shape.triangle_down, at = anchor.top, color = color.rose.400, text = "Div")
render.mark(bull_div, shape = shape.triangle_up, at = anchor.bottom, color = color.emerald.400, text = "Div")
alert.rule(fade_up or fade_dn, "Pressure fade", "Fast pressure turned back from outside the channel")
alert.rule(bull_div or bear_div, "Pressure divergence", "Price made a new pivot that pressure did not confirm")

Connors RSI Heat

analysis · presets/analysis/momentum/connors_heat.qs

Connors RSI (price RSI, streak RSI and percent rank of the one-bar return) as a heat-colored histogram around 50, with an ultimate-oscillator confirmation line. Extreme readings shade the pane, and marks appear when an extreme agrees with the slower oscillator.

QuaScriptengine result
Loading the engine result…
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)
connors_heat.qs
analysis "Connors RSI Heat" {
    pane   = hpane
    height = 0.22
}
/// Connors RSI (price RSI, streak RSI and percent rank of the one-bar return) as a heat-colored
/// histogram around 50, with an ultimate-oscillator confirmation line. Extreme readings shade the
/// pane, and marks appear when an extreme agrees with the slower oscillator.
rsi_len    = param.int(3, title = "RSI length", min = 2, max = 20)
streak_len = param.int(2, title = "Streak length", min = 2, max = 20)
rank_len   = param.int(100, title = "Rank length", min = 20, max = 500)
lo         = param.float(10, title = "Oversold", min = 1, max = 40, step = 1)

c = ta.connors_rsi(close, rsi_len, streak_len, rank_len)
u = ta.ultimate(7, 14, 28)
heat = c >= 50 ? color.gradient(c, 50, 100, color.slate.700, color.orange.400) : color.gradient(c, 0, 50, color.cyan.400, color.slate.700)
render.histogram(c, "Connors RSI", base = 50, color = heat)
render.spline(u, "Ultimate", color = color.violet.300, width = 2)
top = render.level(100, "Top", color = color.transparent)
hi = render.level(100 - lo, "Overbought", color = color.orange.700, stroke = style.dashed)
p_os = render.level(lo, "Oversold", color = color.cyan.700, stroke = style.dashed)
bot = render.level(0, "Bottom", color = color.transparent)
render.band(top, hi, color = color.orange.500, opacity = 0.08, title = "Overbought zone")
render.band(p_os, bot, color = color.cyan.500, opacity = 0.08, title = "Oversold zone")
buy = c < lo and u < 40
sell = c > 100 - lo and u > 60
render.mark(buy, shape = shape.circle, at = anchor.bottom, color = color.cyan.300, size = 0.6, title = "Oversold confirmed")
render.mark(sell, shape = shape.circle, at = anchor.top, color = color.orange.300, size = 0.6, title = "Overbought confirmed")
render.background(c < lo ? color.translucent(color.cyan.500, 90) : c > 100 - lo ? color.translucent(color.orange.500, 90) : na)
alert.rule(buy, "Connors oversold", "Connors RSI below the oversold level with a weak ultimate oscillator")
alert.rule(sell, "Connors overbought", "Connors RSI above the overbought level with a strong ultimate oscillator")

Elder Impulse System

analysis · presets/analysis/momentum/elder_impulse.qs

Elder's impulse system: the slope of a 13-bar EMA (inertia) and of the MACD histogram (momentum) agree to give green, red or neutral bars. Shown as the MACD histogram colored by impulse, with the force index as a spline and the triple screen's weekly-equivalent trend (a 5x longer EMA) as the background tide. Price candles take the impulse color too.

QuaScriptengine result
Loading the engine result…
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)
elder_impulse.qs
analysis "Elder Impulse System" {
    pane   = hpane
    height = 0.24
}
/// Elder's impulse system: the slope of a 13-bar EMA (inertia) and of the MACD histogram
/// (momentum) agree to give green, red or neutral bars. Shown as the MACD histogram colored by
/// impulse, with the force index as a spline and the triple screen's weekly-equivalent trend (a
/// 5x longer EMA) as the background tide. Price candles take the impulse color too.
fast = param.int(12, title = "MACD fast", min = 2, max = 100)
slow = param.int(26, title = "MACD slow", min = 5, max = 200)
sig  = param.int(9, title = "MACD signal", min = 2, max = 50)
ema_len = param.int(13, title = "Inertia EMA", min = 2, max = 100)

(m, s, h) = ta.macd(close, fast, slow, sig)
e = ta.ema(close, ema_len)
tide = ta.ema(close, ema_len * 5)
bull = e > e[1] and h > h[1]
bear = e < e[1] and h < h[1]
c = bull ? color.emerald.400 : bear ? color.rose.400 : color.sky.600
render.histogram(h, "Impulse", color = c)
f = ta.force(13)
f_n = f / math.max(stat.stdev(f, 100), 1e-12) * stat.stdev(h, 100)
render.spline(f_n, "Force (scaled)", color = color.amber.300, width = 1.5)
render.line(s - m, "Signal spread", color = color.translucent(color.slate.300, 50), width = 1, stroke = style.dashed)
render.level(0, "Zero", color = color.slate.600)
render.background(tide > tide[1] ? color.translucent(color.emerald.500, 93) : color.translucent(color.rose.500, 93))
render.candle_color(c)
render.mark(bull and not bull[1] and tide > tide[1], shape = shape.triangle_up, at = anchor.bottom, color = color.emerald.400, title = "Impulse buy")
render.mark(bear and not bear[1] and tide < tide[1], shape = shape.triangle_down, at = anchor.top, color = color.rose.400, title = "Impulse sell")
alert.rule(bull and not bull[1] and tide > tide[1], "Impulse long", "Inertia and momentum turned up with the tide")
alert.rule(bear and not bear[1] and tide < tide[1], "Impulse short", "Inertia and momentum turned down with the tide")

Fisher Slope

analysis · presets/analysis/momentum/fisher_regression.qs

The Fisher transform of the regression slope's t-statistic, squashed into a sharp-turning oscillator. Peaks and troughs are clearer than the raw slope, and crossings of the trigger line (its previous value) give timing.

QuaScriptengine result
Loading the engine result…
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)
fisher_regression.qs
analysis "Fisher Slope" {
    pane   = hpane
    height = 0.2
    render = webgpu
}
/// The Fisher transform of the regression slope's t-statistic, squashed into a sharp-turning
/// oscillator. Peaks and troughs are clearer than the raw slope, and crossings of the trigger line
/// (its previous value) give timing.
len = param.int(30, title = "Regression window", min = 5, max = 500)
(v, s, _) = stat.linreg(close, len, 0)
sd = stat.stdev(close - v, len)
tt = s / math.max(sd / math.sqrt(len * (len * len - 1) / 12), 1e-12)
x = math.clamp(tt / 10, -0.999, 0.999)
fz = ta.ema(0.5 * math.log((1 + x) / (1 - x)), 3)
trig = fz[1]
render.line(fz, "Fisher slope", color = fz >= trig ? color.cyan.300 : color.pink.300, width = 2)
render.line(trig, "Trigger", color = color.translucent(color.slate.300, 40), width = 1)
render.level(0, "Zero", color = color.translucent(color.slate.500, 60))
render.mark(ta.cross_up(fz, trig) and fz < -0.5, shape = shape.triangle_up, at = anchor.bottom, color = color.cyan.300, title = "Turn from low")
render.mark(ta.cross_down(fz, trig) and fz > 0.5, shape = shape.triangle_down, at = anchor.top, color = color.pink.300, title = "Turn from high")

Fisher Transform Waves

analysis · presets/analysis/momentum/fisher_waves.qs

Three Fisher transforms (fast, medium and slow) as overlapping waves. The fast wave is filled against its trigger, the slow wave sets the background tide, and a turn of the fast wave at an extreme in the direction of the tide is marked.

QuaScriptengine result
Loading the engine result…
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)
fisher_waves.qs
analysis "Fisher Transform Waves" {
    pane   = hpane
    height = 0.22
}
/// Three Fisher transforms (fast, medium and slow) as overlapping waves. The fast wave is filled
/// against its trigger, the slow wave sets the background tide, and a turn of the fast wave at an
/// extreme in the direction of the tide is marked.
f1 = param.int(9, title = "Fast", min = 3, max = 50)
f2 = param.int(21, title = "Medium", min = 5, max = 100)
f3 = param.int(55, title = "Slow", min = 10, max = 300)
ext = param.float(1.5, title = "Extreme", min = 0.5, max = 4, step = 0.1)

(a1, t1) = ta.fisher(f1)
(a2, _) = ta.fisher(f2)
(a3, _) = ta.fisher(f3)
p1 = render.line(a1, "Fast wave", color = a1 >= t1 ? color.sky.300 : color.pink.300, width = 2)
pt = render.line(t1, "Trigger", color = color.translucent(color.slate.300, 40), width = 1)
render.band(p1, pt, texture = "gradient", upper_color = color.sky.500, lower_color = color.pink.500, opacity = 0.3, title = "Fast fill")
render.spline(a2, "Medium wave", color = color.violet.300, width = 1.5)
render.area(a3, "Slow tide", base = 0, color = a3 >= 0 ? color.translucent(color.emerald.500, 75) : color.translucent(color.rose.500, 75))
render.level(ext, "Upper extreme", color = color.slate.600, stroke = style.dotted)
render.level(-ext, "Lower extreme", color = color.slate.600, stroke = style.dotted)
turn_up = ta.cross_up(a1, t1) and a1 < -ext and a3 > a3[1]
turn_dn = ta.cross_down(a1, t1) and a1 > ext and a3 < a3[1]
render.mark(turn_up, shape = shape.arrow_up, at = anchor.bottom, color = color.sky.300, title = "Wave low")
render.mark(turn_dn, shape = shape.arrow_down, at = anchor.top, color = color.pink.300, title = "Wave high")
alert.rule(turn_up, "Fisher wave low", "Fast Fisher turned up from an extreme with a rising tide")
alert.rule(turn_dn, "Fisher wave high", "Fast Fisher turned down from an extreme with a falling tide")

MACD

analysis · presets/analysis/momentum/macd.qs

Moving average convergence / divergence with a direction-colored histogram.

QuaScriptengine result
Loading the engine result…
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)
macd.qs
analysis "MACD" {
    overlay = false
    render  = webgl
}
// Moving average convergence / divergence with a direction-colored histogram.
fast = param.int(12, title = "Fast", min = 1, max = 100)
slow = param.int(26, title = "Slow", min = 1, max = 200)
sig  = param.int(9, title = "Signal", min = 1, max = 100)

(line, signal, hist) = ta.macd(close, fast, slow, sig)
rising = hist > hist[1]
hist_color = hist >= 0 ? (rising ? color.teal.400 : color.teal.700) : (rising ? color.rose.700 : color.rose.400)
render.histogram(hist, "Histogram", color = hist_color)
render.line(line, "MACD", color = color.blue.500, width = 2)
render.line(signal, "Signal", color = color.amber.500, width = 1)
render.level(0, "Zero", color = color.slate.500)
render.mark(ta.cross_up(line, signal), shape = shape.arrow_up, at = anchor.below_bar, color = color.emerald.500, text = "Bull")
render.mark(ta.cross_down(line, signal), shape = shape.arrow_down, at = anchor.above_bar, color = color.rose.500, text = "Bear")
alert.rule(ta.cross_up(line, signal), "MACD bullish cross", "MACD crossed above its signal line")
alert.rule(ta.cross_down(line, signal), "MACD bearish cross", "MACD crossed below its signal line")

Momentum Matrix

analysis · presets/analysis/momentum/momentum_matrix.qs · uses viz/palette

Seven oscillators normalised to -1 .. +1 and averaged into one composite. An on-screen table shows each oscillator's current reading.

QuaScriptengine result
Loading the engine result…
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)
momentum_matrix.qs
analysis "Momentum Matrix" {
    overlay = false
}
/// Seven oscillators normalised to -1 .. +1 and averaged into one composite. An on-screen table
/// shows each oscillator's current reading.
use "viz/palette" as pal

rsi = (ta.rsi(close, 14) - 50) / 50
(sk, _) = ta.stoch(close, high, low, 14, 3, 3)
stoch = (sk - 50) / 50
wr = (ta.williams(14) + 50) / 50
cci = math.clamp(ta.cci(bar.typical, 20) / 200, -1, 1)
mfi = (ta.money_flow(14) - 50) / 50
(tsi, _) = ta.tsi(close, 13, 25, 13)
tsi_n = math.clamp(tsi / 50, -1, 1)
(fish, _) = ta.fisher(9)
fish_n = math.clamp(fish / 3, -1, 1)
composite = (rsi + stoch + wr + cci + mfi + tsi_n + fish_n) / 7

render.histogram(composite, "Composite", color = pal.signed_heat(composite, 0.6))
render.line(ta.ema(composite, 5), "Signal", color = color.amber.400, width = 2)
render.level(0.5, "Strong up", color = color.teal.800, stroke = style.dotted)
render.level(-0.5, "Strong down", color = color.rose.800, stroke = style.dotted)

persist t = draw.table.create(anchor.top_right, 8, 2)
if bar.is_last {
    row = 0
    for name in ["RSI", "Stoch", "%R", "CCI", "MFI", "TSI", "Fisher"] {
        draw.table.cell(t, row, 0, name, text_color = color.slate.300)
        row := row + 1
    }
    draw.table.cell(t, 0, 1, str.format("{:.2f}", rsi), bg_color = pal.signed_heat(rsi, 1))
    draw.table.cell(t, 1, 1, str.format("{:.2f}", stoch), bg_color = pal.signed_heat(stoch, 1))
    draw.table.cell(t, 2, 1, str.format("{:.2f}", wr), bg_color = pal.signed_heat(wr, 1))
    draw.table.cell(t, 3, 1, str.format("{:.2f}", cci), bg_color = pal.signed_heat(cci, 1))
    draw.table.cell(t, 4, 1, str.format("{:.2f}", mfi), bg_color = pal.signed_heat(mfi, 1))
    draw.table.cell(t, 5, 1, str.format("{:.2f}", tsi_n), bg_color = pal.signed_heat(tsi_n, 1))
    draw.table.cell(t, 6, 1, str.format("{:.2f}", fish_n), bg_color = pal.signed_heat(fish_n, 1))
    draw.table.cell(t, 7, 0, "Composite", text_color = color.white)
    draw.table.cell(t, 7, 1, str.format("{:.2f}", composite), bg_color = pal.signed_heat(composite, 0.6))
}

Momentum Spectrum Heatmap

analysis · presets/analysis/momentum/momentum_spectrum.qs

Sixteen RSI lengths (2 .. 47) stacked as rows of a heatmap: each cell is colored by that length's reading, cool when oversold and hot when overbought. Fast rows sit at the bottom, so momentum shifts can be read travelling up the spectrum. The top row is the spectrum average and marks show a full-spectrum alignment.

QuaScriptengine result
Loading the engine result…
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)
momentum_spectrum.qs
analysis "Momentum Spectrum Heatmap" {
    pane   = hpane
    height = 0.28
}
/// Sixteen RSI lengths (2 .. 47) stacked as rows of a heatmap: each cell is colored by that
/// length's reading, cool when oversold and hot when overbought. Fast rows sit at the bottom, so
/// momentum shifts can be read travelling up the spectrum. The top row is the spectrum average
/// and marks show a full-spectrum alignment.
src   = param.source(close, title = "Source")
first = param.int(2, title = "Shortest length", min = 2, max = 50)
gap   = param.int(3, title = "Length step", min = 1, max = 20)

total = 0.0
hot = 0
cold = 0
for k in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] {
    r = ta.rsi(src, first + k * gap)
    total := total + r
    if r > 60 { hot := hot + 1 }
    if r < 40 { cold := cold + 1 }
    heat = r >= 50 ? color.gradient(r, 50, 85, color.slate.800, color.orange.400) : color.gradient(r, 15, 50, color.cyan.400, color.slate.800)
    render.line(k, str.format("Row {}", k + 1), color = r >= 80 ? color.yellow.300 : r <= 20 ? color.sky.200 : heat, width = 9)
}
avg = total / 16
render.line(17, "Spectrum average", color = color.gradient(avg, 25, 75, color.cyan.400, color.rose.500), width = 12)
render.mark(hot >= 15 and hot[1] < 15, shape = shape.diamond, at = anchor.top, color = color.orange.400, title = "Hot spectrum")
render.mark(cold >= 15 and cold[1] < 15, shape = shape.diamond, at = anchor.bottom, color = color.cyan.400, title = "Cold spectrum")
alert.rule(hot >= 15 and hot[1] < 15, "Spectrum overbought", "Fifteen of sixteen RSI lengths are above 60")
alert.rule(cold >= 15 and cold[1] < 15, "Spectrum oversold", "Fifteen of sixteen RSI lengths are below 40")

Quantum Momentum Oscillator

analysis · presets/analysis/momentum/quantum_momentum.qs

An ensemble of RSI, stochastic RSI, MFI and the Fisher transform rescaled to -100 .. +100. The histogram is shaded by acceleration, a spline signal line follows it, the overbought and oversold areas are gradient bands, divergences with price are marked and the background warms up while the ensemble is extreme.

QuaScriptengine result
Loading the engine result…
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)
quantum_momentum.qs
analysis "Quantum Momentum Oscillator" {
    pane   = hpane
    height = 0.25
}
/// An ensemble of RSI, stochastic RSI, MFI and the Fisher transform rescaled to -100 .. +100.
/// The histogram is shaded by acceleration, a spline signal line follows it, the overbought and
/// oversold areas are gradient bands, divergences with price are marked and the background warms
/// up while the ensemble is extreme.
src  = param.source(close, title = "Source")
len  = param.int(14, title = "Length", min = 3, max = 100)
sig  = param.int(9, title = "Signal", min = 2, max = 50)
ob   = param.float(60, title = "Overbought", min = 10, max = 95, step = 1)

r = (ta.rsi(src, len) - 50) * 2
(k, d) = ta.stoch_rsi(src, len, len, 3, 3)
srsi = (k - 50) * 2
mfi = (ta.money_flow(len) - 50) * 2
(fish, _) = ta.fisher(len)
fz = math.clamp(fish * 33, -100, 100)
q = ta.ema((r * 2 + srsi + mfi + fz) / 5, 3)
signal = ta.ema(q, sig)
accel = q - q[1]
shade = q >= 0 ? (accel >= 0 ? color.emerald.400 : color.emerald.800) : (accel <= 0 ? color.rose.400 : color.rose.800)
render.histogram(q, "Quantum", color = shade)
render.spline(signal, "Signal", color = color.amber.300, width = 2)
top = render.level(100, "Top", color = color.transparent)
p_ob = render.level(ob, "Overbought", color = color.rose.700, stroke = style.dashed)
p_os = render.level(-ob, "Oversold", color = color.emerald.700, stroke = style.dashed)
bot = render.level(-100, "Bottom", color = color.transparent)
render.band(top, p_ob, texture = "gradient", upper_color = color.rose.500, lower_color = color.rose.900, opacity = 0.18, title = "Overbought zone")
render.band(p_os, bot, texture = "gradient", upper_color = color.emerald.900, lower_color = color.emerald.500, opacity = 0.18, title = "Oversold zone")
render.level(0, "Zero", color = color.slate.600, stroke = style.dotted)
(bull_div, bear_div) = ta.divergence(q, 5, 5, 60)
render.mark(bull_div, shape = shape.tag_up, at = anchor.bottom, color = color.emerald.500, text = "Bull div")
render.mark(bear_div, shape = shape.tag_down, at = anchor.top, color = color.rose.500, text = "Bear div")
render.mark(ta.cross_up(q, signal) and q < -ob, shape = shape.circle, at = anchor.bottom, color = color.emerald.300, size = 0.6, title = "Oversold cross")
render.mark(ta.cross_down(q, signal) and q > ob, shape = shape.circle, at = anchor.top, color = color.rose.300, size = 0.6, title = "Overbought cross")
render.background(q > ob ? color.translucent(color.rose.500, 90) : q < -ob ? color.translucent(color.emerald.500, 90) : na)
alert.rule(bull_div, "Bullish divergence", "Price made a lower low while momentum made a higher low")
alert.rule(bear_div, "Bearish divergence", "Price made a higher high while momentum made a lower high")

Untitled

analysis · presets/analysis/momentum/roc.qs

Rate of Change (ROC) Momentum with Arrow Marks

QuaScriptengine result
Loading the engine result…
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)
roc.qs
analysis "Untitled" {
    render  = canvas
    overlay = false
}
// Rate of Change (ROC) Momentum with Arrow Marks
len = param.float(12, "length", min=1, max=100)

val = ta.roc(close, len)
render.line(val, "ROC", color=color.blue.500, width=2)
render.level(0, "Baseline (0)", color=color.slate.500)

render.mark(ta.cross_up(val, 0), shape=shape.arrow_up, text="MOMENTUM UP", color=color.emerald.500, at=anchor.below_bar)
render.mark(ta.cross_down(val, 0), shape=shape.arrow_down, text="MOMENTUM DOWN", color=color.rose.500, at=anchor.above_bar)

alert.rule(ta.cross_up(val, 0), "ROC Turned Positive", "Rate of Change crossed above zero")
alert.rule(ta.cross_down(val, 0), "ROC Turned Negative", "Rate of Change crossed below zero")

RSI Divergence

analysis · presets/analysis/momentum/rsi_divergence.qs · uses viz/palette

RSI with a heat-colored line, regular and hidden divergences found at confirmed pivots, and an overbought / oversold band.

QuaScriptengine result
Loading the engine result…
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)
rsi_divergence.qs
analysis "RSI Divergence" {
    overlay = false
}
/// RSI with a heat-colored line, regular and hidden divergences found at confirmed pivots, and
/// an overbought / oversold band.
use "viz/palette" as pal

len    = param.int(14, title = "RSI length", min = 2, max = 100)
legs   = param.int(5, title = "Pivot legs", min = 1, max = 20)
range  = param.int(60, title = "Max bars between pivots", min = 10, max = 300)
hidden = param.bool(false, title = "Hidden divergences")

r = ta.rsi(close, len)
(bull, bear) = ta.divergence(r, legs, legs, range, hidden)
render.line(r, "RSI", color = pal.osc_heat(r), width = 2)
ob = render.level(70, "Overbought", color = color.rose.700)
render.level(50, "Mid", color = color.slate.600, stroke = style.dotted)
os = render.level(30, "Oversold", color = color.teal.700)
render.band(ob, os, color = color.violet.500, opacity = 0.05)
render.mark(bull, shape = shape.tag_up, at = anchor.bottom, color = pal.BULL, text = "Bull div")
render.mark(bear, shape = shape.tag_down, at = anchor.top, color = pal.BEAR, text = "Bear div")
alert.rule(bull, "Bullish divergence", "RSI made a higher low against a lower price low")
alert.rule(bear, "Bearish divergence", "RSI made a lower high against a higher price high")

RSI Price Bands

analysis · presets/analysis/momentum/rsi_price_bands.qs

The RSI turned inside out: the prices at which the next bar would push the RSI to its overbought, midline and oversold levels, solved from Wilder's averages. The bands show where momentum would become extreme before it happens; the outer areas are gradient-filled and candles that close beyond them are marked.

QuaScriptengine result
Loading the engine result…
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)
rsi_price_bands.qs
analysis "RSI Price Bands" {
    overlay = true
}
/// The RSI turned inside out: the prices at which the next bar would push the RSI to its
/// overbought, midline and oversold levels, solved from Wilder's averages. The bands show where
/// momentum would become extreme before it happens; the outer areas are gradient-filled and
/// candles that close beyond them are marked.
len = param.int(14, title = "RSI length", min = 2, max = 100)
ob  = param.float(70, title = "Overbought", min = 51, max = 88, step = 1)
os  = param.float(30, title = "Oversold", min = 12, max = 49, step = 1)

ch = close - close[1]
up = ta.rma(math.max(ch, 0), len)
dn = ta.rma(math.max(-ch, 0), len)
fn price_at(level: float, u: float, d: float, n: int) -> float {
    x = (n - 1) * (d * level / (100 - level) - u)
    return x >= 0 ? close + x : close + x * (100 - level) / level
}
s80 = ta.ema(price_at(ob + 10, up, dn, len), 3)
s70 = ta.ema(price_at(ob, up, dn, len), 3)
s50 = ta.ema(price_at(50, up, dn, len), 3)
s30 = ta.ema(price_at(os, up, dn, len), 3)
s20 = ta.ema(price_at(os - 10, up, dn, len), 3)
l80 = render.line(s80, "Extreme high", color = color.translucent(color.rose.300, 60), width = 1)
l70 = render.line(s70, "Overbought price", color = color.rose.400, width = 2)
render.line(s50, "Midline price", color = color.translucent(color.slate.300, 40), width = 1, stroke = style.dashed)
l30 = render.line(s30, "Oversold price", color = color.emerald.400, width = 2)
l20 = render.line(s20, "Extreme low", color = color.translucent(color.emerald.300, 60), width = 1)
render.band(l80, l70, texture = "gradient", upper_color = color.rose.500, lower_color = color.rose.900, opacity = 0.3, title = "Overbought area")
render.band(l30, l20, texture = "gradient", upper_color = color.emerald.900, lower_color = color.emerald.500, opacity = 0.3, title = "Oversold area")
render.band(l70, l30, color = color.slate.500, opacity = 0.04, title = "Neutral")
hot = close > s70[1]
cold = close < s30[1]
render.mark(hot and not hot[1], shape = shape.triangle_down, at = anchor.above_bar, color = color.rose.400, title = "Overbought close")
render.mark(cold and not cold[1], shape = shape.triangle_up, at = anchor.below_bar, color = color.emerald.400, title = "Oversold close")
alert.rule(hot and not hot[1], "Overbought price reached", "Close beyond the price that makes the RSI overbought")
alert.rule(cold and not cold[1], "Oversold price reached", "Close beyond the price that makes the RSI oversold")

RSI Regression Channel

analysis · presets/analysis/momentum/rsi_regression_channel.qs

RSI with its own regression channel. Trends in the oscillator often break before trends in price: a break of the RSI channel against its slope is an early warning. The channel is fitted to the last bars of RSI, with sigma bands, and breaks are marked.

QuaScriptengine result
Loading the engine result…
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)
rsi_regression_channel.qs
analysis "RSI Regression Channel" {
    pane   = hpane
    height = 0.22
    render = webgpu
}
/// RSI with its own regression channel. Trends in the oscillator often break before trends in
/// price: a break of the RSI channel against its slope is an early warning. The channel is fitted
/// to the last bars of RSI, with sigma bands, and breaks are marked.
rsi_n = param.int(14, title = "RSI length", min = 2, max = 200)
len   = param.int(40, title = "Channel window", min = 5, max = 500)
k     = param.float(1.5, title = "Band (sigma)", min = 0.5, max = 4, step = 0.1)
r = ta.rsi(close, rsi_n)
(v, s, _) = stat.linreg(r, len, 0)
sd = stat.stdev(r - v, len)
pu = render.line(v + k * sd, "Upper", color = color.translucent(color.violet.300, 40), width = 1)
pc = render.line(v, "Channel", color = color.translucent(color.violet.200, 50), width = 1)
pd = render.line(v - k * sd, "Lower", color = color.translucent(color.violet.300, 40), width = 1)
render.band(pu, pd, color = color.violet.500, opacity = 0.1, title = "RSI channel")
render.line(r, "RSI", color = r > 50 ? color.emerald.300 : color.rose.300, width = 1.8)
render.level(70, "70", color = color.translucent(color.rose.500, 60))
render.level(30, "30", color = color.translucent(color.emerald.500, 60))
up_break = s < 0 and ta.cross_up(r, v + k * sd)
dn_break = s > 0 and ta.cross_down(r, v - k * sd)
render.mark(up_break, shape = shape.triangle_up, at = anchor.bottom, color = color.emerald.300, title = "RSI breaks a falling channel")
render.mark(dn_break, shape = shape.triangle_down, at = anchor.top, color = color.rose.300, title = "RSI breaks a rising channel")
alert.rule(up_break or dn_break, "RSI channel break", "RSI broke its regression channel against the slope")

Untitled

analysis · presets/analysis/momentum/rsi.qs

Relative Strength Index (RSI) with Dynamic Momentum Boundary Shapes

QuaScriptengine result
Loading the engine result…
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)
rsi.qs
analysis "Untitled" {
    render  = canvas
    overlay = false
}
// Relative Strength Index (RSI) with Dynamic Momentum Boundary Shapes
len = param.float(14, "length", min=1, max=100)
overbought = param.float(70, "overbought", min=50, max=95)
oversold   = param.float(30, "oversold", min=5, max=50)

val = ta.rsi(close, len)
render.line(val, "RSI", color=color.purple.500, width=2)
render.level(overbought, "Overbought (70)", color=color.rose.500)
render.level(50, "Centerline (50)", color=color.slate.500)
render.level(oversold, "Oversold (30)", color=color.teal.500)

render.mark(val < oversold, shape=shape.circle, text="OVERSOLD", color=color.emerald.500, at=anchor.below_bar)
render.mark(val > overbought, shape=shape.circle, text="OVERBOUGHT", color=color.rose.500, at=anchor.above_bar)
render.mark(ta.cross_up(val, 50), shape=shape.triangle_up, text="BULL MOMENTUM", color=color.emerald.500, at=anchor.below_bar)
render.mark(ta.cross_down(val, 50), shape=shape.triangle_down, text="BEAR MOMENTUM", color=color.rose.500, at=anchor.above_bar)

alert.rule(val > overbought, "RSI Overbought", "RSI is in overbought territory")
alert.rule(val < oversold, "RSI Oversold", "RSI is in oversold territory")

Sequential Exhaustion

analysis · presets/analysis/momentum/sequential_exhaustion.qs

A nine-bar setup count (close against the close four bars earlier) followed by a thirteen-bar countdown. Counts 7 .. 9 are labelled, a completed setup draws its support or resistance line across the setup and colors the exhaustion bar, and a finished countdown is tagged with a target zone.

QuaScriptengine result
Loading the engine result…
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)
sequential_exhaustion.qs
analysis "Sequential Exhaustion" {
    overlay     = true
    max_objects = 400
}
/// A nine-bar setup count (close against the close four bars earlier) followed by a thirteen-bar
/// countdown. Counts 7 .. 9 are labelled, a completed setup draws its support or resistance line
/// across the setup and colors the exhaustion bar, and a finished countdown is tagged with a
/// target zone.
show_all = param.bool(false, title = "Label every count")
use_cd   = param.bool(true, title = "Countdown")

up_cond = close > close[4]
dn_cond = close < close[4]
persist buy_n = 0
persist sell_n = 0
buy_n := dn_cond ? buy_n + 1 : 0
sell_n := up_cond ? sell_n + 1 : 0
buy9 = buy_n == 9
sell9 = sell_n == 9
lo9 = ta.lowest(low, 9)
hi9 = ta.highest(high, 9)
a = ta.atr(14)

// labels: the late counts (or all of them)
if buy_n >= (show_all ? 1 : 7) and buy_n <= 9 {
    draw.tag.create(bar.index, low - 0.3 * a, str.of(buy_n), color = buy_n == 9 ? color.emerald.500 : color.translucent(color.emerald.700, 30), shape = shape.tag_up, size = buy_n == 9 ? 1 : 0.7)
}
if sell_n >= (show_all ? 1 : 7) and sell_n <= 9 {
    draw.tag.create(bar.index, high + 0.3 * a, str.of(sell_n), color = sell_n == 9 ? color.rose.500 : color.translucent(color.rose.700, 30), shape = shape.tag_down, size = sell_n == 9 ? 1 : 0.7)
}
// setup lines: the high of a buy setup is resistance, the low of a sell setup is support
if buy9 { draw.segment.create(bar.index - 8, hi9, bar.index + 12, hi9, color = color.rose.400, width = 1.5, stroke = style.dashed) }
if sell9 { draw.segment.create(bar.index - 8, lo9, bar.index + 12, lo9, color = color.emerald.400, width = 1.5, stroke = style.dashed) }

// countdown: after a completed setup, count closes beyond the low / high two bars earlier
persist cd_buy = -1
persist cd_sell = -1
if buy9 { cd_buy := 0 }
if sell9 { cd_sell := 0 }
if cd_buy >= 0 and close <= low[2] { cd_buy := cd_buy + 1 }
if cd_sell >= 0 and close >= high[2] { cd_sell := cd_sell + 1 }
done_buy = use_cd and cd_buy == 13
done_sell = use_cd and cd_sell == 13
if done_buy {
    draw.zone.create(bar.index, low + a, bar.index + 8, low, color = color.translucent(color.emerald.400, 75), text = "13 Buy")
    cd_buy := -1
}
if done_sell {
    draw.zone.create(bar.index, high, bar.index + 8, high - a, color = color.translucent(color.rose.400, 75), text = "13 Sell")
    cd_sell := -1
}
if sell9 { cd_buy := -1 }
if buy9 { cd_sell := -1 }
render.candle_color(buy9 ? color.emerald.300 : sell9 ? color.rose.300 : done_buy ? color.lime.300 : done_sell ? color.orange.300 : na)
render.mark(done_buy, shape = shape.star, at = anchor.below_bar, color = color.lime.300, title = "Countdown buy")
render.mark(done_sell, shape = shape.star, at = anchor.above_bar, color = color.orange.300, title = "Countdown sell")
alert.rule(buy9, "Buy setup", "Nine consecutive closes below the close four bars earlier")
alert.rule(sell9, "Sell setup", "Nine consecutive closes above the close four bars earlier")

Stochastic RSI Heat

analysis · presets/analysis/momentum/stoch_rsi_heat.qs · uses viz/palette

Stochastic RSI %K / %D with a heat-colored fill between them and turn marks only inside the extreme zones.

QuaScriptengine result
Loading the engine result…
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)
stoch_rsi_heat.qs
analysis "Stochastic RSI Heat" {
    overlay = false
}
/// Stochastic RSI %K / %D with a heat-colored fill between them and turn marks only inside the
/// extreme zones.
use "viz/palette" as pal

rsi_len = param.int(14, title = "RSI length", min = 2)
st_len  = param.int(14, title = "Stochastic length", min = 2)
k_s     = param.int(3, title = "%K smoothing", min = 1)
d_s     = param.int(3, title = "%D smoothing", min = 1)

(k, d) = ta.stoch_rsi(close, rsi_len, st_len, k_s, d_s)
pk = render.line(k, "%K", color = pal.osc_heat(k), width = 2)
pd = render.line(d, "%D", color = color.slate.300, width = 1)
// the fill is green while %K leads and red while it lags: one band per side, each collapsing to
// zero width on the other side
p_k_up = render.line(k >= d ? k : d, "Bull edge", color = color.transparent, width = 0)
p_k_dn = render.line(k < d ? k : d, "Bear edge", color = color.transparent, width = 0)
render.band(p_k_up, pd, color = pal.BULL, opacity = 0.18)
render.band(pd, p_k_dn, color = pal.BEAR, opacity = 0.18)
render.level(80, "Upper", color = color.rose.700, stroke = style.dashed)
render.level(20, "Lower", color = color.teal.700, stroke = style.dashed)
render.mark(ta.cross_up(k, d) and d < 20, shape = shape.triangle_up, at = anchor.bottom, color = pal.BULL)
render.mark(ta.cross_down(k, d) and d > 80, shape = shape.triangle_down, at = anchor.top, color = pal.BEAR)

Stochastic

analysis · presets/analysis/momentum/stochastic.qs

%K / %D stochastic oscillator with overbought / oversold zones.

QuaScriptengine result
Loading the engine result…
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)
stochastic.qs
analysis "Stochastic" {
    overlay = false
}
// %K / %D stochastic oscillator with overbought / oversold zones.
len    = param.int(14, title = "%K length", min = 1, max = 100)
smooth = param.int(3, title = "%K smoothing", min = 1, max = 50)
d_len  = param.int(3, title = "%D length", min = 1, max = 50)

(k, d) = ta.stoch(close, high, low, len, smooth, d_len)
render.line(k, "%K", color = color.blue.500, width = 2)
render.line(d, "%D", color = color.amber.500, width = 1)
ob = render.level(80, "Overbought", color = color.rose.500)
render.level(50, "Mid", color = color.slate.500, stroke = style.dotted)
os = render.level(20, "Oversold", color = color.teal.500)
render.band(ob, os, color = color.violet.500, opacity = 0.05)
render.mark(ta.cross_up(k, d) and k < 20, shape = shape.triangle_up, at = anchor.below_bar, color = color.emerald.500, text = "Buy")
render.mark(ta.cross_down(k, d) and k > 80, shape = shape.triangle_down, at = anchor.above_bar, color = color.rose.500, text = "Sell")
alert.rule(ta.cross_up(k, d) and k < 20, "Stochastic bullish turn", "%K crossed above %D in the oversold zone")
alert.rule(ta.cross_down(k, d) and k > 80, "Stochastic bearish turn", "%K crossed below %D in the overbought zone")

True Strength Cloud

analysis · presets/analysis/momentum/tsi_cloud.qs

The true strength index and its signal joined by a gradient cloud, a histogram of their spread shaded by acceleration, KST as a slower confirmation, and marks for zero-line crosses and divergences against price.

QuaScriptengine result
Loading the engine result…
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)
tsi_cloud.qs
analysis "True Strength Cloud" {
    pane   = hpane
    height = 0.24
}
/// The true strength index and its signal joined by a gradient cloud, a histogram of their spread
/// shaded by acceleration, KST as a slower confirmation, and marks for zero-line crosses and
/// divergences against price.
long  = param.int(25, title = "Long", min = 5, max = 100)
short = param.int(13, title = "Short", min = 2, max = 50)
sig   = param.int(13, title = "Signal", min = 2, max = 50)

(t, s) = ta.tsi(close, short, long, sig)
(k, ks) = ta.kst(close)
spread = t - s
p_t = render.line(t, "TSI", color = t >= s ? color.emerald.300 : color.rose.300, width = 2)
p_s = render.line(s, "Signal", color = color.slate.400, width = 1)
render.band(p_t, p_s, texture = "gradient", upper_color = color.emerald.500, lower_color = color.rose.500, opacity = 0.28, title = "TSI cloud")
render.histogram(spread, "Spread", color = spread >= 0 ? (spread > spread[1] ? color.emerald.500 : color.emerald.800) : (spread < spread[1] ? color.rose.500 : color.rose.800))
render.line(k / 4, "KST (scaled)", color = color.translucent(color.amber.300, 30), width = 1, stroke = style.dashed)
render.level(25, "Strong", color = color.slate.700, stroke = style.dotted)
render.level(0, "Zero", color = color.slate.600)
render.level(-25, "Weak", color = color.slate.700, stroke = style.dotted)
(bull, bear) = ta.divergence(t, 5, 5, 60)
render.mark(bull, shape = shape.tag_up, at = anchor.bottom, color = color.emerald.500, text = "Div")
render.mark(bear, shape = shape.tag_down, at = anchor.top, color = color.rose.500, text = "Div")
render.mark(ta.cross_up(t, 0), shape = shape.circle, at = anchor.bottom, color = color.emerald.300, size = 0.5, title = "Zero cross up")
render.mark(ta.cross_down(t, 0), shape = shape.circle, at = anchor.top, color = color.rose.300, size = 0.5, title = "Zero cross down")
alert.rule(ta.cross_up(t, s) and t < -25, "Oversold TSI cross", "TSI crossed its signal below -25")
alert.rule(ta.cross_down(t, s) and t > 25, "Overbought TSI cross", "TSI crossed its signal above 25")

Untitled

analysis · presets/analysis/momentum/williams_r.qs

Williams %R Range Momentum with Boundary Shapes

QuaScriptengine result
Loading the engine result…
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)
williams_r.qs
analysis "Untitled" {
    render  = canvas
    overlay = false
}
// Williams %R Range Momentum with Boundary Shapes
len = param.float(14, "length", min=1, max=100)

val = ta.williams(len)
render.line(val, "Williams %R", color=color.pink.500, width=2)
render.level(-20, "Overbought (-20)", color=color.rose.500)
render.level(-50, "Midpoint (-50)",   color=color.slate.500)
render.level(-80, "Oversold (-80)",   color=color.teal.500)

render.mark(ta.cross_up(val, -80), shape=shape.circle, text="BOUNCE", color=color.emerald.500, at=anchor.below_bar)
render.mark(ta.cross_down(val, -20), shape=shape.circle, text="REJECTION", color=color.rose.500, at=anchor.above_bar)

alert.rule(ta.cross_up(val, -80), "Williams %R Bullish Exit", "Exited oversold region")
alert.rule(ta.cross_down(val, -20), "Williams %R Bearish Exit", "Exited overbought region")

© 2026 Quatick Technologies. All rights reserved.