Spot v Perp aggrRollingVWAP
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// RVWAP code is from tradingview, I've just added EMAs to it
// @TRaderKalis

//@version=5
indicator("SPOTvPERP aggrRVWAP", "SPOTvPERP aggrRVWAP", 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")

var string GRP2       = '═══════════   Time Period   ═══════════'
bool fixedTfInput     = input.bool(false, "Use a fixed time period", group = GRP2)
int  daysInput        = input.int(1, "Days", minval = 0, group = GRP2) * MS_IN_DAY
int  hoursInput       = input.int(0, "Hours", minval = 0, group = GRP2) * MS_IN_HOUR
int  minsInput        = input.int(0, "Minutes", minval = 0, group = GRP2) * MS_IN_MIN
bool tableInput       = input.bool(true, "Show time period", group = GRP2)
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 GRP3       = '═════════  Deviation Bands  ═════════'
stdevMult1            = input.float(defval = 1.9, title = "Inside Band")
stdevMult2            = input.float(defval = 3.1, title = "Outside Band")

// }



// ———————————————————— 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
// }

// ———————————————————— SPOT 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
var int timeInMs   = fixedTfInput ? minsInput + hoursInput + daysInput : timeStep()

float spotCoinVol = request.security('COINBASE:BTCUSD', timeframe.period, volume)
float spotBinVol = request.security('BINANCE:BTCUSDT', timeframe.period, volume)
float spotFtxVol = request.security('FTX:BTCUSD', timeframe.period, volume)
float spotFinexVol = request.security('BITFINEX:BTCUSD', timeframe.period, volume)
float spotOkxVol = request.security('OKX:BTCUSDT', timeframe.period, volume)

float spotAggrVol = (spotCoinVol + spotBinVol + spotFtxVol + spotFinexVol + spotOkxVol)

float spotSumSrcVol    = pc.totalForTimeWhen(srcInput * spotAggrVol, timeInMs, true, 10)
float spotSumVol       = pc.totalForTimeWhen(spotAggrVol, timeInMs, true, 10)
float spotSumSrcSrcVol = pc.totalForTimeWhen(spotAggrVol * math.pow(srcInput, 2), timeInMs, true, 10)

float spotRollingVWAP  = spotSumSrcVol / spotSumVol

float spotVariance = spotSumSrcSrcVol / spotSumVol - math.pow(spotRollingVWAP, 2)
spotVariance := spotVariance < 0 ? 0 : spotVariance

float stDev = math.sqrt(spotVariance)

plot(spotRollingVWAP, "Spot Rolling VWAP", color = color.new(#F2A900, 25))

// ———————————————————— PERP 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

float perpBinVol = request.security('BINANCE:BTCUSDTPERP', timeframe.period, volume)
float perpMexVol = request.security('BITMEX:XBTUSD', timeframe.period, volume)
float perpFtxVol = request.security('FTX:BTCPERP', timeframe.period, volume)
float perpBybitVol = request.security('BYBIT:BTCUSDT', timeframe.period, volume)
float perpOkxVol = request.security('OKX:BTCUSDTPERP', timeframe.period, volume)

float perpAggrVol = (perpBinVol + perpMexVol + perpFtxVol + perpBybitVol + perpOkxVol)

float perpSumSrcVol    = pc.totalForTimeWhen(srcInput * perpAggrVol, timeInMs, true, 10)
float perpSumVol       = pc.totalForTimeWhen(perpAggrVol, timeInMs, true, 10)
float perpSumSrcSrcVol = pc.totalForTimeWhen(perpAggrVol * math.pow(srcInput, 2), timeInMs, true, 10)

float perpRollingVWAP  = perpSumSrcVol / perpSumVol

float perpVariance = perpSumSrcSrcVol / perpSumVol - math.pow(perpRollingVWAP, 2)
perpVariance := perpVariance < 0 ? 0 : perpVariance

plot(perpRollingVWAP, "Perp Rolling VWAP", color = color.new(#00BCD4, 25))

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

//stDev Bands
aggrVol = spotAggrVol + perpAggrVol
float sumSrcVol    = pc.totalForTimeWhen(srcInput * aggrVol, timeInMs, true, 10)
float sumVol       = pc.totalForTimeWhen(aggrVol, timeInMs, true, 10)
float sumSrcSrcVol = pc.totalForTimeWhen(aggrVol * math.pow(srcInput, 2), timeInMs, true, 10)

float aggrRollingVWAP = sumSrcVol / sumVol

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

p1 = plot(stdevMult1 != 0 ? upperBand1 : na, "Inside Upper Band", color = color.new(#b9b8b8, 50), display = display.none)
p2 = plot(stdevMult1 != 0 ? lowerBand1 : na, "Inside Lower Band", color = color.new(#b9b8b8, 50), display = display.none)

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

p3 = plot(stdevMult2 != 0 ? upperBand2 : na, "Outside Upper Band", color = color.new(#b9b8b8, 25))
p4 = plot(stdevMult2 != 0 ? lowerBand2 : na, "Outside Upper Band", color = color.new(#b9b8b8, 25))

fill(p1, p3, color = color.new(#b9b8b8, 95))
fill(p2, p4, color = color.new(#b9b8b8, 95))

// 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.new(#9a6c00, 0), text_size = textSizeInput)
// }
