//@version=6
indicator("FAIRPRICE_VWAP_RD", overlay = true)
// FAIRPRICE_VWAP_RD
// This script plots an **anchored VWAP (Volume Weighted Average Price)** that resets
// based on the user-selected anchor period. It acts as a dynamic “fair value” line
// that reflects where the market has actually transacted during the chosen period.
//
// FEATURES
// - Multiple anchor options: Session, Week, Month, Quarter, Year, Decade, Century,
// Earnings, Dividends, or Splits.
// - Intelligent handling of the “Session” anchor so it works correctly on both 1m
// (resets each new day) and 1D (continuous, non-resetting VWAP).
// - Manual VWAP calculation using cumulative(price * volume) and cumulative(volume),
// ensuring the line is stable and works on all timeframes.
// - Optional hiding of VWAP on daily or higher charts.
// - Offset input for horizontal shifting if desired.
// - VWAP provides a true “fair price” reference for trend, mean-reversion,
// and institutional-level analysis.
//
// PURPOSE
// This indicator solves the common problem of VWAP behaving incorrectly on higher
// timeframes, on synthetic data, or with unusual anchors. By implementing VWAP
// manually and allowing flexible reset conditions, it functions reliably as
// an institutional-style fair value benchmark across any timeframe.
// Inputs
lineThickness = input.int(1)
hideonDWM = input.bool(false, title = "Hide VWAP on 1D or Above")
anchor = input.string(defval = "Session", title = "Anchor Period", options = ["Session", "Week", "Month", "Quarter", "Year", "Decade", "Century", "Earnings", "Dividends", "Splits"])
src = input(hlc3, title = "Source")
offset = input.int(0, title = "Offset (bars, usually 0)", minval = 0)
//---------------------
// Volume check
//---------------------
cumVolume = ta.cum(volume)
if barstate.islast and cumVolume == 0
runtime.error("No volume is provided by the data vendor.")
//---------------------
// Anchor logic
//---------------------
new_earnings = request.earnings(syminfo.tickerid, earnings.actual,
barmerge.gaps_on, barmerge.lookahead_on, ignore_invalid_symbol = true)
new_dividends = request.dividends(syminfo.tickerid, dividends.gross,
barmerge.gaps_on, barmerge.lookahead_on, ignore_invalid_symbol = true)
new_split = request.splits(syminfo.tickerid, splits.denominator,
barmerge.gaps_on, barmerge.lookahead_on, ignore_invalid_symbol = true)
// Special handling for "Session" so it works on both 1m and 1D
sessNew =
timeframe.isintraday ? timeframe.change("D") : // intraday: reset each new day
timeframe.isdaily ? false : // daily: no reset, full-history VWAP
false
isNewPeriod = switch anchor
"Earnings" => not na(new_earnings)
"Dividends" => not na(new_dividends)
"Splits" => not na(new_split)
"Session" => sessNew
"Week" => timeframe.change("W")
"Month" => timeframe.change("M")
"Quarter" => timeframe.change("3M")
"Year" => timeframe.change("12M")
"Decade" => timeframe.change("12M") and year % 10 == 0
"Century" => timeframe.change("12M") and year % 100 == 0
=> false
isEsdAnchor = anchor == "Earnings" or anchor == "Dividends" or anchor == "Splits"
// On the very first bar, force a reset (for non-E/D/S anchors)
if na(src[1]) and not isEsdAnchor
isNewPeriod := true
//---------------------
// Manual anchored VWAP
//---------------------
var float cumPV = na
var float cumVol = na
float vwapValue = na
if not (hideonDWM and timeframe.isdwm)
if isNewPeriod or na(cumVol)
// Reset VWAP at new anchor
cumPV := src * volume
cumVol := volume
else
cumPV += src * volume
cumVol += volume
vwapValue := cumVol != 0 ? cumPV / cumVol : na
//---------------------
// Plot: VWAP line
//---------------------
plot(vwapValue, title = "VWAP", color = color.new(color.aqua, 50), offset = offset, linewidth = lineThickness)