Fair Price VWAP

//@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)