Better Divergence On Any Indicator [DoctaBot]
// 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("Ultimate Divs On Any Indicator [DoctaBot]", format=format.price, overlay=true, max_lines_count = 500, max_bars_back = 100)
//Inputs{
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             = input.source(ohlc4,       title = 'Indicator',                    group = 'Divergence Settings')
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)
OBOSFilt        = input.string('Off',       title = 'OB/OS Filt',                   group = 'Divergence Settings', options = ['Off', 'On', 'Strict'], tooltip = 'On will only show divs where currently OB/OS. Strict will require start and end of div line be OB/OS')
OBLev           = input.int(70,             title = 'Overbought level',             group = 'Divergence Settings', tooltip = 'Oversold = 100 - this')
OSLev           = 100 - OBLev
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 and (OBOSFilt == 'Strict' ? osc[lbR] >= OBLev : true)
        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 and (OBOSFilt == 'Strict' ? osc[lbR] <= OSLev : true)
        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[lbR] : osc
    PriceEnd    = _conf ? _divDir == 'Bull' ? PricePL : PricePH : close
    BarEnd      = _conf ? bar_index - lbR : 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)) and (OBOSFilt == 'On' ? _divDir == 'Bull' ? OscEnd <= OSLev : OscEnd >= OBLev : true)
            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 )

//}













