Time of Day - Volatility Report
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © sbtnc
// Created: 2023-02-23
// Last modified: 2023-02-23
// Version 1.0


//@version=5
indicator("Time of Day - Volatility Report", format=format.percent)


//--------------------------------------------------------------------
//                             Constants
//--------------------------------------------------------------------

var DEFAULT_COLUMN_BORDERCOLOR  = chart.bg_color
var DEFAULT_COLUMN_WIDTH        = 2
var DEFAULT_COLUMN_GAP          = 2
var DEFAULT_CAPTION_COLOR       = color.gray


//--------------------------------------------------------------------
//                               Inputs
//--------------------------------------------------------------------

t_timezone              = "Exchange and geographical time zones may observe Daylight Saving Time (DST)."
t_atrPeriod             = "By default, compares the hourly volatility relatively to the average daily volatility of the past 20 days."
t_output                = "Mean: the average of all values in the dataset.\nMedian: the middle value in the ordered dataset."

i_timezone              = input.string  ("Exchange", "Time Zone",
     [
     "UTC",
     "Exchange",
     "Africa/Cairo",
     "Africa/Johannesburg",
     "Africa/Lagos",
     "Africa/Nairobi",
     "Africa/Tunis",
     "America/Argentina/Buenos_Aires",
     "America/Bogota",
     "America/Caracas",
     "America/Chicago",
     "America/Denver",
     "America/El_Salvador",
     "America/Juneau",
     "America/Lima",
     "America/Los_Angeles",
     "America/New_York",
     "America/Mexico_City",
     "America/Phoenix",
     "America/Santiago",
     "America/Sao_Paulo",
     "America/Toronto",
     "America/Vancouver",
     "Asia/Almaty",
     "Asia/Ashgabat",
     "Asia/Bahrain",
     "Asia/Bangkok",
     "Asia/Dubai",
     "Asia/Chongqing",
     "Asia/Colombo",
     "Asia/Ho_Chi_Minh",
     "Asia/Hong_Kong",
     "Asia/Istanbul",
     "Asia/Jakarta",
     "Asia/Jerusalem",
     "Asia/Karachi",
     "Asia/Kathmandu",
     "Asia/Kolkata",
     "Asia/Kuwait",
     "Asia/Manila",
     "Asia/Muscat",
     "Asia/Nicosia",
     "Asia/Qatar",
     "Asia/Riyadh",
     "Asia/Seoul",
     "Asia/Shanghai",
     "Asia/Singapore",
     "Asia/Taipei",
     "Asia/Tehran",
     "Asia/Tokyo",
     "Asia/Yangon",
     "Atlantic/Reykjavik",
     "Australia/Adelaide",
     "Australia/Brisbane",
     "Australia/Perth",
     "Australia/Sydney",
     "Europe/Amsterdam",
     "Europe/Athens",
     "Europe/Belgrade",
     "Europe/Berlin",
     "Europe/Bratislava",
     "Europe/Brussels",
     "Europe/Bucharest",
     "Europe/Budapest",
     "Europe/Copenhagen",
     "Europe/Dublin",
     "Europe/Helsinki",
     "Europe/Madrid",
     "Europe/Malta",
     "Europe/Moscow",
     "Europe/Lisbon",
     "Europe/London",
     "Europe/Luxembourg",
     "Europe/Oslo",
     "Europe/Paris",
     "Europe/Riga",
     "Europe/Rome",
     "Europe/Stockholm",
     "Europe/Tallinn",
     "Europe/Vilnius",
     "Europe/Warsaw",
     "Europe/Zurich",
     "Pacific/Auckland",
     "Pacific/Chatham",
     "Pacific/Fakaofo",
     "Pacific/Honolulu",
     "Pacific/Norfolk"
     ],
     t_timezone
 )
i_atrPeriod             = input.int     (20,            "Daily ATR Period", minval=1,           tooltip=t_atrPeriod)
i_output                = input.string  ("Median",      "Ouput",            ["Mean", "Median"], t_output)
i_highVolatilityColor   = input.color   (color.red,     "High Volatility")
i_lowVolatilityColor    = input.color   (color.blue,    "Low Volatility")


//--------------------------------------------------------------------
//                        Variables declarations
//--------------------------------------------------------------------

var a_results   = array.new_float(24)
var a_sizes     = array.new_float(24)
var a_dataset0  = array.new_float()
var a_dataset1  = array.new_float()
var a_dataset2  = array.new_float()
var a_dataset3  = array.new_float()
var a_dataset4  = array.new_float()
var a_dataset5  = array.new_float()
var a_dataset6  = array.new_float()
var a_dataset7  = array.new_float()
var a_dataset8  = array.new_float()
var a_dataset9  = array.new_float()
var a_dataset10 = array.new_float()
var a_dataset11 = array.new_float()
var a_dataset12 = array.new_float()
var a_dataset13 = array.new_float()
var a_dataset14 = array.new_float()
var a_dataset15 = array.new_float()
var a_dataset16 = array.new_float()
var a_dataset17 = array.new_float()
var a_dataset18 = array.new_float()
var a_dataset19 = array.new_float()
var a_dataset20 = array.new_float()
var a_dataset21 = array.new_float()
var a_dataset22 = array.new_float()
var a_dataset23 = array.new_float()


//--------------------------------------------------------------------
//                              Functions 
//--------------------------------------------------------------------

// @function Get the time zone from the input settings
// @return string
f_getTimezone() => 
    switch i_timezone
        "UTC"       => "UTC+0"
        "Exchange"  => syminfo.timezone  
        => i_timezone


// @function Check if current bar's time is at a given hour
// @return bool
f_checkTime(int _hour) =>
    var _tz = f_getTimezone()
    hour(time, _tz) == _hour


// @function Get the output data based on the input settings
// @return float
f_getData(array<float> data) =>
    switch i_output
        "Mean"                  => array.avg(data)
        "Median"                => array.median(data)


// @function Draw a chart column
// @return void
f_drawColumn(int _index) =>
    var box _col            = box.new(na, na, na, na, DEFAULT_COLUMN_BORDERCOLOR)
    var label _legend       = label.new(na, na, color=color(na), style=label.style_label_up)
    var label _value        = label.new(na, na, color=color(na), style=label.style_label_down)
    var _gridMultiplier     = DEFAULT_COLUMN_WIDTH + DEFAULT_COLUMN_GAP 
    
    if barstate.islast
        _hasValue           = not na(array.get(a_results, _index))
        _y                  = _hasValue ? array.get(a_results, _index) : 0.0
        _x1                 = bar_index + (_index - 24) * _gridMultiplier
        _x2                 = _x1 + DEFAULT_COLUMN_WIDTH
        _center             = _x1 + DEFAULT_COLUMN_WIDTH / 2       
        _min                = array.min(a_results)
        _max                = array.max(a_results)
        _color              = color.from_gradient(_y, _min, _max, i_lowVolatilityColor, i_highVolatilityColor)
        _samples            = array.get(a_sizes, _index)


        // Column

        box.set_lefttop     (_col, _x1, _y)
        box.set_rightbottom (_col, _x2, 0)
        box.set_bgcolor     (_col, _color)


        // Legend

        label.set_xy        (_legend, _center, 0)
        label.set_text      (_legend, str.tostring(_index))
        label.set_textcolor (_legend, _color)
        label.set_tooltip   (_legend, str.format("{0}:00 to {0}:59 ({1})", _index, f_getTimezone()))


        // Value

        if _hasValue
            label.set_xy        (_value, _center, _y)
            label.set_text      (_value, str.tostring(math.round(_y)))
            label.set_textcolor (_value, _color)
            label.set_tooltip   (_value, str.format("{0} based on {1} samples", str.tostring(_y, format.percent), _samples))


//--------------------------------------------------------------------
//                                Logic
//--------------------------------------------------------------------


if not (timeframe.isminutes and timeframe.multiplier == 60)
    runtime.error("The report need to compute data from the hourly timeframe. Please change to 60 or 1H timeframe.")


dailyAtr        = request.security(syminfo.tickerid, "D", ta.atr(i_atrPeriod))
relativeToAtr   = ta.tr * 100 / dailyAtr


// Collect the volatility data

switch
    f_checkTime(0)  => array.push(a_dataset0,   relativeToAtr)
    f_checkTime(1)  => array.push(a_dataset1,   relativeToAtr)
    f_checkTime(2)  => array.push(a_dataset2,   relativeToAtr)
    f_checkTime(3)  => array.push(a_dataset3,   relativeToAtr)
    f_checkTime(4)  => array.push(a_dataset4,   relativeToAtr)
    f_checkTime(5)  => array.push(a_dataset5,   relativeToAtr)
    f_checkTime(6)  => array.push(a_dataset6,   relativeToAtr)
    f_checkTime(7)  => array.push(a_dataset7,   relativeToAtr)
    f_checkTime(8)  => array.push(a_dataset8,   relativeToAtr)
    f_checkTime(9)  => array.push(a_dataset9,   relativeToAtr)
    f_checkTime(10) => array.push(a_dataset10,  relativeToAtr)
    f_checkTime(11) => array.push(a_dataset11,  relativeToAtr)
    f_checkTime(12) => array.push(a_dataset12,  relativeToAtr)
    f_checkTime(13) => array.push(a_dataset13,  relativeToAtr)
    f_checkTime(14) => array.push(a_dataset14,  relativeToAtr)
    f_checkTime(15) => array.push(a_dataset15,  relativeToAtr)
    f_checkTime(16) => array.push(a_dataset16,  relativeToAtr)
    f_checkTime(17) => array.push(a_dataset17,  relativeToAtr)
    f_checkTime(18) => array.push(a_dataset18,  relativeToAtr)
    f_checkTime(19) => array.push(a_dataset19,  relativeToAtr)
    f_checkTime(20) => array.push(a_dataset20,  relativeToAtr)
    f_checkTime(21) => array.push(a_dataset21,  relativeToAtr)
    f_checkTime(22) => array.push(a_dataset22,  relativeToAtr)
    f_checkTime(23) => array.push(a_dataset23,  relativeToAtr)


// Compute on last bar for optimizing performances

if barstate.islast

    // The results

    array.set(a_results, 0,  f_getData(a_dataset0))
    array.set(a_results, 1,  f_getData(a_dataset1))
    array.set(a_results, 2,  f_getData(a_dataset2))
    array.set(a_results, 3,  f_getData(a_dataset3))
    array.set(a_results, 4,  f_getData(a_dataset4))
    array.set(a_results, 5,  f_getData(a_dataset5))
    array.set(a_results, 6,  f_getData(a_dataset6))
    array.set(a_results, 7,  f_getData(a_dataset7))
    array.set(a_results, 8,  f_getData(a_dataset8))
    array.set(a_results, 9,  f_getData(a_dataset9))
    array.set(a_results, 10, f_getData(a_dataset10))
    array.set(a_results, 11, f_getData(a_dataset11))
    array.set(a_results, 12, f_getData(a_dataset12))
    array.set(a_results, 13, f_getData(a_dataset13))
    array.set(a_results, 14, f_getData(a_dataset14))
    array.set(a_results, 15, f_getData(a_dataset15))
    array.set(a_results, 16, f_getData(a_dataset16))
    array.set(a_results, 17, f_getData(a_dataset17))
    array.set(a_results, 18, f_getData(a_dataset18))
    array.set(a_results, 19, f_getData(a_dataset19))
    array.set(a_results, 20, f_getData(a_dataset20))
    array.set(a_results, 21, f_getData(a_dataset21))
    array.set(a_results, 22, f_getData(a_dataset22))
    array.set(a_results, 23, f_getData(a_dataset23))


    // The sample sizes

    array.set(a_sizes, 0,  array.size(a_dataset0))
    array.set(a_sizes, 1,  array.size(a_dataset1))
    array.set(a_sizes, 2,  array.size(a_dataset2))
    array.set(a_sizes, 3,  array.size(a_dataset3))
    array.set(a_sizes, 4,  array.size(a_dataset4))
    array.set(a_sizes, 5,  array.size(a_dataset5))
    array.set(a_sizes, 6,  array.size(a_dataset6))
    array.set(a_sizes, 7,  array.size(a_dataset7))
    array.set(a_sizes, 8,  array.size(a_dataset8))
    array.set(a_sizes, 9,  array.size(a_dataset9))
    array.set(a_sizes, 10, array.size(a_dataset10))
    array.set(a_sizes, 11, array.size(a_dataset11))
    array.set(a_sizes, 12, array.size(a_dataset12))
    array.set(a_sizes, 13, array.size(a_dataset13))
    array.set(a_sizes, 14, array.size(a_dataset14))
    array.set(a_sizes, 15, array.size(a_dataset15))
    array.set(a_sizes, 16, array.size(a_dataset16))
    array.set(a_sizes, 17, array.size(a_dataset17))
    array.set(a_sizes, 18, array.size(a_dataset18))
    array.set(a_sizes, 19, array.size(a_dataset19))
    array.set(a_sizes, 20, array.size(a_dataset20))
    array.set(a_sizes, 21, array.size(a_dataset21))
    array.set(a_sizes, 22, array.size(a_dataset22))
    array.set(a_sizes, 23, array.size(a_dataset23))


//--------------------------------------------------------------------
//                          Plotting & styling
//--------------------------------------------------------------------

// Draw the column chart

f_drawColumn(0)
f_drawColumn(1)
f_drawColumn(2)
f_drawColumn(3)
f_drawColumn(4)
f_drawColumn(5)
f_drawColumn(6)
f_drawColumn(7)
f_drawColumn(8)
f_drawColumn(9)
f_drawColumn(10)
f_drawColumn(11)
f_drawColumn(12)
f_drawColumn(13)
f_drawColumn(14)
f_drawColumn(15)
f_drawColumn(16)
f_drawColumn(17)
f_drawColumn(18)
f_drawColumn(19)
f_drawColumn(20)
f_drawColumn(21)
f_drawColumn(22)
f_drawColumn(23)