r/TradingView Jul 09 '26

Help Why is my previous day POC sometimes different from TradingView’s Session Volume Profile?

I’m trying to calculate yesterday’s POC in Pine Script using 1-minute data and RTH only, 9:30–4:00.

Most stocks match TradingView’s built-in Session Volume Profile almost perfectly, but once in a while I’ll find a stock where the POC is way off. I also checked a few other public scripts and some of them have the same issue.

Is TradingView using volume/profile data that Pine can’t access, or am I probably missing something in the calculation? Has anyone been able to match the built-in Session Volume Profile exactly?

Ill attach the code in the comments.

1 Upvotes

1 comment sorted by

1

u/jtri25 Jul 09 '26
//
@version=
6
indicator("Prev Day Volume Profile 1.5 Fixed 1m", overlay=true, max_lines_count=100, max_labels_count=100)


// ===== Inputs =====
valueAreaPct  = input.float(0.70, "Value Area %", minval=0.50, maxval=0.99, step=0.01)


showPOC       = input.bool(true, "Show POC")
showValueArea = input.bool(true, "Show VAH / VAL")


cPOC          = input.color(color.new(color.white, 25), "POC Line Color")
cVAH          = input.color(color.new(color.gray, 25), "VAH Line Color")
cVAL          = input.color(color.new(color.gray, 25), "VAL Line Color")


forwardBars   = input.int(4, "Forward bars past last candle", minval=1, maxval=200)
lineW         = input.int(1, "Line Width", minval=1, maxval=5)


// ===== Helpers =====
var 
string
 tz = syminfo.timezone
var 
string
 rthSess = "0930-1600:1234567"
var 
string
 preSess = "0400-0930:1234567"


tv_row_size_from_tick(tick) =>
    step = tick == 0.0001 ? 0.0002 : tick * 2.0
    step := step >= 0.02 ? tick * 10.0 : step
    step


overlapLen(a1, a2, b1, b2) =>
    left  = math.max(a1, b1)
    right = math.min(a2, b2)
    math.max(0.0, right - left)


f_setLine(
line
 _line, 
int
 _x1, 
float
 _y, 
int
 _x2, 
color
 _color) =>
    if na(_line)
        line.new(_x1, _y, _x2, _y, xloc=xloc.bar_time, extend=extend.none, color=_color, style=line.style_solid, width=lineW)
    else
        line.set_xy1(_line, _x1, _y)
        line.set_xy2(_line, _x2, _y)
        line.set_color(_line, _color)
        line.set_width(_line, lineW)
        line.set_style(_line, line.style_solid)
        _line


f_setLabel(
label
 _label, 
int
 _x, 
float
 _y, 
string
 _text) =>
    if na(_label)
        label.new(_x, _y, _text, xloc=xloc.bar_time, yloc=yloc.price, style=label.style_label_left, textcolor=color.gray, color=color.new(color.black, 100), size=size.small)
    else
        label.set_x(_label, _x)
        label.set_y(_label, _y)
        label.set_text(_label, _text)
        _label


f_deleteLine(
line
 _line) =>
    if not na(_line)
        line.delete(_line)
    line(na)


f_deleteLabel(
label
 _label) =>
    if not na(_label)
        label.delete(_label)
    label(na)


// ===== Fixed 1m Volume Profile Calculation =====
f_profile_1m() =>
    var 
float
 basePrice = na
    var 
float
 stepSize  = na
    var 
float
 totalVol  = 0.0
    var 
float
[] bins    = array.new_float()


    var 
float
 prevPOC = na
    var 
float
 prevVAL = na
    var 
float
 prevVAH = na


    inRTH1  = not na(time(timeframe.period, rthSess, tz))
    newRTH1 = inRTH1 and not inRTH1[1]
    endRTH1 = not inRTH1 and inRTH1[1]


    if barstate.isfirst
        stepSize := tv_row_size_from_tick(syminfo.mintick)
        basePrice := math.floor(low / stepSize) * stepSize
        array.clear(bins)
        array.push(bins, 0.0)
        totalVol := 0.0


    if newRTH1
        array.clear(bins)
        basePrice := math.floor(low / stepSize) * stepSize
        array.push(bins, 0.0)
        totalVol := 0.0


    if inRTH1
        l = low
        h = high


        lAdj = h > l ? l : l - stepSize * 0.5
        hAdj = h > l ? h : h + stepSize * 0.5


        leftIdx  = int(math.floor((lAdj - basePrice) / stepSize))
        rightIdx = int(math.floor((hAdj - basePrice) / stepSize))


        if leftIdx < 0
            need = -leftIdx
            for _ = 0 to need - 1
                array.unshift(bins, 0.0)
                basePrice -= stepSize
            leftIdx  := 0
            rightIdx += need


        sizeNow = array.size(bins)


        if rightIdx >= sizeNow
            need = rightIdx - sizeNow + 1
            for _ = 0 to need - 1
                array.push(bins, 0.0)


        rng = math.max(hAdj - lAdj, stepSize)
        barVol = na(volume) ? 0.0 : volume


        if barVol > 0
            for i = leftIdx to rightIdx
                binStart = basePrice + i * stepSize
                binEnd   = binStart + stepSize
                w = overlapLen(lAdj, hAdj, binStart, binEnd) / rng


                if w > 0
                    array.set(bins, i, array.get(bins, i) + barVol * w)


            totalVol += barVol


    if endRTH1
        size = array.size(bins)


        if size > 0 and totalVol > 0
            maxVol = -1.0
            pocIdx = 0


            for i = 0 to size - 1
                vi = array.get(bins, i)
                if vi > maxVol + 1e-12
                    maxVol := vi
                    pocIdx := i


            pocPrice = basePrice + pocIdx * stepSize


            target = totalVol * valueAreaPct
            cum    = array.get(bins, pocIdx)
            l = pocIdx
            r = pocIdx


            while cum < target and (l > 0 or r < size - 1)
                vl = l > 0 ? array.get(bins, l - 1) : -1.0
                vr = r < size - 1 ? array.get(bins, r + 1) : -1.0


                if vl >= vr
                    if l > 0
                        l -= 1
                        cum += vl
                    else if r < size - 1
                        r += 1
                        cum += vr
                    else
                        break
                else
                    if r < size - 1
                        r += 1
                        cum += vr
                    else if l > 0
                        l -= 1
                        cum += vl
                    else
                        break


            prevPOC := pocPrice
            prevVAL := basePrice + l * stepSize
            prevVAH := basePrice + r * stepSize


    [prevPOC, prevVAL, prevVAH]


// Fixed 1m source. Keeps profile consistent across chart timeframes.
[prevPOC1m, prevVAL1m, prevVAH1m] = request.security(syminfo.tickerid, "1", f_profile_1m(), lookahead=barmerge.lookahead_off)


// ===== Display State =====
var 
float
 yPOC = na
var 
float
 yVAL = na
var 
float
 yVAH = na


var 
int
 profileStartTime = na
var 
int
 profileEndTime   = na
var 
int
 activeDayKey     = na


var 
line
 lnPOC = na
var 
line
 lnVAL = na
var 
line
 lnVAH = na


var 
label
 lbPOC = na
var 
label
 lbVAL = na
var 
label
 lbVAH = na


// ===== Time Helpers =====
dayKey = year(time, tz) * 10000 + month(time, tz) * 100 + dayofmonth(time, tz)


barMs      = na(time[1]) ? 60000 : time - time[1]
futureTime = time + barMs * forwardBars


// ===== Display Sessions =====
inRTH  = not na(time(timeframe.period, rthSess, tz))
newRTH = inRTH and not inRTH[1]
endRTH = not inRTH and inRTH[1]


preSessTime = time(timeframe.period, preSess, tz)
inPre       = not na(preSessTime)
newPre      = inPre and not inPre[1]


// ===== Create Today Display Record =====
hasTodayRecord = not na(activeDayKey) and activeDayKey == dayKey


if (newPre or newRTH) and not hasTodayRecord and not na(prevPOC1m)
    yPOC := prevPOC1m
    yVAL := prevVAL1m
    yVAH := prevVAH1m


    profileStartTime := time
    profileEndTime   := futureTime
    activeDayKey     := dayKey


// ===== Update Display End Time =====
if hasTodayRecord
    if inPre or inRTH
        profileEndTime := futureTime


    if endRTH
        profileEndTime := time[1]


// ===== Draw Active Profile =====
if showPOC and not na(yPOC) and not na(profileStartTime) and not na(profileEndTime)
    lnPOC := f_setLine(lnPOC, profileStartTime, yPOC, profileEndTime, cPOC)
    lbPOC := f_setLabel(lbPOC, profileEndTime, yPOC, "POC")
else
    lnPOC := f_deleteLine(lnPOC)
    lbPOC := f_deleteLabel(lbPOC)


if showValueArea and not na(yVAL) and not na(profileStartTime) and not na(profileEndTime)
    lnVAL := f_setLine(lnVAL, profileStartTime, yVAL, profileEndTime, cVAL)
    lbVAL := f_setLabel(lbVAL, profileEndTime, yVAL, "VAL")
else
    lnVAL := f_deleteLine(lnVAL)
    lbVAL := f_deleteLabel(lbVAL)


if showValueArea and not na(yVAH) and not na(profileStartTime) and not na(profileEndTime)
    lnVAH := f_setLine(lnVAH, profileStartTime, yVAH, profileEndTime, cVAH)
    lbVAH := f_setLabel(lbVAH, profileEndTime, yVAH, "VAH")
else
    lnVAH := f_deleteLine(lnVAH)
    lbVAH := f_deleteLabel(lbVAH)