Trading StrategyPine Script v6

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.

ComponentDescriptionRole
Bollinger Bands20-bar SMA plus or minus 2 standard deviations by default.Defines the boundary used by fresh close crossings.
Recent squeezeBB inside an EMA/ATR envelope, or low BandWidth percentile.Requires compression within the preceding five bars unless disabled.
Volume and trend filtersVolume above 1.25 times its SMA; direction relative to a 200-bar EMA.Restricts eligible entries without proving a profitable edge.
Exit modeHybrid 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

Pine Script v6
//@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. 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. 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. 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. 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. 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.

MetricValueInterpretation
Initial capital10,000Simulation setting in the script; record the account currency.
Order size20% of equityPosition allocation per entry; loss depends on the stop distance and fills.
Commission0.075%Illustrative percentage charged on executed orders, including entries and exits.
Slippage1 tickIllustrative adjustment for market and stop fills. Stress-test larger values.
Default exitHybridSignal ATR times 2 stop plus a confirmed close on the adverse side of the basis.
PerformanceNot measuredExport 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

Use the Visual Pine Script Editor to build and inspect Bollinger Bands conditions, then test the resulting strategy on TradingView. The Strategy Optimizer can compare input combinations. Keep a separate validation period and document every setting tried. Generated code and optimizer results still need review; neither establishes that a trading rule is profitable.

Strategy Optimization Suggestions

1

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.

2

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.

3

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

Only under a defined breakout rule. In this example it must be a fresh crossover on a confirmed bar, after a recent squeeze, with the enabled filters satisfied and no open position. A close outside the band by itself is insufficient.

No. It requires a squeeze on one of the previous five bars by default. The current bar may remain in a squeeze. A release rule, such as requiring isSqueeze to become false, would be a different strategy to test.

There is no measured best setting for this example. The starting values are a 20-bar SMA and two standard deviations. Compare a limited set of settings with recorded costs, then evaluate unchanged settings on reserved data. Two standard deviations do not guarantee that a fixed percentage of market prices stays within the bands.

No. It waits for a confirmed close on the adverse side of the moving average, then submits a market exit. Midline-only mode has no intrabar stop. Hybrid mode adds an ATR-based stop, but gaps and slippage can still create losses larger than its planned distance.

Breakout rules enter in the direction of a fresh outer-band crossing to test continuation. Mean reversion rules fade an extreme or enter after price returns inside the bands to test a move toward the average. They need separate entry, exit and validation rules.

It prevents this script from entering on an unfinished chart bar under the supplied settings. It does not guarantee results are unchanged when the data feed, chart history, parameters or execution settings change. This code does not request higher-timeframe data.

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.