Indicator · Pine Script v6 · On the chart

VP RSD Native-Like Bar Profile Diagnostic

by 345-strategies· code credited to TheStratSuite contributors

This research diagnostic indicator reconstructs fixed-range volume profiles on the main price chart using lower-timeframe bar data.

View on GitHub

GitHub stars5on the repository
Pine versionv6current
Lines of code1,038
Settings17inputs
LicenceMPL-2.0
Last update2 days ago26 Oct 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

1,038 lines of Pine Script v6 from pine/research/VP_RSD_Native_Like_Bar_Profile_Diagnostic_v0.3.1.pine, published under MPL-2.0. Keep the author's credit and licence lines when you reuse it.

VP_RSD_Native_Like_Bar_Profile_Diagnostic_v0.3.1.pine
// This source code is subject to the terms of the Mozilla Public License 2.0
// at https://mozilla.org/MPL/2.0/
// © 2026 TheStratSuite contributors
//@version=6

// RESEARCH DIAGNOSTIC ONLY. NOT A TRADING SIGNAL.
//
// This variant approximates TradingView's native Fixed Range Volume Profile
// from lower-timeframe OHLCV bars. It uses an immutable RSD-derived 15-minute
// input range, a selectable fixed source timeframe, and three explicit total-
// volume allocation modes for fixed output rows.
//
// Endpoint contract:
//   [Start 15m bar time, End 15m bar time + 15 minutes)
//
// Source timeframes are deliberately explicit because the native tool can step
// up its internal timeframe for long ranges. This script does not claim parity;
// it exists to compare 1m/3m/5m/15m reconstructions with the native drawing.
//
// The scorer is discovery instrumentation. It displays multiple clean log-jump
// boundaries and does not create entries, exits, support/resistance claims, or
// production visibility decisions.
indicator(
     "VP RSD Native-Like Bar Profile Diagnostic v0.3.1",
     shorttitle = "VP RSD BAR 0.3.1",
     overlay = true,
     max_bars_back = 5000,
     max_lines_count = 500,
     max_boxes_count = 10,
     max_labels_count = 100)


// ============================================================================
// INPUTS
// ============================================================================

string rangeGroup = "1. Immutable RSD range (15m chart time)"
int profileStartInput = input.time(
     timestamp("23 Jun 2026 12:45 -0700"),
     "Start swing bar",
     group = rangeGroup)
int profileEndBarInput = input.time(
     timestamp("14 Jul 2026 06:30 -0700"),
     "End swing bar (included)",
     group = rangeGroup)

string constructionGroup = "2. Native-like bar construction"
string sourceTimeframeInput = input.string(
     "5",
     "Source timeframe",
     options = ["1", "3", "5", "15"],
     group = constructionGroup,
     tooltip = "Default 5m is the next empirical parity check. Run 1m, 3m, 5m, and 15m as separate comparisons.")
string allocationModeInput = input.string(
     "Close bin",
     "Allocation mode",
     options = [
         "High-low overlap",
         "Close bin",
         "HLC3 bin"],
     group = constructionGroup,
     tooltip = "High-low overlap spreads total bar volume by exact tick overlap. Close bin and HLC3 bin assign the full source-bar volume to one fixed output row.")
int outputTicksInput = input.int(
     10,
     "Ticks per row",
     minval = 1,
     maxval = 500,
     group = constructionGroup,
     tooltip = "Match native FRVP: 25 for current SPY/DIA/NVDA examples; 10 for XLE.")
float valueAreaPercentInput = input.float(
     70.0,
     "Value Area target (%)",
     minval = 1.0,
     maxval = 100.0,
     step = 0.5,
     group = constructionGroup)

string scoringGroup = "3. Raw shelf scorer"
int shelfCountInput = input.int(
     8,
     "Selected shelf candidates",
     minval = 1,
     maxval = 12,
     group = scoringGroup)
bool applyDiscoveryGatesInput = input.bool(
     false,
     "Apply provisional discovery gates",
     group = scoringGroup)
float minimumRatioInput = input.float(
     1.75,
     "Gate: minimum ratio",
     minval = 1.0,
     step = 0.05,
     group = scoringGroup)
float maximumNoiseFractionInput = input.float(
     0.50,
     "Gate: max W / J",
     minval = 0.0,
     step = 0.05,
     group = scoringGroup)
float minimumDenseFractionInput = input.float(
     0.20,
     "Gate: dense side / max row",
     minval = 0.0,
     maxval = 1.0,
     step = 0.05,
     group = scoringGroup)
float minimumRobustZInput = input.float(
     2.50,
     "Gate: minimum robust z",
     minval = 0.0,
     step = 0.25,
     group = scoringGroup)
int clusterRowsInput = input.int(
     2,
     "Cluster radius (rows)",
     minval = 0,
     maxval = 10,
     group = scoringGroup)
bool extendShelvesInput = input.bool(
     true,
     "Extend selected shelves to current bar",
     group = scoringGroup)

string displayGroup = "4. Display"
float histogramWidthInput = input.float(
     24.0,
     "Histogram width (% of range)",
     minval = 5.0,
     maxval = 50.0,
     group = displayGroup)
color rangeColorInput = input.color(
     #26c6da,
     "Range / histogram",
     group = displayGroup)
color shelfColorInput = input.color(
     #ffd54f,
     "Shelf candidates",
     group = displayGroup)


// ============================================================================
// CONSTANTS AND HELPERS
// ============================================================================

const int MINUTE_MS = 60000
const int PROFILE_BAR_MS = 15 * MINUTE_MS
const int GRID_ORIGIN_TICKS = 0
const int MAX_PROFILE_BINS = 400
const int SHELF_SIDE_ROWS = 3
const int MAX_RANKED_DIAGNOSTICS = 60

int profileEndExclusive = profileEndBarInput + PROFILE_BAR_MS
int sourceBarMs = int(
     timeframe.in_seconds(sourceTimeframeInput) * 1000)

priceToTicks(float price) =>
    na(price) ? na : int(math.round(price / syminfo.mintick))

binVolume(map<int, float> bins, int bin) =>
    bins.contains(bin) ? bins.get(bin) : 0.0

median3(float a, float b, float c) =>
    a + b + c -
     math.min(a, math.min(b, c)) -
     math.max(a, math.max(b, c))

mad3(float a, float b, float c, float center) =>
    median3(
         math.abs(a - center),
         math.abs(b - center),
         math.abs(c - center))

medianSorted(array<float> values) =>
    int count = values.size()
    float result = na
    if count > 0
        int middle = int(math.floor(count / 2.0))
        result := count % 2 == 1 ?
             values.get(middle) :
             math.avg(values.get(middle - 1), values.get(middle))
    result

appendReason(string existing, string addition) =>
    existing == "" ? addition : existing + "+" + addition

timeText(int value) =>
    na(value) ? "NA" :
     str.format_time(value, "yyyy-MM-dd HH:mm", syminfo.timezone)

floatText(float value) =>
    na(value) ? "NA" : str.tostring(value, "#.########")

clearLines(array<line> drawings) =>
    int count = drawings.size()
    if count > 0
        for index = 0 to count - 1
            line.delete(drawings.get(index))
    drawings.clear()

clearLabels(array<label> drawings) =>
    int count = drawings.size()
    if count > 0
        for index = 0 to count - 1
            label.delete(drawings.get(index))
    drawings.clear()

diagnosticRow(
     table target,
     int row,
     string key,
     string value,
     color background) =>
    table.cell(
         target, 0, row, key,
         text_color = color.silver,
         text_size = size.tiny,
         bgcolor = background,
         text_halign = text.align_left)
    table.cell(
         target, 1, row, value,
         text_color = color.white,
         text_size = size.tiny,
         bgcolor = background,
         text_halign = text.align_left)

boundedPut(
     map<int, float> bins,
     int key,
     float addedVolume,
     int currentMin,
     int currentMax) =>
    bool alreadyPresent = bins.contains(key)
    int candidateMin = na(currentMin) ? key : math.min(currentMin, key)
    int candidateMax = na(currentMax) ? key : math.max(currentMax, key)
    bool overflow = candidateMax - candidateMin + 1 >
         MAX_PROFILE_BINS or
         (not alreadyPresent and bins.size() >= MAX_PROFILE_BINS)
    int nextMin = currentMin
    int nextMax = currentMax
    if not overflow
        bins.put(key, binVolume(bins, key) + addedVolume)
        nextMin := candidateMin
        nextMax := candidateMax
    [nextMin, nextMax, overflow]


// ============================================================================
// FIXED LOWER-TIMEFRAME BAR REQUEST
// ============================================================================

sourcePayload() =>
    [time,
     priceToTicks(high),
     priceToTicks(low),
     priceToTicks(close),
     volume]

[sourceTimes,
 sourceHighTicks,
 sourceLowTicks,
 sourceCloseTicks,
 sourceVolumes] = request.security_lower_tf(
     syminfo.tickerid,
     sourceTimeframeInput,
     sourcePayload(),
     ignore_invalid_timeframe = true)

bool chartSupported = chart.is_standard and
     timeframe.isminutes and
     timeframe.in_seconds() == 15 * 60 and
     timeframe.in_seconds(sourceTimeframeInput) <= 15 * 60
bool inputWindowValid = profileStartInput < profileEndBarInput and
     profileStartInput % MINUTE_MS == 0 and
     profileEndBarInput % MINUTE_MS == 0

var map<int, float> profileBins = map.new<int, float>()
var int profileMinBin = na
var int profileMaxBin = na
var float sourceTotalVolume = 0.0
var float allocatedTotalVolume = 0.0
var int includedSourceBars = 0
var int firstIncludedSourceTime = na
var int lastIncludedSourceTime = na
var int previousIncludedSourceTime = na
var int invalidSourceBars = 0
var bool sourceArrayIssue = false
var bool sourceOrderIssue = false
var bool profileOverflow = false

if chartSupported and inputWindowValid and barstate.isconfirmed
    int timeCount = sourceTimes.size()
    int highCount = sourceHighTicks.size()
    int lowCount = sourceLowTicks.size()
    int closeCount = sourceCloseTicks.size()
    int volumeCount = sourceVolumes.size()
    bool lengthsMatch = timeCount == highCount and
         timeCount == lowCount and
         timeCount == closeCount and
         timeCount == volumeCount
    sourceArrayIssue := sourceArrayIssue or not lengthsMatch
    int safeCount = math.min(
         math.min(timeCount, highCount),
         math.min(
             lowCount,
             math.min(closeCount, volumeCount)))

    if safeCount > 0
        for sourceIndex = 0 to safeCount - 1
            int sourceTime = sourceTimes.get(sourceIndex)
            int sourceHigh = sourceHighTicks.get(sourceIndex)
            int sourceLow = sourceLowTicks.get(sourceIndex)
            int sourceClose = sourceCloseTicks.get(sourceIndex)
            float sourceVolume = sourceVolumes.get(sourceIndex)
            bool useForProfile = not na(sourceTime) and
                 sourceTime >= profileStartInput and
                 sourceTime < profileEndExclusive

            if useForProfile
                firstIncludedSourceTime :=
                     includedSourceBars == 0 ?
                     sourceTime : firstIncludedSourceTime
                sourceOrderIssue := sourceOrderIssue or
                     (not na(previousIncludedSourceTime) and
                      sourceTime <= previousIncludedSourceTime)
                previousIncludedSourceTime := sourceTime
                lastIncludedSourceTime := sourceTime
                includedSourceBars += 1

                bool sourceValid = not na(sourceHigh) and
                     not na(sourceLow) and
                     not na(sourceClose) and
                     not na(sourceVolume) and
                     sourceVolume >= 0.0 and
                     sourceHigh >= sourceLow and
                     sourceClose >= sourceLow and
                     sourceClose <= sourceHigh
                invalidSourceBars += sourceValid ? 0 : 1

                if sourceValid
                    sourceTotalVolume += sourceVolume

                    if allocationModeInput == "High-low overlap"
                        int sourceLowBin = int(math.floor(
                             (sourceLow - GRID_ORIGIN_TICKS) /
                             float(outputTicksInput)))
                        int sourceHighBin = int(math.floor(
                             (sourceHigh - GRID_ORIGIN_TICKS) /
                             float(outputTicksInput)))
                        int sourceTickCount =
                             sourceHigh - sourceLow + 1

                        // A flat bar has one traded tick and therefore gives
                        // all volume to its containing output row. Wider bars
                        // allocate by exact overlapping tick-price count.
                        for outputBin = sourceLowBin to sourceHighBin
                            int binLowTick =
                                 GRID_ORIGIN_TICKS +
                                 outputBin * outputTicksInput
                            int binHighTick =
                                 binLowTick + outputTicksInput - 1
                            int overlapLow =
                                 math.max(sourceLow, binLowTick)
                            int overlapHigh =
                                 math.min(sourceHigh, binHighTick)
                            int overlapTicks = math.max(
                                 0, overlapHigh - overlapLow + 1)
                            float allocatedVolume =
                                 sourceVolume *
                                 overlapTicks /
                                 sourceTickCount
                            if overlapTicks > 0
                                [nextMin, nextMax, putOverflow] =
                                     boundedPut(
                                         profileBins,
                                         outputBin,
                                         allocatedVolume,
                                         profileMinBin,
                                         profileMaxBin)
                                profileMinBin := nextMin
                                profileMaxBin := nextMax
                                profileOverflow :=
                                     profileOverflow or putOverflow
                                allocatedTotalVolume += allocatedVolume
                    else
                        int allocationTick =
                             allocationModeInput == "Close bin" ?
                             sourceClose :
                             int(math.round(
                                 (sourceHigh +
                                  sourceLow +
                                  sourceClose) / 3.0))
                        int outputBin = int(math.floor(
                             (allocationTick -
                              GRID_ORIGIN_TICKS) /
                             float(outputTicksInput)))
                        [nextMin, nextMax, putOverflow] =
                             boundedPut(
                                 profileBins,
                                 outputBin,
                                 sourceVolume,
                                 profileMinBin,
                                 profileMaxBin)
                        profileMinBin := nextMin
                        profileMaxBin := nextMax
                        profileOverflow :=
                             profileOverflow or putOverflow
                        allocatedTotalVolume += sourceVolume


// ============================================================================
// POC / CONTIGUOUS VALUE AREA
// ============================================================================

calculateProfile(
     map<int, float> bins,
     int minBin,
     int maxBin,
     float totalVolume) =>
    bool valid = not na(minBin) and not na(maxBin) and
         maxBin >= minBin and
         maxBin - minBin + 1 <= MAX_PROFILE_BINS and
         bins.size() > 0 and totalVolume > 0.0
    int pocBin = na
    int valueAreaLowBin = na
    int valueAreaHighBin = na
    float achievedPercent = na
    float maximumVolume = na

    if valid
        float weightedCenter2 = 0.0
        for bin = minBin to maxBin
            float volumeAtBin = binVolume(bins, bin)
            int center2 = 2 * (
                 GRID_ORIGIN_TICKS + bin * outputTicksInput) +
                 outputTicksInput
            weightedCenter2 += volumeAtBin * center2
            maximumVolume := na(maximumVolume) ?
                 volumeAtBin :
                 math.max(maximumVolume, volumeAtBin)

        float meanCenter2 = weightedCenter2 / totalVolume
        float bestDistance = na
        float tieTolerance = math.max(
             0.000000001,
             math.abs(maximumVolume) * 0.000000000001)
        for bin = minBin to maxBin
            float volumeAtBin = binVolume(bins, bin)
            if math.abs(volumeAtBin - maximumVolume) <= tieTolerance
                int center2 = 2 * (
                     GRID_ORIGIN_TICKS + bin * outputTicksInput) +
                     outputTicksInput
                float distance = math.abs(center2 - meanCenter2)
                if na(bestDistance) or
                 distance < bestDistance - 0.000000001 or
                 (math.abs(distance - bestDistance) <=
                  0.000000001 and bin < pocBin)
                    pocBin := bin
                    bestDistance := distance

        if not na(pocBin)
            valueAreaLowBin := pocBin
            valueAreaHighBin := pocBin
            float includedVolume = binVolume(bins, pocBin)
            float targetVolume =
                 totalVolume * valueAreaPercentInput / 100.0
            float tolerance = math.max(
                 0.000000001,
                 math.abs(totalVolume) * 0.000000000001)
            int steps = 0
            while includedVolume < targetVolume - tolerance and
             steps < MAX_PROFILE_BINS
                bool hasLower = valueAreaLowBin > minBin
                bool hasUpper = valueAreaHighBin < maxBin
                if not hasLower and not hasUpper
                    break
                int lowerCandidate = valueAreaLowBin - 1
                int upperCandidate = valueAreaHighBin + 1
                float lowerVolume = hasLower ?
                     binVolume(bins, lowerCandidate) : na
                float upperVolume = hasUpper ?
                     binVolume(bins, upperCandidate) : na
                bool chooseUpper = not hasLower
                if hasLower and hasUpper
                    chooseUpper :=
                         upperVolume > lowerVolume or
                         (upperVolume == lowerVolume and
                          math.abs(upperCandidate - pocBin) <=
                          math.abs(lowerCandidate - pocBin))
                float selectedVolume =
                     chooseUpper ? upperVolume : lowerVolume
                if includedVolume + selectedVolume >
                 targetVolume + tolerance
                    break
                if chooseUpper
                    valueAreaHighBin := upperCandidate
                else
                    valueAreaLowBin := lowerCandidate
                includedVolume += selectedVolume
                steps += 1
            achievedPercent :=
                 includedVolume / totalVolume * 100.0

    [valid and not na(pocBin),
     pocBin,
     valueAreaLowBin,
     valueAreaHighBin,
     achievedPercent,
     maximumVolume]


// ============================================================================
// DRAWING STATE
// ============================================================================

var box rangeBox = na
var line pocLine = na
var line valueAreaHighLine = na
var line valueAreaLowLine = na
var array<line> histogramLines = array.new_line()
var array<line> shelfLines = array.new_line()
var array<label> shelfLabels = array.new_label()
var table diagnosticTable = table.new(
     position.top_right,
     2,
     20,
     frame_color = color.new(color.gray, 35),
     frame_width = 1,
     border_color = color.new(color.gray, 70),
     border_width = 1)


// ============================================================================
// LAST-BAR PROFILE, SCORER, AND VISUALS
// ============================================================================

if barstate.islast
    if not na(rangeBox)
        box.delete(rangeBox)
        rangeBox := na
    if not na(pocLine)
        line.delete(pocLine)
        pocLine := na
    if not na(valueAreaHighLine)
        line.delete(valueAreaHighLine)
        valueAreaHighLine := na
    if not na(valueAreaLowLine)
        line.delete(valueAreaLowLine)
        valueAreaLowLine := na
    clearLines(histogramLines)
    clearLines(shelfLines)
    clearLabels(shelfLabels)

    float outlineHigh = na
    float outlineLow = na
    int scanBars = math.min(bar_index, 4999)
    for offset = 0 to scanBars
        if time[offset] >= profileStartInput and
         time[offset] < profileEndExclusive
            outlineHigh := na(outlineHigh) ?
                 high[offset] : math.max(outlineHigh, high[offset])
            outlineLow := na(outlineLow) ?
                 low[offset] : math.min(outlineLow, low[offset])
    if inputWindowValid and not na(outlineHigh)
        rangeBox := box.new(
             left = profileStartInput,
             top = outlineHigh,
             right = profileEndExclusive,
             bottom = outlineLow,
             xloc = xloc.bar_time,
             border_color = rangeColorInput,
             border_width = 2,
             border_style = line.style_dashed,
             bgcolor = color.new(rangeColorInput, 97))

    bool endpointsComplete =
         firstIncludedSourceTime == profileStartInput and
         lastIncludedSourceTime + sourceBarMs ==
         profileEndExclusive
    float reconciliationTolerance = math.max(
         0.000001,
         math.abs(sourceTotalVolume) * 0.000000001)
    bool reconciliationValid =
         math.abs(
             allocatedTotalVolume -
             sourceTotalVolume) <=
         reconciliationTolerance
    bool baseComplete = chartSupported and
         inputWindowValid and
         time >= profileEndExclusive and
         endpointsComplete and
         includedSourceBars > 0 and
         invalidSourceBars == 0 and
         not sourceArrayIssue and
         not sourceOrderIssue and
         not profileOverflow and
         reconciliationValid

    [algorithmValid,
     pocBin,
     valueAreaLowBin,
     valueAreaHighBin,
     achievedValueAreaPercent,
     maximumBinVolume] = calculateProfile(
         profileBins,
         profileMinBin,
         profileMaxBin,
         allocatedTotalVolume)
    bool profileValid = baseComplete and algorithmValid

    float pocPrice = profileValid ?
         (GRID_ORIGIN_TICKS + pocBin * outputTicksInput +
          outputTicksInput * 0.5) * syminfo.mintick : na
    float valueAreaLowPrice = profileValid ?
         (GRID_ORIGIN_TICKS +
          valueAreaLowBin * outputTicksInput) *
          syminfo.mintick : na
    float valueAreaHighPrice = profileValid ?
         (GRID_ORIGIN_TICKS +
          (valueAreaHighBin + 1) * outputTicksInput) *
          syminfo.mintick : na

    // Robust clean log-jump scorer: three rows below and above each boundary.
    array<int> rawBins = array.new_int()
    array<float> rawJumps = array.new_float()
    array<float> rawNoise = array.new_float()
    array<float> rawClean = array.new_float()
    array<float> rawRatios = array.new_float()
    array<float> rawDenseVolumes = array.new_float()
    array<int> rawDenseSigns = array.new_int()
    array<float> rawZ = array.new_float()
    array<float> positiveVolumes = array.new_float()
    float logEpsilon = na
    float cleanMedian = na
    float cleanMad = na

    if profileValid
        for bin = profileMinBin to profileMaxBin
            float volumeAtBin = binVolume(profileBins, bin)
            if volumeAtBin > 0.0
                positiveVolumes.push(volumeAtBin)
        positiveVolumes.sort(order.ascending)
        logEpsilon := math.max(
             medianSorted(positiveVolumes) * 0.01,
             0.000000001)

        int firstBoundary =
             profileMinBin + SHELF_SIDE_ROWS
        int lastBoundary =
             profileMaxBin - SHELF_SIDE_ROWS + 1
        if firstBoundary <= lastBoundary
            for boundary = firstBoundary to lastBoundary
                float l0 = math.log(
                     binVolume(profileBins, boundary - 3) +
                     logEpsilon)
                float l1 = math.log(
                     binVolume(profileBins, boundary - 2) +
                     logEpsilon)
                float l2 = math.log(
                     binVolume(profileBins, boundary - 1) +
                     logEpsilon)
                float r0 = math.log(
                     binVolume(profileBins, boundary) +
                     logEpsilon)
                float r1 = math.log(
                     binVolume(profileBins, boundary + 1) +
                     logEpsilon)
                float r2 = math.log(
                     binVolume(profileBins, boundary + 2) +
                     logEpsilon)
                float leftMedian = median3(l0, l1, l2)
                float rightMedian = median3(r0, r1, r2)
                float jump =
                     math.abs(rightMedian - leftMedian)
                float noise =
                     mad3(l0, l1, l2, leftMedian) +
                     mad3(r0, r1, r2, rightMedian)
                float cleanJump =
                     math.max(0.0, jump - noise)
                float denseVolume = math.max(
                     0.0,
                     math.exp(
                         math.max(leftMedian, rightMedian)) -
                     logEpsilon)
                rawBins.push(boundary)
                rawJumps.push(jump)
                rawNoise.push(noise)
                rawClean.push(cleanJump)
                rawRatios.push(math.exp(jump))
                rawDenseVolumes.push(denseVolume)
                rawDenseSigns.push(
                     rightMedian > leftMedian ? 1 :
                     rightMedian < leftMedian ? -1 : 0)

        if rawClean.size() > 0
            array<float> cleanSorted = rawClean.copy()
            cleanSorted.sort(order.ascending)
            cleanMedian := medianSorted(cleanSorted)
            array<float> deviations = array.new_float()
            for index = 0 to rawClean.size() - 1
                deviations.push(
                     math.abs(
                         rawClean.get(index) -
                         cleanMedian))
            deviations.sort(order.ascending)
            cleanMad := medianSorted(deviations)
            float robustScale = math.max(
                 1.4826 * cleanMad,
                 0.000000001)
            for index = 0 to rawClean.size() - 1
                rawZ.push(
                     (rawClean.get(index) -
                      cleanMedian) /
                     robustScale)

    array<int> rankedIndices = array.new_int()
    array<bool> consumed =
         array.new_bool(rawClean.size(), false)
    int rankLimit = math.min(
         rawClean.size(),
         MAX_RANKED_DIAGNOSTICS)
    if rankLimit > 0
        for rank = 0 to rankLimit - 1
            int bestIndex = -1
            float bestClean = na
            int bestBoundary = na
            for index = 0 to rawClean.size() - 1
                if not consumed.get(index)
                    float candidateClean =
                         rawClean.get(index)
                    int candidateBoundary =
                         rawBins.get(index)
                    if na(bestClean) or
                     candidateClean > bestClean or
                     (candidateClean == bestClean and
                      candidateBoundary < bestBoundary)
                        bestIndex := index
                        bestClean := candidateClean
                        bestBoundary := candidateBoundary
            if bestIndex >= 0
                consumed.set(bestIndex, true)
                rankedIndices.push(bestIndex)

    array<int> selectedBins = array.new_int()
    array<int> selectedRanks = array.new_int()
    if rankedIndices.size() > 0
        for rankedPosition = 0 to rankedIndices.size() - 1
            int index = rankedIndices.get(rankedPosition)
            string reason = ""
            if applyDiscoveryGatesInput
                if rawRatios.get(index) < minimumRatioInput
                    reason := appendReason(reason, "ratio")
                if rawNoise.get(index) >
                 maximumNoiseFractionInput *
                 rawJumps.get(index)
                    reason := appendReason(reason, "noise")
                if rawDenseVolumes.get(index) <
                 minimumDenseFractionInput *
                 maximumBinVolume
                    reason := appendReason(reason, "thin")
                if rawZ.get(index) < minimumRobustZInput
                    reason := appendReason(reason, "z")

            int boundary = rawBins.get(index)
            bool clustered = false
            if reason == "" and selectedBins.size() > 0
                for selectedIndex = 0 to selectedBins.size() - 1
                    if math.abs(
                         boundary -
                         selectedBins.get(selectedIndex)) <=
                         clusterRowsInput
                        clustered := true
                        break
            if reason == "" and not clustered and
             selectedBins.size() < shelfCountInput
                selectedBins.push(boundary)
                selectedRanks.push(rankedPosition + 1)

    if profileValid
        int histogramWidthMs = int(
             (profileEndExclusive - profileStartInput) *
             histogramWidthInput / 100.0)
        for bin = profileMinBin to profileMaxBin
            float volumeAtBin = binVolume(profileBins, bin)
            float relativeWidth =
                 maximumBinVolume > 0.0 ?
                 volumeAtBin / maximumBinVolume : 0.0
            float rowCenterPrice = (
                 GRID_ORIGIN_TICKS +
                 bin * outputTicksInput +
                 outputTicksInput * 0.5) *
                 syminfo.mintick
            line histogramLine = line.new(
                 profileEndExclusive -
                  int(histogramWidthMs * relativeWidth),
                 rowCenterPrice,
                 profileEndExclusive,
                 rowCenterPrice,
                 xloc = xloc.bar_time,
                 color = color.new(rangeColorInput, 65),
                 width = 2)
            histogramLines.push(histogramLine)

        pocLine := line.new(
             profileStartInput, pocPrice,
             profileEndExclusive, pocPrice,
             xloc = xloc.bar_time,
             color = color.orange,
             width = 2)
        valueAreaHighLine := line.new(
             profileStartInput, valueAreaHighPrice,
             profileEndExclusive, valueAreaHighPrice,
             xloc = xloc.bar_time,
             color = color.new(color.silver, 20),
             style = line.style_dotted)
        valueAreaLowLine := line.new(
             profileStartInput, valueAreaLowPrice,
             profileEndExclusive, valueAreaLowPrice,
             xloc = xloc.bar_time,
             color = color.new(color.silver, 20),
             style = line.style_dotted)

        int shelfEndTime =
             extendShelvesInput ?
             time : profileEndExclusive
        if selectedBins.size() > 0
            for selectedPosition = 0 to selectedBins.size() - 1
                int boundary =
                     selectedBins.get(selectedPosition)
                int rawIndex = -1
                for index = 0 to rawBins.size() - 1
                    if rawBins.get(index) == boundary
                        rawIndex := index
                        break
                if rawIndex >= 0
                    float shelfPrice = (
                         GRID_ORIGIN_TICKS +
                         boundary * outputTicksInput) *
                         syminfo.mintick
                    string denseSide =
                         rawDenseSigns.get(rawIndex) > 0 ?
                         "ABOVE" :
                         rawDenseSigns.get(rawIndex) < 0 ?
                         "BELOW" : "EVEN"
                    int transparency =
                         math.min(
                             70,
                             5 + selectedPosition * 8)
                    line shelfLine = line.new(
                         profileStartInput,
                         shelfPrice,
                         shelfEndTime,
                         shelfPrice,
                         xloc = xloc.bar_time,
                         color = color.new(
                             shelfColorInput,
                             transparency),
                         width =
                             selectedPosition < 2 ? 2 : 1,
                         style =
                             selectedPosition < 2 ?
                             line.style_solid :
                             line.style_dashed)
                    shelfLines.push(shelfLine)
                    string shelfLabelText =
                         "S" +
                         str.tostring(
                             selectedPosition + 1) +
                         " " +
                         str.tostring(
                             shelfPrice,
                             format.mintick) +
                         " | " +
                         str.tostring(
                             rawRatios.get(rawIndex),
                             "#.00") +
                         "x | C " +
                         str.tostring(
                             rawClean.get(rawIndex),
                             "#.000") +
                         " / z " +
                         str.tostring(
                             rawZ.get(rawIndex),
                             "#.00") +
                         " | dense " +
                         denseSide +
                         " | rank " +
                         str.tostring(
                             selectedRanks.get(
                                 selectedPosition))
                    label shelfLabel = label.new(
                         shelfEndTime,
                         shelfPrice,
                         shelfLabelText,
                         xloc = xloc.bar_time,
                         style = label.style_label_left,
                         color = color.new(
                             color.black,
                             78),
                         textcolor = color.new(
                             shelfColorInput,
                             transparency),
                         size = size.tiny)
                    shelfLabels.push(shelfLabel)

    string state =
         profileValid ? "VALID / EXPERIMENTAL" :
         not chartSupported ? "USE STANDARD 15m CHART" :
         not inputWindowValid ? "INVALID INPUT RANGE" :
         not endpointsComplete ? "ENDPOINT COVERAGE FAIL" :
         invalidSourceBars > 0 ? "INVALID SOURCE BAR" :
         sourceArrayIssue ? "SOURCE ARRAY FAIL" :
         sourceOrderIssue ? "SOURCE ORDER FAIL" :
         profileOverflow ? "PROFILE CAP FAIL" :
         not reconciliationValid ? "VOLUME RECONCILIATION FAIL" :
         "PROFILE ALGORITHM FAIL"

    color headerBg = color.new(#1565c0, 10)
    color rowBg = color.new(color.black, 20)
    color warningBg = color.new(#8d6e63, 15)
    diagnosticRow(
         diagnosticTable, 0,
         "VP RSD BAR 0.3.1", state, headerBg)
    diagnosticRow(
         diagnosticTable, 1,
         "Source timeframe",
         sourceTimeframeInput + "m bars", warningBg)
    diagnosticRow(
         diagnosticTable, 2,
         "Input start",
         timeText(profileStartInput), rowBg)
    diagnosticRow(
         diagnosticTable, 3,
         "Input end incl.",
         timeText(profileEndBarInput), rowBg)
    diagnosticRow(
         diagnosticTable, 4,
         "Source bars",
         str.tostring(includedSourceBars), rowBg)
    diagnosticRow(
         diagnosticTable, 5,
         "Invalid bars",
         str.tostring(invalidSourceBars), rowBg)
    diagnosticRow(
         diagnosticTable, 6,
         "Row size",
         str.tostring(outputTicksInput) +
         " ticks = " +
         str.tostring(
             outputTicksInput * syminfo.mintick,
             format.mintick),
         rowBg)
    diagnosticRow(
         diagnosticTable, 7,
         "Source volume",
         floatText(sourceTotalVolume), rowBg)
    diagnosticRow(
         diagnosticTable, 8,
         "Allocated / diff",
         floatText(allocatedTotalVolume) +
         " / " +
         floatText(
             allocatedTotalVolume -
             sourceTotalVolume),
         rowBg)
    diagnosticRow(
         diagnosticTable, 9,
         "Profile bins",
         profileValid ?
             str.tostring(
                 profileMaxBin -
                 profileMinBin + 1) :
             "NA",
         rowBg)
    diagnosticRow(
         diagnosticTable, 10,
         "POC",
         str.tostring(pocPrice, format.mintick),
         rowBg)
    diagnosticRow(
         diagnosticTable, 11,
         "VAL / VAH",
         str.tostring(
             valueAreaLowPrice,
             format.mintick) +
         " / " +
         str.tostring(
             valueAreaHighPrice,
             format.mintick),
         rowBg)
    diagnosticRow(
         diagnosticTable, 12,
         "VA achieved",
         str.tostring(
             achievedValueAreaPercent,
             "#.0") + "%",
         rowBg)
    diagnosticRow(
         diagnosticTable, 13,
         "log eps / C MAD",
         floatText(logEpsilon) +
         " / " +
         floatText(cleanMad),
         rowBg)
    diagnosticRow(
         diagnosticTable, 14,
         "Gates",
         applyDiscoveryGatesInput ?
             "ON (provisional)" :
             "OFF (raw rank)",
         rowBg)
    diagnosticRow(
         diagnosticTable, 15,
         "Selected / ranked",
         str.tostring(selectedBins.size()) +
         " / " +
         str.tostring(rankedIndices.size()),
         rowBg)
    diagnosticRow(
         diagnosticTable, 16,
         "Allocation",
         allocationModeInput,
         rowBg)
    diagnosticRow(
         diagnosticTable, 17,
         "Warning",
         "Native-like research; verify native FRVP",
         warningBg)
    diagnosticRow(
         diagnosticTable, 18,
         "Range default",
         "UTC-7; end bar 06:30 included",
         rowBg)
    diagnosticRow(
         diagnosticTable, 19,
         "Direction split",
         "not drawn; total-volume profile",
         rowBg)

Overview

VP RSD Native-Like Bar Profile Diagnostic approximates TradingView native fixed-range volume profiles over a specified time range using configurable source timeframes such as 1m, 3m, or 5m. The script supports volume allocation modes including close bin, HLC3 bin, and high-low overlap, while calculating value area targets and candidate volume shelf levels via discovery gates. It draws volume profile histograms and shelf candidate lines directly on the chart to assist in volume distribution research.

Details

Published by

345-strategies on GitHub

Code credit

TheStratSuite contributors, as written in the script's header

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

Volume, Support & resistance

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Topics

indicatorpine-scriptthestrattradingtradingviewprice-actionstratpine-script-librarythestrat-suite

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
Start swing bartimetimestamp("23 Jun 2026 12:45 -0700")profileStartInput
End swing bar (included)timetimestamp("14 Jul 2026 06:30 -0700")profileEndBarInput
Source timeframestring5sourceTimeframeInput
Allocation modestringClose binallocationModeInput
Ticks per rowint10outputTicksInput
Value Area target (%)float70.0valueAreaPercentInput
Selected shelf candidatesint8shelfCountInput
Apply provisional discovery gatesboolfalseapplyDiscoveryGatesInput
Gate: minimum ratiofloat1.75minimumRatioInput
Gate: max W / Jfloat0.50maximumNoiseFractionInput
Gate: dense side / max rowfloat0.20minimumDenseFractionInput
Gate: minimum robust zfloat2.50minimumRobustZInput
Show all 17 settings
SettingTypeDefaultVariable
Cluster radius (rows)int2clusterRowsInput
Extend selected shelves to current barbooltrueextendShelvesInput
Histogram width (% of range)float24.0histogramWidthInput
Range / histogramcolor#26c6darangeColorInput
Shelf candidatescolor#ffd54fshelfColorInput
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

This overlay indicator draws a fixed-range volume profile on the price chart, splitting traded activity into up and down volume at each price level. It also plots a sentiment profile, a developing point of control, and value-area high and low around a configur

Indicator v5 VolumeSupport & resistance
Updated 2 months ago · MIT GitHub repository of Volume Profile and Indicator by DGT

MGIC-tracker

by Allen1212
303 stars

The MGIC-tracker is a TradingView indicator that overlays ARK Invest trade records onto stock price charts. It plots buy and sell labels at the exact timestamp of each transaction, showing share volume and ETF weight. Users select a specific ETF type via setti

Indicator v4 Support & resistanceVolume
Updated 2 years ago · None stated GitHub repository of MGIC-tracker
218 stars

Comprehensive Volume Profile is an overlay indicator that calculates and plots volume distribution across specified time ranges such as daily sessions, weekly, monthly, or custom bar lengths. It identifies key structural price levels including the Point of Con

Indicator v6 VolumeSupport & resistance
Updated 3 months ago · None stated GitHub repository of Comprehensive Volume Profile

Moon Dev GEX Arrows

by moondevonyt
92 stars

This overlay indicator plots option strike walls and an optional session VWAP on the price chart for SPY or ES. It spaces major strikes by the selected instrument, draws a set number of strikes on each side, and marks wall-trap and sweep events after a cooldow

Indicator v5 Support & resistanceSessions & time
Updated 8 weeks ago · Custom licence GitHub repository of Moon Dev GEX Arrows

More indicators

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers