Aggregated Volume Spot & Futures
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// @HALDRO Project // —————— https://www.tradingview.com/script/T6dlVzTV-Aggregated-Volume-CVD-Liquidations-Delta/
// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— \\
// @version=5
indicator(title='Aggregated Volume Spot & Futures', shorttitle='Aggregated Volume', format=format.volume)

// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— \\
// —————— Tips
TTmode       = '𝐕𝐨𝐥𝐮𝐦𝐞 ➖ Shows the volume selected in tab "𝐕𝐨𝐥𝐮𝐦𝐞 𝐁𝐲" \n𝐕𝐨𝐥𝐮𝐦𝐞 (𝐂𝐨𝐥𝐨𝐫𝐞𝐝) ➖ Shows aggregated volume but applying different volume colors for different exchanges. \n𝐃𝐞𝐥𝐭𝐚 ➖ Displays the difference between the number of buyers and sellers. ❢ \n𝐂𝐮𝐦𝐮𝐥𝐚𝐭𝐢𝐯𝐞 𝐃𝐞𝐥𝐭𝐚 ➖ Displays the cumulative delta between sellers and buyers. ❢ \n𝐒𝐩𝐨𝐭 & 𝐏𝐞𝐫𝐩 ➖ Shows Spot and Futures volume at the same time. \n𝐃𝐞𝐥𝐭𝐚 (𝐒𝐩𝐨𝐭 - 𝐏𝐞𝐫𝐩) ➖ Shows the difference between Spot and Futures volume. \n𝐋𝐢𝐪𝐮𝐢𝐝𝐚𝐭𝐢𝐨𝐧𝐬 ➖ Displays potential Liquidations(The difference in volume between Futures and Spots). ❢ \n𝐎𝐁𝐕 ➖ On Balance Volume. \n𝐌𝐅𝐈 ➖ Money Flow Indicator. \n𝐃𝐚𝐭𝐚 𝐓𝐲𝐩𝐞 ➖ Choose Single or Aggregated data \n\n ❢ ➖ With this sign I marked the functions that are calculated by Me, it is not exactly the original data from the exchange. It is impossible to calculate them perfectly because TV gives only the total volume from the exchanges.'
TTtype       = 'Be Careful, Reacts to every mode.'
TTvol        = 'Show Current Volume.'
TTtable      = 'Table shows the current volume of the last candle. Also in the mode "Colored" shows the color of exchanges if they have volume.'
TTlen        = "Lookback period for 'Cumulative Delta' and for 'MFI'."
TTspot       = 'Spot Volume'
TTperp       = 'Futures Volume'
TTEX         = 'You can write any name of the exchange in any field in the Exchanges group and get the result. Example: BITMEX\nHUOBI\nMEXC\nGATEIO and others. \n*Be careful and check if there is such a ticker on TV.'

// —————— Inputs
groupset     = '➤ ＳＥＴＴＩＮＧＳ'
mode         = input.string('Volume',        ' Mode     ',           group = groupset, inline = 'MODE', options = ['Volume', 'Volume (Colored)', 'Delta', 'Cumulative Delta', 'Spot & Perp', 'Delta (Spot - Perp)', 'Liquidations', 'OBV', 'MFI'])
datatype     = input.string('Aggregated',    ' Data Type   ',        group = groupset, inline = 'MODE', options = ['Aggregated', 'Single'], tooltip = TTmode)
coinusd      = input.string('USD',           ' Volume By ',          group = groupset, inline = 'VOLM', options = ['COIN', 'USD', 'EUR', 'RUB'])
calctype     = input.string('SUM',           ' Calculate By ',       group = groupset, inline = 'VOLM', options = ['SUM', 'AVG', 'MEDIAN', 'VARIANCE'], tooltip = TTtype)
malen        = input.int   (21,              ' MA Period ',          group = groupset, inline = 'CIPS', minval=1)
showMA       = input.bool  (false,           ' Show MA',             group = groupset, inline = 'CIPS')
universalen  = input.int   (14,              ' Lookback  ',          group = groupset, inline = 'OTHR')
FilterPeriod = input.int   (100,             ' Liquidation Filter',  group = groupset, inline = 'OTHR', minval=1, tooltip = TTlen)
showtable    = input.bool  (true,            ' Show Table',          group = groupset, inline = 'CIPS', tooltip = TTtable)

groupexc     = '➤ ＥＸＣＨＡＮＧＥＳ'
EX_NO1       = input.bool  (true,            '1',                    group = groupexc, inline = 'EX1')
IN_NO1       = input.string('BINANCE',       '',                     group = groupexc, inline = 'EX1')
EX_NO2       = input.bool  (true,            '2',                    group = groupexc, inline = 'EX1')
IN_NO2       = input.string('BYBIT',         '',                     group = groupexc, inline = 'EX1')
EX_NO3       = input.bool  (true,            '3',                    group = groupexc, inline = 'EX1')
IN_NO3       = input.string('OKEX',          '',                     group = groupexc, inline = 'EX1')
EX_NO4       = input.bool  (true,            '4',                    group = groupexc, inline = 'EX2')
IN_NO4       = input.string('COINBASE',      '',                     group = groupexc, inline = 'EX2')
EX_NO5       = input.bool  (true,            '5',                    group = groupexc, inline = 'EX2')
IN_NO5       = input.string('BITFINEX',      '',                     group = groupexc, inline = 'EX2')
EX_NO6       = input.bool  (true,            '6',                    group = groupexc, inline = 'EX2')
IN_NO6       = input.string('BITSTAMP',      '',                     group = groupexc, inline = 'EX2')
EX_NO7       = input.bool  (true,            '7',                    group = groupexc, inline = 'EX3')
IN_NO7       = input.string('DERIBIT',       '',                     group = groupexc, inline = 'EX3')
EX_NO8       = input.bool  (true,            '8',                    group = groupexc, inline = 'EX3')
IN_NO8       = input.string('PHEMEX',        '',                     group = groupexc, inline = 'EX3')
EX_NO9       = input.bool  (true,            '9',                    group = groupexc, inline = 'EX3')
IN_NO9       = input.string('MEXC',          '',                     group = groupexc, inline = 'EX3', tooltip = TTEX)

groupcur     = '➤ ＣＵＲＲＥＮＣＹ'
PERP1        = input.bool  (true,            '  USD.P     ',         group = groupcur, inline = 'CUR1')
PERP2        = input.bool  (true,            '  USDT.P ',            group = groupcur, inline = 'CUR1', tooltip = TTperp)
SPOT1        = input.bool  (true,            '  USDT   ',            group = groupcur, inline = 'CUR2')
SPOT2        = input.bool  (true,            '᠎᠎᠎᠎᠎᠎᠎᠎᠎᠎᠎᠎᠎᠎᠎᠎᠎᠎᠎᠎᠎᠎᠎᠎',                     group = groupcur, inline = 'CUR2')
XSPOT2       = input.string('USD',           '᠎',                     group = groupcur, inline = 'CUR2', options = ['USD', 'USDC', 'BUSD', 'DAI', 'EUR'], tooltip = TTspot)

VAREUR = request.security('FX_IDC:EURUSD', timeframe.period, close)
VARRUB = request.security('(MOEX:USDRUB_TOM+MOEX:USDRUB_TOD)/2', timeframe.period, close)
// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— \\
// —————— Aggregated Volume Calculate
Vtype = str.lower(syminfo.volumetype)
CurrentVolume = Vtype=='tick' or Vtype=='quote' or Vtype=='usd' ? volume / close : volume

GetTicker(exchange, typeSymbol) =>
    GetTicker = exchange + ':' + syminfo.basecurrency + typeSymbol
    FixBitmex = exchange == 'BITMEX' ? str.replace(GetTicker, 'BTC', 'XBT') : GetTicker
    Ticker    = FixBitmex

GetRequest(Ticker) =>
    GetVolume = request.security(Ticker, timeframe.period, CurrentVolume, ignore_invalid_symbol=true)
    CheckVolume = GetVolume[1] > 0 ? GetVolume : 0

GetExchange(typeSymbol)=>
    EX_1 = EX_NO1 ? GetRequest( GetTicker(IN_NO1, typeSymbol) ) : 0
    EX_2 = EX_NO2 ? GetRequest( GetTicker(IN_NO2, typeSymbol) ) : 0
    EX_3 = EX_NO3 ? GetRequest( GetTicker(IN_NO3, typeSymbol) ) : 0
    EX_4 = EX_NO4 ? GetRequest( GetTicker(IN_NO4, typeSymbol) ) : 0
    EX_5 = EX_NO5 ? GetRequest( GetTicker(IN_NO5, typeSymbol) ) : 0
    EX_6 = EX_NO6 ? GetRequest( GetTicker(IN_NO6, typeSymbol) ) : 0
    EX_7 = EX_NO7 ? GetRequest( GetTicker(IN_NO7, typeSymbol) ) : 0
    EX_8 = EX_NO8 ? GetRequest( GetTicker(IN_NO8, typeSymbol) ) : 0
    EX_9 = EX_NO9 ? GetRequest( GetTicker(IN_NO9, typeSymbol) ) : 0
    [EX_1, EX_2, EX_3, EX_4, EX_5, EX_6, EX_7, EX_8, EX_9]

EditVolume(type)=>
    Type = calctype == 'AVG' ? array.avg(type) : calctype == 'MEDIAN' ? array.median(type) : calctype == 'VARIANCE' ? array.variance(type) : array.sum(type)
    Volume = coinusd == 'USD' ? Type*close : coinusd == 'EUR' ? Type*(1/VAREUR)*close : coinusd == 'RUB' ? Type*(VARRUB/1)*close : Type 
    Volume

// —————— Get Volume 
[PERP2_1, PERP2_2, PERP2_3, PERP2_4, PERP2_5, PERP2_6, PERP2_7, PERP2_8, PERP2_9] = GetExchange(PERP1 ? 'USDT.P' : '0')
[PERP1_1, PERP1_2, PERP1_3, PERP1_4, PERP1_5, PERP1_6, PERP1_7, PERP1_8, PERP1_9] = GetExchange(PERP2 ? 'USD.P'  : '0')
[SPOT1_1, SPOT1_2, SPOT1_3, SPOT1_4, SPOT1_5, SPOT1_6, SPOT1_7, SPOT1_8, SPOT1_9] = GetExchange(SPOT1 ? 'USDT'   : '0')
[SPOT2_1, SPOT2_2, SPOT2_3, SPOT2_4, SPOT2_5, SPOT2_6, SPOT2_7, SPOT2_8, SPOT2_9] = GetExchange(SPOT2 ? XSPOT2   : '0')

// —————— Processing Volume
GetSpotEX_1 = EditVolume(array.from(SPOT1_1, SPOT2_1)),     GetPerpEX_1 = EditVolume(array.from(PERP1_1, PERP2_1))
GetSpotEX_2 = EditVolume(array.from(SPOT1_2, SPOT2_2)),     GetPerpEX_2 = EditVolume(array.from(PERP1_2, PERP2_2))
GetSpotEX_3 = EditVolume(array.from(SPOT1_3, SPOT2_3)),     GetPerpEX_3 = EditVolume(array.from(PERP1_3, PERP2_3))
GetSpotEX_4 = EditVolume(array.from(SPOT1_4, SPOT2_4)),     GetPerpEX_4 = EditVolume(array.from(PERP1_4, PERP2_4))
GetSpotEX_5 = EditVolume(array.from(SPOT1_5, SPOT2_5)),     GetPerpEX_5 = EditVolume(array.from(PERP1_5, PERP2_5))
GetSpotEX_6 = EditVolume(array.from(SPOT1_6, SPOT2_6)),     GetPerpEX_6 = EditVolume(array.from(PERP1_6, PERP2_6))
GetSpotEX_7 = EditVolume(array.from(SPOT1_7, SPOT2_7)),     GetPerpEX_7 = EditVolume(array.from(PERP1_7, PERP2_7))
GetSpotEX_8 = EditVolume(array.from(SPOT1_8, SPOT2_8)),     GetPerpEX_8 = EditVolume(array.from(PERP1_8, PERP2_8))
GetSpotEX_9 = EditVolume(array.from(SPOT1_9, SPOT2_9)),     GetPerpEX_9 = EditVolume(array.from(PERP1_9, PERP2_9))

SPOT   = GetSpotEX_1 + GetSpotEX_2 + GetSpotEX_3 + GetSpotEX_4 + GetSpotEX_5 + GetSpotEX_6 + GetSpotEX_7 + GetSpotEX_8 + GetSpotEX_9
PERP   = GetPerpEX_1 + GetPerpEX_2 + GetPerpEX_3 + GetPerpEX_4 + GetPerpEX_5 + GetPerpEX_6 + GetPerpEX_7 + GetPerpEX_8 + GetPerpEX_9
Volume = datatype == 'Single' ? EditVolume(array.from(CurrentVolume)) : SPOT + PERP + (EditVolume(array.from(CurrentVolume)))

// —————— Color Setup
bull_color = #00d4b1bb
bear_color = #e91e71bb
// —————— Plot Volume
plot(mode == 'Volume' ? Volume : na, 'Volume', color = open < close ?  bull_color : bear_color, style=plot.style_columns)
// —————— Plot MA
plot(showMA and mode == 'Volume' ? ta.ema(Volume, malen) : na, 'Volume MA', color = #884feb, style = plot.style_line)

// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— \\
// —————— OBV
OBV = ta.cum(math.sign(ta.change(close)) * Volume)
plot(mode == 'OBV' ? OBV : na, 'OBV', color = #7E57C2, style=plot.style_line)

// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— \\
// —————— MFI
float upperMFI = math.sum(Volume * (ta.change(hlc3) <= 0.0 ? 0.0 : hlc3), universalen)
float lowerMFI = math.sum(Volume * (ta.change(hlc3) >= 0.0 ? 0.0 : hlc3), universalen)
MFI = 100.0 - (100.0 / (1.0 + upperMFI / lowerMFI))
plot(mode == 'MFI' ? MFI : na, 'MFI', color = #7E57C2, style=plot.style_line)

// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— \\
// —————— SPOT / FUTURES Calculate
DeltaSP = SPOT - PERP
plot(mode == 'Spot & Perp' ? SPOT : na, 'SPOT', color = bull_color, style = plot.style_line)
plot(mode == 'Spot & Perp' ? PERP : na, 'PERP', color = bear_color, style = plot.style_line)
plot(mode == 'Delta (Spot - Perp)' ? DeltaSP : na, 'Delta (Spot - Perp)', color = SPOT > PERP ?  bull_color : bear_color, style = plot.style_columns)

// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— \\
// —————— Liquidations Calculate // Credit @Thomas_Davison
timePeriodMean  = FilterPeriod
// —————— Calculate liquidations as the difference between futures volume and spot volume
difference      = (PERP - SPOT)
direction       = close > open ? bear_color : bull_color // (RED = Short Liquidation)
differenceSum   = math.sum(difference, timePeriodMean)
differenceMean  = differenceSum / timePeriodMean
if difference < differenceMean or difference < 0
    difference := 0
plot(mode == 'Liquidations' ? difference : na, 'Liquidations (RED = Short Liquidation)', color = difference == 0 ? na : direction, style = plot.style_columns)

// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— \\
// —————— DELTA Calculation
deltalen = universalen
// —————— Check Candles
bullCandle = (open < close)
price_spread = (high - low)

// —————— Set lengths
upper_wick = (open < close) ? (high - close) : (high - open)
lower_wick = (open < close) ? (open - low) : (close - low)
body = (high - low) - (upper_wick + lower_wick)

// —————— Convert lengths to percents
upper_wick_percent = upper_wick / (high - low)
lower_wick_percent = lower_wick / (high - low)
body_percent = body / (high - low)

// —————— Calculate Delta
volume_buy = Volume * ((open < close) ? (body_percent + (upper_wick_percent + lower_wick_percent) / 2) : (upper_wick_percent + lower_wick_percent) / 2)
volume_sell = Volume * ((open < close) ? (upper_wick_percent + lower_wick_percent) / 2 : (body_percent + (upper_wick_percent + lower_wick_percent) / 2))
Delta = volume_buy - volume_sell

// —————— Calculate Cumulative Delta
volume_buy_cum_sum = math.sum(volume_buy, deltalen)
volume_sell_cum_sum = math.sum(volume_sell, deltalen)
CumDelta = (volume_buy_cum_sum - volume_sell_cum_sum) 

plot( mode == 'Delta' ? Delta : na, 'Delta', style=plot.style_columns, color= Delta > 0 ? bull_color : bear_color )
plot( mode == 'Cumulative Delta' ? CumDelta : na, 'Cumulative Delta', color=CumDelta > 0 ? bull_color : bear_color, style=plot.style_line)
hline(mode != 'OBV' and mode != 'MFI' ? 0 : na, 'Zero Line', linestyle=hline.style_dotted)

// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— \\
// —————— Get Volume Colored
EXv1 =         (GetSpotEX_1 + GetPerpEX_1)
EXv2 = EXv1  + (GetSpotEX_2 + GetPerpEX_2)
EXv3 = EXv2  + (GetSpotEX_3 + GetPerpEX_3)
EXv4 = EXv3  + (GetSpotEX_4 + GetPerpEX_4)
EXv5 = EXv4  + (GetSpotEX_5 + GetPerpEX_5)
EXv6 = EXv5  + (GetSpotEX_6 + GetPerpEX_6)
EXv7 = EXv6  + (GetSpotEX_7 + GetPerpEX_7)
EXv8 = EXv7  + (GetSpotEX_8 + GetPerpEX_8)
EXv9 = EXv8  + (GetSpotEX_9 + GetPerpEX_9)

// —————— Plot Volume Colored
colEXv1 = input.color(#e1d14d, 'EX 1', group ='Volume (Colored)', inline = 'VC')
colEXv2 = input.color(#db7602, 'EX 2', group ='Volume (Colored)', inline = 'VC')
colEXv3 = input.color(#4289db, 'EX 3', group ='Volume (Colored)', inline = 'VC')
colEXv4 = input.color(#ca43f7, 'EX 4', group ='Volume (Colored)', inline = 'VC')
colEXv5 = input.color(#7104ff, 'EX 5', group ='Volume (Colored)', inline = 'VC')
colEXv6 = input.color(#001aff, 'EX 6', group ='Volume (Colored)', inline = 'VC')
colEXv7 = input.color(#1d813e, 'EX 7', group ='Volume (Colored)', inline = 'VC')
colEXv8 = input.color(#02C99A, 'EX 8', group ='Volume (Colored)', inline = 'VC')
colEXv9 = input.color(#ff3f3c, 'EX 9', group ='Volume (Colored)', inline = 'VC')

plot(EXv9, 'EX 9', style = plot.style_columns, color = colEXv9, display = mode=='Volume (Colored)' ? na : display.none, editable = false)
plot(EXv8, 'EX 8', style = plot.style_columns, color = colEXv8, display = mode=='Volume (Colored)' ? na : display.none, editable = false)
plot(EXv7, 'EX 7', style = plot.style_columns, color = colEXv7, display = mode=='Volume (Colored)' ? na : display.none, editable = false)
plot(EXv6, 'EX 6', style = plot.style_columns, color = colEXv6, display = mode=='Volume (Colored)' ? na : display.none, editable = false)
plot(EXv5, 'EX 5', style = plot.style_columns, color = colEXv5, display = mode=='Volume (Colored)' ? na : display.none, editable = false)
plot(EXv4, 'EX 4', style = plot.style_columns, color = colEXv4, display = mode=='Volume (Colored)' ? na : display.none, editable = false)
plot(EXv3, 'EX 3', style = plot.style_columns, color = colEXv3, display = mode=='Volume (Colored)' ? na : display.none, editable = false)
plot(EXv2, 'EX 2', style = plot.style_columns, color = colEXv2, display = mode=='Volume (Colored)' ? na : display.none, editable = false)
plot(EXv1, 'EX 1', style = plot.style_columns, color = colEXv1, display = mode=='Volume (Colored)' ? na : display.none, editable = false)

// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— \\
// —————— DEBUG Tools
// Check on Errors
var cumVol = 0.
cumVol += nz(Volume)
checkerror = barstate.islast and cumVol == 0 ? "Received error, Volume not found ! 🙄" : na

// —————— PANEL
convertVol = str.format("{0,number}", math.round(Volume))
var table panel = table.new(position = position.bottom_right, columns = 2, rows = 11, bgcolor = #363A45)
if barstate.islast and showtable
    table.cell(panel, 0, 9, text=checkerror+str.tostring(convertVol), text_color = #ffa726, bgcolor = #363A45)

// —————— Volume Colored Table
if mode=='Volume (Colored)' and barstate.islast and showtable
    table.cell(panel, 0, 0, text=IN_NO1, text_color = GetSpotEX_1+GetPerpEX_1>=1 ? colEXv1 : na, bgcolor = #1e222d, text_size = size.normal, text_font_family = font.family_monospace, height = 7)
    table.cell(panel, 0, 1, text=IN_NO2, text_color = GetSpotEX_2+GetPerpEX_2>=1 ? colEXv2 : na, bgcolor = #1e222d, text_size = size.normal, text_font_family = font.family_monospace, height = 7)
    table.cell(panel, 0, 2, text=IN_NO3, text_color = GetSpotEX_3+GetPerpEX_3>=1 ? colEXv3 : na, bgcolor = #1e222d, text_size = size.normal, text_font_family = font.family_monospace, height = 7)
    table.cell(panel, 0, 3, text=IN_NO4, text_color = GetSpotEX_4+GetPerpEX_4>=1 ? colEXv4 : na, bgcolor = #1e222d, text_size = size.normal, text_font_family = font.family_monospace, height = 7)
    table.cell(panel, 0, 4, text=IN_NO5, text_color = GetSpotEX_5+GetPerpEX_5>=1 ? colEXv5 : na, bgcolor = #1e222d, text_size = size.normal, text_font_family = font.family_monospace, height = 7)
    table.cell(panel, 0, 5, text=IN_NO6, text_color = GetSpotEX_6+GetPerpEX_6>=1 ? colEXv6 : na, bgcolor = #1e222d, text_size = size.normal, text_font_family = font.family_monospace, height = 7)
    table.cell(panel, 0, 6, text=IN_NO7, text_color = GetSpotEX_7+GetPerpEX_7>=1 ? colEXv7 : na, bgcolor = #1e222d, text_size = size.normal, text_font_family = font.family_monospace, height = 7)
    table.cell(panel, 0, 7, text=IN_NO8, text_color = GetSpotEX_8+GetPerpEX_8>=1 ? colEXv8 : na, bgcolor = #1e222d, text_size = size.normal, text_font_family = font.family_monospace, height = 7)
    table.cell(panel, 0, 8, text=IN_NO9, text_color = GetSpotEX_9+GetPerpEX_9>=1 ? colEXv9 : na, bgcolor = #1e222d, text_size = size.normal, text_font_family = font.family_monospace, height = 7)
// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— \\