Statistics: High & Low timings of custom session; 1yr history
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/

// Getting stats on 15m timeframe only: for high and low timings of custom session
// © twingall

//@version=5

indicator(title='Statistics: 15m high & low timings custom session', overlay=true, max_bars_back =5000)

//user inputs:
timeZone = input.string("America/New_York", group = 'Statistics, Sessions, Timezones', inline = '7', options =["America/New_York", "GMT-10", "GMT-9", "GMT-8", "GMT-7", "GMT-6", "GMT-5", "GMT-4", "GMT-3", "GMT-2", "GMT-1", "GMT", "GMT+1", "GMT+2", "GMT+3", "GMT+4", "GMT+5", "GMT+6", "GMT+7", "GMT+8", "GMT+9", "GMT+10", "GMT+11", "GMT+12", "GMT+13"])
inputSess = input.session("1330-1600", "User Session", group = 'Statistics, Sessions, Timezones', inline = '5')
_daysInput = input.string("ALL", "Day Of Week", group = 'Statistics, Sessions, Timezones', options = ["ALL","CURRENT","MON","TUE","WED","THU","FRI", "SAT", "SUN"], tooltip = "Choose to get stats on specific days of week\n\nLeave set to 'ALL' for indicator to work as before\n\nSample size will obviously be smaller if you select specific days.")

showTable = input.bool(true, "Table", group = "Table Settings", inline ='1' )
horizontalTab = input.bool(true, "show table horizontally",group = "Table Settings", inline ='2', tooltip = "toggle this on when you have an especially long session")
tabPos = input.string(position.top_right, "Position:", group = "Table Settings", inline ='3', options = [ position.top_right, position.top_left, position.top_center,  position.middle_left, position.middle_center, position.middle_right, position.bottom_left, position.bottom_center, position.bottom_right])
txtSize = input.string( size.large, "Text size:",group = "Table Settings",inline ='3', options = [size.auto, size.tiny, size.small, size.normal, size.large, size.huge])
txtCol = input.color(color.red, "Text Color:", group = "Table Settings", inline ='3')

tabCellValueCol = input.color(color.new(color.fuchsia, 67), "Value cells",group = "Table Settings", inline = '5')
tabCellSessCol = input.color(color.new(color.blue, 70), "Session cells",group = "Table Settings", inline = '5')
highTitleCol = input.color(color.new(color.green, 60), "High title cell",group = "Table Settings", inline = '5')
lowTitleCol = input.color(color.new(color.red, 60), "Low title cell",group = "Table Settings", inline = '5')
showPct = input.bool(true, "show % (rather than count) ||",group = "Table Settings", inline = '7')
precision = input.int(0, "decimal precision for %", minval = 0 ,group = "Table Settings", inline = '7')

showSessHighlight= input(defval=true, title='Session Background highlight', inline = '8')
bgCol = input.color(color.new(color.fuchsia, 67), "", inline = '8')

is15min = timeframe.period == "15"

useStartDate = input.bool(false, "use start date | ", inline = "1", group = "~~  Manual Backtesting Period ~~")
startLineCol = input.color(color.red, "start line color", inline = "1", group = "~~  Manual Backtesting Period ~~", tooltip = "this line can be set manually here, or dragged across the chart to change")
startLineWidth = input.int(2, "width", options= [1,2,3,4,5], inline = "1", group = "~~  Manual Backtesting Period ~~")
startDate= input.time(timestamp("20 May 2023 00:00 +0300"), "Start Date", inline = "2", confirm = false, group = "~~  Manual Backtesting Period ~~")

startDateCond = useStartDate?time>startDate:true
var line lnStrt = na
if barstate.islastconfirmedhistory and useStartDate
    lnStrt:=line.new(startDate, high, startDate, high+1, xloc = xloc.bar_time, color= startLineCol, extend = extend.both, width = startLineWidth)
    line.delete(lnStrt[1])
/////////////////

int daysInput = switch _daysInput
    "SUN" => 1
    "MON" => 2
    "TUE" => 3
    "WED" => 4
    "THU" => 5
    "FRI" => 6
    "SAT" => 7
    =>na
_currentDay = dayofweek(timenow, timeZone)
inputDayCond = _daysInput == "ALL"? true: _daysInput== "CURRENT"? dayofweek(time, timeZone) == _currentDay: dayofweek(time, timeZone) == daysInput

    //get the length (in 15min bars) of user input session
int sessStHr = math.round(str.tonumber(str.substring(inputSess,0,2)))
int sessStMn = math.round(str.tonumber(str.substring(inputSess,2,4)))
sessStart = timestamp("America/New_York", year, month, dayofweek, sessStHr, sessStMn, 00 )
int sessEndHr = math.round(str.tonumber(str.substring(inputSess,5,7)))
int sessEndMn = math.round(str.tonumber(str.substring(inputSess,7,9)))
sessEnd = timestamp("America/New_York", year, month, dayofweek, sessEndHr, sessEndMn, 00 )
int sessLen = math.round((sessEnd-sessStart)/1000/15/60)

userSess=time(timeframe.period, inputSess, timeZone)
bool inSess= nz(userSess)
bgcolor(showSessHighlight and is15min and inSess and inputDayCond? bgCol : na)

var array<int> highIncremArr = array.new<int>(sessLen+1,0)
var array<int> lowIncremArr = array.new<int>(sessLen+1,0)

offset_H = math.abs(ta.highestbars(high[1],sessLen))
offset_L = math.abs(ta.lowestbars(low[1],sessLen))

    // ~~~~FUNCTIONS ~~~~
    
getTimeTitle(sess)=>
    var string timeTitle =na
    var array<string> timeTitleArr = array.new<string>(sessLen)
    if sess[1] and not sess 
        for i=sessLen -1 to 0
            timeTitle:= str.format_time(time_close[i+2],"HH:mm", "America/New_York") 
            array.set(timeTitleArr, i, timeTitle)
    timeTitleArr

incrementerFxn(array<int> arr, int offsetter)=>
    if inSess[1] and not inSess and inputDayCond and startDateCond //inputDayCond: is input day at the END of your session, or simply = true if 'ALL' is selected
        for i= 0 to sessLen 
            if  offsetter == i   
                x = array.get(arr, offsetter)+1
                array.set(arr, offsetter, x)
            arr

pct(countSum, int _int)=>
    float pct = 0
    if countSum>0
        pct:= math.round((_int/countSum)*100,precision)
    pct

tableTimesTitleFill(array<string> arr, table, arrSize)=>
    if not horizontalTab and showTable
        for i = 0 to arrSize-1
            table.cell(table, 0, i+2, str.tostring(array.get(arr, (arrSize-1)-i)),bgcolor = tabCellSessCol, text_color = txtCol, text_size = txtSize)
    if horizontalTab
        for i = 0 to arrSize-1
            table.cell(table, i+2, 0, str.tostring(array.get(arr, (arrSize-1)-i)),bgcolor = tabCellSessCol, text_color = txtCol, text_size = txtSize)

    // for percent calculations
daySum = array.sum(highIncremArr)

tableIntsToFill(array<int> arr, int num, table, arrSize)=>
    if not horizontalTab and showTable
        for i = 0 to arrSize-1
            table.cell(table, 1+num, i+2, showPct?str.tostring(pct(daySum,array.get(arr, (arrSize-1)-i)))+" %":str.tostring(array.get(arr, (arrSize-1)-i)),bgcolor = tabCellValueCol, text_color = txtCol, text_size = txtSize)
    if horizontalTab
        for i = 0 to arrSize-1
            table.cell(table, i+2, 1+num, showPct?str.tostring(pct(daySum,array.get(arr, (arrSize-1)-i)))+" %":str.tostring(array.get(arr, (arrSize-1)-i)),bgcolor = tabCellValueCol, text_color = txtCol, text_size = txtSize)

incrementerFxn(highIncremArr,offset_H)
incrementerFxn(lowIncremArr,offset_L)
timeTitleArr = getTimeTitle(inSess)
titleArrSize = array.size(timeTitleArr)

string currentDay = switch _currentDay
    1 => "SUN"
    2 => "MON"
    3 => "TUE"
    4 => "WED"
    5 => "THU"
    6 => "FRI"
    7 => "SAT"
    =>na

dayString = _daysInput == "ALL"? "Trading Days": _daysInput == "CURRENT"? currentDay :str.tostring(_daysInput)

var Table = table.new(tabPos, columns = 100, rows =100, bgcolor =color.new(color.gray, 74), border_width = 1)
if is15min and showTable
    table.cell(Table,0, 0, "~~STATISTICS~~\n"+str.tostring(daySum) +"x " +dayString, text_color = txtCol, text_size = txtSize, bgcolor = color.yellow, text_halign = text.align_center)
    table.cell(Table,horizontalTab?1:0, horizontalTab?0:1, "session", text_color = txtCol, text_size = txtSize, bgcolor = tabCellSessCol, text_halign = text.align_center)
    table.cell(Table,1, 1, "high:", text_color = txtCol, text_size = txtSize, bgcolor = highTitleCol, text_halign = text.align_center)
    tableTimesTitleFill(timeTitleArr, Table, titleArrSize)
    tableIntsToFill(highIncremArr,0, Table, titleArrSize)
    tableIntsToFill(lowIncremArr,1, Table, titleArrSize)
    if not horizontalTab
        table.cell(Table,1, 0, "Day of Week:", text_color = txtCol, text_size = txtSize, bgcolor = color.new(color.gray,70), text_halign = text.align_center)
        table.cell(Table,2, 0, str.tostring(_daysInput), text_color = txtCol, text_size = txtSize, bgcolor = color.new(color.gray,70), text_halign = text.align_center)
        table.cell(Table,horizontalTab?1:2, horizontalTab?2:1, "     low:     ", text_color = txtCol, text_size = txtSize, bgcolor = lowTitleCol, text_halign = text.align_center)
    if horizontalTab
        table.cell(Table,0, 1, "~ Day of Week ~", text_color = txtCol, text_size = txtSize, bgcolor = color.new(color.gray,70), text_halign = text.align_center)
        table.cell(Table,0, 2, str.tostring(_daysInput), text_color = txtCol, text_size = txtSize, bgcolor = color.new(color.gray,70), text_halign = text.align_center)
        table.cell(Table,horizontalTab?1:2, horizontalTab?2:1, "low:", text_color = txtCol, text_size = txtSize, bgcolor = lowTitleCol, text_halign = text.align_center)
