Interactive Volume Profile - Based on LTF volume
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © HeWhoMustNotBeNamed

//   __    __            __       __  __                  __       __                        __      __    __              __      _______             __    __                                          __ 
//  /  |  /  |          /  |  _  /  |/  |                /  \     /  |                      /  |    /  \  /  |            /  |    /       \           /  \  /  |                                        /  |
//  $$ |  $$ |  ______  $$ | / \ $$ |$$ |____    ______  $$  \   /$$ | __    __   _______  _$$ |_   $$  \ $$ |  ______   _$$ |_   $$$$$$$  |  ______  $$  \ $$ |  ______   _____  ____    ______    ____$$ |
//  $$ |__$$ | /      \ $$ |/$  \$$ |$$      \  /      \ $$$  \ /$$$ |/  |  /  | /       |/ $$   |  $$$  \$$ | /      \ / $$   |  $$ |__$$ | /      \ $$$  \$$ | /      \ /     \/    \  /      \  /    $$ |
//  $$    $$ |/$$$$$$  |$$ /$$$  $$ |$$$$$$$  |/$$$$$$  |$$$$  /$$$$ |$$ |  $$ |/$$$$$$$/ $$$$$$/   $$$$  $$ |/$$$$$$  |$$$$$$/   $$    $$< /$$$$$$  |$$$$  $$ | $$$$$$  |$$$$$$ $$$$  |/$$$$$$  |/$$$$$$$ |
//  $$$$$$$$ |$$    $$ |$$ $$/$$ $$ |$$ |  $$ |$$ |  $$ |$$ $$ $$/$$ |$$ |  $$ |$$      \   $$ | __ $$ $$ $$ |$$ |  $$ |  $$ | __ $$$$$$$  |$$    $$ |$$ $$ $$ | /    $$ |$$ | $$ | $$ |$$    $$ |$$ |  $$ |
//  $$ |  $$ |$$$$$$$$/ $$$$/  $$$$ |$$ |  $$ |$$ \__$$ |$$ |$$$/ $$ |$$ \__$$ | $$$$$$  |  $$ |/  |$$ |$$$$ |$$ \__$$ |  $$ |/  |$$ |__$$ |$$$$$$$$/ $$ |$$$$ |/$$$$$$$ |$$ | $$ | $$ |$$$$$$$$/ $$ \__$$ |
//  $$ |  $$ |$$       |$$$/    $$$ |$$ |  $$ |$$    $$/ $$ | $/  $$ |$$    $$/ /     $$/   $$  $$/ $$ | $$$ |$$    $$/   $$  $$/ $$    $$/ $$       |$$ | $$$ |$$    $$ |$$ | $$ | $$ |$$       |$$    $$ |
//  $$/   $$/  $$$$$$$/ $$/      $$/ $$/   $$/  $$$$$$/  $$/      $$/  $$$$$$/  $$$$$$$/     $$$$/  $$/   $$/  $$$$$$/     $$$$/  $$$$$$$/   $$$$$$$/ $$/   $$/  $$$$$$$/ $$/  $$/  $$/  $$$$$$$/  $$$$$$$/ 
//                                                                                                                                                                                                          
//                                                                                                                                                                                                          
//
//@version=5
indicator("Interactive Volume Profile - Based on LTF volume", shorttitle='LtfVP', overlay=true, max_boxes_count=500, max_labels_count=500)
import HeWhoMustNotBeNamed/arrays/1 as pa

lowerTimeFrame = input.timeframe('1', 'Lower Timeframe', tooltip='Lower the timeframe more granular the calculation will be. But, it also means less number of bars can be used. Should always be less than current timeframe', confirm=true)
applyMaxAvailable = input.bool(false, 'Apply for max available bars', tooltip='If selected will ignore start and end time and draw volume profile for maximum available data', confirm=true)
source = input.string('traded', 'Volume Source', options=['absolute', 'traded'], tooltip='absolute - volume\ntraded - volume*price', confirm=true)
startTime = input.time(0,'Start', tooltip='Time range start for volume profile calculation', confirm=true)
endTime = input.time(0,'End', tooltip='Time range end for volume profile calculation', confirm=true)
numberOfLevels = input.int(100, 'Number of Levels', minval=10, maxval=500, step=50, tooltip='Number of volume bars to display', confirm=true)
transparency = input.int(80, 'Transparency', minval=0, maxval=100, step=10, tooltip='Transparency of volume profile bars')
showBorder = input.bool(true, 'Border', tooltip='Display borders for volume profile bars')

borderWidth = showBorder? 1 : 0
showVolume = input.bool(true, 'Display Volume', tooltip='Display volume on bars')
usePercentile = input.bool(false, 'Percentile', tooltip='Use percentile to define bar length instead of percent')

colorLow = input.color(color.yellow, '', inline='c', group='Colors')
colorHigh = input.color(color.red, '', inline='c', group='Colors')
// referenceType = input.string(xloc.bar_time, 'Drawing reference', options=[xloc.bar_time, xloc.bar_index])
referenceType = xloc.bar_time
var priceArray = array.new<float>()
var volumeArray = array.new<float>()

[tickVolumes, tickPrices] = request.security_lower_tf(syminfo.tickerid, lowerTimeFrame, [source == 'absolute'? volume : (volume*close), close], true)

var startBar = 0
var endBar = 0
var realStartTime = 0
var realEndTime = 0
if(time >= math.min(startTime, endTime) and time <= math.max(startTime, endTime)) or applyMaxAvailable
    if(array.size(tickVolumes) != 0)
        startBar := startBar == 0? (referenceType == xloc.bar_index ? bar_index : time) : startBar
        endBar := (referenceType == xloc.bar_index ? bar_index : time)

    for [index, tickVolume] in tickVolumes
        tickPrice = array.get(tickPrices, index)
        idx = array.binary_search(priceArray, tickPrice)
        if(idx == -1)
            idxi = array.binary_search_rightmost(priceArray, tickPrice)
            array.insert(priceArray, idxi, tickPrice)
            array.insert(volumeArray, idxi, tickVolume)
        else
            array.set(volumeArray, idx, array.get(volumeArray, idx)+tickVolume)
        


highest = array.max(priceArray)
lowest = array.min(priceArray)
levelDiff = (highest-lowest)/numberOfLevels

levelStart = array.new<float>()
levelEnd = array.new<float>()

vProfileMatrix = matrix.new<float>()

startPrice = lowest
while(startPrice < highest)
    st = startPrice
    startPrice := startPrice + levelDiff
    en = startPrice
    matrix.add_col(vProfileMatrix, matrix.columns(vProfileMatrix), array.from(st, en, 0.0))

if(matrix.rows(vProfileMatrix) > 0 and barstate.islast)
    startArray = matrix.row(vProfileMatrix, 0)
    printed = false
    for [index, price] in priceArray
        vol = array.get(volumeArray, index)
        nIndex = math.min(array.binary_search_leftmost(startArray, price), numberOfLevels-1)
        matrix.set(vProfileMatrix, 2, nIndex, matrix.get(vProfileMatrix, 2, nIndex)+vol)


    maxV = array.max(matrix.row(vProfileMatrix, 2))
    var boxArray = array.new<box>()
    var labelArray = array.new<label>()
    pa.clear(boxArray)
    
    if(not applyMaxAvailable)
        var line startLine = na
        var line endLine = na
        line.delete(startLine)
        line.delete(endLine)
        startLine := line.new(startTime, high, startTime, low, extend=extend.both, xloc=xloc.bar_time, color=color.maroon, width=2)
        endLine := line.new(endTime, high, endTime, low, extend=extend.both, xloc=xloc.bar_time, color=color.maroon, width=2)
        
    cVolumeArray = matrix.row(vProfileMatrix, 2)    
    for [i, currentV] in cVolumeArray
        vPercent = usePercentile? array.percentrank(cVolumeArray, i)/100 : currentV/maxV
        left = startBar
        right = math.floor(left + vPercent*(endBar-left))
        bottom = matrix.get(vProfileMatrix, 0, i)
        top = matrix.get(vProfileMatrix, 1, i)
        barcolor = color.new(color.from_gradient(vPercent, 0, 1, colorLow, colorHigh), transparency)
        vText = showVolume? str.tostring(currentV) : ''
        bx = box.new(left, top, right, bottom, bgcolor=barcolor, border_color=barcolor,
                 text=vText, text_size=size.auto, text_color=color.white, text_halign=text.align_left,
                 border_width=borderWidth, xloc=referenceType)
        array.push(boxArray, bx)