Indicator · Pine Script v5 · Own pane

CEP v2 Compression/Expansion Score

by AssetWhisper

An indicator in a separate pane that scores market compression from 0 to 100 by combining percentile ranks of ATR, Bollinger Band width and Donchian width with narrow-range bar patterns and quiet volume, each with an adjustable weight.

View on GitHub

GitHub stars1on the repository
Pine versionv5runs as is
Lines of code114
Settings12inputs
LicenceMPL-2.0
Last update7 weeks ago10 Sep 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

114 lines of Pine Script v5 from indicators/CEP_Compression_Expansion_Score.pine, published under MPL-2.0. Keep the author's credit and licence lines when you reuse it.

CEP_Compression_Expansion_Score.pine
//@version=5
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © AssetWhisper — https://assetwhisper.com/indicators/
// ============================================================================
//  CEP v2 — Compression → Expansion Score
//  Idea: las rupturas limpias nacen de compresiones limpias. Se puntúa 0-100 la
//  compresión combinando percentiles de ATR, anchura de Bollinger y Donchian,
//  patrones NR4/NR7 y sequedad de volumen; se valida la ruptura (Donchian con
//  rango y volumen expandidos) y se etiqueta como "limpia" si viene de una
//  compresión alta. Añade sesgo direccional previo y rango de expansión proyectado.
// ============================================================================
indicator("CEP v2 — Compression/Expansion Score", shorttitle="CEP v2", overlay=false)

rankLen  = input.int(100, "Percentile lookback", minval=30, maxval=500)
dcLen    = input.int(20, "Donchian length (breakout)", minval=10, maxval=60)
bbLen    = input.int(20, "Bollinger length", minval=10, maxval=60)
volLen   = input.int(20, "Volume average length", minval=10, maxval=60)
cleanTh  = input.int(55, "Clean breakout: prior compression >=", minval=30, maxval=90)
volBreak = input.float(1.3, "Breakout volume mult", minval=1.0, maxval=3.0, step=0.1)
grpW = "Pesos"
wAtr = input.float(30, "ATR rank", minval=0, maxval=100, group=grpW)
wBbw = input.float(25, "BB width rank", minval=0, maxval=100, group=grpW)
wNr  = input.float(20, "NR4 / NR7", minval=0, maxval=100, group=grpW)
wVol = input.float(15, "Dry volume", minval=0, maxval=100, group=grpW)
wDc  = input.float(10, "Donchian width rank", minval=0, maxval=100, group=grpW)
showComp = input.bool(false, "Show components", group="Visual")

// === COMPONENTES DE COMPRESIÓN (0-100, 100 = máxima compresión) ===
trueRange = ta.tr(true)
atr = ta.atr(14)
atrRank = ta.percentrank(atr, rankLen)
[bbB, bbU, bbL] = ta.bb(close, bbLen, 2)
bbw = bbB > 0 ? (bbU - bbL) / bbB : 0.0
bbwRank = ta.percentrank(bbw, rankLen)
dcH = ta.highest(high, dcLen)
dcL = ta.lowest(low, dcLen)
dcw = close > 0 ? (dcH - dcL) / close : 0.0
dcwRank = ta.percentrank(dcw, rankLen)
nr7 = trueRange <= ta.lowest(trueRange, 7)
nr4 = trueRange <= ta.lowest(trueRange, 4)
volMA = ta.sma(volume, volLen)
volRatio = volMA > 0 ? volume / volMA : 1.0

cAtr = 100 - atrRank
cBbw = 100 - bbwRank
cNr  = nr7 ? 100.0 : nr4 ? 60.0 : 0.0
cVol = math.max(0.0, math.min(1.0, (1 - volRatio) / 0.5)) * 100
cDc  = 100 - dcwRank
wSum = wAtr + wBbw + wNr + wVol + wDc
compRaw = wSum > 0 ? (wAtr * cAtr + wBbw * cBbw + wNr * cNr + wVol * cVol + wDc * cDc) / wSum : 0.0
comp = ta.ema(compRaw, 3)
compPrior = ta.highest(comp[1], 5)              // compresión máxima en las 5 barras previas

stage = comp >= 75 ? "EXTREME" : comp >= 55 ? "HIGH" : comp >= 35 ? "MODERATE" : "EXPANSION"
stageColor = comp >= 75 ? color.purple : comp >= 55 ? color.blue : comp >= 35 ? color.orange : color.red

// === RUPTURA ===
breakUp = close > dcH[1] and trueRange > atr and volRatio >= volBreak
breakDn = close < dcL[1] and trueRange > atr and volRatio >= volBreak
cleanUp = breakUp and compPrior >= cleanTh
cleanDn = breakDn and compPrior >= cleanTh

// Sesgo previo a la ruptura: posición en el canal + pendiente de la EMA20
posInRange = dcH != dcL ? (close - dcL) / (dcH - dcL) : 0.5
ema20 = ta.ema(close, 20)
emaSlope = ema20 - ema20[5]
bias = posInRange > 0.6 and emaSlope > 0 ? 1 : posInRange < 0.4 and emaSlope < 0 ? -1 : 0
biasLabel = bias == 1 ? "UP" : bias == -1 ? "DOWN" : "NEUTRAL"

// Rango de expansión proyectado (2 × ATR de 50 barras); se muestra en la tabla
avgExpansion = ta.sma(atr, 50) * 2

// === VISUAL ===
hline(75, "Extreme", color=color.new(color.purple, 60), linestyle=hline.style_dashed)
hline(55, "High", color=color.new(color.blue, 60), linestyle=hline.style_dashed)
hline(35, "Moderate", color=color.new(color.orange, 70), linestyle=hline.style_dotted)
plot(comp, "Compression score", color=stageColor, linewidth=3)
plot(showComp ? cAtr : na, "ATR comp", color=color.new(color.red, 70), linewidth=1)
plot(showComp ? cBbw : na, "BBW comp", color=color.new(color.blue, 70), linewidth=1)
plot(showComp ? cVol : na, "Dry vol comp", color=color.new(color.teal, 70), linewidth=1)
plot(showComp ? cDc : na, "Donchian comp", color=color.new(color.orange, 70), linewidth=1)
plot(nr7 ? 5 : nr4 ? 3 : 0, "NR marker", style=plot.style_columns, color=color.new(color.purple, 50))
bgcolor(comp >= 75 ? color.new(color.purple, 90) : comp >= 55 ? color.new(color.blue, 93) : na, title="Compression zones")
plotshape(cleanUp, "Clean breakout up", style=shape.triangleup, location=location.bottom, color=color.lime, size=size.normal, text="CLEAN")
plotshape(cleanDn, "Clean breakout down", style=shape.triangledown, location=location.top, color=color.red, size=size.normal, text="CLEAN")
plotshape(breakUp and not cleanUp, "Breakout up", style=shape.circle, location=location.bottom, color=color.new(color.green, 40), size=size.tiny)
plotshape(breakDn and not cleanDn, "Breakout down", style=shape.circle, location=location.top, color=color.new(color.red, 40), size=size.tiny)
plot(comp, "CEP score (export)", display=display.none)
plot(cleanUp ? 2 : breakUp ? 1 : cleanDn ? -2 : breakDn ? -1 : 0, "CEP breakout (export: ±2 clean, ±1 normal)", display=display.none)

var table t = table.new(position.top_right, 2, 8, bgcolor=color.new(color.black, 60))
table.cell(t, 0, 0, "STAGE", text_color=color.white, text_size=size.small)
table.cell(t, 1, 0, stage + " (" + str.tostring(comp, "#") + ")", bgcolor=color.new(stageColor, 30), text_color=color.white, text_size=size.small)
table.cell(t, 0, 1, "ATR rank", text_color=color.white, text_size=size.small)
table.cell(t, 1, 1, str.tostring(atrRank, "#") + "%", text_color=atrRank < 25 ? color.aqua : color.gray, text_size=size.small)
table.cell(t, 0, 2, "BB width rank", text_color=color.white, text_size=size.small)
table.cell(t, 1, 2, str.tostring(bbwRank, "#") + "%", text_color=bbwRank < 25 ? color.aqua : color.gray, text_size=size.small)
table.cell(t, 0, 3, "Narrow range", text_color=color.white, text_size=size.small)
table.cell(t, 1, 3, nr7 ? "NR7" : nr4 ? "NR4" : "-", text_color=nr7 or nr4 ? color.purple : color.gray, text_size=size.small)
table.cell(t, 0, 4, "Volume", text_color=color.white, text_size=size.small)
table.cell(t, 1, 4, str.tostring(volRatio, "#.##") + "x avg", text_color=volRatio < 0.7 ? color.aqua : volRatio >= volBreak ? color.orange : color.gray, text_size=size.small)
table.cell(t, 0, 5, "Pre-break bias", text_color=color.white, text_size=size.small)
table.cell(t, 1, 5, biasLabel, text_color=bias == 1 ? color.lime : bias == -1 ? color.red : color.gray, text_size=size.small)
table.cell(t, 0, 6, "Expansion target", text_color=color.white, text_size=size.small)
table.cell(t, 1, 6, "±" + str.tostring(avgExpansion, format.mintick) + " (" + str.tostring(avgExpansion / close * 100, "#.#") + "%)", text_color=color.white, text_size=size.small)
table.cell(t, 0, 7, "Donchian " + str.tostring(dcLen), text_color=color.white, text_size=size.small)
table.cell(t, 1, 7, str.tostring(dcL, format.mintick) + " - " + str.tostring(dcH, format.mintick), text_color=color.gray, text_size=size.small)

// === ALERTAS ===
alertcondition(cleanUp, "CEP Clean breakout up", "CEP v2: clean upward breakout after compression")
alertcondition(cleanDn, "CEP Clean breakout down", "CEP v2: clean downward breakout after compression")
alertcondition(comp >= 75 and comp[1] < 75, "CEP Extreme compression", "CEP v2: extreme compression - prepare for expansion")
alertcondition(comp >= 55 and comp[1] < 55, "CEP High compression", "CEP v2: high compression reached")
alertcondition(nr7, "CEP NR7", "CEP v2: NR7 bar")

Overview

CEP v2 Compression/Expansion Score then checks whether a Donchian breakout came with expanded range and volume, labelling it clean when prior compression was high enough. Percentile, channel and volume lengths are inputs, and the components can be shown.

Details

Published by

AssetWhisper on GitHub

Draws

Separate pane below the chart

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
Percentile lookbackint100rankLen
Donchian length (breakout)int20dcLen
Bollinger lengthint20bbLen
Volume average lengthint20volLen
Clean breakout: prior compression >=int55cleanTh
Breakout volume multfloat1.3volBreak
ATR rankfloat30wAtr
BB width rankfloat25wBbw
NR4 / NR7float20wNr
Dry volumefloat15wVol
Donchian width rankfloat10wDc
Show componentsboolfalseshowComp
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

Chop and Explode

by capissimo
114 stars

This indicator measures market consolidation and breakout phases by computing a scaled Relative Strength Index (RSI) on normalized price data. It identifies chop zones when the RSI oscillates between 40 and 60, highlighting tight consolidation between 45 and 5

Indicator v5 Oscillators & momentumVolatility & bands
Updated 1 year ago · None stated GitHub repository of Chop and Explode

Squeeze Momentum

by mihakralj
99 stars

This indicator plots Squeeze Momentum in a separate pane. It compares Bollinger Bands with a Keltner Channel to mark a squeeze when the bands sit inside the channel. Momentum is a linear regression of price relative to a Donchian midline and an SMA, shown as a

Indicator v6 Volatility & bandsOscillators & momentum
Updated 4 weeks ago · Apache-2.0 GitHub repository of Squeeze Momentum

ATR Breakout

by TamaraKGreen
46 stars

This indicator plots the 100-bar percent rank of ATR in a separate pane and draws a midline at 50. It colors that midline green, red, or gray by comparing the close with the open a set number of bars earlier against a multiple of ATR across a lookback window.

Indicator v5 Volatility & bandsBreakout
Updated 7 weeks ago · None stated GitHub repository of ATR Breakout

Renko Acceleration

by f-cksociety
11 stars

Calculates the time elapsed between consecutive bars to measure market speed and volatility, designed primarily for Renko chart blocks where bar duration varies. In Momentum mode, it contrasts the current duration against a 100-period moving average baseline,

Indicator v4 Volatility & bandsOscillators & momentum
Updated 4 years ago · MPL-2.0 GitHub repository of Renko Acceleration

CRT + Turtle Soup Scanner

by alboogycOdR
6 stars

Calculates CRT session range boundaries and detects Turtle Soup setups to identify potential breakout signals. The script tracks session high, low, and midpoint values to determine intraday bias while measuring price break distances against ATR thresholds. It

Indicator v5 SMC / ICT / price actionSessions & time
Updated 7 months ago · None stated GitHub repository of CRT + Turtle Soup Scanner

EMA Breakout

by jerryinyang
5 stars

This indicator calculates and plots the exponential moving average on the chart, along with Keltner Channel bands. It applies optional filters based on Keltner Channel and Money Flow to identify breakout signals. Breakouts occur when price crosses the EMA with

Indicator v4 TrendMoving averages
Updated 2 years ago · None stated GitHub repository of EMA Breakout

More Volatility & bands scripts

Related on ForexR

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers