Bollinger Bands are the envelope you have seen wrapped around price on countless charts: a middle line with two bands that widen when the market gets volatile and pinch together when it goes quiet. Developed by John Bollinger in the 1980s, they turn an abstract idea, how far price has strayed from its recent average, into something you can see and code. This guide explains what the bands actually measure, why traders use them, and how to build them from scratch in Pine Script v6 on TradingView, then how to turn them into two very different strategies.
This is a coding tutorial, not trading advice. Bollinger Bands are a way to express a question about volatility and deviation in code, and building them yourself is the clearest path to understanding where they help and where they mislead.
What Bollinger Bands measure
The middle band, called the basis, is a simple moving average of the closing price, traditionally over 20 bars. The upper and lower bands sit a set number of standard deviations away from that basis, traditionally two. Standard deviation is a measure of how spread out recent prices are, so when the market is choppy and prices are scattered, the standard deviation grows and the bands widen. When the market is calm and prices cluster tightly around the average, the standard deviation shrinks and the bands squeeze together.
That is the whole idea, and it is more elegant than it first looks. Because the bands are built from standard deviation rather than a fixed percentage, they adapt automatically to each market's own volatility. Two standard deviations captures roughly 95 percent of recent price action under a normal distribution, so price reaching a band is, statistically, a relatively uncommon event, though markets are famously not perfectly normal, which is part of why the bands are a guide rather than a rule.
Why traders use them
Traders read Bollinger Bands in two broadly opposite ways, which is what makes them interesting. The first is mean reversion: when price stretches to the upper band, it has moved unusually far above its average and may snap back toward the middle, and the same logic applies in reverse at the lower band. The second is the squeeze: when the bands contract to an unusually narrow width, it signals a period of low volatility that often precedes a sharp expansion, so traders watch a squeeze as a setup for a breakout in either direction.
The tension between those two readings is the key lesson. A touch of the upper band means "snap back" to a mean-reversion trader and "strong breakout, get on board" to a trend trader, and only the surrounding context tells you which is playing out. That ambiguity is exactly why building the bands yourself, and adding your own filters, beats trading them blindly.
| Mean Reversion | Squeeze Breakout | |
|---|---|---|
| Reads | Touch of a band | Bands narrowing then widening |
| Assumes | Price returns to the basis | Low volatility precedes a move |
| Best market | Range-bound | Coiled, pre-breakout |
| Common trap | Fading a real breakout | Acting before the expansion confirms |
Building the indicator
Pine Script provides Bollinger Bands as a single built-in function that returns all three lines as a tuple. Here is the complete v6 indicator, drawn as an overlay on the price chart.
//@version=6
indicator("Bollinger Bands", overlay = true)
length = input.int(20, "Length")
mult = input.float(2.0, "StdDev Multiplier")
[basis, upper, lower] = ta.bb(close, length, mult)
plot(basis, "Basis", color = color.orange)
p1 = plot(upper, "Upper", color = color.blue)
p2 = plot(lower, "Lower", color = color.blue)
fill(p1, p2, color = color.new(color.blue, 90), title = "Band Fill")The line doing the work is [basis, upper, lower] = ta.bb(close, length, mult). The ta.bb() function computes the moving-average basis and both bands in one call and returns them as a tuple, which Pine's syntax unpacks into three named variables. After that it is presentation: the three plot() calls draw the lines, and fill() shades the area between the upper and lower plots. Note that fill() takes plot handles, which is why the upper and lower plots are assigned to p1 and p2 first. Setting overlay = true draws everything on the price chart, where the bands belong, since they are measured in the same units as price.
Strategy one: mean reversion
The mean-reversion approach buys when price closes below the lower band, betting on a snap back toward the basis, and exits when price returns to the middle. Here it is as a v6 strategy.
//@version=6
strategy("BB Mean Reversion", overlay = true, margin_long = 100, margin_short = 100)
length = input.int(20, "Length")
mult = input.float(2.0, "StdDev Multiplier")
[basis, upper, lower] = ta.bb(close, length, mult)
if ta.crossunder(close, lower)
strategy.entry("Long", strategy.long)
if ta.crossover(close, basis)
strategy.close("Long")The entry uses ta.crossunder(close, lower), which fires on the bar where price crosses from above the lower band to below it, rather than a plain close < lower that would be true on every bar price stays beneath the band. The exit fires when price crosses back above the basis, capturing the reversion to the mean. The order calls sit inside if blocks as v6 requires. This approach lives or dies on the market being range-bound; in a strong downtrend, price can ride the lower band down for a long time, and every one of those touches would be a losing entry.
Strategy two: the squeeze breakout
The squeeze approach is almost the opposite. Instead of fading a band touch, it waits for the bands to contract to an unusually narrow width and then enters in the direction of the breakout when price pushes through a band. Detecting the squeeze means comparing the current band width to its own recent range.
//@version=6
strategy("BB Squeeze Breakout", overlay = true, margin_long = 100, margin_short = 100)
length = input.int(20, "Length")
mult = input.float(2.0, "StdDev Multiplier")
[basis, upper, lower] = ta.bb(close, length, mult)
bandWidth = (upper - lower) / basis
isSqueeze = bandWidth < ta.lowest(bandWidth, 50) * 1.1
breakout = ta.crossover(close, upper)
if breakout and isSqueeze[1]
strategy.entry("Long", strategy.long)
if ta.crossunder(close, basis)
strategy.close("Long")The bandWidth variable normalizes the distance between the bands by the basis, so the width is comparable across price levels. The isSqueeze check asks whether the current width is near its lowest value over the last 50 bars. The entry condition uses isSqueeze[1], the squeeze state on the previous bar, so we are looking for a breakout that happens just as a squeeze ends. The [1] is the history-referencing operator, one of the core ideas in how Pine runs your code once per bar; if that feels unfamiliar, our guide to the Pine Script execution model explains it in depth. Assigning breakout to its own variable before the if keeps the ta.crossover() call running on every bar, which it needs to track its state correctly.
The honest pitfalls
The central pitfall is choosing the wrong reading for the market. Mean reversion fades band touches and gets destroyed in trends; squeeze breakouts chase expansions and get chopped up in ranges. There is no setting that makes Bollinger Bands work in every environment, which is why the more useful skill is recognizing which regime you are in and applying the matching approach, ideally with a confirmation filter rather than acting on a band touch alone.
The second pitfall is misreading a band touch as a signal in itself. Price touching the upper band is common and means only that price has moved two standard deviations above its average; it is a description, not an instruction. Judge any Bollinger strategy across a meaningful sample of trades and more than one symbol. If you want to see exactly how the basis and the standard-deviation bands are computed from a price series, our Bollinger Bands calculator lays out the math with the Pine Script equivalent alongside.
Building it faster
Coding this by hand teaches you how the bands react to volatility, which is worth doing at least once. When you already know the rules you want, describing them in plain English is faster than assembling the script. A tool like PineScripter generates the v6 code and, when it does not compile, reads TradingView's error and fixes it automatically instead of making you copy it back and forth. You can compare that workflow with other options in our roundup of the best AI Pine Script generators. Because Bollinger Bands are built on a moving average, our moving average crossover guide is a useful companion read.
The takeaway
Bollinger Bands are a moving average with standard-deviation bands around it, delivered by one function, ta.bb(). Their real value is that they adapt to each market's volatility, and their real difficulty is that a band touch can mean two opposite things. Build them, decide whether you are trading reversion or breakouts, add a filter that fits the regime, and test the result across a real sample.
Disclaimer: PineScripter is a coding tool for Pine Script development. It does not provide financial advice and does not guarantee trading profits. Always backtest strategies thoroughly and understand the risks before live trading.