Liquidity Sweeps and Break of Structure (BOS) in Pine Script: SMC Decoded
Detect liquidity sweeps and break-of-structure (BOS) signals in Pine Script — full code, ICT logic explained, and ready-to-trade alerts.
ICT traders talk about liquidity like it is a physical thing. Pools of stops above the highs, pools of stops below the lows, and a market that hunts both before it picks a direction. Most retail traders see this language and dismiss it as jargon. The mechanics, however, are simple enough to encode in Pine Script in under 80 lines.
This guide walks through the two most useful concepts in the smart money toolkit: liquidity sweeps and break of structure (BOS). You will get the detection logic for each, working code, and a combined strategy filter that only fires when both align.
What Liquidity Actually Is
Liquidity in trading is a pool of pending stop orders. When price has formed an obvious swing high, every breakout trader who bought near it placed a stop just below — and every short seller targeting the level placed a stop just above. That cluster of stops is liquidity. It exists at exactly the levels every chart reader notices: equal highs, equal lows, recent swing points.
A liquidity sweep is when price spikes through that pool, triggers the stops, and then reverses. The wick stays beyond the level; the body closes back inside. That single candle is the institutional fingerprint — somebody large used the stop cluster as fuel to fill an opposite-direction order.
The fingerprint is detectable in code. Three conditions:
- The candle's high (or low) exceeds the prior swing high (or low)
- The candle's body closes back inside the prior range
- The wick is meaningfully larger than the body
If those three line up on the candle that just closed, you have a sweep.
What Break of Structure Means
Market structure is the sequence of swing highs and lows. In an uptrend, each swing high is higher than the last; each swing low is higher than the last. The trend is intact as long as that structure holds.
A break of structure (BOS) is the moment that pattern fails. In an uptrend, BOS occurs when price closes below the most recent swing low — the structure inverts. In a downtrend, BOS is a close above the most recent swing high.
BOS by itself is not enough to enter a trade. Random ranges produce false BOS signals constantly. The high-probability version is BOS that follows a liquidity sweep. The sweep clears the stops; the BOS confirms the new direction.
Detecting Equal Highs and Lows
Before you can detect sweeps, you need to identify the liquidity pools. The simplest version: track recent swing highs and swing lows that are close to each other.
//@version=6
indicator("Liquidity Pools", overlay=true)
length = input.int(10, "Pivot lookback", minval=3)
tolerance = input.float(0.001, "Equal-level tolerance (%)", minval=0)
ph = ta.pivothigh(high, length, length)
pl = ta.pivotlow(low, length, length)
var float lastHigh = na
var float lastLow = na
var line eqhLine = na
var line eqlLine = na
if not na(ph)
if not na(lastHigh) and math.abs(ph - lastHigh) / lastHigh < tolerance
line.delete(eqhLine)
eqhLine := line.new(bar_index[length], ph, bar_index, ph,
color=color.new(color.red, 30), width=2)
lastHigh := ph
if not na(pl)
if not na(lastLow) and math.abs(pl - lastLow) / lastLow < tolerance
line.delete(eqlLine)
eqlLine := line.new(bar_index[length], pl, bar_index, pl,
color=color.new(color.green, 30), width=2)
lastLow := plThis script draws a red line at every pair of equal highs and a green line at every pair of equal lows. The tolerance input controls how close two levels must be to count as equal — 0.001 is 0.1% which works for most timeframes.

Sweep Detection in Pine Script
With liquidity pools identified, sweep detection becomes straightforward. Watch for a candle that pierces the pool with its wick but closes back inside.
//@version=6
indicator("Liquidity Sweep Detector", overlay=true, max_labels_count=100)
length = input.int(10, "Pivot lookback", minval=3)
wickRatio = input.float(0.6, "Min wick-to-body ratio", minval=0.3, step=0.1)
ph = ta.pivothigh(high, length, length)
pl = ta.pivotlow(low, length, length)
var float liqHigh = na
var float liqLow = na
if not na(ph)
liqHigh := ph
if not na(pl)
liqLow := pl
bodySize = math.abs(close - open)
upperWick = high - math.max(close, open)
lowerWick = math.min(close, open) - low
bullSweep = not na(liqLow) and low < liqLow and close > liqLow
and lowerWick > bodySize * wickRatio
bearSweep = not na(liqHigh) and high > liqHigh and close < liqHigh
and upperWick > bodySize * wickRatio
if bullSweep
label.new(bar_index, low, "SWEEP", style=label.style_label_up,
color=color.new(color.green, 0), textcolor=color.white, size=size.small)
if bearSweep
label.new(bar_index, high, "SWEEP", style=label.style_label_down,
color=color.new(color.red, 0), textcolor=color.white, size=size.small)
alertcondition(bullSweep, "Bullish Liquidity Sweep")
alertcondition(bearSweep, "Bearish Liquidity Sweep")The script labels every sweep on the chart and fires alerts. A bullish sweep takes out a liquidity low and closes back above it — typically followed by an up move. A bearish sweep takes out a liquidity high and closes back below — typically followed by a down move.
The wickRatio input filters out indecision candles. A sweep should have a long wick relative to the body; if the wick is small, the move was not really a stop run.
BOS Detection With Displacement
Break of structure detection needs a structural reference point. The simplest version: track the most recent swing high and swing low, then watch for a strong close beyond either.
//@version=6
indicator("BOS Detector", overlay=true)
length = input.int(10, "Pivot lookback")
displacement = input.float(0.5, "Displacement (ATR multiple)", minval=0.2, step=0.1)
ph = ta.pivothigh(high, length, length)
pl = ta.pivotlow(low, length, length)
atrValue = ta.atr(14)
var float swingHigh = na
var float swingLow = na
if not na(ph)
swingHigh := ph
if not na(pl)
swingLow := pl
bullBOS = not na(swingHigh) and close > swingHigh
and (close - swingHigh) > atrValue * displacement
bearBOS = not na(swingLow) and close < swingLow
and (swingLow - close) > atrValue * displacement
if bullBOS
line.new(bar_index[1], swingHigh, bar_index, swingHigh,
color=color.green, width=2)
label.new(bar_index, high, "BOS", style=label.style_label_down,
color=color.green, textcolor=color.white, size=size.small)
swingHigh := na
if bearBOS
line.new(bar_index[1], swingLow, bar_index, swingLow,
color=color.red, width=2)
label.new(bar_index, low, "BOS", style=label.style_label_up,
color=color.red, textcolor=color.white, size=size.small)
swingLow := na
alertcondition(bullBOS, "Bullish Break of Structure")
alertcondition(bearBOS, "Bearish Break of Structure")The displacement input filters out marginal breaks. A real BOS pushes through the level with conviction — the close must exceed the broken level by at least 0.5 ATR by default. Tighten this to require stronger displacement; loosen it for choppier markets.
Once a BOS is confirmed, the script invalidates that swing point (swingHigh := na). The next pivot becomes the new structural reference. This prevents the same level from triggering BOS multiple times.
Combining Sweep and BOS for High-Probability Entries
A liquidity sweep without BOS is just a wick. A BOS without a sweep is often a false break. Together, the two confirm a directional shift driven by institutional flow.
The pattern: a bullish sweep sets up a bullish BOS. Price wicks below the recent low, closes back above, and on the next several bars breaks above the recent high. That sequence — sweep then BOS — is the cleanest version of an SMC trend reversal.
// Combined signal — fires only when sweep is followed by BOS within N bars
sweepLookback = input.int(10, "Bars to confirm BOS after sweep", minval=2)
bullSetup = ta.barssince(bullSweep) <= sweepLookback and bullBOS
bearSetup = ta.barssince(bearSweep) <= sweepLookback and bearBOS
alertcondition(bullSetup, "Bullish SMC Setup",
"Bullish liquidity sweep confirmed by BOS")
alertcondition(bearSetup, "Bearish SMC Setup",
"Bearish liquidity sweep confirmed by BOS")This filter dramatically reduces noise. On most charts you will see far fewer signals than with sweep or BOS alone — but the ones that fire have institutional context behind them.

Going From Indicator to Strategy
The scripts above paint signals on the chart. To backtest a sweep + BOS strategy, you need to convert the logic into a strategy script: define the entry on confirmed setup, set a stop loss below the sweep wick (long) or above the sweep wick (short), and define a take profit at the next opposing structural level.
The full conversion process — turning any indicator into a backtestable strategy — is documented in the indicator-to-strategy conversion guide.
Common Mistakes With SMC Signals
Three errors show up constantly when traders code SMC concepts.
- Chasing every sweep. Not every wick beyond a high is a true sweep. Without the wick-to-body filter, the script fires on every indecision candle. The 0.6 wick ratio default is conservative — never go below 0.4.
- Ignoring higher-timeframe context. A sweep + BOS on the 5-minute chart against a daily downtrend is a counter-trend setup that fails most of the time. Always check structure on a higher timeframe.
- Treating CHoCH as identical to BOS. Change of character (CHoCH) is the first BOS in the opposite direction — a stronger signal because it indicates trend reversal, not just continuation. The simple BOS detector above does not distinguish between them. To separate, track the prior trend direction and label any opposite-direction BOS as CHoCH.
Wiring Alerts to Live Trading
The scripts above use alertcondition() for compatibility with TradingView's built-in alert system. To send the signal to a webhook URL — for live broker automation — switch to alert() inside an if block:
if bullSetup
alert('{"action": "buy", "ticker": "{{ticker}}", "price": {{close}}}',
alert.freq_once_per_bar_close)The full webhook setup, including JSON payload templates by broker, is covered in the Pine Script webhook alerts guide.
Build a Custom SMC Indicator With PineWiz
The detectors above are starting points. Most SMC traders end up wanting variations: a sweep filter that only counts sweeps near session opens, a BOS with multi-timeframe confluence, or a combined setup that also requires order block confluence at the entry candle.
Each variant is another 30-50 lines of Pine Script and another debugging cycle. The faster path: describe what you want in PineWiz. "Liquidity sweep plus BOS, but only fire on the 1-hour during London session, and require price to be inside a daily order block." You get a working script in under a minute.
The same applies to CHoCH detection, multi-timeframe SMC stacks, and any SMC strategy that combines two or three concepts. Skip the manual coding entirely.
PineWiz Team
The PineWiz team specializes in Pine Script and algorithmic trading. We build AI tools that help retail traders turn their ideas into production-ready TradingView strategies and indicators — no coding required.
Ready to bring your idea to life?
Turn your trading ideas into Pine Script code without writing a single line.
Start Building