OBV+
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © DoctaBot 

//@version=5
indicator('OBV+ [DoctaBot]', overlay = false, format=format.volume, max_bars_back = 500)

//----------//OBV//----------//

OBV = ta.obv

//----------//OBV MAs----------//{

ma_type     =  input.string(defval = 'EMA', title = 'MA Type ', options=['SMA', 'EMA', 'WMA', 'VWMA', 'SMMA', 'Hull MA', 'Triangular MA', 'SuperSmooth MA'], inline = 'MAs', group = 'MA Settings')
ma_source   = OBV

// MA Lengths{
show_ma1    = input.bool(true, 'MA №1',     inline = 'MA #1',             group = 'MA Settings')
ma1_length  = input.int(21, '',             inline = 'MA #1', minval=1,   group = 'MA Settings')
ma1_color   = input.color(#f6c309, '',      inline = 'MA #1',             group = 'MA Settings')

show_ma2    = input.bool(false, 'MA №2',    inline = 'MA #2',             group = 'MA Settings')
ma2_length  = input.int(50, '',             inline = 'MA #2', minval=1,   group = 'MA Settings')
ma2_color   = input.color(#fb9800, '',      inline = 'MA #2',             group = 'MA Settings')

show_ma3    = input.bool(false, 'MA №3',    inline = 'MA #1',             group = 'MA Settings')
ma3_length  = input.int(99, '',             inline = 'MA #1', minval=1,   group = 'MA Settings')
ma3_color   = input.color(#fb6500, '',      inline = 'MA #1',             group = 'MA Settings')

show_ma4    = input.bool(true, 'MA №4',     inline = 'MA #2',             group = 'MA Settings')
ma4_length  = input.int(200, '',            inline = 'MA #2', minval=1,   group = 'MA Settings')
ma4_color   = input.color(#f60c0c, '',      inline = 'MA #2',             group = 'MA Settings') //}}

// SuperSmoother filter{
variant_supersmoother(source, length) =>
    a1 = math.exp(-1.414 * 3.14159 / length)
    b1 = 2 * a1 * math.cos(1.414 * 3.14159 / length)
    c2 = b1
    c3 = -a1 * a1
    c1 = 1 - c2 - c3
    v9 = 0.0
    v9 := c1 * (source + nz(source[1])) / 2 + c2 * nz(v9[1]) + c3 * nz(v9[2])
    v9

variant_smoothed(source, length) =>
    v5 = 0.0
    sma_1 = ta.sma(source, length)
    v5 := na(v5[1]) ? sma_1 : (v5[1] * (length - 1) + source) / length
    v5

// return variant, defaults to SMA 
variant(type, source, length) =>
    ema_1 = ta.ema(source, length)
    wma_1 = ta.wma(source, length)
    vwma_1 = ta.vwma(source, length)
    variant_smoothed__1 = variant_smoothed(source, length)
    wma_2 = ta.wma(source, length / 2)
    wma_3 = ta.wma(source, length)
    wma_4 = ta.wma(2 * wma_2 - wma_3, math.round(math.sqrt(length)))
    variant_supersmoother__1 = variant_supersmoother(source, length)
    sma_1 = ta.sma(source, length)
    sma_2 = ta.sma(sma_1, length)
    sma_3 = ta.sma(source, length)
    type == 'EMA' ? ema_1 : type == 'WMA' ? wma_1 : type == 'VWMA' ? vwma_1 : type == 'SMMA' ? variant_smoothed__1 : type == 'Hull MA' ? wma_4 : type == 'SuperSmooth MA' ? variant_supersmoother__1 : type == 'Triangular MA' ? sma_2 : sma_3


ma1 = variant(ma_type, ma_source, ma1_length)
ma2 = variant(ma_type, ma_source, ma2_length)
ma3 = variant(ma_type, ma_source, ma3_length)
ma4 = variant(ma_type, ma_source, ma4_length) //}

//----------//Plots//----------//

//{
OBVplot = plot(OBV, title = 'OBV', color=color.new(color.white, 0))
ma1plot = plot(show_ma1 ? ma1 : na, color=ma1_color, title = 'OBV MA1')
ma2plot = plot(show_ma2 ? ma2 : na, color=ma2_color, title = 'OBV MA2')
ma3plot = plot(show_ma3 ? ma3 : na, color=ma3_color, title = 'OBV MA3')
ma4plot = plot(show_ma4 ? ma4 : na, color=ma4_color, title = 'OBV MA4') //}

// Trend Fill
//{
trendFill       = input.bool(defval=true, title = '', inline = 'MA Trend1', group = 'Trend Settings')
BullFill = input.color(color.green, 'Bull/Bear Fill', inline = 'MA Trend1', group = 'Trend Settings')
BearFill = input.color(color.red, '', inline = 'MA Trend1', group = 'Trend Settings')
OBVarraysize    = input.int(defval = 20, title = 'Trend Strength Lookback', inline = 'MA Trend2', group = 'Trend Settings')

var OBVSpreadArray  = array.new_float()
bool newbar         = ta.change(time('')) != 0

OBVSpread = ma1 - ma4

//Shift (remove) last value from and add current value to array
if newbar
    array.push(OBVSpreadArray, OBVSpread)
if array.size(OBVSpreadArray) > OBVarraysize
    array.shift(OBVSpreadArray)

OBVSpreadMax = array.max(OBVSpreadArray)
OBVSpreadMin = array.min(OBVSpreadArray)

OBVBullFill1 = color.new(BullFill, 60)
OBVBullFill2 = color.new(BullFill, 80)
OBVBearFill1 = color.new(BearFill, 60)
OBVBearFill2 = color.new(BearFill, 80)
OBVBullGrad = color.from_gradient(OBVSpread, OBVSpreadMin, OBVSpreadMax, OBVBullFill2, OBVBullFill1)
OBVBearGrad = color.from_gradient(OBVSpread, OBVSpreadMin, OBVSpreadMax, OBVBearFill1, OBVBearFill2)

OBVFillCol = OBV > ma4 ? OBVBullGrad : OBVBearGrad
fill(OBVplot, ma4plot, color=trendFill ? OBVFillCol : na) //}

//Divergences
plotBull        = input.bool(defval = true, title = 'Regular Bullish   ',           group = 'Alert Options',        inline = 'Divs1')
plotBear        = input.bool(defval = true, title = 'Regular Bearish',              group = 'Alert Options',        inline = 'Divs1')
plotHiddenBull  = input.bool(defval = true, title = 'Hidden Bullish   ',            group = 'Alert Options',        inline = 'Divs2')
plotHiddenBear  = input.bool(defval = true, title = 'Hidden Bearish',               group = 'Alert Options',        inline = 'Divs2')
PotAlerts       = input.bool(defval = true, title = 'Potential Divergences',        group = 'Alert Options',        inline = 'Divs3')
plotDivLines    = input.bool(defval = true, title = 'Plot Divergence Lines    ',    group = 'Plot Options',         inline = 'Plot1')
plotDivLabels   = input.bool(defval = true, title = 'Plot Divergence Labels',       group = 'Plot Options',         inline = 'Plot1')
plotPotentials  = input.bool(defval = true, title = 'Plot Potential Divergences',   group = 'Plot Options',         inline = 'Plot2')
overlay_main    = input(false,              title = 'Plot on price',                group = 'Plot Options',         inline = 'Plot2', tooltip = 'rather than on indicator')
osc             = OBV
rangeUpper      = input.int(defval = 20,    title = 'Max of Lookback Range',        group = 'Divergence Settings')
rangeLower      = input.int(defval = 2,     title = 'Min of Lookback Range',        group = 'Divergence Settings', minval = 2)
maxdivs         = input.int(defval = 25,    title = 'Max Divs to Show',      group = 'Divergence Settings', minval = 1)
lbR             = 1
lbL             = 1

bullColor       = input.color(defval = color.new(color.green, 0),   title = 'Reg Bull Color', group = 'Colors', inline = 'Colors1')
bearColor       = input.color(defval = color.new(color.red, 0),     title = 'Reg Bear Color', group = 'Colors', inline = 'Colors1')
hiddenBullColor = input.color(defval = color.new(color.green, 70),  title = 'Hid Bull Color', group = 'Colors', inline = 'Colors2')
hiddenBearColor = input.color(defval = color.new(color.red, 70),    title = 'Hid Bear Color', group = 'Colors', inline = 'Colors2')
textColor       = color.new(color.white, 0)
noneColor       = color.new(color.white, 100)
//}

//Arrays{
//Line Arrays
var URegBullDivLines    = array.new_line()
var UHidBullDivLines    = array.new_line()
var URegBearDivLines    = array.new_line()
var UHidBearDivLines    = array.new_line()
var RegBullDivLines     = array.new_line()
var HidBullDivLines     = array.new_line()
var RegBearDivLines     = array.new_line()
var HidBearDivLines     = array.new_line()
//Label Arrays
var URegBullDivLabels   = array.new_label()
var UHidBullDivLabels   = array.new_label()
var URegBearDivLabels   = array.new_label()
var UHidBearDivLabels   = array.new_label()
var RegBullDivLabels    = array.new_label()
var HidBullDivLabels    = array.new_label()
var RegBearDivLabels    = array.new_label()
var HidBearDivLabels    = array.new_label() //}

//Load Pivots{
//Check Arrays are even to ensure Price and Bar values have been added properly
f_checkPivArrays(float[] PriceArr, float[] OscArr, int[] BarsArr) =>
    if array.size(PriceArr) != array.size(OscArr) or array.size(PriceArr) != array.size(BarsArr) or array.size(OscArr) != array.size(BarsArr)
        runtime.error('Array sizes are not equal!')

//Load new Price, Osc, and Bar values into their respective arrays        
f_loadNewPivValues(float[] PriceArr, Price, float[] OscArr, Osc, int[] BarsArr, Bars) =>
    f_checkPivArrays(PriceArr, OscArr, BarsArr)
    array.unshift(PriceArr, Price)
    array.unshift(OscArr, Osc)
    array.unshift(BarsArr, Bars)
    
f_loadPivArrays() =>
    phFound = na(ta.pivothigh(osc, lbL, lbR)) ? false : true
    plFound = na(ta.pivotlow(osc, lbL, lbR)) ? false : true
    
    //Declare Pivot High Arrays
    var PricePHArr  = array.new_float()
    var OscPHArr    = array.new_float()
    var BarPHArr    = array.new_int()
    
    //Declare Pivot Low Arrays
    var PricePLArr  = array.new_float()
    var OscPLArr    = array.new_float()
    var BarPLArr    = array.new_int()
    
    //Declare Pivot Values
    PricePH = math.max(open[lbR], close[lbR]) //ta.highest(close, lbR + 2)
    PricePL = math.min(open[lbR], close[lbR]) //ta.lowest(close, lbR + 2)
    
    //Load Pivot High Values into Arrays and remove pivots outside range lookback
    if phFound
        f_loadNewPivValues(PricePHArr, PricePH, OscPHArr, osc[lbR], BarPHArr, bar_index - lbR)
        if bar_index - array.min(BarPHArr) > rangeUpper
            array.pop(PricePHArr)
            array.pop(OscPHArr)
            array.pop(BarPHArr)
    //Load Pivot Low Values into Arrays and remove pivots outside range lookback
    if plFound
        f_loadNewPivValues(PricePLArr, PricePL, OscPLArr, osc[lbR], BarPLArr, bar_index - lbR)
        if bar_index - array.min(BarPLArr) > rangeUpper
            array.pop(PricePLArr)
            array.pop(OscPLArr)
            array.pop(BarPLArr)
    [PricePH, PricePL, phFound, plFound, PricePHArr, OscPHArr, BarPHArr, PricePLArr, OscPLArr, BarPLArr] //}

//Get Divergences{
f_getDivergence(_plotOn, _divType, _divDir, _conf, line[]_DivLineArr, label[]_DivLabelArr)=>
    [PricePH, PricePL, phFound, plFound, PricePHArr, OscPHArr, BarPHArr, PricePLArr, OscPLArr, BarPLArr] = f_loadPivArrays()
    _OscArr     = _divDir == 'Bull' ? OscPLArr : OscPHArr
    _PriceArr   = _divDir == 'Bull' ? PricePLArr : PricePHArr
    _BarArr     = _divDir == 'Bull' ? BarPLArr : BarPHArr
    ConfLineCol = _divDir == 'Bull' ? _divType == 'Regular' ? bullColor : hiddenBullColor : _divType == 'Regular' ? bearColor : hiddenBearColor
    PotLineCol  = plotPotentials ? _divDir == 'Bull' ? _divType == 'Regular' ? bullColor : hiddenBullColor : _divType == 'Regular' ? bearColor : hiddenBearColor : noneColor
    LineColor   = plotDivLines ? _conf ? ConfLineCol : PotLineCol : noneColor
    LineStyle   = _conf ? line.style_solid : line.style_dashed
    LabColor    = plotDivLabels ? _conf ? _divDir == 'Bull' ? bullColor : bearColor : plotPotentials ? _divDir == 'Bull' ? hiddenBullColor : hiddenBearColor : noneColor : noneColor
    LabStyle    = _divDir == 'Bull' ? label.style_label_up : label.style_label_down
    LabTextCol  = plotDivLabels ? _conf ? textColor : plotPotentials ? textColor : noneColor : noneColor
    IntLBStart  = _conf ? lbR + 1 : 1
    var line    newDivLine      = na
    var label   newDivLabel     = na
    DivCount    = 0
    OscEnd      = _conf ? osc[1] : osc
    PriceEnd    = _divDir == 'Bull' ? PricePL : PricePH
    BarEnd      = _conf ? bar_index - 1 : bar_index
    LineEnd     = overlay_main ? PriceEnd : OscEnd
    if array.size(_OscArr) > 0
        for i = array.size(_OscArr) - 1 to 0
            OscStart    = array.get(_OscArr, i)
            PriceStart  = array.get(_PriceArr, i)
            BarStart    = array.get(_BarArr, i)
            LineStart   = overlay_main ? PriceStart : OscStart
            oscH        = OscEnd > OscStart
            oscL        = OscEnd < OscStart
            PriceH      = PriceEnd > PriceStart
            PriceL      = PriceEnd < PriceStart
            DivLen      = BarEnd - BarStart
            Divergence  = (_conf ? _divDir == 'Bull' ? plFound : phFound : true) and (((_divType == 'Regular' and _divDir == 'Bull') or (_divType == 'Hidden' and _divDir == 'Bear')) ? (oscH and PriceL) : (oscL and PriceH))
            if DivLen >= rangeLower and Divergence and _plotOn
                NoIntersect = true
                OscSlope    = (OscEnd - OscStart)/(BarEnd - BarStart)
                PriceSlope  = (PriceEnd - PriceStart)/(BarEnd - BarStart)
                OscLine     = OscEnd - OscSlope
                PriceLine   = PriceEnd - PriceSlope
                for x = IntLBStart to DivLen - 1
                    if (_divDir == 'Bull' and (osc[x] < OscLine or nz(close[x]) < PriceLine)) or (_divDir == 'Bear' and (osc[x] > OscLine or nz(close[x]) > PriceLine))
                        NoIntersect := false
                        break
                    OscLine     -= OscSlope
                    PriceLine   -= PriceSlope
                    NoIntersect
                if NoIntersect
                    newDivLine      := line.new(BarStart, LineStart, BarEnd, LineEnd, xloc.bar_index, extend.none, LineColor, LineStyle, 1)
                    array.push(_DivLineArr, newDivLine)              //Add new osc line to end of array
                    DivCount := DivCount + 1
                DivCount
    if DivCount > 0
        if array.size(_DivLabelArr) > 0
            if BarEnd  != label.get_x(array.get(_DivLabelArr, array.size(_DivLabelArr) - 1))
                newDivLabel     := label.new(BarEnd, LineEnd, _divType == 'Regular' ? 'R' : 'H', xloc.bar_index, yloc.price, LabColor, LabStyle, LabTextCol, size.small)
                array.push(_DivLabelArr, newDivLabel)
        else
            newDivLabel     := label.new(BarEnd, LineEnd, _divType == 'Regular' ? 'R' : 'H', xloc.bar_index, yloc.price, LabColor, LabStyle, LabTextCol, size.small)
            array.push(_DivLabelArr, newDivLabel)
    DivCount                

PotRegBullDivCount = f_getDivergence(PotAlerts and plotBull, 'Regular', 'Bull', false, URegBullDivLines, URegBullDivLabels)
PotRegBearDivCount = f_getDivergence(PotAlerts and plotBear, 'Regular', 'Bear', false, URegBearDivLines, URegBearDivLabels)
PotHidBullDivCount = f_getDivergence(PotAlerts and plotHiddenBull, 'Hidden', 'Bull', false, UHidBullDivLines, UHidBullDivLabels)
PotHidBearDivCount = f_getDivergence(PotAlerts and plotHiddenBear, 'Hidden', 'Bear', false, UHidBearDivLines, UHidBearDivLabels)
RegBullDivCount = f_getDivergence(plotBull, 'Regular', 'Bull', true, RegBullDivLines, RegBullDivLabels)
RegBearDivCount = f_getDivergence(plotBear, 'Regular', 'Bear', true, RegBearDivLines, RegBearDivLabels)
HidBullDivCount = f_getDivergence(plotHiddenBull, 'Hidden', 'Bull', true, HidBullDivLines, HidBullDivLabels)
HidBearDivCount = f_getDivergence(plotHiddenBear, 'Hidden', 'Bear', true, HidBearDivLines, HidBearDivLabels)

NewPotRegBullDiv = PotRegBullDivCount > 0
NewPotRegBearDiv = PotRegBearDivCount > 0
NewPotHidBullDiv = PotHidBullDivCount > 0
NewPotHidBearDiv = PotHidBearDivCount > 0

NewRegBullDiv = RegBullDivCount > 0
NewRegBearDiv = RegBearDivCount > 0
NewHidBullDiv = HidBullDivCount > 0
NewHidBearDiv = HidBearDivCount > 0 //} 

//Purge Old Divergences{
//Old Potential Divs
f_purgeOldUnconDivs(line[]_DivLineArr, label[]_DivLabelArr)=>
    if array.size(_DivLineArr) > 0
        if line.get_x2(array.get(_DivLineArr, 0)) <= bar_index - lbR
            for i = array.size(_DivLineArr) - 1 to 0 
                line.delete(array.get(_DivLineArr, i))
            array.clear(_DivLineArr)
    if array.size(_DivLabelArr) > 0
        if label.get_x(array.get(_DivLabelArr, 0)) <= bar_index - lbR
            for i = array.size(_DivLabelArr) - 1 to 0 
                label.delete(array.get(_DivLabelArr, i))
            array.clear(_DivLabelArr)

var int LastBar = 0
RegBullDivDisp = plotBull ? array.size(RegBullDivLabels) : 0
RegBearDivDisp = plotBear ? array.size(RegBearDivLabels) : 0
HidBullDivDisp = plotHiddenBull ? array.size(HidBullDivLabels) : 0
HidBearDivDisp = plotHiddenBear ? array.size(HidBearDivLabels) : 0
ConfDivCount = RegBullDivDisp + RegBearDivDisp + HidBullDivDisp +  HidBearDivDisp 
if ConfDivCount > maxdivs and array.size(label.all) > 0
    LastBar := label.get_x(array.get(label.all, array.size(label.all) - maxdivs))
    
f_purgeOldConDivs(_LineArr, _LabelArr)=>
    if array.size(_LabelArr) > 0 
        if label.get_x(array.get(_LabelArr, 0)) < LastBar
            label.delete(array.shift(_LabelArr))
    if array.size(_LineArr) > 0
        if line.get_x2(array.get(_LineArr, 0)) < LastBar
            line.delete(array.shift(_LineArr))

f_purgeOldUnconDivs(URegBullDivLines, URegBullDivLabels)
f_purgeOldUnconDivs(URegBearDivLines, URegBearDivLabels)
f_purgeOldUnconDivs(UHidBullDivLines, UHidBullDivLabels)
f_purgeOldUnconDivs(UHidBearDivLines, UHidBearDivLabels)

f_purgeOldConDivs(RegBullDivLines, RegBullDivLabels)
f_purgeOldConDivs(RegBearDivLines, RegBearDivLabels)
f_purgeOldConDivs(HidBullDivLines, HidBullDivLabels)
f_purgeOldConDivs(HidBearDivLines, HidBearDivLabels) //}

//Alerts{
if NewRegBullDiv
    alert('Regular Bull Divergence x' + str.tostring(RegBullDivCount), alert.freq_once_per_bar_close )
if NewHidBullDiv
    alert('Hidden Bull Divergence x' + str.tostring(HidBullDivCount), alert.freq_once_per_bar_close )
if NewRegBearDiv
    alert('Regular Bear Divergence x' + str.tostring(RegBearDivCount), alert.freq_once_per_bar_close )
if NewHidBearDiv
    alert('Hidden Bear Divergence x' + str.tostring(HidBearDivCount), alert.freq_once_per_bar_close )
if NewPotRegBullDiv
    alert('Potential Regular Bull Divergence x' + str.tostring(PotRegBullDivCount), alert.freq_once_per_bar_close )
if NewPotHidBullDiv
    alert('Potential Hidden Bull Divergence x' + str.tostring(PotHidBullDivCount), alert.freq_once_per_bar_close )
if NewPotRegBearDiv
    alert('Potential Regular Bear Divergence x' + str.tostring(PotRegBearDivCount), alert.freq_once_per_bar_close )
if NewPotHidBearDiv
    alert('Potential Hidden Bear Divergence x' + str.tostring(PotHidBearDivCount), alert.freq_once_per_bar_close )




