We already know both mathematical pieces needed for Bollinger Bands.
Simple Moving Average
→ center
Population Standard Deviation
→ dispersion
Bollinger Bands combine them.
Middle Band
=
SMA
Upper Band
=
SMA + k × standard deviation
Lower Band
=
SMA - k × standard deviation
This lesson is therefore about composition, not about memorizing another unrelated formula.
1. Start with the Middle Band
The traditional middle Bollinger Band is a simple moving average.
Middle Band
=
SMA(N)
With the common default:
N = 20
If you already understand SMA, the center line contains no new mathematics.
2. Add the Dispersion Block We Just Built
Our previous lesson built rolling population standard deviation.
Close prices
→ mean
→ deviations
→ squared deviations
→ variance
→ square root
→ standard deviation
Review: What Is Standard Deviation? Measure Price Dispersion with Python .
3. Build the Upper and Lower Bands
Upper Band
=
SMA + k × StdDev
Lower Band
=
SMA - k × StdDev
The traditional default is:
period = 20
k = 2
John Bollinger's official explanation emphasizes that these are defaults, not universal constants.
4. Work Through a Tiny Example
100, 102, 104, 106, 108
From the previous lesson:
Mean
=
104
Population StdDev
=
√8
≈
2.828427
With k = 2:
Upper Band
=
104 + 2 × 2.828427
≈
109.656854
Lower Band
=
104 - 2 × 2.828427
≈
98.343146
5. What Makes the Bands Expand?
larger price dispersion
→ larger StdDev
→ bands move farther from SMA
Band width adapts to recent price dispersion.
6. What Makes the Bands Contract?
prices cluster more tightly
→ smaller StdDev
→ bands move closer to SMA
7. The Middle Band and Band Width Answer Different Questions
Middle Band
→ Where is the smoothed price level?
Band distance
→ How dispersed are recent prices?
Bollinger Bands put those two properties on one chart.
8. A Touch of the Upper Band Is Not Automatically a Sell Signal
price touches Upper Band
does not automatically mean
sell
Bollinger's own rules explicitly warn against treating an upper-band tag as a sell signal by itself.
9. A Touch of the Lower Band Is Not Automatically a Buy Signal
price touches Lower Band
does not automatically mean
buy
A band touch is an observation. A trading rule requires additional definitions and testing.
10. Price Can Walk Along a Band
persistent upward movement
→ price may walk the Upper Band
persistent downward movement
→ price may walk the Lower Band
This is why the shortcut “upper = overbought, lower = oversold” can be misleading.
11. A Close Outside the Bands Is Not Automatically a Reversal
Bollinger's published rules also note that closes outside the bands are not automatically reversal signals.
measurement
≠
trading rule
12. Why Population Standard Deviation Matters
In Phase 3-22 we deliberately used:
variance
=
sum squared deviations
/
N
Bollinger's official explanation states that the traditional bands use the population calculation for standard deviation.
So the block we already built can be reused without changing its definition.
13. Build Bollinger Bands as One Higher-Level Function
window
↓
arithmetic_mean()
↓
Middle Band
window
↓
population_standard_deviation()
↓
StdDev
Middle ± k × StdDev
↓
Upper / Lower Bands
14. Why This Is a Good Example of Modular Indicator Design
known block
SMA
+
known block
Standard Deviation
=
new indicator
Bollinger Bands
More advanced does not always mean more mysterious.
15. What the AAPL Chart Shows
AAPL candlesticks
Middle Band
SMA 20
Upper Band
SMA + 2 StdDev
Lower Band
SMA - 2 StdDev
Rising candles use seagreen.
Falling candles use firebrick.
16. Change the Multiplier Yourself
Start with:
standard_deviation_multiplier = 2.0
Then try:
1.0
and:
3.0
Ask:
How does band width change?
How often does price
move outside the bands?
17. Change the Period Yourself
Try:
band_period = 10
and:
band_period = 40
shorter period
→ faster center
→ faster-changing dispersion
longer period
→ slower center
→ slower-changing dispersion
18. Bollinger Bands Are Not a Complete Strategy
Bollinger Bands do not automatically define:
entry
exit
position size
stop
holding period
Those questions belong to Phase 4.
19. The Complete Python Program
This lesson introduces no new external package.
It reuses FinanceDataReader,
matplotlib,
and Python's built-in math module.
phase3_bollinger_bands.py
from pathlib import Path
from datetime import date, timedelta
import math
import os
import FinanceDataReader as fdr
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
symbol = "AAPL"
recent_trading_days = 180
band_period = 20
standard_deviation_multiplier = 2.0
bullish_color = "seagreen"
bearish_color = "firebrick"
middle_band_color = "dimgray"
upper_band_color = "black"
lower_band_color = "black"
SCRIPT_DIR = Path(__file__).resolve().parent
os.chdir(SCRIPT_DIR)
def arithmetic_mean(values):
if len(values) == 0:
raise ValueError("values must not be empty")
return sum(float(v) for v in values) / len(values)
def population_standard_deviation(values):
if len(values) == 0:
raise ValueError("values must not be empty")
mean_value = arithmetic_mean(values)
variance = sum(
(float(v) - mean_value) ** 2
for v in values
) / len(values)
return math.sqrt(variance)
def bollinger_bands(values, period, multiplier):
middle = [None] * len(values)
upper = [None] * len(values)
lower = [None] * len(values)
stds = [None] * len(values)
if period <= 0:
raise ValueError("period must be positive")
if multiplier < 0:
raise ValueError("multiplier must be non-negative")
for i in range(period - 1, len(values)):
window = values[i - period + 1 : i + 1]
mean_value = arithmetic_mean(window)
std_value = population_standard_deviation(window)
middle[i] = mean_value
upper[i] = mean_value + multiplier * std_value
lower[i] = mean_value - multiplier * std_value
stds[i] = std_value
return middle, upper, lower, stds
def draw_candlesticks(ax, market_df, body_width=0.62):
for x, (_, row) in enumerate(market_df.iterrows()):
o = float(row["Open"])
h = float(row["High"])
l = float(row["Low"])
c = float(row["Close"])
color = bullish_color if c >= o else bearish_color
ax.vlines(
x, l, h,
color=color,
linewidth=1.0,
)
bottom = min(o, c)
height = abs(c - o)
if height == 0:
height = max(h - l, 0.01) * 0.02
ax.add_patch(
Rectangle(
(x - body_width / 2.0, bottom),
body_width,
height,
facecolor=color,
edgecolor=color,
linewidth=0.8,
)
)
toy = [100.0, 102.0, 104.0, 106.0, 108.0]
m, u, l, s = bollinger_bands(
toy,
period=5,
multiplier=2.0,
)
expected_mean = 104.0
expected_std = math.sqrt(8.0)
expected_upper = expected_mean + 2.0 * expected_std
expected_lower = expected_mean - 2.0 * expected_std
assert m[:4] == [None, None, None, None]
assert abs(m[4] - expected_mean) < 1e-12
assert abs(s[4] - expected_std) < 1e-12
assert abs(u[4] - expected_upper) < 1e-12
assert abs(l[4] - expected_lower) < 1e-12
print("Self-test")
print("=========")
print("Middle:", f"{m[4]:.6f}")
print("Population StdDev:", f"{s[4]:.6f}")
print("Upper:", f"{u[4]:.6f}")
print("Lower:", f"{l[4]:.6f}")
print("Self-test: PASS")
print()
today = date.today()
start_date = (today - timedelta(days=420)).strftime("%Y-%m-%d")
end_date = today.strftime("%Y-%m-%d")
df = fdr.DataReader(symbol, start_date, end_date)
df = df.tail(recent_trading_days).copy()
close_values = [float(v) for v in df["Close"]]
middle, upper, lower, stds = bollinger_bands(
close_values,
period=band_period,
multiplier=standard_deviation_multiplier,
)
df["Middle Band"] = middle
df["Upper Band"] = upper
df["Lower Band"] = lower
df["StdDev"] = stds
valid_df = df.dropna(
subset=["Middle Band", "Upper Band", "Lower Band"]
)
latest = valid_df.iloc[-1]
latest_date = valid_df.index[-1]
print("Latest Bollinger Bands")
print("======================")
print("Symbol:", symbol)
print("Date:", latest_date.strftime("%Y-%m-%d"))
print("Close:", f'{latest["Close"]:.2f}')
print("Middle:", f'{latest["Middle Band"]:.2f}')
print("Upper:", f'{latest["Upper Band"]:.2f}')
print("Lower:", f'{latest["Lower Band"]:.2f}')
print("StdDev:", f'{latest["StdDev"]:.4f}')
plot_df = df.tail(100).copy()
x = list(range(len(plot_df)))
fig, ax = plt.subplots(figsize=(12, 8))
draw_candlesticks(ax, plot_df)
ax.plot(
x,
plot_df["Middle Band"],
color=middle_band_color,
linewidth=1.5,
label=f"SMA {band_period}",
)
ax.plot(
x,
plot_df["Upper Band"],
color=upper_band_color,
linewidth=1.5,
label="Upper Band",
)
ax.plot(
x,
plot_df["Lower Band"],
color=lower_band_color,
linewidth=1.5,
label="Lower Band",
)
ax.fill_between(
x,
plot_df["Lower Band"].astype(float),
plot_df["Upper Band"].astype(float),
alpha=0.08,
)
ax.set_title(
f"{symbol} — Bollinger Bands "
f"({band_period}, {standard_deviation_multiplier:g})"
)
ax.set_ylabel("Price")
ax.grid(axis="y", alpha=0.20)
ax.legend()
step = max(1, len(plot_df) // 8)
positions = list(range(0, len(plot_df), step))
labels = [
plot_df.index[i].strftime("%Y-%m-%d")
for i in positions
]
ax.set_xticks(positions)
ax.set_xticklabels(labels, rotation=35, ha="right")
fig.subplots_adjust(
left=0.09,
right=0.98,
top=0.93,
bottom=0.15,
)
output_file = SCRIPT_DIR / "bollinger_bands_aapl.png"
fig.savefig(output_file, dpi=140)
print()
print("Chart saved:")
print(output_file)
plt.show()
plt.close(fig)
20. Run the Program
python phase3_bollinger_bands.py
Confirm:
Self-test: PASS
The chart is saved as:
bollinger_bands_aapl.png
Check Your Understanding
- The Middle Band is a simple moving average in the traditional construction.
- The Upper and Lower Bands are placed a multiple of population standard deviation above and below the middle band.
- The common 20-period and ±2-standard-deviation settings are defaults, not universal laws.
- Greater recent price dispersion produces wider bands.
- A tag of the upper band is not automatically a sell signal.
- A tag of the lower band is not automatically a buy signal.
- Price can move along a band during a persistent trend.
- Bollinger Bands reuse the SMA and population standard-deviation blocks already learned.
What You Just Learned
Close prices
↓
same rolling window
↙ ↘
SMA StdDev
↓ ↓
center dispersion
↘ ↙
combine
↓
Middle / Upper / Lower Bands
↓
dynamic price envelope
Bollinger Bands combine a moving center with a dispersion measure. The bands adapt because standard deviation changes through time.
Where Do We Go Next?
Where is today's Close
inside the recent
High-Low range?
That leads to the Stochastic Oscillator.
Sources
- John Bollinger — official Bollinger Bands explanation — moving-average center, population standard deviation, and traditional defaults.
- John Bollinger — Bollinger Band Rules — band tags are not signals, price can walk a band, and defaults are only defaults.