A divergence screener is one of the least useful lists you can build. Divergences are common, they mean “momentum is no longer confirming price”, and that is all they mean. Handed a column of them you still have to open every chart to find out whether anything came of it.
This screener does not look for divergences. It looks for the breakout that a divergence authorised: an instrument where momentum diverged, a candle traded far more than its recent average, and price has now closed beyond the extreme of that candle while the setup was still valid.
It is the scanning companion to the CCI Divergence Volume Breakout indicator, and it runs that indicator’s engine unabridged. Every instrument the list returns has a level, a break and a badge drawn on its chart.
Three things, in this order, and each one can end the sequence.
A divergence on the CCI, matched on price. Pivots are scanned at four strengths at once (3, 5, 7 and 9 bars on each side), so a sharp turn and a broad one are both eligible. The first pivot has to be beyond the extreme level; the second only has to be less extreme than the first. Then the price side has to agree: within a radius of seven bars around each CCI pivot there must be a real price high or low, the two swings must measure roughly the same length, and there must be no deeper extreme in between.
A candle that traded. Once a divergence is confirmed, the engine opens a finite search for a candle whose volume is at least minVolMult times its average – first between the two CCI pivots, and only failing that in a window around the second one. It takes the highest volume candidate, not the first. The colour of that candle is irrelevant: what matters is the size that changed hands. Its extreme becomes the level – the high for a bullish setup, the low for a bearish one.
A close beyond it, before the level expires. A wick through the level is not a signal. And the level is not permanent: it can fire for maxSetupBars bars from the moment it was created, and then it is done. It is not re-armed by a new session or a new divergence.
That expiry is the reason this list stays short. A level that keeps triggering months after it was drawn is not a level, it is a coincidence generator.
Dir is 1 for a BUY (a bullish setup broke upwards) and -1 for a SELL.
Level is the price that was broken: the extreme of the high volume candle. It is the line the indicator draws, so it is the number to check first when you open the chart.
Stop is the opposite extreme of that same candle – the low of it for a long, the high for a short. It is the raw structural level, without the ATR cushion the indicator adds on the chart, so treat it as the reference rather than as a finished order.
Vol x is how many times its own average the level candle traded. This is the column to sort by. Everything on the list already passed the threshold, so what separates the rows is how far past it they went.
Bars ago is the age of the signal, counted from the last bar on the chart. Read the next section before you use it, because zero and one do not mean the same thing here.
The engine advances once per closed bar. Nothing flickers inside a live candle, and nothing gets counted twice while the market is moving. The price paid is that the map is one bar behind: a breakout that happens on Friday’s close is not processed until the next bar opens.
On a daily chart that is a detail. On a weekly chart it means that if you look at your charts over the weekend, the badge for the week that just closed is not there yet – it appears on Monday.
checkLastBar exists for exactly that. With it on, the live levels are tested against the last bar as well as against the closed one, which is the only way to see a weekly breakout while the market is shut. Bars ago then reads:
0 – the break is on the last bar of the chart, and the engine has not confirmed it yet.1 – the break is on the last closed bar the engine processed. This is the one the chart draws.Two honest limits on that, because a screener you trust wrongly is worse than one you do not use.
First, it does not bring everything forward. When a divergence is confirmed the engine looks backwards for its volume candle, so a level can be born already broken by the close of that same bar and fire in the same pass. One bar earlier that level did not exist, so there was nothing to test. In testing, roughly six signals in ten arrive a bar earlier; the rest show up on the next bar like any other.
Second, Bars ago = 0 is provisional by definition. With the market open, that bar is still moving and the close that produced it can be taken back. Over the weekend, or after the session ends, it is as final as any other close.
Set checkLastBar to 0 if you would rather the list never told you anything the chart is not already showing.
//----------------------------------------------
//PRC_CCI Divergence Volume Breakout - SCREENER
//version = 1
//17.09.2026
//Ivan Gonzalez @ www.prorealcode.com
//Sharing ProRealTime knowledge
//----------------------------------------------
// === 1. CCI AND DIVERGENCE ===
cciLength = 20 // CCI period
extremeLevel = 150 // only the FIRST pivot must be beyond this level
maxDivBars = 60 // maximum bars between the two CCI pivots
pivotRadius = 7 // bars searched on each side of a CCI pivot for the price pivot
spanTolPct = 35 // maximum price/CCI span difference, in percent
// === 2. VOLUME MAP ===
volAvgLength = 20 // volume average period
minVolMult = 1.5 // minimum volume / average ratio
searchBars = 15 // bars after the divergence that may supply a volume candle
maxCandidates = 1 // volume levels created per divergence
maxSetupBars = 40 // bars a level may fire, counted from its creation
extendExpired = 1 // 1 = an unbroken expired level stays as a grey reference
maxLevels = 60 // volume levels tracked at the same time
// === 3. SCREENER ===
dirMode = 0 // 0 = BUY and SELL, 1 = BUY only, 2 = SELL only
lookback = 3 // bars back searched for the most recent signal
checkLastBar = 1 // 1 = also test the live levels against the last bar
// === 5. SERIES ===
cciSrc = (high + low + close) / 3
cciV = cci[cciLength](cciSrc)
volAvg = average[volAvgLength](volume)
atrRaw = averagetruerange[14](close)
cl3 = lowest[3](cciV)
cl5 = lowest[5](cciV)
cl7 = lowest[7](cciV)
cl9 = lowest[9](cciV)
ch3 = highest[3](cciV)
ch5 = highest[5](cciV)
ch7 = highest[7](cciV)
ch9 = highest[9](cciV)
plo3 = lowest[3](low)
phi3 = highest[3](high)
isPLo = 0
if low[3] < plo3 and low[3] <= plo3[4] then
isPLo = 1
endif
isPHi = 0
if high[3] > phi3 and high[3] >= phi3[4] then
isPHi = 1
endif
// first bar on which every window the engine needs is complete
wUp = maxDivBars + 20 + volAvgLength + cciLength
// === 6. SIGNAL SERIES ===
sigBull = 0
sigBullLvl = 0
sigBullStp = 0
sigBullMlt = 0
sigBullOff = 1
sigBear = 0
sigBearLvl = 0
sigBearStp = 0
sigBearMlt = 0
sigBearOff = 1
// === 7. ENGINE, ONE PASS PER CLOSED BAR ===
if barindex = 0 then
for z = 0 to 24 do
$gv[z] = 0
next
$gv[0] = 0 - 1
for z = 0 to 7 do
$rdyF[z] = 0
$rdyB[z] = 0
$rdyV[z] = 0
next
$stg[0] = 3
$stg[1] = 5
$stg[2] = 7
$stg[3] = 9
endif
if barindex > wUp and barindex > $gv[0] then
$gv[0] = barindex
cIdx = barindex - 1
cHigh = high[1]
cLow = low[1]
cClose = close[1]
cAtr = atrRaw[1]
cVol = volume[1]
cAvgV = volAvg[1]
nLv = $gv[1]
nSn = $gv[6]
serial = $gv[7]
actId = $gv[8]
actDir = $gv[9]
setStart = $gv[10]
collEnd = $gv[11]
candCnt = $gv[12]
cMult = 0
if cAvgV > 0 then
cMult = cVol / cAvgV
endif
// --- 6.1 CCI pivots, four strengths ---
$pLoOk[0] = 0
$pHiOk[0] = 0
if cciV[4] < cl3[1] and cciV[4] < cl3[5] then
$pLoOk[0] = 1
endif
if cciV[4] > ch3[1] and cciV[4] > ch3[5] then
$pHiOk[0] = 1
endif
$pLoOk[1] = 0
$pHiOk[1] = 0
if cciV[6] < cl5[1] and cciV[6] < cl5[7] then
$pLoOk[1] = 1
endif
if cciV[6] > ch5[1] and cciV[6] > ch5[7] then
$pHiOk[1] = 1
endif
$pLoOk[2] = 0
$pHiOk[2] = 0
if cciV[8] < cl7[1] and cciV[8] < cl7[9] then
$pLoOk[2] = 1
endif
if cciV[8] > ch7[1] and cciV[8] > ch7[9] then
$pHiOk[2] = 1
endif
$pLoOk[3] = 0
$pHiOk[3] = 0
if cciV[10] < cl9[1] and cciV[10] < cl9[11] then
$pLoOk[3] = 1
endif
if cciV[10] > ch9[1] and cciV[10] > ch9[11] then
$pHiOk[3] = 1
endif
// --- 6.2 a move into the opposite extreme starts a new momentum cycle ---
for z = 0 to 3 do
if $rdyF[z] = 1 and cciV[1] >= extremeLevel then
$rdyF[z] = 0
endif
if $rdyF[z + 4] = 1 and cciV[1] <= 0 - extremeLevel then
$rdyF[z + 4] = 0
endif
next
// --- 6.3 divergence state machine ---
newDiv = 0
newDir = 0
c1Bar = 0
c2Bar = 0
p1Bar = 0
p2Bar = 0
for s = 0 to 3 do
sStr = $stg[s]
pOff = sStr + 1
for d = 0 to 1 do
idx = d * 4 + s
pOk = $pLoOk[s]
if d = 1 then
pOk = $pHiOk[s]
endif
if pOk = 1 then
pVal = cciV[pOff]
b2 = cIdx - sStr
atExt = 0
if d = 0 and pVal <= 0 - extremeLevel then
atExt = 1
endif
if d = 1 and pVal >= extremeLevel then
atExt = 1
endif
if $rdyF[idx] = 0 then
// arm on the first pivot beyond the extreme level
if atExt = 1 then
$rdyF[idx] = 1
$rdyB[idx] = b2
$rdyV[idx] = pVal
endif
else
b1 = $rdyB[idx]
v1 = $rdyV[idx]
dist = b2 - b1
oscOk = 0
if d = 0 and pVal > v1 then
oscOk = 1
endif
if d = 1 and pVal < v1 then
oscOk = 1
endif
psOk = 0
psB1 = 0
psB2 = 0
if dist > 0 and dist <= maxDivBars and oscOk = 1 then
psBest = 1000000
cSpan = max(dist, 1)
o1c = cIdx - b1
o2c = cIdx - b2
for k1 = 0 to 2 * pivotRadius do
d1 = k1 - pivotRadius
o1 = o1c + d1
ok1 = 0
if o1 >= 3 and o1 + 3 <= cIdx then
if d = 0 then
ok1 = isPLo[o1 - 2]
else
ok1 = isPHi[o1 - 2]
endif
endif
if ok1 = 1 then
for k2 = 0 to 2 * pivotRadius do
d2 = k2 - pivotRadius
o2 = o2c + d2
ok2 = 0
if o2 >= 3 and o2 + 3 <= cIdx then
if d = 0 then
ok2 = isPLo[o2 - 2]
else
ok2 = isPHi[o2 - 2]
endif
endif
if ok2 = 1 then
if d = 0 then
val1 = low[o1 + 1]
val2 = low[o2 + 1]
else
val1 = 0 - high[o1 + 1]
val2 = 0 - high[o2 + 1]
endif
pb1 = cIdx - o1
pb2 = cIdx - o2
pSpan = pb2 - pb1
spanDif = abs(pSpan - cSpan) / cSpan * 100
scr = abs(d1) + abs(d2) + spanDif * 0.1
if pb2 > pb1 and val2 < val1 and spanDif <= spanTolPct and scr < psBest then
intOk = 1
if pb2 - pb1 > 1 then
for ib = pb1 + 1 to pb2 - 1 do
ob = cIdx - ib
obOk = 0
obVal = 0
if ob >= 3 and ob + 3 <= cIdx then
if d = 0 then
obOk = isPLo[ob - 2]
obVal = low[ob + 1]
else
obOk = isPHi[ob - 2]
obVal = 0 - high[ob + 1]
endif
endif
if obOk = 1 and obVal < val2 then
intOk = 0
break
endif
next
endif
if intOk = 1 then
psOk = 1
psBest = scr
psB1 = pb1
psB2 = pb2
endif
endif
endif
next
endif
next
endif
if psOk = 1 then
if newDiv = 0 then
dirTest = 1
if d = 1 then
dirTest = 0 - 1
endif
dup = 0
nSnDraw = min(nSn, 100)
if nSnDraw > 0 then
snS = (nSn - nSnDraw) mod 100
for k = 0 to nSnDraw - 1 do
j = (snS + k) mod 100
if $snDir[j] = dirTest and abs($snF[j] - psB1) <= 5 and abs($snL[j] - psB2) <= 5 then
dup = 1
break
endif
next
endif
if dup = 0 then
newDiv = 1
newDir = dirTest
c1Bar = b1
c2Bar = b2
p1Bar = psB1
p2Bar = psB2
endif
endif
$rdyF[idx] = 0
else
reset = 0
if dist > maxDivBars then
reset = 1
endif
if d = 0 and atExt = 1 and pVal <= v1 then
reset = 1
endif
if d = 1 and atExt = 1 and pVal >= v1 then
reset = 1
endif
if reset = 1 then
$rdyF[idx] = atExt
if atExt = 1 then
$rdyB[idx] = b2
$rdyV[idx] = pVal
endif
endif
endif
endif
endif
next
next
// --- 6.4 a confirmed divergence opens a setup ---
if newDiv = 1 then
snSlot = nSn mod 100
$snDir[snSlot] = newDir
$snF[snSlot] = p1Bar
$snL[snSlot] = p2Bar
nSn = nSn + 1
// the levels of the previous setup lose their signal window
if actId > 0 then
nLvD = min(nLv, maxLevels)
if nLvD > 0 then
lvS = (nLv - nLvD) mod maxLevels
for k = 0 to nLvD - 1 do
i = (lvS + k) mod maxLevels
if $lvSid[i] = actId and $lvSt[i] = 0 then
$lvSt[i] = 3
if extendExpired = 1 then
$lvSt[i] = 2
endif
$lvX2[i] = cIdx
endif
next
endif
endif
serial = serial + 1
actId = serial
actDir = newDir
setStart = cIdx
collEnd = cIdx + searchBars
candCnt = 0
// --- automatic search of the first qualifying volume candle ---
vFound = 0
vBar = 0
vHigh = 0
vLow = 0
vMult = 0
vAtr = 0
vBest = 0
for pass = 0 to 1 do
if vFound = 0 then
sStart = c1Bar
sEnd = c2Bar
if pass = 1 then
sStart = c2Bar - 9
sEnd = c2Bar + 9
endif
for o = 0 to maxDivBars + 18 do
aBar = cIdx - o
if aBar >= sStart and aBar <= sEnd then
oAvg = volAvg[o + 1]
oMult = 0
if oAvg > 0 then
oMult = volume[o + 1] / oAvg
endif
if oMult >= minVolMult and (vFound = 0 or volume[o + 1] > vBest) then
vFound = 1
vBar = aBar
vHigh = high[o + 1]
vLow = low[o + 1]
vMult = oMult
vAtr = atrRaw[o + 1]
vBest = volume[o + 1]
endif
endif
next
endif
next
if vFound = 1 then
addOk = 1
nLvD = min(nLv, maxLevels)
if nLvD > 0 then
lvS = (nLv - nLvD) mod maxLevels
for k = 0 to nLvD - 1 do
i = (lvS + k) mod maxLevels
if $lvSid[i] = actId and $lvSrc[i] = vBar then
addOk = 0
break
endif
next
endif
if addOk = 1 then
lvSlot = nLv mod maxLevels
$lvDir[lvSlot] = actDir
$lvPx[lvSlot] = vLow
if actDir = 1 then
$lvPx[lvSlot] = vHigh
endif
$lvCHi[lvSlot] = vHigh
$lvCLo[lvSlot] = vLow
$lvSrc[lvSlot] = vBar
$lvEnd[lvSlot] = cIdx + maxSetupBars
$lvSid[lvSlot] = actId
$lvSt[lvSlot] = 0
$lvX2[lvSlot] = cIdx + 1
$lvMlt[lvSlot] = vMult
$lvAtr[lvSlot] = vAtr
nLv = nLv + 1
candCnt = candCnt + 1
// an older grey reference stops when a new level replaces it
nLvD = min(nLv, maxLevels)
lvS = (nLv - nLvD) mod maxLevels
for k = 0 to nLvD - 1 do
i = (lvS + k) mod maxLevels
if $lvSid[i] <> actId and $lvSt[i] = 2 then
$lvSt[i] = 3
$lvX2[i] = cIdx
endif
next
endif
endif
endif
// --- 6.5 further volume candidates inside the collection window ---
if actId > 0 and cIdx > setStart and cIdx <= collEnd and candCnt < maxCandidates and cMult >= minVolMult then
addOk = 1
nLvD = min(nLv, maxLevels)
if nLvD > 0 then
lvS = (nLv - nLvD) mod maxLevels
for k = 0 to nLvD - 1 do
i = (lvS + k) mod maxLevels
if $lvSid[i] = actId and $lvSrc[i] = cIdx then
addOk = 0
break
endif
next
endif
if addOk = 1 then
lvSlot = nLv mod maxLevels
$lvDir[lvSlot] = actDir
$lvPx[lvSlot] = cLow
if actDir = 1 then
$lvPx[lvSlot] = cHigh
endif
$lvCHi[lvSlot] = cHigh
$lvCLo[lvSlot] = cLow
$lvSrc[lvSlot] = cIdx
$lvEnd[lvSlot] = cIdx + maxSetupBars
$lvSid[lvSlot] = actId
$lvSt[lvSlot] = 0
$lvX2[lvSlot] = cIdx + 1
$lvMlt[lvSlot] = cMult
$lvAtr[lvSlot] = cAtr
nLv = nLv + 1
candCnt = candCnt + 1
nLvD = min(nLv, maxLevels)
lvS = (nLv - nLvD) mod maxLevels
for k = 0 to nLvD - 1 do
i = (lvS + k) mod maxLevels
if $lvSid[i] <> actId and $lvSt[i] = 2 then
$lvSt[i] = 3
$lvX2[i] = cIdx
endif
next
endif
endif
if actId > 0 and cIdx > collEnd then
actDir = 0
endif
// --- 6.6 level lifecycle and breakout authorisation ---
bullSig = 0
bearSig = 0
bullSrc = 0 - 1
bearSrc = 0 - 1
bullStp = 0
bearStp = 0
bullLvl = 0
bearLvl = 0
bullMlt = 0
bearMlt = 0
xBull = 0
xBear = 0
xBullSrc = 0 - 1
xBearSrc = 0 - 1
xBullStp = 0
xBearStp = 0
xBullLvl = 0
xBearLvl = 0
xBullMlt = 0
xBearMlt = 0
nLvD = min(nLv, maxLevels)
if nLvD > 0 then
lvS = (nLv - nLvD) mod maxLevels
for k = 0 to nLvD - 1 do
i = (lvS + k) mod maxLevels
st = $lvSt[i]
if st = 0 and cIdx > $lvEnd[i] then
st = 3
if extendExpired = 1 then
st = 2
endif
$lvSt[i] = st
$lvX2[i] = $lvEnd[i]
endif
if st = 0 or st = 2 then
$lvX2[i] = cIdx + 1
endif
auth = 0
if st = 0 and cIdx <= $lvEnd[i] and cIdx > $lvSrc[i] then
auth = 1
endif
brk = 0
if auth = 1 and $lvDir[i] = 1 and cClose > $lvPx[i] then
brk = 1
endif
if auth = 1 and $lvDir[i] = 0 - 1 and cClose < $lvPx[i] then
brk = 1
endif
if brk = 1 then
$lvSt[i] = 1
$lvX2[i] = cIdx
if $lvDir[i] = 1 then
bullSig = 1
if $lvSrc[i] > bullSrc then
bullSrc = $lvSrc[i]
bullStp = $lvCLo[i]
bullLvl = $lvPx[i]
bullMlt = $lvMlt[i]
endif
else
bearSig = 1
if $lvSrc[i] > bearSrc then
bearSrc = $lvSrc[i]
bearStp = $lvCHi[i]
bearLvl = $lvPx[i]
bearMlt = $lvMlt[i]
endif
endif
endif
if checkLastBar = 1 and $lvSt[i] = 0 and barindex <= $lvEnd[i] and barindex > $lvSrc[i] then
if $lvDir[i] = 1 and close > $lvPx[i] and $lvSrc[i] > xBullSrc then
xBull = 1
xBullSrc = $lvSrc[i]
xBullStp = $lvCLo[i]
xBullLvl = $lvPx[i]
xBullMlt = $lvMlt[i]
endif
if $lvDir[i] = 0 - 1 and close < $lvPx[i] and $lvSrc[i] > xBearSrc then
xBear = 1
xBearSrc = $lvSrc[i]
xBearStp = $lvCHi[i]
xBearLvl = $lvPx[i]
xBearMlt = $lvMlt[i]
endif
endif
next
endif
// --- the signal of this bar, published as a series ---
if bullSig = 1 then
sigBull = 1
sigBullLvl = bullLvl
sigBullStp = bullStp
sigBullMlt = bullMlt
sigBullOff = 1
elsif xBull = 1 then
sigBull = 1
sigBullLvl = xBullLvl
sigBullStp = xBullStp
sigBullMlt = xBullMlt
sigBullOff = 0
endif
if bearSig = 1 then
sigBear = 1
sigBearLvl = bearLvl
sigBearStp = bearStp
sigBearMlt = bearMlt
sigBearOff = 1
elsif xBear = 1 then
sigBear = 1
sigBearLvl = xBearLvl
sigBearStp = xBearStp
sigBearMlt = xBearMlt
sigBearOff = 0
endif
$gv[1] = nLv
$gv[6] = nSn
$gv[7] = serial
$gv[8] = actId
$gv[9] = actDir
$gv[10] = setStart
$gv[11] = collEnd
$gv[12] = candCnt
endif
// === 8. MOST RECENT SIGNAL INSIDE THE LOOKBACK WINDOW ===
sigFound = 0
sigAge = 0
dirOut = 0
lvlOut = 0
stpOut = 0
mltOut = 0
for kk = 0 to lookback do
if sigFound = 0 then
okBull = 0
if sigBull[kk] = 1 and dirMode <> 2 then
okBull = 1
endif
okBear = 0
if sigBear[kk] = 1 and dirMode <> 1 then
okBear = 1
endif
takeBull = 0
if okBull = 1 and okBear = 1 then
if sigBullOff[kk] <= sigBearOff[kk] then
takeBull = 1
endif
elsif okBull = 1 then
takeBull = 1
endif
if okBull = 1 or okBear = 1 then
sigFound = 1
if takeBull = 1 then
sigAge = kk + sigBullOff[kk]
dirOut = 1
lvlOut = sigBullLvl[kk]
stpOut = sigBullStp[kk]
mltOut = round(sigBullMlt[kk] * 100) / 100
else
sigAge = kk + sigBearOff[kk]
dirOut = 0 - 1
lvlOut = sigBearLvl[kk]
stpOut = sigBearStp[kk]
mltOut = round(sigBearMlt[kk] * 100) / 100
endif
endif
endif
next
SCREENER[sigFound = 1 and sigAge <= lookback](dirOut AS "Dir", lvlOut AS "Level", stpOut AS "Stop", mltOut AS "Vol x", sigAge AS "Bars ago")