Support Resistance - Dynamic v2 w/ Timeframe option
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © platsn

// I take no credit for this, but I think this modification is very useful when using this script created by © LonesomeTheBlue
// Copied directly from © LonesomeTheBlue's Support Resistance - Dynamic v2 code but with modification to allow option 
// to choose different timeframe to calculate S/R levels. 
// Very often, we don't want to use S/R levels on the same timeframe that we are trading at. ie. if trading at 5min, we often use 15min, 30min or 1hr timeframe to define 
// our S/R levels.

//@version=5
indicator('[Pt] Support Resistance - Dynamic v2 w/ Timeframe option', '[Pt]SRv2 - custom tf', overlay=true)
tf = input.timeframe('D', "Timeframe", group='Setup', tooltip = 'Please note that if a higher timeframe chosen is "too far" from the chart timeframe, the S/R may not necessaryily match to that of the S/R generated if they were created at that higher timeframe.') // option to choose which timeframe to calculate S/R levels
prd = input.int(defval=10, title='Pivot Period', minval=1, group='Setup', tooltip = 'With respect to higher timeframe')
ppsrc = input.string(defval='High/Low', title='Source', options=['High/Low', 'Close/Open'], group='Setup')
maxnumpp = input.int(defval=100, title=' Maximum Number of Pivot', minval=5, group='Setup')
ChannelW = input.int(defval=10, title='Maximum Channel Width %', minval=1, group='Setup')
maxnumsr = input.int(defval=5, title=' Maximum Number of S/R', minval=1, maxval=10, group='Setup')
min_strength = input.int(defval=2, title=' Minimum Strength', minval=1, maxval=10, group='Setup')
labelloc = input.int(defval=20, title='Label Location', group='Display', tooltip='Positive numbers reference future bars, negative numbers reference histical bars')
labelloc2 = input.int(defval=-100, title='TF Label Location', group='Display', tooltip='Positive numbers reference future bars, negative numbers reference histical bars')
linestyle = input.string(defval='Dashed', title='Line Style', options=['Solid', 'Dotted', 'Dashed'], group='Display')
linewidth = input.int(defval=2, title='Line Width', minval=1, maxval=4, group='Display')
resistancecolor = input.color(defval=color.red, title='Resistance Color', group='Display', inline = 'resist')
resist_lbl_col = input.color(defval = color.red, title='', group = 'Display', inline = 'resist')
resist_lbl_text_col = input.color(defval = color.white, title='', group = 'Display', inline = 'resist')
supportcolor = input.color(defval=color.lime, title='Support Color', group='Display', inline = 'supp')
supp_lbl_col = input.color(defval = color.lime, title='', group = 'Display', inline = 'supp')
supp_lbl_text_col = input.color(defval = color.black, title='', group = 'Display', inline = 'supp')
showpp = input.bool(false, title='Show Pivot Points', group='Display')
showtf = input.bool(true, title = 'Show Timeframe label', group='Display')
showloc = input.bool(true, title = 'Show Price level', group='Display')

// get data on ticker based on chosen timeframe
src_h = request.security(syminfo.tickerid,tf,high, gaps = barmerge.gaps_off, lookahead = barmerge.lookahead_off)
src_l = request.security(syminfo.tickerid,tf,low, gaps = barmerge.gaps_off, lookahead = barmerge.lookahead_off)
src_c = request.security(syminfo.tickerid,tf,close, gaps = barmerge.gaps_off, lookahead = barmerge.lookahead_off)
src_o = request.security(syminfo.tickerid,tf,open, gaps = barmerge.gaps_off, lookahead = barmerge.lookahead_off)

f_resInMinutes() =>
    _resInMinutes = timeframe.multiplier * (timeframe.isseconds ? 1. / 60 : timeframe.isminutes ? 1. : timeframe.isdaily ? 60. * 24 : timeframe.isweekly ? 60. * 24 * 7 : timeframe.ismonthly ? 60. * 24 * 30.4375 : na)
    _resInMinutes

f_tfResInMinutes(_res) =>
    request.security(syminfo.tickerid, _res, f_resInMinutes())

f_tfIsIntraday(_res) =>
    [intraday, daily, weekly, monthly] = request.security(syminfo.tickerid, _res, [timeframe.isintraday, timeframe.isdaily, timeframe.isweekly, timeframe.ismonthly])
    check = intraday ? "Intraday" : daily ? "Daily" : weekly ? "Weekly" : monthly ? "Monthly" : "Error" 
    check

mtf_multiplier = int (f_tfResInMinutes(tf) / f_resInMinutes())
prd := prd * mtf_multiplier

float src1 = ppsrc == 'High/Low' ? src_h : math.max(src_c, src_o)
float src2 = ppsrc == 'High/Low' ? src_l : math.min(src_c, src_o)
float src3 = ppsrc == 'High/Low' ? high : math.max(close, open)
float src4 = ppsrc == 'High/Low' ? low : math.min(close, open)
float ph = ta.pivothigh(src1, prd, prd)
float pl = ta.pivotlow(src2, prd, prd)
float ph_chart = ta.pivothigh(src3, prd, prd)
float pl_chart = ta.pivotlow(src4, prd, prd)


plotshape(ph_chart and showpp, text = "H",  style = shape.labeldown, color = na, textcolor = color.red, location = location.abovebar,  offset = -prd)
plotshape(pl_chart and showpp, text = "L",  style = shape.labelup, color = na, textcolor = color.lime, location = location.belowbar,  offset = -prd)

Lstyle = linestyle == 'Dashed' ? line.style_dashed : linestyle == 'Solid' ? line.style_solid : line.style_dotted

tf_res = f_tfIsIntraday(tf)
tf_text = str.tostring(tf)

if str.tostring(tf) == ""//tf is equal to chart
    tf_text := na(timeframe.multiplier / 60) ? timeframe.period : timeframe.multiplier < 60 ?  timeframe.period + " min" : str.tostring(timeframe.multiplier / 60) + " hour"
else if tf_res == "Intraday"
    tf_text := na(str.tonumber(tf) / 60) ? str.tostring(tf) : str.tonumber(tf) < 60 ?  str.tostring(tf) + " min" : str.tostring(str.tonumber(tf) / 60) + " hour"
else
    tf_text := str.tostring(tf)


//calculate maximum S/R channel zone width
prdhighest = request.security(syminfo.tickerid, tf, ta.highest(300))
prdlowest = request.security(syminfo.tickerid, tf, ta.lowest(300))
cwidth = (prdhighest - prdlowest) * ChannelW / 100

var pivotvals = array.new_float(0)

if ph or pl
    array.unshift(pivotvals, ph ? ph : pl)
    if array.size(pivotvals) > maxnumpp  // limit the array size
        array.pop(pivotvals)

get_sr_vals(ind) =>
    float lo = array.get(pivotvals, ind)
    float hi = lo
    int numpp = 0
    for y = 0 to array.size(pivotvals) - 1 by 1
        float cpp = array.get(pivotvals, y)
        float wdth = cpp <= lo ? hi - cpp : cpp - lo
        if wdth <= cwidth  // fits the max channel width?
            lo := cpp <= lo ? cpp : lo
            hi := cpp > lo ? cpp : hi
            numpp += 1
            numpp
    [hi, lo, numpp]

var sr_up_level = array.new_float(0)
var sr_dn_level = array.new_float(0)
sr_strength = array.new_float(0)

find_loc(strength) =>
    ret = array.size(sr_strength)
    for i = ret > 0 ? array.size(sr_strength) - 1 : na to 0 by 1
        if strength <= array.get(sr_strength, i)
            break
        ret := i
        ret
    ret

check_sr(hi, lo, strength) =>
    ret = true
    for i = 0 to array.size(sr_up_level) > 0 ? array.size(sr_up_level) - 1 : na by 1
        //included?
        if array.get(sr_up_level, i) >= lo and array.get(sr_up_level, i) <= hi or array.get(sr_dn_level, i) >= lo and array.get(sr_dn_level, i) <= hi
            if strength >= array.get(sr_strength, i)
                array.remove(sr_strength, i)
                array.remove(sr_up_level, i)
                array.remove(sr_dn_level, i)
                ret
            else
                ret := false
                ret
            break
    ret

var sr_lines = array.new_line(11, na)
var sr_labels = array.new_label(11, na)
var tf_labels = array.new_label(11, na)

for x = 1 to 10 by 1
    rate = 100 * (label.get_y(array.get(sr_labels, x)) - close) / close
    label.set_text(array.get(sr_labels, x), text=str.tostring(label.get_y(array.get(sr_labels, x))) + '(' + str.tostring(rate, '#.##') + '%)')
    label.set_x(array.get(sr_labels, x), x=bar_index + labelloc)
    label.set_color(array.get(sr_labels, x), color=label.get_y(array.get(sr_labels, x)) >= close ? resist_lbl_col : supp_lbl_col)
    label.set_textcolor(array.get(sr_labels, x), textcolor=label.get_y(array.get(sr_labels, x)) >= close ? resist_lbl_text_col : supp_lbl_text_col)
    label.set_style(array.get(sr_labels, x), style=label.get_y(array.get(sr_labels, x)) >= close ? label.style_label_down : label.style_label_up)
    line.set_color(array.get(sr_lines, x), color=line.get_y1(array.get(sr_lines, x)) >= close ? resistancecolor : supportcolor)

    label.set_text(array.get(tf_labels, x), text=tf_text)
    label.set_x(array.get(tf_labels, x), x=bar_index + labelloc2)
    label.set_color(array.get(tf_labels, x), color=label.get_y(array.get(tf_labels, x)) >= close ? resist_lbl_col : supp_lbl_col)
    label.set_textcolor(array.get(tf_labels, x), textcolor=label.get_y(array.get(tf_labels, x)) >= close ? resist_lbl_text_col : supp_lbl_text_col)
    label.set_style(array.get(tf_labels, x), style=label.get_y(array.get(tf_labels, x)) >= close ? label.style_label_down : label.style_label_up)


if ph or pl
    //because of new calculation, remove old S/R levels
    array.clear(sr_up_level)
    array.clear(sr_dn_level)
    array.clear(sr_strength)
    //find S/R zones
    for x = 0 to array.size(pivotvals) - 1 by 1
        [hi, lo, strength] = get_sr_vals(x)
        if check_sr(hi, lo, strength)
            loc = find_loc(strength)
            // if strength is in first maxnumsr sr then insert it to the arrays 
            if loc < maxnumsr and strength >= min_strength
                array.insert(sr_strength, loc, strength)
                array.insert(sr_up_level, loc, hi)
                array.insert(sr_dn_level, loc, lo)
                // keep size of the arrays = 5
                if array.size(sr_strength) > maxnumsr
                    array.pop(sr_strength)
                    array.pop(sr_up_level)
                    array.pop(sr_dn_level)

    for x = 1 to 10 by 1
        line.delete(array.get(sr_lines, x))
        label.delete(array.get(sr_labels, x))
        label.delete(array.get(tf_labels, x))

    for x = 0 to array.size(sr_up_level) > 0 ? array.size(sr_up_level) - 1 : na by 1
        float mid = math.round_to_mintick((array.get(sr_up_level, x) + array.get(sr_dn_level, x)) / 2)
        rate = 100 * (mid - close) / close
        if showloc
            array.set(sr_labels, x + 1, label.new(x=bar_index + labelloc, y=mid, text=str.tostring(mid) + '(' + str.tostring(rate, '#.##') + '%)', color=mid >= close ? color.red : color.lime, textcolor=mid >= close ? color.white : color.black, style=mid >= close ? label.style_label_down : label.style_label_up))
        if showtf
            array.set(tf_labels, x + 1, label.new(x=bar_index + labelloc2, y=mid, text=tf_text, color=mid >= close ? color.red : color.lime, textcolor=mid >= close ? color.white : color.black, style=mid >= close ? label.style_label_down : label.style_label_up))
        
        array.set(sr_lines, x + 1, line.new(x1=bar_index, y1=mid, x2=bar_index - 1, y2=mid, extend=extend.both, color=mid >= close ? resistancecolor : supportcolor, style=Lstyle, width=linewidth))

f_crossed_over() =>
    ret = false
    for x = 0 to array.size(sr_up_level) > 0 ? array.size(sr_up_level) - 1 : na by 1
        float mid = math.round_to_mintick((array.get(sr_up_level, x) + array.get(sr_dn_level, x)) / 2)
        if close[1] <= mid and close > mid
            ret := true
            ret
    ret

f_crossed_under() =>
    ret = false
    for x = 0 to array.size(sr_up_level) > 0 ? array.size(sr_up_level) - 1 : na by 1
        float mid = math.round_to_mintick((array.get(sr_up_level, x) + array.get(sr_dn_level, x)) / 2)
        if close[1] >= mid and close < mid
            ret := true
            ret
    ret

alertcondition(f_crossed_over(), title='Resistance Broken', message='Resistance Broken')
alertcondition(f_crossed_under(), title='Support Broken', message='Support Broken')

