Aggregated Volume Profile 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/P2XgvcKM-Aggregated-Volume-Profile-Spot-Futures/
// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— \\
// @version=5
indicator(title='Aggregated Volume Profile Spot & Futures', shorttitle='Aggregated Volume Profile', overlay=true, precision=4, linktoseries=true, max_bars_back=1000, max_lines_count=500)

// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— \\
// —————— Tips
TTmode       = '𝐃𝐚𝐭𝐚 𝐓𝐲𝐩𝐞 ➖ Choose Single or Aggregated data.'
TTspot       = 'Spot Volume Pairs'
TTperp       = 'Futures Volume Pairs'
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     = '➤ ＳＥＴＴＩＮＧＳ'
coinusd      = input.string('USD',           ' Volume By',                          group = groupset, inline = 'MODE', options = ['COIN', 'USD', 'EUR', 'RUB'])
datatype     = input.string('Aggregated',    ' Data Type   ',                       group = groupset, inline = 'MODE', options = ['Aggregated', 'Single'], tooltip = TTmode)
delta_type   = input.string('Both',          ' Delta Type',                         group = groupset, inline = 'DELT', options = ['Both', 'Bullish', 'Bearish'])
lookback     = input.int   (250,             ' Volume Lookback Depth',              group = groupset, minval = 10, maxval = 1000, step = 10)
max_bars     = input.int   (500,             ' Number of Bars',                     group = groupset, minval = 10, maxval = 500, step = 25)
bar_mult     = input.int   (35,              ' Bar Length Multiplier',              group = groupset, minval = 10, maxval = 100, step = 5)
bar_offset   = input.int   (35,              ' Bar Horizontal Offset',              group = groupset, minval = 0, maxval = 100, step = 5)
bar_width    = input.int   (1,               ' Bar Width',                          group = groupset, minval = 1, maxval = 20)
poc_show     = input.bool  (true,            ' Show POC Line',                      group = groupset, inline = 'DELT')
bar_color    = input.color (#335367,       ' Bar Color',                          group = groupset, inline = 'COLR')
poc_color    = input.color (#a13c44,       ' POC Color',                          group = groupset, inline = 'COLR')

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)

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)

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 = 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)))

// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— \\
// —————————————————————————————————————————————————   Volume Profile   ——————————————————————————————————————————————————— \\
// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— \\
float Vmax = 0.0
int VmaxId = 0
var int first = time
price_levels = array.new_float(max_bars + 1, 0.0)
interval_volumes = array.new_float(max_bars, 0.0)
volumes_totals = array.new_float(max_bars, 0.0)
bar_widths = array.new_int(max_bars, 0)

float HH = ta.highest(high, lookback)
float LL = ta.lowest(low, lookback)

if barstate.islast
    float HL = (HH - LL) / max_bars
    for j = 1 to max_bars + 1 by 1
        array.set(price_levels, j - 1, LL + HL * j)
    for i = 0 to lookback - 1 by 1
        int j_interval_count = 0
        array.fill(volumes_totals, 0.0)
        for j = 0 to max_bars - 1 by 1
            float j_price_level = array.get(price_levels, j)
            if low[i] < j_price_level and high[i] > j_price_level and (delta_type == 'Bullish' ? close[i] >= open[i] : delta_type == 'Bearish' ? close[i] <= open[i] : true)
                float j_volumes_totals = array.get(volumes_totals, j)
                float new_volume_total = j_volumes_totals + nz(Volume[i])
                array.set(volumes_totals, j, new_volume_total)
                j_interval_count += 1
                j_interval_count
        for j = 0 to max_bars - 1 by 1
            float j_interval_volume = array.get(interval_volumes, j)
            float j_volumes_totals = array.get(volumes_totals, j)
            float dj_interval_volume = j_interval_volume + (j_interval_count > 0 ? j_volumes_totals / j_interval_count : 0.0)
            array.set(interval_volumes, j, dj_interval_volume)
    Vmax := array.max(interval_volumes)
    VmaxId := array.indexof(interval_volumes, Vmax)
    for j = 0 to max_bars - 1 by 1
        float j_interval_volume = array.get(interval_volumes, j)
        int j_bar_widths = math.round(bar_mult * j_interval_volume / Vmax)
        array.set(bar_widths, j, j_bar_widths)

if barstate.isfirst
    first := time
    first
change = ta.change(time)
x_loc = timenow + math.round(change * bar_offset)

f_setup_bar(n) =>
    x1 = VmaxId == n and poc_show ? math.max(time[lookback], first) : timenow + math.round(change * (bar_offset - array.get(bar_widths, n)))
    ys = array.get(price_levels, n)
    line.new(x1=x1, y1=ys, x2=x_loc, y2=ys, xloc=xloc.bar_time, extend=extend.none, color=VmaxId == n ? poc_color : bar_color, style=line.style_solid, width=bar_width)

if barstate.islast
    for i = 0 to max_bars - 1 by 1
        f_setup_bar(i)

// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— \\