Volume-Weighted RSI [wbburgin]
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © wbburgin

//@version=5
indicator("Volume-Weighted RSI [wbburgin]",overlay=false)

// =====================================================================================================================
//                                                      INPUTS
// =====================================================================================================================

length = input(14, "Length",group="Base Inputs")
src = input.source(close,"Source",group="Base Inputs")
plotBase = input.bool(true,"Plot Volume-weighted RSI",group="Base Inputs")

usema = input.bool(true,"Use Signal",inline="Sig",group="Signal")
pvrsi_ma_length = input.int(25," | Signal Length",inline="Sig",group="Signal")

useabn = input.bool(true,"Plot Abnormal Activity",inline="Abn",group="Abnormal Activity")
abn_mult = input.float(2.5," | SD Multiple",tooltip="Use a higher multiple for more"+
 " stringent filtering of abnormal activity (only affects crosses/circles on plot, not RSI calculation)",inline="Abn"
  ,group="Abnormal Activity")

stylesheet = input.string("Dark Style",title="Color Style",options=["Dark Style","Light Style"],group="Style")

overbought_level = input.float(70, 'Overbought', inline = 'ob', group = 'Style')
overbought_linecolor = input(#089981, '', inline = 'ob', group = 'Style')
overbought_areacolor = input(color.new(#089981, 80), '', inline = 'ob', group = 'Style')

oversold_level = input.float(30, 'Oversold', inline = 'os', group = 'Style')
oversold_linecolor = input(#f23645, '', inline = 'os', group = 'Style')
oversold_areacolor = input(color.new(#f23645, 80), '', inline = 'os', group = 'Style')

barcolors = input.bool(true,"Bar Colors",group="Style")

// =====================================================================================================================
//                                                      CORE LOGIC
// =====================================================================================================================

pricevolume_rsi(x, y) => 
    u_x = math.max(x - x[1], 0) * math.max(volume - volume[1],0)
    d_x = math.max(x[1] - x, 0) * math.max(volume[1] - volume,0)
    u = u_x > 0 ? math.sqrt(u_x) : -math.sqrt(u_x)
    d = d_x > 0 ? math.sqrt(d_x) : -math.sqrt(d_x)
    rs = ta.rma(u, y) / ta.rma(d, y)
    res = 100 - 100 / (1 + rs)
    res

pvrsi = pricevolume_rsi(src,length)
pvrsi_ma = ta.rma(pvrsi,pvrsi_ma_length)

// Abnormal Volume Calculations
obv = ta.cum(math.sign(ta.change(src)) * volume)
volume_difference = math.abs(volume - volume[1]) / volume[1]
abnormal_volume = volume_difference >= ta.sma(volume_difference,length) + (abn_mult*ta.stdev(volume_difference,length))
abv_pos = abnormal_volume and obv > obv[1]
abv_neg = abnormal_volume and obv < obv[1]
vd_placement = abv_neg ? 100 : abv_pos ? 0 : na

// Abnormal Price Calculations
obp = ta.cum(math.sign(ta.change(volume)) * src)
price_difference = math.abs(src - src[1]) / src[1]
abnormal_price= price_difference >= ta.sma(price_difference,length) + (abn_mult*ta.stdev(price_difference,length))
abp_pos = abnormal_price and obp > obp[1]
abp_neg = abnormal_price and obp < obp[1]
pd_placement = abp_neg ? 100 : abp_pos ? 0 : na



// =====================================================================================================================
//                                                      STYLE & DESIGN
// =====================================================================================================================


// RSI Design  ---------- |

pvrsiColor = switch
    stylesheet == "Dark Style" => (color.white)
    stylesheet == "Light Style" => (color.black)

plot_rsi = plot(plotBase?pvrsi:na,color= pvrsi > overbought_level or pvrsi < oversold_level ? color.new(pvrsiColor,90) : pvrsiColor,
 title="Volume RSI")


// Adding gradients on top of the PVRSI plot ---------- |
 
pvrsi_up = pvrsi > 50 ? pvrsi : na
pvrsi_dn = pvrsi < 50 ? pvrsi : na

pvrsi_up_color = color.from_gradient(pvrsi,50,100,pvrsiColor,color.new(color.lime,75))
pvrsi_dn_color = color.from_gradient(pvrsi,0,50,color.new(color.red,75),pvrsiColor)

plot(not plotBase ? na : pvrsi_up,color=pvrsi_up_color,editable = false,style=plot.style_linebr)
plot(not plotBase ? na : pvrsi_dn,color=pvrsi_dn_color,editable = false,style=plot.style_linebr)


// Signal and Bar Color ---------- |

barcolor = color.from_gradient(pvrsi,oversold_level,overbought_level,oversold_linecolor,overbought_linecolor)
plot(usema ? pvrsi_ma : na, "Signal", color= barcolor, linewidth = 2)
barcolor(not barcolors ? na : barcolor)


// Overbought / Oversold  ---------- |

obColor = color.from_gradient(pvrsi,oversold_level,overbought_level,overbought_linecolor,
 color.new(overbought_linecolor,90))
osColor = color.from_gradient(pvrsi,oversold_level,overbought_level,color.new(oversold_linecolor,90),oversold_linecolor)

plot_up = plot(overbought_level, color = obColor, editable = false)
plot_avg = plot(50, color = na, editable = false)
plot_dn = plot(oversold_level, color = osColor, editable = false)

fill(plot_rsi, plot_up, pvrsi > overbought_level ? overbought_areacolor : na)
fill(plot_dn, plot_rsi, pvrsi < oversold_level ? oversold_areacolor : na)

fill(plot_rsi, plot_avg, overbought_level, 50, overbought_areacolor, color.new(chart.bg_color, 100))
fill(plot_avg, plot_rsi, 50, oversold_level, color.new(chart.bg_color, 100), oversold_areacolor)


// Abnormal Activity  ---------- |

pvs = switch
    abv_pos and abp_pos => color.new(overbought_linecolor,50)
    abv_neg and abp_pos => color.new(oversold_linecolor,50)

bgcolor(not useabn ? na : pvs)

plotshape(useabn?vd_placement:na, title="Abnormal Volume",style=shape.circle,location=location.absolute,
 color= obv < obv[1] ? color.new(color.red,50) : color.new(color.lime,50),size=size.tiny)
plotshape(useabn?pd_placement:na, title="Abnormal Price",style=shape.cross,location=location.absolute,
 color= obp < obp[1] ? color.new(color.red,50) : color.new(color.lime,50),size=size.tiny)


// =====================================================================================================================
//                                                      ALERTS
// =====================================================================================================================



alertcondition(abv_pos,"Abnormal Positive Volume")
alertcondition(abv_neg,"Abnormal Negative Volume")
alertcondition(abp_pos,"Abnormal Price - Up")
alertcondition(abp_neg,"Abnormal Price - Down")
alertcondition( ta.crossover(pvrsi,oversold_level),"RSI Crossing over Oversold" )
alertcondition( ta.crossunder(pvrsi,overbought_level),"RSI Crossing under Overbought" )