Indicator · Pine Script v6 · On the chart

CVD - Cumulative Volume Delta (Chart)

by regalouisei· code credited to TradingView

This indicator calculates and plots cumulative volume delta directly on the price chart using lower timeframe intrabar data.

View on GitHub

GitHub stars2on the repository
Pine versionv6current
Lines of code502
Settings38inputs
LicenceNone statedfile: MPL-2.0
Last update8 months ago25 Feb 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

502 lines of Pine Script v6 from pinescript/editors_picks/cvd-cumulative-volume-delta-chart.pine, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

cvd-cumulative-volume-delta-chart.pine
// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © TradingView

//@version=6
indicator(
    "CVD - Cumulative Volume Delta (Chart)", "CVD Chart", true, format = format.volume, max_lines_count = 500,
    max_labels_count = 500, max_boxes_count = 500
)

// CVD - Cumulative Volume Delta (Chart)
// v3, 2026.01.09

// This code's style is based on the recommendations from the Pine Script User Manual's Style guide:
//    https://www.tradingview.com/pine-script-docs/writing/style-guide/



import PineCoders/Time/5 as PCtime
import PineCoders/lower_tf/5 as PCltf
import TradingView/ta/12 as TVta
import PineCoders/VisibleChart/5 as chart



//#region ———————————————————— Constants and inputs


// ————— Constants

int     MS_IN_MIN   = 60 * 1000
int     MS_IN_HOUR  = MS_IN_MIN  * 60
int     MS_IN_DAY   = MS_IN_HOUR * 24
string  LBL_TXT     = " \n \n \n \n \n \n \n \n \n "

// Default colors
color   LIME        = color.lime
color   PINK        = color.fuchsia
color   WHITE       = color.white
color   ORANGE      = color.orange
color   GRAY        = #808080ff
color   LIME_LINE   = color.new(LIME, 30)
color   LIME_BG     = color.new(LIME, 95)
color   LIME_HI     = color.new(LIME, 80)
color   PINK_LINE   = color.new(PINK, 30)
color   PINK_BG     = color.new(PINK, 95)
color   PINK_LO     = color.new(PINK, 80)
color   BG_DIV      = color.new(ORANGE, 90)
color   BG_RESETS   = color.new(GRAY, 90)

// Reset conditions
string  RST1 = "None"
string  RST2 = "On a stepped higher timeframe"
string  RST3 = "On a fixed higher timeframe..."
string  RST4 = "At a fixed time..."
string  RST5 = "At the beginning of the regular session"
string  RST6 = "At the first visible chart bar"
string  RST7 = "On trend changes..."

// Trend types
string  TR01 = "Supertrend"
string  TR02 = "Aroon"
string  TR03 = "Parabolic SAR"

// Volume delta calculation modes
string  VD01 = "Volume delta"
string  VD02 = "Volume delta percent"

// CVD line types
string  LN01 = "Line"
string  LN02 = "Histogram"

// Label sizes
string  LS01 = "tiny"
string  LS02 = "small"
string  LS03 = "normal"
string  LS04 = "large"
string  LS05 = "huge"

// LTF precision options
string LTF1  = "Covering most chart bars (least precise)"
string LTF2  = "Covering some chart bars (less precise)"
string LTF3  = "Covering less chart bars (more precise)"
string LTF4  = "Covering few chart bars (very precise)"
string LTF5  = "Covering the least chart bars (most precise)"
string LTF6  = "~12 intrabars per chart bar"
string LTF7  = "~24 intrabars per chart bar"
string LTF8  = "~50 intrabars per chart bar"
string LTF9  = "~100 intrabars per chart bar"
string LTF10 = "~250 intrabars per chart bar"

// Tooltips
string TT_RST = (
    "Specifies the reset behavior of the CVD calculation. When selecting one of the last three options, use the "
    + "inputs below to specify additional settings for the reset conditions."
)
string TT_RST_HTF = (
    "Specifies the higher timeframe to use for the 'On a fixed higher timeframe' CVD reset option. "
    + "The CVD calculation resets at the open of a bar on the specified timeframe."
)
string TT_RST_TIME = (
    "Hour: 0-23\nMinute: 0-59\nThese values specify the fixed time of day to use for the 'At a fixed time' CVD reset "
    + "option. The CVD calculation resets when the time of day is after or equal to the bar's opening time and "
    + "before the bar's closing time."
)
string TT_RST_TREND = (
    "These values control the settings of the trend indicator to use for the 'On trend changes' CVD reset option. "
    + "The CVD calculation resets when the indicator signals a change in trend direction.\n\n"
    + "The first field specifies the length for the Aroon indicator or the ATR length for the Supertrend indicator.\n\n"
    + "The second field specifies the ATR multiplier for the Supertrend indicator.\n\n"
    + "The Parabolic SAR uses predefined settings. Neither field affects it."
)
string TT_LINE = "Select the style and color of the CVD lines."
string TT_LTF  = (
    "Controls the number of intrabars that the indicator analyzes per chart bar. "
    + "Increasing the intrabars per chart bar enables more precise calculations. However, it also reduces the number "
    + "of chart bars that the indicator can analyze. "
    + "The first five options determine the requested lower timeframe based on a desired relative amount of chart bar "
    + "coverage. The last five determine the timeframe based on an approximate number of intrabars per chart bar."
)
string TT_CVD = (
    "If enabled, the indicator displays cumulative delta values summed from the reset point. "
    + "Otherwise, it displays the raw volume delta for each bar."
)
string TT_YSPCE  = "Scales the height of all oscillator zones using a percentage of the largest zone's height."


// ————— Inputs

string  resetInput              = input.string(RST2,        "CVD resets",                       inline = "00", options = [RST1, RST2, RST5, RST6, RST3, RST4, RST7], tooltip = TT_RST)
string  fixedTfInput            = input.timeframe("D",      "  Fixed higher timeframe:",        inline = "01", tooltip = TT_RST_HTF)
int     hourInput               = input.int(9,              "  Fixed time: Hour",               inline = "02", minval  = 0, maxval = 23)
int     minuteInput             = input.int(30,             "Minute",                           inline = "02", minval  = 0, maxval = 59, tooltip = TT_RST_TIME)
string  trendInput              = input.string(TR01,        "  Trend: ",                        inline = "03", options = [TR02, TR03, TR01])
int     trendPeriodInput        = input.int(14,             " Length",                          inline = "03", minval  = 2)
float   trendValue2Input        = input.float(3.0,          "",                                 inline = "03", minval  = 0.25, step = 0.25, tooltip = TT_RST_TREND)
string  ltfModeInput            = input.string(LTF3,        "Intrabar precision",               inline = "04", options = [LTF1, LTF2, LTF3, LTF4, LTF5, LTF6, LTF7, LTF8, LTF9, LTF10], tooltip = TT_LTF)
string  vdCalcModeInput         = input.string(VD01,        "Volume delta calculation",         inline = "05", options = [VD01, VD02])
bool    cumulativeInput         = input.bool(true,          "Cumulative values",                inline = "06", tooltip = TT_CVD)

string  GRP1                    = "Visuals"
string  lineTypeInput           = input.string(LN01,        "CVD",                              inline = "00", group = GRP1, options = [LN01, LN02])
color   upColorInput            = input.color(LIME_LINE,    "🡓",                                inline = "01", group = GRP1)
color   dnColorInput            = input.color(PINK_LINE,    "🡓",                                inline = "01", group = GRP1, tooltip = TT_LINE)
float   percentYRangeInput      = input.float(100,          "Zone height (%)",                  inline = "02", group = GRP1, tooltip = TT_YSPCE) / 100.0
bool    showBgAreaInput         = input.bool(true,          "  Color background area ",         inline = "05", group = GRP1)
color   upBgColorInput          = input.color(LIME_BG,      "🡑",                                inline = "05", group = GRP1)
color   dnBgColorInput          = input.color(PINK_BG,      "🡓",                                inline = "05", group = GRP1)
bool    showHiLoInput           = input.bool(true,          "  Hi/Lo lines ",                   inline = "06", group = GRP1)
color   hiColorInput            = input.color(LIME_HI,      "🡑",                                inline = "06", group = GRP1)
color   loColorInput            = input.color(PINK_LO,      "🡓",                                inline = "06", group = GRP1)
bool    showZeroLineInput       = input.bool(true,          "  Zero line",                      inline = "07", group = GRP1)
color   zeroLineColorInput      = input.color(GRAY,         "🡓",                                inline = "07", group = GRP1)
bool    labelInput              = input.bool(true,          "Hi/Lo labels",                     inline = "03", group = GRP1)
string  labelSizeInput          = input.string(LS03,        "",                                 inline = "03", group = GRP1, options = [LS01, LS02, LS03, LS04, LS05])
bool    tooltipInput            = input.bool(true,          "Value tooltips",                   inline = "04", group = GRP1)
bool    colorDivBodiesInput     = input.bool(true,          "Color bars on divergences ",       inline = "08", group = GRP1)
color   upDivColorInput         = input.color(LIME,         "🡑",                                inline = "08", group = GRP1)
color   dnDivColorInput         = input.color(PINK,         "🡓",                                inline = "08", group = GRP1)
bool    bgDivInput              = input.bool(false,         "Color background on divergences ", inline = "09", group = GRP1)
color   bgDivColorInput         = input.color(BG_DIV,       "",                                 inline = "09", group = GRP1)
bool    bgResetInput            = input.bool(true,          "Color background on resets ",      inline = "10", group = GRP1)
color   bgResetColorInput       = input.color(BG_RESETS,    "",                                 inline = "10", group = GRP1)
bool    showInfoBoxInput        = input.bool(true,          "Show information box ",            inline = "11", group = GRP1)
string  infoBoxSizeInput        = input.string("small",     "Size ",                            inline = "12", group = GRP1, options = ["tiny", "small", "normal", "large", "huge", "auto"])
string  infoBoxYPosInput        = input.string("bottom",    "↕",                                inline = "12", group = GRP1, options = ["top", "middle", "bottom"])
string  infoBoxXPosInput        = input.string("right",     "↔",                                inline = "12", group = GRP1, options = ["left", "center", "right"])
color   infoBoxColorInput       = input.color(GRAY,         "",                                 inline = "12", group = GRP1)
color   infoBoxTxtColorInput    = input.color(WHITE,        "T",                                inline = "12", group = GRP1)
//#endregion



//#region ———————————————————— Functions


// @function            Categorizes volume as upward or downward based on price action.
// @returns             ([float, float]) A tuple containing the upward and downward volume values. The first value
//                      is the bar's volume if the volume is upward. Otherwise, the second value is the bar's volume.
//                      If a call to `request.security_lower_tf()` uses a call to this function as its `expression`
//                      argument, it returns a tuple of "float" arrays containing the upward and downward volume for
//                      each available intrabar in the chart bar.
upDnIntrabarVolumes() =>
    float upVol = 0.0
    float dnVol = 0.0
    switch
        close > open     => upVol += volume
        close < open     => dnVol -= volume
        close > close[1] => upVol += volume
        close < close[1] => dnVol -= volume
        upVol[1] > 0     => upVol += volume
        dnVol[1] < 0     => dnVol -= volume
        close == open    => upVol += volume
    [upVol, dnVol]


// @function            Selects a string representing a higher timeframe based on the chart's timeframe.
// @returns             (simple string) A timeframe string.
htfStep() =>
    int tfInMs = timeframe.in_seconds() * 1000
    string result = switch
        tfInMs <= MS_IN_MIN       => "60"
        tfInMs <  MS_IN_HOUR * 3  => "1D"
        tfInMs <= MS_IN_HOUR * 12 => "1W"
        tfInMs <  MS_IN_DAY  * 7  => "1M"
        =>                           "12M"


// @function            Determines whether an intraday bar opens before or at a specified time of day and closes after
//                      that time.
// @param hours         (series int) The hour of the day.
// @param minutes       (series int) The minute within the hour.
// @returns             (bool) `true` if the bar's span includes the specified time of day, and `false` otherwise.
timeReset(int hours, int minutes) =>
    int openTime = timestamp(year, month, dayofmonth, hours, minutes, 0)
    bool timeInBar = time <= openTime and time_close > openTime
    bool result = timeframe.isintraday and not timeInBar[1] and timeInBar


// @function            Scales a specified value within a given range to align with the scale of a new range.
// @param oldValue      (series float) The value to convert using the new scale.
// @param oldMin        (series float) The minimum value of the original range.
// @param oldMax        (series float) The maximum value of the original range.
// @param oldMin        (series float) The minimum value of the new range.
// @param oldMax        (series float) The maximum value of the new range.
// @returns             (float) A new value scaled to fit between the values of `newMin` and `newMax` based
//                      on the position of `oldValue` relative to the values of `oldMax` and `oldMin`.
scale(series float oldValue, series float oldMin, series float oldMax, series float newMin, series float newMax) =>
    float oldRange = oldMax - oldMin
    float newRange = newMax - newMin
    float newValue = (((oldValue - oldMin) * newRange) / oldRange) + newMin


// @function            Calculates a scaled with value for displayed histogram lines using a stepped, nonlinear curve
//                      based on a specified bar span.
// @param bars          (series int) The number of bars in the displayed range.
// @param maxWidth      (simple int) The maximum line width.
// @param decayLength   (simple int) Distance from 0 where the curve reaches its minimum value.
// @param offset        (series int) Offset of the curve along the x-axis.
// @returns             (int) A line width value that is nonlinearly scaled based on the specified number of bars.
scaleHistoWidth(series int bars, simple int maxWidth, simple int decayLength, simple int offset) =>
    int result = math.ceil(2 * maxWidth / (1 + math.exp(6 * (bars - offset) / decayLength)))
//#endregion



//#region ———————————————————— Calculations


// @variable A string representing the lower timeframe for which to retrieve intrabar data.
var string ltfString = PCltf.ltf(ltfModeInput, LTF1, LTF2, LTF3, LTF4, LTF5, LTF6, LTF7, LTF8, LTF9, LTF10)

// Retrieve the IDs of arrays containing upward and downward volume for each available intrabar in the current bar.
[upVolumes, dnVolumes] = request.security_lower_tf(
    syminfo.tickerid, ltfString, upDnIntrabarVolumes(), calc_bars_count = 200000
)

// Create conditions for lines and labels from user inputs.
bool useVdPct = vdCalcModeInput == VD02
bool useHisto = lineTypeInput   == LN02

// Get visible chart information.
int  leftBar    = chart.leftBarIndex()
int  rightBar   = chart.rightBarIndex()
bool isVisible  = chart.barIsVisible()
bool isLastBar  = chart.isLastVisibleBar()
int  totalBars  = chart.bars()

// Calculate line width for histogram display.
int lineWidth = scaleHistoWidth(totalBars, 29, 391, -63)

// Calculate the maximum volumes, total volume, and volume delta values.
float totalUpVolume = nz(upVolumes.sum())
float totalDnVolume = nz(dnVolumes.sum())
float maxUpVolume   = nz(upVolumes.max())
float maxDnVolume   = nz(dnVolumes.min())
float totalVolume   = totalUpVolume - totalDnVolume
float delta         = totalUpVolume + totalDnVolume
float deltaPct      = delta / totalVolume
float barDelta      = useVdPct ? deltaPct : delta

// Declare variables to track CVD reset information.
[reset, hasTrendDirection, trendIsUp, resetDescription] = switch resetInput
    RST1 => [false, false, false, "No resets"]
    RST2 => [timeframe.change(htfStep()), false, false, "Resets every " + htfStep()]
    RST3 => [timeframe.change(fixedTfInput), false, false, "Resets every " + fixedTfInput]
    RST4 => [
         timeReset(hourInput, minuteInput), false, false,
         str.format("Resets at {0,number,00}:{1,number,00}", hourInput, minuteInput)
     ]
    RST5 => [session.isfirstbar_regular, false, false, "Resets at the beginning of the session"]
    RST6 => [time == chart.left_visible_bar_time, false, false, "Resets at the beginning of visible bars"]
    RST7 =>
        switch trendInput
            TR01 =>
                [_, direction] = ta.supertrend(trendValue2Input, trendPeriodInput)
                [ta.change(direction, 1) != 0, true, direction == -1, "Resets on Supertrend changes"]
            TR02 =>
                [up, dn] = TVta.aroon(trendPeriodInput)
                [ta.cross(up, dn), true, ta.crossover(up, dn), "Resets on Aroon changes"]
            TR03 =>
                float psar = ta.sar(0.02, 0.02, 0.2)
                [ta.cross(psar, close), true, ta.crossunder(psar, close), "Resets on PSAR changes"]
    => [false, false, false, na]

// Get the number of intrabars per chart bar and calculate the cumulative average.
int intrabars = upVolumes.size()
int chartBarsCovered = int(ta.cum(math.sign(intrabars)))
float avgIntrabars = ta.cum(intrabars) / chartBarsCovered

// Detect divergences between volume delta and the bar's polarity.
bool divergence = delta != 0 and math.sign(delta) != math.sign(close - open)

// Declare variables to manage data for the CVD display.
var array<float> segCvdValues = array.new<float>()
var array<float> rawCvdValues = array.new<float>()
var array<float> barCenters   = array.new<float>()
var array<float> zeroLevels   = array.new<float>()
var array<float> priceRanges  = array.new<float>()
var array<float> highCvd      = array.new<float>()
var array<float> lowCvd       = array.new<float>()
var array<int>   resetBars    = array.new<int>()
var float priceHi = high
var float priceLo = low
var float cvd     = 0.0
priceHi := isVisible ? math.max(priceHi, high) : high
priceLo := isVisible ? math.min(priceLo, low)  : low

// Update data for new visible segments on a reset bar or the last bar.
if reset or isLastBar
    if isVisible
        segCvdValues.push(cumulativeInput ? cvd + barDelta : barDelta)
        resetBars.push(bar_index)
        zeroLevels.push(math.avg(priceHi, priceLo) - (priceHi - priceLo))
        priceRanges.push((priceHi - priceLo) / 2)
        highCvd.push(segCvdValues.max())
        lowCvd.push(segCvdValues.min())
        barCenters.push(hl2)
        rawCvdValues.concat(segCvdValues)
        segCvdValues.clear()
    priceHi := high
    priceLo := low
    cvd     := reset ? 0 : cvd

// Track the previous CVD value and calculate the new value.
float cvdO = cvd
cvd += barDelta

// Logic to create drawings on the last available bar to display CVD data on the chart.
if isLastBar
    // Initialize `startBar` to the leftmost bar, then find the highest range value and greatest absolute CVD Value.
    int startBar = leftBar
    int step = 0
    float yRangeMax = priceRanges.max() * percentYRangeInput
    float highScale = useVdPct and not cumulativeInput ? 1 : array.max(rawCvdValues.abs())
    // Loop to create the scaled drawings.
    for [i, resetBar] in resetBars
        // Find the segment's zero level, vertical range, and high/low CVD, and calculate an adjusted range.
        float zero      = zeroLevels.get(i)
        float yRange    = priceRanges.get(i)
        float highValue = highCvd.get(i)
        float lowValue  = lowCvd.get(i)
        float highLevel = scale(highValue, 0, highScale, zero, zero + yRangeMax)
        float offset    = zero + yRange - (useHisto ? math.max(highLevel, zero) : highLevel)
        float zeroLevel = zero + offset
        float hiLevel   = zeroLevel + yRangeMax
        float loLevel   = zeroLevel - yRangeMax
        // Render the zero line, high and low boundaries, and the background fill based on the user's selection.
        if showZeroLineInput
            line.new(startBar, zeroLevel, resetBar, zeroLevel, color = zeroLineColorInput, style = line.style_dashed)
        if showHiLoInput
            line.new(startBar, hiLevel, resetBar, hiLevel, color = hiColorInput)
            line.new(startBar, loLevel, resetBar, loLevel, color = loColorInput)
        if showBgAreaInput
            box.new(startBar, hiLevel, resetBar, zeroLevel, border_color = color(na), bgcolor = upBgColorInput)
            box.new(startBar, loLevel, resetBar, zeroLevel, border_color = color(na), bgcolor = dnBgColorInput)
        // Declare variables to track bar position, last price, and label location information.
        int bar = startBar
        float lastPriceValue = na
        bool placeHigh = true
        bool placeLow  = true
        // For each CVD value in the segment, scale the value, then determine the corresponding line color and label text.
        for x = step to resetBar - startBar + step
            // For each CVD value in the segment, calculate and scale price levels. Determine line color and label text.
            float  cvdValue  = rawCvdValues.get(x)
            float  eachCvd   = cvdValue != 0 ? cvdValue : na
            float  priceLvl  = scale(eachCvd, 0, highScale, zero, zero + yRangeMax) + offset
            color  lineColor = priceLvl > zeroLevel ? upColorInput : dnColorInput
            string labelStr  = (
                labelInput ? useVdPct  ?
                str.format("{0, number, percent}", eachCvd) : str.tostring(eachCvd, format.volume) : string(na)
            )
            bool   isHiCvd   = eachCvd == highValue
            bool   isLoCvd   = eachCvd == lowValue
            // Draw a label for the highest or lowest CVD in the segment if "Hi/Lo labels" is enabled.
            if labelInput and (isHiCvd or isLoCvd)
                bool   drawHiLbl  = isHiCvd and placeHigh and bar != leftBar
                bool   drawLoLbl  = isLoCvd and placeLow  and bar != leftBar
                string labelStyle = isHiCvd  ? label.style_label_down : label.style_label_up
                float  labelPrice = (
                    useHisto ? highValue < 0 and isHiCvd or lowValue > 0 and isLoCvd ? zeroLevel : priceLvl : priceLvl
                )
                if drawHiLbl or drawLoLbl
                    label.new(
                        bar, labelPrice, labelStr, color = color(na), style = labelStyle, textcolor = lineColor,
                        size = labelSizeInput
                    )
                    placeHigh := drawHiLbl ? false : placeHigh
                    placeLow  := drawLoLbl ? false : placeLow
            // Draw an invisible label with a CVD value tooltip over the chart bar if "Value tooltips" is enabled.
            if tooltipInput
                label.new(
                    bar, barCenters.get(x), LBL_TXT, color = color(na), size = size.tiny,
                    style = label.style_label_center, tooltip = labelStr
                )
            // Draw a line for the CVD value on each bar, using the current and previous bar values for line display,
            // or the current bar and zero level for histogram display.
            switch
                useHisto       => line.new(bar, zeroLevel, bar, priceLvl, color = lineColor, width = lineWidth)
                bar > startBar => line.new(bar - 1, lastPriceValue, bar, priceLvl, color = lineColor, width = 2)
            // Increment `bar` by one, and set the last price value to the value of `priceLvl`.
            bar += 1
            lastPriceValue := priceLvl
        // Increment the step counter by the number of bars in the current segment after the inner loop ends.
        // Set the `startBar` to store the current `resetBar` value for the next iteration.
        step += resetBar - startBar + 1
        startBar := resetBar

// Store necessary values for visible bars.
if isVisible
    segCvdValues.push(cumulativeInput ? cvd : barDelta)
    barCenters.push(hl2)
//#endregion



//#region ———————————————————— Visuals


color candleColor = colorDivBodiesInput and divergence ? delta > 0 ? upDivColorInput : dnDivColorInput : na

// Display key values in the Data Window.
displayDataWindow       = display.data_window
plot(delta,             "Volume delta for the bar", candleColor,    display = displayDataWindow)
plot(totalUpVolume,     "Up volume for the bar",    upColorInput,   display = displayDataWindow)
plot(totalDnVolume,     "Dn volume for the bar",    dnColorInput,   display = displayDataWindow)
plot(totalVolume,       "Total volume",                             display = displayDataWindow)
plot(na,                "═════════════════",                        display = displayDataWindow)
plot(cvdO,              "CVD before this bar",                      display = displayDataWindow)
plot(cvd,               "CVD after this bar",                       display = displayDataWindow)
plot(maxUpVolume,       "Max intrabar up volume",   upColorInput,   display = displayDataWindow)
plot(maxDnVolume,       "Max intrabar dn volume",   dnColorInput,   display = displayDataWindow)
plot(intrabars,         "Intrabars in this bar",                    display = displayDataWindow)
plot(avgIntrabars,      "Average intrabars",                        display = displayDataWindow)
plot(chartBarsCovered,  "Chart bars covered",                       display = displayDataWindow)
plot(bar_index + 1,     "Chart bars",                               display = displayDataWindow)
plot(na,                "═════════════════",                        display = displayDataWindow)
plot(totalBars,         "Total visible bars",                       display = displayDataWindow)
plot(leftBar,           "First visible bar index",                  display = displayDataWindow)
plot(rightBar,          "Last visible bar index",                   display = displayDataWindow)
plot(na,                "═════════════════",                        display = displayDataWindow)

// Plot an up or down arrow when a reset occurs based on trend changes.
plotchar(hasTrendDirection and reset and trendIsUp,     "Up trend", "▲", location.top, upColorInput)
plotchar(hasTrendDirection and reset and not trendIsUp, "Dn trend", "▼", location.top, dnColorInput)

// Color the background on bars where resets or divergences occur.
bgcolor(bgResetInput and reset ? bgResetColorInput : bgDivInput and divergence ? bgDivColorInput : na)
barcolor(candleColor)

// Create a table to display intrabar information on the last historical bar.
if showInfoBoxInput and barstate.islastconfirmedhistory
    var table infoBox = table.new(infoBoxYPosInput + "_" + infoBoxXPosInput, 1, 1)
    color infoBoxBgColor = infoBoxColorInput
    string txt = str.format(
      "{0}\nUses intrabars at {1}\nAvg intrabars per chart bar: {2,number,#.##}\n"
      + "Chart bars covered: {3} / {4} ({5,number,percent})",
      resetDescription, PCtime.formattedNoOfPeriods(timeframe.in_seconds(ltfString) * 1000),
      avgIntrabars, chartBarsCovered, bar_index + 1, chartBarsCovered / (bar_index + 1))
    if avgIntrabars < 5
        txt += "\nThis quantity of intrabars is dangerously small.\nResults will not be as reliable with so few."
        infoBoxBgColor := color.red
    table.cell(
        infoBox, 0, 0, txt, text_color = infoBoxTxtColorInput, text_size = infoBoxSizeInput, bgcolor = infoBoxBgColor
    )
//#endregion



//#region ———————————————————— Errors


if resetInput == RST3 and timeframe.in_seconds(fixedTfInput) <= timeframe.in_seconds()
    runtime.error("The higher timeframe for resets must be greater than the chart's timeframe.")
else if resetInput == RST4 and not timeframe.isintraday
    runtime.error("Resets at a fixed time work on intraday charts only.")
else if resetInput == RST5 and not timeframe.isintraday
    runtime.error("Resets at the begining of session work on intraday charts only.")
else if ta.cum(totalVolume) == 0 and barstate.islast
    runtime.error("No volume is provided by the data vendor.")
else if ta.cum(intrabars) == 0 and barstate.islast
    runtime.error("No intrabar information exists at the '" + ltfString + "' timeframe.")
//#endregion

Overview

CVD - Cumulative Volume Delta (Chart) displays volume delta values as lines or histograms, tracking volume flow across customizable reset intervals such as fixed sessions, higher timeframes, or trend changes. The script signals volume divergences by coloring candle bodies and the chart background. Key settings control lower timeframe precision, calculation modes, high and low boundary lines, and reset conditions. Being an indicator, it provides visual volume analysis without generating automated trade orders or managing positions.

Details

Published by

regalouisei on GitHub

Code credit

TradingView, as written in the script's header

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

Volume, Divergence

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.

Settings

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

SettingTypeDefaultVariable
CVD resetsstringRST2resetInput
  Fixed higher timeframe:timeframeDfixedTfInput
  Fixed time: Hourint9hourInput
Minuteint30minuteInput
  Trend: stringTR01trendInput
Lengthint14trendPeriodInput
trendValue2Inputfloat3.0trendValue2Input
Intrabar precisionstringLTF3ltfModeInput
Volume delta calculationstringVD01vdCalcModeInput
Cumulative valuesbooltruecumulativeInput
CVDstringLN01lineTypeInput
🡓colorLIME_LINEupColorInput
Show all 38 settings
SettingTypeDefaultVariable
🡓colorPINK_LINEdnColorInput
Zone height (%)float100percentYRangeInput
  Color background area booltrueshowBgAreaInput
🡑colorLIME_BGupBgColorInput
🡓colorPINK_BGdnBgColorInput
  Hi/Lo lines booltrueshowHiLoInput
🡑colorLIME_HIhiColorInput
🡓colorPINK_LOloColorInput
  Zero linebooltrueshowZeroLineInput
🡓colorGRAYzeroLineColorInput
Hi/Lo labelsbooltruelabelInput
labelSizeInputstringLS03labelSizeInput
Value tooltipsbooltruetooltipInput
Color bars on divergences booltruecolorDivBodiesInput
🡑colorLIMEupDivColorInput
🡓colorPINKdnDivColorInput
Color background on divergences boolfalsebgDivInput
bgDivColorInputcolorBG_DIVbgDivColorInput
Color background on resets booltruebgResetInput
bgResetColorInputcolorBG_RESETSbgResetColorInput
Show information box booltrueshowInfoBoxInput
Size stringsmallinfoBoxSizeInput
↕stringbottominfoBoxYPosInput
↔stringrightinfoBoxXPosInput
infoBoxColorInputcolorGRAYinfoBoxColorInput
TcolorWHITEinfoBoxTxtColorInput
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

257 stars

This overlay indicator looks for regular price divergences against a set of oscillators, using pivot highs and lows. It compares those pivots with RSI, MACD and its histogram, stochastic, CCI, momentum, OBV, a directional oscillator, volume-weighted MACD, Chai

Indicator v5 DivergenceOscillators & momentum Repaints
Updated 6 weeks ago · None stated GitHub repository of Snag - Best Futures V3

Price Divergence

by capissimo
114 stars

This indicator detects regular and hidden divergences across multiple technical factors plotted directly on the price chart. It evaluates price highs and lows against peaks and troughs identified by a five-bar fractal mechanism on RSI, RSI Stochastic, MACD, re

Indicator v4 DivergenceOscillators & momentum
Updated 1 year ago · None stated GitHub repository of Price Divergence

RSI Div at dVWAP StDev

by cryptorife
75 stars

This indicator marks RSI divergences only when price trades beyond daily VWAP standard-deviation bands. It compares recent RSI extremes with price highs and lows over a lookback window, and it treats a divergence as valid only while RSI stays beyond overbought

Indicator v4 DivergenceOscillators & momentum Repaints
Updated 3 years ago · GPL-3.0 GitHub repository of RSI Div at dVWAP StDev

This indicator builds a momentum score from the rate of change of close, using a lookback that expands or contracts with ATR relative to its standard deviation. It divides that rate of change by price standard deviation, scales the result, and weights it by vo

Indicator v5 Oscillators & momentumVolatility & bands Repaints
Updated 7 weeks ago · None stated GitHub repository of Adaptive Volatility-Adjusted Momentum Score

Nexural QWAP

by JasonTeixeira
9 stars

Nexural QWAP calculates a volume-weighted average price combined with a cumulative volume delta order flow engine directly on the main price chart. It analyzes market structure by detecting volume absorption, liquidity sweeps, price-CVD divergence, stacked imb

Indicator v6 SMC / ICT / price actionVolume
Updated 12 days ago · Custom licence GitHub repository of Nexural QWAP

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