1Copy the scriptPress Copy script above, or the copy button on the code below.
2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
3Paste over the templateCreate a new library, select all of the template text and paste. Press Save and give it a name.
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.
Pine Script v6 user-defined types for pattern scanning
Configurable Zigzag pivot offsets and error ratio thresholds
Customizable trendline, pivot label, and theme drawing properties
Support for filtering allowed pattern types and pivot directions
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.
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
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
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,
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
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