Weekly Range Support & Resistance Levels [QuantVue]
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © QuantVue

//@version=5
indicator("Weekly Range Support & Resistance Levels", overlay = true, max_labels_count = 500, max_lines_count = 500)
 
//-----------------------------------------------//
//runtime errors
//-----------------------------------------------//
if timeframe.ismonthly 
    runtime.error('Please switch to lower timeframe')
else if (timeframe.in_seconds() / 60 < timeframe.in_seconds('3') / 60) and (syminfo.type == 'stock' or syminfo.type == 'fund' or syminfo.type == 'index') 
    runtime.error('Please switch to 3 minute chart or higher')
else if timeframe.in_seconds() / 60 < timeframe.in_seconds('30') / 60 and (syminfo.type == 'crypto' or syminfo.type == 'forex')
    runtime.error('Please switch to 30 minute time frame or higher')
else if timeframe.in_seconds() / 60 < timeframe.in_seconds('15') / 60 and syminfo.type == 'cfd'
    runtime.error('Please switch to 15 minute time frame or higher')
 
//-----------------------------------------------//
//inputs
//-----------------------------------------------//
showTable = input.bool(true, 'Show Stats on Weekly Chart', inline = '1')
yPos = input.string("Top", " ", options = ["Top", "Middle", "Bottom"], inline = '1')
xPos = input.string("Right", " ", options = ["Right","Center", "Left"], inline = '1')
statsBG = input.color(color.gray, 'Stats Table BG Color', inline = '2')
statsText = input.color(color.white, 'Stats Table Text Color', inline = '2')
 
averagingPerdiod = input.int(30, 'Averaging Period', minval = 5, step = 1)
multiplier = input.float(1.0, 'StDev Multiplier', minval = .25, maxval = 4, step = .25)
 
sLineColor = input.color(color.green, 'Support Color', inline = '3')
rLineColor = input.color(color.red, 'Resistance Color', inline = '3')
showFill = input.bool(true, 'Fill', inline = '3')
showPrice = input.bool(true, 'Show Support / Resistance Prices', inline = '4')
labelTextColor = input.color(color.rgb(255, 255, 255), ' ', inline = '4')
 
showWO = input.bool(true, 'Show Weekly Open Line', inline = '5')
wOColor = input.color(color.orange, ' ', inline = '5')
r1Style = input.string('Solid', 'R1 Line Style', ['Solid', 'Dashed', 'Dotted'], inline = '6')
r2Style = input.string('Solid', 'R2 Line Style', ['Solid', 'Dashed', 'Dotted'], inline = '6')
s1Style = input.string('Solid', 'S1 Line Style', ['Solid', 'Dashed', 'Dotted'], inline = '7')
s2Style = input.string('Solid', 'S2 Line Style', ['Solid', 'Dashed', 'Dotted'], inline = '7')
showPrevious = input.bool(false, 'Show Previous Levls')
 
//-----------------------------------------------//
//methods
//-----------------------------------------------//
method switcher(string this) =>
    switch this
        'Solid' => line.style_solid
        'Dashed' => line.style_dashed
        'Dotted' => line.style_dotted
 
//-----------------------------------------------//
//variables
//-----------------------------------------------//
var table stats = table.new(str.lower(yPos) + '_' + str.lower(xPos), 5, 4, border_color = color.new(color.white,100), border_width = 2,
     frame_color = color.new(color.white,100), frame_width = 2)
 
var float[] upAvgArray = array.new<float>()
var float[] downAvgArray = array.new<float>()
 
var line r1Line = na, var line r2Line = na, var line s1Line = na, var line s2Line = na, var line WO = na 
var label r1Label = na, var label r2Label = na, var label s1Label = na, var label s2Label = na
var box rFill = na, var box sFill = na
var weekCount = 0, var closeAboveR1 = 0, var closeAboveR2 = 0, var closeBelowS1 = 0, var closeBelowS2 = 0
var touchR1 = 0, var touchR2 = 0, var touchS1 = 0, var touchS2 = 0
var R1 = 0.0, var R2 = 0.0, var S1 = 0.0, var S2 = 0.0
 
[weekOpen, weekHigh, weekLow, weekClose] = request.security(syminfo.tickerid, 'W',[open,high,low,close], lookahead = barmerge.lookahead_on)
avgPeriod = timeframe.period == 'W' ?  30 : timeframe.period == 'D' ? averagingPerdiod * 5 : timeframe.isminutes ?  
     (58500 / str.tonumber(timeframe.period)) : averagingPerdiod
newWeek = ta.change(time('W'))
idxCount = ta.barssince(newWeek)
 
//-----------------------------------------------//
//calculations
//-----------------------------------------------//
up = newWeek ? 100 * ((weekHigh - weekOpen) / weekClose) : na
down = newWeek ? 100 * math.abs(((weekOpen - weekLow) / weekClose)) : na
upStdev = ta.stdev(up, averagingPerdiod) * multiplier
upSD = newWeek ? upStdev[1] : na
downStdev = ta.stdev(down, averagingPerdiod) * multiplier
downSd = newWeek ? downStdev[1] : na
 
if newWeek
    if upAvgArray.size() > averagingPerdiod
        upAvgArray.pop()
        upAvgArray.unshift(up)
    else
        upAvgArray.unshift(up)
upAvg = upAvgArray.size() > 0 ? upAvgArray.avg() : na
 
if newWeek
    if downAvgArray.size() > averagingPerdiod
        downAvgArray.pop()
        downAvgArray.unshift(down)
    else
        downAvgArray.unshift(down)
downAvg = downAvgArray.size() > 0 ? downAvgArray.avg() : na
 
R1 := newWeek ? weekOpen + (upAvg / 100) * weekOpen : R1[1]
R2 := newWeek ? weekOpen + ((upAvg + upSD) / 100) * weekOpen : R2[1]
S1 := newWeek ? weekOpen - (downAvg / 100) * weekOpen : S1[1]
S2 := newWeek ? weekOpen - ((downAvg + downSd) / 100) * weekOpen : S2[1]
 
//-----------------------------------------------//
//weekly stats
//-----------------------------------------------//
weekCount := newWeek ? weekCount + 1 : weekCount
if weekClose > R1
    closeAboveR1 += 1
if weekClose > R2
    closeAboveR2 += 1 
if weekClose < S1
    closeBelowS1 += 1
if weekClose < S2
    closeBelowS2 += 1
if weekHigh >= R1
    touchR1 += 1
if weekHigh >= R2
    touchR2 += 1
if weekLow <= S1
    touchS1 += 1
if weekLow <= S2
    touchS2 += 1
closeInsideAvg = weekCount - closeAboveR1 - closeBelowS1
closeInsideAvgPlus = weekCount - closeAboveR2 - closeBelowS2
 
//-----------------------------------------------//
//weekly stats table
//-----------------------------------------------//
if barstate.islast and showTable and timeframe.isweekly and yPos == 'Top'
    stats.cell(0,0, ' ')
    stats.cell(0, 1, 'Touch R1: ' + str.tostring(touchR1) + ' / ' + str.tostring((touchR1/weekCount), '#.#%'), 
         text_color = statsText, bgcolor = statsBG)
    stats.cell(1, 1, 'Close > R1: ' + str.tostring(closeAboveR1) + ' / ' + str.tostring((closeAboveR1/weekCount), '#.#%'), 
         text_color = statsText, bgcolor = statsBG)
    stats.cell(2, 1, 'Touch R2: ' + str.tostring(touchR2) + ' / ' + str.tostring((touchR2/weekCount), '#.#%'), 
         text_color = statsText, bgcolor = statsBG)
    stats.cell(3, 1, 'Close > R2: ' + str.tostring(closeAboveR2) + ' / ' + str.tostring((closeAboveR2/weekCount), '#.#%'), 
         text_color = statsText, bgcolor = statsBG)
    stats.cell(0, 2, 'Touch S1: ' + str.tostring(touchS1) + ' / ' + str.tostring((touchS1/weekCount), '#.#%'), 
         text_color = statsText, bgcolor = statsBG)
    stats.cell(1, 2, 'Close < S1: ' + str.tostring(closeBelowS1) + ' / ' + str.tostring((closeBelowS1/weekCount), '#.#%'), 
         text_color = statsText, bgcolor = statsBG)
    stats.cell(2, 2, 'Touch S2: ' + str.tostring(touchS2) + ' / ' + str.tostring((touchS2/weekCount), '#.#%'), 
         text_color = statsText, bgcolor = statsBG)
    stats.cell(3, 2, 'Close < S2: ' + str.tostring(closeBelowS2) + ' / ' + str.tostring((closeBelowS2/weekCount), '#.#%'), 
         text_color = statsText, bgcolor = statsBG)
    stats.cell(4, 3, 'Weeks Analyzed: ' + str.tostring(weekCount), text_color = statsText, bgcolor = statsBG)
    stats.cell(4, 1, 'Total Closes Inside R1/S1: ' + str.tostring(closeInsideAvg) + ' / ' + str.tostring((closeInsideAvg/weekCount), '#.#%'), 
         text_color = statsText, bgcolor = statsBG)
    stats.cell(4, 2, 'Total Closes Inside R2/S2: ' + str.tostring(closeInsideAvgPlus) +  ' / ' + str.tostring((closeInsideAvgPlus/weekCount), '#.#%'), 
         text_color = statsText, bgcolor = statsBG)
else if barstate.islast and showTable and timeframe.isweekly
    stats.cell(0, 0, 'Touch R1: ' + str.tostring(touchR1) + ' / ' + str.tostring((touchR1/weekCount), '#.#%'), 
         text_color = statsText, bgcolor = statsBG)
    stats.cell(1, 0, 'Close > R1: ' + str.tostring(closeAboveR1) + ' / ' + str.tostring((closeAboveR1/weekCount), '#.#%'), 
         text_color = statsText, bgcolor = statsBG)
    stats.cell(2, 0, 'Touch R2: ' + str.tostring(touchR2) + ' / ' + str.tostring((touchR2/weekCount), '#.#%'), 
         text_color = statsText, bgcolor = statsBG)
    stats.cell(3, 0, 'Close > R2: ' + str.tostring(closeAboveR2) + ' / ' + str.tostring((closeAboveR2/weekCount), '#.#%'), 
         text_color = statsText, bgcolor = statsBG)
    stats.cell(0, 1, 'Touch S1: ' + str.tostring(touchS1) + ' / ' + str.tostring((touchS1/weekCount), '#.#%'), 
         text_color = statsText, bgcolor = statsBG)
    stats.cell(1, 1, 'Close < S1: ' + str.tostring(closeBelowS1) + ' / ' + str.tostring((closeBelowS1/weekCount), '#.#%'), 
         text_color = statsText, bgcolor = statsBG)
    stats.cell(2, 1, 'Touch S2: ' + str.tostring(touchS2) + ' / ' + str.tostring((touchS2/weekCount), '#.#%'), 
         text_color = statsText, bgcolor = statsBG)
    stats.cell(3, 1, 'Close < S2: ' + str.tostring(closeBelowS2) + ' / ' + str.tostring((closeBelowS2/weekCount), '#.#%'), 
         text_color = statsText, bgcolor = statsBG)
    stats.cell(0, 2, 'Weeks Analyzed: ' + str.tostring(weekCount), text_color = statsText, bgcolor = statsBG)
    stats.cell(1, 2, 'Total Closes Inside R1/S1: ' + str.tostring(closeInsideAvg) + ' / ' + str.tostring((closeInsideAvg/weekCount), '#.#%'), 
         text_color = statsText, bgcolor = statsBG)
    stats.cell(2, 2, 'Total Closes Inside R2/S2: ' + str.tostring(closeInsideAvgPlus) +  ' / ' + str.tostring((closeInsideAvgPlus/weekCount), '#.#%'), 
         text_color = statsText, bgcolor = statsBG)
 
//-----------------------------------------------//
//support and resistance levels
//-----------------------------------------------//
if newWeek and (syminfo.type == 'stock' or syminfo.type == 'fund' or syminfo.type == 'index')
    (r1Line[1]).delete(), (r2Line[1]).delete(), (s1Line[1]).delete(), (s2Line[1]).delete()
    (r1Label[1]).delete(), (r2Label[1]).delete(), (s1Label[1]).delete(), (s2Label[1]).delete()
    (WO[1]).delete()
    if showWO
        WO := line.new(bar_index, weekOpen, timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 4 : 
             timeframe.isminutes ? bar_index + int((390 / (str.tonumber(timeframe.period)) * 5)) : bar_index + 4, weekOpen, 
             color = wOColor, width = 3)
    r1Line := line.new(bar_index, R1, timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 4 : 
         timeframe.isminutes ? bar_index + int((390 / (str.tonumber(timeframe.period)) * 5)) : bar_index + 4, R1, 
         color = rLineColor, width = 2, style = r1Style.switcher())
    r2Line := line.new(bar_index, R2, timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 4 : 
         timeframe.isminutes ? bar_index + int((390 / (str.tonumber(timeframe.period)) * 5)) : bar_index + 4, R2, 
         color = rLineColor, width = 2, style = r2Style.switcher())
    s1Line := line.new(bar_index, S1, timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 4 : 
         timeframe.isminutes ? bar_index + int((390 / (str.tonumber(timeframe.period)) * 5)) : bar_index + 4, S1, 
         color = sLineColor, width = 2, style = s1Style.switcher())
    s2Line := line.new(bar_index, S2, timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 4 : 
         timeframe.isminutes ? bar_index + int((390 / (str.tonumber(timeframe.period)) * 5)) : bar_index + 4, S2, 
         color = sLineColor, width = 2, style = s2Style.switcher())
    if showPrice
        r1Label := label.new(timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 4 : 
             timeframe.isminutes ? bar_index + int((390 / (str.tonumber(timeframe.period)) * 5)) : bar_index + 4, 
             R1, 'R1 $' + str.tostring(R1, format.mintick), style = label.style_label_left, color = color.new(color.white,100),
             textcolor = labelTextColor)
        r2Label := label.new(timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 4 : 
             timeframe.isminutes ? bar_index + int((390 / (str.tonumber(timeframe.period)) * 5)) : bar_index + 4, 
             R2, 'R2 $' + str.tostring(R2, format.mintick), style = label.style_label_left, color = color.new(color.white,100),
             textcolor = labelTextColor)
        s1Label := label.new(timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 4 : 
             timeframe.isminutes ? bar_index + int((390 / (str.tonumber(timeframe.period)) * 5)) : bar_index + 4, 
             S1, 'S1 $' + str.tostring(S1, format.mintick), style = label.style_label_left, color = color.new(color.white,100),
             textcolor = labelTextColor)
        s2Label := label.new(timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 4 : 
             timeframe.isminutes ? bar_index + int((390 / (str.tonumber(timeframe.period)) * 5)) : bar_index + 4, 
             S2, 'S2 $' + str.tostring(S2, format.mintick), style = label.style_label_left, color = color.new(color.white,100),
             textcolor = labelTextColor)
    if showFill
        (sFill[1]).delete(), (rFill[1]).delete()
        sFill := box.new(bar_index, S1, timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 4 : 
         timeframe.isminutes ? bar_index + int((390 / (str.tonumber(timeframe.period)) * 5)) : bar_index + 4, S2, 
         border_width = 0, bgcolor = color.new(sLineColor,80))
        rFill := box.new(bar_index, R1, timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 4 : 
         timeframe.isminutes ? bar_index + int((390 / (str.tonumber(timeframe.period)) * 5)) : bar_index + 4, R2, 
         border_width = 0, bgcolor = color.new(rLineColor,80))
else if newWeek and (syminfo.type == 'crypto' or syminfo.type == 'forex' or syminfo.type == 'cfd')
    (r1Line[1]).delete(), (r2Line[1]).delete(), (s1Line[1]).delete(), (s2Line[1]).delete()
    (r1Label[1]).delete(), (r2Label[1]).delete(), (s1Label[1]).delete(), (s2Label[1]).delete()
    (WO[1]).delete()
    if showWO
        WO := line.new(bar_index, weekOpen, timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 6 : 
             timeframe.isminutes ? bar_index + int((24 * 60 / (str.tonumber(timeframe.period)) * 7)) : bar_index + 6, weekOpen, 
             color = wOColor, width = 3)
    r1Line := line.new(bar_index, R1, timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 6 : 
         timeframe.isminutes ? bar_index + int((24 * 60 / (str.tonumber(timeframe.period)) * 7)) : bar_index + 6, R1, 
         color = rLineColor, width = 2, style = r1Style.switcher())
    r2Line := line.new(bar_index, R2, timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 6 : 
         timeframe.isminutes ? bar_index + int((24 * 60/ (str.tonumber(timeframe.period)) * 7)) : bar_index + 6, R2, 
         color = rLineColor, width = 2, style = r2Style.switcher())
    s1Line := line.new(bar_index, S1, timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 6 : 
         timeframe.isminutes ? bar_index + int((24 * 60 / (str.tonumber(timeframe.period)) * 7)) : bar_index + 6, S1, 
         color = sLineColor, width = 2, style = s1Style.switcher())
    s2Line := line.new(bar_index, S2, timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 6 : 
         timeframe.isminutes ? bar_index + int((24 * 60 / (str.tonumber(timeframe.period)) * 7)) : bar_index + 6, S2, 
         color = sLineColor, width = 2, style = s2Style.switcher())
    if showPrice
        r1Label := label.new(timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 6 : 
             timeframe.isminutes ? bar_index + int((24 * 60 / (str.tonumber(timeframe.period)) * 7)) : bar_index + 6, 
             R1, 'R1 $' + str.tostring(R1, format.mintick), style = label.style_label_left, color = color.new(color.white,100),
             textcolor = labelTextColor)
        r2Label := label.new(timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 6 : 
             timeframe.isminutes ? bar_index + int((24 * 60 / (str.tonumber(timeframe.period)) * 7)) : bar_index + 6, 
             R2, 'R2 $' + str.tostring(R2, format.mintick), style = label.style_label_left, color = color.new(color.white,100),
             textcolor = labelTextColor)
        s1Label := label.new(timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 6 : 
             timeframe.isminutes ? bar_index + int((24 * 60 / (str.tonumber(timeframe.period)) * 7)) : bar_index + 6, 
             S1, 'S1 $' + str.tostring(S1, format.mintick), style = label.style_label_left, color = color.new(color.white,100),
             textcolor = labelTextColor)
        s2Label := label.new(timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 6 : 
             timeframe.isminutes ? bar_index + int((24 * 60 / (str.tonumber(timeframe.period)) * 7)) : bar_index + 6, 
             S2, 'S2 $' + str.tostring(S2, format.mintick), style = label.style_label_left, color = color.new(color.white,100),
             textcolor = labelTextColor)
    if showFill
        (sFill[1]).delete(), (rFill[1]).delete()
        sFill := box.new(bar_index, S1, timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 6 : 
         timeframe.isminutes ? bar_index + int((24 * 60 / (str.tonumber(timeframe.period)) * 7)) : bar_index + 6, S2, 
         border_width = 0, bgcolor = color.new(sLineColor,80))
        rFill := box.new(bar_index, R1, timeframe.isweekly ? bar_index + 2 : timeframe.isdaily ? bar_index + 6 : 
         timeframe.isminutes ? bar_index + int((24 * 60 / (str.tonumber(timeframe.period)) * 7)) : bar_index + 6, R2, 
         border_width = 0, bgcolor = color.new(rLineColor,80))
if showPrevious and showFill
    sFill.set_left(bar_index), rFill.set_left(bar_index)
 
//-----------------------------------------------//
//show historical levels
//-----------------------------------------------//
fillColor = color.new(color.white,100)
r1Plot = plot(showPrevious ? R1 : na, color = color.red)
r2Plot = plot(showPrevious ? R2 : na, color = color.maroon)
s1Plot = plot(showPrevious ? S1 : na, color = color.green)
s2Plot = plot(showPrevious ? S2 : na, color = color.lime)
woPlot = plot(showPrevious ? weekOpen : na, color = wOColor, style = plot.style_linebr)
fill(r1Plot, r2Plot, color = showFill ? color.new(rLineColor, 80) : fillColor)
fill(s1Plot, s2Plot, color = showFill ? color.new(sLineColor, 80) : fillColor)
 
//-----------------------------------------------//
//alert conditions
//-----------------------------------------------//
crossR1 = ta.crossover(close,R1)
crossR2 = ta.crossover(close,R2)
crossS1 = ta.crossunder(close,S1)
crossS2 = ta.crossunder(close,S2)
 
alertcondition(crossR1, 'Cross Above R1', 'Price crossing above R1')
alertcondition(crossR2, 'Cross Above R2', 'Price crossing above R2')
alertcondition(crossS1, 'Cross Below S1', 'Price crossing below S1')
alertcondition(crossS2, 'Cross Below S2', 'Price crossing below S2')