本页源码来自 TradingView 公开发布的开源脚本,版权归原作者所有, 请遵循其原始许可(Pine 脚本常见 CC BY-NC-SA / MPL-2.0 / MIT)。 本项目仅用于研究检索与许可范围内的移植。
// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © AlgoAlpha
//@version=6
indicator("Support and Resistance Retest Breakout Signals [AlgoAlpha]", "AlgoAlpha - SR Retest Breakout", overlay = true, max_boxes_count = 500, max_lines_count = 500, max_labels_count = 500, max_bars_back = 5000)
import TradingView/ta/12
// Inputs
zoneGroup = "S/R Zone Settings"
timeHorizon = input.int(7, "Time Horizon", minval = 1, group = zoneGroup, tooltip = "Controls how many bars are used to confirm swing points. Higher values create fewer, broader zones. Lower values create more responsive zones.")
volatilityLookback = input.int(40, "Zone Match Lookback", minval = 1, group = zoneGroup, tooltip = "Number of bars used to measure normal price variation for matching nearby swing highs or lows.")
zoneTolerance = input.float(1.0, "Zone Match Tolerance", minval = 0.01, step = 0.05, group = zoneGroup, tooltip = "Controls how close two same-side swing points must be to form a zone. Higher values allow wider matches.")
swingMemory = input.int(500, "Swing Memory", minval = 10, maxval = 500, group = zoneGroup, tooltip = "Maximum number of internal swing points stored for zone detection.")
volumeGroup = "Volume Balance"
showDeltaBars = input.bool(true, "Show Buy/Sell Balance", group = volumeGroup, tooltip = "Shows green and red volume balance bars inside each support/resistance zone.")
ltf_ = input.timeframe("5", "Volume Timeframe", group = volumeGroup, tooltip = "Lower timeframe used to estimate buying and selling volume inside each zone.")
deltaBarTransparency = input.int(60, "Balance Bar Transparency", minval = 0, maxval = 100, group = volumeGroup, tooltip = "Transparency of the internal buy/sell volume balance bars.")
deltaLookbackLimit = input.int(500, "Volume Scan Limit", minval = 1, maxval = 5000, group = volumeGroup, tooltip = "Maximum number of bars scanned when calculating the zone's buy/sell balance between the two swing points.")
managementGroup = "Zone Management"
preventOverlap = input.bool(true, "Avoid Overlapping Zones", group = managementGroup, tooltip = "When enabled, new zones are skipped if they overlap an existing zone in both price and time.")
expiryBars = input.int(500, "Zone Expiration", minval = 1, group = managementGroup, tooltip = "Number of bars after which unfinished zones are removed from the chart.")
maxZones = input.int(150, "Maximum Active Zones", minval = 1, maxval = 160, group = managementGroup, tooltip = "Maximum number of zones kept on the chart. Each zone uses multiple boxes, so this limit is capped to stay within TradingView object limits.")
maxSignalLabels = input.int(250, "Signal Label Limit", minval = 1, maxval = 500, group = managementGroup, tooltip = "Maximum number of retest signal labels kept on the chart.")
appearanceGroup = "Appearance"
zoneFillTransparency = input.int(85, "Zone Fill Transparency", minval = 0, maxval = 100, group = appearanceGroup, tooltip = "Transparency of the main support/resistance zone fill.")
showMidline = input.bool(true, "Show Center Line", group = appearanceGroup, tooltip = "Shows a dotted center line inside each zone. This is visual only and is not used for signal logic.")
showRetestSignals = input.bool(true, "Show Retest Signals", group = appearanceGroup, tooltip = "Shows bullish and bearish labels after price cleanly breaks through a zone and retests it.")
green = input.color(#00ffbb, "Colors", inline = "colors", group = appearanceGroup)
red = input.color(#ff1100, "", inline = "colors", group = appearanceGroup, tooltip = "Bullish and bearish colors used for retest labels and volume balance bars.")
// Types
type PivotPoint
int idx
float price
float o
float h
float l
float c
// Volume
[upv, downv, deltav] = ta.requestUpAndDownVolume(ltf_)
// Helpers
edgeDistance(bool isResistance, float edge, float val) =>
isResistance ? edge - val : val - edge
includeDistance(float currentMax, bool isResistance, float edge, float val, float tolerance) =>
float d = edgeDistance(isResistance, edge, val)
d >= 0.0 and d <= tolerance ? math.max(currentMax, d) : currentMax
zoneThickness(PivotPoint p1, PivotPoint p2, bool isResistance, float tolerance) =>
float edge = isResistance ? math.max(p1.h, p2.h) : math.min(p1.l, p2.l)
float maxDist = 0.0
maxDist := includeDistance(maxDist, isResistance, edge, p1.o, tolerance)
maxDist := includeDistance(maxDist, isResistance, edge, p1.h, tolerance)
maxDist := includeDistance(maxDist, isResistance, edge, p1.l, tolerance)
maxDist := includeDistance(maxDist, isResistance, edge, p1.c, tolerance)
maxDist := includeDistance(maxDist, isResistance, edge, p2.o, tolerance)
maxDist := includeDistance(maxDist, isResistance, edge, p2.h, tolerance)
maxDist := includeDistance(maxDist, isResistance, edge, p2.l, tolerance)
maxDist := includeDistance(maxDist, isResistance, edge, p2.c, tolerance)
math.max(maxDist, syminfo.mintick)
zoneBounds(PivotPoint p1, PivotPoint p2, bool isResistance, float tolerance) =>
float edge = isResistance ? math.max(p1.h, p2.h) : math.min(p1.l, p2.l)
float thickness = zoneThickness(p1, p2, isResistance, tolerance)
float top = isResistance ? edge : edge + thickness
float bottom = isResistance ? edge - thickness : edge
[math.max(top, bottom), math.min(top, bottom)]
rectsOverlap(int aLeft, int aRight, float aTop, float aBottom, int bLeft, int bRight, float bTop, float bBottom) =>
bool timeOverlap = aLeft <= bRight and aRight >= bLeft
bool priceOverlap = aBottom <= bTop and aTop >= bBottom
timeOverlap and priceOverlap
formationVolume(int x1, int x2, int maxBars) =>
int leftX = math.min(x1, x2)
int rightX = math.max(x1, x2)
int fromBack = math.max(bar_index - rightX, 0)
int toBack = math.min(bar_index - leftX, maxBars)
float upSum = 0.0
float downSum = 0.0
if toBack >= fromBack
for k = fromBack to toBack
upSum += nz(upv[k])
downSum += math.abs(nz(downv[k]))
[math.max(upSum, 0.0000001), math.max(downSum, 0.0000001)]
// Zigzag
ph = ta.pivothigh(high, timeHorizon, timeHorizon)
pl = ta.pivotlow(low, timeHorizon, timeHorizon)
tolerance = ta.stdev(close, volatilityLookback) * zoneTolerance
var int lasttype = 0
var PivotPoint[] pivh = array.new<PivotPoint>()
var PivotPoint[] pivl = array.new<PivotPoint>()
var PivotPoint[] zzHighs = array.new<PivotPoint>()
var PivotPoint[] zzLows = array.new<PivotPoint>()
// Zones
var box[] zoneBoxes = array.new_box()
var box[] zoneBullVolBoxes = array.new_box()
var box[] zoneBearVolBoxes = array.new_box()
var line[] zoneMids = array.new_line()
var float[] zoneTops = array.new_float()
var float[] zoneBottoms = array.new_float()
var float[] zoneUpVolData = array.new_float()
var float[] zoneDownVolData = array.new_float()
var int[] zoneStarts = array.new_int()
var int[] zoneCreatedBars = array.new_int()
var int[] zoneStates = array.new_int()
var int[] zoneBreakBars = array.new_int()
var label[] signalLabels = array.new_label()
bool newResistanceZone = false
bool newSupportZone = false
bool bullishRetestSignal = false
bool bearishRetestSignal = false
// Zone helpers
f_overlaps_existing(int leftX, int rightX, float zoneTop, float zoneBottom) =>
bool overlapsExisting = false
if preventOverlap and zoneBoxes.size() > 0
for j = 0 to zoneBoxes.size() - 1
box existingBox = zoneBoxes.get(j)
int existingLeft = box.get_left(existingBox)
int existingRight = box.get_right(existingBox)
float existingTop = box.get_top(existingBox)
float existingBottom = box.get_bottom(existingBox)
if rectsOverlap(leftX, rightX, zoneTop, zoneBottom, existingLeft, existingRight, existingTop, existingBottom)
overlapsExisting := true
break
overlapsExisting
f_update_delta_boxes(int i) =>
if zoneBoxes.size() > i and zoneBullVolBoxes.size() > i and zoneBearVolBoxes.size() > i and zoneUpVolData.size() > i and zoneDownVolData.size() > i
box zBox = zoneBoxes.get(i)
box bullBox = zoneBullVolBoxes.get(i)
box bearBox = zoneBearVolBoxes.get(i)
int leftX = box.get_left(zBox)
int rightX = math.max(box.get_right(zBox), leftX + 1)
float top = box.get_top(zBox)
float bottom = box.get_bottom(zBox)
float midY = math.avg(top, bottom)
int midX = int(math.avg(leftX, rightX))
int span = math.max(midX - leftX, 1)
float upVol = math.max(zoneUpVolData.get(i), 0.0000001)
float downVol = math.max(zoneDownVolData.get(i), 0.0000001)
float ratio = upVol / downVol
box.set_left(bearBox, leftX)
box.set_top(bearBox, top)
box.set_bottom(bearBox, midY)
box.set_bgcolor(bearBox, showDeltaBars ? color.new(red, deltaBarTransparency) : color.new(red, 100))
box.set_border_color(bearBox, color.new(red, 100))
box.set_left(bullBox, leftX)
box.set_top(bullBox, midY)
box.set_bottom(bullBox, bottom)
box.set_bgcolor(bullBox, showDeltaBars ? color.new(green, deltaBarTransparency) : color.new(green, 100))
box.set_border_color(bullBox, color.new(green, 100))
if ratio > 1
box.set_right(bullBox, midX)
box.set_right(bearBox, int(leftX + span / ratio))
else
box.set_right(bullBox, int(leftX + span * ratio))
box.set_right(bearBox, midX)
f_delete_zone(int i) =>
zoneBoxes.remove(i).delete()
zoneBullVolBoxes.remove(i).delete()
zoneBearVolBoxes.remove(i).delete()
zoneMids.remove(i).delete()
zoneTops.remove(i)
zoneBottoms.remove(i)
zoneUpVolData.remove(i)
zoneDownVolData.remove(i)
zoneStarts.remove(i)
zoneCreatedBars.remove(i)
zoneStates.remove(i)
zoneBreakBars.remove(i)
f_new_zone(int leftX, int rightX, float zoneTop, float zoneBottom, float upVol, float downVol) =>
bool created = false
if zoneTop > zoneBottom and not f_overlaps_existing(leftX, rightX, zoneTop, zoneBottom)
float mid = math.avg(zoneTop, zoneBottom)
box zBox = box.new(left = leftX, top = zoneTop, right = rightX, bottom = zoneBottom, xloc = xloc.bar_index, border_color = color.new(chart.fg_color, 100), border_width = 0, border_style = line.style_solid, bgcolor = color.new(chart.fg_color, zoneFillTransparency))
box bearVolBox = box.new(left = leftX, top = zoneTop, right = rightX, bottom = mid, xloc = xloc.bar_index, border_color = color.new(red, 100), bgcolor = showDeltaBars ? color.new(red, deltaBarTransparency) : color.new(red, 100))
box bullVolBox = box.new(left = leftX, top = mid, right = rightX, bottom = zoneBottom, xloc = xloc.bar_index, border_color = color.new(green, 100), bgcolor = showDeltaBars ? color.new(green, deltaBarTransparency) : color.new(green, 100))
line zMid = line.new(x1 = leftX, y1 = mid, x2 = rightX, y2 = mid, xloc = xloc.bar_index, color = showMidline ? color.new(chart.fg_color, 0) : color.new(chart.fg_color, 100), style = line.style_dotted, width = 1)
zoneBoxes.unshift(zBox)
zoneBullVolBoxes.unshift(bullVolBox)
zoneBearVolBoxes.unshift(bearVolBox)
zoneMids.unshift(zMid)
zoneTops.unshift(zoneTop)
zoneBottoms.unshift(zoneBottom)
zoneUpVolData.unshift(upVol)
zoneDownVolData.unshift(downVol)
zoneStarts.unshift(leftX)
zoneCreatedBars.unshift(bar_index)
zoneStates.unshift(0)
zoneBreakBars.unshift(na)
f_update_delta_boxes(0)
created := true
created
// Pivot updates
if not na(ph)
PivotPoint hp = PivotPoint.new(bar_index - timeHorizon, ph, open[timeHorizon], high[timeHorizon], low[timeHorizon], close[timeHorizon])
if lasttype != 1 or pivh.size() == 0
pivh.unshift(hp)
else
PivotPoint activeHigh = pivh.first()
if ph > activeHigh.price
pivh.unshift(hp)
lasttype := 1
if not na(pl)
PivotPoint lp = PivotPoint.new(bar_index - timeHorizon, pl, open[timeHorizon], high[timeHorizon], low[timeHorizon], close[timeHorizon])
if lasttype != -1 or pivl.size() == 0
pivl.unshift(lp)
else
PivotPoint activeLow = pivl.first()
if pl < activeLow.price
pivl.unshift(lp)
lasttype := -1
// Zone creation
if lasttype != lasttype[1] and pivh.size() > 0 and pivl.size() > 0
if lasttype == 1
PivotPoint confirmedLow = pivl.first()
zzLows.unshift(confirmedLow)
if zzLows.size() >= 2 and not na(tolerance)
PivotPoint low2 = zzLows.get(0)
PivotPoint low1 = zzLows.get(1)
bool lowsWithinTolerance = math.abs(low2.price - low1.price) <= tolerance
if lowsWithinTolerance
[zoneTop, zoneBottom] = zoneBounds(low1, low2, false, tolerance)
int leftX = math.min(low1.idx, low2.idx)
int rightX = bar_index + 1
[upVol, downVol] = formationVolume(low1.idx, low2.idx, deltaLookbackLimit)
if f_new_zone(leftX, rightX, zoneTop, zoneBottom, upVol, downVol)
newSupportZone := true
if lasttype == -1
PivotPoint confirmedHigh = pivh.first()
zzHighs.unshift(confirmedHigh)
if zzHighs.size() >= 2 and not na(tolerance)
PivotPoint high2 = zzHighs.get(0)
PivotPoint high1 = zzHighs.get(1)
bool highsWithinTolerance = math.abs(high2.price - high1.price) <= tolerance
if highsWithinTolerance
[zoneTop, zoneBottom] = zoneBounds(high1, high2, true, tolerance)
int leftX = math.min(high1.idx, high2.idx)
int rightX = bar_index + 1
[upVol, downVol] = formationVolume(high1.idx, high2.idx, deltaLookbackLimit)
if f_new_zone(leftX, rightX, zoneTop, zoneBottom, upVol, downVol)
newResistanceZone := true
// Pivot memory
while pivh.size() > swingMemory
pivh.pop()
while pivl.size() > swingMemory
pivl.pop()
while zzHighs.size() > swingMemory
zzHighs.pop()
while zzLows.size() > swingMemory
zzLows.pop()
// Zone limit
while zoneBoxes.size() > maxZones
f_delete_zone(zoneBoxes.size() - 1)
// Zone management
if zoneBoxes.size() > 0
for i = zoneBoxes.size() - 1 to 0
if i < zoneBoxes.size()
box zBox = zoneBoxes.get(i)
line zMid = zoneMids.get(i)
float zTop = zoneTops.get(i)
float zBottom = zoneBottoms.get(i)
int zStart = zoneStarts.get(i)
int zState = zoneStates.get(i)
int zBreakBar = zoneBreakBars.get(i)
int age = bar_index - zStart
bool expired = age > expiryBars
if expired
f_delete_zone(i)
else
box.set_bgcolor(zBox, color.new(chart.fg_color, zoneFillTransparency))
box.set_border_color(zBox, color.new(chart.fg_color, 100))
line.set_color(zMid, showMidline ? color.new(chart.fg_color, 0) : color.new(chart.fg_color, 100))
bool zoneFrozen = math.abs(zState) == 3
bool wholeBelow = high < zBottom
bool wholeAbove = low > zTop
bool closeAbove = close > zTop
bool closeBelow = close < zBottom
bool touchedZone = high >= zBottom and low <= zTop
if not zoneFrozen
box.set_right(zBox, bar_index + 1)
line.set_x2(zMid, bar_index + 1)
if zState == 0
if wholeBelow
zState := -1
zBreakBar := na
else if wholeAbove
zState := 1
zBreakBar := na
else if zState == -1
if closeAbove
zState := 2
zBreakBar := bar_index
else if wholeAbove
zState := 1
zBreakBar := na
else if zState == 1
if closeBelow
zState := -2
zBreakBar := bar_index
else if wholeBelow
zState := -1
zBreakBar := na
else if zState == 2
bool bullishRetest = not na(zBreakBar) and bar_index > zBreakBar and touchedZone and close >= zBottom
if bullishRetest
bullishRetestSignal := true
zState := 3
box.set_right(zBox, bar_index)
line.set_x2(zMid, bar_index)
if showRetestSignals
label sig = label.new(x = bar_index, y = zBottom, text = "▲", xloc = xloc.bar_index, yloc = yloc.price, color = color.new(green, 65), style = label.style_label_up, textcolor = color.white, size = size.small)
signalLabels.unshift(sig)
else if closeBelow
zState := wholeBelow ? -1 : 0
zBreakBar := na
else if zState == -2
bool bearishRetest = not na(zBreakBar) and bar_index > zBreakBar and touchedZone and close <= zTop
if bearishRetest
bearishRetestSignal := true
zState := -3
box.set_right(zBox, bar_index)
line.set_x2(zMid, bar_index)
if showRetestSignals
label sig = label.new(x = bar_index, y = zTop, text = "▼", xloc = xloc.bar_index, yloc = yloc.price, color = color.new(red, 65), style = label.style_label_down, textcolor = color.white, size = size.small)
signalLabels.unshift(sig)
else if closeAbove
zState := wholeAbove ? 1 : 0
zBreakBar := na
zoneStates.set(i, zState)
zoneBreakBars.set(i, zBreakBar)
f_update_delta_boxes(i)
// Label memory
while signalLabels.size() > maxSignalLabels
label oldSig = signalLabels.pop()
label.delete(oldSig)
// Alerts
alertcondition(newResistanceZone, "New Resistance Zone", "A new resistance zone was detected from two matching swing highs.")
alertcondition(newSupportZone, "New Support Zone", "A new support zone was detected from two matching swing lows.")
alertcondition(bullishRetestSignal, "Bullish Zone Retest", "Price was fully below the zone, broke above it, and retested into the zone.")
alertcondition(bearishRetestSignal, "Bearish Zone Retest", "Price was fully above the zone, broke below it, and retested into the zone.")