What it does
Volatility Squeeze Momentum highlights periods when volatility contracts unusually tightly, which often come before a larger move, and shows the direction and strength of momentum when that contraction ends.
A row of dots on the zero line shows the squeeze state, and a histogram shows momentum. The idea is simple: Bollinger Bands, which react to standard deviation, usually sit outside Keltner Channels, which react to average range. When the Bollinger Bands shrink inside the Keltner Channels, the market is in a squeeze.
How it works
- Bollinger Bands: SMA(close, BB length) ± BB deviations × standard deviation.
- Keltner Channels: SMA(close, KC length) ± KC multiplier × SMA(true range, KC length).
- Squeeze on (orange dot): both Bollinger Bands are inside the Keltner Channels.
- Squeeze off (grey dot): both Bollinger Bands are outside the Keltner Channels.
- Neither (blue dot): one band inside, one outside.
Momentum: the script takes the midpoint of the highest high and lowest low over the momentum length, averages it with the SMA of close, and measures close minus that reference. A linear regression over the same length smooths this into the histogram value. Bright green means positive and rising, pale green positive and falling, bright red negative and falling, pale red negative and rising.
A release is the first closed bar after a squeeze ends. It is labeled bullish if momentum is above zero and bearish if below.
How to use it
- A run of orange dots means volatility is compressed. It says nothing yet about direction; wait for the release.
- At release, the sign and slope of the histogram suggest the direction of the expansion. A bright bar in the release direction is a stronger reading than a pale one.
- Pale bars after a strong run often show momentum fading, which some traders use to manage or scale out of a position.
- Check the higher-timeframe trend and nearby levels before acting. The Bollinger Bands lesson explains the volatility side.
Settings
| Input | Default | What it changes |
|---|---|---|
| BB length / deviations | 20 / 2.0 | Bollinger Band sensitivity. |
| KC length / range multiplier | 20 / 1.5 | Keltner Channel width. Higher multiplier = more squeezes. |
| Use true range for KC | On | True range or simple high-low range. |
| Momentum length | 20 | Smoothness of the momentum histogram. |
| Histogram and dot colors | various | Four momentum shades and three squeeze states. |
Limitations
- A squeeze shows that volatility is low, not that a breakout is coming soon. Some squeezes last a long time, and some release with a small move.
- The release direction can reverse quickly; false breakouts are common, especially around news.
- Momentum is built from moving averages and regression, so it lags turning points.
- Squeeze frequency depends heavily on the KC multiplier and the market, so the same settings will behave differently on gold and on a quiet major pair.
Pine Script source
In TradingView open Pine Editor, paste the code, click Save and then Add to chart.
FXP_VolatilitySqueezeMomentum.pine
71 lines
// © FXPIPS ONLINE — https://fxpips.online/tradingview-indicators/volatility-squeeze-momentum/
// License: MIT. Educational tool — not financial advice. Trading involves substantial risk.
//@version=6
// Volatility squeeze: Bollinger Bands contracting inside Keltner Channels, shown as dots on the
// zero line, plus a linear-regression momentum histogram. Squeeze releases confirm on bar close.
indicator("FXP Volatility Squeeze Momentum", shorttitle = "FXP Squeeze", overlay = false)
// ── Inputs ─────────────────────────────────────────────────────────────
grpB = "Bollinger Bands"
bbLen = input.int(20, "BB length", minval = 2, group = grpB)
bbMult = input.float(2.0, "BB standard deviations", minval = 0.1, step = 0.1, group = grpB)
grpK = "Keltner Channels"
kcLen = input.int(20, "KC length", minval = 2, group = grpK)
kcMult = input.float(1.5, "KC range multiplier", minval = 0.1, step = 0.1, group = grpK)
useTr = input.bool(true, "Use true range for KC", group = grpK, tooltip = "Off = simple high - low range.")
grpM = "Momentum"
momLen = input.int(20, "Momentum length", minval = 2, group = grpM)
grpV = "Display"
colUpStr = input.color(#26a69a, "Momentum up, rising", group = grpV)
colUpWeak = input.color(#a5d6a7, "Momentum up, falling", group = grpV)
colDnStr = input.color(#ef5350, "Momentum down, falling", group = grpV)
colDnWeak = input.color(#ef9a9a, "Momentum down, rising", group = grpV)
colSqzOn = input.color(#ff9800, "Squeeze on dot", group = grpV)
colSqzOff = input.color(#9e9e9e, "Squeeze off dot", group = grpV)
colNoSqz = input.color(#2962ff, "No squeeze dot", group = grpV)
// ── Bands ──────────────────────────────────────────────────────────────
bbBasis = ta.sma(close, bbLen)
bbDev = bbMult * ta.stdev(close, bbLen)
bbUpper = bbBasis + bbDev
bbLower = bbBasis - bbDev
kcBasis = ta.sma(close, kcLen)
trueRange = ta.tr(true)
rangeSrc = useTr ? trueRange : high - low
kcRange = ta.sma(rangeSrc, kcLen)
kcUpper = kcBasis + kcMult * kcRange
kcLower = kcBasis - kcMult * kcRange
sqzOn = bbLower > kcLower and bbUpper < kcUpper
sqzOff = bbLower < kcLower and bbUpper > kcUpper
// ── Momentum: linear regression of price versus its range/average midpoint ──
rangeMid = math.avg(ta.highest(high, momLen), ta.lowest(low, momLen))
midRef = math.avg(rangeMid, ta.sma(close, momLen))
mom = ta.linreg(close - midRef, momLen, 0)
momUp = mom > nz(mom[1])
// ── Signals ────────────────────────────────────────────────────────────
sqzStart = sqzOn and not sqzOn[1] and barstate.isconfirmed
released = sqzOn[1] and not sqzOn and barstate.isconfirmed
releaseBull = released and mom > 0
releaseBear = released and mom < 0
// ── Plots ──────────────────────────────────────────────────────────────
histColor = mom >= 0 ? (momUp ? colUpStr : colUpWeak) : (momUp ? colDnWeak : colDnStr)
dotColor = sqzOn ? colSqzOn : sqzOff ? colSqzOff : colNoSqz
plot(mom, "Momentum", color = histColor, style = plot.style_columns)
plot(0, "Squeeze dots", color = dotColor, style = plot.style_circles, linewidth = 3)
plotshape(releaseBull, "Squeeze release, bullish", style = shape.triangleup, location = location.bottom, color = colUpStr, size = size.small)
plotshape(releaseBear, "Squeeze release, bearish", style = shape.triangledown, location = location.top, color = colDnStr, size = size.small)
// ── Alerts ─────────────────────────────────────────────────────────────
alertcondition(sqzStart, "Squeeze started", "Volatility squeeze started on {{ticker}} ({{interval}})")
alertcondition(releaseBull, "Squeeze released bullish", "Volatility squeeze released with positive momentum on {{ticker}} ({{interval}})")
alertcondition(releaseBear, "Squeeze released bearish", "Volatility squeeze released with negative momentum on {{ticker}} ({{interval}})")

