OPEN-SOURCE SCRIPT
Double Pattern Screener v7

//version=6
indicator("Double Pattern Screener", shorttitle="DPS", overlay=false)
// Screener Inputs
leftBars = input.int(5, "Left Bars", minval=3, maxval=10)
rightBars = input.int(5, "Right Bars", minval=3, maxval=10)
tolerance = input.float(0.02, "Max Difference", step=0.01)
atrLength = input.int(14, "ATR Length", minval=1)
// NEW: Filter for number of equal peaks
filterPeaks = input.int(3, "Filter: Show Only X Equal Peaks", minval=2, maxval=5)
enableFilter = input.bool(true, "Enable Peak Count Filter")
// Arrays for tracking swings
var array<float> todaySwingLevels = array.new<float>(0)
var array<int> swingCounts = array.new<int>(0)
var array<bool> isResistance = array.new<bool>(0)
var array<int> swingBars = array.new<int>(0)
var int maxEqualPeaks = 0
var float nearestEqualLevel = na
var float distanceToNearest = na
var bool hasPattern = false
// Detect swings
swingHigh = ta.pivothigh(high, leftBars, rightBars)
swingLow = ta.pivotlow(low, leftBars, rightBars)
// Track current day
currentDay = dayofmonth
isNewDay = currentDay != currentDay[1]
// Clear on new day
if barstate.isfirst or isNewDay
array.clear(todaySwingLevels)
array.clear(swingCounts)
array.clear(isResistance)
array.clear(swingBars)
maxEqualPeaks := 0
nearestEqualLevel := na
distanceToNearest := na
hasPattern := false
// Function to find matching level
findMatchingLevel(newLevel, isHigh) =>
matchIndex = -1
if array.size(todaySwingLevels) > 0
for i = 0 to array.size(todaySwingLevels) - 1
existingLevel = array.get(todaySwingLevels, i)
existingIsResistance = array.get(isResistance, i)
if math.abs(newLevel - existingLevel) <= tolerance and existingIsResistance == isHigh
matchIndex := i
break
matchIndex
// Process swing highs
if not na(swingHigh)
matchIndex = findMatchingLevel(swingHigh, true)
if matchIndex >= 0
newCount = array.get(swingCounts, matchIndex) + 1
array.set(swingCounts, matchIndex, newCount)
// Update max equal peaks
if newCount > maxEqualPeaks
maxEqualPeaks := newCount
else
array.push(todaySwingLevels, swingHigh)
array.push(swingCounts, 1)
array.push(isResistance, true)
array.push(swingBars, bar_index)
// Process swing lows
if not na(swingLow)
matchIndex = findMatchingLevel(swingLow, false)
if matchIndex >= 0
newCount = array.get(swingCounts, matchIndex) + 1
array.set(swingCounts, matchIndex, newCount)
// Update max equal peaks
if newCount > maxEqualPeaks
maxEqualPeaks := newCount
else
array.push(todaySwingLevels, swingLow)
array.push(swingCounts, 1)
array.push(isResistance, false)
array.push(swingBars, bar_index)
// Remove broken levels
if array.size(todaySwingLevels) > 0
for i = array.size(todaySwingLevels) - 1 to 0
level = array.get(todaySwingLevels, i)
isRes = array.get(isResistance, i)
levelBroken = isRes ? close > level : close < level
if levelBroken
removedCount = array.get(swingCounts, i)
array.remove(todaySwingLevels, i)
array.remove(swingCounts, i)
array.remove(isResistance, i)
array.remove(swingBars, i)
// Recalculate max if we removed the highest count
if removedCount == maxEqualPeaks
maxEqualPeaks := 0
if array.size(swingCounts) > 0
for j = 0 to array.size(swingCounts) - 1
count = array.get(swingCounts, j)
if count > maxEqualPeaks
maxEqualPeaks := count
// Calculate nearest equal level and distance
nearestEqualLevel := na
distanceToNearest := na
smallestDistance = 999999.0
if array.size(todaySwingLevels) > 0
for i = 0 to array.size(todaySwingLevels) - 1
count = array.get(swingCounts, i)
level = array.get(todaySwingLevels, i)
// Only consider levels with 2+ touches
if count >= 2
distance = math.abs(close - level)
if distance < smallestDistance
smallestDistance := distance
nearestEqualLevel := level
distanceToNearest := distance
// Pattern detection with filter
hasPattern := false
if maxEqualPeaks >= 2
if enableFilter
hasPattern := maxEqualPeaks == filterPeaks
else
hasPattern := true
// Screener outputs
patternSignal = hasPattern ? 1 : 0
numberEqualPeaks = maxEqualPeaks
nearestLevelPrice = nearestEqualLevel
distanceCents = distanceToNearest
// Calculate ATR normalized distance
atr = ta.atr(atrLength)
atrDistance = not na(distanceToNearest) and not na(atr) and atr > 0 ? distanceToNearest / atr : na
// Plot screener values
plot(patternSignal, title="Pattern Signal", display=display.data_window)
plot(numberEqualPeaks, title="Number Equal Peaks", display=display.data_window)
plot(nearestLevelPrice, title="Nearest Equal Level Price", display=display.data_window)
plot(distanceCents, title="Distance to Nearest (Price Units)", display=display.data_window)
plot(atrDistance, title="ATR Normalized Distance", display=display.data_window)
// Table for current symbol info
if barstate.islast
var table infoTable = table.new(position.top_right, 2, 6, bgcolor=color.new(color.black, 70))
table.cell(infoTable, 0, 0, "Symbol:", bgcolor=color.new(color.gray, 50), text_color=color.white)
table.cell(infoTable, 1, 0, syminfo.ticker, bgcolor=color.new(color.blue, 50), text_color=color.white)
table.cell(infoTable, 0, 1, "Pattern:", bgcolor=color.new(color.gray, 50), text_color=color.white)
table.cell(infoTable, 1, 1, str.tostring(patternSignal), bgcolor=patternSignal == 1 ? color.new(color.purple, 50) : color.new(color.gray, 50), text_color=color.white)
table.cell(infoTable, 0, 2, "Equal Peaks:", bgcolor=color.new(color.gray, 50), text_color=color.white)
table.cell(infoTable, 1, 2, str.tostring(numberEqualPeaks), bgcolor=color.new(color.yellow, 50), text_color=color.black)
table.cell(infoTable, 0, 3, "Nearest Level:", bgcolor=color.new(color.gray, 50), text_color=color.white)
table.cell(infoTable, 1, 3, str.tostring(nearestLevelPrice, "#.####"), bgcolor=color.new(color.orange, 50), text_color=color.white)
table.cell(infoTable, 0, 4, "Distance:", bgcolor=color.new(color.gray, 50), text_color=color.white)
table.cell(infoTable, 1, 4, str.tostring(distanceCents, "#.####"), bgcolor=color.new(color.green, 50), text_color=color.white)
table.cell(infoTable, 0, 5, "Filter Active:", bgcolor=color.new(color.gray, 50), text_color=color.white)
filterText = enableFilter ? str.tostring(filterPeaks) + " peaks" : "OFF"
table.cell(infoTable, 1, 5, filterText, bgcolor=enableFilter ? color.new(color.red, 50) : color.new(color.gray, 50), text_color=color.white)
indicator("Double Pattern Screener", shorttitle="DPS", overlay=false)
// Screener Inputs
leftBars = input.int(5, "Left Bars", minval=3, maxval=10)
rightBars = input.int(5, "Right Bars", minval=3, maxval=10)
tolerance = input.float(0.02, "Max Difference", step=0.01)
atrLength = input.int(14, "ATR Length", minval=1)
// NEW: Filter for number of equal peaks
filterPeaks = input.int(3, "Filter: Show Only X Equal Peaks", minval=2, maxval=5)
enableFilter = input.bool(true, "Enable Peak Count Filter")
// Arrays for tracking swings
var array<float> todaySwingLevels = array.new<float>(0)
var array<int> swingCounts = array.new<int>(0)
var array<bool> isResistance = array.new<bool>(0)
var array<int> swingBars = array.new<int>(0)
var int maxEqualPeaks = 0
var float nearestEqualLevel = na
var float distanceToNearest = na
var bool hasPattern = false
// Detect swings
swingHigh = ta.pivothigh(high, leftBars, rightBars)
swingLow = ta.pivotlow(low, leftBars, rightBars)
// Track current day
currentDay = dayofmonth
isNewDay = currentDay != currentDay[1]
// Clear on new day
if barstate.isfirst or isNewDay
array.clear(todaySwingLevels)
array.clear(swingCounts)
array.clear(isResistance)
array.clear(swingBars)
maxEqualPeaks := 0
nearestEqualLevel := na
distanceToNearest := na
hasPattern := false
// Function to find matching level
findMatchingLevel(newLevel, isHigh) =>
matchIndex = -1
if array.size(todaySwingLevels) > 0
for i = 0 to array.size(todaySwingLevels) - 1
existingLevel = array.get(todaySwingLevels, i)
existingIsResistance = array.get(isResistance, i)
if math.abs(newLevel - existingLevel) <= tolerance and existingIsResistance == isHigh
matchIndex := i
break
matchIndex
// Process swing highs
if not na(swingHigh)
matchIndex = findMatchingLevel(swingHigh, true)
if matchIndex >= 0
newCount = array.get(swingCounts, matchIndex) + 1
array.set(swingCounts, matchIndex, newCount)
// Update max equal peaks
if newCount > maxEqualPeaks
maxEqualPeaks := newCount
else
array.push(todaySwingLevels, swingHigh)
array.push(swingCounts, 1)
array.push(isResistance, true)
array.push(swingBars, bar_index)
// Process swing lows
if not na(swingLow)
matchIndex = findMatchingLevel(swingLow, false)
if matchIndex >= 0
newCount = array.get(swingCounts, matchIndex) + 1
array.set(swingCounts, matchIndex, newCount)
// Update max equal peaks
if newCount > maxEqualPeaks
maxEqualPeaks := newCount
else
array.push(todaySwingLevels, swingLow)
array.push(swingCounts, 1)
array.push(isResistance, false)
array.push(swingBars, bar_index)
// Remove broken levels
if array.size(todaySwingLevels) > 0
for i = array.size(todaySwingLevels) - 1 to 0
level = array.get(todaySwingLevels, i)
isRes = array.get(isResistance, i)
levelBroken = isRes ? close > level : close < level
if levelBroken
removedCount = array.get(swingCounts, i)
array.remove(todaySwingLevels, i)
array.remove(swingCounts, i)
array.remove(isResistance, i)
array.remove(swingBars, i)
// Recalculate max if we removed the highest count
if removedCount == maxEqualPeaks
maxEqualPeaks := 0
if array.size(swingCounts) > 0
for j = 0 to array.size(swingCounts) - 1
count = array.get(swingCounts, j)
if count > maxEqualPeaks
maxEqualPeaks := count
// Calculate nearest equal level and distance
nearestEqualLevel := na
distanceToNearest := na
smallestDistance = 999999.0
if array.size(todaySwingLevels) > 0
for i = 0 to array.size(todaySwingLevels) - 1
count = array.get(swingCounts, i)
level = array.get(todaySwingLevels, i)
// Only consider levels with 2+ touches
if count >= 2
distance = math.abs(close - level)
if distance < smallestDistance
smallestDistance := distance
nearestEqualLevel := level
distanceToNearest := distance
// Pattern detection with filter
hasPattern := false
if maxEqualPeaks >= 2
if enableFilter
hasPattern := maxEqualPeaks == filterPeaks
else
hasPattern := true
// Screener outputs
patternSignal = hasPattern ? 1 : 0
numberEqualPeaks = maxEqualPeaks
nearestLevelPrice = nearestEqualLevel
distanceCents = distanceToNearest
// Calculate ATR normalized distance
atr = ta.atr(atrLength)
atrDistance = not na(distanceToNearest) and not na(atr) and atr > 0 ? distanceToNearest / atr : na
// Plot screener values
plot(patternSignal, title="Pattern Signal", display=display.data_window)
plot(numberEqualPeaks, title="Number Equal Peaks", display=display.data_window)
plot(nearestLevelPrice, title="Nearest Equal Level Price", display=display.data_window)
plot(distanceCents, title="Distance to Nearest (Price Units)", display=display.data_window)
plot(atrDistance, title="ATR Normalized Distance", display=display.data_window)
// Table for current symbol info
if barstate.islast
var table infoTable = table.new(position.top_right, 2, 6, bgcolor=color.new(color.black, 70))
table.cell(infoTable, 0, 0, "Symbol:", bgcolor=color.new(color.gray, 50), text_color=color.white)
table.cell(infoTable, 1, 0, syminfo.ticker, bgcolor=color.new(color.blue, 50), text_color=color.white)
table.cell(infoTable, 0, 1, "Pattern:", bgcolor=color.new(color.gray, 50), text_color=color.white)
table.cell(infoTable, 1, 1, str.tostring(patternSignal), bgcolor=patternSignal == 1 ? color.new(color.purple, 50) : color.new(color.gray, 50), text_color=color.white)
table.cell(infoTable, 0, 2, "Equal Peaks:", bgcolor=color.new(color.gray, 50), text_color=color.white)
table.cell(infoTable, 1, 2, str.tostring(numberEqualPeaks), bgcolor=color.new(color.yellow, 50), text_color=color.black)
table.cell(infoTable, 0, 3, "Nearest Level:", bgcolor=color.new(color.gray, 50), text_color=color.white)
table.cell(infoTable, 1, 3, str.tostring(nearestLevelPrice, "#.####"), bgcolor=color.new(color.orange, 50), text_color=color.white)
table.cell(infoTable, 0, 4, "Distance:", bgcolor=color.new(color.gray, 50), text_color=color.white)
table.cell(infoTable, 1, 4, str.tostring(distanceCents, "#.####"), bgcolor=color.new(color.green, 50), text_color=color.white)
table.cell(infoTable, 0, 5, "Filter Active:", bgcolor=color.new(color.gray, 50), text_color=color.white)
filterText = enableFilter ? str.tostring(filterPeaks) + " peaks" : "OFF"
table.cell(infoTable, 1, 5, filterText, bgcolor=enableFilter ? color.new(color.red, 50) : color.new(color.gray, 50), text_color=color.white)
Open-source script
In true TradingView spirit, the creator of this script has made it open-source, so that traders can review and verify its functionality. Kudos to the author! While you can use it for free, remember that republishing the code is subject to our House Rules.
Disclaimer
The information and publications are not meant to be, and do not constitute, financial, investment, trading, or other types of advice or recommendations supplied or endorsed by TradingView. Read more in the Terms of Use.
Open-source script
In true TradingView spirit, the creator of this script has made it open-source, so that traders can review and verify its functionality. Kudos to the author! While you can use it for free, remember that republishing the code is subject to our House Rules.
Disclaimer
The information and publications are not meant to be, and do not constitute, financial, investment, trading, or other types of advice or recommendations supplied or endorsed by TradingView. Read more in the Terms of Use.