Bollinger Bands Strategy in Python
Bollinger Bands are a volatility indicator created by John Bollinger. They consist of a middle band (SMA) and upper/lower bands set at 2 standard deviations above and below. When bands squeeze, a breakout is coming. When price touches the outer bands, a reversal may follow. In this tutorial, we build Bollinger Bands from scratch and create a mean-reversion trading strategy.
Prerequisites
- ✓Basic Python and pandas
- ✓Understanding of standard deviation
Step 1.Get market data
We use SPY (S&P 500 ETF) as it exhibits clear mean-reversion behaviour.
import yfinance as yf
import pandas as pd
import matplotlib.pyplot as plt
df = yf.download("SPY", start="2021-01-01", end="2024-06-01")
df = df[["Close"]].copy()
df.columns = ["close"]Step 2.Calculate Bollinger Bands
Bandwidth measures how wide the bands are relative to price. Low bandwidth means a squeeze.
WINDOW = 20
NUM_STD = 2
df["sma"] = df["close"].rolling(window=WINDOW).mean()
df["std"] = df["close"].rolling(window=WINDOW).std()
df["upper"] = df["sma"] + (NUM_STD * df["std"])
df["lower"] = df["sma"] - (NUM_STD * df["std"])
df["bandwidth"] = (df["upper"] - df["lower"]) / df["sma"]
print(df[["close", "sma", "upper", "lower", "bandwidth"]].tail())Step 3.Generate mean-reversion signals
The mean-reversion idea: price touching the lower band is oversold, upper band is overbought.
df["signal"] = 0
df.loc[df["close"] < df["lower"], "signal"] = 1 # Buy when price below lower band
df.loc[df["close"] > df["upper"], "signal"] = -1 # Sell when price above upper band
buys = df[df["signal"] == 1]
sells = df[df["signal"] == -1]
print(f"Buy signals: {len(buys)}")
print(f"Sell signals: {len(sells)}")Step 4.Detect volatility squeezes
A squeeze often precedes a big move. Traders watch for the bands to expand after a squeeze.
squeeze_threshold = df["bandwidth"].quantile(0.1)
df["squeeze"] = df["bandwidth"] < squeeze_threshold
squeezes = df[df["squeeze"]]
print(f"Squeeze days (lowest 10% bandwidth): {len(squeezes)}")Step 5.Plot everything
Upper chart shows price with Bollinger Bands and buy/sell signals. Lower chart shows bandwidth with squeeze detection.
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 10))
ax1.plot(df.index, df["close"], label="Price", linewidth=1)
ax1.plot(df.index, df["sma"], label="SMA 20", alpha=0.7)
ax1.fill_between(df.index, df["upper"], df["lower"], alpha=0.15, color="cyan")
ax1.scatter(buys.index, buys["close"], marker="^", color="lime", s=60)
ax1.scatter(sells.index, sells["close"], marker="v", color="red", s=60)
ax1.set_title("Bollinger Bands")
ax1.legend()
ax2.plot(df.index, df["bandwidth"], color="orange")
ax2.axhline(squeeze_threshold, color="red", linestyle="--", alpha=0.5, label="Squeeze threshold")
ax2.set_title("Bandwidth (Squeeze Detector)")
ax2.legend()
plt.tight_layout()
plt.savefig("bollinger.png", dpi=150)
plt.show()Expected Output
Buy signals: 42
Sell signals: 38
Squeeze days (lowest 10% bandwidth): 87Next Steps
- →Combine Bollinger Bands with RSI for confirmation
- →Backtest the mean-reversion strategy with transaction costs
- →Try different window and standard deviation settings
Recommended Reading
Bollinger on Bollinger Bands →As an Amazon Associate we may earn from qualifying purchases.