Bar Magnified Volume Profile/Fixed Range [ChartPrime]
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © ExoMaven

//@version=5
indicator(title = "Bar Magnified Volume Profile/Fixed Range [ChartPrime]", shorttitle = "Bar Magnified Volume Profile/Fixed Range [ChartPrime]", overlay = true, max_boxes_count = 100, max_bars_back = 5000)


//░█████╗░██╗░░██╗░█████╗░██████╗░████████╗  ██████╗░██████╗░██╗███╗░░░███╗███████╗
//██╔══██╗██║░░██║██╔══██╗██╔══██╗╚══██╔══╝  ██╔══██╗██╔══██╗██║████╗░████║██╔════╝
//██║░░╚═╝███████║███████║██████╔╝░░░██║░░░  ██████╔╝██████╔╝██║██╔████╔██║█████╗░░
//██║░░██╗██╔══██║██╔══██║██╔══██╗░░░██║░░░  ██╔═══╝░██╔══██╗██║██║╚██╔╝██║██╔══╝░░
//╚█████╔╝██║░░██║██║░░██║██║░░██║░░░██║░░░  ██║░░░░░██║░░██║██║██║░╚═╝░██║███████╗
//╚════╝░╚═╝░░╚═╝╚═╝░░╚═╝╚═╝░░╚═╝░░░╚═╝░░░  ╚═╝░░░░░╚═╝░░╚═╝╚═╝╚═╝░░░░░╚═╝╚══════╝

//--------------------------------------------------------------------------------


//░█████╗░██████╗░███████╗░█████╗░████████╗███████╗██████╗░  ██████╗░██╗░░░██╗
//██╔══██╗██╔══██╗██╔════╝██╔══██╗╚══██╔══╝██╔════╝██╔══██╗  ██╔══██╗╚██╗░██╔╝
//██║░░╚═╝██████╔╝█████╗░░███████║░░░██║░░░█████╗░░██║░░██║  ██████╦╝░╚████╔╝░
//██║░░██╗██╔══██╗██╔══╝░░██╔══██║░░░██║░░░██╔══╝░░██║░░██║  ██╔══██╗░░╚██╔╝░░
//╚█████╔╝██║░░██║███████╗██║░░██║░░░██║░░░███████╗██████╔╝  ██████╦╝░░░██║░░░
//░╚════╝░╚═╝░░╚═╝╚══════╝╚═╝░░╚═╝░░░╚═╝░░░╚══════╝╚═════╝░  ╚═════╝░░░░╚═╝░░░

//███████╗██╗░░██╗░█████╗░███╗░░░███╗░█████╗░██╗░░░██╗███████╗███╗░░██╗
//██╔════╝╚██╗██╔╝██╔══██╗████╗░████║██╔══██╗██║░░░██║██╔════╝████╗░██║
//█████╗░░░╚███╔╝░██║░░██║██╔████╔██║███████║╚██╗░██╔╝█████╗░░██╔██╗██║
//██╔══╝░░░██╔██╗░██║░░██║██║╚██╔╝██║██╔══██║░╚████╔╝░██╔══╝░░██║╚████║
//███████╗██╔╝╚██╗╚█████╔╝██║░╚═╝░██║██║░░██║░░╚██╔╝░░███████╗██║░╚███║
//╚══════╝╚═╝░░╚═╝░╚════╝░╚═╝░░░░░╚═╝╚═╝░░╚═╝░░░╚═╝░░░╚══════╝╚═╝░░╚══╝

//-------------------------------------------------------------------------------

// user input
lookback = input.int(title = "Lookback Period", defval = 100, maxval = 1000, group = "Main Configuration", tooltip = "Number of bars to search for highest high and highest low to create a price range")
segs = input.int(title = "Number Of Levels", defval = 100, maxval = 100, group = "Main Configuration", tooltip = "Number of volume grids/nodes for the volume profile")
n_skew = input.int(title = "Profile Length", defval = 25, minval = 10, maxval = 1000, group = "Main Configuration", tooltip = "Length of the volume profile. Higher = Wider, Lower = Skinnier")
n_shift = input.int(title = "Profile Offset", defval = 35, group = "Main Configuration", tooltip = "Offset the entire volume profile left or right. Higher = Right, Lower = Left")
high_volume_color = input.color(color.new(color.green, 10), "High Volume", inline = "Colors", group = "Cosmetic Settings")
low_volume_color = input.color(color.new(color.blue, 10), "Low Volume", inline = "Colors", group = "Cosmetic Settings")
activity_bull_color = input.color(color.new(color.orange, 10), "Above Point Of Interest", inline = "POI", group = "Cosmetic Settings")
activity_bear_color = input.color(color.new(color.orange, 10), "Below Point Of Interest", inline = "POI", group = "Cosmetic Settings")

// data
highest_high = ta.highest(high, lookback)
lowest_low = ta.lowest(low, lookback)

// lower timeframe data extraction
tf = timeframe.isdaily ? 1440 : timeframe.isweekly ? 1440 * 7 : timeframe.ismonthly ? 1440 * 30 : str.tonumber(timeframe.period)
req_tf = math.round(tf / 16) < 1 ? "30S" : str.tostring(math.round(tf / 16))
[h, l, v] = request.security_lower_tf(syminfo.tickerid, req_tf, [high, low, volume])

// arrays
var borders = array.new_float()
var boxes = array.new_box()
var box_vols = array.new_float()

var line activity_line = na

// initialize box arrays on first bar
if barstate.isfirst
    for i = 0 to segs - 1
        array.push(boxes, box.new(na, na, na, na, xloc = xloc.bar_time, border_color = na))
        array.push(box_vols, 0)

        activity_line := line.new(na, na, na, na, style = line.style_dashed, width = 2)

if barstate.islast
    //define range and create grid
    inc = (highest_high - lowest_low) / segs
    for i = 0 to segs
        if i == 0
            array.clear(borders)
        array.push(borders, highest_high - (inc * i))

    //parse all lower timeframe bars and attribute volume to each grid
    for b = 0 to array.size(boxes) > 0 ? array.size(boxes) - 1 : na
        box_ = array.get(boxes, b)
        box_vol = 0.0
        t_border = array.get(borders, b)
        b_border = array.get(borders, b + 1)

        for q = 0 to lookback - 1
            for e = 0 to array.size(h[q]) > 0 ? array.size(h[q]) - 1 : na
                ltf_high = array.get(h[q], e)
                ltf_low = array.get(l[q], e)
                ltf_vol = array.get(v[q], e)

                ltf_diff = ltf_high - ltf_low

                if ltf_low <= t_border and ltf_high >= b_border
                    top_register = math.min(ltf_high, t_border)
                    bot_register = math.max(ltf_low, b_border)

                    register_diff = top_register - bot_register
                    register_vol = register_diff / ltf_diff
                    box_vol += nz(register_vol * ltf_vol)
        array.set(box_vols, b, box_vol)

    //update box objects with proper volume, color, and position
    max_vol = array.max(box_vols)
    min_vol = array.min(box_vols)
    shift = (time - time[n_shift])
    skew = (time - time[n_skew]) / max_vol
    for b = 0 to array.size(boxes) > 0 ? array.size(boxes) - 1 : na
        box_ = array.get(boxes, b)
        box_vol = array.get(box_vols, b)
        t_border = array.get(borders, b)
        b_border = array.get(borders, b + 1)

        box.set_lefttop(box_, (time + shift) - int(box_vol * skew), t_border)
        box.set_rightbottom(box_, time + shift, b_border)
        array.set(box_vols, b, box_vol)

        box.set_bgcolor(box_, color.from_gradient(box_vol, min_vol, max_vol, low_volume_color, high_volume_color))

        if box_vol == max_vol
            mid = (t_border + b_border) / 2
            line.set_xloc(activity_line, time, (time + shift) - int(box_vol * skew), xloc.bar_time)
            line.set_y1(activity_line, mid)
            line.set_y2(activity_line, mid)
            line.set_color(activity_line, close > mid ? activity_bull_color : activity_bear_color)