ATR Adaptive RSI Oscillator
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © tkarolak
//@version=5

// ====================
// ==== Background ====
// ====================

// The "ATR Adaptive RSI Oscillator" combines the ATR (Average True Range) and the RSI (Relative Strength Index) to enhance the responsiveness of the RSI to current market conditions.
// Hypothesis: Adjusting the RSI period dynamically based on the ATR's measure of market volatility could offer more precise signals in different market environments.
// This dynamic adjustment employs a Z-score mechanism that utilizes the standard deviation and mean of the ATR over a specified sample size to set a threshold.
// The oscillator leverages volume-smoothed RSI calculations, which use a time-decayed weighted moving average of RSI values to potentially reduce noise and offer a clearer trend representation.
// It adapts the RSI calculation period automatically, choosing longer periods in high volatility environments and shorter ones during low volatility, aiming to enhance the accuracy of the RSI in diverse market situations.
// Insights:
// - The adaptive mode can be toggled on or off, allowing users to switch between a dynamic RSI and a traditional fixed-period RSI, facilitating a customizable approach to market analysis.
// - Users can fine-tune the sensitivity of the adaptive mechanism through settings such as the Z-score multiplier, which filters ATR values based on their deviation from the mean.
// - The visual representation of the oscillator is color-coded to depict the current RSI period dynamically, assisting in quick visual analysis.
// - The indicator includes an option to display a histogram representing the number of bars the RSI length was increased, aiding in the historical analysis of market volatility and its effects on the RSI period.
// - For identifying potential reversals, the script features divergence detection settings, allowing users to specify the lookback range for detecting divergences.
// - The Overbought (OB) and Oversold (OS) boundaries are configurable, offering users the ability to set their thresholds for these critical levels, including extreme OB and OS levels for more stringent filtering of signals.
// - While utilizing this tool, traders should remain aware of the broader market context and consider using it in conjunction with other analysis methods for a well-rounded approach.
// Usage:
// - In highly volatile markets, traders might observe the adaptive RSI taking on higher periods, potentially filtering out noise and highlighting more significant trends.
// - In quieter markets, the adaptive mechanism might default to shorter periods, potentially offering quicker signals for short-term opportunities.
// - Users can exploit the divergence detection feature to identify potential reversal points by observing discrepancies between price movements and the RSI.
// - For those who prefer a more traditional approach, the adaptive feature can be disabled to revert to a standard RSI analysis with a fixed period.
// - Consider utilizing the OB/OS boundaries to identify potential entry and exit points, keeping an eye on the extreme levels for potential high-confidence signals.

indicator("ATR Adaptive RSI Oscillator", "ATR ARSI", false, format.price, 2)

// Custom types
type SettingsRsi
	float 	source
    int     lenght
    int     lenghtMax
    bool    smoothing
    int     sampleSize
    float   zScore

////////////////////////////////////////////////////////////////////////////////
//                           ====== TOOLTIPS ======                           //
////////////////////////////////////////////////////////////////////////////////

// RSI settings
string gRsi             = "General RSI settings"
string gRsiAtr          = "ATR-Based Periods"
string ttSourceRsi 		= "Source for RSI calculations"
string ttLenRsi 		= "The minimum RSI length for adaptive mode or the default RSI length used when adaptive mode is disabled"
string ttSmoothingRsi 	= "The smoothing process computes a time-decayed weighted moving average of RSI values over the last two bars, using volume-based weights.\n\nThis approach provides a time-sensitive smoothing effect, reducing noise for a clearer view of trend strength compared to the standard RSI"
string ttAdaptiveRsi 	= "Enable adaptive RSI periods based on ATR.\n\nThis feature dynamically adjusts the RSI calculation period based on market volatility measured by ATR. It enhances accuracy during different market conditions by using longer periods in high volatility and shorter periods in lower volatility."
string ttAtrLengthRsi 	= "Lenght used for ATR calculation"
string ttMaxLenRsi 		= "Maximum RSI length for adaptive periods"
string ttSampleSizeRsi	= "Dataset length for statistical computations in deriving a normal distribution, used to identify data points (ATR), meeting criteria exceeding the standard deviation multiplied by the Z-score coefficient"
string ttZscoreRsi		= "The Z-scores multiplier serves as a filtering mechanism, providing insight into the extent to which a data point (ATR) deviates from the mean in terms of standard deviations."
string ttHistogramRsi	= "Displays a histogram representing the number of bars the RSI length was increased"

SettingsRsi settingsRsi		= 
 SettingsRsi.new(
   input.source (close, 	  "Source", 			    		group = gRsi, tooltip = ttSourceRsi),
   input.int    (14,          "Length",                         group = gRsi, minval = 3, tooltip = ttLenRsi),
   input.int    (28,          "Maximum RSI Length",             group = gRsi, minval = 7, maxval = 36, tooltip = ttMaxLenRsi),
   input.bool   (true,        "Smoothed Mode RSI?",             group = gRsi, tooltip = ttSmoothingRsi),
   input.int    (200,         "Control sample size",            group = gRsiAtr, step = 1, minval = 100, maxval = 500, tooltip = ttSampleSizeRsi),
   input.float  (2.0,         "Z-score",                        group = gRsiAtr, step = 0.1, minval = 1.0, maxval = 3.0, tooltip = ttZscoreRsi)
 )
bool settingsRsiEnableAdaptive 	= input.bool(true,	"Adaptive Mode RSI?",       group = gRsi, tooltip = ttAdaptiveRsi)
int settingsRsiLengthAtr    	= input.int (14,"ATR Length",					group = gRsiAtr, minval = 3)
bool settingsRsiHistogram		= input.bool(true,	"Show RSI boost histogram",	group = gRsiAtr, tooltip = ttHistogramRsi)

// Divergences Config
string gdivergence 		= "Divergence Detector"
settingsDvgShowDvg 		= input.bool	(false, "Enable Divergences?", 		group = gdivergence)
settingsDvgLookbackR 	= input			(5, 	"Pivot Lookback Right", 	group = gdivergence)
settingsDvgLookbackL 	= input			(5, 	"Pivot Lookback Left", 		group = gdivergence)
settingsDvgLookbackMax	= input			(60, 	"Max of Lookback Range", 	group = gdivergence)
settingsDvgLookbackMin 	= input			(5, 	"Min of Lookback Range", 	group = gdivergence)

// -- Channel OB/OS config
string gRsiBoundaries 	= "OB/OS Boundaries"
int settingsRsiUpper	= input.int		(70,	"OB", 						group = gRsiBoundaries, inline = "OB", minval = 50, maxval = 100)
int settingsRsiUpperx 	= input.int		(80,	"OB Extreme", 				group = gRsiBoundaries, inline = "OB", minval = 50, maxval = 100)
int settingsRsiLower 	= input.int		(30,	"OS", 						group = gRsiBoundaries, inline = "OS", minval = 0, maxval = 50)
int settingsRsiLowerx 	= input.int		(20,	"OS Extreme", 				group = gRsiBoundaries, inline = "OS", minval = 0, maxval = 50)

////////////////////////////////////////////////////////////////////////////////
//                          ====== FUNCTIONS ======                           //
////////////////////////////////////////////////////////////////////////////////

if barstate.islast and settingsRsi.lenghtMax <= settingsRsi.lenght 
    runtime.error("The maximum RSI length must exceed the RSI length")

// Function to calculate RSI with optional volume smoothing
getRsi(_source, _length, _mode) =>
    float _zrsi = ta.rsi(_source, _length)
    var float _smoothed = na
    _smoothed := na(_smoothed[2]) ? _zrsi : (_smoothed[2] * volume[2]/3 + _smoothed[1] * volume[1]/2 + _zrsi * volume) / (volume[2]/3+volume[1]/2+volume)
    _mode ? _smoothed : _zrsi

//////////////////////////////////////////////////////////////////////////
//                   ====== RSI / ADAPTIVE RSI ======                   //
//////////////////////////////////////////////////////////////////////////

rsiArray        = array.new_float(37)
// Repeat RSI calculation for other periods
array.set(rsiArray,0,na)
array.set(rsiArray,1,na)
array.set(rsiArray,2,na)
array.set(rsiArray,3,getRsi(settingsRsi.source, 3, settingsRsi.smoothing))
array.set(rsiArray,4,getRsi(settingsRsi.source, 4, settingsRsi.smoothing))
array.set(rsiArray,5,getRsi(settingsRsi.source, 5, settingsRsi.smoothing))
array.set(rsiArray,6,getRsi(settingsRsi.source, 6, settingsRsi.smoothing))
array.set(rsiArray,7,getRsi(settingsRsi.source, 7, settingsRsi.smoothing))
array.set(rsiArray,8,getRsi(settingsRsi.source, 8, settingsRsi.smoothing))
array.set(rsiArray,9,getRsi(settingsRsi.source, 9, settingsRsi.smoothing))
array.set(rsiArray,10,getRsi(settingsRsi.source, 10, settingsRsi.smoothing))
array.set(rsiArray,11,getRsi(settingsRsi.source, 11, settingsRsi.smoothing))
array.set(rsiArray,12,getRsi(settingsRsi.source, 12, settingsRsi.smoothing))
array.set(rsiArray,13,getRsi(settingsRsi.source, 13, settingsRsi.smoothing))
array.set(rsiArray,14,getRsi(settingsRsi.source, 14, settingsRsi.smoothing))
array.set(rsiArray,15,getRsi(settingsRsi.source, 15, settingsRsi.smoothing))
array.set(rsiArray,16,getRsi(settingsRsi.source, 16, settingsRsi.smoothing))
array.set(rsiArray,17,getRsi(settingsRsi.source, 17, settingsRsi.smoothing))
array.set(rsiArray,18,getRsi(settingsRsi.source, 18, settingsRsi.smoothing))
array.set(rsiArray,19,getRsi(settingsRsi.source, 19, settingsRsi.smoothing))
array.set(rsiArray,20,getRsi(settingsRsi.source, 20, settingsRsi.smoothing))
array.set(rsiArray,21,getRsi(settingsRsi.source, 21, settingsRsi.smoothing))
array.set(rsiArray,22,getRsi(settingsRsi.source, 22, settingsRsi.smoothing))
array.set(rsiArray,23,getRsi(settingsRsi.source, 23, settingsRsi.smoothing))
array.set(rsiArray,24,getRsi(settingsRsi.source, 24, settingsRsi.smoothing))
array.set(rsiArray,25,getRsi(settingsRsi.source, 25, settingsRsi.smoothing))
array.set(rsiArray,26,getRsi(settingsRsi.source, 26, settingsRsi.smoothing))
array.set(rsiArray,27,getRsi(settingsRsi.source, 27, settingsRsi.smoothing))
array.set(rsiArray,28,getRsi(settingsRsi.source, 28, settingsRsi.smoothing))
array.set(rsiArray,29,getRsi(settingsRsi.source, 29, settingsRsi.smoothing))
array.set(rsiArray,30,getRsi(settingsRsi.source, 30, settingsRsi.smoothing))
array.set(rsiArray,31,getRsi(settingsRsi.source, 31, settingsRsi.smoothing))
array.set(rsiArray,32,getRsi(settingsRsi.source, 32, settingsRsi.smoothing))
array.set(rsiArray,33,getRsi(settingsRsi.source, 33, settingsRsi.smoothing))
array.set(rsiArray,34,getRsi(settingsRsi.source, 34, settingsRsi.smoothing))
array.set(rsiArray,35,getRsi(settingsRsi.source, 35, settingsRsi.smoothing))
array.set(rsiArray,36,getRsi(settingsRsi.source, 36, settingsRsi.smoothing))

// Get default RSI
float rsi = array.get(rsiArray,settingsRsi.lenght)

// Calculate RSI Lenght for Adaptive RSI
atr         = ta.atr(settingsRsiLengthAtr)
// Calculate standard deviation and mean of ATR with samle data size
atrStddev   = ta.stdev(atr, settingsRsi.sampleSize)
atrMean     = ta.sma(atr, settingsRsi.sampleSize)
zScoreThreshold = atrMean + settingsRsi.zScore * atrStddev
// Gather and normalize ATR values exceeding one standard deviation (# z-score ranges right)
overZscoreAtr = array.new_float()
for i = 0 to settingsRsi.sampleSize-1 by 1
    if atr[i] > zScoreThreshold
        array.push(overZscoreAtr,atr[i])

// Normalize ATR values exceeding z-score limit to boosted range of RSI Lenght
overZscoreAtrMin = array.min(overZscoreAtr)
overZscoreAtrMax = array.max(overZscoreAtr)
minBoostedRsi = settingsRsi.lenght + 1
// Normalization taking place here
newRsiLenght = atr > zScoreThreshold ? int((atr - overZscoreAtrMin) / (overZscoreAtrMax - overZscoreAtrMin) * (settingsRsi.lenghtMax - minBoostedRsi) + minBoostedRsi) : settingsRsi.lenght

// Determine RSI Length based on Adaptive Mode
// If adaptive mode is enabled (i_adaptive), 
// find the corresponding RSI length from the array based on the ATR value
// Otherwise, use the default RSI (rsi).
rsi := settingsRsiEnableAdaptive and atr > zScoreThreshold ? array.get(rsiArray,newRsiLenght): rsi
rsiOriginal = settingsRsiEnableAdaptive ? array.get(rsiArray,settingsRsi.lenght): na
////////////////////////////////////////////////////////////////////////////////
//                     ====== DRAWING and PLOTTING ======                     //
////////////////////////////////////////////////////////////////////////////////

// RSI and histogram color
colStep = int(255/(settingsRsi.lenghtMax-settingsRsi.lenght))
greenPaint = settingsRsiEnableAdaptive ? 255-colStep*(newRsiLenght-settingsRsi.lenght) : 255
greenPaint := greenPaint > 255 ? 255 : greenPaint < 0 ? 0 : greenPaint
color colRSI = color.new(color.rgb(255,greenPaint,0),20)
color colHistogram = color.new(color.rgb(255,greenPaint,0),50)

// RSI channel Lines
top 	= hline(100, 				"Top",			color.new(color.red, 80),	display = display.none)
upperx	= hline(settingsRsiUpperx,	"OB Extreme",	color.new(color.red, 80), 				hline.style_solid)
upper	= hline(settingsRsiUpper, 	"OB",			color.new(color.red, 90))
median 	= hline(50, 				"Median",		color.new(color.orange,50), 			hline.style_dotted)
lower 	= hline(settingsRsiLower, 	"OS",			color.new(color.green, 90))
lowerx 	= hline(settingsRsiLowerx, 	"OS Extreme",	color.new(color.green, 80),			hline.style_solid)
bottom 	= hline(settingsRsiHistogram and settingsRsiEnableAdaptive ? 0 : na, 			"Baseline",		color.new(color.rgb(204,0,102),60),	hline.style_dotted)

// Channel fill
fill(upper, upperx, color.new(color.red, 90),		title="Background Fill OB")
fill(upper, lower,  color.new(color.blue, 95), 	title="Background Channel")
fill(lower, lowerx, color.new(color.green, 90), 	title="Background Fill OS")

// Plot the number of bars the RSI length was increased
plot(settingsRsiHistogram and settingsRsiEnableAdaptive and atr > zScoreThreshold ? newRsiLenght-settingsRsi.lenght : na, title="RSI Lenght increase", color=colHistogram, style=plot.style_histogram)

// RSI plot
plot(rsiOriginal, "RSI orginal", color.new(color.yellow,66),1)
plot(rsi, "RSI", colRSI, 2)
////////////////////////////////////////////////////////////////////////////////
//                           ====== DIVERGENCE ======                         //
////////////////////////////////////////////////////////////////////////////////

plFound = na(ta.pivotlow(rsi, settingsDvgLookbackL, settingsDvgLookbackR)) ? false : true
phFound = na(ta.pivothigh(rsi, settingsDvgLookbackL, settingsDvgLookbackR)) ? false : true
_inRange(cond) =>
	bars = ta.barssince(cond == true)
	settingsDvgLookbackMin <= bars and bars <= settingsDvgLookbackMax
bearColor = color.red
bullColor = color.green
noneColor = color.new(color.white, 100)
textColor = color.white

// Regular Bullish
// RSI: Higher Low
RSIHL = rsi[settingsDvgLookbackR] > ta.valuewhen(plFound, rsi[settingsDvgLookbackR], 1) and _inRange(plFound[1])

// Price: Lower Low
priceLL = low[settingsDvgLookbackR] < ta.valuewhen(plFound, low[settingsDvgLookbackR], 1)
bullCond =  priceLL and RSIHL and plFound

plot(
     plFound and settingsDvgShowDvg ? rsi[settingsDvgLookbackR] : na,
     offset=-settingsDvgLookbackR,
     title="Regular Bullish",
     linewidth=2,
     color=(bullCond ? bullColor : noneColor),
	 display=display.pane
     )

plotshape(
	 bullCond and settingsDvgShowDvg ? rsi[settingsDvgLookbackR] : na,
	 offset=-settingsDvgLookbackR,
	 title="Regular Bullish Label",
	 text=" Bull ",
	 style=shape.labelup,
	 location=location.absolute,
	 color=bullColor,
	 textcolor=textColor,
	 display=display.pane
	 )

// Regular Bearish
// RSI: Lower High
RSILH = rsi[settingsDvgLookbackR] < ta.valuewhen(phFound, rsi[settingsDvgLookbackR], 1) and _inRange(phFound[1])

// Price: Higher High
priceHH = high[settingsDvgLookbackR] > ta.valuewhen(phFound, high[settingsDvgLookbackR], 1)
bearCond = priceHH and RSILH and phFound

plot(
	 phFound and settingsDvgShowDvg ? rsi[settingsDvgLookbackR] : na,
	 offset=-settingsDvgLookbackR,
	 title="Regular Bearish",
	 linewidth=2,
	 color=(bearCond ? bearColor : noneColor),
	 display=display.pane
	 )

plotshape(
	 bearCond and settingsDvgShowDvg ? rsi[settingsDvgLookbackR] : na,
	 offset=-settingsDvgLookbackR,
	 title="Regular Bearish Label",
	 text=" Bear ",
	 style=shape.labeldown,
	 location=location.absolute,
	 color=bearColor,
	 textcolor=textColor,
	 display=display.pane
	 )
	
//easy peasy