PROTECTED SOURCE SCRIPT
Updated

Sweep + BOS Alerts

43
//version=5
indicator("Sweep + BOS Alerts", overlay=true, shorttitle="SweepBOS")

// === User inputs ===
// Lookback length for pivot highs/lows. Higher values produce fewer swings/signals.
length = input.int(5, title="Pivot length", minval=1, maxval=50)
// Minimum relative wick size to qualify as a sweep (ratio of wick to body)
minWickMult = input.float(1.5, title="Min wick‑to‑body ratio", minval=0.0)
// Volume confirmation multiplier: volume must be at least this multiple of average volume
volMult = input.float(1.0, title="Volume multiple for BOS confirmation", minval=0.0)
// Maximum signals per month (to limit to ~5–7 as requested)
maxSignals = input.int(7, title="Max signals per month", minval=1, maxval=20)
// Only alert once per sweep/BOS pair
onlyFirst = input.bool(true, title="Only first BOS after sweep")

// === Helpers ===
// Identify pivot highs/lows using built‑in pivot functions
pivotHighPrice = ta.pivothigh(high, length, length)
pivotLowPrice = ta.pivotlow(low, length, length)

// Track the most recent swing high/low and their bar indices
var float lastSwingHigh = na
var float lastSwingLow = na
var int lastSwingHighBar = na
var int lastSwingLowBar = na

if not na(pivotHighPrice)
lastSwingHigh := pivotHighPrice
lastSwingHighBar := bar_index - length

if not na(pivotLowPrice)
lastSwingLow := pivotLowPrice
lastSwingLowBar := bar_index - length

// Calculate average volume for confirmation
avgVol = ta.sma(volume, 20)

// === Sweep detection ===
// Flags to signal a sweep occurred and BOS expected
var bool awaitingBearBOS = false
var bool awaitingBullBOS = false

// Check for sell sweep (buyside liquidity sweep)
// Condition: current high breaks previous swing high and closes back below the swing high with a long upper wick
bearSweep = false
if (not na(lastSwingHigh) and high > lastSwingHigh)
// compute candle components
bodySize = math.abs(close - open)
upperWick = high - math.max(open, close)
isLongUpperWick = bodySize > 0 ? upperWick / bodySize >= minWickMult : false
// price closes below the last swing high (reversion inside range)
closesInside = close < lastSwingHigh
bearSweep := isLongUpperWick and closesInside

// Check for buy sweep (sellside liquidity sweep)
bullSweep = false
if (not na(lastSwingLow) and low < lastSwingLow)
bodySize = math.abs(close - open)
lowerWick = math.min(open, close) - low
isLongLowerWick = bodySize > 0 ? lowerWick / bodySize >= minWickMult : false
closesInside = close > lastSwingLow
bullSweep := isLongLowerWick and closesInside

// When sweep occurs, set awaiting BOS flags
if bearSweep
awaitingBearBOS := true
awaitingBullBOS := false
if bullSweep
awaitingBullBOS := true
awaitingBearBOS := false

// === BOS detection ===
// Evaluate BOS only if a sweep has happened
autoSellSignal = false
autoBuySignal = false
if awaitingBearBOS
// Look for break of structure to downside: close lower than last swing low.
// Confirm with volume if needed: if average volume is zero (e.g. at start of data), accept any volume.
bool volOkDown = (avgVol == 0) or (volume >= volMult * avgVol)
if (not na(lastSwingLow) and close < lastSwingLow and volOkDown)
autoSellSignal := true
// If only first BOS should trigger, reset flag; otherwise keep awaiting further BOS
awaitingBearBOS := not onlyFirst

if awaitingBullBOS
// Look for break of structure to upside: close higher than last swing high.
bool volOkUp = (avgVol == 0) or (volume >= volMult * avgVol)
if (not na(lastSwingHigh) and close > lastSwingHigh and volOkUp)
autoBuySignal := true
awaitingBullBOS := not onlyFirst

// === Signal throttling per month ===
// Convert current date to month index (year*12 + month)
monthIndex = year * 12 + month
var int currentMonth = monthIndex
var int signalCount = 0
if monthIndex != currentMonth
currentMonth := monthIndex
signalCount := 0

// Limit number of signals per month
buyAllowed = autoBuySignal and (signalCount < maxSignals)
sellAllowed = autoSellSignal and (signalCount < maxSignals)

if buyAllowed or sellAllowed
signalCount += 1

// === Plotting signals ===
plotshape(buyAllowed, title="Buy Signal", style=shape.triangleup, location=location.belowbar, color=color.new(color.green, 0), size=size.tiny, text="BUY")
plotshape(sellAllowed, title="Sell Signal", style=shape.triangledown, location=location.abovebar, color=color.new(color.red, 0), size=size.tiny, text="SELL")

// Plot swing levels (optional for visual reference)
plot(lastSwingHigh, title="Swing High", color=color.gray, style=plot.style_linebr)
plot(lastSwingLow, title="Swing Low", color=color.gray, style=plot.style_linebr)

// === Alerts ===
// These alertconditions allow TradingView to trigger notifications
alertcondition(buyAllowed, title="Buy Alert", message="Sweep+BOS Buy signal on {{exchange}} {{ticker}} @ {{close}} on {{interval}}")
alertcondition(sellAllowed, title="Sell Alert", message="Sweep+BOS Sell signal on {{exchange}} {{ticker}} @ {{close}} on {{interval}}")

Release Notes
//version=5
indicator("Sweep + BOS Alerts", overlay=true, shorttitle="SweepBOS")

// === User inputs ===
length = input.int(5, title="Pivot length", minval=1, maxval=50)
minWickMult = input.float(1.5, title="Min wick‑to‑body ratio", minval=0.0)
volMult = input.float(1.0, title="Volume multiple", minval=0.0)
onlyFirst = input.bool(true, title="Only first BOS after sweep")
buyColor = input.color(color.green, title="Buy arrow color")
sellColor = input.color(color.red, title="Sell arrow color")
// Liquidity detection
liqLen = input.int(7, title='Liquidity Detection Length', minval=3, maxval=13, group='Liquidity')
liqMargin = input.float(6.9, title='Liquidity Margin', minval=4, maxval=9, step=0.1, group='Liquidity')
showLiqBuy = input.bool(true, title='Show Buyside Liquidity', group='Liquidity')
showLiqSell = input.bool(true, title='Show Sellside Liquidity', group='Liquidity')

// === Helpers ===
pivotHighPrice = ta.pivothigh(high, length, length)
pivotLowPrice = ta.pivotlow(low, length, length)

var float lastSwingHigh = na
var float lastSwingLow = na
if not na(pivotHighPrice)
lastSwingHigh := pivotHighPrice
if not na(pivotLowPrice)
lastSwingLow := pivotLowPrice

avgVol = ta.sma(volume, 20)

// === Sweep detection ===
var bool awaitingBearBOS = false
var bool awaitingBullBOS = false

bearSweep = false
if not na(lastSwingHigh) and high > lastSwingHigh
bodySize = math.abs(close - open)
upperWick = high - math.max(open, close)
isLongUpper = bodySize > 0 ? upperWick / bodySize >= minWickMult : false
closesInside = close < lastSwingHigh
bearSweep := isLongUpper and closesInside

bullSweep = false
if not na(lastSwingLow) and low < lastSwingLow
bodySize = math.abs(close - open)
lowerWick = math.min(open, close) - low
isLongLower = bodySize > 0 ? lowerWick / bodySize >= minWickMult : false
closesInside = close > lastSwingLow
bullSweep := isLongLower and closesInside

if bearSweep
awaitingBearBOS := true
awaitingBullBOS := false
if bullSweep
awaitingBullBOS := true
awaitingBearBOS := false

// === BOS detection ===
autoSellSignal = false
autoBuySignal = false
if awaitingBearBOS
bool volOkDown = (avgVol == 0) or (volume >= volMult * avgVol)
if not na(lastSwingLow) and close < lastSwingLow and volOkDown
autoSellSignal := true
awaitingBearBOS := not onlyFirst
if awaitingBullBOS
bool volOkUp = (avgVol == 0) or (volume >= volMult * avgVol)
if not na(lastSwingHigh) and close > lastSwingHigh and volOkUp
autoBuySignal := true
awaitingBullBOS := not onlyFirst

// === Signals without monthly limits ===
buyAllowed = autoBuySignal
sellAllowed = autoSellSignal

// === Liquidity detection ===
liqHigh = ta.pivothigh(high, liqLen, liqLen)
liqLow = ta.pivotlow (low, liqLen, liqLen)
var float[] liq_highs = array.new<float>()
var float[] liq_lows = array.new<float>()
if not na(liqHigh)
array.unshift(liq_highs, liqHigh)
if array.size(liq_highs) > 50
array.pop(liq_highs)
if not na(liqLow)
array.unshift(liq_lows, liqLow)
if array.size(liq_lows) > 50
array.pop(liq_lows)

liqATR = ta.atr(10)
liqDetect(level, prices) =>
int count = 0
float minVal = na
float maxVal = na
for i = 0 to array.size(prices) - 1
float p = prices.get(i)
if p < level + (liqATR/liqMargin) and p > level - (liqATR/liqMargin)
count += 1
minVal := na(minVal) ? p : math.min(minVal, p)
maxVal := na(maxVal) ? p : math.max(maxVal, p)
if count > 2
[minVal, maxVal]
else
[na, na]

var box[] liqBuyZones = array.new<box>()
var box[] liqSellZones = array.new<box>()

if bar_index > 0
if array.size(liq_highs) > 2 and showLiqBuy
[liMin, liMax] = liqDetect(liq_highs.get(0), liq_highs)
if not na(liMin)
bool updated = false
if array.size(liqBuyZones) > 0
for i = 0 to array.size(liqBuyZones) - 1
box b = liqBuyZones.get(i)
if close < b.get_top() and close > b.get_bottom()
b.set_top(liMax + (liqATR/liqMargin))
b.set_bottom(liMin - (liqATR/liqMargin))
b.set_right(bar_index)
updated := true
break
if not updated
box newBox = box.new(bar_index - liqLen, liMax + (liqATR/liqMargin),
bar_index + liqLen, liMin - (liqATR/liqMargin),
bgcolor=color.new(color.green,80), border_color=color.new(color.green,0))
array.unshift(liqBuyZones, newBox)
if array.size(liqBuyZones) > 5
box oldBox = array.pop(liqBuyZones)
oldBox.delete()
if array.size(liq_lows) > 2 and showLiqSell
[liMinS, liMaxS] = liqDetect(liq_lows.get(0), liq_lows)
if not na(liMinS)
bool updatedS = false
if array.size(liqSellZones) > 0
for i = 0 to array.size(liqSellZones) - 1
box b = liqSellZones.get(i)
if close < b.get_top() and close > b.get_bottom()
b.set_top(liMaxS + (liqATR/liqMargin))
b.set_bottom(liMinS - (liqATR/liqMargin))
b.set_right(bar_index)
updatedS := true
break
if not updatedS
box newBoxS = box.new(bar_index - liqLen, liMaxS + (liqATR/liqMargin),
bar_index + liqLen, liMinS - (liqATR/liqMargin),
bgcolor=color.new(color.red,80), border_color=color.new(color.red,0))
array.unshift(liqSellZones, newBoxS)
if array.size(liqSellZones) > 5
box oldBoxS = array.pop(liqSellZones)
oldBoxS.delete()

// === Plotting ===
// Usa size.normal para flechas más grandes; puedes cambiarlo a size.small, size.large, etc.
plotshape(buyAllowed, title="Buy Signal", style=shape.arrowup, location=location.belowbar, color=buyColor, size=size.normal)
plotshape(sellAllowed, title="Sell Signal", style=shape.arrowdown, location=location.abovebar, color=sellColor, size=size.normal)

// Swing levels
plot(lastSwingHigh, title="Swing High", color=color.gray, style=plot.style_linebr)
plot(lastSwingLow, title="Swing Low", color=color.gray, style=plot.style_linebr)

// Alertas
alertcondition(buyAllowed, title="Buy Alert", message="Sweep+BOS Buy signal on {{ticker}} @ {{close}}")
alertcondition(sellAllowed, title="Sell Alert", message="Sweep+BOS Sell signal on {{ticker}} @ {{close}}")

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.