A CCI reading of 85 says price is stretched, but not how far it still has to go before the classic +100 signal. That is a price, and it belongs on the price chart.
CCI Master draws it there. The CCI can be solved backwards: knowing the last 19 typical prices, there is exactly one value for the next bar that puts the CCI at +100, and one that puts it at −100. Those prices are the edges of an overbought zone above price and an oversold zone below it, so the distance to each zone is the room price has before the CCI signals. It follows the idea of RSI Master, which does the same with the RSI, and keeps its look.
The CCI. CCI = (typical price − its 20-bar mean) / (0.015 × the mean absolute deviation over the same 20 bars), with typical price = (high + low + close) / 3.
Turned into a price. Tomorrow’s window holds the last 19 values and tomorrow’s. Written in terms of tomorrow’s mean, the equation “CCI = 100” is linear between consecutive window values and always rising, so it has exactly one answer: the indicator checks how many of the 19 values fall below it and solves that piece exactly, with no approximation and no search. The same gives −100, and the midline (CCI 0) is just the mean of the 19 values that stay. The edges are smoothed with a 3-bar EMA.
Typical price or close. The classic CCI uses the typical price, so the edges are the typical price the next bar needs. A bar that closes at its high needs a close a third of its range above the edge; a bar that closes mid-range, about the edge itself. Set the source to the close and the edges are exact closes.
A ceiling. A CCI can never go past length / 0.03 in either direction: about 667 with 20 bars, 167 with 5. A level beyond the ceiling has no edge and its zone is not drawn.
The zones, the candles, the dots and the arrows. The overbought zone glows from its edge upwards and fades out 2.2 ATR above it; the oversold zone does the same downwards. Candles stay a soft grey through the middle of the range, warm toward the overbought colour from CCI 30 to 100 and cool toward the oversold colour from −30 to −100. A dot marks the first close into a zone, an arrow the exit. Both are drawn once the bar has closed.
CCI
Look
//-----------------------------------------------------------------------
//PRC_CCI Master
//version = 0
//02.11.2026
//Iván González @ www.prorealcode.com
//Sharing ProRealTime knowledge
//-----------------------------------------------------------------------
// Apply ON THE PRICE CHART.
//
// The CCI turned into price: the overbought and oversold edges are the
// prices the next bar would have to reach for the CCI to hit those levels,
// worked out exactly from the CCI formula. The overbought zone glows above
// its edge and the oversold zone below its edge; candles warm toward the
// overbought colour and cool toward the oversold colour as the CCI moves.
// Dots mark the first close into a zone, arrows the exits.
//
// The classic CCI is computed on the typical price, (high + low + close)
// / 3, so with that source the edges are the typical price the next bar
// needs: a bar that closes at its high needs a close a third of its range
// above the edge. With the close as source the edges are exact closes.
//
// A CCI has a ceiling: it can never go past length / 0.03 either way
// (about 667 with 20 bars, 167 with 5). A level beyond it has no edge.
//
// Dots and arrows are drawn on the bar where they happened once that bar
// has CLOSED (when the next one opens), so they never appear and vanish
// inside a bar.
//-----------------------------------------------------------------------
// ===================== INPUTS =====================
// --- CCI ---
lenIn = 20 // CCI length (3 to 100)
srcIn = 1 // 1 = typical price (the classic CCI), 2 = close
obIn = 100 // overbought level
osIn = -100 // oversold level
smIn = 3 // smoothing of the zone edges, in bars (1 to 10)
// --- Look ---
themeIn = 1 // 1 = Neon, 2 = Classic, 3 = Aurora, 4 = Ice, 5 = Custom (the two colours below)
cuOsR = 0 // custom oversold colour #00FFA3
cuOsG = 255
cuOsB = 163
cuObR = 255 // custom overbought colour #FF3D71
cuObG = 61
cuObB = 113
bright = 3 // brightness (1 to 5)
depthIn = 2.2 // how far the zone glow reaches past its edge, in ATRs (0.5 to 6)
showCandles = 1 // 1 = CCI candles
showMid = 0 // 1 = midline (CCI 0)
showDots = 1 // 1 = entry dots
showTurns = 1 // 1 = exit arrows
// ===================== COLOURS =====================
IF themeIn = 2 THEN
osR = 0 // #00FF31
osG = 255
osB = 49
obR = 255 // #FF0013
obG = 0
obB = 19
ELSIF themeIn = 3 THEN
osR = 52 // #34D399
osG = 211
osB = 153
obR = 244 // #F472B6
obG = 114
obB = 182
ELSIF themeIn = 4 THEN
osR = 125 // #7DD3FC
osG = 211
osB = 252
obR = 253 // #FDA4AF
obG = 164
obB = 175
ELSIF themeIn = 5 THEN
osR = cuOsR
osG = cuOsG
osB = cuOsB
obR = cuObR
obG = cuObG
obB = cuObB
ELSE
osR = 0 // Neon: #00FFA3 oversold, #FF3D71 overbought
osG = 255
osB = 163
obR = 255
obG = 61
obB = 113
ENDIF
brightN = max(1, min(5, round(bright)))
brightMul = 0.4 + 0.2 * brightN
// Arrows and dots sit straight on the chart: a zone colour too light to read on a white background
// (the mint, the Ice theme) is darkened just enough, to a contrast of 4 to 1 against white; a colour
// that already reads is kept
lumS = 0.2126 * pow(osR / 255, 2.2) + 0.7152 * pow(osG / 255, 2.2) + 0.0722 * pow(osB / 255, 2.2)
kS = 1
IF lumS > 0.2125 THEN
kS = pow(0.2125 / lumS, 1 / 2.2)
ENDIF
osTR = round(osR * kS)
osTG = round(osG * kS)
osTB = round(osB * kS)
lumO = 0.2126 * pow(obR / 255, 2.2) + 0.7152 * pow(obG / 255, 2.2) + 0.0722 * pow(obB / 255, 2.2)
kO = 1
IF lumO > 0.2125 THEN
kO = pow(0.2125 / lumO, 1 / 2.2)
ENDIF
obTR = round(obR * kO)
obTG = round(obG * kO)
obTB = round(obB * kO)
// ===================== CCI, AND THE PRICES BEHIND IT =====================
lnC = max(3, min(100, round(lenIn)))
smN = max(1, min(10, round(smIn)))
// ATR(14), the bar range until it exists, never below one tick
atr14 = averagetruerange[14](close)
IF barindex >= 13 THEN
atrV = atr14
ELSE
atrV = high - low
ENDIF
atrV = max(atrV, ticksize)
IF srcIn = 2 THEN
srcV = close
ELSE
srcV = typicalprice
ENDIF
// Every bar's source value, by bar number: the loops below read the window from here
$srcA[barindex] = srcV
// CCI: distance from the mean over 0.015 mean absolute deviations; a window with no deviation reads 0
cciOk = 0
cciV = 0
IF barindex >= lnC - 1 THEN
cciOk = 1
meanC = summation[lnC](srcV) / lnC
devC = 0
FOR i = 0 TO lnC - 1 DO
devC = devC + abs($srcA[barindex - i] - meanC)
NEXT
devC = devC / lnC
IF devC > 0 THEN
cciV = (srcV - meanC) / (0.015 * devC)
ENDIF
ENDIF
// The source value next bar that would put the CCI exactly at a level. Tomorrow's window is the last
// lnC - 1 values plus tomorrow's; written in terms of tomorrow's mean m, the CCI equation is linear
// between consecutive window values and always rising, so it is enough to count how many of them
// fall below the answer and solve that piece exactly. The midline (CCI 0) is the mean of the
// lnC - 1 values that stay
obRaw = close
osRaw = close
midRaw = close
obRch = 0
osRch = 0
IF cciOk = 1 THEN
nC = lnC
sumP = summation[lnC - 1](srcV)
midRaw = sumP / (nC - 1)
kOB = 0.015 * obIn
kOS = 0.015 * osIn
IF abs(kOB) < nC / 2 THEN
obRch = 1
ENDIF
IF abs(kOS) < nC / 2 THEN
osRch = 1
ENDIF
aOB = nC - abs(kOB)
aOS = nC - abs(kOS)
cbOB = 0
sbOB = 0
cbOS = 0
sbOS = 0
FOR j = 0 TO lnC - 2 DO
pj = $srcA[barindex - j]
// total absolute deviation of the window values around pj
dj = 0
FOR i = 0 TO lnC - 2 DO
dj = dj + abs($srcA[barindex - i] - pj)
NEXT
gj = ((nC - 1) * pj - sumP) * aOB - kOB * dj
IF gj <= 0 THEN
cbOB = cbOB + 1
sbOB = sbOB + pj
ENDIF
gj = ((nC - 1) * pj - sumP) * aOS - kOS * dj
IF gj <= 0 THEN
cbOS = cbOS + 1
sbOS = sbOS + pj
ENDIF
NEXT
IF obRch = 1 THEN
mOB = (sumP * aOB + kOB * ((sumP - sbOB) - sbOB)) / ((nC - 1) * aOB - kOB * (cbOB - (nC - 1 - cbOB)))
obRaw = nC * mOB - sumP
ENDIF
IF osRch = 1 THEN
mOS = (sumP * aOS + kOS * ((sumP - sbOS) - sbOS)) / ((nC - 1) * aOS - kOS * (cbOS - (nC - 1 - cbOS)))
osRaw = nC * mOS - sumP
ENDIF
ENDIF
// EMA of each edge, seeded with the simple mean of its first smN values
lvlStart = lnC + smN - 2
aSm = 2 / (smN + 1)
lvlOk = 0
IF barindex = lvlStart THEN
obE = summation[smN](obRaw) / smN
osE = summation[smN](osRaw) / smN
midE = summation[smN](midRaw) / smN
ELSIF barindex > lvlStart THEN
obE = obE + aSm * (obRaw - obE)
osE = osE + aSm * (osRaw - osE)
midE = midE + aSm * (midRaw - midE)
ENDIF
IF barindex >= lvlStart THEN
lvlOk = 1
ELSE
obE = close
osE = close
midE = close
ENDIF
inOB = 0
inOS = 0
IF cciOk = 1 THEN
IF cciV >= obIn THEN
inOB = 1
ENDIF
IF cciV <= osIn THEN
inOS = 1
ENDIF
ENDIF
enterOB = 0
exitOB = 0
enterOS = 0
exitOS = 0
IF barindex > lnC - 1 THEN
IF inOB = 1 AND inOB[1] = 0 THEN
enterOB = 1
ENDIF
IF inOB = 0 AND inOB[1] = 1 THEN
exitOB = 1
ENDIF
IF inOS = 1 AND inOS[1] = 0 THEN
enterOS = 1
ENDIF
IF inOS = 0 AND inOS[1] = 1 THEN
exitOS = 1
ENDIF
ENDIF
// ===================== DRAWING =====================
// --- The zones: full at the edge, fading away from price, in bands of constant opacity ---
obTop = obE + depthIn * atrV
osBot = osE - depthIn * atrV
zA = 0
IF lvlOk = 1 THEN
zA = round(255 * min(max(0.26 * brightMul, 0), 1))
ENDIF
zAo = zA * obRch
zAs = zA * osRch
zStep = depthIn * atrV / 8
COLORBETWEEN(obE, obE + zStep, obR, obG, obB, round(zAo * 15 / 16))
COLORBETWEEN(obE + zStep, obE + 2 * zStep, obR, obG, obB, round(zAo * 13 / 16))
COLORBETWEEN(obE + 2 * zStep, obE + 3 * zStep, obR, obG, obB, round(zAo * 11 / 16))
COLORBETWEEN(obE + 3 * zStep, obE + 4 * zStep, obR, obG, obB, round(zAo * 9 / 16))
COLORBETWEEN(obE + 4 * zStep, obE + 5 * zStep, obR, obG, obB, round(zAo * 7 / 16))
COLORBETWEEN(obE + 5 * zStep, obE + 6 * zStep, obR, obG, obB, round(zAo * 5 / 16))
COLORBETWEEN(obE + 6 * zStep, obE + 7 * zStep, obR, obG, obB, round(zAo * 3 / 16))
COLORBETWEEN(obE + 7 * zStep, obTop, obR, obG, obB, round(zAo * 1 / 16))
COLORBETWEEN(osE, osE - zStep, osR, osG, osB, round(zAs * 15 / 16))
COLORBETWEEN(osE - zStep, osE - 2 * zStep, osR, osG, osB, round(zAs * 13 / 16))
COLORBETWEEN(osE - 2 * zStep, osE - 3 * zStep, osR, osG, osB, round(zAs * 11 / 16))
COLORBETWEEN(osE - 3 * zStep, osE - 4 * zStep, osR, osG, osB, round(zAs * 9 / 16))
COLORBETWEEN(osE - 4 * zStep, osE - 5 * zStep, osR, osG, osB, round(zAs * 7 / 16))
COLORBETWEEN(osE - 5 * zStep, osE - 6 * zStep, osR, osG, osB, round(zAs * 5 / 16))
COLORBETWEEN(osE - 6 * zStep, osE - 7 * zStep, osR, osG, osB, round(zAs * 3 / 16))
COLORBETWEEN(osE - 7 * zStep, osBot, osR, osG, osB, round(zAs * 1 / 16))
// --- The candles: a soft neutral through the middle of the range, warming to the overbought colour
// over the last stretch before the zone and cooling to the oversold colour on the way down, with up
// candles a shade brighter than down candles ---
IF showCandles = 1 AND cciOk = 1 THEN
IF close >= open THEN
nuR = 163 // #A3ABBD
nuG = 171
nuB = 189
ELSE
nuR = 95 // #5F6677
nuG = 102
nuB = 119
ENDIF
warmFrom = obIn * 0.3
coolFrom = osIn * 0.3
IF cciV >= 0 THEN
tW = pow(min(max((cciV - warmFrom) / (obIn - warmFrom), 0), 1), 1.4)
cdR = nuR + (obR - nuR) * tW
cdG = nuG + (obG - nuG) * tW
cdB = nuB + (obB - nuB) * tW
ELSE
tC = pow(min(max((coolFrom - cciV) / (coolFrom - osIn), 0), 1), 1.4)
cdR = nuR + (osR - nuR) * tC
cdG = nuG + (osG - nuG) * tC
cdB = nuB + (osB - nuB) * tC
ENDIF
DRAWCANDLE(open, high, low, close) COLOURED(cdR, cdG, cdB) BORDERCOLOR(cdR, cdG, cdB)
ENDIF
// --- Exit arrows: when price flickers in and out of a zone over a few bars, only the first exit gets
// an arrow ---
ONCE lastArOB = 0 - 10
ONCE lastArOS = 0 - 10
arrowOB = 0
arrowOS = 0
IF exitOB = 1 AND barindex - lastArOB > 3 THEN
arrowOB = 1
lastArOB = barindex
ENDIF
IF exitOS = 1 AND barindex - lastArOS > 3 THEN
arrowOS = 1
lastArOS = barindex
ENDIF
// --- Events of the bar that has just CLOSED (offset 1), drawn on that bar ---
IF barindex > lvlStart THEN
xE = barindex - 1
// The first close into a zone: a soft halo and a solid dot
IF showDots = 1 AND enterOB[1] = 1 THEN
DRAWPOINT(xE, high[1], 4) COLOURED(obR, obG, obB, round(255 * min(0.18 * brightMul, 1)))
DRAWPOINT(xE, high[1], 2) COLOURED(obTR, obTG, obTB, 255)
ENDIF
IF showDots = 1 AND enterOS[1] = 1 THEN
DRAWPOINT(xE, low[1], 4) COLOURED(osR, osG, osB, round(255 * min(0.18 * brightMul, 1)))
DRAWPOINT(xE, low[1], 2) COLOURED(osTR, osTG, osTB, 255)
ENDIF
// Where price leaves a zone: down out of overbought, up out of oversold
IF showTurns = 1 AND arrowOB[1] = 1 THEN
DRAWTEXT("▼", xE, high[1] + 0.5 * atrV[1], dialog, bold, 12) COLOURED(obTR, obTG, obTB, 255)
ENDIF
IF showTurns = 1 AND arrowOS[1] = 1 THEN
DRAWTEXT("▲", xE, low[1] - 0.5 * atrV[1], dialog, bold, 12) COLOURED(osTR, osTG, osTB, 255)
ENDIF
ENDIF
// --- Returned series: the edges with a soft halo, and the midline. Before the CCI exists they sit on
// the close with alpha 0, so they do not squash the price scale; a level past the CCI's ceiling has
// no edge and stays hidden ---
edA = 0
glA = 0
IF lvlOk = 1 THEN
edA = 255
glA = round(255 * min(max(0.16 * brightMul, 0), 1))
ENDIF
mdA = 0
IF showMid = 1 AND lvlOk = 1 THEN
mdA = 115
ENDIF
edAo = edA * obRch
glAo = glA * obRch
edAs = edA * osRch
glAs = glA * osRch
RETURN obE COLOURED(obR, obG, obB, glAo) STYLE(line, 5) AS "Overbought glow", obE COLOURED(obR, obG, obB, edAo) STYLE(line, 2) AS "Overbought", osE COLOURED(osR, osG, osB, glAs) STYLE(line, 5) AS "Oversold glow", osE COLOURED(osR, osG, osB, edAs) STYLE(line, 2) AS "Oversold", midE COLOURED(123, 156, 255, mdA) STYLE(point, 1) AS "Midline"