
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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.
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.
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.
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
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.
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.
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.
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.
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
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")