Bollinger Bands Strategy on TradingView: Breakout Rules
A Bollinger Bands breakout strategy buys a confirmed close crossing above the upper band or sells a close crossing below the lower band. This example requires a recent squeeze and optional volume and trend filters, then exits through an ATR stop, a midline close, or a fixed target. It tests continuation; a mean reversion strategy takes the opposite premise. The complete Pine Script v6 example below is educational and has no measured performance results.
Define the squeeze, breakout and exit separately
Bollinger Bands use a moving average and standard deviation bands. A band touch alone does not establish a reversal: TradingView describes how price can walk an outer band during a trend. This page tests continuation after compression. For rules that trade a return toward the average, use the mean reversion strategy guide. These examples are not investment advice.
Compression is a state, not a direction signal
The default squeeze requires both Bollinger Bands to sit inside a Keltner-style envelope: a 20-bar EMA plus or minus 1.5 times 20-bar ATR. This is the formula used here; other Keltner implementations can use different range smoothing. The alternative ranks normalized BandWidth over 100 bars and accepts a percentile rank at or below 20. These are adjustable test settings, not established optimal values.
A squeeze must have occurred on one of the preceding five bars. The breakout bar cannot qualify itself, but the squeeze can still be active on that bar. The script does not require a separate squeeze-release event. Selecting None removes this gate. Purple shading and dots show compression, not orders.
Require a new close crossing, then check the filters
A long trigger requires the previous close to be at or below the previous upper band and the current close above the current upper band. Shorts mirror this at the lower band. Price staying outside a band is a state, not another crossover. Both bands include the current close. Entry conditions use barstate.isconfirmed; a wick outside the band is insufficient.
With default filters enabled, volume must exceed 1.25 times its 20-bar SMA, including the current bar. Longs must close above the 200-bar EMA and shorts below it. Missing volume blocks entries when its filter is enabled. Volume does not identify institutional participants, and these filters do not eliminate failed breakouts. Entries occur only while flat, with no automatic reversal.
Choose what ends the trade
The default Hybrid mode attaches a stop using signal-bar ATR times 2, rounded to ticks, measured from the simulated entry fill. It also requests a market exit when a long closes below the basis or a short above it. Midline close mode has no protective stop. Fixed Risk/Reward mode uses the same stop distance and a target at twice that distance, with no midline exit.
The midline can move in either direction, so this is not a ratcheting trailing stop. A midline exit can realize a loss. Stop and bracket orders are submitted with the entry; they do not wait until the entry bar closes. Gaps and slippage can produce a loss beyond the stop distance. The 20% equity order size is position allocation, not 20% risk.
Read the signal and fill as separate events
Under the supplied settings, market entries and midline exits normally fill at the following bar open. Review TradingView's strategy execution and cost documentation before changing those settings. This example uses chart-timeframe data without future references; close confirmation does not guarantee identical results after data revisions or changes to chart history.
Explore the TradingView strategies hub or compare a breakout and retest strategy that waits for a pullback after the initial break.
| Component | Description | Role |
|---|---|---|
| Bollinger Bands | 20-bar SMA plus or minus 2 standard deviations by default. | Defines the boundary used by fresh close crossings. |
| Recent squeeze | BB inside an EMA/ATR envelope, or low BandWidth percentile. | Requires compression within the preceding five bars unless disabled. |
| Volume and trend filters | Volume above 1.25 times its SMA; direction relative to a 200-bar EMA. | Restricts eligible entries without proving a profitable edge. |
| Exit mode | Hybrid ATR stop and midline close, fixed stop/target, or midline only. | Defines loss handling and whether a profit target caps a trade. |
Pine Script v6 Implementation
//@version=6
strategy(
title = "TradingView Bollinger Bands Breakout Strategy [Pineify]",
shorttitle = "BB Breakout [Pineify]",
overlay = true,
initial_capital = 10000,
default_qty_type = strategy.percent_of_equity,
default_qty_value = 20,
commission_type = strategy.commission.percent,
commission_value = 0.075,
slippage = 1,
pyramiding = 0,
calc_on_every_tick = false,
process_orders_on_close = false
)
// --- Strategy Inputs ---
group_bb = "Bollinger Bands Settings"
bbLength = input.int(20, "BB Length", minval=1, group=group_bb, tooltip="Period for the baseline Simple Moving Average and Standard Deviation calculation.")
bbMult = input.float(2.0, "BB Multiplier", minval=0.5, step=0.1, group=group_bb, tooltip="Standard deviation multiplier. Compare settings on a reserved test period.")
group_sqz = "Squeeze Detection"
squeezeMode = input.string("Keltner Channel Squeeze", "Squeeze Mode", options=["Keltner Channel Squeeze", "BandWidth Percentile", "None"], group=group_sqz, tooltip="Choose BB inside an EMA/ATR envelope or rolling BandWidth percentile.")
kcLength = input.int(20, "Keltner Length", minval=1, group=group_sqz)
kcMult = input.float(1.5, "Keltner ATR Multiplier", minval=0.1, step=0.1, group=group_sqz)
bwLookback = input.int(100, "BandWidth Lookback", minval=10, group=group_sqz, tooltip="Number of bars used to calculate rolling BandWidth percentile rank.")
bwThreshold = input.float(20.0, "BandWidth Percentile Max (%)", minval=1.0, maxval=50.0, step=1.0, group=group_sqz, tooltip="Maximum percentile rank to qualify as a squeeze (e.g. 20 = bottom 20% volatility).")
maxBarsSqz = input.int(5, "Max Bars Since Squeeze", minval=1, maxval=20, group=group_sqz, tooltip="Allow entries if a valid squeeze occurred within this many bars prior to breakout.")
group_filters = "Confirmation Filters"
useVolumeFilter = input.bool(true, "Require Volume Surge", group=group_filters, tooltip="Require volume above the selected threshold. Missing volume blocks entries when enabled.")
volMaLength = input.int(20, "Volume MA Length", minval=1, group=group_filters)
volMult = input.float(1.25, "Volume Threshold Multiplier", minval=1.0, step=0.05, group=group_filters, tooltip="Volume must be greater than Volume MA * Multiplier.")
useTrendFilter = input.bool(true, "Filter by 200 EMA Trend", group=group_filters, tooltip="Only enter longs when price is above 200 EMA, and shorts when below 200 EMA.")
trendEmaLength = input.int(200, "Trend EMA Length", minval=1, group=group_filters)
group_exit = "Exit Rules"
exitMode = input.string("Hybrid (Midline + ATR Stop)", "Exit Strategy", options=["Midline close", "Fixed Risk/Reward", "Hybrid (Midline + ATR Stop)"], group=group_exit)
atrLength = input.int(14, "ATR Length (Stop / Target)", minval=1, group=group_exit)
atrStopMult= input.float(2.0, "ATR Stop Multiplier", minval=0.5, step=0.1, group=group_exit)
rrRatio = input.float(2.0, "Risk-to-Reward Ratio", minval=0.5, step=0.1, group=group_exit)
// --- Indicator Calculations ---
// Bollinger Bands
[bbMiddle, bbUpper, bbLower] = ta.bb(close, bbLength, bbMult)
bbBandwidth = bbMiddle != 0 ? (bbUpper - bbLower) / math.abs(bbMiddle) : na
// Keltner-style envelope: EMA basis plus/minus Wilder ATR
kcBasis = ta.ema(close, kcLength)
kcRange = ta.atr(kcLength)
kcUpper = kcBasis + (kcMult * kcRange)
kcLower = kcBasis - (kcMult * kcRange)
kcSqueeze = (bbUpper < kcUpper) and (bbLower > kcLower)
// BandWidth Percentile Squeeze
bwPercentile = ta.percentrank(bbBandwidth, bwLookback)
bwSqueeze = bwPercentile <= bwThreshold
// Active Squeeze Status
isSqueeze = switch squeezeMode
"Keltner Channel Squeeze" => kcSqueeze
"BandWidth Percentile" => bwSqueeze
=> false
// The current bar cannot qualify itself as a prior squeeze.
barsSinceSqz = ta.barssince(isSqueeze[1]) + 1
squeezeValid = squeezeMode == "None" or (not na(barsSinceSqz) and barsSinceSqz <= maxBarsSqz)
// Volume & Trend Validation
volMa = ta.sma(volume, volMaLength)
volConfirmed = not useVolumeFilter or (not na(volume) and not na(volMa) and volume > volMa * volMult)
trendEma = ta.ema(close, trendEmaLength)
trendBull = not useTrendFilter or (close > trendEma)
trendBear = not useTrendFilter or (close < trendEma)
// --- Confirmed entry triggers, evaluated on every bar ---
longCross = ta.crossover(close, bbUpper)
shortCross = ta.crossunder(close, bbLower)
atrVal = ta.atr(atrLength)
ready = not na(atrVal) and atrVal > 0 and not na(bbUpper) and not na(bbLower)
longCondition = barstate.isconfirmed and ready and longCross and squeezeValid and volConfirmed and trendBull
shortCondition = barstate.isconfirmed and ready and shortCross and squeezeValid and volConfirmed and trendBear
// Freeze signal-bar ATR as a tick distance from the eventual entry fill.
// Submit brackets with the entry, so they do not wait for the entry bar to close.
riskTicks = math.max(1, math.round(atrVal * atrStopMult / syminfo.mintick))
targetTicks = math.max(1, math.round(riskTicks * rrRatio))
if strategy.position_size == 0
if longCondition
strategy.entry("BB_Long", strategy.long)
if exitMode == "Fixed Risk/Reward"
strategy.exit("Long_Bracket", "BB_Long", loss=riskTicks, profit=targetTicks)
else if exitMode == "Hybrid (Midline + ATR Stop)"
strategy.exit("Long_Stop", "BB_Long", loss=riskTicks)
else if shortCondition
strategy.entry("BB_Short", strategy.short)
if exitMode == "Fixed Risk/Reward"
strategy.exit("Short_Bracket", "BB_Short", loss=riskTicks, profit=targetTicks)
else if exitMode == "Hybrid (Midline + ATR Stop)"
strategy.exit("Short_Stop", "BB_Short", loss=riskTicks)
// A midline close is a market exit signal, not an intrabar trailing stop.
if barstate.isconfirmed and exitMode != "Fixed Risk/Reward"
if strategy.position_size > 0 and close < bbMiddle
strategy.close("BB_Long", comment="Close below basis")
if strategy.position_size < 0 and close > bbMiddle
strategy.close("BB_Short", comment="Close above basis")
// --- Chart Visuals ---
plot(bbMiddle, "BB Basis", color=color.new(#FF9800, 0), linewidth=1)
pUpper = plot(bbUpper, "BB Upper Band", color=color.new(#2962FF, 10), linewidth=1)
pLower = plot(bbLower, "BB Lower Band", color=color.new(#2962FF, 10), linewidth=1)
// Dynamic cloud fill between bands: purple during squeeze compression, blue during expansion
cloudColor = isSqueeze ? color.new(#9C27B0, 80) : color.new(#2962FF, 92)
fill(pUpper, pLower, color=cloudColor, title="Bollinger Band Cloud Fill")
// Squeeze indicator dots at chart bottom
plotshape(isSqueeze, title="Squeeze Dot", style=shape.circle, location=location.bottom, color=color.purple, size=size.tiny)
// Optional trend filter plot
plot(useTrendFilter ? trendEma : na, "Trend EMA", color=color.new(#787B86, 20), linewidth=2)How the Pine Script rules execute
- 1
Squeeze state: ta.barssince(isSqueeze[1]) + 1 measures how long ago a completed prior bar met the chosen squeeze definition. Before any qualifying squeeze, that gate remains closed.
- 2
Entry event: crossover and crossunder are calculated every bar. Confirmed signals must pass the squeeze, volume and trend gates, and the strategy must be flat.
- 3
Bracket timing: loss and profit distances are in ticks from the entry fill. The ATR distance is fixed at the signal and orders are submitted alongside the entry.
- 4
Midline behavior: closes on the adverse side of the current SMA request a market exit. This is different from placing a stop order at the moving average.
- 5
Costs: 0.075% commission and one tick of slippage are editable modeling assumptions, not verified fees for any particular venue.
Backtest Assumptions and Metrics
No backtest has been run for this example, so no return, win rate or drawdown is reported. To reproduce a test, paste the complete script into TradingView Pine Editor, add it to a standard candlestick chart, and record the exchange symbol, timeframe, dates, inputs and Properties. Start with the settings below, then replace cost assumptions with those appropriate to your market.
| Metric | Value | Interpretation |
|---|---|---|
| Initial capital | 10,000 | Simulation setting in the script; record the account currency. |
| Order size | 20% of equity | Position allocation per entry; loss depends on the stop distance and fills. |
| Commission | 0.075% | Illustrative percentage charged on executed orders, including entries and exits. |
| Slippage | 1 tick | Illustrative adjustment for market and stop fills. Stress-test larger values. |
| Default exit | Hybrid | Signal ATR times 2 stop plus a confirmed close on the adverse side of the basis. |
| Performance | Not measured | Export actual results before comparing net profit, drawdown, payoff and trade count. |
Reserve a later period before choosing parameters. Tune on the earlier period, freeze the rules, and evaluate the reserved period without retuning. Record all attempted settings and losing periods. Compare squeeze modes and exits using the same symbol, dates, costs and sizing. Check trade count, average win/loss, drawdown and net results after costs; a few large winners can dominate a small sample. Then forward-test the unchanged setup. None of these checks establishes future profitability.
Pineify Backtest Deep Report: Beyond Basic Metrics
Strategy Optimization Suggestions
Compare the compression definitions
Test the EMA/ATR envelope and BandWidth percentile separately with identical exit and cost settings. Keep None as a baseline to measure the effect of the squeeze gate.
Measure the exit tradeoff
Compare Hybrid with a fixed target and midline-only exits. Record both lost profit from early exits and losses that grow while waiting for a close.
Reserve data before searching
Choose training and validation dates before comparing inputs. Repeatedly choosing settings from the validation period makes that period part of the training process.
Frequently Asked Questions
Build and Review Your Bollinger Bands Rules
Use the example as a starting specification for Pineify, inspect the generated Pine Script, and compare settings on TradingView with costs and a reserved test period.
Disclaimer: Trading involves significant risk. The strategies and code provided are for educational purposes only and do not constitute financial advice. Past performance is not indicative of future results.
Related Strategy Tutorials
Mean Reversion Strategy Tutorial
Learn how to build, backtest, and optimize a Mean Reversion Strategy on TradingView using Bollinger Bands and RSI with Pine Script v5.
Breakout and Retest Strategy Tutorial
Learn how to build, backtest, and optimize the Breakout and Retest strategy on TradingView with full Pine Script v5 code and Pineify analysis.
SuperTrend Strategy Tutorial
Learn how to build, backtest, and optimize the SuperTrend strategy on TradingView using Pine Script v5 and Pineify Deep Report.
RSI Overbought Oversold Strategy Tutorial
Compare RSI threshold entries, recovery crosses and EMA trend filters. Test a Pine Script v6 strategy with explicit stops, costs and time exits on TradingView.