Rolling VWAP with Standard Deviations
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © TradingView

//@version=5
indicator("Rolling VWAP with Standard Deviations", "RVWAP + StdDev", true)

import PineCoders/ConditionalAverages/1 as pc



// ———————————————————— Constants and Inputs {

int MS_IN_MIN   = 60 * 1000
int MS_IN_HOUR  = MS_IN_MIN  * 60
int MS_IN_DAY   = MS_IN_HOUR * 24

var string TT_WINDOW = "By default, the time period used to calculate the RVWAP automatically adjusts with the chart's timeframe.
  Check this to use a fixed-size time period instead, which you define with the following three values."
var string TT_MINBARS = "The minimum number of last values to keep in the moving window, even if these values are outside the time period.
  This avoids situations where a large time gap between two bars would cause the time window to be empty."
var string TT_STDEV = "The multiplier for the standard deviation bands offset above and below the RVWAP. Example: 1.0 is 100% of the offset value. 
  \n\nNOTE: A value of 0.0 will hide the bands."

float srcInput        = input.source(hlc3, "Source", tooltip = "The source used to calculate the VWAP. The default is the average of the high, low and close prices.")

var string GRP2       = '═══════════   Time Period   ═══════════'
bool fixedTfInput     = input.bool(false, "Use a fixed time period", group = GRP2, tooltip = TT_WINDOW)
int  daysInput        = input.int(1, "Days", minval = 0, maxval = 90, group = GRP2) * MS_IN_DAY
int  hoursInput       = input.int(0, "Hours", minval = 0, maxval = 23, group = GRP2) * MS_IN_HOUR
int  minsInput        = input.int(0, "Minutes", minval = 0, maxval = 59, group = GRP2) * MS_IN_MIN
bool tableInput       = input.bool(true, "Show time period", group = GRP2, tooltip = "Displays the time period of the rolling window.")
string textSizeInput  = input.string("large", "Text size", group = GRP2, options = ["tiny", "small", "normal", "large", "huge", "auto"])
string tableYposInput = input.string("bottom", "Position     ", inline = "21", group = GRP2, options = ["top", "middle", "bottom"])
string tableXposInput = input.string("right", "", inline = "21", group = GRP2, options = ["left", "center", "right"])

var string GRP4       = '════════  Minimum Window Size  ════════'
int  minBarsInput     = input.int(10, "Bars", group = GRP4, tooltip = TT_MINBARS)
// }



// ———————————————————— Functions {

timeStep() =>
    // @function    Determines a time period from the chart's timeframe.
    // @returns     (int) A value of time in milliseconds that is appropriate for the current chart timeframe. To be used in the RVWAP calculation.
    int tfInMs = timeframe.in_seconds() * 1000
    float step =
      switch
        tfInMs <= MS_IN_MIN        => MS_IN_HOUR
        tfInMs <= MS_IN_MIN * 5    => MS_IN_HOUR * 4
        tfInMs <= MS_IN_HOUR       => MS_IN_DAY * 1
        tfInMs <= MS_IN_HOUR * 4   => MS_IN_DAY * 3
        tfInMs <= MS_IN_HOUR * 12  => MS_IN_DAY * 7
        tfInMs <= MS_IN_DAY        => MS_IN_DAY * 30.4375
        tfInMs <= MS_IN_DAY * 7    => MS_IN_DAY * 90
        => MS_IN_DAY * 365
    int result = int(step)


tfString(int timeInMs) =>
    // @function    Produces a string corresponding to the input time in days, hours, and minutes.
    // @param       (series int) A time value in milliseconds to be converted to a string variable. 
    // @returns     (string) A string variable reflecting the amount of time from the input time.
    int s  = timeInMs / 1000
    int m  = s / 60
    int h  = m / 60
    int tm = math.floor(m % 60)
    int th = math.floor(h % 24)
    int d  = math.floor(h / 24)
    string result = 
      switch
        d == 30 and th == 10 and tm == 30 => "1M"
        d == 7  and th == 0  and tm == 0  => "1W"
        =>
            string dStr = d  ? str.tostring(d)  + "D "  : ""
            string hStr = th ? str.tostring(th) + "H "  : ""
            string mStr = tm ? str.tostring(tm) + "min" : ""
            dStr + hStr + mStr
// }



// ———————————————————— Calculations and Plots {

// Stop the indicator on charts with no volume.
if barstate.islast and ta.cum(nz(volume)) == 0
    runtime.error("No volume is provided by the data vendor.")

// RVWAP + stdev bands
var int timeInMs   = fixedTfInput ? minsInput + hoursInput + daysInput : timeStep()

float sumSrcVol    = pc.totalForTimeWhen(srcInput * volume, timeInMs, true, minBarsInput)
float sumVol       = pc.totalForTimeWhen(volume, timeInMs, true, minBarsInput)
float sumSrcSrcVol = pc.totalForTimeWhen(volume * math.pow(srcInput, 2), timeInMs, true, minBarsInput)

float rollingVWAP  = sumSrcVol / sumVol

float variance = sumSrcSrcVol / sumVol - math.pow(rollingVWAP, 2)
variance := variance < 0 ? 0 : variance

float stDev = math.sqrt(variance)

r1 = plot(rollingVWAP, "Rolling VWAP", color.purple)

n2 = plot(rollingVWAP - stDev * 1, "Lower 1", color.new(color.green, 90))
n3 = plot(rollingVWAP - stDev * 1.5, "1.5", color.new(color.green, 85))
n4 = plot(rollingVWAP - stDev * 2, "2", color.new(color.green, 80))
n5 = plot(rollingVWAP - stDev * 2.5, "2.5", color.new(color.green, 75))
n6 = plot(rollingVWAP - stDev * 3, "3", color.new(color.green, 70))

p2 = plot(rollingVWAP + stDev * 1, "Upper 1", color.new(color.red, 90))
p3 = plot(rollingVWAP + stDev * 1.5, "1.5", color.new(color.red, 85))
p4 = plot(rollingVWAP + stDev * 2, "2", color.new(color.red, 80))
p5 = plot(rollingVWAP + stDev * 2.5, "2.5", color.new(color.red, 75))
p6 = plot(rollingVWAP + stDev * 3, "3", color.new(color.red, 70))

fill(r1, p2, color.new(color.red, 97), "Mid Lower Fill")
fill(r1, n2, color.new(color.green, 97), "Mid Upper Fill")

fill(p2, p3, color.new(color.red, 95), "-1 Fill")
fill(p3, p4, color.new(color.red, 90), "-1.5 Fill")
fill(p4, p5, color.new(color.red, 85), "-2 Fill")
fill(p5, p6, color.new(color.red, 80), "-2.5 Fill")

fill(n2, n3, color.new(color.green, 95), "-1 Fill")
fill(n3, n4, color.new(color.green, 90), "-1.5 Fill")
fill(n4, n5, color.new(color.green, 85), "-2 Fill")
fill(n5, n6, color.new(color.green, 80), "-2.5 Fill")




// Display of time period.
var table tfDisplay = table.new(tableYposInput + "_" + tableXposInput, 1, 1)
if tableInput
    table.cell(tfDisplay, 0, 0, tfString(timeInMs), bgcolor = na, text_color = color.gray, text_size = textSizeInput)
// }