Volatility Percentile
// 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("Volatility Percentile", overlay=false)
import HeWhoMustNotBeNamed/enhanced_ta/10 as eta

amatype = input.string("sma", title="Type", group="ATR", options=["sma", "ema", "hma", "rma", "wma", "vwma", "swma", "linreg", "median"])
amalength = input.int(20, title="Length", group="ATR")
roundingType = input.string("round", title="Rounding Type", group="ATR", options=["round", "floor", "ceil"])
precision = input.int(2, group="ATR", minval=0, maxval=2)
displayLowerPercentile = input.bool(false, "Inverse", group="Display", inline="b")
displayStatsTable = input.bool(true, "Stats", group="Display", inline="b")
displayDetailedStats = input.bool(false, 'Detailed Stats', group="Display", inline="b")

textSize = input.string(size.small, title='Text Size', options=[size.tiny, size.small, size.normal, size.large, size.huge], group='Table', inline='text')
headerColor = input.color(color.maroon, title='Header', group='Table', inline='GC')
neutralColor = input.color(#FFEEBB, title='Cell', group='Table', inline='GC')
presentColor = input.color(color.aqua, title='Present', group='Table', inline='GC')


useStartTime = input.bool(false, title='Start Time', group='Window', inline="start")
startTime = input.time(defval=timestamp('01 Jan 2010 00:00 +0000'), title='', group='Window', inline='start')
useEndTime = input.bool(false, title='End Time ', group='Window', inline="end")
endTime = input.time(defval=timestamp('01 Jan 2099 00:00 +0000'), title='', group='Window', inline='end')
inDateRange = (not useStartTime or time >= startTime) and (not useEndTime or time <= endTime)

precisionMultiplier = int(math.pow(10, precision))
var pArray = array.new_int(100*precisionMultiplier, 0)

float percentile = na
color cellColor = color.blue
if(inDateRange)
    var dateStart = str.tostring(year) + '/' + str.tostring(month) + '/' + str.tostring(dayofmonth)
    dateEnd = str.tostring(year) + '/' + str.tostring(month) + '/' + str.tostring(dayofmonth)
    atrpercent = eta.atrpercent(amatype, amalength)
    index = roundingType == "round"? math.round(atrpercent*precisionMultiplier) :
                 roundingType == "floor"? math.floor(atrpercent*precisionMultiplier) : math.ceil(atrpercent*precisionMultiplier)
    if(index > 0)
        array.set(pArray, index, array.get(pArray, index)+1)

    upperPortion = array.slice(pArray, index+1, array.size(pArray))
    lowerPortion = array.slice(pArray, 0, index)
    
    total = array.sum(pArray)
    upper = array.sum(upperPortion)
    lower = array.sum(lowerPortion)
    same = array.get(pArray, index)
    
    upperPercentile = (total-upper)*100/total
    lowerPercentile = (total-lower)*100/total
    
    cumulativeCount = 0
    medianIndex = 0
    maxIndex = 0
    for i=0 to array.size(pArray)-1
        cumulativeCount += array.get(pArray, i)
        if(cumulativeCount < total/2)
            medianIndex := i+1
        if(cumulativeCount == total)
            maxIndex := i
            break
            
    medianAtrPercent = medianIndex/precisionMultiplier
    maxAtrPercent = maxIndex/precisionMultiplier
    
    medianAndMax = array.join(array.from(str.tostring(medianAtrPercent, format.percent), str.tostring(maxAtrPercent, format.percent)), ' / ')
    displayTable = table.new(position=position.bottom_right, columns=2, rows=3, border_width=1)
    cellColor := color.from_gradient(upperPercentile, 0, 100, displayLowerPercentile? color.red : color.green, displayLowerPercentile ? color.green : color.red)
    
    percentile := displayLowerPercentile? lowerPercentile : upperPercentile

    table.cell(table_id=displayTable, column=0, row=0, text='ATR Percent', bgcolor=color.teal, text_color=color.white, text_size=textSize)
    table.cell(table_id=displayTable, column=1, row=0, text=str.tostring(atrpercent, format.percent), bgcolor=cellColor, text_color=color.white, text_size=textSize)
    
    table.cell(table_id=displayTable, column=0, row=1, text='Median/Max', bgcolor=color.teal, text_color=color.white, text_size=textSize)
    table.cell(table_id=displayTable, column=1, row=1, text=medianAndMax, bgcolor=cellColor, text_color=color.white, text_size=textSize)
    
    table.cell(table_id=displayTable, column=0, row=2, text='Percentile', bgcolor=color.teal, text_color=color.white, text_size=textSize)
    table.cell(table_id=displayTable, column=1, row=2, text=str.tostring(percentile, format.percent), bgcolor=cellColor, text_color=color.white, text_size=textSize)
    
    if(displayDetailedStats and total!=0)
        detailedTable = table.new(position=position.middle_center, columns=21, rows=10, border_width=1)
        table.cell(table_id=detailedTable, column=0, row=0, text=dateStart + ' to ' + dateEnd, bgcolor=color.teal, text_color=color.white, text_size=textSize)
        table.cell(table_id=detailedTable, column=0, row=1, text=syminfo.tickerid, bgcolor=color.teal, text_color=color.white, text_size=textSize)
        
        headerArray = array.new_string()
        valueArray = array.new_string()
        cellColorArray = array.new_color()
        for i=0 to 99
            end = math.min((i+1)*precisionMultiplier, array.size(pArray))
            slice = array.slice(pArray, 0, end)
            count = array.sum(slice)
            row = math.floor(i/10)
            col = (i%10) * 2 + 1
            if (count != 0)
                percent = count*100/total
                headerText = '0 - ' + str.tostring(end/precisionMultiplier)
                cColor = math.floor(index/precisionMultiplier) == i ? presentColor : neutralColor
                
                array.push(headerArray, headerText)
                array.push(valueArray, str.tostring(percent, format.percent))
                array.push(cellColorArray, cColor)
            if(count == total)
                break

        rowCol = math.ceil(math.sqrt(array.size(headerArray)))
        
        counter = 0
        for i=0 to rowCol-1
            for j=0 to rowCol-1
                row = i
                col = j*2 + 1
                if(counter >= array.size(headerArray))
                    break
                table.cell(table_id=detailedTable, column=col, row=row, text=array.get(headerArray, counter), bgcolor=headerColor, text_color=color.white, text_size=textSize)
                table.cell(table_id=detailedTable, column=col + 1, row=row, text=array.get(valueArray, counter), bgcolor=array.get(cellColorArray, counter), text_color=color.black, text_size=textSize)
                counter += 1


plot(displayDetailedStats? na : percentile, title="Historical Volatility Percentile", color=cellColor)

