Global & local RSI / quantifytools
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © quantifytools

//@version=5
indicator("Global & local RSI w/ adaptive bands", max_labels_count=500)

//                      Inputs

//RSI inputs
groupRsi= "RSI settings"

i_plotType = input.string("Candle", "Plot type", options=["Line", "Candle", "Bar"], group=groupRsi)
i_rsiLength = input.int(14, "Global and local RSI length", group=groupRsi, minval=1, maxval=100)

groupSmoothing = "Smoothing settings"

i_adaptiveOn = input.bool(false, "Enable adaptive bands", group=groupSmoothing, tooltip="Unlikely to have meaningful difference if smoothing is off (set to 1) and/or RSI length is set to default length (14). Will increase script loading time.")
i_Smoothing = input.int(1, "Smoothing factor", minval=1, maxval=100, group=groupSmoothing)
i_smoothingType = input.string("SMA", "Smoothing type", options=["SMA", "EMA", "HMA", "RMA", "WMA"], group=groupSmoothing)

//Stochastic RSI inputs
groupLocal = "Local RSI colors"

localExtremeUpCol1 = input.color(color.new(color.yellow, 60), "1 ▲", group=groupLocal, inline="up")
localExtremeUpCol2 = input.color(color.new(color.orange, 40), "2 ▲", group=groupLocal, inline="up")
localExtremeUpCol3 = input.color(color.new(color.fuchsia, 20), "3 ▲", group=groupLocal, inline="up")

localExtremeDownCol1 = input.color(color.new(color.yellow, 60), "1 ▼", group=groupLocal, inline="down")
localExtremeDownCol2 = input.color(color.new(color.orange, 40), "2 ▼", group=groupLocal, inline="down")
localExtremeDownCol3 = input.color(color.new(color.fuchsia, 20), "3 ▼", group=groupLocal, inline="down")

//Color inputs
groupGlobal = "Global RSI colors"

globalUpCol = input.color(color.white, "▲", group=groupGlobal, inline="global")
globalDownCol = input.color(color.rgb(85, 85, 85), "▼", group=groupGlobal, inline="global")
globalBandCol = input.color(color.rgb(255, 255, 255, 84), "☰", group=groupGlobal, inline="global")

groupDots = "Panel colors"

panelCol = input.color(color.rgb(255, 255, 255), "● & T", group=groupDots, inline="global")


//                      RSI calculations

//RSI length, stochastic RSI length and default smoothing for stochastic RSI
lengthRSI = i_rsiLength
lengthStoch = i_rsiLength
smoothK = 3 

//Separated RSI OHLC values for bars/candles
rsiOpen = ta.rsi(open, lengthRSI)
rsiHigh = ta.rsi(high, lengthRSI)
rsiLow = ta.rsi(low, lengthRSI)
rsiClose = ta.rsi(close, lengthRSI)

//Function to smooth RSI values with chosen method
smoothedValue(source, length) =>
    i_smoothingType == "SMA" ? ta.sma(source, length) :
     i_smoothingType == "EMA" ? ta.ema(source, length) :
      i_smoothingType == "HMA" ? ta.hma(source, length) :
       i_smoothingType == "RMA" ? ta.rma(source, length) : ta.wma(source, length)


//If smoothing is at or above 2, apply smoothing, else regular value. Same for stochastic RSI, but using smoothing length of 3 as a baseline.
rsiOpenSmooth = i_Smoothing >= 2 ? smoothedValue(rsiOpen, i_Smoothing) : rsiOpen
rsiHighSmooth = i_Smoothing >= 2 ? smoothedValue(rsiHigh, i_Smoothing) : rsiHigh
rsiLowSmooth = i_Smoothing >= 2 ? smoothedValue(rsiLow, i_Smoothing) : rsiLow
rsiCloseSmooth = i_Smoothing >= 2 ? smoothedValue(rsiClose, i_Smoothing) : rsiClose

//If smoothing at or above 2, use smoothedValue function for smoothing stochastic RSI, else use smooth factor of 3.
rsiStochNoSmooth = ta.stoch(rsiClose, rsiClose, rsiClose, lengthStoch)
rsiStochLightSmooth = ta.sma(rsiStochNoSmooth, smoothK)
k = i_Smoothing >= 2 ? smoothedValue(rsiStochNoSmooth, 3 + i_Smoothing) : rsiStochLightSmooth


//                      Adaptive bands

//When RSI > 55 or < 45, fetch pivot point. Same for stochastic RSI.
rsiPivotHigh = rsiCloseSmooth > 55 ? ta.pivothigh(rsiCloseSmooth, 5, 5) : na
rsiPivotLow = rsiCloseSmooth < 45 ? ta.pivotlow(rsiCloseSmooth, 5, 5) : na
stochPivotHigh = k > 55 ? ta.pivothigh(k, 5, 5) : na
stochPivotLow = k < 45 ? ta.pivotlow(k, 5, 5) : na

//Arrays for pivots
var pivotHighs = array.new_float(0, 0)
var pivotLows = array.new_float(0, 0)

var pivotHighsStoch = array.new_float(0, 0)
var pivotLowsStoch = array.new_float(0, 0)

//Populate RSI pivot arrays if adaptive bands are on 
if i_adaptiveOn == true
    array.push(pivotHighs, rsiPivotHigh)
    array.push(pivotLows, rsiPivotLow)

//Populate stochastic RSI pivot arrays if adaptive bands are on 
if i_adaptiveOn == true
    array.push(pivotHighsStoch, stochPivotHigh)
    array.push(pivotLowsStoch, stochPivotLow)

//Keep array size fixed
sizeLimit = 3000

if array.size(pivotHighs) > sizeLimit
    array.remove(pivotHighs, 0)

if array.size(pivotLows) > sizeLimit
    array.remove(pivotLows, 0)

if array.size(pivotHighsStoch) > sizeLimit
    array.remove(pivotHighsStoch, 0)

if array.size(pivotLowsStoch) > sizeLimit
    array.remove(pivotLowsStoch, 0)

//Median pivots for RSI and stochastic RSI to form adaptive bands. If adaptive is off, use traditional values.
medianPivotHigh = i_adaptiveOn == true ? array.median(pivotHighs) : 70
medianPivotLow = i_adaptiveOn == true ? array.median(pivotLows) : 30

medianPivotHighStoch = i_adaptiveOn == true ? array.median(pivotHighsStoch) : 100
medianPivotLowStoch = i_adaptiveOn == true ? array.median(pivotLowsStoch) : 1

//Forming middle point for RSI and stochastic RSI based on median pivots
medianMiddle = (medianPivotHigh + medianPivotLow) / 2
stochMid = (medianPivotHighStoch + medianPivotLowStoch) / 2

//Forming extreme zones for stochastic RSI based on median pivots (1/4, 1.5/4, 3/4, 3.5/4 of median pivots)
stochdDownExtreme1 = (stochMid + medianPivotLowStoch) / 2
stochUpExtreme1 = (stochMid + medianPivotHighStoch) / 2

stochdDownExtreme2 = (stochdDownExtreme1 + medianPivotLowStoch) / 2
stochUpExtreme2 = (stochUpExtreme1 + medianPivotHighStoch) / 2

stochUpExtreme3 = medianPivotHighStoch
stochdDownExtreme3 = medianPivotLowStoch 

//RSI median band lower/upper part of bands. If adaptive is off, use traditional values, else extend 10 %.
medianPivotHighX = i_adaptiveOn == true ? medianPivotHigh * 1.10 : 80 
medianPivotLowX = i_adaptiveOn == true ? medianPivotLow - (medianPivotHighX - medianPivotHigh) : 20


//                      Plot related calculations

//Pick user defined stochastic RSI colors
localExtremeUp = k >= stochUpExtreme1 ? localExtremeUpCol1 : na
localExtremeDown = k <= stochdDownExtreme1 ? localExtremeDownCol1: na

localExtremeUp2 = k >= stochUpExtreme2 ? localExtremeUpCol2 : na
localExtremeDown2 = k <= stochdDownExtreme2 ? localExtremeDownCol2 : na

localExtremeUp3 = k >= stochUpExtreme3 ? localExtremeUpCol3: na
localExtremeDown3 = k <= stochdDownExtreme3 ? localExtremeDownCol3: na

//Pick user defined visualization

//Bar
plotBarOpen = i_plotType == "Bar" ?  rsiOpenSmooth : na
plotBarClose = i_plotType == "Bar" ?  rsiCloseSmooth : na
plotBarHigh = i_plotType == "Bar" ?  rsiHighSmooth : na
plotBarLow = i_plotType == "Bar" ?  rsiLowSmooth : na

//Candle
plotCandleOpen = i_plotType == "Candle" ?  rsiOpenSmooth : na
plotCandleClose = i_plotType == "Candle" ?  rsiCloseSmooth : na
plotCandleHigh = i_plotType == "Candle" ?  rsiHighSmooth : na
plotCandleLow = i_plotType == "Candle" ?  rsiLowSmooth : na

//Line
plotLine = i_plotType == "Line" ? rsiCloseSmooth : na

//Up and down color for global RSI
globalCol = rsiCloseSmooth > rsiCloseSmooth[1] ? globalUpCol : globalDownCol


//                      Plots

//Plots for bands
p2x = plot(medianPivotHighX, "Upper RSI band 2", color=color.new(color.white, 1), linewidth=1)
p2 = plot(medianPivotHigh, "Upper RSI band 1", color=color.white, linewidth=1)

p3 = plot(medianMiddle, "Middle point", color=globalBandCol, linewidth=1)

p1 = plot(medianPivotLow, "Lower RSI band 1", color=color.white, linewidth=1)
p1x = plot(medianPivotLowX, "Lower RSI band 2", color=color.new(color.white, 1), linewidth=1)

//Fills for bands
fill(p1, p1x, color=globalBandCol) 
fill(p2, p2x, color=globalBandCol) 

fill(p1, p1x, color=localExtremeDown)
fill(p2, p2x, color=localExtremeUp) 

fill(p1, p1x, color=localExtremeDown2)
fill(p2, p2x, color=localExtremeUp2) 

fill(p1, p1x, color=localExtremeDown3)
fill(p2, p2x, color=localExtremeUp3) 

//Plots for bars, candles and line
plotbar(plotBarOpen, plotBarHigh, plotBarLow, plotBarClose, title="RSI bars", color=globalCol)
plotcandle(plotCandleOpen, plotCandleHigh, plotCandleLow, plotCandleClose, title="RSI candles", color=globalCol, wickcolor=globalCol, bordercolor=globalCol)
plot(plotLine, title="RSI line", color=globalCol)


//                      Table

//Rounding RSI and stochastic RSI values for table
roundedGlobal = math.round(rsiCloseSmooth, 0)
roundedLocal = math.round(k, 0)

//Colors for dot on table, the more blue the closer to 0, the more red the closer to 100.
globalDotUpCol = color.from_gradient(rsiCloseSmooth, medianMiddle, medianPivotHigh, panelCol, color.red)
globalDotDownCol = color.from_gradient(rsiCloseSmooth, medianPivotLow, medianMiddle, color.blue, panelCol)

localDotUpCol = color.from_gradient(k, stochMid, medianPivotHighStoch, panelCol, color.red)
localDotDownCol = color.from_gradient(k, medianPivotLowStoch, stochMid, color.blue, panelCol)

globalDotCol = rsiCloseSmooth >= 50 ? globalDotUpCol : globalDotDownCol
localDotCol = k >= 50 ? localDotUpCol : localDotDownCol

//Initializing table
var rsiTable = table.new(position = position.top_right, columns = 50, rows = 50, bgcolor = color.new(color.black, 100), border_width = 0, border_color=color.gray)

//Populating table
table.cell(table_id = rsiTable, column = 0, row = 0, text = "Global RSI: " + str.tostring(roundedGlobal), text_color=panelCol)
table.cell(table_id = rsiTable, column = 1, row = 0, text = "●", text_color=globalDotCol)

table.cell(table_id = rsiTable, column = 2, row = 0, text = "Local RSI: " + str.tostring(roundedLocal), text_color=panelCol)
table.cell(table_id = rsiTable, column = 3, row = 0, text = "●", text_color=localDotCol)

//                      Alerts

//Defining alert condition for global RSI extremes
globalRsiExtremeUp = ta.crossover(rsiCloseSmooth, medianPivotHigh)
globalRsiExtremeDown = ta.crossunder(rsiCloseSmooth, medianPivotLow)

//Defining alert condition for local RSI extremes
localRsiExtremeUp = ta.crossover(k, medianPivotHighStoch)
localRsiExtremeDown = ta.crossunder(k, medianPivotLowStoch)

//Alerts
alertcondition(globalRsiExtremeUp, "Global RSI > upper band", "Global RSI overbought condition detected.")
alertcondition(globalRsiExtremeDown, "Global RSI < lower band", "Global RSI oversold condition detected.")
alertcondition(localRsiExtremeUp, "Local RSI > upper band", "Local RSI extreme up condition detected.")
alertcondition(localRsiExtremeDown, "Local RSI < lower band", "Local RSI extreme down condition detected.")