A breakout of a swing level is only half of the story: the other half is who pushed the price through it. Breakout Volume Delta draws the most recent swing highs and lows as levels, detects when they are broken and, on every breakout candle, splits the body into its buying and selling volume, with a label that gives both figures and their share.
An optional volume filter keeps only the breakouts where the side that broke the level controlled most of the volume of the candle. Breakouts that fail the filter do not count: the level is simply removed.
A swing high is a period whose high is strictly above the highs of the swLenL periods before it and the swLenR periods after it; a swing low is the mirror case. The periods are the candles of the chart or, optionally, the candles of a higher timeframe (in minutes, up to daily). The level starts on the candle where the extreme was made and is drawn a few bars beyond the last candle while it is intact. Only the maxLvls most recent levels of each side are kept.
A swing high is broken when a candle closes above it (or, if you choose, when its wick goes above it); a swing low, when a candle closes below it. The broken level is cut at the breakout candle and changes from dotted to solid.
The volume of the candle is split according to where the close sits inside its range: buy volume is volume * (close - low) / (high - low) and sell volume is the rest. A close on the high gives 100 % buying, a close on the low 100 % selling. This estimate uses only the data of the candle, so it is the same on every timeframe and in the whole history.
On the breakout candle, the wick is drawn in the colour of the breakout and the body is split into two parts: green for the buy share, red for the sell share, with the share of the breakout side starting from the side the price came from.
swTfMin = 240 on a 15-minute chart) show the major levels, while the breakouts are still read candle by candle.
swLenL (default: 10): bars (or higher timeframe periods) to the left of a swing.swLenR (default: 10): bars to the right of a swing. A swing is confirmed after these periods.swTfMin (default: 0): timeframe of the swings in minutes. 0 = chart timeframe, 1440 = daily.useWick (default: 0): 0 = breakout by close, 1 = breakout by wick.maxLvls (default: 5): most recent swing levels kept on each side.volFilt (default: 0): 1 = enable the breakout volume filter.volFiltPct (default: 60): minimum share, in %, of the breakout side in the volume of the candle.unmitStyle (default: 2) and unmitW (default: 1): style (0 = solid, 1 = dashed, 2 = dotted) and width (1 to 4) of intact levels.brkStyle (default: 0) and brkW (default: 1): style and width of broken levels.extendBars (default: 5): bars an intact level is drawn past the last candle.extendRight (default: 0): 1 = intact levels extended to the right without limit.showLabels (default: 1): volume labels on the breakout candles.shR/G/B (swing highs), slR/G/B (swing lows), buR/G/B (buy volume), beR/G/B (sell volume).
//---------------------------------------------------------------
// PRC_Breakout Volume Delta
// version = 0
// 02.10.2026
// Iván González @ www.prorealcode.com
// Author: Flux Charts
// Sharing ProRealTime knowledge
//--------------------------------------------------------------------//
DEFPARAM DRAWONLASTBARONLY = true
//---------------------------------------------------------------
// INPUTS
//---------------------------------------------------------------
swLenL = 10 // swing left bars
swLenR = 10 // swing right bars
swTfMin = 0 // swing timeframe in minutes: 0 = chart timeframe, 1440 = daily
useWick = 0 // breakout by: 0 = close, 1 = wick
maxLvls = 5 // most recent swing levels kept on each side
volFilt = 0 // 1 = breakout volume filter
volFiltPct = 60 // minimum buy (or sell) share of the breakout candle, in %
unmitStyle = 2 // unmitigated levels: 0 = solid, 1 = dashed, 2 = dotted
unmitW = 1 // unmitigated levels width (1 to 4)
brkStyle = 0 // broken levels: 0 = solid, 1 = dashed, 2 = dotted
brkW = 1 // broken levels width (1 to 4)
extendBars = 5 // bars an unmitigated level is drawn past the last candle
extendRight = 0 // 1 = unmitigated levels extended to the right without limit
showLabels = 1 // 1 = volume delta labels on the breakout candles
// swing high levels (lime)
shR = 0
shG = 230
shB = 118
// swing low levels (red)
slR = 242
slG = 54
slB = 69
// buy volume (green) and sell volume (red) of the breakout candle
buR = 0
buG = 255
buB = 106
beR = 255
beG = 45
beB = 85
//---------------------------------------------------------------
// PER-BAR SERIES
//---------------------------------------------------------------
swLenL = max(1, swLenL)
swLenR = max(1, swLenR)
maxLvls = max(1, maxLvls)
volThr = max(0, min(1, volFiltPct / 100))
// buy / sell volume of the candle, split by where the close sits in the range
IF high > low THEN
bullV = volume * (close - low) / (high - low)
ELSE
bullV = volume / 2
ENDIF
bearV = volume - bullV
// first bar of each swing-timeframe period
mNow = floor(opentime / 10000) * 60 + floor(opentime / 100) - floor(opentime / 10000) * 100
newPer = 1
IF swTfMin > 0 AND barindex > 0 THEN
newPer = 0
IF date <> date[1] THEN
newPer = 1
ELSIF swTfMin < 1440 THEN
IF floor(mNow / swTfMin) <> floor(mNow[1] / swTfMin) THEN
newPer = 1
ENDIF
ENDIF
ENDIF
//---------------------------------------------------------------
// ENGINE
// Closed candles are processed once and committed to the $c arrays (pass 1: the
// previous candle if it was the live one, pass 2: a historical candle). The live
// candle is processed on a working copy that is rebuilt on every tick (pass 3),
// so a breakout that disappears before the close leaves no trace.
// State cells: 0 last processed bar, 1 swing history count, 2-5 current swing
// period high / its bar / low / its bar, 6 period started, 7-8 bar of the last
// swing high / low level, 9-10 number of high / low levels, 11 number of breakouts
//---------------------------------------------------------------
IF barindex = 0 THEN
FOR k = 0 TO 11 DO
$cS[k] = 0
NEXT
$cS[0] = -1
$cS[7] = -1
$cS[8] = -1
ENDIF
FOR ps = 1 TO 3 DO
ofs = 0
runIt = 0
commitIt = 0
IF ps = 1 AND barindex >= 1 AND $cS[0] < barindex - 1 THEN
ofs = 1
runIt = 1
commitIt = 1
ELSIF ps = 2 AND NOT islastbarupdate AND $cS[0] < barindex THEN
runIt = 1
commitIt = 1
ELSIF ps = 3 AND islastbarupdate THEN
runIt = 1
ENDIF
IF runIt = 1 THEN
// load the committed state
nH = $cS[1]
curH = $cS[2]
curHx = $cS[3]
curL = $cS[4]
curLx = $cS[5]
inited = $cS[6]
lastPhX = $cS[7]
lastPlX = $cS[8]
nHi = $cS[9]
nLo = $cS[10]
nEv = $cS[11]
IF nHi > 0 THEN
FOR i = 0 TO nHi - 1 DO
$wHp[i] = $cHp[i]
$wHx[i] = $cHx[i]
$wHm[i] = $cHm[i]
$wHe[i] = $cHe[i]
NEXT
ENDIF
IF nLo > 0 THEN
FOR i = 0 TO nLo - 1 DO
$wLp[i] = $cLp[i]
$wLx[i] = $cLx[i]
$wLm[i] = $cLm[i]
$wLe[i] = $cLe[i]
NEXT
ENDIF
bH = high[ofs]
bL = low[ofs]
bO = open[ofs]
bC = close[ofs]
bX = barindex - ofs
bNew = newPer[ofs]
bBullV = bullV[ofs]
bBearV = bearV[ofs]
// swing timeframe candle: close the previous one and track high / low of the new one
IF bNew = 1 AND inited = 1 THEN
$hsH[nH] = curH
$hsHx[nH] = curHx
$hsL[nH] = curL
$hsLx[nH] = curLx
nH = nH + 1
inited = 0
ENDIF
IF inited = 0 THEN
curH = bH
curHx = bX
curL = bL
curLx = bX
inited = 1
ELSE
IF bH > curH THEN
curH = bH
curHx = bX
ENDIF
IF bL < curL THEN
curL = bL
curLx = bX
ENDIF
ENDIF
// pivot: candidate swLenR periods back, strictly above (below) swLenR periods
// on its right and swLenL periods on its left
IF bNew = 1 AND nH >= swLenL + swLenR + 1 THEN
cI = nH - 1 - swLenR
candH = $hsH[cI]
candL = $hsL[cI]
isPh = 1
isPl = 1
FOR j = 0 TO swLenR + swLenL DO
IF j <> swLenR THEN
hI = nH - 1 - j
IF candH <= $hsH[hI] THEN
isPh = 0
ENDIF
IF candL >= $hsL[hI] THEN
isPl = 0
ENDIF
ENDIF
NEXT
IF isPh = 1 AND $hsHx[cI] <> lastPhX THEN
$wHp[nHi] = candH
$wHx[nHi] = $hsHx[cI]
$wHm[nHi] = 0
$wHe[nHi] = 0
nHi = nHi + 1
WHILE nHi > maxLvls DO
FOR i = 0 TO nHi - 2 DO
$wHp[i] = $wHp[i + 1]
$wHx[i] = $wHx[i + 1]
$wHm[i] = $wHm[i + 1]
$wHe[i] = $wHe[i + 1]
NEXT
nHi = nHi - 1
WEND
lastPhX = $hsHx[cI]
ENDIF
IF isPl = 1 AND $hsLx[cI] <> lastPlX THEN
$wLp[nLo] = candL
$wLx[nLo] = $hsLx[cI]
$wLm[nLo] = 0
$wLe[nLo] = 0
nLo = nLo + 1
WHILE nLo > maxLvls DO
FOR i = 0 TO nLo - 2 DO
$wLp[i] = $wLp[i + 1]
$wLx[i] = $wLx[i + 1]
$wLm[i] = $wLm[i + 1]
$wLe[i] = $wLe[i + 1]
NEXT
nLo = nLo - 1
WEND
lastPlX = $hsLx[cI]
ENDIF
ENDIF
// buy / sell share of the candle
totV = bBullV + bBearV
bullPct = 0.5
IF totV > 0 THEN
bullPct = max(0, min(1, bBullV / totV))
ENDIF
bearPct = 1 - bullPct
// breakouts: a level that fails the volume filter is removed, otherwise it is broken
didHi = 0
didLo = 0
IF nHi > 0 THEN
FOR i = nHi - 1 DOWNTO 0 DO
IF $wHm[i] = 0 THEN
lvl = $wHp[i]
brOk = 0
IF useWick = 1 THEN
IF bH > lvl THEN
brOk = 1
ENDIF
ELSIF bC > lvl THEN
brOk = 1
ENDIF
IF brOk = 1 THEN
IF volFilt = 1 AND bullPct <= volThr THEN
IF i < nHi - 1 THEN
FOR m = i TO nHi - 2 DO
$wHp[m] = $wHp[m + 1]
$wHx[m] = $wHx[m + 1]
$wHm[m] = $wHm[m + 1]
$wHe[m] = $wHe[m + 1]
NEXT
ENDIF
nHi = nHi - 1
ELSE
$wHm[i] = 1
$wHe[i] = bX
didHi = 1
ENDIF
ENDIF
ENDIF
NEXT
ENDIF
IF nLo > 0 THEN
FOR i = nLo - 1 DOWNTO 0 DO
IF $wLm[i] = 0 THEN
lvl = $wLp[i]
brOk = 0
IF useWick = 1 THEN
IF bL < lvl THEN
brOk = 1
ENDIF
ELSIF bC < lvl THEN
brOk = 1
ENDIF
IF brOk = 1 THEN
IF volFilt = 1 AND bearPct <= volThr THEN
IF i < nLo - 1 THEN
FOR m = i TO nLo - 2 DO
$wLp[m] = $wLp[m + 1]
$wLx[m] = $wLx[m + 1]
$wLm[m] = $wLm[m + 1]
$wLe[m] = $wLe[m + 1]
NEXT
ENDIF
nLo = nLo - 1
ELSE
$wLm[i] = 1
$wLe[i] = bX
didLo = 1
ENDIF
ENDIF
ENDIF
NEXT
ENDIF
// breakout candle: body split into the buy and the sell share
IF didHi = 1 OR didLo = 1 THEN
bodyTop = max(bO, bC)
bodyBtm = min(bO, bC)
bodyRng = bodyTop - bodyBtm
IF bodyRng <= 0 THEN
pad0 = max(bH - bL, pipsize) * 0.35
bodyTop = bC + pad0
bodyBtm = bC - pad0
bodyRng = bodyTop - bodyBtm
ENDIF
pad = max(bH - bL, pipsize) * 0.25
IF didHi = 1 THEN
cut = bodyBtm + bodyRng * bullPct
$evBu[nEv] = 1
$evBuLo[nEv] = bodyBtm
$evBuHi[nEv] = cut
$evBeLo[nEv] = cut
$evBeHi[nEv] = bodyTop
$evLy[nEv] = bH + pad
ELSE
cut = bodyTop - bodyRng * bearPct
$evBu[nEv] = 0
$evBuLo[nEv] = bodyBtm
$evBuHi[nEv] = cut
$evBeLo[nEv] = cut
$evBeHi[nEv] = bodyTop
$evLy[nEv] = bL - pad
ENDIF
$evX[nEv] = bX
$evH[nEv] = bH
$evL[nEv] = bL
$evBv[nEv] = bBullV
$evSv[nEv] = bBearV
$evBp[nEv] = bullPct
nEv = nEv + 1
ENDIF
// commit a closed candle
IF commitIt = 1 THEN
$cS[0] = bX
$cS[1] = nH
$cS[2] = curH
$cS[3] = curHx
$cS[4] = curL
$cS[5] = curLx
$cS[6] = inited
$cS[7] = lastPhX
$cS[8] = lastPlX
$cS[9] = nHi
$cS[10] = nLo
$cS[11] = nEv
IF nHi > 0 THEN
FOR i = 0 TO nHi - 1 DO
$cHp[i] = $wHp[i]
$cHx[i] = $wHx[i]
$cHm[i] = $wHm[i]
$cHe[i] = $wHe[i]
NEXT
ENDIF
IF nLo > 0 THEN
FOR i = 0 TO nLo - 1 DO
$cLp[i] = $wLp[i]
$cLx[i] = $wLx[i]
$cLm[i] = $wLm[i]
$cLe[i] = $wLe[i]
NEXT
ENDIF
ENDIF
ENDIF
NEXT
//---------------------------------------------------------------
// DRAWING (from the working state of the live candle)
//---------------------------------------------------------------
IF islastbarupdate THEN
// swing levels
IF nHi + nLo > 0 THEN
FOR lv = 0 TO nHi + nLo - 1 DO
IF lv < nHi THEN
yv = $wHp[lv]
x1 = $wHx[lv]
isMit = $wHm[lv]
x2 = $wHe[lv]
rr = shR
gg = shG
bb = shB
ELSE
yv = $wLp[lv - nHi]
x1 = $wLx[lv - nHi]
isMit = $wLm[lv - nHi]
x2 = $wLe[lv - nHi]
rr = slR
gg = slG
bb = slB
ENDIF
IF isMit = 1 THEN
sC = brkStyle
wC = brkW
isRay = 0
ELSE
sC = unmitStyle
wC = unmitW
isRay = extendRight
x2 = barindex + extendBars
ENDIF
IF isRay = 0 THEN
IF sC = 0 THEN
IF wC <= 1 THEN
DRAWSEGMENT(x1, yv, x2, yv) COLOURED(rr, gg, bb) STYLE(line, 1)
ELSIF wC = 2 THEN
DRAWSEGMENT(x1, yv, x2, yv) COLOURED(rr, gg, bb) STYLE(line, 2)
ELSIF wC = 3 THEN
DRAWSEGMENT(x1, yv, x2, yv) COLOURED(rr, gg, bb) STYLE(line, 3)
ELSE
DRAWSEGMENT(x1, yv, x2, yv) COLOURED(rr, gg, bb) STYLE(line, 4)
ENDIF
ELSIF sC = 1 THEN
IF wC <= 1 THEN
DRAWSEGMENT(x1, yv, x2, yv) COLOURED(rr, gg, bb) STYLE(dottedline2, 1)
ELSIF wC = 2 THEN
DRAWSEGMENT(x1, yv, x2, yv) COLOURED(rr, gg, bb) STYLE(dottedline2, 2)
ELSIF wC = 3 THEN
DRAWSEGMENT(x1, yv, x2, yv) COLOURED(rr, gg, bb) STYLE(dottedline2, 3)
ELSE
DRAWSEGMENT(x1, yv, x2, yv) COLOURED(rr, gg, bb) STYLE(dottedline2, 4)
ENDIF
ELSE
IF wC <= 1 THEN
DRAWSEGMENT(x1, yv, x2, yv) COLOURED(rr, gg, bb) STYLE(dottedline, 1)
ELSIF wC = 2 THEN
DRAWSEGMENT(x1, yv, x2, yv) COLOURED(rr, gg, bb) STYLE(dottedline, 2)
ELSIF wC = 3 THEN
DRAWSEGMENT(x1, yv, x2, yv) COLOURED(rr, gg, bb) STYLE(dottedline, 3)
ELSE
DRAWSEGMENT(x1, yv, x2, yv) COLOURED(rr, gg, bb) STYLE(dottedline, 4)
ENDIF
ENDIF
ELSE
IF sC = 0 THEN
IF wC <= 1 THEN
DRAWRAY(x1, yv, barindex, yv) COLOURED(rr, gg, bb) STYLE(line, 1)
ELSIF wC = 2 THEN
DRAWRAY(x1, yv, barindex, yv) COLOURED(rr, gg, bb) STYLE(line, 2)
ELSIF wC = 3 THEN
DRAWRAY(x1, yv, barindex, yv) COLOURED(rr, gg, bb) STYLE(line, 3)
ELSE
DRAWRAY(x1, yv, barindex, yv) COLOURED(rr, gg, bb) STYLE(line, 4)
ENDIF
ELSIF sC = 1 THEN
IF wC <= 1 THEN
DRAWRAY(x1, yv, barindex, yv) COLOURED(rr, gg, bb) STYLE(dottedline2, 1)
ELSIF wC = 2 THEN
DRAWRAY(x1, yv, barindex, yv) COLOURED(rr, gg, bb) STYLE(dottedline2, 2)
ELSIF wC = 3 THEN
DRAWRAY(x1, yv, barindex, yv) COLOURED(rr, gg, bb) STYLE(dottedline2, 3)
ELSE
DRAWRAY(x1, yv, barindex, yv) COLOURED(rr, gg, bb) STYLE(dottedline2, 4)
ENDIF
ELSE
IF wC <= 1 THEN
DRAWRAY(x1, yv, barindex, yv) COLOURED(rr, gg, bb) STYLE(dottedline, 1)
ELSIF wC = 2 THEN
DRAWRAY(x1, yv, barindex, yv) COLOURED(rr, gg, bb) STYLE(dottedline, 2)
ELSIF wC = 3 THEN
DRAWRAY(x1, yv, barindex, yv) COLOURED(rr, gg, bb) STYLE(dottedline, 3)
ELSE
DRAWRAY(x1, yv, barindex, yv) COLOURED(rr, gg, bb) STYLE(dottedline, 4)
ENDIF
ENDIF
ENDIF
NEXT
ENDIF
// breakout candles: wick in the colour of the breakout, body split buy / sell
IF nEv > 0 THEN
FOR e = 0 TO nEv - 1 DO
ex = $evX[e]
IF $evBu[e] = 1 THEN
evR = buR
evG = buG
evB = buB
ELSE
evR = beR
evG = beG
evB = beB
ENDIF
DRAWSEGMENT(ex, $evL[e], ex, $evH[e]) COLOURED(evR, evG, evB) STYLE(line, 1)
DRAWSEGMENT(ex, $evBuLo[e], ex, $evBuHi[e]) COLOURED(buR, buG, buB) STYLE(line, 4)
DRAWSEGMENT(ex, $evBeLo[e], ex, $evBeHi[e]) COLOURED(beR, beG, beB) STYLE(line, 4)
// label: buy volume (share) | sell volume (share), both in the unit of the larger one
IF showLabels = 1 AND e >= nEv - 500 THEN
vBig = max(abs($evBv[e]), abs($evSv[e]))
pBu = round($evBp[e] * 1000) / 10
pBe = round((1 - $evBp[e]) * 1000) / 10
lbY = $evLy[e]
IF vBig >= 1000000000 THEN
vBu = round($evBv[e] / 10000000) / 100
vBe = round($evSv[e] / 10000000) / 100
DRAWTEXT("#vBu#B (#pBu#%) | #vBe#B (#pBe#%)", ex, lbY, SansSerif, Standard, 10) COLOURED(evR, evG, evB)
ELSIF vBig >= 1000000 THEN
vBu = round($evBv[e] / 10000) / 100
vBe = round($evSv[e] / 10000) / 100
DRAWTEXT("#vBu#M (#pBu#%) | #vBe#M (#pBe#%)", ex, lbY, SansSerif, Standard, 10) COLOURED(evR, evG, evB)
ELSIF vBig >= 1000 THEN
vBu = round($evBv[e] / 10) / 100
vBe = round($evSv[e] / 10) / 100
DRAWTEXT("#vBu#K (#pBu#%) | #vBe#K (#pBe#%)", ex, lbY, SansSerif, Standard, 10) COLOURED(evR, evG, evB)
ELSE
vBu = round($evBv[e])
vBe = round($evSv[e])
DRAWTEXT("#vBu# (#pBu#%) | #vBe# (#pBe#%)", ex, lbY, SansSerif, Standard, 10) COLOURED(evR, evG, evB)
ENDIF
ENDIF
NEXT
ENDIF
ENDIF
RETURN
Breakout Volume Delta adds one question to every breakout: how much of the candle’s volume was on the side of the move. With the split body and the label, each breakout can be judged at a glance, and the optional filter keeps only those with real volume behind them.