An anchored VWAP tells you the average price paid by the market since a given bar. Its standard deviation bands tell you how far price usually travels around that average. A volume profile tells you at which prices the volume was actually traded. Most charts show these three things with three different tools, and the trader has to put them together.
Heatmap VWAP Profile by ChartPrime draws them as a single picture. From an anchor a fixed number of bars back, it plots the anchored VWAP and a fan of 71 deviation bands, from -3 to +3 standard deviations. Each band is coloured by the volume that the profile holds at its level: faint where little volume traded, deep orange where most of it did. On the right of the last bar, the volume profile of the same range shows the same information as horizontal bars, split into up and down volume, with labels for the top and bottom of the range, the VWAP and the point of control.
Anchored VWAP. The anchor is the bar that lies a fixed number of bars before the last bar (300 by default). From that bar onwards the indicator accumulates the typical price, (high + low + close) / 3, weighted by volume:
Both are computed for every bar since the anchor, so the VWAP line and the bands follow the market bar by bar. On the anchor bar σ is zero, which is why all the bands start from a single point and open like a fan as volume and dispersion build up.
Deviation bands. The range from -3σ to +3σ is cut into 70 equal steps, which gives 71 band lines (VWAP – 3σ, VWAP – 3σ + 6σ/70, … up to VWAP + 3σ). With an even number of rows the middle band is the VWAP itself.
Volume profile. On the last bar, the same range (VWAP ± 3σ of the last bar) is split into 70 rows. Each of the 300 bars after the anchor shares its volume equally among the rows its high-low range covers; a bar partly outside the range puts all its volume in the rows it does cover, and a bar completely outside does not count. The volume goes to the up side when the bar closed at or above its open, and to the down side otherwise. The row with the most volume is the point of control (POC).
The heatmap. Each band line separates two rows of the profile. Its colour, its opacity and its style come from the average volume of those two rows relative to the largest row: from a soft, almost transparent gold for low volume to a vivid, almost solid orange for the highest volume, and a solid line instead of a dotted one when the volume is above 75% of the maximum. The profile rows use the same scale: soft gold to deep orange for up volume, soft tan to burnt bronze for down volume.
The chart needs at least anchorBars + 1 bars loaded; with fewer bars the anchor is the first bar of the chart. The indicator needs volume: on instruments without volume nothing is drawn.
//---------------------------------------------------------------
//PRC_Heatmap VWAP Profile
//version = 0
//21.10.2026
//Iván González @ www.prorealcode.com
//Author: ChartPrime
//Sharing ProRealTime knowledge
//--------------------------------------------------------------------//
defparam drawonlastbaronly = true
// ===================== INPUTS =====================
anchorBars = 300 // anchor: bars back from the last bar (1 to 500)
showBands = 1 // 1 = show the dynamic standard deviation bands (heatmap lines)
maxStdevMult = 3.0 // outer range of the bands and of the profile, in standard deviations (min 0.5)
showVP = 1 // 1 = show the volume profile
vpRows = 70 // volume profile rows / number of bands (2 to 100)
vpWidth = 60 // maximum width of the volume profile, in bars (min 10)
showLabels = 1 // 1 = show the price labels (TOP, VWAP, POC, BOT)
lblOff = 10 // label text centred this many bars to the right of its anchor point
minTransp = 10 // transparency of the highest volume (0 = solid)
maxTransp = 85 // transparency of the lowest volume (100 = invisible)
bandStep = 1 // bars per band segment: 1 = every bar; raise it to 2 or 3 if the chart gets slow
// Chart background colour: thin border between the profile rows
bgR = 255
bgG = 255
bgB = 255
// ===================== CALCULATIONS =====================
aBars = max(1, min(500, round(anchorBars)))
kMult = max(0.5, maxStdevMult)
nRows = max(2, min(100, round(vpRows)))
wid = max(10, round(vpWidth))
minT = max(0, min(100, minTransp))
maxT = max(0, min(100, maxTransp))
bStep = max(1, round(bandStep))
IF islastbarupdate THEN
// The anchor bar is aOff bars back from the last bar
aOff = min(aBars, barindex)
// 1. Anchored VWAP and its volume-weighted standard deviation, bar by bar since the anchor
cumPV = 0
cumVol = 0
cumPV2 = 0
jFirst = -1
FOR j = aOff DOWNTO 0 DO
tp = (high[j] + low[j] + close[j]) / 3
vj = volume[j]
cumPV = cumPV + tp * vj
cumVol = cumVol + vj
cumPV2 = cumPV2 + tp * tp * vj
IF cumVol > 0 THEN
vw = cumPV / cumVol
vr = cumPV2 / cumVol - vw * vw
IF vr > 0 THEN
sd = sqrt(vr)
ELSE
sd = 0
ENDIF
$vw[j] = vw
$sd[j] = sd
IF jFirst < 0 THEN
jFirst = j
ENDIF
ENDIF
NEXT
// No volume since the anchor: no VWAP and nothing to draw
IF jFirst >= 0 THEN
vwNow = $vw[0]
sdNow = $sd[0]
hiPrice = vwNow + kMult * sdNow
loPrice = vwNow - kMult * sdNow
// 2. Volume profile between -k and +k standard deviations, split into up and down volume
FOR r = 0 TO nRows - 1 DO
$upV[r] = 0
$dnV[r] = 0
$totV[r] = 0
NEXT
maxVol = 0
pocRow = 0
IF hiPrice > loPrice THEN
rowSize = (hiPrice - loPrice) / nRows
// The bars after the anchor (the anchor bar itself only on a short chart)
nI = min(aBars - 1, aOff)
FOR i = 0 TO nI DO
rowLo = max(0, floor((low[i] - loPrice) / rowSize))
rowHi = min(nRows - 1, floor((high[i] - loPrice) / rowSize))
IF rowHi >= rowLo THEN
// A bar partly outside the range puts all its volume in the rows it covers
volPerRow = volume[i] / (rowHi - rowLo + 1)
IF close[i] >= open[i] THEN
FOR r = rowLo TO rowHi DO
$upV[r] = $upV[r] + volPerRow
NEXT
ELSE
FOR r = rowLo TO rowHi DO
$dnV[r] = $dnV[r] + volPerRow
NEXT
ENDIF
ENDIF
NEXT
// Point of control: the row with the most volume (the lowest one on a tie)
FOR r = 0 TO nRows - 1 DO
tot = $upV[r] + $dnV[r]
$totV[r] = tot
IF tot > maxVol THEN
maxVol = tot
pocRow = r
ENDIF
NEXT
ENDIF
// 3. Anchored VWAP line
IF jFirst > 0 THEN
FOR j = jFirst DOWNTO 1 DO
DRAWSEGMENT(barindex - j, $vw[j], barindex - j + 1, $vw[j - 1]) COLOURED(255, 179, 0, 255) STYLE(line, 2)
NEXT
ENDIF
// 4. Volume profile to the right of the last bar: up volume, then down volume
IF showVP = 1 AND maxVol > 0 AND hiPrice > loPrice THEN
xLeft = barindex
FOR r = 0 TO nRows - 1 DO
tot = $totV[r]
IF tot > 0 THEN
rowLow = loPrice + r * rowSize
rowHigh = rowLow + rowSize
upW = round(wid * $upV[r] / maxVol)
dnW = round(wid * $dnV[r] / maxVol)
ratio = tot / maxVol
trp = round(maxT - ratio * (maxT - minT))
alphaV = round(255 * (100 - trp) / 100)
// Colour gradient by volume: soft gold to deep orange (up), soft tan to bronze (down)
uR = 255
uG = round(224 + (109 - 224) * ratio)
uB = round(130 + (0 - 130) * ratio)
dR = round(215 + (141 - 215) * ratio)
dG = round(204 + (110 - 204) * ratio)
dB = round(200 + (99 - 200) * ratio)
IF upW > 0 THEN
DRAWRECTANGLE(xLeft, rowHigh, xLeft + upW, rowLow) COLOURED(bgR, bgG, bgB, 255) FILLCOLOR(uR, uG, uB, alphaV)
ENDIF
IF dnW > 0 THEN
DRAWRECTANGLE(xLeft + upW, rowHigh, xLeft + upW + dnW, rowLow) COLOURED(bgR, bgG, bgB, 255) FILLCOLOR(dR, dG, dB, alphaV)
ENDIF
ENDIF
NEXT
ENDIF
// 5. Heatmap bands: one line per row boundary, coloured by the volume of the rows it separates
IF showBands = 1 AND jFirst > 0 THEN
stepStd = 2 * kMult / nRows
// At most 100 band lines are kept: with 100 rows the first band (-k) is not shown
firstBand = max(0, nRows - 99)
FOR r = firstBand TO nRows DO
lineVol = 0
IF maxVol > 0 THEN
IF r = 0 THEN
lineVol = $totV[0]
ELSIF r = nRows THEN
lineVol = $totV[nRows - 1]
ELSE
lineVol = ($totV[r - 1] + $totV[r]) / 2
ENDIF
binRatio = lineVol / maxVol
ELSE
binRatio = 0
ENDIF
trp = round(maxT - binRatio * (maxT - minT))
alphaV = round(255 * (100 - trp) / 100)
uR = 255
uG = round(224 + (109 - 224) * binRatio)
uB = round(130 + (0 - 130) * binRatio)
mult = -kMult + r * stepStd
jA = jFirst
yA = $vw[jA] + mult * $sd[jA]
WHILE jA > 0 DO
jB = max(0, jA - bStep)
yB = $vw[jB] + mult * $sd[jB]
// Solid line on the high volume nodes, dotted elsewhere
IF binRatio > 0.75 THEN
DRAWSEGMENT(barindex - jA, yA, barindex - jB, yB) COLOURED(uR, uG, uB, alphaV) STYLE(line, 1)
ELSE
DRAWSEGMENT(barindex - jA, yA, barindex - jB, yB) COLOURED(uR, uG, uB, alphaV) STYLE(dottedline, 1)
ENDIF
jA = jB
yA = yB
WEND
NEXT
ENDIF
// 6. Price labels (TOP, VWAP, POC, BOT)
IF showLabels = 1 AND hiPrice > loPrice THEN
pocPrice = loPrice + (pocRow + 0.5) * rowSize
labelX = barindex + lblOff
kTxt = round(kMult * 10) / 10
hiTxt = round(hiPrice * 100) / 100
vwTxt = round(vwNow * 100) / 100
pocTxt = round(pocPrice * 100) / 100
loTxt = round(loPrice * 100) / 100
DRAWTEXT("TOP (+#kTxt#σ): #hiTxt#", labelX, hiPrice, SansSerif, Bold, 10) COLOURED(255, 109, 0, 255)
DRAWTEXT("VWAP: #vwTxt#", labelX, vwNow, SansSerif, Bold, 10) COLOURED(0, 0, 0, 255)
DRAWTEXT("POC: #pocTxt#", labelX + wid, pocPrice, SansSerif, Bold, 10) COLOURED(255, 61, 0, 255)
DRAWTEXT("BOT (-#kTxt#σ): #loTxt#", labelX, loPrice, SansSerif, Bold, 10) COLOURED(0, 0, 0, 255)
ENDIF
ENDIF
ENDIF
RETURN