Multiple Divergences (UDTs - objects) - Educational
fi(ki)=>'ra' // ©fikira - This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
//@version=5

indicator('Multiple Divergences (UDTs - objects)' , max_lines_count=500, max_labels_count=500, overlay=true) // max_bars_back=2000,

// ––––––––––––––––––––––––––––––––––––––––––––––––––––––[  input's  ]––––––––––––––––––––––––––––––––––––––––––––––––––––––

int  left = input.int   (    3, 'left'      , minval=  1, maxval= 100)
int right = input.int   (    1, 'right'     , minval=  1, maxval= 100)

int   mx  = input.int   (  750, 'max values', minval=100, maxval=2000)

bool trsi = input.bool  ( true, ''                                   , inline='1', group='Oscillators (+ length)')
int l_rsi = input.int   (   14, 'RSI'       , minval=  1, maxval= 100, inline='1', group='Oscillators (+ length)')
color bl1 = input.color (color.lime  , ''                          , inline='1', group='Oscillators (+ length)')
color br1 = input.color (#FF0000     , ''                          , inline='1', group='Oscillators (+ length)')

bool tCci = input.bool  ( true, ''                                   , inline='2', group='Oscillators (+ length)')
int l_cci = input.int   (   14, 'CCI'       , minval=  1, maxval= 100, inline='2', group='Oscillators (+ length)')
color bl2 = input.color (color.olive , ''                          , inline='2', group='Oscillators (+ length)')
color br2 = input.color (color.red   , ''                          , inline='2', group='Oscillators (+ length)')

bool tMfi = input.bool  ( true, ''                                   , inline='3', group='Oscillators (+ length)')
int l_mfi = input.int   (   14, 'MFI'       , minval=  1, maxval= 100, inline='3', group='Oscillators (+ length)')
color bl3 = input.color (color.yellow, ''                          , inline='3', group='Oscillators (+ length)')
color br3 = input.color (color.orange, ''                          , inline='3', group='Oscillators (+ length)')


// ––––––––––––––––––––––––––––––––––––––––––––––––––––––[   UDT's   ]––––––––––––––––––––––––––––––––––––––––––––––––––––––

// @type                An object of highs and lows
// @field b             bar_index - right
// @field h             high[right]
// @field l             low [right]
// @field isPh          is Pivot High
// @field isPl          is Pivot Low
type HLs
    int   b      = na
    float h      = na 
    float l      = na 
    bool isPh    = false
    bool isPl    = false

// @type                An object of oscillator values
// @field b             bar_index - right
// @field o             oscillator[right]
// @field isPh          is Pivot High
// @field isPl          is Pivot Low
type Osc
    int   b       = na
    float o       = na 
    bool isPh     = false
    bool isPl     = false


// ––––––––––––––––––––––––––––––––––––––––––––––––––––––[  array's  ]––––––––––––––––––––––––––––––––––––––––––––––––––––––

var HLs[] aHLs    = array.new      <HLs>()
var Osc[] aRSI    = array.new      <Osc>()
var Osc[] aCCI    = array.new      <Osc>()
var Osc[] aMFI    = array.new      <Osc>()
var line[] aLn    = array.new     <line>()

// ––––––––––––––––––––––––––––––––––––––––––––––––––––––[   data    ]––––––––––––––––––––––––––––––––––––––––––––––––––––––

int        sz     = array.size    ( aHLs )
int        ls     = array.size    ( aLn  ) 

float      hi     = nz(high       [right])
float      lo     = nz(low        [right])
int        bx     = nz(bar_index - right )

// ––––––––––––––––––––––––––––––––––––––––––––––––––––––[ functions ]––––––––––––––––––––––––––––––––––––––––––––––––––––––

// @function            Fills Oscillator array
aOsc(series bool osc_ph, series bool osc_pl, series float osc, Osc[] aOsc) =>
    max_bars_back(osc, 2000)
    os            = nz(osc        [right])
    switch
        osc_ph => get = Osc.new(bx, os, true , false), array.unshift(aOsc, get)
        osc_pl => get = Osc.new(bx, os, false, true ), array.unshift(aOsc, get)
        =>        get = Osc.new(bx, os, false, false), array.unshift(aOsc, get)


// @function            Checks for Regular Divergence (high/low)
Reg_Div_Piv(series HLs getZ, series string BlBr) =>        //   -   -   -   -   -   // getZ = current Pivot point
    max_bars_back(time, 2000)                                                       // prevents "Pine cannot determine the referencing length of a series. Try using max_bars_back ..." error
    bull   = BlBr == 'bl'  //   -   -   -   -   -   -   -   -   -   -   -   -   -   // bullish/bearish divergence
    check  = false                                                                  // checks for a non-pierced DIV line
    int x1 = na, float y1 = na, int x2 = na, float y2 = na                          //    
    if  sz > right         //   -   -   -   -   -   -   -   -   -   -   -   -   -   // if enough data in array
        //-{                                                                        //
        for i = 0 to sz - right                                                     //
            getA     = array.get(aHLs, i)                  //   -   -   -   -   -   // getA = point in history
            if (bull ? getA.isPl : getA.isPh)                                       // if getA is a Pivot point
                _x1  = getA.b, _y1 = (bull ? getA.l : getA.h)                       // collect data
                _x2  = getZ.b, _y2 = (bull ? getZ.l : getZ.h)                       //
                if (bull ? _y2 < _y1 : _y2 > _y1)          //   -   -   -   -   -   // if condition for DIV is true
                    testline = line.new(_x1, _y1, _x2, _y2)                         // create testline
                    bix      = bar_index   - _x1                                    //
                    //-{                                                            //
                    for j    = right to bix                //   -   -   -   -   -   // check if high/low pierces testline in between
                        getY = array.get(aHLs, j)                                   //
                        if (bull ? getY.l < line.get_price(testline, getY.b) :      // 
                                   getY.h > line.get_price(testline, getY.b) )      //
                            break      //   -   -   -   -   -   -   -   -   -   -   // if pierced -> break, check remains false
                        if j == bix                                                 // if at end of loop
                            check := true                  //   -   -   -   -   -   // else -> collect data for output
                            x1    := getA.b, y1 := (bull ? getA.l : getA.h)         //
                            x2    := getZ.b, y2 := (bull ? getZ.l : getZ.h)         //
                    // }-                                                           //
                    line.delete(testline)                  //   -   -   -   -   -   // deleted testline
        // }-                                                                       //
    [check, x1, y1, x2, y2]//   -   -   -   -   -   -   -   -   -   -   -   -   -   // output


// @function            Checks for Regular Divergence (oscillator)
Reg_Div_Osc(Osc[] aOsc, series int pv_x1, series int pv_x2, series string BlBr) =>  // aOsc = array of oscillator values
    max_bars_back(time, 2000)                                                       // prevents "Pine cannot determine the referencing length of a series. Try using max_bars_back ..." error
    bull   = BlBr == 'bl'  //   -   -   -   -   -   -   -   -   -   -   -   -   -   // bullish/bearish divergence
    check  = false                                                                  // checks for a non-pierced DIV line
    int x1 = na, float y1 = na, int x2 = na, float y2 = na                          //
    if sz > right and array.size(aOsc) > 0                 //   -   -   -   -   -   // if enough data in array
        getZ      =   array.get (aOsc  , 0)                                         // get current oscillator data 
        //-{                                                                        //
        for i = 0 to sz - right                                                     //
            getA  =    array.get(aOsc  , i)                //   -   -   -   -   -   //  getA = point in history
            _x1   = getA.b, _y1 = getA.o, _x2 = getZ.b, _y2 = getZ.o                //
            isPiv = (bull ? getA.isPl : getA.isPh)                                  // if getA is a Pivot point
            if isPiv and math.abs(pv_x1 - _x1) < 3 and math.abs(pv_x2 - _x2) < 3    // if Piv high/low is not too far from Piv Oscillator
                if (bull ? _y2 > _y1 : _y2 < _y1)          //   -   -   -   -   -   // if condition for DIV is true
                    testline = line.new(_x1, _y1, _x2, _y2)                         // create testline
                    bix      = bar_index   - _x1                                    //
                    //-{                                                            //
                    for j = right to bix                   //   -   -   -   -   -   // check if oscillator pierces testline in between
                        getY = array.get(aOsc, j)                                   //
                        if (bull ? getY.o < line.get_price(testline, getY.b) :      // 
                                   getY.o > line.get_price(testline, getY.b) )      //
                            break      //   -   -   -   -   -   -   -   -   -   -   // if pierced -> break, check remains false
                        if j == bix                                                 // if at end of loop 
                            check := true                  //   -   -   -   -   -   // else -> collect data for output
                            x1    := getA.b, y1 := getA.o                           //
                            x2    := getZ.b, y2 := getZ.o                           //
                    // }-                                                           //        
                    line.delete(testline)                  //   -   -   -   -   -   // deleted testline
        // }-                                                                       //
    [check, x1, y1, x2, y2]//   -   -   -   -   -   -   -   -   -   -   -   -   -   // output


// @function            Draws a line when divergence is spotted
draw(series string BlBr, series bool toggle, Osc[] aOsc, series int x1, series float y1, series int x2, series float y2, series color col) => 
    int  check = 0
    bool bull  = BlBr == 'bl'
    if toggle
        [ch_osc, x1_o, y1_o, x2_o, y2_o] = Reg_Div_Osc(aOsc, x1, x2, bull ? 'bl' : 'br') 
        if ch_osc
            check := 1
            array.unshift(aLn, line.new(x1, y1, x2, y2, color=col, width=1))
    check


// @function            Cleans array when full
pop(arr) =>
    if array.size(arr) > mx
        array.pop(arr)


// @function            Lines are dotted when unconfirmed DIV, solid if confirmed
setLine(series int n)  =>
    line ln = ls > n ? array.get(aLn, n) : na
    line.set_style(ln, bar_index - line.get_x2(ln) <= right ? line.style_dotted : line.style_solid )


// ––––––––––––––––––––––––––––––––––––––––––––––––––––––[   Action  ]––––––––––––––––––––––––––––––––––––––––––––––––––––––

// calculate values -{
float rsi = ta.rsi      (close     , l_rsi)
float cci = ta.cci      (close     , l_cci) 
float mfi = ta.mfi      (close     , l_mfi) 

ph        = ta.pivothigh(      left, right)
pl        = ta.pivotlow (      left, right)
rsi_ph    = ta.pivothigh(rsi , left, right)
rsi_pl    = ta.pivotlow (rsi , left, right)
cci_ph    = ta.pivothigh(cci , left, right)
cci_pl    = ta.pivotlow (cci , left, right)
mfi_ph    = ta.pivothigh(mfi , left, right)
mfi_pl    = ta.pivotlow (mfi , left, right)
// }

// add object (Osc.new) to array -{
if trsi
    aOsc(rsi_ph, rsi_pl, rsi, aRSI)

if tCci
    aOsc(cci_ph, cci_pl, cci, aCCI)

if tMfi    
    aOsc(mfi_ph, mfi_pl, mfi, aMFI)
// }

// add object (HLs.new) to array -{
switch  
    //
    ph =>
        getZ = HLs.new(bx, hi, lo, true , false), array.unshift(aHLs, getZ)
        [check, x1, y1, x2, y2] = Reg_Div_Piv(getZ, 'br') 
        if check
            check1 = draw('br', trsi, aRSI, x1, y1, x2, y2, br1)
            check2 = draw('br', tCci, aCCI, x1, y1, x2, y2, br2)
            check3 = draw('br', tMfi, aMFI, x1, y1, x2, y2, br3)
            total  = check1 + check2 + check3
            if total > 0
                label.new(
                 bx, hi, style=label.style_label_down, text=str.tostring(total),
                  color=color.new(#FF0000, 90), textcolor=#FF0000
                   )
    //
    pl =>
        getZ = HLs.new(bx, hi, lo, false, true ), array.unshift(aHLs, getZ)
        [check, x1, y1, x2, y2] = Reg_Div_Piv(getZ, 'bl')  
        if check
            check1 = draw('bl', trsi, aRSI, x1, y1, x2, y2, bl1)
            check2 = draw('bl', tCci, aCCI, x1, y1, x2, y2, bl2)
            check3 = draw('bl', tMfi, aMFI, x1, y1, x2, y2, bl3)
            total  = check1 + check2 + check3
            if total > 0
                label.new(
                 bx, lo, style=label.style_label_up  , text=str.tostring(total),
                  color=color.new(#00FF00, 90), textcolor=#00FF00
                   )
    //
    =>  array.unshift(aHLs, HLs.new(bx, hi, lo, false, false))
// }

// ––––––––––––––––––––––––––––––––––––––––––––––––––––––[ ––––––––– ]––––––––––––––––––––––––––––––––––––––––––––––––––––––

// clean array's
pop(aHLs), pop(aRSI), pop(aCCI), pop(aMFI)

// clean line array
if ls > 500
    line.delete(array.pop(aLn))

// set line style
setLine(0), setLine(1), setLine(2)

// ––––––––––––––––––––––––––––––––––––––––––––––––––––––[ ––––––––– ]––––––––––––––––––––––––––––––––––––––––––––––––––––––


