Indicator · Pine Script v5 · On the chart

Session & RTH VWAP

by TamaraKGreen· code credited to npg

Calculates and plots session Volume Weighted Average Price along with Regular Trading Hours VWAP, historical session tracking, and statistical deviation bands.

View on GitHub

GitHub stars46on the repository
Pine versionv5runs as is
Lines of code666
Settings37inputs
LicenceNone statedfile: MPL-2.0
Last update7 weeks ago4 Sep 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

666 lines of Pine Script v5 from Statistical VWAP study Session and RTH VWAP.txt, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

Statistical VWAP study Session and RTH VWAP.txt
// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © npg

//@version=5
indicator("Session & RTH VWAP", shorttitle = 'SV+RTH', overlay = true, max_bars_back = 5000)

var g0      = 'Sessions Settings'
tz          = input.string("GMT-4", "Time Zone", tooltip = "GMT and UTC is the same thing \nMatch this setting to bottom right time", options = ["GMT-10", "GMT-9", "GMT-8", "GMT-7", "GMT-6", "GMT-5", "GMT-4", "GMT-3", "GMT+0", "GMT+1", "GMT+2", "GMT+3", "GMT+4", "GMT+5", "GMT+6", "GMT+7", "GMT+8", "GMT+9", "GMT+10", "GMT+10:30", "GMT+11", "GMT+12", "GMT+13", "GMT+13:45"], group = g0)
sessionTime = input.session('0600-0900', 'Session', group = g0)
highlight   = input.bool(false, 'Highlight Session', inline = '1', group = g0)
hCol        = input.color(color.new(color.aqua,90), '', inline = '1', group = g0)

var g1      = 'Anchor Session VWAP Settings'
vsrc        = input.source(hlc3, 'VWAP Source', group = g1)
showHigh    = input.bool(true, 'Show Session VWAP', inline = '1', group = g1)
highCol     = input.color(color.blue, '', inline = '1', group = g1)
highW       = input.int(1, '', inline = '1', group = g1)
showOld     = input.bool(true, 'Show Previous Session VWAP\'s', group = g1)
numSessions = input.int(50, 'Number of Previous Sessions', minval = 1, maxval = 100, group = g1)

var g2      = 'RTH VWAP Settings'
rth_vsrc    = input.source(hlc3, 'RTH VWAP Source', group = g2)
showRTH     = input.bool(true, 'Show RTH VWAP', inline = '1', group = g2)
rthCol      = input.color(color.new(color.purple, 30), '', inline = '1', group = g2)
rthW        = input.int(2, '', inline = '1', group = g2)
input_rth   = input.session("0930-1600", "RTH Session Time (EST)", group = g2)


var g3      = 'Stats Table'
showTable   = input.bool(true, 'Show Stats Table', group = g3)
tblPos      = input.string('Top Right', 'Position', options = ['Top Right', 'Top Left', 'Top Center', 'Bottom Right', 'Bottom Left', 'Bottom Center', 'Middle Right', 'Middle Left'], group = g3)
tblSize     = input.string('Small', 'Text Size', options = ['Tiny', 'Small', 'Normal'], group = g3)
maxDays     = input.int(100, 'Max Days to Track', minval = 10, maxval = 500, group = g3)

// ─── Band Levels inputs ───────────────────────────────────────────────────────
// Anchor = midpoint of RTH VWAP and Session VWAP each bar.
// All bands are offsets from that anchor, calculated from the intraday
// distribution of (price - anchor) deviations, volume-weighted where noted.

var g4        = 'Band Levels'

// Anchor line
showAnchor    = input.bool(false, 'Anchor (midpoint)',     inline = 'anc',  group = g4)
anchorCol     = input.color(color.new(color.black, 30),  '',               inline = 'anc',  group = g4)

// Symmetric stdev
show1s        = input.bool(false, '± 1σ  (symmetric)',    inline = 's1',   group = g4)
col1s         = input.color(color.new(color.yellow, 50), '',               inline = 's1',   group = g4)
show2s        = input.bool(false, '± 2σ  (symmetric)',    inline = 's2',   group = g4)
col2s         = input.color(color.new(color.yellow, 65), '',               inline = 's2',   group = g4)

// Asymmetric stdev (upper/lower computed separately)
showAsym      = input.bool(false, 'Asymmetric σ (upper/lower)', inline = 'asy', group = g4)
colAsymHi     = input.color(color.new(color.orange, 50), '',               inline = 'asy',  group = g4)
colAsymLo     = input.color(color.new(color.orange, 50), '',               inline = 'asy',  group = g4)

// MAD
showMAD       = input.bool(false, 'MAD  (± median abs dev)', inline = 'mad', group = g4)
colMAD        = input.color(color.new(color.teal, 40),   '',               inline = 'mad',  group = g4)

// Percentile bands — 10th/25th/75th/90th of deviation distribution
showPct       = input.bool(false, 'Percentile bands (10/25/75/90)', inline = 'pct', group = g4)
colPct25      = input.color(color.new(color.blue, 50),   '',               inline = 'pct',  group = g4)
colPct75      = input.color(color.new(color.blue, 50),   '',               inline = 'pct',  group = g4)
colPct10      = input.color(color.new(color.blue, 65),   '',               inline = 'pct',  group = g4)
colPct90      = input.color(color.new(color.blue, 65),   '',               inline = 'pct',  group = g4)

// VWAP of deviations — volume-weighted mean deviation line
showVWD       = input.bool(false, 'VWAP of deviations',   inline = 'vwd',  group = g4)
colVWD        = input.color(color.new(color.fuchsia, 40),'',               inline = 'vwd',  group = g4)

// ─── Helpers ──────────────────────────────────────────────────────────────────
f_pos() =>
    switch tblPos
        'Top Right'     => position.top_right
        'Top Left'      => position.top_left
        'Top Center'    => position.top_center
        'Bottom Right'  => position.bottom_right
        'Bottom Left'   => position.bottom_left
        'Bottom Center' => position.bottom_center
        'Middle Right'  => position.middle_right
        'Middle Left'   => position.middle_left
        => position.top_right

f_size() =>
    switch tblSize
        'Tiny'   => size.tiny
        'Small'  => size.small
        'Normal' => size.normal
        => size.small

f_pad(string s, int w) =>
    out = s
    extra = w - str.length(s)
    if extra > 0
        for i = 1 to extra
            out := out + ' '
    out

f_price(float p) =>
    na(p) ? 'n/a' : str.tostring(p, format.mintick)

f_sess_start() =>
    str.substring(sessionTime, 0, 2) + ':' + str.substring(sessionTime, 2, 4)

f_rth_start() =>
    str.substring(input_rth, 0, 2) + ':' + str.substring(input_rth, 2, 4)

f_round2(float v) =>
    str.tostring(math.round(v, 2))

// Convert fractional hour (e.g. 10.75) to HH:MM string
f_hhmm(float fh) =>
    if na(fh)
        'n/a'
    else
        int h   = int(fh)
        int m   = int(math.round((fh - h) * 60))
        string hs = h < 10 ? '0' + str.tostring(h) : str.tostring(h)
        string ms = m < 10 ? '0' + str.tostring(m) : str.tostring(m)
        hs + ':' + ms

// Current bar time as fractional hour in EST (for intraday event tagging)
f_bar_hour_frac() =>
    float t_mins = (hour(time, "America/New_York") * 60 + minute(time, "America/New_York"))
    t_mins / 60.0

// Median of a float array (sorts a copy)
f_median(array<float> arr) =>
    float result = na
    if arr.size() > 0
        sorted = arr.copy()
        sorted.sort()
        int n = sorted.size()
        result := n % 2 == 1 ? sorted.get(n / 2) : (sorted.get(n / 2 - 1) + sorted.get(n / 2)) / 2
    result

// Quartiles
f_q1(array<float> arr) =>
    float result = na
    if arr.size() >= 4
        sorted = arr.copy()
        sorted.sort()
        int n = sorted.size()
        int mid = n / 2
        lower = sorted.slice(0, mid)
        result := f_median(lower)
    result

f_q3(array<float> arr) =>
    float result = na
    if arr.size() >= 4
        sorted = arr.copy()
        sorted.sort()
        int n = sorted.size()
        int mid = n % 2 == 1 ? n / 2 + 1 : n / 2
        upper = sorted.slice(mid, n)
        result := f_median(upper)
    result

// ─── Table cell helpers ───────────────────────────────────────────────────────
var table _tbl = na

f_header(row, txt) =>
    table.cell(_tbl, 0, row, txt, text_color = color.white, text_size = f_size(), bgcolor = color.new(color.black, 0), text_halign = text.align_left, text_font_family = font.family_monospace)
    table.merge_cells(_tbl, 0, row, 2, row)

f_subhead(row, c0, c1, c2) =>
    table.cell(_tbl, 0, row, f_pad(' ' + c0, 14), text_color = color.white, text_size = f_size(), bgcolor = color.new(color.black, 0), text_halign = text.align_left, text_font_family = font.family_monospace)
    table.cell(_tbl, 1, row, f_pad(' ' + c1, 14), text_color = color.white, text_size = f_size(), bgcolor = color.new(color.black, 0), text_halign = text.align_left, text_font_family = font.family_monospace)
    table.cell(_tbl, 2, row, f_pad(' ' + c2, 16), text_color = color.white, text_size = f_size(), bgcolor = color.new(color.black, 0), text_halign = text.align_left, text_font_family = font.family_monospace)

f_row(row, lbl, val, detail) =>
    table.cell(_tbl, 0, row, f_pad(' ' + lbl,    14), text_color = color.black, text_size = f_size(), bgcolor = color.white, text_halign = text.align_left, text_font_family = font.family_monospace)
    table.cell(_tbl, 1, row, f_pad(' ' + val,    14), text_color = color.black, text_size = f_size(), bgcolor = color.white, text_halign = text.align_left, text_font_family = font.family_monospace)
    table.cell(_tbl, 2, row, f_pad(' ' + detail, 16), text_color = color.black, text_size = f_size(), bgcolor = color.white, text_halign = text.align_left, text_font_family = font.family_monospace)

// ─── Session & VWAP logic ─────────────────────────────────────────────────────
session      = time(timeframe.period, sessionTime, tz)
startSession = not na(session) and na(session[1])
newDay       = ta.change(dayofweek) != 0

type vwapData
    float [] vol
    float [] pv
    float [] vwaps
    float hl
    int startBar
    chart.point fbs
    chart.point [] cps
    polyline vwapLine
    bool ended

method popper(array<vwapData> this) =>
    if showOld
        if this.size() > numSessions + 1
            this.last().vwapLine.delete()
            this.pop()
    else
        if this.size() > 1
            this.last().vwapLine.delete()
            this.pop()

method vwapper(array<vwapData> this, lineCol, lineW) =>
    if newDay and this.size() > 0
        for i = 0 to this.size() - 1
            tempSession = this.get(i)
            tempSession.ended := true
    if this.size() > 0
        s = this.first()
        if bar_index > s.startBar and not s.ended
            s.vol.push(s.vol.get(0) + volume)
            s.vol.shift()
            s.pv.push(s.pv.get(0) + vsrc * volume)
            s.pv.shift()
            s.vwaps.unshift(s.pv.get(0) / s.vol.get(0))
            s.cps.clear()
            for i = 0 to s.vwaps.size() - 1
                s.cps.push(chart.point.from_index(bar_index[i], s.vwaps.get(i)))
            s.cps.push(s.fbs)
            s.vwapLine.delete()
            s.vwapLine := polyline.new(s.cps, line_color = lineCol, line_width = lineW)
        if this.size() > 1 and showOld
            s2 = this.last()
            if bar_index > s2.startBar and not s2.ended
                s2.vol.push(s2.vol.get(0) + volume)
                s2.vol.shift()
                s2.pv.push(s2.pv.get(0) + vsrc * volume)
                s2.pv.shift()
                s2.vwaps.unshift(s2.pv.get(0) / s2.vol.get(0))
                s2.cps.clear()
                for i = 0 to s2.vwaps.size() - 1
                    s2.cps.push(chart.point.from_index(bar_index[i], s2.vwaps.get(i)))
                s2.cps.push(s2.fbs)
                s2.vwapLine.delete()
                s2.vwapLine := polyline.new(s2.cps, line_color = lineCol, line_width = lineW)

method hlReset(array<vwapData> this, h) =>
    if this.size() > 0
        s = this.first()
        if h
            if not na(session) and high > s.hl
                s.vwapLine.delete()
                s.vwapLine := na
                this.shift()
                this.unshift(vwapData.new(array.new<float>(1, volume), array.new<float>(1, volume * vsrc), array.new<float>(), high, bar_index, chart.point.from_index(bar_index, (volume * vsrc) / volume), array.new<chart.point>(), na, false))
        else
            if not na(session) and low < s.hl
                s.vwapLine.delete()
                s.vwapLine := na
                this.shift()
                this.unshift(vwapData.new(array.new<float>(1, volume), array.new<float>(1, volume * vsrc), array.new<float>(), low, bar_index, chart.point.from_index(bar_index, (volume * vsrc) / volume), array.new<chart.point>(), na, false))

var startArr = array.new<vwapData>()
var highArr  = array.new<vwapData>()
var lowArr   = array.new<vwapData>()

if startSession
    if showHigh
        highArr.unshift(vwapData.new(array.new<float>(1, volume), array.new<float>(1, volume * vsrc), array.new<float>(), high, bar_index, chart.point.from_index(bar_index, (volume * vsrc) / volume), array.new<chart.point>(), na, false))
        highArr.popper()

if showHigh
    highArr.vwapper(highCol, highW)
    highArr.hlReset(true)

// ─── RTH VWAP ─────────────────────────────────────────────────────────────────
rth_session = time("1440", str.format("{0}:23456", input_rth), "America/New_York")

var float rth_vwap       = na
var float rth_p          = na
var float rth_vol        = na
var float rth_anchor_val = na

if rth_session
    if not rth_session[1]
        rth_anchor_val := rth_vsrc
        rth_p          := rth_vsrc * volume
        rth_vol        := volume
        rth_vwap       := rth_vsrc
    else
        rth_p    := rth_p[1] + rth_vsrc * volume
        rth_vol  := rth_vol[1] + volume
        rth_vwap := rth_p / rth_vol
else
    rth_vwap := na

// ─── Live session VWAP value ──────────────────────────────────────────────────
var float sess_vwap_live = na
if highArr.size() > 0
    s = highArr.first()
    if s.vwaps.size() > 0
        sess_vwap_live := s.vwaps.get(0)

// ─── Crossover & Retest Detection (confirmed bars only) ───────────────────────
var int   day_rth_retests  = 0
var int   day_sess_retests = 0
var int   day_crossovers   = 0
var float prev_rth_side    = na
var float prev_sess_side   = na
var bool  prev_rth_above   = na

// Arrays to accumulate per-day counts
var array<float> arr_rth_retests  = array.new<float>()
var array<float> arr_sess_retests = array.new<float>()
var array<float> arr_crossovers   = array.new<float>()

// ─── Timing arrays (fractional hour in EST, one entry per event) ──────────────
// All-time event times — used for mean/median across entire history
var array<float> arr_rth_retest_times  = array.new<float>()
var array<float> arr_sess_retest_times = array.new<float>()
var array<float> arr_crossover_times   = array.new<float>()

// Per-hour bucket accumulators (9 buckets: 09, 10, 11, 12, 13, 14, 15, and one each for pre/post)
// Indices 0-6 = hours 9–15 EST, index 7 = other (shouldn't normally fire during RTH)
var array<int> bucket_rth_ret  = array.new<int>(8, 0)
var array<int> bucket_sess_ret = array.new<int>(8, 0)
var array<int> bucket_cross    = array.new<int>(8, 0)

f_bucket(float fh) =>
    int h = int(fh)
    h >= 9 and h <= 15 ? h - 9 : 7

f_bucket_label(int idx) =>
    switch idx
        0 => '09'
        1 => '10'
        2 => '11'
        3 => '12'
        4 => '13'
        5 => '14'
        6 => '15'
        => 'oth'

// Find the peak hour bucket across an array
f_peak_bucket(array<int> arr) =>
    int best_idx = 0
    int best_val = 0
    for i = 0 to arr.size() - 1
        v = arr.get(i)
        if v > best_val
            best_val := v
            best_idx := i
    best_idx

// Stats arrays
var int  stat_wins        = 0
var int  stat_bullish     = 0
var int  stat_bearish     = 0
var int  stat_total       = 0
var int  stat_streak      = 0
var bool stat_streak_win  = true

var float snap_rth_vwap   = na
var float snap_sess_vwap  = na
var float snap_open       = na

// RTH return distribution
var array<float> arr_returns = array.new<float>()

rthEnd = not na(rth_session[1]) and na(rth_session)

// Track open at RTH start
if rth_session and not rth_session[1]
    snap_open      := open
    prev_rth_side  := na
    prev_sess_side := na
    prev_rth_above := na
    day_rth_retests  := 0
    day_sess_retests := 0
    day_crossovers   := 0

// Intraday detection on confirmed bars (use [1] values to avoid repaint)
if rth_session and not na(rth_vwap[1]) and not na(sess_vwap_live[1]) and barstate.isconfirmed
    float c    = close[1]
    float h    = high[1]
    float l    = low[1]
    float rv   = rth_vwap[1]
    float sv   = sess_vwap_live[1]

    // Use [1] bar time for event tagging (confirmed bar)
    float evt_fh = float(hour(time[1], "America/New_York")) + float(minute(time[1], "America/New_York")) / 60.0

    // RTH VWAP retest
    float cur_rth_side = c > rv ? 1 : c < rv ? -1 : 0
    if not na(prev_rth_side) and cur_rth_side == prev_rth_side
        if (prev_rth_side > 0 and l < rv) or (prev_rth_side < 0 and h > rv)
            day_rth_retests += 1
            arr_rth_retest_times.unshift(evt_fh)
            if arr_rth_retest_times.size() > maxDays * 10
                arr_rth_retest_times.pop()
            int bk = f_bucket(evt_fh)
            bucket_rth_ret.set(bk, bucket_rth_ret.get(bk) + 1)
    prev_rth_side := cur_rth_side

    // Session VWAP retest
    float cur_sess_side = c > sv ? 1 : c < sv ? -1 : 0
    if not na(prev_sess_side) and cur_sess_side == prev_sess_side
        if (prev_sess_side > 0 and l < sv) or (prev_sess_side < 0 and h > sv)
            day_sess_retests += 1
            arr_sess_retest_times.unshift(evt_fh)
            if arr_sess_retest_times.size() > maxDays * 10
                arr_sess_retest_times.pop()
            int bk = f_bucket(evt_fh)
            bucket_sess_ret.set(bk, bucket_sess_ret.get(bk) + 1)
    prev_sess_side := cur_sess_side

    // RTH vs Session VWAP crossover
    bool cur_rth_above = rv > sv
    if not na(prev_rth_above) and cur_rth_above != prev_rth_above
        day_crossovers += 1
        arr_crossover_times.unshift(evt_fh)
        if arr_crossover_times.size() > maxDays * 10
            arr_crossover_times.pop()
        int bk = f_bucket(evt_fh)
        bucket_cross.set(bk, bucket_cross.get(bk) + 1)
    prev_rth_above := cur_rth_above

if rth_session
    snap_rth_vwap  := rth_vwap
    snap_sess_vwap := sess_vwap_live

// At RTH end: commit all stats for the day
if rthEnd and not na(snap_rth_vwap) and not na(snap_sess_vwap) and stat_total < maxDays
    bool rth_above   = snap_rth_vwap > snap_sess_vwap
    bool closed_bull = close > snap_open
    bool is_win      = (rth_above and closed_bull) or (not rth_above and not closed_bull)

    stat_total += 1
    if is_win
        stat_wins += 1
        if rth_above
            stat_bullish += 1
        else
            stat_bearish += 1
        if stat_streak_win
            stat_streak += 1
        else
            stat_streak     := 1
            stat_streak_win := true
    else
        if not stat_streak_win
            stat_streak += 1
        else
            stat_streak     := 1
            stat_streak_win := false

    if not na(snap_open) and snap_open != 0
        float ret = (close - snap_open) / snap_open * 100
        arr_returns.unshift(ret)
        if arr_returns.size() > maxDays
            arr_returns.pop()

    arr_rth_retests.unshift(day_rth_retests)
    arr_sess_retests.unshift(day_sess_retests)
    arr_crossovers.unshift(day_crossovers)
    if arr_rth_retests.size()  > maxDays
        arr_rth_retests.pop()
    if arr_sess_retests.size() > maxDays
        arr_sess_retests.pop()
    if arr_crossovers.size()   > maxDays
        arr_crossovers.pop()

// ─── Distribution Stats (for table counts) ───────────────────────────────────
float rth_ret_mean  = arr_rth_retests.size()  > 0 ? arr_rth_retests.avg()  : na
float sess_ret_mean = arr_sess_retests.size()  > 0 ? arr_sess_retests.avg() : na
float cross_mean    = arr_crossovers.size()    > 0 ? arr_crossovers.avg()   : na
float rth_ret_med   = f_median(arr_rth_retests)
float sess_ret_med  = f_median(arr_sess_retests)
float cross_med     = f_median(arr_crossovers)

// ─── Timing Stats ─────────────────────────────────────────────────────────────
float rth_ret_time_mean   = arr_rth_retest_times.size()  > 0 ? arr_rth_retest_times.avg()  : na
float sess_ret_time_mean  = arr_sess_retest_times.size() > 0 ? arr_sess_retest_times.avg() : na
float cross_time_mean     = arr_crossover_times.size()   > 0 ? arr_crossover_times.avg()   : na
float rth_ret_time_med    = f_median(arr_rth_retest_times)
float sess_ret_time_med   = f_median(arr_sess_retest_times)
float cross_time_med      = f_median(arr_crossover_times)

int pk_rth_ret  = f_peak_bucket(bucket_rth_ret)
int pk_sess_ret = f_peak_bucket(bucket_sess_ret)
int pk_cross    = f_peak_bucket(bucket_cross)


// These run bar-by-bar inside the RTH session to build the deviation
// distribution from which all bands are derived.

var float band_vol_sum   = 0.0   // Σ vol
var float band_pvv_sum   = 0.0   // Σ vol×dev²            → weighted variance
var float band_pvv_hi    = 0.0   // Σ vol×dev² (dev > 0)  → upper variance
var float band_pvv_lo    = 0.0   // Σ vol×dev² (dev < 0)  → lower variance
var float band_vol_hi    = 0.0   // Σ vol where dev > 0
var float band_vol_lo    = 0.0   // Σ vol where dev < 0
var float band_vwd_num   = 0.0   // Σ vol×dev             → VWAP of deviations
var array<float> band_devs    = array.new<float>()   // per-bar deviations
var array<float> band_abs_dev = array.new<float>()   // per-bar |deviation|

// Anchor: midpoint of the two live VWAPs
float band_anchor = not na(rth_vwap) and not na(sess_vwap_live) ? (rth_vwap + sess_vwap_live) / 2 : na

// Reset at RTH open
if rth_session and not rth_session[1]
    band_vol_sum  := 0.0
    band_pvv_sum  := 0.0
    band_pvv_hi   := 0.0
    band_pvv_lo   := 0.0
    band_vol_hi   := 0.0
    band_vol_lo   := 0.0
    band_vwd_num  := 0.0
    band_devs.clear()
    band_abs_dev.clear()

// Accumulate each bar inside RTH
if rth_session and not na(band_anchor)
    float dev  = vsrc - band_anchor
    float adev = math.abs(dev)
    band_vol_sum  += volume
    band_pvv_sum  += volume * dev * dev
    band_vwd_num  += volume * dev
    band_devs.push(dev)
    band_abs_dev.push(adev)
    if dev > 0
        band_pvv_hi += volume * dev * dev
        band_vol_hi += volume
    else if dev < 0
        band_pvv_lo += volume * dev * dev
        band_vol_lo += volume

// ─── Percentile helper (linear interpolation) ────────────────────────────────
f_pct(array<float> arr, float p) =>
    // p in [0,100]
    float result = na
    int n = arr.size()
    if n >= 2
        sorted = arr.copy()
        sorted.sort()
        float idx = p / 100.0 * (n - 1)
        int   lo  = int(idx)
        int   hi  = math.min(lo + 1, n - 1)
        float frac = idx - lo
        result := sorted.get(lo) * (1 - frac) + sorted.get(hi) * frac
    result

// ─── Band Level Calculation ───────────────────────────────────────────────────
// Symmetric stdev
float b_stdev     = band_vol_sum > 0 ? math.sqrt(band_pvv_sum / band_vol_sum) : na
float b_1s_hi     = show1s and not na(band_anchor) and not na(b_stdev) ? band_anchor + b_stdev       : na
float b_1s_lo     = show1s and not na(band_anchor) and not na(b_stdev) ? band_anchor - b_stdev       : na
float b_2s_hi     = show2s and not na(band_anchor) and not na(b_stdev) ? band_anchor + 2 * b_stdev   : na
float b_2s_lo     = show2s and not na(band_anchor) and not na(b_stdev) ? band_anchor - 2 * b_stdev   : na

// Asymmetric stdev
float b_stdev_hi  = band_vol_hi > 0 ? math.sqrt(band_pvv_hi / band_vol_hi) : na
float b_stdev_lo  = band_vol_lo > 0 ? math.sqrt(band_pvv_lo / band_vol_lo) : na
float b_asym_hi   = showAsym and not na(band_anchor) and not na(b_stdev_hi) ? band_anchor + b_stdev_hi : na
float b_asym_lo   = showAsym and not na(band_anchor) and not na(b_stdev_lo) ? band_anchor - b_stdev_lo : na

// MAD (median of absolute deviations)
float b_mad       = f_median(band_abs_dev)
float b_mad_hi    = showMAD and not na(band_anchor) and not na(b_mad) ? band_anchor + b_mad : na
float b_mad_lo    = showMAD and not na(band_anchor) and not na(b_mad) ? band_anchor - b_mad : na

// Percentile bands (offsets added to anchor)
float b_pct10     = showPct and not na(band_anchor) ? band_anchor + f_pct(band_devs, 10) : na
float b_pct25     = showPct and not na(band_anchor) ? band_anchor + f_pct(band_devs, 25) : na
float b_pct75     = showPct and not na(band_anchor) ? band_anchor + f_pct(band_devs, 75) : na
float b_pct90     = showPct and not na(band_anchor) ? band_anchor + f_pct(band_devs, 90) : na

// VWAP of deviations (volume-weighted mean deviation, offset from anchor)
float b_vwd_raw   = band_vol_sum > 0 ? band_vwd_num / band_vol_sum : na
float b_vwd       = showVWD and not na(band_anchor) and not na(b_vwd_raw) ? band_anchor + b_vwd_raw : na

// ─── Live status ──────────────────────────────────────────────────────────────
bool today_rth_active = not na(rth_vwap)
bool today_rth_above  = today_rth_active and not na(sess_vwap_live) and rth_vwap > sess_vwap_live
bool today_bull       = not na(snap_open) and close > snap_open

// ─── Stats Table ──────────────────────────────────────────────────────────────
if showTable and barstate.islast
    if not na(_tbl)
        _tbl.delete()

    // Rows: 0 = coverage info, 1 = spacer/header W/L, 2 subhead, 3-6 W/L rows,
    //       7 = header retests, 8 subhead, 9-11 count rows,
    //       12 = header timing, 13 subhead, 14-16 timing rows,
    //       17 = header hourly, 18 subhead, 19-25 hourly rows  → 26 rows total
    _tbl := table.new(f_pos(), 3, 26,
         bgcolor      = color.new(color.black, 0),
         border_width = 1,
         border_color = color.new(color.gray, 50),
         frame_width  = 1,
         frame_color  = color.new(color.gray, 50))

    // ── Row 0: Coverage info ──────────────────────────────────────────────
    string cov_txt = ' SESSIONS: ' + str.tostring(stat_total) + ' days  |  max tracked: ' + str.tostring(maxDays)
    table.cell(_tbl, 0, 0, cov_txt, text_color = color.yellow, text_size = f_size(), bgcolor = color.new(color.black, 0), text_halign = text.align_left, text_font_family = font.family_monospace)
    table.merge_cells(_tbl, 0, 0, 2, 0)

    // ── Section 1: Win/Loss Stats ─────────────────────────────────────────
    f_header(1, ' WIN / LOSS')
    f_subhead(2, 'METRIC', 'VALUE', 'DETAIL')

    string today_pos = today_rth_active ? (today_rth_above ? 'RTH ABOVE' : 'RTH BELOW') : 'AWAITING'
    string today_dir = today_rth_active ? (today_bull      ? 'BULL'      : 'BEAR')      : '-'
    f_row(3, 'TODAY', today_pos, today_dir)

    float  wr     = stat_total > 0 ? (stat_wins / stat_total) * 100 : 0.0
    f_row(4, 'WIN RATE', f_round2(wr) + '%', str.tostring(stat_wins) + '/' + str.tostring(stat_total) + ' days')
    f_row(5, 'BULL/BEAR', str.tostring(stat_bullish) + 'B / ' + str.tostring(stat_bearish) + 'b', 'confirmed')

    string sk_dir = stat_streak_win ? 'WIN' : 'LOSS'
    f_row(6, 'STREAK', sk_dir, str.tostring(stat_streak) + (stat_streak == 1 ? ' day' : ' days'))

    // ── Section 2: Retests & Crossovers (counts) ──────────────────────────
    f_header(7, ' RETESTS & CROSSOVERS')
    f_subhead(8, 'METRIC', 'MEAN/DAY', 'MED/DAY')
    f_row(9,  'RTH RETEST',  na(rth_ret_mean)  ? 'n/a' : f_round2(rth_ret_mean),  na(rth_ret_med)  ? 'n/a' : f_round2(rth_ret_med))
    f_row(10, 'SESS RETEST', na(sess_ret_mean) ? 'n/a' : f_round2(sess_ret_mean), na(sess_ret_med) ? 'n/a' : f_round2(sess_ret_med))
    f_row(11, 'CROSSOVERS',  na(cross_mean)    ? 'n/a' : f_round2(cross_mean),    na(cross_med)    ? 'n/a' : f_round2(cross_med))

    // ── Section 3: Event Timing ───────────────────────────────────────────
    f_header(12, ' EVENT TIMING (EST)')
    f_subhead(13, 'EVENT', 'AVG TIME', 'MED / PEAK HR')
    f_row(14, 'RTH RETEST',
         na(rth_ret_time_mean) ? 'n/a' : f_hhmm(rth_ret_time_mean),
         (na(rth_ret_time_med) ? 'n/a' : f_hhmm(rth_ret_time_med)) + ' / ' + f_bucket_label(pk_rth_ret) + 'h')
    f_row(15, 'SESS RETEST',
         na(sess_ret_time_mean) ? 'n/a' : f_hhmm(sess_ret_time_mean),
         (na(sess_ret_time_med) ? 'n/a' : f_hhmm(sess_ret_time_med)) + ' / ' + f_bucket_label(pk_sess_ret) + 'h')
    f_row(16, 'CROSSOVER',
         na(cross_time_mean) ? 'n/a' : f_hhmm(cross_time_mean),
         (na(cross_time_med) ? 'n/a' : f_hhmm(cross_time_med)) + ' / ' + f_bucket_label(pk_cross) + 'h')

    // ── Section 4: Hourly Breakdown ───────────────────────────────────────
    f_header(17, ' HOURLY EVENT COUNT')
    f_subhead(18, 'HOUR', 'RTH RET', 'SESS RET / CROSS')
    for i = 0 to 6
        f_row(19 + i,
             f_bucket_label(i) + ':00',
             str.tostring(bucket_rth_ret.get(i)),
             str.tostring(bucket_sess_ret.get(i)) + ' / ' + str.tostring(bucket_cross.get(i)))

// ─── Band Plots ───────────────────────────────────────────────────────────────
plot(showAnchor ? band_anchor : na, title = 'Anchor (midpoint)',  color = anchorCol, linewidth = 1, style = plot.style_linebr)

plot(b_1s_hi,   title = '+1σ',            color = col1s,    linewidth = 1, style = plot.style_linebr)
plot(b_1s_lo,   title = '-1σ',            color = col1s,    linewidth = 1, style = plot.style_linebr)
plot(b_2s_hi,   title = '+2σ',            color = col2s,    linewidth = 1, style = plot.style_linebr)
plot(b_2s_lo,   title = '-2σ',            color = col2s,    linewidth = 1, style = plot.style_linebr)

plot(b_asym_hi, title = 'Asym σ upper',   color = colAsymHi, linewidth = 1, style = plot.style_linebr)
plot(b_asym_lo, title = 'Asym σ lower',   color = colAsymLo, linewidth = 1, style = plot.style_linebr)

plot(b_mad_hi,  title = 'MAD upper',      color = colMAD,   linewidth = 1, style = plot.style_linebr)
plot(b_mad_lo,  title = 'MAD lower',      color = colMAD,   linewidth = 1, style = plot.style_linebr)

plot(b_pct90,   title = '90th pct',       color = colPct90, linewidth = 1, style = plot.style_linebr)
plot(b_pct75,   title = '75th pct',       color = colPct75, linewidth = 1, style = plot.style_linebr)
plot(b_pct25,   title = '25th pct',       color = colPct25, linewidth = 1, style = plot.style_linebr)
plot(b_pct10,   title = '10th pct',       color = colPct10, linewidth = 1, style = plot.style_linebr)

plot(b_vwd,     title = 'VWAP of devs',   color = colVWD,   linewidth = 1, style = plot.style_linebr)

// ─── Plots ────────────────────────────────────────────────────────────────────
bgcolor(not na(session) and highlight ? hCol : na)
plot(showRTH and rth_session ? rth_vwap : na, title = "RTH VWAP", color = rthCol, linewidth = rthW, style = plot.style_linebr)

Overview

The script features customizable time zones, session highlighting, and an optional statistics table displaying tracked days. Settings allow configuring sources, line widths, and display states for standard deviations or median absolute deviation levels. It draws directly on the price chart and has no exit rules or stop loss since it is an indicator.

Details

Published by

TamaraKGreen on GitHub

Code credit

npg, as written in the script's header

Type

Indicator, Pine Script v5

Draws

On the price chart

Category

Support & resistance, Sessions & time

Markets

Any symbol

Timeframes

M15-H1, H4 and above

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
Time ZonestringGMT-4tz
Sessionsession0600-0900sessionTime
Highlight Sessionboolfalsehighlight
hColcolorcolor.new(color.aqua,90)hCol
VWAP Sourcesourcehlc3vsrc
Show Session VWAPbooltrueshowHigh
highColcolorcolor.bluehighCol
highWint1highW
Show Previous Session VWAP'sbooltrueshowOld
Number of Previous Sessionsint50numSessions
RTH VWAP Sourcesourcehlc3rth_vsrc
Show RTH VWAPbooltrueshowRTH
Show all 37 settings
SettingTypeDefaultVariable
rthColcolorcolor.new(color.purple, 30)rthCol
rthWint2rthW
RTH Session Time (EST)session0930-1600input_rth
Show Stats TablebooltrueshowTable
PositionstringTop RighttblPos
Text SizestringSmalltblSize
Max Days to Trackint100maxDays
Anchor (midpoint)boolfalseshowAnchor
anchorColcolorcolor.new(color.black, 30)anchorCol
± 1σ (symmetric)boolfalseshow1s
col1scolorcolor.new(color.yellow, 50)col1s
± 2σ (symmetric)boolfalseshow2s
col2scolorcolor.new(color.yellow, 65)col2s
Asymmetric σ (upper/lower)boolfalseshowAsym
colAsymHicolorcolor.new(color.orange, 50)colAsymHi
colAsymLocolorcolor.new(color.orange, 50)colAsymLo
MAD (± median abs dev)boolfalseshowMAD
colMADcolorcolor.new(color.teal, 40)colMAD
Percentile bands (10/25/75/90)boolfalseshowPct
colPct25colorcolor.new(color.blue, 50)colPct25
colPct75colorcolor.new(color.blue, 50)colPct75
colPct10colorcolor.new(color.blue, 65)colPct10
colPct90colorcolor.new(color.blue, 65)colPct90
VWAP of deviationsboolfalseshowVWD
colVWDcolorcolor.new(color.fuchsia, 40)colVWD
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

575 stars

This indicator builds Market Profile Time Price Opportunity (TPO) structures across defined session timeframes directly on the main price chart. It visualizes the Point of Control (POC) and Value Area boundaries using dynamic box drawings. An integrated multi-

Indicator v5 Support & resistanceScreeners & dashboards
Updated 1 year ago · MPL-2.0 GitHub repository of Market Profile with Screener
257 stars

This indicator plots auto trade signals for E-mini S&P 500 futures on the price chart. It computes an EMA200 filter along with day ETH RTH session levels and percentage-based entry zones. Signals trigger CALL or PUT cases when price crosses defined thresholds

Indicator v6 TrendSupport & resistance
Updated 6 weeks ago · None stated GitHub repository of Auto Trade Futures ES
219 stars

This indicator plots reference levels for previous daily high and low prices directly on the price chart. It requests daily timeframe data using the security function and plots the offset values as continuous lines, displaying resistance in red and support in

Indicator v4 Support & resistanceSessions & time
Updated 5 years ago · None stated GitHub repository of 昨日高低點標示

Golden Bullet Strategy

by TradersPost
218 stars

The Golden Bullet Strategy is an overlay signal indicator based on Smart Money Concepts and time-based liquidity models. It identifies liquidity sweeps of a designated reference session range during a specified hunt window. It then confirms Market Structure Sh

Indicator v6 SMC / ICT / price actionSessions & time
Updated 3 months ago · None stated GitHub repository of Golden Bullet Strategy

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

Key Levels

by cryptorife
75 stars

This indicator plots reference levels and session markers directly on the price chart. It calculates and draws lines for the daily open, weekly open, monthly open, previous day OHLC, and previous week extremes, along with a selectable day-of-week range and ini

Indicator v4 Support & resistanceSessions & time Repaints
Updated 3 years ago · GPL-3.0 GitHub repository of Key Levels

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