Library · Pine Script v6 · Library

Chart Patterns

by deepentropy

Chart Patterns is a Pine Script v6 library designed to scan for and render technical chart patterns directly on the price chart.

View on GitHub

Repaints

GitHub stars13on the repository
Pine versionv6current
Lines of code612
Settings0inputs
LicenceMIT
Last update6 months ago29 Apr 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 library, select all of the template text and paste. Press Save and give it a name.
  4. 4Publish or import itA library draws nothing. Publish it from the editor, then import its functions in your own scripts.

Source Code

612 lines of Pine Script v6 from examples/chartpatterns.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

chartpatterns.pine
//@version=6

// @description Library having complete chart pattern implementation
library('chartpatterns', overlay = true)

import Trendoscope/ZigzagLite/3 as zg
import Trendoscope/LineWrapper/2 as wr
import Trendoscope/ohlc/3 as o

// @type Object containing properties for pattern scanning
// @field offset Zigzag pivot offset. Set it to 1 for non repainting scan.
// @field numberOfPivots Number of pivots to be used in pattern search. Can be either 5 or 6
// @field errorRatio Error Threshold to be considered for comparing the slope of lines
// @field flatRatio Retracement ratio threshold used to determine if the lines are flat
// @field flagRatio max ratio threshold for flag and pennnant
// @field checkBarRatio Also check bar ratio are within the limits while scanning the patterns
// @field barRatioLimit Bar ratio limit used for checking the bars. Used only when checkBarRatio is set to true
// @field avoidOverlap avoid overlapping patterns.
// @field repaint allow repainting pattern if the new coordinates are more appropriate
// @field allowedPatterns array of bool encoding the allowed pattern types.
// @field allowedLastPivotDirections array of int representing allowed last pivot direction for each pattern types
// @field themeColors color array of themes to be used.
export type ScanProperties
    int offset = 0
    int numberOfPivots = 5
    float errorRatio = 0.2
    float flatRatio = 0.2
    float flagRatio = 0.8
    bool checkBarRatio = true
    float barRatioLimit = 0.382
    bool avoidOverlap = true
    bool repaint = false
    array<bool> allowedPatterns
    array<int> allowedLastPivotDirections
    array<color> themeColors

// @type Object containing properties for pattern drawing
// @field patternLineWidth Line width of the pattern trend lines
// @field showZigzag show zigzag associated with pattern
// @field zigzagLineWidth line width of the zigzag lines. Used only when showZigzag is set to true
// @field zigzagLineColor color of the zigzag lines. Used only when showZigzag is set to true
// @field showPatternLabel display pattern label containing the name
// @field patternLabelSize size of the pattern label. Used only when showPatternLabel is set to true
// @field showPivotLabels Display pivot labels of the patterns marking 1-6
// @field pivotLabelSize size of the pivot label. Used only when showPivotLabels is set to true
// @field pivotLabelColor color of the pivot label outline. chart.bg_color or chart.fg_color are the appropriate values.
// @field deleteOnPop delete the pattern when popping out from the array of Patterns.
export type DrawingProperties
    int patternLineWidth = 2
    bool showZigzag = true
    int zigzagLineWidth = 1
    color zigzagLineColor = color.blue
    bool showPatternLabel = true
    string patternLabelSize = size.normal
    bool showPivotLabels = true
    string pivotLabelSize = size.normal
    color pivotLabelColor = chart.bg_color
    bool deleteOnPop = false

// @type Object containing Individual Pattern data
// @field pivots array of Zigzag Pivot points
// @field trendLine1 First trend line joining pivots 1, 3, 5
// @field trendLine2 Second trend line joining pivots 2, 4 (, 6)
// @field properties DrawingProperties Object carrying common properties
// @field patternColor Individual pattern color. Lines and labels will be using this color.
// @field ratioDiff Difference between trendLine1 and trendLine2 ratios
// @field zigzagLine Internal zigzag line drawing Object
// @field pivotLabels array containning Pivot labels
// @field patternLabel pattern label Object
// @field patternType integer representing the pattern type
// @field patternName Type of pattern in string
export type Pattern
    array<zg.Pivot> pivots
    wr.Line trendLine1
    wr.Line trendLine2
    DrawingProperties properties
    color patternColor
    float ratioDiff
    polyline zigzagLine
    array<label> pivotLabels
    label patternLabel
    int patternType
    string patternName

// @type Object containing Individual Pattern data
// @field pivots array of Zigzag Pivot points
// @field trendLine1 First trend line joining pivots 1, 3, 5
// @field trendLine2 Second trend line joining pivots 2, 4 (, 6)
// @field baseLine Base line of the flag or pennant
// @field properties DrawingProperties Object carrying common properties
// @field patternColor Individual pattern color. Lines and labels will be using this color.
// @field ratioDiff Difference between trendLine1 and trendLine2 ratios
// @field zigzagLine Internal zigzag line drawing Object
// @field pivotLabels array containning Pivot labels
// @field patternLabel pattern label Object
// @field patternType integer representing the pattern type
// @field patternName Type of pattern in string
export type FNPPattern
    array<zg.Pivot> pivots
    wr.Line trendLine1
    wr.Line trendLine2
    wr.Line baseLine
    DrawingProperties properties
    color patternColor
    float ratioDiff
    polyline zigzagLine
    array<label> pivotLabels
    label patternLabel
    int patternType
    string patternName


getPatternNameById(int id) =>
    switch id
        1 => 'Ascending Channel'
        2 => 'Descending Channel'
        3 => 'Ranging Channel'
        4 => 'Rising Wedge (Expanding)'
        5 => 'Falling Wedge (Expanding)'
        6 => 'Diverging Triangle'
        7 => 'Ascending Triangle (Expanding)'
        8 => 'Descending Triangle (Expanding)'
        9 => 'Rising Wedge (Contracting)'
        10 => 'Falling Wedge (Contracting)'
        11 => 'Converging Triangle'
        12 => 'Descending Triangle (Contracting)'
        13 => 'Ascending Triangle (Contracting)'
        => 'Error'

getFNPPatternNameById(int id) =>
    switch id
        1 => 'Bullish Flag'
        2 => 'Bearish Flag'
        3 => 'Bullish Pennant'
        4 => 'Bearish Pennant'
        => 'Error'


inRange(value, min, max) =>
    value >= min and value <= max

isSame(_first, _second, _third, properties) =>
    r1 = (_second.point.price - _first.point.price) / (_second.point.index - _first.point.index)
    r2 = (_third.point.price - _second.point.price) / (_third.point.index - _second.point.index)
    max = math.max(r1, r2)
    min = math.min(r1, r2)

    rMax = math.max(_third.ratio, _second.ratio)
    rMin = math.min(_third.ratio, _second.ratio)

    (min >= (1 - properties.errorRatio) * max or rMin >= (1 - properties.errorRatio) * rMax) and (properties.checkBarRatio ? inRange(_third.barRatio, properties.barRatioLimit, 1 / properties.barRatioLimit) and inRange(_second.barRatio, properties.barRatioLimit, 1 / properties.barRatioLimit) : true)


// @function draws pattern on the chart
// @param this Pattern object that needs to be drawn
// @returns Current Pattern object
export method draw(Pattern this) =>
    this.trendLine1.xloc := xloc.bar_time
    this.trendLine2.xloc := xloc.bar_time
    this.trendLine1.draw()
    this.trendLine2.draw()
    points = this.pivots.getPoints()
    if this.properties.showZigzag
        this.zigzagLine := polyline.new(points, line_color = this.properties.zigzagLineColor, line_width = this.properties.zigzagLineWidth, line_style = line.style_dotted, xloc = xloc.bar_time)
        this.zigzagLine
    if this.properties.showPatternLabel
        patternStyle = this.pivots.last().dir > 0 ? label.style_label_lower_left : label.style_label_upper_left
        this.patternLabel := label.new(points.last(), this.patternName, color = this.patternColor, textcolor = chart.bg_color, style = patternStyle, size = this.properties.patternLabelSize, xloc = xloc.bar_time)
        this.patternLabel
    if this.properties.showPivotLabels
        if na(this.pivotLabels)
            this.pivotLabels := array.new<label>()
            this.pivotLabels
        for [i, pivot] in this.pivots
            labelStyle = label.style_text_outline
            labelYloc = yloc.price
            this.pivotLabels.push(label.new(pivot.point, str.tostring(i + 1), yloc = labelYloc, style = labelStyle, color = this.properties.pivotLabelColor, textcolor = this.patternColor, size = this.properties.pivotLabelSize, xloc = xloc.bar_time))
    this

// @function draws pattern on the chart
// @param this FNPPattern object that needs to be drawn
// @returns Current FNPPattern object
export method draw(FNPPattern this) =>
    this.trendLine1.xloc := xloc.bar_time
    this.trendLine2.xloc := xloc.bar_time
    this.trendLine1.draw()
    this.trendLine2.draw()
    this.baseLine.draw()
    points = this.pivots.getPoints()
    if this.properties.showZigzag
        this.zigzagLine := polyline.new(points, line_color = this.properties.zigzagLineColor, line_width = this.properties.zigzagLineWidth, line_style = line.style_dotted, xloc = xloc.bar_time)
        this.zigzagLine
    if this.properties.showPatternLabel
        patternStyle = this.pivots.last().dir > 0 ? label.style_label_lower_left : label.style_label_upper_left
        this.patternLabel := label.new(points.last(), this.patternName, color = this.patternColor, textcolor = chart.bg_color, style = patternStyle, size = this.properties.patternLabelSize, xloc = xloc.bar_time)
        this.patternLabel
    if this.properties.showPivotLabels
        if na(this.pivotLabels)
            this.pivotLabels := array.new<label>()
            this.pivotLabels
        for [i, pivot] in this.pivots
            labelStyle = label.style_text_outline
            labelYloc = yloc.price
            this.pivotLabels.push(label.new(pivot.point, str.tostring(i + 1), yloc = labelYloc, style = labelStyle, color = this.properties.pivotLabelColor, textcolor = this.patternColor, size = this.properties.pivotLabelSize, xloc = xloc.bar_time))
    this

// @function erase the given pattern on the chart
// @param this Pattern object that needs to be erased
// @returns Current Pattern object
export method erase(Pattern this) =>
    this.trendLine1.delete()
    this.trendLine2.delete()
    this.zigzagLine.delete()
    this.patternLabel.delete()
    if not na(this.pivotLabels)
        while this.pivotLabels.size() > 0
            this.pivotLabels.pop().delete()
    this

// @function erase the given pattern on the chart
// @param this Pattern object that needs to be erased
// @returns Current Pattern object
export method erase(FNPPattern this) =>
    this.trendLine1.delete()
    this.trendLine2.delete()
    this.baseLine.delete()
    this.zigzagLine.delete()
    this.patternLabel.delete()
    if not na(this.pivotLabels)
        while this.pivotLabels.size() > 0
            this.pivotLabels.pop().delete()
    this

// @function creates FNP Pattern from base Pattern
// @param p Pattern object from which the FNP Pattern needs to be created
// @param base base point of the flag or pennant
// @returns Current Pattern object
export method createFNP(Pattern p, chart.point base) =>
    baseLine = wr.Line.new(base, p.pivots.first().point, xloc = xloc.bar_time, color = p.patternColor, width = p.properties.patternLineWidth)
    patternType = p.patternType == 2 or p.patternType == 10 ? 1 : p.patternType == 1 or p.patternType == 9 ? 2 : p.patternType == 11 or p.patternType == 12 or p.patternType == 13 ? p.pivots.last().dir > 0 ? 3 : 4 : 5

    FNPPattern.new(p.pivots, p.trendLine1.copy(), p.trendLine2.copy(), baseLine, p.properties, p.patternColor, p.ratioDiff, patternType = patternType, patternName = getFNPPatternNameById(patternType))

// @function push Pattern object to the array by keeping maxItems limit
// @param this array of Pattern objects
// @param p Pattern object to be added to array
// @oaram maxItems Max number of items the array can hold
// @returns Current Pattern array
export method push(array<Pattern> this, Pattern p, int maxItems) =>
    this.push(p)
    while this.size() > maxItems
        removedPattern = this.shift()
        if removedPattern.properties.deleteOnPop
            removedPattern.erase()

// @function push FNPPattern object to the array by keeping maxItems limit
// @param this array of FNPPattern objects
// @param p FNPPattern object to be added to array
// @oaram maxItems Max number of items the array can hold
// @returns Current FNPPattern array
export method push(array<FNPPattern> this, FNPPattern p, int maxItems) =>
    this.push(p)
    while this.size() > maxItems
        removedPattern = this.shift()
        if removedPattern.properties.deleteOnPop
            removedPattern.erase()
    this

method inspect(wr.Line ln, int stratingBar, int endingBar, int otherBar, float direction, array<o.OHLC> ohlcArray) =>
    valid = true
    score = 0.0
    for barIndex = stratingBar to endingBar by 1
        if barIndex > bar_index
            runtime.error('Something went wrong. Bars cannot exceed the current bar_index ' + str.tostring(barIndex) + ' : ' + str.tostring(bar_index) + ' : ' + str.tostring(ohlcArray.size()))
        barOhlc = na(ohlcArray) ? o.OHLC.new(open[bar_index - barIndex], high[bar_index - barIndex], low[bar_index - barIndex], close[bar_index - barIndex]) : ohlcArray.size() <= barIndex ? o.OHLC.new(open[bar_index - barIndex], high[bar_index - barIndex], low[bar_index - barIndex], close[bar_index - barIndex]) : ohlcArray.get(barIndex)
        barPrice = direction > 0 ? barOhlc.h : barOhlc.l
        barOutPrice = direction > 0 ? barOhlc.l : barOhlc.h
        linePrice = ln.get_price(barIndex)
        if linePrice * direction < math.min(barOhlc.o * direction, barOhlc.c * direction)
            valid := false
            break
        if linePrice * direction >= barOutPrice * direction and linePrice * direction <= barPrice * direction
            score := score + 1
            score
        else if barIndex == otherBar
            valid := false
            break
    [valid, score]

method inspect(array<chart.point> points, int stratingBar, int endingBar, float direction, array<o.OHLC> ohlcArray) =>
    if points.size() == 3
        trendLine1 = wr.Line.new(points.first(), points.last())
        [valid1, score1] = trendLine1.inspect(stratingBar, endingBar, points.get(1).index, direction, ohlcArray)

        trendLine2 = wr.Line.new(points.first(), points.get(1))
        [valid2, score2] = trendLine2.inspect(stratingBar, endingBar, points.last().index, direction, ohlcArray)

        trendLine3 = wr.Line.new(points.get(1), points.last())
        [valid3, score3] = trendLine3.inspect(stratingBar, endingBar, points.first().index, direction, ohlcArray)

        returnItem = valid1 and score1 > math.max(score2, score3) ? 1 : valid2 and score2 > math.max(score1, score3) ? 2 : 3
        [returnItem == 1 ? valid1 : returnItem == 2 ? valid2 : valid3, returnItem == 1 ? trendLine1 : returnItem == 2 ? trendLine2 : trendLine3]
    else
        trendLine = wr.Line.new(points.first(), points.last())
        [valid, score] = trendLine.inspect(stratingBar, endingBar, points.first().index, direction, ohlcArray)
        [valid, trendLine]

method resolvePatternName(Pattern this, ScanProperties properties) =>
    t1p1 = this.trendLine1.p1.price
    t1p2 = this.trendLine1.p2.price
    t2p1 = this.trendLine2.p1.price
    t2p2 = this.trendLine2.p2.price

    upperAngle = t1p1 > t2p1 ? (t1p2 - math.min(t2p1, t2p2)) / (t1p1 - math.min(t2p1, t2p2)) : (t2p2 - math.min(t1p1, t1p2)) / (t2p1 - math.min(t1p1, t1p2))
    lowerAngle = t1p1 > t2p1 ? (t2p2 - math.max(t1p1, t1p2)) / (t2p1 - math.max(t1p1, t1p2)) : (t1p2 - math.max(t2p1, t2p2)) / (t1p1 - math.max(t2p1, t2p2))

    upperLineDir = upperAngle > 1 + properties.flatRatio ? 1 : upperAngle < 1 - properties.flatRatio ? -1 : 0
    lowerLineDir = lowerAngle > 1 + properties.flatRatio ? -1 : lowerAngle < 1 - properties.flatRatio ? 1 : 0

    startDiff = math.abs(this.trendLine1.p1.price - this.trendLine2.p1.price)
    endDiff = math.abs(this.trendLine1.p2.price - this.trendLine2.p2.price)

    minDiff = math.min(startDiff, endDiff)
    barDiff = this.trendLine1.p2.index - this.trendLine2.p1.index
    priceDiff = math.abs(startDiff - endDiff) / barDiff

    probableConvergingBars = minDiff / priceDiff

    isExpanding = math.abs(t1p2 - t2p2) > math.abs(t1p1 - t2p1)
    isContracting = math.abs(t1p2 - t2p2) < math.abs(t1p1 - t2p1)

    isChannel = probableConvergingBars > 2 * barDiff or not isExpanding and not isContracting or upperLineDir == 0 and lowerLineDir == 0
    invalid = math.sign(t1p1 - t2p1) != math.sign(t1p2 - t2p2)
    this.patternType := invalid ? 0 : isChannel ? upperLineDir > 0 and lowerLineDir > 0 ? 1 : upperLineDir < 0 and lowerLineDir < 0 ? 2 : upperLineDir == 0 and lowerLineDir == 0 ? 3 : 3 : isExpanding ? upperLineDir > 0 and lowerLineDir > 0 ? 4 : upperLineDir < 0 and lowerLineDir < 0 ? 5 : upperLineDir > 0 and lowerLineDir < 0 ? 6 : upperLineDir > 0 and lowerLineDir == 0 ? 7 : upperLineDir == 0 and lowerLineDir < 0 ? 8 : -2 : isContracting ? upperLineDir > 0 and lowerLineDir > 0 ? 9 : upperLineDir < 0 and lowerLineDir < 0 ? 10 : upperLineDir < 0 and lowerLineDir > 0 ? 11 : lowerLineDir == 0 ? upperLineDir < 0 ? 12 : 1 : upperLineDir == 0 ? lowerLineDir > 0 ? 13 : 2 : -3 : -4
    if this.patternType < 0
        log.info('Wrong Type detected {0}, Dirs : {1}, {2}, Invalid : {7}, Prices: {3}, {4}, {5}. {6}', this.patternType, upperLineDir, lowerLineDir, t1p1, t1p2, t2p1, t2p2, invalid)
        this.patternType := 0
        this.patternType
    this

getRatioDiff(pivot1, pivot2, pivot3) =>
    firstRatio = (pivot2.point.price - pivot1.point.price) / (pivot2.point.index - pivot1.point.index)
    secondRatio = (pivot3.point.price - pivot2.point.price) / (pivot3.point.index - pivot2.point.index)
    math.abs(firstRatio - secondRatio)

method resolve(Pattern this, ScanProperties properties) =>
    firstIndex = this.pivots.getBars().first()
    lastIndex = this.pivots.getBars().last()
    firstTime = this.pivots.getPoints().first().time
    lastTime = this.pivots.getPoints().last().time
    this.trendLine1.p1 := chart.point.new(firstTime, firstIndex, this.trendLine1.get_price(firstIndex))
    this.trendLine1.p2 := chart.point.new(lastTime, lastIndex, this.trendLine1.get_price(lastIndex))

    this.trendLine1.color := this.patternColor

    this.trendLine2.p1 := chart.point.new(firstTime, firstIndex, this.trendLine2.get_price(firstIndex))
    this.trendLine2.p2 := chart.point.new(lastTime, lastIndex, this.trendLine2.get_price(lastIndex))
    this.trendLine2.color := this.patternColor

    for [i, pivot] in this.pivots
        currentTrendLine = i % 2 == 1 ? properties.numberOfPivots == 6 ? this.trendLine1 : this.trendLine2 : properties.numberOfPivots == 6 ? this.trendLine2 : this.trendLine1
        pivot.point.price := currentTrendLine.get_price(pivot.point.index)
        pivot.point.price

    lastPivot = this.pivots.last()
    llastPivot = this.pivots.get(properties.numberOfPivots - 2)
    lllastPivot = this.pivots.get(properties.numberOfPivots - 3)
    llllastPivot = this.pivots.get(properties.numberOfPivots - 4)
    lastPivot.ratio := math.abs(lastPivot.point.price - llastPivot.point.price) / math.abs(llastPivot.point.price - lllastPivot.point.price)
    llastPivot.ratio := math.abs(llastPivot.point.price - lllastPivot.point.price) / math.abs(lllastPivot.point.price - llllastPivot.point.price)

    this.resolvePatternName(properties)
    this

method deepcopy(zg.Pivot this) =>
    zg.Pivot.new(this.point.copy(), this.dir, this.level, this.ratio, this.sizeRatio, this.barRatio)

// @function Find patterns based on the currect zigzag object and store them in the patterns array
// @param this Zigzag object containing pivots
// @param properties ScanProperties object
// @oaram dProperties DrawingProperties object
// @param patterns Array of Pattern objects
// @param array of OHLC values.
// @param maxLivePatterns max number of patterns to be retained in the patterns array
// @param draw pattern is drawn on the chart automatically if set to true
// @returns Current Pattern object
export method findPattern(zg.Zigzag this, ScanProperties properties, DrawingProperties dProperties, array<Pattern> patterns, array<o.OHLC> ohlcArray = na, int maxLivePatterns = 20, bool draw = true) =>
    p6 = this.zigzagPivots.get(0 + properties.offset).deepcopy()
    p5 = this.zigzagPivots.get(1 + properties.offset).deepcopy()
    p4 = this.zigzagPivots.get(2 + properties.offset).deepcopy()
    p3 = this.zigzagPivots.get(3 + properties.offset).deepcopy()
    p2 = this.zigzagPivots.get(4 + properties.offset).deepcopy()
    p1 = properties.numberOfPivots == 6 ? this.zigzagPivots.get(5 + properties.offset).deepcopy() : na

    array<zg.Pivot> pivots = properties.numberOfPivots == 6 ? array.from(p1, p2, p3, p4, p5, p6) : array.from(p2, p3, p4, p5, p6)
    ignorePattern = false
    existingPattern = false
    existingPatternIndex = -1
    existingRatioDiff = 5.0
    for [index, pattern] in patterns
        startBar = pattern.pivots.first().point.index
        endBar = pattern.pivots.last().point.index
        currentStartBar = pivots.first().point.index
        currentLastBar = pivots.last().point.index

        if currentStartBar > startBar and currentStartBar < endBar and properties.avoidOverlap
            ignorePattern := true
            break

        match = true
        for i = 0 to properties.numberOfPivots - 2 by 1
            cp = pivots.get(i)
            ep = pattern.pivots.get(i)
            if cp.point.index != ep.point.index
                match := false
                break
        if match
            existingPattern := true
            existingPatternIndex := index
            existingRatioDiff := pattern.ratioDiff
            break

    validPattern = ignorePattern or existingPattern and not properties.repaint ? false : properties.numberOfPivots == 6 ? isSame(p2, p4, p6, properties) and isSame(p1, p3, p5, properties) : isSame(p2, p4, p6, properties)
    if validPattern
        trendPointArray1 = array.from(p2.point, p4.point, p6.point)
        trendPointArray2 = properties.numberOfPivots == 6 ? array.from(p1.point, p3.point, p5.point) : array.from(p3.point, p5.point)

        firstIndex = properties.numberOfPivots == 6 ? p1.point.index : p2.point.index
        lastIndex = p6.point.index
        [valid1, trendLine1] = trendPointArray1.inspect(firstIndex, lastIndex, math.sign(p6.dir), ohlcArray)
        [valid2, trendLine2] = trendPointArray2.inspect(firstIndex, lastIndex, math.sign(p5.dir), ohlcArray)

        if valid1 and valid2
            if na(properties.themeColors)
                properties.themeColors := array.from(color.blue)
                properties.themeColors

            if properties.themeColors.size() == 0
                properties.themeColors.push(color.blue)

            patternColor = properties.themeColors.shift()
            properties.themeColors.push(patternColor)
            trendLine1.width := dProperties.patternLineWidth
            trendLine2.width := dProperties.patternLineWidth
            ratioDiff = getRatioDiff(p2, p4, p6)
            currentPattern = Pattern.new(pivots, trendLine1, trendLine2, dProperties, patternColor, ratioDiff).resolve(properties)

            lastDir = math.sign(p6.dir)
            allowedLastPivotDirection = na(properties.allowedLastPivotDirections) ? 0 : properties.allowedLastPivotDirections.size() <= currentPattern.patternType ? 0 : properties.allowedLastPivotDirections.get(currentPattern.patternType)

            if (na(properties.allowedPatterns) ? true : properties.allowedPatterns.size() <= currentPattern.patternType ? false : currentPattern.patternType >= 0 ? properties.allowedPatterns.get(currentPattern.patternType) : false) and (allowedLastPivotDirection == 0 or allowedLastPivotDirection == lastDir)
                deleteOld = existingPattern and existingRatioDiff > currentPattern.ratioDiff
                if deleteOld
                    patterns.remove(existingPatternIndex).erase()

                if deleteOld or not existingPattern
                    currentPattern.patternName := getPatternNameById(currentPattern.patternType)
                    if draw
                        currentPattern.draw()
                    patterns.push(currentPattern, maxLivePatterns)
                else
                    validPattern := false
                    validPattern
            else
                validPattern := false
                validPattern
        else
            validPattern := false
            validPattern
    validPattern

// @function Find patterns based on the currect zigzag object but will not store them in the pattern array.
// @param this Zigzag object containing pivots
// @param properties ScanProperties object
// @oaram dProperties DrawingProperties object
// @param patterns Array of Pattern objects
// @param array of OHLC values.
// @returns Flag indicating if the pattern is valid, Current Pattern object
export method findPatternPlain(zg.Zigzag this, ScanProperties properties, DrawingProperties dProperties, array<Pattern> patterns = na, array<o.OHLC> ohlcArray = na) =>
    p6 = this.zigzagPivots.get(0 + properties.offset).deepcopy()
    p5 = this.zigzagPivots.get(1 + properties.offset).deepcopy()
    p4 = this.zigzagPivots.get(2 + properties.offset).deepcopy()
    p3 = this.zigzagPivots.get(3 + properties.offset).deepcopy()
    p2 = this.zigzagPivots.get(4 + properties.offset).deepcopy()
    p1 = properties.numberOfPivots == 6 ? this.zigzagPivots.get(5 + properties.offset).deepcopy() : na

    array<zg.Pivot> pivots = properties.numberOfPivots == 6 ? array.from(p1, p2, p3, p4, p5, p6) : array.from(p2, p3, p4, p5, p6)
    ignorePattern = false
    existingPattern = false

    for [index, pattern] in patterns
        startBar = pattern.pivots.first().point.index
        endBar = pattern.pivots.last().point.index
        currentStartBar = pivots.first().point.index
        currentLastBar = pivots.last().point.index

        if currentStartBar > startBar and currentStartBar < endBar and properties.avoidOverlap
            ignorePattern := true
            break
        match = true
        for i = 0 to properties.numberOfPivots - 2 by 1
            cp = pivots.get(i)
            ep = pattern.pivots.get(i)
            if cp.point.index != ep.point.index
                match := false
                break
        if match
            existingPattern := true
            existingPattern

    validPattern = ignorePattern or existingPattern ? false : properties.numberOfPivots == 6 ? isSame(p2, p4, p6, properties) and isSame(p1, p3, p5, properties) : isSame(p2, p4, p6, properties)
    Pattern currentPattern = na
    if validPattern
        trendPointArray1 = array.from(p2.point, p4.point, p6.point)
        trendPointArray2 = properties.numberOfPivots == 6 ? array.from(p1.point, p3.point, p5.point) : array.from(p3.point, p5.point)

        firstIndex = properties.numberOfPivots == 6 ? p1.point.index : p2.point.index
        lastIndex = p6.point.index
        [valid1, trendLine1] = trendPointArray1.inspect(firstIndex, lastIndex, math.sign(p6.dir), ohlcArray)
        [valid2, trendLine2] = trendPointArray2.inspect(firstIndex, lastIndex, math.sign(p5.dir), ohlcArray)
        if valid1 and valid2
            if na(properties.themeColors)
                properties.themeColors := array.from(color.blue)
                properties.themeColors

            if properties.themeColors.size() == 0
                properties.themeColors.push(color.blue)

            patternColor = properties.themeColors.shift()
            properties.themeColors.push(patternColor)
            trendLine1.width := dProperties.patternLineWidth
            trendLine2.width := dProperties.patternLineWidth
            ratioDiff = getRatioDiff(p2, p4, p6)
            currentPattern := Pattern.new(pivots, trendLine1, trendLine2, dProperties, patternColor, ratioDiff).resolve(properties)

            lastDir = math.sign(p6.dir)
            allowedLastPivotDirection = na(properties.allowedLastPivotDirections) ? 0 : properties.allowedLastPivotDirections.size() <= currentPattern.patternType ? 0 : properties.allowedLastPivotDirections.get(currentPattern.patternType)

            allowedPattern = (na(properties.allowedPatterns) ? true : properties.allowedPatterns.size() <= currentPattern.patternType ? false : currentPattern.patternType >= 0 ? properties.allowedPatterns.get(currentPattern.patternType) : false) and (allowedLastPivotDirection == 0 or allowedLastPivotDirection == lastDir)
            if allowedPattern
                currentPattern.patternName := getPatternNameById(currentPattern.patternType)
                currentPattern.patternName
            else
                validPattern := false
                validPattern
        else
            validPattern := false
            validPattern
    [validPattern, currentPattern]

// @function Find flag and pennant patterns based on the current zigzag object and returns the pattern object along with flag
// @param this Zigzag object containing pivots
// @param currentPattern Pattern object to be used as base
// @param sProperties ScanProperties object
// @returns Flag indicating if the fng pattern is valid, new FNPPattern object
export method findFNP(zg.Zigzag this, Pattern currentPattern, ScanProperties sProperties) =>
    valid = true
    confirmed = false
    flagConfirmed = false
    chart.point lastPoint = na
    dir = this.zigzagPivots.first().dir

    invalidationPrice = math.max(currentPattern.trendLine1.p1.price * dir, currentPattern.trendLine1.p2.price * dir)
    validationPrice = math.min(currentPattern.trendLine2.p1.price * dir, currentPattern.trendLine2.p2.price * dir)
    lastPrice = currentPattern.pivots.get(currentPattern.pivots.size() - 2).point.price
    FNPPattern currentFNPPattern = na
    prices = currentPattern.pivots.getPrices()
    prices.sort(dir > 0 ? order.descending : order.ascending)
    currentPriceIndex = 0
    float iinvalidationPrice = invalidationPrice
    for i = this.zigzagPivots.size() > sProperties.numberOfPivots + sProperties.offset ? sProperties.numberOfPivots + sProperties.offset : na to this.zigzagPivots.size() - 1 by 1
        pivot = this.zigzagPivots.get(i)
        if confirmed
            if pivot.point.price * dir < lastPoint.price * dir
                lastPoint := pivot.point
                lastPoint
            if pivot.point.price * dir >= iinvalidationPrice
                break
            if pivot.point.price * dir < iinvalidationPrice
                iinvalidationPrice := pivot.point.price * dir
                iinvalidationPrice
        else
            for j = currentPriceIndex to prices.size() - 1 by 1
                price = prices.get(j)
                if pivot.point.price * dir < price * dir
                    currentPriceIndex := j
                    iinvalidationPrice := price * dir
                    iinvalidationPrice
                else
                    break
            invalidationRatio = math.abs(math.abs(iinvalidationPrice) - pivot.point.price) / math.abs(invalidationPrice - iinvalidationPrice)
            if pivot.point.price * dir > iinvalidationPrice ? invalidationRatio > 0.5 : false
                valid := false
                break
            if pivot.point.price * dir < validationPrice
                confirmed := true
                lastPoint := pivot.point
                continue
    if valid and confirmed
        midbar = lastPoint.index + int((currentPattern.trendLine2.p1.index - lastPoint.index) * sProperties.flagRatio)
        priceAtMidBar = currentPattern.trendLine2.get_price(midbar)
        flagPriceAtMidBar = (lastPoint.price + this.zigzagPivots.first().point.price) / 2

        if priceAtMidBar * dir >= flagPriceAtMidBar * dir
            flagConfirmed := true
            flagConfirmed

    if valid and confirmed and flagConfirmed
        currentFNPPattern := currentPattern.createFNP(lastPoint)
        currentFNPPattern

    [valid and confirmed and flagConfirmed, currentFNPPattern]

Overview

Chart Patterns defines custom user-defined types to configure scanning parameters, such as Zigzag pivot offsets, error ratios, flat ratios, and flag thresholds. Additionally, it provides drawing properties to customize trendlines, Zigzag overlays, and pivot labels. Because the underlying pivot calculations default to an offset of zero, pattern detections can repaint on live bars until confirmed.

Details

Published by

deepentropy on GitHub

Type

Library, Pine Script v6

Draws

Library (no plots)

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

  • Signals can change on the live bar or after it closes (repainting)
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

FunctionLinearRegression

by ricardosantos79
261 stars

This Pine Script v5 library provides functions to calculate and plot linear regression channels using sample point arrays. It evaluates paired X and Y arrays to calculate the regression slope, intercept, fitted values, maximum deviation, minimum deviation, and

Library v5 Trend
Updated 4 years ago · None stated GitHub repository of FunctionLinearRegression

PositionSizing

by iamrichardD
57 stars

This Pine Script v6 library calculates trade position sizes and provides risk management functions for external scripts. It computes position sizes from account equity, percentage risk, and stop-loss distance, and offers dynamic sizing adjustments based on vol

Library v6
Updated 1 year ago · Apache-2.0 GitHub repository of PositionSizing

WebhookMessageLibrary

by TradersPost
6 stars

WebhookMessageLibrary is a helper library designed to construct structured JSON payload messages for automated trading integrations like TradersPost. It defines custom exported types and constants for trade actions, market sentiments, quantities, limit prices,

Library v5
Updated 1 year ago · None stated GitHub repository of WebhookMessageLibrary

Standard Lib

by vitahoang
5 stars

Standard Lib is a utility Pine Script library designed to assist developers with chart debugging and dynamic text display. It exports a print function that creates and updates a single text label on the price chart. The label automatically positions itself at

Library v5
Updated 3 years ago · None stated GitHub repository of Standard Lib

TheStratGrammar

by 345-strategies
5 stars

This library calculates TheStrat price action states, classifying candles by directional break structure, close relative to open, and rejected break conditions. It draws demo bar labels directly on the price chart using standard or glyph notation. User inputs

Library v6 Repaints
Updated 2 days ago · MPL-2.0 GitHub repository of TheStratGrammar

More libraries

Trade Your Charts With a Regulated Broker

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

Compare brokers