Bollinger-Keltner Squeeze — Volatility Drop
# Bollinger-Keltner Squeeze — Volatility Drop
When Bollinger Bands tuck inside the Keltner Channels, the market is coiling — realized volatility has dropped below the typical ATR-driven envelope, and a directional move tends to follow once the bands expand back out. This drop plots both envelopes, marks the squeeze on the basis line, and fires the moment the coil releases so you can read the setup without eyeballing the bands.
## What it does
- Plots Bollinger Bands (20, 2.0σ) and Keltner Channels (20-period SMA ± 1.5×ATR) on the same chart with the BB basis as a dashed midline
- Detects **Squeeze ON** = both BB lines are inside the corresponding KC lines (low-vol coil)
- Drops red basis-line dots every bar a squeeze is active and a single yellow "fire" dot the bar the squeeze releases — the actionable trigger
- Tints the chart background red while the squeeze is on so you can see the coil at a glance, even zoomed out
- Built-in alerts on squeeze begin and squeeze fire, plus a status label in the top-left
## Recommended settings
- BB length: **20**
- BB std-dev multiplier: **2.0**
- KC length: **20**
- KC ATR multiplier: **1.5**
- Source: **close**
- Show bands: **on**
- Tint background on squeeze: **on**
- Enable alerts: **on**
## Ideal timeframe
Works on anything from 5m intraday up to the daily, but the cleanest setups show up on **15m–4h** for swing entries and **Daily** for position-level breakouts where a multi-bar coil resolves into a real trend leg.
## How to use
Use the squeeze as a *setup*, not a signal — direction is decided by whatever trend / structure read you trust. When the chart is tinted red and you see a string of squeeze dots, the market is winding up; tighten stops, lift size, and wait. The trade trigger is the **yellow fire dot**, which prints the first bar BB pushes back outside KC. Take the breakout in the direction price exits the squeeze (close above prior BB upper = long bias, close below prior BB lower = short bias), and use the basis line as your trail. If the bands re-contract within a few bars without follow-through, treat it as a failed expansion and stand down — these are common on lower timeframes and are the main thing the basis-line trail saves you from. For more conservative entries, drop the KC multiplier to 1.0 (rarer but higher-quality coils) or raise it to 2.0 (more squeezes, more chop). Pair with a higher-timeframe trend filter to bias direction.
## thinkScript code
```
# ─────────────────────────────────────────────
# │ MarketFragments.com | DNA & Market │
# │ info@marketfragments.com │
# │ www.marketfragments.com │
# ─────────────────────────────────────────────
# Time →
# │
# █ █ █│ █
# █ █ █ │ █ █
# █ █ █ │ █ █ █ ╭─╮
# █ █ █ │ █ █ █ █ ╭─╯ ╰─╮
# █ █ █ │ █ █ █ █ █ █ ╭─╯ ╰─╮
# █ █ █ │ █ █ █ █ █ █ █ █ ╭─╯ ╰─╮
# █ █ █ │ █ █ █ █ █ █ █ █ █ █╭─╯ ╰─╮
# █ █ █ │ █ █ █ █ █ █ █ █ ╰─╮ ╭─╯
# █ █ █ │ █ █ █ █ █ █ ╰─╮ ╭─╯
# █ █ █ │ █ █ █ █ ╰─╮ ╭─╯
# █ █ █ │ █ █ ╰─╮ ╭─╯
# █ █ █ │ █ ╰─────╯
# ──────┴──────────────────────────────────────────────────────────────
# T1 T2 T3 T4 T5 T6
#
# Bollinger-Keltner Squeeze (BKS) — MarketFragments free drop
# Detects volatility compression: Bollinger Bands contained inside Keltner Channels.
# Squeeze ON = coil forming. Squeeze FIRE = first bar BB expands back outside KC (breakout trigger).
# Free for non-commercial use. Attribution appreciated.
declare upper;
input source = close;
input bbLength = 20;
input bbMult = 2.0;
input kcLength = 20;
input kcMult = 1.5;
input showBands = yes;
input tintBackground = yes;
input showAlerts = yes;
# --- Bollinger Bands ---
def bbBasis = Average(source, bbLength);
def bbDev = StDev(source, bbLength);
def bbU = bbBasis + bbMult * bbDev;
def bbL = bbBasis - bbMult * bbDev;
# --- Keltner Channels (SMA + ATR) ---
def kcBasis = Average(source, kcLength);
def kcRange = Average(TrueRange(high, close, low), kcLength);
def kcU = kcBasis + kcMult * kcRange;
def kcL = kcBasis - kcMult * kcRange;
# --- Squeeze state ---
def squeezeOn = bbU < kcU and bbL > kcL;
def squeezeFire = !squeezeOn and squeezeOn[1];
def squeezeBegin = squeezeOn and !squeezeOn[1];
# --- Plots: bands ---
plot BBU = if showBands then bbU else Double.NaN;
plot BBL = if showBands then bbL else Double.NaN;
plot KCU = if showBands then kcU else Double.NaN;
plot KCL = if showBands then kcL else Double.NaN;
plot Basis = bbBasis;
BBU.SetDefaultColor(Color.CYAN);
BBL.SetDefaultColor(Color.CYAN);
KCU.SetDefaultColor(Color.ORANGE);
KCL.SetDefaultColor(Color.ORANGE);
Basis.SetDefaultColor(Color.GRAY);
Basis.SetStyle(Curve.SHORT_DASH);
# --- Squeeze dots on the basis line ---
plot SqzOnDot = if squeezeOn then bbBasis else Double.NaN;
SqzOnDot.SetPaintingStrategy(PaintingStrategy.POINTS);
SqzOnDot.SetDefaultColor(Color.RED);
SqzOnDot.SetLineWeight(2);
SqzOnDot.HideTitle();
plot SqzFireDot = if squeezeFire then bbBasis else Double.NaN;
SqzFireDot.SetPaintingStrategy(PaintingStrategy.POINTS);
SqzFireDot.SetDefaultColor(Color.YELLOW);
SqzFireDot.SetLineWeight(5);
SqzFireDot.HideTitle();
# --- Background tint while squeeze is active ---
AssignBackgroundColor(if tintBackground and squeezeOn then CreateColor(40, 20, 20) else Color.CURRENT);
# --- Status label ---
AddLabel(yes,
"BKS " + (if squeezeOn then "SQUEEZE ON" else if squeezeFire then "FIRE!" else "no sqz"),
if squeezeOn then Color.RED else if squeezeFire then Color.YELLOW else Color.DARK_GRAY);
# --- Alerts ---
Alert(showAlerts and squeezeBegin, "BKS: Squeeze ON", Alert.BAR, Sound.Bell);
Alert(showAlerts and squeezeFire, "BKS: Squeeze FIRED", Alert.BAR, Sound.Ring);
```
Drop your tweaks, screenshots, and questions below — that's how the library gets better.

