OBV Oscillator Volume Filter
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Harrocop

// ------------------------------------------------------------------------------------------------------ //
// Script Summary:
// - The indicator calculates the on balance volume (OBV) and is plotted as an Oscillater.
// - The indicator has a volume filter using a % threshold based on the highest OBV value over X number of bars back.
// - The volume filter is thus based on a rolling window (time) and can be adjusted in sensitivity and # number of bars back.
// - The OBV can be calculated with different types of MA to your liking.
// - The signals are derived from potential trend reversals (eg. for entry and exit).
// - The script automatically shows divergences (green line for bullish and red line for bearish).
// - The script automatically shows hidden devergences using dotted lines. 
// - The script includes the option to turn on alerts.
// - The script uses multiple plotted offsets of the OBV to make the indicator more appealing to read / interpret.
// ------------------------------------------------------------------------------------------------------ //

//@version=5
indicator("OBV Oscillator Volume Filter", "OBV Oscillator Volume Filter", overlay=false)

// Input Parameters
OBVgroup = "On Balance Volume Settings"
src = close
Type_MA  = input.string(defval="WMA" , options=["EMA","DEMA","TEMA","SMA","WMA", "HMA"], title="MA type to calc OBV", tooltip = "MA used to calculate cumulative volume (OBV)", inline = "OBV1", group = OBVgroup)
length = input.int(21, title="Length", tooltip = "number # bars to calculate the OBV", inline = "OBV1", group = OBVgroup)
apply_smoothing = input.bool(true, title="Apply Smoothing?", inline = "OBV2", group = OBVgroup)
smoothing_length = input.int(21, title="Length Smoothing", tooltip = "To reduce noise the indicator is smoothed using a SMA)", inline = "OBV2", group = OBVgroup)
LookbackBars = input.int(480, title = "Lookback # Bars", inline = "OBV3", group = OBVgroup, tooltip = "Calculates the absolute OBV over the nr of bars back to determine deadzone, this is a rolling window")
Sensitivity = input.int(50, title = "Deadzone Sensitivity", minval = 1, maxval = 100, step = 1, tooltip = "Expressed as % value (between 1 - 100) of Absolute OBV based on Lookback # Bars, the higher the % the less signals occur", inline = "OBV3", group = OBVgroup) / 100

// Calculate DEMA
fx_dema(inp_source, inp_lenght) =>
    ema_01 = ta.ema(inp_source, inp_lenght)
    ema_02 = ta.ema(ema_01    , inp_lenght)
    output =       (2 * ema_01 - ema_02)
// Calculate TEMA
fx_tema(inp_source, inp_lenght) =>
    ema_01 = ta.ema(inp_source, inp_lenght)
    ema_02 = ta.ema(ema_01    , inp_lenght)
    ema_03 = ta.ema(ema_02    , inp_lenght)
    output = (3 * (ema_01 - ema_02) + ema_03)
// Calculate HMA
fx_hma(inp_source, inp_lenght) => 
    output = ta.wma(2 * ta.wma(inp_source, inp_lenght / 2) - ta.wma(inp_source, inp_lenght), math.round(math.sqrt(inp_lenght)))
// Select type of MA
fx_typeofmavg(inp_type, inp_source, inp_length) =>
    _switch = (inp_type == "EMA" ) ? ta.ema(inp_source , inp_length) :
              (inp_type == "DEMA") ? fx_dema(inp_source, inp_length) :
              (inp_type == "TEMA") ? fx_tema(inp_source, inp_length) :
              (inp_type == "SMA" ) ? ta.sma (inp_source, inp_length) :
              (inp_type == "WMA" ) ? ta.wma (inp_source, inp_length) :
              (inp_type == "HMA" ) ? fx_hma(inp_source, inp_length) :
              0.0 // return zero in case of mistake
    output = _switch


// Calculate the On Balance Volume (OBV)
obv(src) =>
    os = ta.cum(ta.change(src) > 0 ? volume : ta.change(src) < 0 ? -volume : 0)
    os

os = obv(src)
obv_osc = os - fx_typeofmavg(Type_MA, os, length)

// Apply smoothing
final_obv_osc = apply_smoothing ? ta.sma(obv_osc, smoothing_length) : obv_osc

// Simulating the 2 bars forward offset
var float offset_obv_osc = na
var float offset_obv_osc_1 = na
offset_obv_osc := offset_obv_osc_1
offset_obv_osc_1 := final_obv_osc

// Smaller mountains for filling area
// Simulating the 2 bars forward offset
var float offset_obv_osc_2 = na
var float offset_obv_osc_3 = na
offset_obv_osc_2 := offset_obv_osc_3
offset_obv_osc_3 := offset_obv_osc
offset_obv_osc_small = offset_obv_osc_2 * 0.85

var float offset_obv_osc_4 = na
var float offset_obv_osc_5 = na
offset_obv_osc_4 := offset_obv_osc_5
offset_obv_osc_5 := offset_obv_osc_2
offset_obv_osc_tiny = offset_obv_osc_4 * 0.7

// Calculate outlier threshold
outlier_threshold = Sensitivity * ta.highest(volume, LookbackBars)

// Long and Short conditions
LongCondition = ta.crossover(final_obv_osc, offset_obv_osc) and final_obv_osc < -outlier_threshold and barstate.isconfirmed
ShortCondition = ta.crossunder(final_obv_osc, offset_obv_osc) and final_obv_osc > outlier_threshold and barstate.isconfirmed

// Plot outlier thresholds and zero line
f1 = plot(outlier_threshold, style = plot.style_area, color=color.rgb(255, 94, 0, 75), linewidth=1, title="Positive Outlier Threshold")
f2 = plot(-outlier_threshold, style = plot.style_area, color=color.rgb(255, 69, 0, 75), linewidth=1, title="Negative Outlier Threshold")
zeroLine = plot(0, color = color.white)

// Plotting the OBV Oscillator
plot(final_obv_osc, style=plot.style_area, color = color.rgb(51, 136, 255), title="OBV-value", linewidth=2)
plot(offset_obv_osc, style=plot.style_area, color = color.rgb(0, 0, 255, 15) , title="OBV-offset", linewidth=2)
plot(offset_obv_osc_small, style=plot.style_area, color = color.rgb(31, 31, 122, 15) , title="OBV-small", linewidth=2)
plot(offset_obv_osc_tiny, style=plot.style_area, color = color.rgb(17, 17, 63, 15) , title="OBV-tiny", linewidth=2)

// plot Signals -- Draw Green and Red Dots on Intersections
plot(LongCondition ? final_obv_osc : na, "Long Condition", style = plot.style_circles, linewidth = 5, color = color.rgb(0, 255, 8))
plot(ShortCondition ? final_obv_osc : na, "Short Condition", style = plot.style_circles, linewidth = 5, color = color.rgb(255, 0, 0))

//////////////////////////////////////////////////
///////////  Divergence Settings  ////////////////
//////////////////////////////////////////////////
/////////////////////
// LONG CONDITIONS //
/////////////////////
// Calculating previous OBV Oscillator, close values, and bar indices
obv_val_current_long = ta.valuewhen(LongCondition, final_obv_osc, 0)
obv_val_previous_long = ta.valuewhen(LongCondition, final_obv_osc, 1)
close_val_current_long = ta.valuewhen(LongCondition, close, 0)
close_val_previous_long = ta.valuewhen(LongCondition, close, 1)
bar_index_current_long = ta.valuewhen(LongCondition, bar_index, 0)
bar_index_previous_long = ta.valuewhen(LongCondition, bar_index, 1)

// Bullish Divergence for LongCondition[0] and LongCondition[1]
bullishDivergenceCond1_long = LongCondition and obv_val_current_long > obv_val_previous_long and close_val_current_long < close_val_previous_long
if (bullishDivergenceCond1_long)
    line.new(x1=bar_index_previous_long, y1=obv_val_previous_long, x2=bar_index_current_long, y2=obv_val_current_long, color=color.rgb(0, 255, 8))

// Fetch values for LongCondition[2]
obv_val_previous2_long = ta.valuewhen(LongCondition, final_obv_osc, 2)
close_val_previous2_long = ta.valuewhen(LongCondition, close, 2)
bar_index_previous2_long = ta.valuewhen(LongCondition, bar_index, 2)

// Bullish Divergence for LongCondition[0] and LongCondition[2]
bullishDivergenceCond2_long = LongCondition and obv_val_current_long > obv_val_previous2_long and close_val_current_long < close_val_previous2_long
if (bullishDivergenceCond2_long)
    line.new(x1=bar_index_previous2_long, y1=obv_val_previous2_long, x2=bar_index_current_long, y2=obv_val_current_long, color=color.rgb(0, 255, 8)) 

// Hidden Bullish Divergence for LongCondition[0] and LongCondition[1]
hiddenBullishDivergenceCond1_long = LongCondition and obv_val_current_long < obv_val_previous_long and close_val_current_long > close_val_previous_long
if (hiddenBullishDivergenceCond1_long)
    line.new(x1=bar_index_previous_long, y1=obv_val_previous_long, x2=bar_index_current_long, y2=obv_val_current_long, color=color.rgb(0, 255, 8), width=1, style=line.style_dotted)

// Hidden Bullish Divergence for LongCondition[0] and LongCondition[2]
hiddenBullishDivergenceCond2_long = LongCondition and obv_val_current_long < obv_val_previous2_long and close_val_current_long > close_val_previous2_long
if (hiddenBullishDivergenceCond2_long)
    line.new(x1=bar_index_previous2_long, y1=obv_val_previous2_long, x2=bar_index_current_long, y2=obv_val_current_long, color=color.rgb(0, 255, 8), width=1, style=line.style_dotted) 

//////////////////////
// SHORT CONDITIONS //
//////////////////////
// Calculating previous OBV Oscillator, close values, and bar indices
obv_val_current_short = ta.valuewhen(ShortCondition, final_obv_osc, 0)
obv_val_previous_short = ta.valuewhen(ShortCondition, final_obv_osc, 1)
close_val_current_short = ta.valuewhen(ShortCondition, close, 0)
close_val_previous_short = ta.valuewhen(ShortCondition, close, 1)
bar_index_current_short = ta.valuewhen(ShortCondition, bar_index, 0)
bar_index_previous_short = ta.valuewhen(ShortCondition, bar_index, 1)

// Bearish Divergence for ShortCondition[0] and ShortCondition[1]
bearishDivergenceCond1_short = ShortCondition and obv_val_current_short < obv_val_previous_short and close_val_current_short > close_val_previous_short
if (bearishDivergenceCond1_short)
    line.new(x1=bar_index_previous_short, y1=obv_val_previous_short, x2=bar_index_current_short, y2=obv_val_current_short, color=color.rgb(255, 0, 0))

// Fetch values for ShortCondition[2]
obv_val_previous2_short = ta.valuewhen(ShortCondition, final_obv_osc, 2)
close_val_previous2_short = ta.valuewhen(ShortCondition, close, 2)
bar_index_previous2_short = ta.valuewhen(ShortCondition, bar_index, 2)

// Bearish Divergence for ShortCondition[0] and ShortCondition[2]
bearishDivergenceCond2_short = ShortCondition and obv_val_current_short < obv_val_previous2_short and close_val_current_short > close_val_previous2_short
if (bearishDivergenceCond2_short)
    line.new(x1=bar_index_previous2_short, y1=obv_val_previous2_short, x2=bar_index_current_short, y2=obv_val_current_short, color=color.rgb(255, 0, 0)) 

// Hidden Bearish Divergence for ShortCondition[0] and ShortCondition[1]
hiddenBearishDivergenceCond1_short = ShortCondition and obv_val_current_short > obv_val_previous_short and close_val_current_short < close_val_previous_short
if (hiddenBearishDivergenceCond1_short)
    line.new(x1=bar_index_previous_short, y1=obv_val_previous_short, x2=bar_index_current_short, y2=obv_val_current_short, color=color.rgb(255, 0, 0), width=1, style=line.style_dotted)

// Hidden Bearish Divergence for ShortCondition[0] and ShortCondition[2]
hiddenBearishDivergenceCond2_short = ShortCondition and obv_val_current_short > obv_val_previous2_short and close_val_current_short < close_val_previous2_short
if (hiddenBearishDivergenceCond2_short)
    line.new(x1=bar_index_previous2_short, y1=obv_val_previous2_short, x2=bar_index_current_short, y2=obv_val_current_short, color=color.rgb(255, 0, 0), width=1, style=line.style_dotted)

//////////////////////////////////////////////////
///////////         Alerts        ////////////////
//////////////////////////////////////////////////
// Alert conditions for Crossover
alertcondition(LongCondition, title="Long Alert", message="Long Condition Triggered!")
alertcondition(ShortCondition, title="Short Alert", message="Short Condition Triggered!")
alertcondition(bullishDivergenceCond1_long or bullishDivergenceCond2_long, title="Bullish Divergence Alert", message="Bullish Divergence Detected!")
alertcondition(hiddenBullishDivergenceCond1_long or hiddenBullishDivergenceCond2_long, title="Hidden Bullish Divergence Alert", message="Hidden Bullish Divergence Detected!")
alertcondition(bearishDivergenceCond1_short or bearishDivergenceCond2_short, title="Bearish Divergence Alert", message="Bearish Divergence Detected!")
alertcondition(hiddenBearishDivergenceCond1_short or hiddenBearishDivergenceCond2_short, title="Hidden Bearish Divergence Alert", message="Hidden Bearish Divergence Detected!")

