Bollinger Bands Indicator Chart — ggplot2

A Bollinger Bands chart displays price data with a volatility envelope consisting of three lines: a middle band (simple moving average), an upper band (SMA plus 2 standard deviations), and a lower band (SMA minus 2 standard deviations). This technical indicator helps traders identify overbought/oversold conditions, volatility patterns, and potential price breakouts by showing how prices relate to their recent statistical range.

Bollinger Bands Indicator Chart rendered with ggplot2

Renders

R source (ggplot2)

#' anyplot.ai
#' indicator-bollinger: Bollinger Bands Indicator Chart
#' Library: ggplot2 3.5.1 | R 4.4.1
#' Quality: 90/100 | Created: 2026-05-17

library(ggplot2)
library(dplyr)
library(tidyr)
library(ragg)

set.seed(42)

# --- Theme tokens -----------------------------------------------------------
THEME       <- Sys.getenv("ANYPLOT_THEME", "light")
PAGE_BG     <- if (THEME == "light") "#FAF8F1" else "#1A1A17"
INK         <- if (THEME == "light") "#1A1A17" else "#F0EFE8"
INK_SOFT    <- if (THEME == "light") "#4A4A44" else "#B8B7B0"
IMPRINT   <- c("#009E73", "#C475FD", "#4467A3", "#BD8233",
                 "#AE3030", "#2ABCCD", "#954477")

# --- Data -------------------------------------------------------------------
n_periods <- 150
dates <- seq.Date(from = as.Date("2024-01-01"), by = "1 day", length.out = n_periods)

price <- 100 + cumsum(rnorm(n_periods, mean = 0.15, sd = 1.2))

sma_period <- 20
sma <- numeric(n_periods)
for (i in 1:n_periods) {
  if (i < sma_period) {
    sma[i] <- mean(price[1:i])
  } else {
    sma[i] <- mean(price[(i - sma_period + 1):i])
  }
}

std_dev <- numeric(n_periods)
for (i in 1:n_periods) {
  if (i < sma_period) {
    std_dev[i] <- sd(price[1:i])
  } else {
    std_dev[i] <- sd(price[(i - sma_period + 1):i])
  }
}

upper_band <- sma + 2 * std_dev
lower_band <- sma - 2 * std_dev

df <- data.frame(
  date = dates,
  close = price,
  sma = sma,
  upper_band = upper_band,
  lower_band = lower_band
)

# --- Plot -------------------------------------------------------------------
p <- ggplot(df, aes(x = date)) +
  geom_ribbon(aes(ymin = lower_band, ymax = upper_band),
              fill = IMPRINT[1], alpha = 0.15) +
  geom_line(aes(y = upper_band), color = IMPRINT[1], linewidth = 0.6,
            linetype = "solid", alpha = 0.6) +
  geom_line(aes(y = lower_band), color = IMPRINT[1], linewidth = 0.6,
            linetype = "solid", alpha = 0.6) +
  geom_line(aes(y = sma), color = IMPRINT[1], linewidth = 0.8,
            linetype = "dashed", alpha = 0.7) +
  geom_line(aes(y = close), color = IMPRINT[1], linewidth = 1.2) +
  labs(
    title = "indicator-bollinger · ggplot2 · anyplot.ai",
    x = "Date",
    y = "Price ($)"
  ) +
  theme_minimal(base_size = 14) +
  theme(
    plot.background  = element_rect(fill = PAGE_BG, color = PAGE_BG),
    panel.background = element_rect(fill = PAGE_BG, color = NA),
    panel.grid.major = element_line(color = INK_SOFT, linewidth = 0.3),
    panel.grid.minor = element_blank(),
    panel.grid.major.x = element_blank(),
    axis.title       = element_text(color = INK, size = 20),
    axis.text        = element_text(color = INK_SOFT, size = 16),
    plot.title       = element_text(color = INK, size = 24, face = "plain"),
    legend.background = element_rect(fill = PAGE_BG, color = NA),
    legend.text      = element_text(color = INK_SOFT, size = 16),
    plot.margin      = unit(c(12, 12, 12, 12), "mm")
  )

# --- Save -------------------------------------------------------------------
ggsave(
  filename = sprintf("plot-%s.png", THEME),
  plot     = p,
  device   = ragg::agg_png,
  width    = 16,
  height   = 9,
  units    = "in",
  dpi      = 300
)

Part of Bollinger Bands Indicator Chart on anyplot.ai.

Other implementations