Indicator · Pine Script v5 · On the chart

Cup & Handle Final

by regalouisei· code credited to fikira

This overlay indicator searches price history for cup and handle shapes using left and right pivot lengths and a lookback of prior pivots.

View on GitHub

GitHub stars2on the repository
Pine versionv5runs as is
Lines of code190
Settings16inputs
LicenceNone statedfile: MPL-2.0
Last update8 months ago25 Feb 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

190 lines of Pine Script v5 from pinescript/editors_picks/cup-handle.pine, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

cup-handle.pine
// 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))

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

Overview

Cup & Handle Final draws the cup fill, border, and retrace on the price chart. Settings limit how steep the top line may be, how tall the cup may be at the top and bottom, and how many bars may break those lines. Flatten options only change the drawn lines. Optional labels and test lines mark the latest cup and its top points. It does not place trades or manage exits.

Details

Published by

regalouisei on GitHub

Code credit

fikira, as written in the script's header

Type

Indicator, Pine Script v5

Draws

On the price chart

Category

Breakout, Support & resistance

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
leftint3left
rightint1right
PP backint50ZZback
% anglefloat22.prcAngle
% cup Height Topfloat22.prcOfCupT
max % breaksfloat20.maxHighs
FlattenbooltrueflatTop
show test-linesboolfalsei_shTlin
show test-labelsboolfalsei_shTlab
% cup Height Botfloat22.prcOfCupB
max % breaksfloat20.maxLows
FlattenbooltrueflatBot
Show all 16 settings
SettingTypeDefaultVariable
show Cup labelsboolfalsei_CupLab
fillcolorcolor.new(color.yellow, 87)c_cup
bordercolorcolor.new(color.aqua , 75)c_bord
retracecolorcolor.new( #FF0000 , 80)c_retr
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

Sideway Zones Live

by dearvn
257 stars

Sideway Zones Live is an overlay indicator that estimates inner and outer sideways bands from recent highs and lows with a rational quadratic kernel. It tracks swing pivots over a loopback period and counts bars that remain between the outer high and low. Afte

Indicator v5 TrendSupport & resistance
Updated 6 weeks ago · None stated GitHub repository of Sideway Zones Live
219 stars

This indicator identifies High Of Low Price (HOLP) and Low Of High Price (LOHP) candlestick setups based on price extremes over a specified lookback period. A HOLP signal triggers when the current bar closes above the high of a recent session low, whereas a LO

Indicator v5 Candlestick patternsBreakout
Updated 1 year ago · None stated GitHub repository of Candlestick Patterns - HOLP-LOHP
218 stars

This indicator plots dynamic support and resistance levels on the price chart. It detects pivot highs and lows using the pivot length and tracks touches to build strength-based levels. Support and resistance zones appear as colored boxes while lines mark key l

Indicator v6 Support & resistanceBreakout
Updated 3 months ago · None stated GitHub repository of Dynamic Support & Resistance

Donchian Channels (DC)

by mihakralj
99 stars

This indicator calculates and plots Donchian Channels directly on the price chart. It determines the highest high and lowest low over a user-selected lookback period using an optimized monotonic deque algorithm, as well as a middle basis line computed as their

Indicator v6 Volatility & bandsSupport & resistance
Updated 4 weeks ago · Apache-2.0 GitHub repository of Donchian Channels (DC)

Lows from New Highs

by be-thomas
65 stars

This indicator tracks bar lows during consecutive rising highs using Pine Script arrays. When at least four lows accumulate or the price breaches the minimum low in the stack, the script sorts the recorded lows in ascending order and flushes them. It plots up

Indicator v4 Support & resistanceBreakout
Updated 4 days ago · GPL-3.0 GitHub repository of Lows from New Highs

This indicator tracks smart money market structure directly on the price chart. It calculates dual-period swing highs and swing lows to detect Changes of Character (CHoCH), Breaks of Structure (BOS), inducements, and liquidity sweeps. When structural levels ar

Indicator v5 SMC / ICT / price actionSupport & resistance
Updated 1 year ago · Apache-2.0 GitHub repository of Market Structure with Inducements & Sweeps [LuxAlgo]

More indicators

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers