Support and Resistance Retest Breakout Signals [AlgoAlpha]

AlgoAlpha · study · 375 行 · 点赞 3,386 · TradingView 原页

本页源码来自 TradingView 公开发布的开源脚本,版权归原作者所有, 请遵循其原始许可(Pine 脚本常见 CC BY-NC-SA / MPL-2.0 / MIT)。 本项目仅用于研究检索与许可范围内的移植。

Pine Script

// 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.")

← 返回列表