Know your Monthly, Weekly, Daily Levels
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © schroederjoa

//@version=5
indicator("Know your Monthly, Weekly, Daily Levels", shorttitle="MWD Levels", overlay=true, max_lines_count=500)
 
ticker =  ticker.new(syminfo.prefix, syminfo.ticker, session.regular)
 
var int max_lines = input.int(500, title="Max number of lines to draw", tooltip="TradingView limits the number of drawing objects per chart to 500. To make sure the most relevant levels are shown, values are sorted based on the latest close value before levels > 500 are cut off.\n\nPlease also note that the max lookback history is limited by the currently chosen timeframe. E.g. on a 5min timeframe, 20000 bars (Premium Plan) result in approx. 5 months of lookback period, meaning you may want to have a 30 min or higher chart open to get a complete picture of levels, while trading on a lower timeframe. ", minval=1, maxval=500)

var GRP1 = "Monthly Line Settings"
var bool show_monthly = input.bool(true, title="Show Monthly", group=GRP1, inline = "01_1")
var int monthly_line_width = input.int(5, title="Line Width", group=GRP1, inline = "01_2")
var color monthly_color = input.color(color.new(color.aqua,40), title="", group=GRP1, inline = "01_2")
var bool show_monthly_high = input.bool(true, title="High", group=GRP1, inline = "01_3")
var bool show_monthly_low = input.bool(true, title="Low", group=GRP1, inline = "01_3")
var bool show_monthly_close = input.bool(true, title="Close", group=GRP1, inline = "01_3", tooltip="Select whether High, Low and Close value of monthly candles should be included in charts.")

var GRP2 = "Weekly Line Settings"
var bool show_weekly = input.bool(true, title="Show Weekly", group=GRP2, inline = "02_1")
var int weekly_line_width = input.int(3, title="Line Width", group=GRP2, inline = "02_2")
var color weekly_color = input.color(color.new(color.fuchsia,40), title="", group=GRP2, inline = "02_2")
var bool show_weekly_high = input.bool(true, title="High", group=GRP2, inline = "02_3")
var bool show_weekly_low = input.bool(true, title="Low", group=GRP2, inline = "02_3")
var bool show_weekly_close = input.bool(true, title="Close", group=GRP2, inline = "02_3")

var GRP3 = "Daily Line Settings"
var bool show_daily = input.bool(true, title="Show Daily", group=GRP3, inline = "03_1")
var int daily_line_width = input.int(1, title="Line Width", group=GRP3, inline = "03_2")
var color daily_color = input.color(color.new(color.white,40), title="", group=GRP3, inline = "03_2")
var bool show_daily_high = input.bool(true, title="High", group=GRP3, inline = "03_3")
var bool show_daily_low = input.bool(true, title="Low", group=GRP3, inline = "03_3")
var bool show_daily_close = input.bool(true, title="Close", group=GRP3, inline = "03_3")
var bool show_daily_pre_high = input.bool(false, title="Pre Market High", group=GRP3, inline = "03_4")
var bool show_daily_pre_low = input.bool(false, title="Pre Market Low", group=GRP3, inline = "03_4")
 
//max_bars_back(time,20000)

// global array to hold level information    
var arr_levels_val_temp = array.new_float(0)
var arr_levels_time_temp = array.new_int(0)
var arr_levels_timeframe_temp = array.new_int(0)

// function to add (or modify) level    
add_level(val, t, timeframe) =>

    found = false
    for i = 0 to (array.size(arr_levels_val_temp) == 0 ? na : array.size(arr_levels_val_temp) - 1)
        v = array.get(arr_levels_val_temp,i)
        if v == val
            found := true
            tf = array.get(arr_levels_timeframe_temp,i)
            // change only level meta data to higher timeframe if level value already exists
            if timeframe >= tf
                array.set(arr_levels_time_temp,i, t)
                array.set(arr_levels_timeframe_temp,i, timeframe)
    // add new level if it does not exist yet
    if not found
        array.push(arr_levels_val_temp, val)
        array.push(arr_levels_time_temp, t)
        array.push(arr_levels_timeframe_temp, timeframe)


[ddh,ddl,ddc,ddt] = request.security(ticker, timeframe.period, [high,low,close,time],gaps=barmerge.gaps_off, lookahead=barmerge.lookahead_off)

price_lowest = 0
price_highest = 9999999

var float pre_high_current = price_lowest
var float pre_low_current = price_highest

var float m_high = price_lowest
var float m_low = price_highest
var float w_high = price_lowest
var float w_low = price_highest
var float d_high = price_lowest
var float d_low = price_highest
var float d_pre_high = price_lowest
var float d_pre_low = price_highest

m_high := math.max(m_high, ddh)
m_low := math.min(m_low, ddl)
w_high := math.max(w_high, ddh)
w_low := math.min(w_low, ddl)
d_high := math.max(d_high, ddh)
d_low := math.min(d_low, ddl)

if not session.ismarket
    d_pre_high := math.max(d_pre_high, high)
    d_pre_low := math.min(d_pre_low, low)

new_m = month(ddt) != month(ddt[1])
new_w = weekofyear(ddt) != weekofyear(ddt[1])
new_d = dayofmonth(ddt) != dayofmonth(ddt[1])
new_d_pre = session.ismarket and not session.ismarket[1] 

if new_m and show_monthly
    if show_monthly_high
        add_level(m_high[1], ddt[1], 3)
    if show_monthly_low
        add_level(m_low[1], ddt[1], 3)
    if show_monthly_close
        add_level(ddc[1], ddt[1], 3)
    m_high := ddh
    m_low := ddl

if new_w and show_weekly
    if show_weekly_high
        add_level(w_high[1], ddt[1], 2)
    if show_weekly_low
        add_level(w_low[1], ddt[1], 2)
    if show_weekly_close
        add_level(ddc[1], ddt[1], 2)
    w_high := ddh
    w_low := ddl

if new_d and show_daily
    if show_daily_high
        add_level(d_high[1], ddt[1], 1)
    if show_daily_low
        add_level(d_low[1], ddt[1], 1)
    if show_daily_close
        add_level(ddc[1], ddt[1], 1)    
    d_high := ddh
    d_low := ddl

if new_d_pre and show_daily
    if show_daily_pre_high
        add_level(d_pre_high, time, 0)
    if show_daily_pre_low
        add_level(d_pre_low, time, 0)

    d_pre_high := price_lowest
    d_pre_low := price_highest

color line_color = na
int line_width = na
string line_style = na

if barstate.islastconfirmedhistory

    // calculate distances to latest close price    
    arr_dist = array.new_float(0)
    for i = 0 to (array.size(arr_levels_val_temp) == 0 ? na : array.size(arr_levels_val_temp) - 1)
        array.push(arr_dist, math.abs(close - array.get(arr_levels_val_temp,i)))
        
    // sort according to smallest values and slice array to keep indexes of x closest values only
    arr_dist_indexes = array.sort_indices(arr_dist)
    arr_dist_indexes := array.slice(arr_dist_indexes,0,math.min(max_lines, array.size(arr_dist_indexes) == 0 ? na : array.size(arr_dist_indexes) - 1))
    
    // populate final arrays, based on filtered indexes of closest values
    var arr_levels_val = array.new_float(0)
    var arr_levels_time = array.new_int(0)
    var arr_levels_timeframe = array.new_int(0)
    
    for i = 0 to (array.size(arr_dist_indexes) == 0 ? na : array.size(arr_dist_indexes) - 1)
        index = array.get(arr_dist_indexes,i)
        array.push(arr_levels_val, array.get(arr_levels_val_temp,index))
        array.push(arr_levels_time, array.get(arr_levels_time_temp,index))
        array.push(arr_levels_timeframe, array.get(arr_levels_timeframe_temp,index))
    
    for i = 0 to (array.size(arr_levels_val) == 0 ? na : array.size(arr_levels_val) - 1)
    
        val = array.get(arr_levels_val,i)
        t = array.get(arr_levels_time,i)
        tf = array.get(arr_levels_timeframe,i)
        
        line_color := tf == 3 ? monthly_color : tf == 2 ? weekly_color : daily_color
        line_width := tf == 3 ? monthly_line_width : tf == 2 ? weekly_line_width : daily_line_width
        line_style := tf == 0 ? line.style_dashed : line.style_solid

        line.new(t, val, t+1, val, xloc.bar_time, color=line_color, style=line_style, width=line_width, extend=extend.right)
