Head & Shoulders S/R Regular
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © fikira
//@version=5
indicator('Head & Shoulders S/R Regular', shorttitle='H&S S/R Reg.', max_lines_count=500, max_labels_count=500, max_boxes_count=500, overlay=true)

i_dateFilter    = input      (true  , title="═════ Date Range Filtering ═════")
i_fromYear      = input.int  (2010  , title="From Year"  , minval = 1900)
i_fromMonth     = input.int  (1     , title="From Month" , minval = 1, maxval = 12)
i_fromDay       = input.int  (1     , title="From Day"   , minval = 1, maxval = 31)
i_toYear        = input.int  (2999  , title="To Year"    , minval = 1900)
i_toMonth       = input.int  (1     , title="To Month"   , minval = 1, maxval = 12)
i_toDay         = input.int  (1     , title="To Day"     , minval = 1, maxval = 31)

fromDate        = timestamp  (i_fromYear, i_fromMonth, i_fromDay, 00, 00)
toDate          = timestamp  (i_toYear  , i_toMonth  , i_toDay  , 23, 59)
f_tradeDateIsAllowed() => not i_dateFilter or (time >= fromDate and time <= toDate)

left            = input.int  (10    , title=''     , group='Pivot Points -> Leftbars - Rightbars' , inline='LR' )
right           = input.int  ( 5    , title=''     , group='Pivot Points -> Leftbars - Rightbars' , inline='LR' )
retraceEmin     = input.float( 0.618, title=''     , group='min/max retrace AD -> E', step=0.1    , inline='RE' )
retraceEmax     = input.float( 1.618, title=''     , group='min/max retrace AD -> E', step=0.1    , inline='RE' )
percTol         = input.float( 5.0  , title=''     , group='% tolerance [L ~ R shoulder height]'  , inline='LS' )
minPercLSh      = input.float(30    , title=''     , group='min/max % [Left Shoulder ~ Head]'     , inline='LSH') / 100
maxPercLSh      = input.float(60    , title=''     , group='min/max % [Left Shoulder ~ Head]'     , inline='LSH') / 100
extLines        = input.int  ( 0    , title=''     , group='width R shoulder = L shoulder + x%'   , inline='LRS') / 100
maxVisablePat   = input.int  (50    , title=''     , group='maximum visible patterns'             , inline='MVP')

c_bl            = input.color(color.new(color.lime , 93), title='color bullish')
c_br            = input.color(color.new(color.red  , 93), title='color bearish')
c_nt            = input.color(color.new(color.blue , 93), title='color neutral')

var float   [] a_phP    = array.new_float   ()
var int     [] a_phB    = array.new_int     ()
var float   [] a_plP    = array.new_float   ()
var int     [] a_plB    = array.new_int     ()
var line    [] a_ln_bl  = array.new_line    ()
var linefill[] a_lnF_bl = array.new_linefill()
var line    [] a_EGmax  = array.new_line    ()
var line    [] a_EGmin  = array.new_line    ()

max = 10

if array.size(a_phP) > max
    array.pop(a_phB)
    array.pop(a_phP)
if array.size(a_plP) > max
    array.pop(a_plB)
    array.pop(a_plP)

if     array.size(a_EGmax ) > maxVisablePat
    if array.size(a_ln_bl ) > maxVisablePat * 4
        line.delete    (array.pop(a_ln_bl ))
    if array.size(a_lnF_bl) > maxVisablePat * 3
        linefill.delete(array.pop(a_lnF_bl))
    if array.size(a_EGmax ) > maxVisablePat * 1
        line.delete    (array.pop(a_EGmax ))
        line.delete    (array.pop(a_EGmin ))

a_diff  = array.new_float()
a_diffP = array.new_float()
a_diffB = array.new_int  ()

f_runtime_error(x1, x2, bars) => 
    if x2 - x1 > bars
        runtime.error(str.tostring(x2 - x1))

f_Head_Shoulders_Bull(left, right, a_plB, a_plP) =>
    // {
    ph = ta.pivothigh(left, right)
    pl = ta.pivotlow (left, right)
    line   testline   = na
    label  testlabel  = na
    line[] a_testline = array.new_line()
    //
    Ab = 0 , Bb = 0 , Cb = 0 , Db = 0 , Eb = 0 , Fb = 0 , Gb = 0
    Ap = 0., Bp = 0., Cp = 0., Dp = 0., Ep = 0., Fp = 0., Gp = 0.
    highestP    = 0.
    highestB    = 0
    if f_tradeDateIsAllowed() and pl
        //
        // {
        if array.size(a_plB) > 1
            for i = 0 to array.size(a_plB) -1
            // --------[ find basis ]-------
            // -------[ draw line from pl to previous pl's (array, i) ]-------
                Cb := array.get(a_plB, i)
                Cp := array.get(a_plP, i)
                Eb := bar_index - right
                Ep := pl
                //
                highMax   = 0.
                index     = 0
                stop      = 0
                array.unshift(a_testline, line.new(Cb, Cp, Eb, Ep))
                //
                // -------[ find head ]-------
                for j = right + 1 to (bar_index - Cb - 1) // all bars between Cb - Eb
                    // -------[ fill array with possible head's ]-------
                    highestP := high       [j]
                    highestB := bar_index - j
                    array.unshift(a_diff , high[j] - line.get_price(array.get(a_testline, 0), bar_index - j))
                    array.unshift(a_diffP, highestP)
                    array.unshift(a_diffB, highestB)
                    //
                    // -------[ check every low for breakthrough testline ]-------
                    if  stop == 0     and   low[j] < line.get_price(array.get(a_testline, 0), bar_index - j)
                        stop := 1
                //
                // -------[ if no breakthrough ]-------
                if stop == 0 
                    // -------[ head found ]-------
                    highMax    := array.max    (a_diff         )
                    index      := array.indexof(a_diff, highMax)
                    //
                    // --------[ width basis ]-------
                    basis_Cb_Eb =  Eb - Cb
                    basis_Cp_Ep = (Ep - Cp) / basis_Cb_Eb
                    //
                    // --------[ go to max 3/4 of basis to the left ]-------
                    for k = 0 to math.round(basis_Cb_Eb * 0.75)
                        line.set_xy1(array.get(a_testline, 0), Cb - k, Cp - (basis_Cp_Ep * k))
                        //
                        // --------[ if at the end -> stop ]-------
                        if k ==  math.round(basis_Cb_Eb * 0.75) 
                            stop := 1
                        //    
                        // --------[ if L shoulder is higher then desired Shoulder/Head ratio -> stop ]-------
                        if high[bar_index - Cb + k] - line.get_price(array.get(a_testline, 0), Cb - k) > highMax * maxPercLSh 
                            stop := 1
                            break 
                        //    
                        // --------[ if close is found that goes under the base line -> break ]-------
                        // --------[ break further at the end of the block ]-------
                        if stop == 0 and 
                           close[bar_index - Cb + k] < line.get_price(array.get(a_testline, 0), Cb - k)
                            Db         := array.get(a_diffB, index)
                            Dp         := array.get(a_diffP, index)
                            //
                            // --------[ A ]-------
                            Ab         := Cb - k
                            // --------[ get line price k further than x1 -> then substract from y1 => line price k back ]-------
                            priceDifference = line.get_price(array.get(a_testline, 0), Cb + k) - Cp
                            Ap         := Cp - priceDifference // close[bar_index - x1 + k]
                            //
                            // --------[ clear array's -> is used further ]-------
                            //
                            array.clear(a_diff )
                            array.clear(a_diffP)
                            array.clear(a_diffB)
                            //
                            break
                    if Ep < Dp - ((Dp - Ap) * retraceEmax) or
                       Ep > Dp - ((Dp - Ap) * retraceEmin)
                        stop := 1
                    //
                    if stop == 0
                        //
                        for l = (bar_index - Cb + 1) to (bar_index - Ab - 1) // all bars between Ab - Cb
                            // -------[ fill array with possible L shoulders ]-------
                            highestP := high       [l]
                            highestB := bar_index - l
                            array.unshift(a_diff , high[l] - line.get_price(array.get(a_testline, 0), bar_index - l))
                            array.unshift(a_diffP, highestP)
                            array.unshift(a_diffB, highestB)
                        //
                        // -------[ L shoulder found ]-------
                        highMax    := array.max    (a_diff          )
                        index      := array.indexof(a_diff , highMax)    
                        Bb         := array.get    (a_diffB, index  )
                        Bp         := array.get    (a_diffP, index  )
                        //
                        if Bp - line.get_price(array.get(a_testline, 0), Bb) < (Dp - (line.get_price(array.get(a_testline, 0), Db))) * minPercLSh
                            stop := 1
                        //    
                        if stop == 0
                            // -------[ find R shoulder ]-------
                            distAC      = math.round((Cb - Ab) * (1 + extLines))
                            Gb         := Eb + distAC
                            Ep_Gp       = Ep - Cp
                            Eb_Gb       = Eb - Cb
                            PdBar       = Ep_Gp / Eb_Gb
                            Gp         := Ep + (PdBar * distAC)
                            //
                            // -------[ ABC ]-------
                            lineAB = line.new(Ab, Ap, Bb, Bp, color=color.new(color.blue, 90))
                            lineBC = line.new(Cb, Cp, Bb, Bp, color=color.new(color.blue, 90))                     
                            // -------[ CDE ]-------
                            lineCD = line.new(Cb, Cp, Db, Dp, color=color.new(color.blue, 90))
                            lineDE = line.new(Eb, Ep, Db, Dp, color=color.new(color.blue, 90))
                            //
                            array.unshift(a_ln_bl , lineAB)
                            array.unshift(a_ln_bl , lineBC)
                            array.unshift(a_lnF_bl, linefill.new(lineAB, lineBC, color=c_nt))                       
                            array.unshift(a_ln_bl , lineCD)
                            array.unshift(a_ln_bl , lineDE)
                            array.unshift(a_lnF_bl, linefill.new(lineCD, lineDE, color=c_nt))
                            //
                            // -------[ EG - EG+ ]-------
                            plus      = highMax * (1 + (percTol / 100))
                            lineEGmax = line.new(Eb, Ep + plus, Gb, Gp + plus, style=line.style_dotted, color=color.new(color.blue, 50))
                            lineEGmin = line.new(Eb, Ep       , Gb, Gp       , style=line.style_dotted, color=color.new(color.blue, 50))
                            //
                            array.unshift(a_EGmax, lineEGmax)
                            array.unshift(a_EGmin, lineEGmin)
                            array.unshift(a_lnF_bl, linefill.new(lineEGmin, lineEGmax, color=c_nt))

        //
        array.unshift(a_plB, bar_index - right)
        array.unshift(a_plP, pl               )
        // }
    //    
    // -------[ delete testlines ]-------
    //
    for lines in a_testline
        line.delete(lines) 
    //
    if array.size(a_EGmax) > 0
        for m = 0 to array.size(a_EGmax) -1
            if bar_index      - line.get_x2(array.get(a_EGmax ,  m)) < 1000 
                if bar_index >= line.get_x1(array.get(a_EGmax ,  m))        and
                   bar_index <= line.get_x2(array.get(a_EGmax ,  m))        and
                   close   > line.get_price(array.get(a_EGmax ,  m), bar_index) 
                    //label.new(bar_index, close) -------[ testlb ]-------
                    linefill.set_color     (array.get(a_lnF_bl,  m * 3        ), c_bl                     )
                    linefill.set_color     (array.get(a_lnF_bl, (m * 3) + 1   ), c_bl                     )
                    linefill.set_color     (array.get(a_lnF_bl, (m * 3) + 2   ), c_bl                     )
                    line.set_color         (array.get(a_EGmax , (m * 1)       ), color.new(color.lime, 50))
                    line.set_color         (array.get(a_EGmin , (m * 1)       ), color.new(color.lime, 50))
        //
        for n = 0 to array.size(a_EGmin) -1
            if bar_index      - line.get_x2(array.get(a_EGmin ,  n)) < 1000 
                if bar_index >= line.get_x1(array.get(a_EGmin ,  n))        and
                   bar_index <= line.get_x2(array.get(a_EGmin ,  n))        and     
                   close   < line.get_price(array.get(a_EGmin ,  n), bar_index) 
                    //label.new(bar_index, close) -------[ testlb ]-------
                    linefill.set_color     (array.get(a_lnF_bl,  n * 3        ), c_br                     )
                    linefill.set_color     (array.get(a_lnF_bl, (n * 3) + 1   ), c_br                     )   
                    linefill.set_color     (array.get(a_lnF_bl, (n * 3) + 2   ), c_br                     )   
                    line.set_color         (array.get(a_EGmax , (n * 1)       ), color.new(color.red , 50))
                    line.set_color         (array.get(a_EGmin , (n * 1)       ), color.new(color.red , 50))                
    // }

f_Head_Shoulders_Bull(left, right, a_plB, a_plP)

