Indicator · Pine Script v6 · On the chart

Volume Strata [WavesUnchained]

by casoon

Volume Strata [WavesUnchained] is a fixed-range volume profile indicator that anchors to the right side of the price chart.

View on GitHub

GitHub stars9on the repository
Pine versionv6current
Lines of code479
Settings25inputs
LicenceMIT
Last update3 days ago27 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

479 lines of Pine Script v6 from indicators/money_flow/volume_strata/volume_strata.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

volume_strata.pine
//@version=6
// ============================================================================
// Volume Strata [WavesUnchained]
// Version: 1.10.1
// Author: WavesUnchained
// Build: 2026-10-12
// ============================================================================
// Fixed-range volume profile anchored to the right, with POC, VAH/VAL,
// HVN/LVN zones, Value Area fill, naked POC tracking, expanded context table,
// and alerts.
//
// Features:
//   ✓ Right-anchored bars — profile grows left into the candle range
//   ✓ Up / down volume split per row, value area highlighted (blue / yellow)
//   ✓ Profile source: Volume or Money Flow (volume × mid-price)
//   ✓ Point of Control (POC) – dashed line with right-side price label
//   ✓ Value Area High / Low (VAH / VAL) – dotted lines with labels
//   ✓ Value Area fill – light shaded zone between VAH and VAL across the lookback
//   ✓ HVN zones – horizontal bands with gradient color intensity (faint at threshold,
//                  strong at peak volume)
//   ✓ LVN zones – horizontal bands at low-volume nodes (local minima below mean − 1σ)
//   ✓ Naked POC tracking – previous POCs not yet retested, drawn until price touches them
//   ✓ Info table – levels, VA width, range, POC strength, up-vol %, total volume,
//                  shape (D/P/b/B), VA state, Virgin POC, developing POC, POC distance
//   ✓ Alerts – POC cross, VA entry/exit, VAH/VAL touch
//   ✓ Configurable label size (Tiny / Small / Normal)
//   ✓ All visual layers individually toggleable
//
// Data Contract:
//   Price:     REQUIRED   chart symbol
//   Volume:    REQUIRED   real trade volume — feed must deliver volume and
//                         syminfo.volumetype must not be "tick"; otherwise not available
//   OI:        NO
//   Reference: NO
//   Verdict:   Exchange-only
//
// ============================================================================

indicator('Volume Strata [WavesUnchained]', overlay=true, max_boxes_count=500, max_bars_back=501)

// Display flag for inputs — keeps the chart status line uncluttered
disp = display.all - display.status_line

// ── Inputs ────────────────────────────────────────────────────────────────────
var string G_RANGE   = 'Range'
var string G_COLORS  = 'Colors'
var string G_DISPLAY = 'Display'

bbars       = input.int  (150,  'Lookback Bars',             minval=1,  maxval=500,  group=G_RANGE, display=disp)
cnum        = input.int  (24,   'Rows',                      minval=5,  maxval=100,  group=G_RANGE, display=disp)
va_pct      = input.float(70.,  'Value Area %',              minval=0., maxval=100., group=G_RANGE, display=disp)
prof_offset = input.int  (150,  'Profile Offset (bars right)', minval=0, maxval=500, group=G_RANGE, display=disp)
npoc_min_atr = input.float(0.1, 'Naked POC Min Distance (ATR)', minval=0., step=0.05, group=G_RANGE, display=disp,
     tooltip='Minimum distance the POC must move (in ATR) before the previous level is tracked as a naked POC. Filters out noise-driven POC shifts.')
src_kind    = input.string('Volume', 'Profile Source',       options=['Volume', 'Money Flow'], group=G_RANGE, display=disp,
     tooltip='Volume: raw traded contracts/shares per row. Money Flow: volume × mid-price of the row — gives a notional dollar value perspective.')

poc_color   = input.color(#e53935,                    'POC',           inline='poc', group=G_COLORS, display=disp)
poc_width   = input.int  (2,                          'Width',         inline='poc', group=G_COLORS, minval=1, maxval=5, display=disp)
va_color    = input.color(color.new(#9e9e9e, 0),      'VAH / VAL',                  group=G_COLORS, display=disp)
hvn_color   = input.color(#ffd600,                    'HVN',                        group=G_COLORS, display=disp)
lvn_color   = input.color(#4dd0e1,                    'LVN',                        group=G_COLORS, display=disp)
vup_color   = input.color(color.new(#1e88e5, 30),'Value Area Up',                  group=G_COLORS, display=disp)
vdown_color = input.color(color.new(#fdd835, 30),'Value Area Down',                group=G_COLORS, display=disp)
up_color    = input.color(color.new(#1e88e5, 75),'Outside Up',                     group=G_COLORS, display=disp)
down_color  = input.color(color.new(#fdd835, 75),'Outside Down',                   group=G_COLORS, display=disp)
label_size_str = input.string('Small', 'Label Size',         options=['Tiny','Small','Normal'], group=G_COLORS, display=disp)

// String → size helper
f_gTS(string _t) =>
    switch _t
        'Tiny'   => size.tiny
        'Small'  => size.small
        'Normal' => size.normal
        => size.small
label_size = f_gTS(label_size_str)

show_profile    = input.bool(true, 'Show profile bars',     group=G_DISPLAY, display=disp)
show_poc        = input.bool(true, 'POC label',             group=G_DISPLAY, display=disp)
show_va         = input.bool(true, 'VAH / VAL',             group=G_DISPLAY, display=disp)
show_va_fill    = input.bool(true, 'Value Area fill',       group=G_DISPLAY, display=disp,
     tooltip='Light shaded zone between VAH and VAL across the lookback range — visualizes the fair value area at a glance.')
show_hvn        = input.bool(true, 'HVN zones',             group=G_DISPLAY, display=disp,
     tooltip='Horizontal shaded bands at high-volume node levels across the lookback range — strong reaction zones. Color intensity scales with relative volume.')
show_lvn        = input.bool(true, 'LVN zones',             group=G_DISPLAY, display=disp,
     tooltip='Horizontal shaded bands at low-volume node levels — fast-move zones, price tends to skip through.')
show_naked_poc  = input.bool(true, 'Naked POCs',            group=G_DISPLAY, display=disp,
     tooltip='Track previous POCs that have not been retested. Drawn as faint dashed lines; removed once price touches them. Tracks new naked POCs from script load.')
show_table      = input.bool(true, 'Info table',            group=G_DISPLAY, display=disp)

// ── Data validity ─────────────────────────────────────────────────────────────
// Echtes Handelsvolumen? Nur "tick" schließt es ausdrücklich aus (Anzahl
// Transaktionen, keine Menge). "n/a" heißt laut Referenz "kein Volumen ODER Art
// nicht angegeben" — die meisten Feeds außer Krypto melden das, auch Futures mit
// echten Kontrakten. Dort entscheidet, ob der Feed überhaupt Volumen liefert.
var bool feedHasVolume = false
if nz(volume) > 0
    feedHasVolume := true
bool volumeIsReal = syminfo.volumetype != "tick" and feedHasVolume

// ── Persistent drawing objects ────────────────────────────────────────────────
var vol_bars  = array.new_box(cnum * 2, na)
var hvn_bars  = array.new_box(cnum,     na)
var lvn_bars  = array.new_box(cnum,     na)
var box   va_fill_box  = na
var line  poc_line = na
var label poc_lbl  = na
var line  vah_line = na
var line  val_line = na
var label vah_lbl  = na
var label val_lbl  = na

// Naked POC tracking
var array<float> naked_pocs    = array.new<float>()
var array<line>  naked_poc_lns = array.new<line>()
NAKED_POC_MAX = 8

// Persisted levels (updated on barstate.islast, used by alertcondition)
var float poc_lvl_p  = na
var float vah_lvl_p  = na
var float val_lvl_p  = na
var float prev_poc_lvl = na

color TC = color.new(color.gray, 20)
color HD = color.new(color.gray, 90)
color SEP = color.new(color.gray, 80)
var table info_tbl = table.new(position.top_right, 2, 15,
     bgcolor=color.new(color.white, 5),
     border_color=color.new(color.gray, 60), border_width=1,
     frame_color=color.new(color.gray, 40), frame_width=1)

// Volume formatting helper
formatVol(float v) =>
    v >= 1e9 ? str.tostring(v / 1e9, '0.##') + 'B' : v >= 1e6 ? str.tostring(v / 1e6, '0.##') + 'M' : v >= 1e3 ? str.tostring(v / 1e3, '0.##') + 'K' : str.tostring(math.round(v))

// ── ATR (runs every bar for alert and table use) ───────────────────────────────
atr_14 = ta.atr(14)

// ── Naked POC touch check (runs every bar) ────────────────────────────────────
if show_naked_poc and array.size(naked_pocs) > 0
    int i = 0
    while i < array.size(naked_pocs)
        npoc = array.get(naked_pocs, i)
        if high >= npoc and low <= npoc
            array.remove(naked_pocs, i)
            line.delete(array.remove(naked_poc_lns, i))
        else
            i += 1

// ── Level grid ────────────────────────────────────────────────────────────────
top  = ta.highest(bbars)
bot  = ta.lowest (bbars)
rng  = math.max(top - bot, syminfo.mintick)
dist = rng / 500
step = rng / cnum

levels = array.new_float(cnum + 1)
for x = 0 to cnum
    array.set(levels, x, bot + step * x)

// ── Volume intersection helper ─────────────────────────────────────────────────
get_vol(y11, y12, y21, y22, height, vol) =>
    float result = 0.
    if height > 0
        overlap = math.max(math.min(math.max(y11, y12), math.max(y21, y22)) - math.max(math.min(y11, y12), math.min(y21, y22)), 0)
        result := nz(overlap * vol / height)
    result

// Kurzer Hinweis unten rechts; was fehlt, steht im Data Contract oben.
var table volWarn = table.new(position.bottom_right, 1, 1)
if barstate.islast and not volumeIsReal
    table.cell(volWarn, 0, 0, "Volume Strata · not available",
         bgcolor = color.new(color.orange, 20), text_color = color.white, text_size = size.small)

if barstate.islast and volumeIsReal
    // ── Accumulate volume per row ──────────────────────────────────────────────
    volumes = array.new_float(cnum * 2, 0.)
    for bars = 0 to bbars - 1
        body_top = math.max(close[bars], open[bars])
        body_bot = math.min(close[bars], open[bars])
        itsgreen = close[bars] >= open[bars]
        tw    = high[bars] - body_top
        bw    = body_bot   - low[bars]
        bd    = body_top   - body_bot
        denom = math.max(2 * tw + 2 * bw + bd, syminfo.mintick)
        bvol  = bd * volume[bars] / denom
        twvol = 2 * tw * volume[bars] / denom
        bwvol = 2 * bw * volume[bars] / denom
        for x = 0 to cnum - 1
            lx  = array.get(levels, x)
            lx1 = array.get(levels, x + 1)
            // Money Flow weighting: each row's contribution is scaled by row mid-price
            row_mid    = (lx + lx1) / 2
            mf_weight  = src_kind == 'Money Flow' ? row_mid : 1.0
            upv = ((itsgreen ? get_vol(lx, lx1, body_bot, body_top, bd, bvol) : 0.) +
                  get_vol(lx, lx1, body_top, high[bars], tw, twvol) / 2 +
                  get_vol(lx, lx1, body_bot, low[bars],  bw, bwvol) / 2) * mf_weight
            dnv = ((itsgreen ? 0. : get_vol(lx, lx1, body_bot, body_top, bd, bvol)) +
                  get_vol(lx, lx1, body_top, high[bars], tw, twvol) / 2 +
                  get_vol(lx, lx1, body_bot, low[bars],  bw, bwvol) / 2) * mf_weight
            array.set(volumes, x,        array.get(volumes, x)        + upv)
            array.set(volumes, x + cnum, array.get(volumes, x + cnum) + dnv)

    // ── Total per row, POC, and stats ──────────────────────────────────────────
    totalvols = array.new_float(cnum, 0.)
    total_up  = 0.
    for x = 0 to cnum - 1
        tv = array.get(volumes, x) + array.get(volumes, x + cnum)
        array.set(totalvols, x, tv)
        total_up += array.get(volumes, x)

    total_all = array.sum(totalvols)
    up_pct    = total_all > 0 ? total_up / total_all * 100 : 50.
    int poc   = array.indexof(totalvols, array.max(totalvols))

    // HVN / LVN thresholds
    vol_mean   = array.avg(totalvols)
    vol_stdev  = array.stdev(totalvols)
    hvn_thresh = vol_mean + vol_stdev
    lvn_thresh = math.max(0., vol_mean - vol_stdev)

    // ── Value area ─────────────────────────────────────────────────────────────
    totalmax = total_all * va_pct / 100.
    va_total = array.get(totalvols, poc)
    int va_up   = poc
    int va_down = poc
    for x = 0 to cnum - 1
        if va_total >= totalmax
            break
        uppervol = va_up   < cnum - 1 ? array.get(totalvols, va_up   + 1) : 0.
        lowervol = va_down > 0         ? array.get(totalvols, va_down - 1) : 0.
        if uppervol == 0. and lowervol == 0.
            break
        if uppervol >= lowervol
            va_total += uppervol
            va_up    += 1
        else
            va_total += lowervol
            va_down  -= 1

    // ── Scale volumes to bar widths ────────────────────────────────────────────
    maxvol = math.max(array.max(totalvols), 1)
    for x = 0 to cnum * 2 - 1
        array.set(volumes, x, array.get(volumes, x) * bbars / (3 * maxvol))

    // ── Key levels ─────────────────────────────────────────────────────────────
    poc_lvl = (array.get(levels, poc) + array.get(levels, poc + 1)) / 2
    vah_lvl = array.get(levels, va_up   + 1)
    val_lvl = array.get(levels, va_down)

    // ── Context: Virgin POC (not touched in last 5 bars) ───────────────────────
    poc_virgin = true
    for t = 0 to 4
        if high[t] >= poc_lvl and low[t] <= poc_lvl
            poc_virgin := false
            break
    vpoc_str = poc_virgin ? 'Virgin' : 'Touched'
    vpoc_col = poc_virgin ? #00c853 : TC

    // ── Context: Developing POC direction ──────────────────────────────────────
    poc_trend_str = na(prev_poc_lvl) ? '—' : poc_lvl > prev_poc_lvl ? '↑ Rising' : poc_lvl < prev_poc_lvl ? '↓ Falling' : '— Stable'
    poc_trend_col = na(prev_poc_lvl) ? TC : poc_lvl > prev_poc_lvl ? #00c853 : poc_lvl < prev_poc_lvl ? #d50000 : TC

    // ── Naked POC: when POC moves, the previous level is added as candidate ────
    // Only added if not touched on the current bar (otherwise it's already retested)
    npoc_already_touched = not na(prev_poc_lvl) and high >= prev_poc_lvl and low <= prev_poc_lvl
    npoc_min_dist = math.max(syminfo.mintick, npoc_min_atr * atr_14)
    if show_naked_poc and not na(prev_poc_lvl) and math.abs(poc_lvl - prev_poc_lvl) > npoc_min_dist and not npoc_already_touched
        if array.size(naked_pocs) >= NAKED_POC_MAX
            line.delete(array.shift(naked_poc_lns))
            array.shift(naked_pocs)
        array.push(naked_pocs, prev_poc_lvl)
        npoc_line = line.new(bar_index - bbars + 1, prev_poc_lvl, bar_index, prev_poc_lvl,
             extend=extend.right, color=color.new(poc_color, 50),
             width=1, style=line.style_dashed)
        array.push(naked_poc_lns, npoc_line)
    prev_poc_lvl := poc_lvl

    // ── Context: Value Area State ───────────────────────────────────────────────
    va_state     = close > vah_lvl ? 'Above VA' : close < val_lvl ? 'Below VA' : 'Inside VA'
    va_state_col = close > vah_lvl ? #00c853 : close < val_lvl ? #d50000 : TC

    // ── Context: POC Distance in ATR ───────────────────────────────────────────
    poc_dist_raw = atr_14 > 0 ? (close - poc_lvl) / atr_14 : 0.
    poc_dist_str = (poc_dist_raw >= 0 ? '+' : '') + str.tostring(math.round(poc_dist_raw, 2)) + ' ATR'
    poc_dist_col = poc_dist_raw >= 0 ? #00c853 : #d50000

    // ── Range, VA Width, POC Strength, Total Volume, Shape ─────────────────────
    range_str = str.tostring(math.round_to_mintick(rng))

    va_width      = vah_lvl - val_lvl
    va_width_pct  = rng > 0 ? va_width / rng * 100 : 0.
    va_width_str  = str.tostring(math.round(va_width_pct, 1)) + '%'
    va_width_col  = va_width_pct < 30 ? #00c853 : va_width_pct > 60 ? #ff6f00 : TC

    vol_at_poc    = array.get(totalvols, poc)
    poc_strength  = total_all > 0 ? vol_at_poc / total_all * 100 : 0.
    poc_str_str   = str.tostring(math.round(poc_strength, 1)) + '%'
    poc_str_col   = poc_strength >= 15 ? #00c853 : poc_strength <= 8 ? #d50000 : TC

    total_vol_str = formatVol(total_all)

    // Profile Shape: position of POC within range + number of separate HVN nodes
    // (a node = contiguous run of rows at/above the HVN threshold; a single smooth
    // peak spans several adjacent rows but is still one node)
    poc_pos = rng > 0 ? (poc_lvl - bot) / rng : 0.5
    hvn_nodes = 0
    bool in_hvn = false
    for x = 0 to cnum - 1
        bool row_hvn = array.get(totalvols, x) >= hvn_thresh
        if row_hvn and not in_hvn
            hvn_nodes += 1
        in_hvn := row_hvn
    shape_str = hvn_nodes >= 2 ? 'B (Double)' : poc_pos > 0.66 ? 'P (Accum)' : poc_pos < 0.34 ? 'b (Distri)' : 'D (Balanced)'
    shape_col = hvn_nodes >= 2 ? #ffd600       : poc_pos > 0.66 ? #00c853     : poc_pos < 0.34 ? #d50000      : TC

    // Persist levels for alert conditions
    poc_lvl_p := poc_lvl
    vah_lvl_p := vah_lvl
    val_lvl_p := val_lvl

    // ── Volume bars (right-anchored, growing left) ─────────────────────────────
    for x = 0 to cnum - 1
        box.delete(array.get(vol_bars, x))
        box.delete(array.get(vol_bars, x + cnum))
        if show_profile
            in_va   = x >= va_down and x <= va_up
            up_w    = math.round(array.get(volumes, x))
            dn_w    = math.round(array.get(volumes, x + cnum))
            lev_top = array.get(levels, x + 1) - dist
            lev_bot = array.get(levels, x)     + dist
            // up-volume on the right (innermost)
            array.set(vol_bars, x, box.new(
                 bar_index + prof_offset - up_w, lev_top, bar_index + prof_offset, lev_bot,
                 border_width=0, bgcolor=in_va ? vup_color : up_color))
            // down-volume extends further left
            array.set(vol_bars, x + cnum, box.new(
                 bar_index + prof_offset - up_w - dn_w, lev_top, bar_index + prof_offset - up_w, lev_bot,
                 border_width=0, bgcolor=in_va ? vdown_color : down_color))

    // ── HVN zones (extended horizontal bands across lookback range) ────────────
    // Color intensity scales with relative volume: faded just above threshold,
    // strong at the peak (POC).
    float hvn_max = array.max(totalvols)
    for x = 0 to cnum - 1
        box.delete(array.get(hvn_bars, x))
        lev_top = array.get(levels, x + 1) - dist
        lev_bot = array.get(levels, x)     + dist
        vol_x   = array.get(totalvols, x)
        if show_hvn and vol_x >= hvn_thresh
            // Gradient: from faint (88 transp) at threshold to strong (60 transp) at peak
            color hvn_grad = color.from_gradient(vol_x, hvn_thresh, hvn_max,
                 color.new(hvn_color, 88), color.new(hvn_color, 60))
            array.set(hvn_bars, x, box.new(
                 bar_index - bbars + 1, lev_top, bar_index, lev_bot,
                 border_width=0, bgcolor=hvn_grad))
        else
            array.set(hvn_bars, x, na)

    // ── LVN zones (local minima below mean − 1σ — fast-move zones) ─────────────
    for x = 0 to cnum - 1
        box.delete(array.get(lvn_bars, x))
        lev_top = array.get(levels, x + 1) - dist
        lev_bot = array.get(levels, x)     + dist
        vol_x   = array.get(totalvols, x)
        is_local_min = x > 0 and x < cnum - 1 and vol_x < array.get(totalvols, x - 1) and vol_x < array.get(totalvols, x + 1)
        if show_lvn and is_local_min and vol_x <= lvn_thresh
            array.set(lvn_bars, x, box.new(
                 bar_index - bbars + 1, lev_top, bar_index, lev_bot,
                 border_width=0, bgcolor=color.new(lvn_color, 88)))
        else
            array.set(lvn_bars, x, na)

    // ── Value Area fill (light zone between VAH and VAL) ───────────────────────
    box.delete(va_fill_box)
    if show_va_fill
        va_fill_box := box.new(bar_index - bbars + 1, vah_lvl, bar_index, val_lvl,
             border_width=0, bgcolor=color.new(va_color, 92))

    // ── POC ────────────────────────────────────────────────────────────────────
    line.delete(poc_line)
    poc_line := line.new(bar_index - bbars + 1, poc_lvl, bar_index, poc_lvl,
                         extend=extend.right, color=poc_color, width=poc_width,
                         style=line.style_dashed)
    label.delete(poc_lbl)
    if show_poc
        poc_lbl := label.new(bar_index + prof_offset + (show_hvn or show_lvn ? 5 : 2), poc_lvl,
                             text='POC  ' + str.tostring(math.round_to_mintick(poc_lvl)),
                             style=label.style_label_left,
                             color=poc_color, textcolor=color.white, size=label_size)

    // ── VAH / VAL ──────────────────────────────────────────────────────────────
    line.delete(vah_line)
    line.delete(val_line)
    label.delete(vah_lbl)
    label.delete(val_lbl)
    if show_va
        vah_line := line.new(bar_index - bbars + 1, vah_lvl, bar_index, vah_lvl,
                             extend=extend.right, color=va_color, width=1,
                             style=line.style_dotted)
        val_line := line.new(bar_index - bbars + 1, val_lvl, bar_index, val_lvl,
                             extend=extend.right, color=va_color, width=1,
                             style=line.style_dotted)
        int lbl_x = bar_index + prof_offset + (show_hvn or show_lvn ? 5 : 2)
        vah_lbl := label.new(lbl_x, vah_lvl,
                             text='VAH  ' + str.tostring(math.round_to_mintick(vah_lvl)),
                             style=label.style_label_left,
                             color=va_color, textcolor=color.white, size=label_size)
        val_lbl := label.new(lbl_x, val_lvl,
                             text='VAL  ' + str.tostring(math.round_to_mintick(val_lvl)),
                             style=label.style_label_left,
                             color=va_color, textcolor=color.white, size=label_size)

    // ── Info table ─────────────────────────────────────────────────────────────
    if show_table
        up_col = up_pct >= 55 ? #00c853 : up_pct <= 45 ? #d50000 : TC
        // Header
        table.cell(info_tbl, 0, 0, 'VOLUME STRATA', text_color=TC, bgcolor=HD, text_size=size.small)
        table.cell(info_tbl, 1, 0, '',               text_color=TC, bgcolor=HD, text_size=size.small)
        // Key levels
        table.cell(info_tbl, 0, 1, 'POC',    text_color=TC,       text_size=size.small)
        table.cell(info_tbl, 1, 1, str.tostring(math.round_to_mintick(poc_lvl)),
                   text_color=poc_color, text_size=size.small)
        table.cell(info_tbl, 0, 2, 'VAH',    text_color=TC,       text_size=size.small)
        table.cell(info_tbl, 1, 2, str.tostring(math.round_to_mintick(vah_lvl)),
                   text_color=TC, text_size=size.small)
        table.cell(info_tbl, 0, 3, 'VAL',    text_color=TC,       text_size=size.small)
        table.cell(info_tbl, 1, 3, str.tostring(math.round_to_mintick(val_lvl)),
                   text_color=TC, text_size=size.small)
        table.cell(info_tbl, 0, 4, 'VA Width', text_color=TC,       text_size=size.small)
        table.cell(info_tbl, 1, 4, va_width_str,
                   text_color=va_width_col, text_size=size.small)
        table.cell(info_tbl, 0, 5, 'Range',    text_color=TC,       text_size=size.small)
        table.cell(info_tbl, 1, 5, range_str,
                   text_color=TC, text_size=size.small)
        // Separator
        table.cell(info_tbl, 0, 6, '', bgcolor=SEP, text_size=size.small)
        table.cell(info_tbl, 1, 6, '', bgcolor=SEP, text_size=size.small)
        // Volume / structure
        table.cell(info_tbl, 0, 7, 'POC Str',  text_color=TC,       text_size=size.small)
        table.cell(info_tbl, 1, 7, poc_str_str, text_color=poc_str_col, text_size=size.small)
        table.cell(info_tbl, 0, 8, 'Up Vol',   text_color=TC,       text_size=size.small)
        table.cell(info_tbl, 1, 8, str.tostring(math.round(up_pct, 1)) + '%',
                   text_color=up_col, text_size=size.small)
        table.cell(info_tbl, 0, 9, 'Total Vol', text_color=TC,       text_size=size.small)
        table.cell(info_tbl, 1, 9, total_vol_str,
                   text_color=TC, text_size=size.small)
        table.cell(info_tbl, 0, 10, 'Shape',   text_color=TC,           text_size=size.small)
        table.cell(info_tbl, 1, 10, shape_str, text_color=shape_col,    text_size=size.small)
        // Context
        table.cell(info_tbl, 0, 11, 'VA State',  text_color=TC,           text_size=size.small)
        table.cell(info_tbl, 1, 11, va_state,    text_color=va_state_col, text_size=size.small)
        table.cell(info_tbl, 0, 12, 'POC',       text_color=TC,           text_size=size.small)
        table.cell(info_tbl, 1, 12, vpoc_str,    text_color=vpoc_col,     text_size=size.small)
        table.cell(info_tbl, 0, 13, 'Dev POC',   text_color=TC,           text_size=size.small)
        table.cell(info_tbl, 1, 13, poc_trend_str, text_color=poc_trend_col, text_size=size.small)
        table.cell(info_tbl, 0, 14, 'POC Dist',  text_color=TC,           text_size=size.small)
        table.cell(info_tbl, 1, 14, poc_dist_str, text_color=poc_dist_col, text_size=size.small)
    else
        table.clear(info_tbl, 0, 0, 1, 14)

// ── Alert conditions ──────────────────────────────────────────────────────────
bool alertsOnBarClose = input.bool(true, "Alerts only on bar close (confirmed)", group="Alerts")
bool alertOk = not alertsOnBarClose or barstate.isconfirmed

alertcondition(alertOk and (ta.cross(close, poc_lvl_p)),
     'VS: POC Cross', 'VST · POC CROSS · {{ticker}} {{interval}}')
alertcondition(alertOk and (close > vah_lvl_p and close[1] <= vah_lvl_p[1]),
     'VS: Exit VA (above)', 'VST · EXIT VA ABOVE · {{ticker}} {{interval}}')
alertcondition(alertOk and (close < val_lvl_p and close[1] >= val_lvl_p[1]),
     'VS: Exit VA (below)', 'VST · EXIT VA BELOW · {{ticker}} {{interval}}')
alertcondition(alertOk and (close <= vah_lvl_p and close[1] > vah_lvl_p[1]),
     'VS: Enter VA (from above)', 'VST · ENTER VA FROM ABOVE · {{ticker}} {{interval}}')
alertcondition(alertOk and (close >= val_lvl_p and close[1] < val_lvl_p[1]),
     'VS: Enter VA (from below)', 'VST · ENTER VA FROM BELOW · {{ticker}} {{interval}}')
alertcondition(alertOk and (high >= vah_lvl_p and low < vah_lvl_p and not (high[1] >= vah_lvl_p[1] and low[1] < vah_lvl_p[1])),
     'VS: VAH Touch', 'VST · VAH TOUCH · {{ticker}} {{interval}}')
alertcondition(alertOk and (high > val_lvl_p and low <= val_lvl_p and not (high[1] > val_lvl_p[1] and low[1] <= val_lvl_p[1])),
     'VS: VAL Touch', 'VST · VAL TOUCH · {{ticker}} {{interval}}')

Overview

Volume Strata [WavesUnchained] calculates volume or money flow distribution over a configurable lookback period to highlight the Point of Control (POC), Value Area High and Low (VAH/VAL), High Volume Nodes (HVN), Low Volume Nodes (LVN), and naked POCs that remain untested. The indicator renders horizontal profile bars, level lines, value area shading, and an info table directly on the main chart.

Details

Published by

casoon on GitHub

Draws

On the price chart

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

pine-scriptpinescripttechnical-analysistrading-indicatorstrading-strategiestradingview

Settings

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

SettingTypeDefaultVariable
Lookback Barsint150bbars
Rowsint24cnum
Value Area %float70.va_pct
Profile Offset (bars right)int150prof_offset
Naked POC Min Distance (ATR)float0.1npoc_min_atr
Profile SourcestringVolumesrc_kind
POCcolor#e53935poc_color
Widthint2poc_width
VAH / VALcolorcolor.new(#9e9e9e, 0)va_color
HVNcolor#ffd600hvn_color
LVNcolor#4dd0e1lvn_color
Value Area Upcolorcolor.new(#1e88e5, 30)vup_color
Show all 25 settings
SettingTypeDefaultVariable
Value Area Downcolorcolor.new(#fdd835, 30)vdown_color
Outside Upcolorcolor.new(#1e88e5, 75)up_color
Outside Downcolorcolor.new(#fdd835, 75)down_color
Label SizestringSmalllabel_size_str
Show profile barsbooltrueshow_profile
POC labelbooltrueshow_poc
VAH / VALbooltrueshow_va
Value Area fillbooltrueshow_va_fill
HVN zonesbooltrueshow_hvn
LVN zonesbooltrueshow_lvn
Naked POCsbooltrueshow_naked_poc
Info tablebooltrueshow_table
Alerts only on bar close (confirmed)booltruealertsOnBarClose
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

5 stars

This indicator builds a frozen volume profile over a fixed, half-open timestamp range and draws it on the price chart. It aggregates one-minute source bars into output rows sized by a chosen tick count, marks a configurable value-area percentage, and can plot

Indicator v6 VolumeSupport & resistance
Updated 4 days ago · MPL-2.0 GitHub repository of VP RSD Fixed Range Shelf Diagnostic
4 stars

This overlay indicator draws an on-chart depth-of-market table that splits volume at each price tick into estimated buy volume, sell volume, delta, and session volume. Growing text bars compare buyers and sellers, and recent bars approximate resting bid and as

Indicator v5 VolumeScreeners & dashboards
Updated 6 months ago · None stated GitHub repository of Custom DOM - Bookmap Style
4 stars

This indicator overlays an order flow intelligence suite directly on the price chart. It renders a liquidity heatmap across configured price zones, alongside an on-chart information panel displaying volume delta, cumulative volume delta, a momentum compass bas

Indicator v6 VolumeScreeners & dashboards
Updated 2 weeks ago · MIT GitHub repository of GHOST — OrderFlow Intelligence
4 stars

An overlay indicator that calculates multi-timeframe market conditions and displays them in an information panel on the price chart. It evaluates timeframe metrics such as 15-minute and 1-hour periods to monitor RSI values, support and resistance levels, and r

Indicator v5 Screeners & dashboardsOscillators & momentum Repaints
Updated 4 years ago · None stated GitHub repository of SnagProfit Logic 15M 1H

More Volume scripts

Related on ForexR

Trade Your Charts With a Regulated Broker

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

Compare brokers