Aggregated Rolling VWAP +
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// Orignal Code by © TradingView
// Modified & Added Code by InFinito

//@version=5
indicator("Aggregated Rolling VWAP +", "ARVWAP+", true, precision = 4)

// Rolling VWAP
// v3, 2022.07.24
// InFinito Edit 2022.09.22
// Edit log:
//      - Added Volume Aggregation Capabilities to the Script
//      - Added Symmetrical Deviations to the Script
//      - Switched default option to manual TF instead of automatic TF
//      - Added TF Presets for quick switching between different settings. (This feature is mostly intended for mobile charting)
//      - Added ON/OFF Switch to all individual deviations to make it easier, faster and cleaner to display different data. (This feature is mostly intended for mobile charting)

// This code was written using the recommendations from the Pine Script™ User Manual's Style Guide:
//   https://www.tradingview.com/pine-script-docs/en/v5/writing/Style_guide.html


import PineCoders/ConditionalAverages/1 as pc



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

// ————— Constants
int MS_IN_MIN   = 60 * 1000
int MS_IN_HOUR  = MS_IN_MIN  * 60
int MS_IN_DAY   = MS_IN_HOUR * 24

string TT_SRC     = "The source used to calculate the VWAP. The default is the average of the high, low and close prices."
string TT_WINDOW  = "The Script can estimate the ideal TF for the RVWAP at any given TF by selecting this option.
  Check this to use a fixed-size time period instead, which you define with the following three values."
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."
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."
string TT_TABLE   = "Displays the time period of the rolling window."
string AGGR_TT   = "Disable to check by symbol. Indicator preset to use BTC data, if you are to use another symbol: 1. Disable this option OR 2. Manually select from which symbols to aggregate at the bottom of this menu"


// ————— Inputs
float   srcInput                = input.source(hlc3, "Source", group = "source", inline ="1", tooltip = TT_SRC)
bool    aggr                    = input.bool(defval=true, title='Use Aggregated Data', group = "source", inline ="1", tooltip= AGGR_TT)



string  GRP2                    = '═══════════   Time Period   ═══════════'
bool    fixedTfInput            = input.bool(false,         "Automatic TF selection", group = GRP2, tooltip = TT_WINDOW)


bool    presetDsw               = input.bool(true,           " ", group = GRP2, inline="1")
int     pdaysInput              = input.int(7,              "Preset Days",             group = GRP2, inline="1", options=[1,3,5,7,14,21,28,30,60,90,182,365]) * MS_IN_DAY
int     daysInput               = presetDsw ? pdaysInput : (input.int(1,              "Custom Days",                    group = GRP2, minval = 0, maxval = 365, inline="1") * MS_IN_DAY) 
bool    presetHsw               = input.bool(false,           " ", group = GRP2, inline="2")
int     phoursInput             = input.int(4,              "Preset Hours",             group = GRP2, inline="2", options=[1,2,4,8,12]) * MS_IN_HOUR
int     hoursInput              = presetHsw ? phoursInput : (input.int(0,              "Custom Hours",                   group = GRP2, minval = 0, maxval = 23, inline="2") * MS_IN_HOUR)
bool    presetMsw               = input.bool(false,           " ", group = GRP2, inline="3")
int     pminsInput              = input.int(15,              "Preset Minutes",             group = GRP2, inline="3", options=[3,5,15,30,45,90]) * MS_IN_MIN
int     minsInput               = presetMsw ? pminsInput : ( input.int(0,              "Custom Minutes",                 group = GRP2, minval = 0, maxval = 59, inline="3") * MS_IN_MIN)

bool    showInfoBoxInput        = input.bool(true,          "Show time period",        group = GRP2)
string  infoBoxSizeInput        = input.string("small",     "Size ",    inline = "21", group = GRP2, options = ["tiny", "small", "normal", "large", "huge", "auto"])
string  infoBoxYPosInput        = input.string("bottom",    "↕",        inline = "21", group = GRP2, options = ["top", "middle", "bottom"])
string  infoBoxXPosInput        = input.string("left",      "↔",        inline = "21", group = GRP2, options = ["left", "center", "right"])
color   infoBoxColorInput       = input.color(color.gray,   "",         inline = "21", group = GRP2)
color   infoBoxTxtColorInput    = input.color(color.white,  "T",        inline = "21", group = GRP2)


string  GRP3                    = '═════════  Standard Deviation Bands  ═════════'
bool    std1sw                  = input.bool(false , "" , group = GRP3, inline = "31")
bool    std2sw                  = input.bool(true , "" , group = GRP3, inline = "32")
bool    std3sw                  = input.bool(false , "" , group = GRP3, inline = "33")
float   stdevMult1              = input.float(1, "Bands Multiplier 1", group = GRP3, inline = "31", minval = 0.0, step = 0.5)
float   stdevMult2              = input.float(2, "Bands Multiplier 2", group = GRP3, inline = "32", minval = 0.0, step = 0.5 )
float   stdevMult3              = input.float(3, "Bands Multiplier 3", group = GRP3, inline = "33", minval = 0.0, step = 0.5 )
color   stdevColor1             = input.color(color.yellow,  "",          group = GRP3, inline = "31")
color   stdevColor2             = input.color(color.green, "",          group = GRP3, inline = "32")
color   stdevColor3             = input.color(color.red,    "",          group = GRP3, inline = "33")

string  GRP4                    = '═════════  Symmetrical Deviation Bands  ═════════'
bool    sd1sw                  = input.bool(false , "" , group = GRP4, inline = "1")
bool    sd2sw                  = input.bool(false , "" , group = GRP4, inline = "2")
bool    sd3sw                  = input.bool(false , "" , group = GRP4, inline = "3")
float   sdevMult1              = input.float(3, "Percentage Multiplier 1", group = GRP4, inline = "1", minval = 0.00001, step = 0.5 )/100
float   sdevMult2              = input.float(5, "Percentage Multiplier 2", group = GRP4, inline = "2", minval = 0.00001, step = 0.5 )/100
float   sdevMult3              = input.float(10, "Percentage Multiplier 3", group = GRP4, inline = "3", minval = 0.00001, step = 0.5 )/100
color   sdevColor1             = input.color(color.olive,  "",          group = GRP4, inline = "1")
color   sdevColor2             = input.color(color.yellow, "",          group = GRP4, inline = "2")
color   sdevColor3             = input.color(color.red,    "",          group = GRP4, inline = "3")

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

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// Inputs FOR AGGREGATION//////////////////////////////////////////////////////////////////////////////

string  GRP6                    = '════════  Aggregation Symbols  ════════'
i_sym1 = input.bool(true, '', inline='1', group=GRP6)
i_sym2 = input.bool(true, '', inline='2', group=GRP6)
i_sym3 = input.bool(true, '', inline='3', group=GRP6)
i_sym4 = input.bool(true, '', inline='4', group=GRP6)
i_sym5 = input.bool(true, '', inline='5', group=GRP6)

i_sym1_ticker = input.symbol('BINANCE:BTCUSDT', '', inline='1', group=GRP6)
i_sym2_ticker = input.symbol('COINBASE:BTCUSD', '', inline='2', group=GRP6)
i_sym3_ticker = input.symbol('BITSTAMP:BTCUSD', '', inline='3', group=GRP6)
i_sym4_ticker = input.symbol('OKX:BTCUSDT', '', inline='4', group=GRP6)
i_sym5_ticker = input.symbol('BITFINEX:BTCUSD', '', inline='5', group=GRP6)

sbase1 = input.string(defval='Coin', title='', options=['Coin', 'USD', 'Other'], inline='1', group=GRP6)
sbase2 = input.string(defval='Coin', title='', options=['Coin', 'USD', 'Other'], inline='2', group=GRP6)
sbase3 = input.string(defval='USD', title='', options=['Coin', 'USD', 'Other'], inline='3', group=GRP6)
sbase4 = input.string(defval='USD', title='', options=['Coin', 'USD', 'Other'], inline='4', group=GRP6)
sbase5 = input.string(defval='Coin', title='', options=['Coin', 'USD', 'Other'], inline='5', group=GRP6)

samount1 = input.float(defval=1, title='#', inline='1', group=GRP6)
samount2 = input.float(defval=1, title='#', inline='2', group=GRP6)
samount3 = input.float(defval=1, title='#', inline='3', group=GRP6)
samount4 = input.float(defval=1, title='#', inline='4', group=GRP6)
samount5 = input.float(defval=1, title='#', inline='5', group=GRP6)
/////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// }



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

// @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.
timeStep() =>
    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)


// @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.
tfString(int timeInMs) =>
    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
// }


////    VOLUME REQUEST FUNCTION/////////////////////////////////////////
f_volume(_ticker) =>
    request.security(_ticker, timeframe.period, volume)
/////////////////////////////////////////////////////////////////////////
///////////SPOT////////////////////////////////////////////////////////////////////
var float finvol = 0

if aggr==true ///////////SPOT//////////////////////////////////////////////////////////////////////////////////////////////////////
    v1x = (i_sym1 ? f_volume(i_sym1_ticker) : 0)
    v1 = sbase1=='Coin' ? v1x*samount1 : sbase1=='USD' or sbase1=='Other' ? (v1x*samount1)/ohlc4 : v1x
    v2x = (i_sym2 ? f_volume(i_sym2_ticker) : 0)
    v2 = sbase2=='Coin' ? v2x*samount2 : sbase2=='USD' or sbase2=='Other' ? (v2x*samount2)/ohlc4 : v2x
    v3x = (i_sym3 ? f_volume(i_sym3_ticker) : 0)
    v3 = sbase2=='Coin' ? v3x*samount3 : sbase3=='USD' or sbase3=='Other' ? (v3x*samount4)/ohlc4 : v3x
    v4x = (i_sym4 ? f_volume(i_sym4_ticker) : 0)
    v4 = sbase4=='Coin' ? v4x*samount4 : sbase4=='USD' or sbase4=='Other' ? (v4x*samount4)/ohlc4 : v4x
    v5x = (i_sym5 ? f_volume(i_sym5_ticker) : 0)
    v5 = sbase5=='Coin' ? v5x*samount5 : sbase5=='USD' or sbase5=='Other' ? (v5x*samount5)/ohlc4 : v5x

    vsf=v1+v2+v3+v4+v5
/////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////FINAL AGGREGATION SELECTION////////////////////////////////////////
    finvol := vsf
/////////////////////////////////////////////////////////////////////////////////////////////////
else if aggr==false
    finvol := volume
/////////////////////////////////////////////////////////////////////////////////////////////////


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

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

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

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

float rollingVWAP  = sumSrcVol / sumVol

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

float stDev        = math.sqrt(variance)

float upperBand1   = rollingVWAP + stDev * stdevMult1
float lowerBand1   = rollingVWAP - stDev * stdevMult1

float upperBand2   = rollingVWAP + stDev * stdevMult2
float lowerBand2   = rollingVWAP - stDev * stdevMult2

float upperBand3   = rollingVWAP + stDev * stdevMult3
float lowerBand3   = rollingVWAP - stDev * stdevMult3

float uppersd1   = rollingVWAP + sdevMult1  * rollingVWAP
float lowersd1   = rollingVWAP - sdevMult1  * rollingVWAP

float uppersd2   = rollingVWAP + sdevMult2 * rollingVWAP
float lowersd2   = rollingVWAP - sdevMult2 * rollingVWAP

float uppersd3   = rollingVWAP + sdevMult3 * rollingVWAP
float lowersd3   = rollingVWAP - sdevMult3 * rollingVWAP

plot(rollingVWAP, "Rolling VWAP", color.orange, linewidth = sd1sw or sd2sw or sd3sw ? 2 : 1)

p1 = plot(std1sw ? upperBand1 : na, "Upper Band 1", stdevColor1)
p2 = plot(std1sw ? lowerBand1 : na, "Lower Band 1", stdevColor1)

p3 = plot(std2sw ? upperBand2 : na, "Upper Band 2", stdevColor2)
p4 = plot(std2sw ? lowerBand2 : na, "Lower Band 2", stdevColor2)

p5 = plot(std3sw ? upperBand3 : na, "Upper Band 3", stdevColor3)
p6 = plot(std3sw ? lowerBand3 : na, "Lower Band 3", stdevColor3)

s1 = plot(sd1sw ? uppersd1 : na, "Upper Symmetrical Dev 1", sdevColor1)
s2 = plot(sd1sw ? lowersd1 : na, "Lower Symmetrical Dev 1", sdevColor1)

s3 = plot(sd2sw ? uppersd2 : na, "Upper Symmetrical Dev 2", sdevColor2)
s4 = plot(sd2sw ? lowersd2 : na, "Lower Symmetrical Dev 2", sdevColor2)

s5 = plot(sd3sw ? uppersd3 : na, "Upper Symmetrical Dev 3", sdevColor3)
s6 = plot(sd3sw ? lowersd3 : na, "Lower Symmetrical Dev 3", sdevColor3)

fill(p1, p2, color.new(color.green, 95), "Bands Fill")
fill(p3, p4, color.new(color.green, 95), "Bands Fill")
fill(p5, p6, color.new(color.green, 95), "Bands Fill")



// Display of time period.
var table tfDisplay = table.new(infoBoxYPosInput + "_" + infoBoxXPosInput, 1, 1)
if showInfoBoxInput and barstate.islastconfirmedhistory
    table.cell(tfDisplay, 0, 0, tfString(timeInMs), bgcolor = infoBoxColorInput, text_color = infoBoxTxtColorInput, text_size = infoBoxSizeInput)
// }