Cup & Handle
// 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('Cup & Handle Final', max_lines_count=500, max_labels_count=500, max_bars_back=5000, overlay=true)

// v82 v109 v233

// –––––––[       input      ]–––––––
calcs     = 'Flattens the lines, \nthis has no effect \n   on calculations \n-> just for visuals'
mHigh     = 'maximum highs breaking through the top line\n (% of bars between left & right point)' 
mLow      = 'maximum lows breaking through the bottom line\n (% of bars between left & right point)' 
cupLin    = 'shows maximum difference \n         (-> "% angle") \n between the 2 top points \n of the latest cup pattern'
cupLab    = 'shows left & right top points of the latest cup pattern'
cupHist   = 'shows position of cup pattern in history'

left      = input.int  (                         3 , title='left'                               , maxval= 50, minval= 1                     )
right     = input.int  (                         1 , title='right'                              , maxval= 50, minval= 1                     )
ZZback    = input.int  (                        50 , title='PP back'                            , maxval= 50, minval= 4                     )
prcAngle  = input.float(                        22., title='% angle'                            , step=  0.1, minval= 0, group='Top line'   ) / 100
prcOfCupT = input.float(                        22., title='% cup Height Top'                   , maxval=100, minval= 0, group='Top line'   ) / 100
maxHighs  = input.float(                        20., title='max % breaks'      , tooltip= mHigh , maxval= 30, minval= 0, group='Top line'   ) / 100
flatTop   = input.bool (                       true, title='Flatten'           , tooltip= calcs                        , group='Top line'   )
i_shTlin  = input.bool (                      false, title='  show test-lines' , tooltip= cupLin                       , group='Top line'   )
i_shTlab  = input.bool (                      false, title='  show test-labels', tooltip= cupLab                       , group='Top line'   )
prcOfCupB = input.float(                        22., title='% cup Height Bot'                   , maxval=100, minval= 0, group='Bottom line') / 100
maxLows   = input.float(                        20., title='max % breaks'      , tooltip= mLow  , maxval= 30, minval= 0, group='Bottom line') / 100
flatBot   = input.bool (                       true, title='Flatten'           , tooltip= calcs                        , group='Bottom line')
i_CupLab  = input.bool (                      false, title='  show Cup labels' , tooltip= cupHist                                           )
c_cup     = input.color(color.new(color.yellow, 87), title='fill'                                                      , group='colors'     )
c_bord    = input.color(color.new(color.aqua  , 75), title='border'                                                    , group='colors'     )
c_retr    = input.color(color.new(   #FF0000  , 80), title='retrace'                                                   , group='colors'     )

// –––––––[     function     ]–––––––
clearFill(fl) =>        
    while array.size(fl) > 0
        lineF         = array.pop(fl)
        line.delete(linefill.get_line1(lineF))
        line.delete(linefill.get_line2(lineF))
        linefill.delete(lineF)

// –––––––[     variables    ]–––––––
var aP         = array.new <float>   ()
var aB         = array.new <int>     ()
var aLbCup     = array.new <label>   ()
var aLnFill    = array.new <linefill>()

var line  l1   = line.new (bar_index, close, bar_index, close)
var line  l2   = line.new (bar_index, close, bar_index, close)
var label b1   = label.new(bar_index, close,  color=color.red, style=label.style_label_right, size= size.tiny    )
var label b2   = label.new(bar_index, close,  color=color.red, style=label.style_label_left , size= size.tiny    )
var box   bx   = box.new  (bar_index, close, bar_index, close, border_color= c_bord, bgcolor= na                 )
var box   bx_  = box.new  (bar_index, close, bar_index, close, border_color= c_cup , bgcolor= na, border_width= 7)
var box   bx2  = box.new  (bar_index, close, bar_index, close, border_color= na    , bgcolor= c_retr             )

// –––––––[      clean       ]–––––––
if array.size(aP) > 50
    array.pop(aP)
    array.pop(aB)

if array.size(aLbCup ) > 500
    label.delete(array.pop(aLbCup))

if array.size(aLnFill) > 500
    lineF         = array.pop(aLnFill)
    line.delete(linefill.get_line1(lineF))
    line.delete(linefill.get_line2(lineF))
    linefill.delete(lineF)

// –––––––[      calcs       ]–––––––
ph          = ta.pivothigh(left, right)

// –––––––[ math.cos(    0      ) =  1 ]–––––––
// –––––––[ math.cos(math.pi / 2) =  0 ]–––––––
// –––––––[ math.cos(math.pi    ) = -1 ]–––––––
// –––––––[ => cos(0 -> pi) => 1 -> -1 ]–––––––
 
if ph 
    lwset = 0., cupH1 = 0., cupH2 = 0.
    if array.size(aP) > 4
        // loopieloopie a{
        for a = 4 to math.min(ZZback, array.size(aP)) -1
            broken = false
            brTop  = 0
            brBot  = 0
            // –––––––[ get points -> x, y ]–––––––
            x1 = array.get(aB, a) , y1 = array.get(aP, a)
            x2 = bar_index - right, y2 = ph
            //
            cupW     = x2 - x1           // width 'cup'
            lo = low
            // loopieloopie c{
            for c = 0 to cupW
                if low[c] < lo
                    lo := low[c]
            // –––––––[ if 'angle' per bar between y2 & y1 is smaller than x% of difference ]–––––––
            if y2 > y1 and y1 > y2 - ((y2 - lo) * prcAngle) and bar_index - x1 < 3000
                // –––––––[ draw line ]–––––––
                testline = line.new(x1, y1, x2, y2)
                // loopieloopie b{
                for b = right to bar_index - x1
                    // –––––––[ if a close between breaks the line -> stop ]–––––––
                    if close[b] > line.get_price(testline, bar_index - b)
                        broken := true
                        break
                //}
                if broken == false              // if not broken
                    //
                    // –––––––[ A) first check if there is no break above/below upper/lower line ]–––––––
                    //
                    aPPT = array.new<float>(), aPPB = array.new<float>()
                    aPBT = array.new<int>  (), aPBB = array.new<int>  ()
                    //
                    //cupW     = x2 - x1           // width 'cup'
                    //lo = low
                    //// loopieloopie c{
                    //for c = 0 to cupW
                    //    if low[c] < lo
                    //        lo := low[c]
                    //}
                    lwset   := lo                // lowest point
                    cupH1   := y2 - lwset        // height 'cup' (y2 - lowest)
                    cupH2   := y1 - lwset        // height 'cup' (y1 - lowest)
                    plus     = cupH1 * prcOfCupT // percentage of difference between highest point and lowest of cup
                    min      = cupH2 * prcOfCupB // percentage of difference between other high point and lowest of cup
                    brT      = math.round(cupW * maxHighs)
                    brB      = math.round(cupW * maxLows )
                    //
                    // loopieloopie d{
                    for d    = 0 to cupW 
                        cos  = math.cos (math.pi / cupW *  d ) //    1 -> -1
                        nxt  = math.sqrt(      1 -  cos * cos) // -> 1 ->  0 -> 1  // math.abs(cos) , -math.abs(cos), cos
                        clcT = y2    -  (         cupH1 * nxt) + plus
                        clcB = y1    -  (         cupH2 * nxt) - min
                        //
                        // [ check for break ]
                        if brTop > brT or brBot > brB
                            break
                        if high[d] > math.min(clcT ,  y2 )  
                            brTop += 1
                        if low [d] < math.max(lwset, clcB)
                            brBot += 1
                        //
                        array.unshift(aPPT, flatTop ? math.min(y2   , clcT ) : clcT)
                        array.unshift(aPPB, flatBot ? math.max(lwset, clcB ) : clcB)
                        array.unshift(aPBT, x2 - d) ,   array.unshift(aPBB , x2 - d)
                    //}
                    // –––––––[ B) if no break -> draw everything ]–––––––
                    //
                    if brTop < brT and brBot < brB
                        clearFill(aLnFill)       // delete previous linefill drawings
                        if i_CupLab
                            array.unshift(aLbCup, 
                              label.new  (x2, y2, color=color.new(color.blue , 75), 
                              textcolor=color.yellow, text='Cup', size=size.small))
                        // loopieloopie e{
                        for e = array.size(aPPT) -2 to 0
                            array.unshift(aLnFill, 
                             linefill.new(
                                 line.new(array.get(aPBT, e    ), array.get(aPPT, e    ), 
                                          array.get(aPBT, e + 1), array.get(aPPT, e + 1), color=c_bord), 
                                 line.new(array.get(aPBB, e    ), array.get(aPPB, e    ), 
                                          array.get(aPBB, e + 1), array.get(aPPB, e + 1), color=c_bord), c_cup))                                     
                        //}
                        // –––––––[ draw box ]–––––––
                        box.set_lefttop     (bx , x2           , y2                 )
                        box.set_rightbottom (bx , x2 + cupW / 3, y2 - (cupH1 * 0.5 ))
                        box.set_lefttop     (bx_, x2           , y2                 )
                        box.set_rightbottom (bx_, x2 + cupW / 3, y2 - (cupH1 * 0.5 ))
                        box.set_lefttop     (bx2, x2           , y2 - (cupH1 * 0.33))
                        box.set_rightbottom (bx2, x2 + cupW / 3, y2 - (cupH1 * 0.5 ))
                        // –––––––[ draw test labels ]–––––––
                        if i_shTlab
                            label.set_xy(b1, x1, y1)                            
                            label.set_xy(b2, x2, y2)
                        // –––––––[ draw test lines ]–––––––
                        if i_shTlin
                            line.set_xy1(l1, x1, y2 )
                            line.set_xy2(l1, x2, y2 )
                            line.set_xy1(l2, x1, y2 - ((y2 - lo) * prcAngle))
                            line.set_xy2(l2, x2, y2 - ((y2 - lo) * prcAngle))
                //
                line.delete(testline)
        //}
    //        
    array.unshift(aP, ph)
    array.unshift(aB, bar_index - right)

//plot(array.size(line.all))

//-------------------------------------------------------
