Bollinger Bands Heatmap (BBH)
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © peacefulLizard50262

//@version=5
indicator("Bollinger Bands Heatmap", "BBH", true, max_boxes_count = 500)

min_max(source, min, max)=>
    (source - min)/(max - min)

rescale_invert(x, n) =>
    inverted = 1 - x
    rescaled = (1 - inverted) * n + inverted * 100
    rescaled

gaussian(x, mu, sigma) =>
    (1.0 / (sigma * math.sqrt(2.0 * math.pi))) * math.exp(-((x - mu)*(x - mu)) / (2.0 * sigma * sigma))

scale = input.float(1, "Scale", minval = 0.125, step = 0.125, tooltip = "Scale the size of the heatmap.")
atr_length = input.int(100, "Scale ATR Length", minval = 5, tooltip = "The ATR used to scale the heatmap boxes.")
offset1 = input.float(0, "Offset", tooltip = "Offset mean by ATR.")

multiplier = input.float(2, "Multiplier", minval = 0, step = 0.2, tooltip = "Bollinger Bands Multiplier")
length = input.int(20, "Length", tooltip = "Length of SMA.")

alpha = input.int(60, "Heat Map Alpha", minval = 0, maxval = 100)

high_color = input.color(#2962ff, "Color", inline = "Colour") 
low_color = input.color(#880e4f, "", inline = "Colour")

average = ta.sma(close, length)
sigma = ta.stdev(close, length) * multiplier

atr = ta.cum(high - low)/bar_index

mu = average + (offset1 > 0 ? atr/4 * offset1 : -(atr/4 * -offset1))

step = ta.atr(atr_length) * scale

boxes = array.new<box>()
source_distribution = array.new<float>()
source_values = array.new<float>()

flag = bar_index > length
if flag
    max_val = gaussian(mu, mu, sigma) 

    float p = 1
    int k = 0
    while p != 0
        value_start = average + step * k
        value_end = average + step * (k + 1)
        p := gaussian(value_start, mu, sigma) 
        colour = color.new(color.from_gradient(p, 0, max_val, low_color, high_color), rescale_invert(p/max_val, alpha))
        array.push(boxes,
         box.new(bar_index, value_end - step/2, bar_index - 1, value_start - step/2, colour, bgcolor = colour)
         )
        k += 1

    k := 0
    p := 1

    while p != 0
        value_start = average - step * k
        value_end = average - step * (k + 1)
        p := gaussian(value_start, mu, sigma)  
        colour = color.new(color.from_gradient(p, 0, max_val, low_color, high_color), rescale_invert(p/max_val, alpha))
        array.push(boxes,
         box.new(bar_index, value_end - step/2, bar_index - 1, value_start - step/2, colour, bgcolor = colour)
         )
        k += 1

