A classic volume profile is anchored to a session or to a fixed number of bars. Swing Profile anchors it to the market structure instead: every time a swing leg is completed, the indicator builds the volume profile of that leg alone, from the swing that started it to the swing that ended it. You get, for each impulse and each pullback, where the volume was traded, which price level was the point of control and how the leg was split between buying and selling volume.
The leg that is still forming is drawn live on the last bar, with a summary label of its total, buy and sell volume and its volume delta.
The indicator keeps a direction flag. A new highest high over the last swingLen bars sets it to “down” (a down leg will start from that high) and a new lowest low over the same window sets it to “up”. A swing high is confirmed on the bar after the extreme, as soon as the high stops making a new swingLen-bar high; the same applies to swing lows.
When the direction flag changes, the leg between the last swing high and the last swing low is complete: a down leg (orange) runs from a swing high to a swing low and is closed when a new high appears; an up leg (lime) runs from a swing low to a swing high and is closed when a new low appears.
swingLen (default: 50, minimum 10): number of bars used to detect swing highs and lows.showProfile (default: 1): draw the volume profile of each leg.showHeatMap (default: 0): stretch each bin across the whole leg as a heatmap.showZigZag (default: 1): connect the confirmed swings.zigZagStyle (default: 0): 0 = dotted, 1 = dashed, 2 = solid.showPOC (default: 1): draw the point of control of each leg.pocWidth (default: 2): width of the POC line, from 1 to 4.dataSize (default: 1): size of the labels. 0 = tiny, 1 = small, 2 = normal, 3 = large, 4 = huge.upR/G/B (up legs), dnR/G/B (down legs), pocR/G/B (point of control) and fgR/G/B (leg still forming).The indicator is calculated on the last 2000 bars, and the profiles need 200 bars of history for the ATR that sets the bin height.
//---------------------------------------------------------------
// PRC_Swing Profile
// version = 0
// 02.10.2026
// Iván González @ www.prorealcode.com
// Author: BigBeluga
// Sharing ProRealTime knowledge
//--------------------------------------------------------------------//
DEFPARAM DRAWONLASTBARONLY = true
DEFPARAM CALCULATEONLASTBARS = 2000
//---------------------------------------------------------------
// INPUTS
//---------------------------------------------------------------
swingLen = 50 // swing length (minimum 10)
showProfile = 1 // 1 = volume profile of each swing leg
showHeatMap = 0 // 1 = stretch the bins across the whole leg (heatmap)
showZigZag = 1 // 1 = connect the confirmed swing points
zigZagStyle = 0 // 0 = dotted, 1 = dashed, 2 = solid
showPOC = 1 // 1 = point of control of each leg
pocWidth = 2 // POC line width (1 to 4)
dataSize = 1 // label size: 0 tiny, 1 small, 2 normal, 3 large, 4 huge
// colour of the up legs, swing low to swing high (lime)
upR = 0
upG = 230
upB = 118
// colour of the down legs, swing high to swing low (orange)
dnR = 255
dnG = 152
dnB = 0
// POC colour (red)
pocR = 242
pocG = 54
pocB = 69
// colour of the leg still forming (chart foreground)
fgR = 60
fgG = 60
fgB = 60
//---------------------------------------------------------------
// SWING DETECTION
//---------------------------------------------------------------
swingLen = max(10, swingLen)
hiSwing = highest[swingLen](high)
loSwing = lowest[swingLen](low)
atrV = averagetruerange[200](close)
// direction flag: 1 after a new swingLen-bar high, 0 after a new swingLen-bar low
ONCE isDown = 0
IF barindex >= swingLen - 1 THEN
IF high = hiSwing THEN
isDown = 1
ENDIF
IF low = loSwing THEN
isDown = 0
ENDIF
ENDIF
// a swing point is confirmed on the bar after the extreme
ONCE hsIdx = -1
ONCE hsPx = 0
ONCE lsIdx = -1
ONCE lsPx = 0
IF barindex >= swingLen THEN
IF high[1] = hiSwing[1] AND high < hiSwing THEN
hsIdx = barindex - 1
hsPx = high[1]
ENDIF
IF low[1] = loSwing[1] AND low > loSwing THEN
lsIdx = barindex - 1
lsPx = low[1]
ENDIF
ENDIF
// bin height = half an ATR(200)
binSize = 0
IF barindex >= 200 THEN
binSize = atrV * 0.5
ENDIF
flip = 0
IF barindex > swingLen THEN
IF isDown <> isDown[1] THEN
flip = 1
ENDIF
ENDIF
//---------------------------------------------------------------
// LEG PROFILES
// pass 1: the leg that has just been completed (direction flip), stored for good
// pass 2: the leg still forming, rebuilt on every tick of the last bar in the
// free slot after the stored legs
// Every array cell written here is first reset and then rebuilt from the price
// series in the same pass, so the result does not depend on how many ticks ran.
//---------------------------------------------------------------
ONCE nLeg = 0
ONCE binPtr = 0
liveOk = 0
FOR ps = 1 TO 2 DO
doLeg = 0
IF hsIdx >= 0 AND lsIdx >= 0 THEN
IF ps = 1 AND flip = 1 THEN
doLeg = 1
ENDIF
IF ps = 2 AND islastbarupdate THEN
doLeg = 1
ENDIF
ENDIF
IF doLeg = 1 THEN
q = nLeg
p0 = binPtr
legLo = min(hsPx, lsPx)
legHi = max(hsPx, lsPx)
lastI = max(hsIdx, lsIdx)
prevI = min(hsIdx, lsIdx)
$lgHsX[q] = hsIdx
$lgHsY[q] = hsPx
$lgLsX[q] = lsIdx
$lgLsY[q] = lsPx
$lgLo[q] = legLo
$lgHi[q] = legHi
$lgPrev[q] = prevI
$lgLast[q] = lastI
$lgDir[q] = isDown
$lgOff[q] = binSize
$lgPtr[q] = p0
nBin = 0
IF binSize > 0 THEN
nBin = floor(abs(hsPx - lsPx) / binSize)
ENDIF
binStp = 0
vMax = 0
vTot = 0
vBuy = 0
vSell = 0
IF nBin >= 1 THEN
binStp = abs(hsPx - lsPx) / nBin
FOR k = 0 TO nBin - 1 DO
$bVol[p0 + k] = 0
$bBuy[p0 + k] = 0
$bSell[p0 + k] = 0
NEXT
// every close of the leg feeds the bins whose middle is closer than one bin height
// (at most two bins, around floor(uPos); one extra candidate on each side keeps
// closes that sit exactly on a boundary identical to a full scan of the bins)
FOR i = barindex - lastI TO barindex - prevI DO
candC = close[i]
candO = open[i]
candV = volume[i]
uPos = (candC - legLo) / binStp - 0.5
kA = floor(uPos)
FOR kk = kA - 1 TO kA + 2 DO
IF kk >= 0 AND kk <= nBin - 1 THEN
binMid = legLo + kk * binStp + binStp / 2
IF abs(binMid - candC) < binStp THEN
$bVol[p0 + kk] = $bVol[p0 + kk] + candV
IF candC > candO THEN
$bBuy[p0 + kk] = $bBuy[p0 + kk] + candV
ELSE
$bSell[p0 + kk] = $bSell[p0 + kk] + candV
ENDIF
ENDIF
ENDIF
NEXT
NEXT
FOR k = 0 TO nBin - 1 DO
vMax = max(vMax, $bVol[p0 + k])
vTot = vTot + $bVol[p0 + k]
vBuy = vBuy + $bBuy[p0 + k]
vSell = vSell + $bSell[p0 + k]
NEXT
ENDIF
$lgNb[q] = nBin
$lgStp[q] = binStp
$lgMax[q] = vMax
$lgTot[q] = vTot
$lgBuy[q] = vBuy
$lgSell[q] = vSell
IF ps = 1 THEN
nLeg = nLeg + 1
binPtr = binPtr + nBin
ELSE
liveOk = 1
ENDIF
ENDIF
NEXT
//---------------------------------------------------------------
// DRAWING
//---------------------------------------------------------------
IF islastbarupdate AND nLeg + liveOk > 0 THEN
nDraw = nLeg + liveOk
FOR q = 0 TO nDraw - 1 DO
isLive = 0
IF q = nLeg THEN
isLive = 1
ENDIF
IF isLive = 1 THEN
mR = fgR
mG = fgG
mB = fgB
ELSIF $lgDir[q] = 1 THEN
mR = dnR
mG = dnG
mB = dnB
ELSE
mR = upR
mG = upG
mB = upB
ENDIF
prevI = $lgPrev[q]
lastI = $lgLast[q]
legLo = $lgLo[q]
legHi = $lgHi[q]
nBin = $lgNb[q]
binStp = $lgStp[q]
p0 = $lgPtr[q]
vMax = $lgMax[q]
offY = $lgOff[q] * 0.5
zx1 = $lgHsX[q]
zy1 = $lgHsY[q]
zx2 = $lgLsX[q]
zy2 = $lgLsY[q]
// zigzag: dashed preview for the forming leg, chosen style for completed legs
IF isLive = 1 THEN
DRAWSEGMENT(zx1, zy1, zx2, zy2) COLOURED(mR, mG, mB) STYLE(dottedline2, 1)
ELSIF showZigZag = 1 THEN
IF zigZagStyle = 0 THEN
DRAWSEGMENT(zx1, zy1, zx2, zy2) COLOURED(mR, mG, mB) STYLE(dottedline, 1)
ELSIF zigZagStyle = 1 THEN
DRAWSEGMENT(zx1, zy1, zx2, zy2) COLOURED(mR, mG, mB) STYLE(dottedline2, 1)
ELSE
DRAWSEGMENT(zx1, zy1, zx2, zy2) COLOURED(mR, mG, mB) STYLE(line, 1)
ENDIF
ENDIF
IF nBin >= 1 AND vMax > 0 THEN
halfW = (lastI - prevI) / 2
olA = 255
IF showHeatMap = 1 THEN
olA = 127
ENDIF
prevW = 0
FOR k = 0 TO nBin - 1 DO
binLo = legLo + k * binStp
binHi = binLo + binStp
binV = $bVol[p0 + k]
vRatio = binV / vMax
profW = floor(vRatio * halfW)
isPoc = 0
IF binV = vMax THEN
isPoc = 1
ENDIF
IF showProfile = 1 THEN
xL = prevI + profW
ELSE
xL = prevI
ENDIF
IF showHeatMap = 1 THEN
xR = lastI
ELSE
xR = prevI
ENDIF
// bin: transparency grows with the volume of the bin, the POC bin takes the POC colour
IF showProfile = 1 OR showHeatMap = 1 THEN
IF isPoc = 1 AND showPOC = 1 THEN
fR = pocR
fG = pocG
fB = pocB
fA = 127
ELSE
fR = mR
fG = mG
fB = mB
fA = round(25 + vRatio * 102)
ENDIF
DRAWRECTANGLE(xL, binHi, xR, binLo) COLOURED(fR, fG, fB, 0) FILLCOLOR(fR, fG, fB, fA)
ENDIF
// point of control: from the edge of the profile to the end of the leg
IF isPoc = 1 AND showPOC = 1 THEN
pocY = (binLo + binHi) / 2
IF pocWidth <= 1 THEN
DRAWSEGMENT(xL, pocY, lastI, pocY) COLOURED(pocR, pocG, pocB) STYLE(line, 1)
ELSIF pocWidth = 2 THEN
DRAWSEGMENT(xL, pocY, lastI, pocY) COLOURED(pocR, pocG, pocB) STYLE(line, 2)
ELSIF pocWidth = 3 THEN
DRAWSEGMENT(xL, pocY, lastI, pocY) COLOURED(pocR, pocG, pocB) STYLE(line, 3)
ELSE
DRAWSEGMENT(xL, pocY, lastI, pocY) COLOURED(pocR, pocG, pocB) STYLE(line, 4)
ENDIF
ENDIF
// volume of the POC bin, written inside it
IF isPoc = 1 AND showHeatMap = 0 THEN
txtX = round((xL + xR) / 2)
txtY = (binLo + binHi) / 2
IF binV >= 1000000000 THEN
vTxt = round(binV / 10000000) / 100
DRAWTEXT("#vTxt#B", txtX, txtY, SansSerif, Standard, 9) COLOURED(0, 0, 0)
ELSIF binV >= 1000000 THEN
vTxt = round(binV / 10000) / 100
DRAWTEXT("#vTxt#M", txtX, txtY, SansSerif, Standard, 9) COLOURED(0, 0, 0)
ELSIF binV >= 1000 THEN
vTxt = round(binV / 10) / 100
DRAWTEXT("#vTxt#K", txtX, txtY, SansSerif, Standard, 9) COLOURED(0, 0, 0)
ELSE
vTxt = round(binV * 100) / 100
DRAWTEXT("#vTxt#", txtX, txtY, SansSerif, Standard, 9) COLOURED(0, 0, 0)
ENDIF
ENDIF
// outline of the profile
IF showProfile = 1 THEN
IF k = 0 THEN
DRAWSEGMENT(prevI, binLo, prevI + profW, binLo) COLOURED(mR, mG, mB, olA)
ELSE
DRAWSEGMENT(prevI + prevW, binLo, prevI + profW, binLo) COLOURED(mR, mG, mB, olA)
ENDIF
DRAWSEGMENT(prevI + profW, binLo, prevI + profW, binHi) COLOURED(mR, mG, mB, olA)
IF k = nBin - 1 THEN
DRAWSEGMENT(prevI + profW, binHi, prevI, binHi) COLOURED(mR, mG, mB, olA)
DRAWSEGMENT(prevI, binHi, prevI, legLo) COLOURED(mR, mG, mB, olA)
ENDIF
ENDIF
prevW = profW
NEXT
ENDIF
// labels
IF isLive = 0 THEN
// completed leg: arrow at the start of the leg
IF $lgDir[q] = 1 THEN
lbY = legHi + offY
IF dataSize = 0 THEN
DRAWTEXT("▼", prevI, lbY, SansSerif, Standard, 8) COLOURED(mR, mG, mB)
ELSIF dataSize = 1 THEN
DRAWTEXT("▼", prevI, lbY, SansSerif, Standard, 11) COLOURED(mR, mG, mB)
ELSIF dataSize = 2 THEN
DRAWTEXT("▼", prevI, lbY, SansSerif, Standard, 14) COLOURED(mR, mG, mB)
ELSIF dataSize = 3 THEN
DRAWTEXT("▼", prevI, lbY, SansSerif, Standard, 18) COLOURED(mR, mG, mB)
ELSE
DRAWTEXT("▼", prevI, lbY, SansSerif, Standard, 24) COLOURED(mR, mG, mB)
ENDIF
ELSE
lbY = legLo - offY
IF dataSize = 0 THEN
DRAWTEXT("▲", prevI, lbY, SansSerif, Standard, 8) COLOURED(mR, mG, mB)
ELSIF dataSize = 1 THEN
DRAWTEXT("▲", prevI, lbY, SansSerif, Standard, 11) COLOURED(mR, mG, mB)
ELSIF dataSize = 2 THEN
DRAWTEXT("▲", prevI, lbY, SansSerif, Standard, 14) COLOURED(mR, mG, mB)
ELSIF dataSize = 3 THEN
DRAWTEXT("▲", prevI, lbY, SansSerif, Standard, 18) COLOURED(mR, mG, mB)
ELSE
DRAWTEXT("▲", prevI, lbY, SansSerif, Standard, 24) COLOURED(mR, mG, mB)
ENDIF
ENDIF
ELSIF nBin >= 1 AND vMax > 0 THEN
// forming leg: total, buy and sell volume of the bins and the delta in percent
vTot = $lgTot[q]
IF isDown = 0 THEN
lbY = legHi + offY
ELSE
lbY = legLo - offY
ENDIF
dPct = round(($lgBuy[q] - $lgSell[q]) / vTot * 10000) / 100
IF vTot >= 1000000000 THEN
vDiv = 1000000000
vUnit = 3
ELSIF vTot >= 1000000 THEN
vDiv = 1000000
vUnit = 2
ELSIF vTot >= 1000 THEN
vDiv = 1000
vUnit = 1
ELSE
vDiv = 1
vUnit = 0
ENDIF
tT = round(vTot / vDiv * 100) / 100
tB = round($lgBuy[q] / vDiv * 100) / 100
tS = round($lgSell[q] / vDiv * 100) / 100
IF vUnit = 3 THEN
IF dataSize = 0 THEN
DRAWTEXT("T: #tT#B B: #tB#B S: #tS#B D: #dPct#%", prevI, lbY, SansSerif, Standard, 8) COLOURED(mR, mG, mB)
ELSIF dataSize = 1 THEN
DRAWTEXT("T: #tT#B B: #tB#B S: #tS#B D: #dPct#%", prevI, lbY, SansSerif, Standard, 11) COLOURED(mR, mG, mB)
ELSIF dataSize = 2 THEN
DRAWTEXT("T: #tT#B B: #tB#B S: #tS#B D: #dPct#%", prevI, lbY, SansSerif, Standard, 14) COLOURED(mR, mG, mB)
ELSIF dataSize = 3 THEN
DRAWTEXT("T: #tT#B B: #tB#B S: #tS#B D: #dPct#%", prevI, lbY, SansSerif, Standard, 18) COLOURED(mR, mG, mB)
ELSE
DRAWTEXT("T: #tT#B B: #tB#B S: #tS#B D: #dPct#%", prevI, lbY, SansSerif, Standard, 24) COLOURED(mR, mG, mB)
ENDIF
ELSIF vUnit = 2 THEN
IF dataSize = 0 THEN
DRAWTEXT("T: #tT#M B: #tB#M S: #tS#M D: #dPct#%", prevI, lbY, SansSerif, Standard, 8) COLOURED(mR, mG, mB)
ELSIF dataSize = 1 THEN
DRAWTEXT("T: #tT#M B: #tB#M S: #tS#M D: #dPct#%", prevI, lbY, SansSerif, Standard, 11) COLOURED(mR, mG, mB)
ELSIF dataSize = 2 THEN
DRAWTEXT("T: #tT#M B: #tB#M S: #tS#M D: #dPct#%", prevI, lbY, SansSerif, Standard, 14) COLOURED(mR, mG, mB)
ELSIF dataSize = 3 THEN
DRAWTEXT("T: #tT#M B: #tB#M S: #tS#M D: #dPct#%", prevI, lbY, SansSerif, Standard, 18) COLOURED(mR, mG, mB)
ELSE
DRAWTEXT("T: #tT#M B: #tB#M S: #tS#M D: #dPct#%", prevI, lbY, SansSerif, Standard, 24) COLOURED(mR, mG, mB)
ENDIF
ELSIF vUnit = 1 THEN
IF dataSize = 0 THEN
DRAWTEXT("T: #tT#K B: #tB#K S: #tS#K D: #dPct#%", prevI, lbY, SansSerif, Standard, 8) COLOURED(mR, mG, mB)
ELSIF dataSize = 1 THEN
DRAWTEXT("T: #tT#K B: #tB#K S: #tS#K D: #dPct#%", prevI, lbY, SansSerif, Standard, 11) COLOURED(mR, mG, mB)
ELSIF dataSize = 2 THEN
DRAWTEXT("T: #tT#K B: #tB#K S: #tS#K D: #dPct#%", prevI, lbY, SansSerif, Standard, 14) COLOURED(mR, mG, mB)
ELSIF dataSize = 3 THEN
DRAWTEXT("T: #tT#K B: #tB#K S: #tS#K D: #dPct#%", prevI, lbY, SansSerif, Standard, 18) COLOURED(mR, mG, mB)
ELSE
DRAWTEXT("T: #tT#K B: #tB#K S: #tS#K D: #dPct#%", prevI, lbY, SansSerif, Standard, 24) COLOURED(mR, mG, mB)
ENDIF
ELSE
IF dataSize = 0 THEN
DRAWTEXT("T: #tT# B: #tB# S: #tS# D: #dPct#%", prevI, lbY, SansSerif, Standard, 8) COLOURED(mR, mG, mB)
ELSIF dataSize = 1 THEN
DRAWTEXT("T: #tT# B: #tB# S: #tS# D: #dPct#%", prevI, lbY, SansSerif, Standard, 11) COLOURED(mR, mG, mB)
ELSIF dataSize = 2 THEN
DRAWTEXT("T: #tT# B: #tB# S: #tS# D: #dPct#%", prevI, lbY, SansSerif, Standard, 14) COLOURED(mR, mG, mB)
ELSIF dataSize = 3 THEN
DRAWTEXT("T: #tT# B: #tB# S: #tS# D: #dPct#%", prevI, lbY, SansSerif, Standard, 18) COLOURED(mR, mG, mB)
ELSE
DRAWTEXT("T: #tT# B: #tB# S: #tS# D: #dPct#%", prevI, lbY, SansSerif, Standard, 24) COLOURED(mR, mG, mB)
ENDIF
ENDIF
ENDIF
NEXT
ENDIF
RETURN
Swing Profile ties volume to market structure. Instead of one profile for a session, you get one for every swing, which makes it easy to compare impulses and pullbacks, find the level where each leg was accepted and follow the balance of the swing that is still forming.