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

//@version=5

// @description divergence: divergence algorithm with top and bottom kline tolerance
library("divergence", overlay=true)

// @function regular_bull: regular bull divergence, lower low src but higher low osc
// @param series float src: the source series
// @param series float osc: the oscillator index
// @param simple int lbL: look back left
// @param simple int lbR: look back right
// @param simple int rangeL: min look back range
// @param simple int rangeU: max look back range
// @param simple int tolerance: the number of tolerant klines
// @returns array: [left_src_value, left_src_bars, left_osc_value, left_osc_bars, right_src_value, right_src_bars, right_osc_value, right_osc_bars, div_cov]
export regular_bull(series float src, series float osc, simple int lbL, simple int lbR, simple int rangeL, simple int rangeU, simple int tolerance) =>
	slv = ta.pivotlow(src, lbL, lbR)
    olv = ta.pivotlow(osc, lbL, lbR)
    slvf = na(slv) ? false : true
    olvf = na(olv) ? false : true
    
    slvb = ta.barssince(slvf)
    slvr = slvb <= tolerance ? slv[slvb] : na
    olvb = ta.barssince(olvf)
    olvr = olvb <= tolerance ? olv[olvb] : na
    
    cmb1 = slvb <= tolerance and olvf
    cmb2 = olvb <= tolerance and slvf
    cmb = cmb1 or cmb2
    
    res = array.new_float(9, na)
    if not cmb or na(slvr) or na(slvb) or na(olvr) or na(olvb)
        res

    for r = math.max(rangeL, lbL, slvb, olvb) to math.max(rangeU, lbL, slvb, olvb)
        if cmb[r] and ((slvr[r] > slvr and olv[r] < olv) or (slv[r] > slv and olvr[r] < olvr)) 
            array.set(res, 0, slvr[r])
            array.set(res, 1, slvb[r] + r)
            array.set(res, 2, olvr[r])
            array.set(res, 3, olvb[r] + r)
            array.set(res, 4, slvr)
            array.set(res, 5, slvb)
            array.set(res, 6, olvr)
            array.set(res, 7, olvb)
            array.set(res, 8, ((slvr/slvr[r] - 1) * 10000 / (slvb[r]+r-slvb)) * ((olvr - olvr[r]) / (olvb[r]+r-olvb)))
            break
        
        if src[r] < slvr
            break
                
    res

// @function hidden_bull: hidden bull divergence, higher low src but lower low osc
// @param series float src: the source series
// @param series float osc: the oscillator index
// @param simple int lbL: look back left
// @param simple int lbR: look back right
// @param simple int rangeL: min look back range
// @param simple int rangeU: max look back range
// @param simple int tolerance: the number of tolerant klines
// @returns array: [left_src_value, left_src_bars, left_osc_value, left_osc_bars, right_src_value, right_src_bars, right_osc_value, right_osc_bars, div_cov]
export hidden_bull(series float src, series float osc, simple int lbL, simple int lbR, simple int rangeL, simple int rangeU, simple int tolerance) =>
    slv = ta.pivotlow(src, lbL, lbR)
    olv = ta.pivotlow(osc, lbL, lbR)
    slvf = na(slv) ? false : true
    olvf = na(olv) ? false : true
    
    slvb = ta.barssince(slvf)
    slvr = slvb <= tolerance ? slv[slvb] : na
    olvb = ta.barssince(olvf)
    olvr = olvb <= tolerance ? olv[olvb] : na
    
    cmb1 = slvb <= tolerance and olvf
    cmb2 = olvb <= tolerance and slvf
    cmb = cmb1 or cmb2
    
    res = array.new_float(9, na)
    if not cmb or na(slvr) or na(slvb) or na(olvr) or na(olvb)
        res

    for r = math.max(rangeL, lbL, slvb, olvb) to math.max(rangeU, lbL, slvb, olvb)
        if cmb[r] and ((slvr[r] < slvr and olv[r] > olv) or (slv[r] < slv and olvr[r] > olvr)) 
            array.set(res, 0, slvr[r])
            array.set(res, 1, slvb[r] + r)
            array.set(res, 2, olvr[r])
            array.set(res, 3, olvb[r] + r)
            array.set(res, 4, slvr)
            array.set(res, 5, slvb)
            array.set(res, 6, olvr)
            array.set(res, 7, olvb)
            array.set(res, 8, ((slvr/slvr[r] - 1) * 10000 / (slvb[r]+r-slvb)) * ((olvr - olvr[r]) / (olvb[r]+r-olvb)))
            break
        
        if osc[r] < olvr
            break
                
    res

// @function regular_bear: regular bear divergence, higher high src but lower high osc
// @param series float src: the source series
// @param series float osc: the oscillator index
// @param simple int lbL: look back left
// @param simple int lbR: look back right
// @param simple int rangeL: min look back range
// @param simple int rangeU: max look back range
// @param simple int tolerance: the number of tolerant klines
// @returns array: [left_src_value, left_src_bars, left_osc_value, left_osc_bars, right_src_value, right_src_bars, right_osc_value, right_osc_bars, div_cov]
export regular_bear(series float src, series float osc, simple int lbL, simple int lbR, simple int rangeL, simple int rangeU, simple int tolerance) =>
    shv = ta.pivothigh(src, lbL, lbR)
    ohv = ta.pivothigh(osc, lbL, lbR)
    shvf = na(shv) ? false : true
    ohvf = na(ohv) ? false : true
    
    shvb = ta.barssince(shvf)
    shvr = shvb <= tolerance ? shv[shvb] : na
    ohvb = ta.barssince(ohvf)
    ohvr = ohvb <= tolerance ? ohv[ohvb] : na
    
    cmb1 = shvb <= tolerance and ohvf
    cmb2 = ohvb <= tolerance and shvf
    cmb = cmb1 or cmb2
    
    res = array.new_float(9, na)
    if not cmb or na(shvr) or na(shvb) or na(ohvr) or na(ohvb)
        res

    for r = math.max(rangeL, lbL, shvb, ohvb) to math.max(rangeU, lbL, shvb, ohvb)
        if cmb[r] and ((shvr[r] < shvr and ohv[r] > ohv) or (shv[r] < shv and ohvr[r] > ohvr)) 
            array.set(res, 0, shvr[r])
            array.set(res, 1, shvb[r] + r)
            array.set(res, 2, ohvr[r])
            array.set(res, 3, ohvb[r] + r)
            array.set(res, 4, shvr)
            array.set(res, 5, shvb)
            array.set(res, 6, ohvr)
            array.set(res, 7, ohvb)
            array.set(res, 8, ((shvr/shvr[r] - 1) * 10000 / (shvb[r]+r-shvb)) * ((ohvr - ohvr[r]) / (ohvb[r]+r-ohvb)))
            break
        
        if src[r] > shvr
            break
                
    res

// @function hidden_bear: hidden bear divergence, lower high src but higher high osc
// @param series float src: the source series
// @param series float osc: the oscillator index
// @param simple int lbL: look back left
// @param simple int lbR: look back right
// @param simple int rangeL: min look back range
// @param simple int rangeU: max look back range
// @param simple int tolerance: the number of tolerant klines
// @returns array: [left_src_value, left_src_bars, left_osc_value, left_osc_bars, right_src_value, right_src_bars, right_osc_value, right_osc_bars, div_cov]
export hidden_bear(series float src, series float osc, simple int lbL, simple int lbR, simple int rangeL, simple int rangeU, simple int tolerance) =>
    shv = ta.pivothigh(src, lbL, lbR)
    ohv = ta.pivothigh(osc, lbL, lbR)
    shvf = na(shv) ? false : true
    ohvf = na(ohv) ? false : true
    
    shvb = ta.barssince(shvf)
    shvr = shvb <= tolerance ? shv[shvb] : na
    ohvb = ta.barssince(ohvf)
    ohvr = ohvb <= tolerance ? ohv[ohvb] : na
    
    cmb1 = shvb <= tolerance and ohvf
    cmb2 = ohvb <= tolerance and shvf
    cmb = cmb1 or cmb2
    
    res = array.new_float(9, na)
    if not cmb or na(shvr) or na(shvb) or na(ohvr) or na(ohvb)
        res

    for r = math.max(rangeL, lbL, shvb, ohvb) to math.max(rangeU, lbL, shvb, ohvb)
        if cmb[r] and ((shvr[r] > shvr and ohv[r] < ohv) or (shv[r] > shv and ohvr[r] < ohvr)) 
            array.set(res, 0, shvr[r])
            array.set(res, 1, shvb[r] + r)
            array.set(res, 2, ohvr[r])
            array.set(res, 3, ohvb[r] + r)
            array.set(res, 4, shvr)
            array.set(res, 5, shvb)
            array.set(res, 6, ohvr)
            array.set(res, 7, ohvb)
            array.set(res, 8, ((shvr/shvr[r] - 1) * 10000 / (shvb[r]+r-shvb)) * ((ohvr - ohvr[r]) / (ohvb[r]+r-ohvb)))
            break
        
        if osc[r] > ohvr
            break
                
    res
