Tick Profile Heatmap
//  This work is licensed under a Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) https://creativecommons.org/licenses/by-nc-sa/4.0/
//  Copyright syntaxgeek, coleJustice, LuxAlgo (https://www.tradingview.com/script/5MggSfGG-Volume-Profile-LUX/)

//  A use of the volume profile based on code by coleJustice which originally utilized LuxAlgo, I wanted to have a profile of where price aligned with TICK extreme prints
//  This makes zero use of volume as the original code had done, so please do not confuse this with volume profile.
//  I also adjusted row size to the max to present the highest resolution as was possible as I felt it necessary for the intended usecase.

//  A variation of a Volume Profile based on code originally by LuxAlgo. The traditional bar chart is replaced with full-width bars that are brighter for high volume price levels.
//  Like a traditional VP, the purpose is to visualise how volume corresponds to specific price levels, allowing you to get a quick idea of where the most activity is occurring,
//  and where it hasn't been. This information may provide clues as to where price action may return, areas of support and resistance, and regions where price may move quickly.

//  Inputs are set up such that you can customize the lookback period, number of rows, and width of rows for most major timeframes individually.
//  Timeframes between those available will use the next lower timeframe settings (e.g., 2m chart will use the 1m settings.)

//  This indicator is experimental and is likely to receive further updates.


//@version=5
indicator('Tick Profile Heatmap', 'Tick Profile Heatmap', true, max_bars_back=1000, max_boxes_count=500)

import PineCoders/VisibleChart/3 as PCvc

tickSymbol = input.symbol("USI:TICK", "TICK Variant", "NYSE Tick is default but there are others for NASDAQ, AMEX, etc.", group="TICK")
tickSource = input.source(hlc3, "TICK Source", "High and low are always utilized for threshold evaluation, but the source utilized for cumulative is up to user.")
tickUpperThreshold = input.int(500, "TICK Upper Threshold", group="TICK Thresholds", inline="Thresholds")
tickLowerThreshold = input.int(-500, "TICK Lower Threshold", group="TICK Thresholds", inline="Thresholds")

useVisibleRange = input.bool(true, "Use Visible Range", inline="vr", group='     Lookback       # Rows')
numRows_vr         = input.int(500, title='', minval=1, maxval=500, inline="vr", group='     Lookback       # Rows')

lookbackLength_1m  = input.int(780, ' 1m', minval=1, maxval=1000, group='     Lookback       # Rows', inline="1m")
numRows_1m         = input.int(250, title='', minval=1, maxval=500, group='     Lookback       # Rows', inline="1m", 
  tooltip = "Lookback period (in bars) and number of rows for each timeframe. Timeframes between those defined here use the next closest lower timeframe.")
lookbackLength_5m  = input.int(390, ' 5m', minval=1, maxval=1000, group='     Lookback       # Rows', inline="5m")
numRows_5m         = input.int(500, title='', minval=1, maxval=500, group='     Lookback       # Rows', inline="5m")
lookbackLength_15m = input.int(260, '15m', minval=1, maxval=1000, group='     Lookback       # Rows', inline="15m")
numRows_15m        = input.int(500, title='', minval=1, maxval=500, group='     Lookback       # Rows', inline="15m")
lookbackLength_30m = input.int(260, '30m', minval=1, maxval=1000, group='     Lookback       # Rows', inline="30m")
numRows_30m        = input.int(500, title='', minval=1, maxval=500, group='     Lookback       # Rows', inline="30m")
lookbackLength_1h  = input.int(188, ' 1h', minval=1, maxval=1000, group='     Lookback       # Rows', inline="1h")
numRows_1h         = input.int(500, title='', minval=1, maxval=500, group='     Lookback       # Rows', inline="1h")
lookbackLength_1d  = input.int(120, '  D', minval=1, maxval=1000, group='     Lookback       # Rows', inline="1d")
numRows_1d         = input.int(500, title='', minval=1, maxval=500, group='     Lookback       # Rows', inline="1d")
lookbackLength_1w  = input.int(75,  '  W', minval=1, maxval=1000, group='     Lookback       # Rows', inline="1w")
numRows_1w         = input.int(500, title='', minval=1, maxval=500, group='     Lookback       # Rows', inline="1w")

lookbackLength_fallback   = input.int(100, '___', minval=1, maxval=1000, group='     Lookback       # Rows', inline="fallback")
numRows_fallback          = input.int(250, title='', minval=1, maxval=500, group='     Lookback       # Rows', inline="fallback",
  tooltip='These values are used for any periods not captured between the above.')

PoCThickness = input.int(4, title="Point of Control    ", tooltip = "Thickness and color of the Point of Control Plot", group="Style", inline="poc")
PoCColor     = input.color(color.orange, title="", group="Style", inline="poc")
lowVolColor  = input.color(color.new(color.lime, 30), title="Volume Gradient   ", inline="grad", group="Style", tooltip = "Colors for the volume rows from Highest to Lowest.")
highVolColor = input.color(color.new(color.white, 99), title="", inline="grad", group="Style")
gradientMult = input.float(1.5, minval=0.1, step=0.1, title="Gradient Multiplier", group="Style", tooltip="Adjusts sharpness of the gradient by emphasizing bright areas and deemphasizing dim areas.")

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

isNewPeriod = ta.change(time('D'))

var float _chartTfInMinutes = timeframe.multiplier * (timeframe.isseconds ? 1. / 60 : timeframe.isminutes ? 1. : timeframe.isdaily ? 60. * 24 : timeframe.isweekly ? 60. * 24 * 7 : timeframe.ismonthly ? 60. * 24 * 30.4375 : na)

lookbackLength = _chartTfInMinutes < 5 ? lookbackLength_1m :
  _chartTfInMinutes < 15 ? lookbackLength_5m :
  _chartTfInMinutes < 30 ? lookbackLength_15m :
  _chartTfInMinutes < 60 ? lookbackLength_30m :
  _chartTfInMinutes < 120 ? lookbackLength_1h :
  _chartTfInMinutes < 10080 ? lookbackLength_1d :
  _chartTfInMinutes == 10080 ? lookbackLength_1w : lookbackLength_fallback
  
numRows = useVisibleRange ? numRows_vr : 
  _chartTfInMinutes < 5 ? numRows_1m :
  _chartTfInMinutes < 15 ? numRows_5m :
  _chartTfInMinutes < 30 ? numRows_15m :
  _chartTfInMinutes < 60 ? numRows_30m :
  _chartTfInMinutes < 120 ? numRows_1h :
  _chartTfInMinutes < 10080 ? numRows_1d :
  _chartTfInMinutes == 10080 ? numRows_1w : numRows_fallback

//Below is a modified version of the code written by LuxAlgo for https://www.tradingview.com/script/5MggSfGG-Volume-Profile-LUX/
var rowsBoxes = array.new_box()
if barstate.isfirst
    for i = 0 to numRows - 1 by 1
        array.push(rowsBoxes, box.new(na, na, na, na))
var pocLine = line.new(na, na, na, na, width=2)

if (useVisibleRange)
    lookbackLength := PCvc.bars() + 1
    lookbackLength_fallback := PCvc.bars() + 1
    
leftBarIndex = PCvc.leftBarIndex()
rightBarIndex = PCvc.rightBarIndex()
rightBarZeroIndex = bar_index - rightBarIndex

highest = useVisibleRange ? PCvc.high() : ta.highest(lookbackLength)
lowest  = useVisibleRange ? PCvc.low() : ta.lowest(lookbackLength)
priceRange = (highest-lowest)/numRows

line poc = na
box rowBox = na
levels = array.new_float(0)
sumVol = array.new_float(0)

    
// Table Dashboard (8 columns) For Debugging during dev
//tablePosition = position.top_center
//var table moodTable = table.new(tablePosition, 3, 1, border_width = 3)

//f_fillCell(_column, _row, _cellText, _c_color) =>
    //table.cell(moodTable, _column, _row, _cellText, bgcolor = color.new(_c_color, 75), text_color = _c_color)

[v_marketTickHigh, v_marketTickLow, v_marketTickSetSource] = request.security(tickSymbol, timeframe.period, [high, low, tickSource])

if barstate.islastconfirmedhistory
    //f_fillCell(0, 0, "leftMost: " + str.tostring(leftBarIndex), color.white)
    //f_fillCell(1, 0, "rightMost: " + str.tostring(rightBarIndex), color.white)
    //f_fillCell(2, 0, "bars: " + str.tostring(rightBarZeroIndex), color.white)
    for i = 0 to numRows by 1
        array.push(levels, lowest + i / numRows * (highest - lowest))
    for j = 0 to numRows - 1 by 1
        sum = 0.
        for k = rightBarZeroIndex to lookbackLength + rightBarZeroIndex - 1 by 1
            if v_marketTickHigh > tickUpperThreshold or v_marketTickLow < tickLowerThreshold
                sum := high[k] > array.get(levels, j) and low[k] < array.get(levels, j + 1) ? sum + v_marketTickSetSource[k] : sum
        array.push(sumVol, sum)
    for j = 0 to numRows - 1 by 1
        mult = math.pow(array.get(sumVol, j) / array.max(sumVol), gradientMult)
        rowBox := array.get(rowsBoxes, j)
        volumeLevel = array.get(levels, j)
        
        box.set_lefttop(rowBox, bar_index - lookbackLength - rightBarZeroIndex, volumeLevel-(priceRange/2))
        box.set_rightbottom(rowBox, bar_index - rightBarZeroIndex, volumeLevel + (priceRange/2))
        box.set_bgcolor(rowBox, color.from_gradient(mult * 99, 0, 100, highVolColor, lowVolColor))
        box.set_border_width(rowBox, 0)
               
        if mult == 1
            avg = math.avg(volumeLevel, array.get(levels, j + 1))
            line.set_xy1(pocLine, bar_index - lookbackLength - rightBarZeroIndex + 1, avg)
            line.set_xy2(pocLine, bar_index, avg)
            line.set_color(pocLine, PoCColor)
            line.set_style(pocLine, line.style_dotted)
            line.set_width(pocLine, PoCThickness)
            