Bollinger Bands Indicator Chart — Apache ECharts

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 Apache ECharts

Renders

JavaScript source (Apache ECharts)

// anyplot.ai
// indicator-bollinger: Bollinger Bands Indicator Chart
// Library: echarts 6.1.0 | JavaScript 22.23.2
// Quality: 92/100 | Created: 2026-09-02

//# anyplot-orientation: landscape
const t = window.ANYPLOT_TOKENS;

// --- Data (in-memory, deterministic) ----------------------------------------
// Aurora Biotech (ABTX) daily close over 120 trading days, with a 20-period
// SMA and +-2 standard-deviation Bollinger Bands.
function lcg(seed) {
  let state = seed >>> 0;
  return function next() {
    state = (1103515245 * state + 12345) >>> 0;
    return state / 4294967296;
  };
}
const rand = lcg(20240102);
function gaussian() {
  const u1 = Math.max(rand(), 1e-9);
  const u2 = rand();
  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
}

const NUM_DAYS = 120;
const PERIOD = 20;
const NUM_STD_DEV = 2;
const MONTH_NAMES = [
  "Jan",
  "Feb",
  "Mar",
  "Apr",
  "May",
  "Jun",
  "Jul",
  "Aug",
  "Sep",
  "Oct",
  "Nov",
  "Dec",
];

const dates = [];
const closes = [];
let cursor = new Date(Date.UTC(2024, 0, 2));
let price = 84;
while (dates.length < NUM_DAYS) {
  const day = cursor.getUTCDay();
  if (day !== 0 && day !== 6) {
    dates.push(`${MONTH_NAMES[cursor.getUTCMonth()]} ${cursor.getUTCDate()}`);
    price += gaussian() * 1.1 + 0.05;
    closes.push(Math.max(price, 5));
  }
  cursor.setUTCDate(cursor.getUTCDate() + 1);
}

const sma = new Array(NUM_DAYS).fill(null);
const upperBand = new Array(NUM_DAYS).fill(null);
const lowerBand = new Array(NUM_DAYS).fill(null);
const bandSpread = new Array(NUM_DAYS).fill(null);
for (let i = PERIOD - 1; i < NUM_DAYS; i += 1) {
  const window = closes.slice(i - PERIOD + 1, i + 1);
  const mean = window.reduce((sum, v) => sum + v, 0) / PERIOD;
  const variance = window.reduce((sum, v) => sum + (v - mean) ** 2, 0) / PERIOD;
  const stdDev = Math.sqrt(variance);
  sma[i] = mean;
  upperBand[i] = mean + NUM_STD_DEV * stdDev;
  lowerBand[i] = mean - NUM_STD_DEV * stdDev;
  bandSpread[i] = upperBand[i] - lowerBand[i];
}

const bandColor = t.palette[1];

// Y-axis auto-scale: pad a few dollars beyond the close/band extremes so the
// squeeze/expansion pattern fills the canvas instead of sitting in a $0-$100 strip.
const spanValues = [];
for (let i = 0; i < NUM_DAYS; i += 1) {
  spanValues.push(closes[i]);
  if (upperBand[i] !== null) spanValues.push(upperBand[i]);
  if (lowerBand[i] !== null) spanValues.push(lowerBand[i]);
}
const dataMin = Math.min(...spanValues);
const dataMax = Math.max(...spanValues);
const yPad = (dataMax - dataMin) * 0.1;
const yMin = Math.floor(dataMin - yPad);
const yMax = Math.ceil(dataMax + yPad);

// Locate the tightest squeeze (minimum band spread) to annotate the key
// Bollinger pattern instead of leaving the story purely implicit in color.
// Search starts one full period after warm-up so the low-variance edge right
// at the SMA's first valid bar isn't mistaken for a genuine volatility squeeze.
let squeezeIndex = -1;
let minSpread = Infinity;
for (let i = PERIOD * 2; i < NUM_DAYS; i += 1) {
  if (bandSpread[i] !== null && bandSpread[i] < minSpread) {
    minSpread = bandSpread[i];
    squeezeIndex = i;
  }
}

// --- Init --------------------------------------------------------------------
const chart = echarts.init(document.getElementById("container"));

// --- Option -------------------------------------------------------------------
chart.setOption({
  animation: false,
  color: t.palette,
  backgroundColor: "transparent",
  title: {
    text: "indicator-bollinger · javascript · echarts · anyplot.ai",
    left: "center",
    textStyle: { color: t.ink, fontSize: 22 },
  },
  legend: {
    data: ["Close", "SMA (20)", "Upper Band", "Lower Band"],
    top: 60,
    textStyle: { color: t.ink, fontSize: 16 },
  },
  grid: { left: 90, right: 60, top: 140, bottom: 80 },
  tooltip: {
    trigger: "axis",
    formatter: (params) =>
      params
        .filter((p) => !p.seriesName.includes("band fill"))
        .map(
          (p) =>
            `${p.marker} ${p.seriesName}: $${p.value == null ? "-" : p.value.toFixed(2)}`,
        )
        .join("<br/>"),
  },
  xAxis: {
    type: "category",
    data: dates,
    boundaryGap: false,
    axisLabel: { color: t.inkSoft, fontSize: 14, interval: 9 },
    axisLine: { lineStyle: { color: t.inkSoft } },
    splitLine: { show: false },
  },
  yAxis: {
    type: "value",
    name: "Price (USD)",
    nameTextStyle: { color: t.ink, fontSize: 14 },
    min: yMin,
    max: yMax,
    axisLabel: { color: t.inkSoft, fontSize: 14, formatter: "${value}" },
    axisLine: { lineStyle: { color: t.inkSoft } },
    splitLine: { lineStyle: { color: t.grid } },
  },
  series: [
    {
      name: "Lower Band (band fill)",
      type: "line",
      data: lowerBand,
      stack: "band",
      symbol: "none",
      lineStyle: { opacity: 0 },
      areaStyle: { opacity: 0 },
      z: 1,
    },
    {
      name: "Band Spread (band fill)",
      type: "line",
      data: bandSpread,
      stack: "band",
      symbol: "none",
      lineStyle: { opacity: 0 },
      areaStyle: { color: bandColor, opacity: 0.15 },
      z: 1,
    },
    {
      name: "Upper Band",
      type: "line",
      data: upperBand,
      symbol: "none",
      itemStyle: { color: bandColor },
      lineStyle: {
        color: bandColor,
        width: 1.5,
        type: "dashed",
        opacity: 0.75,
      },
      z: 2,
    },
    {
      name: "Lower Band",
      type: "line",
      data: lowerBand,
      symbol: "none",
      itemStyle: { color: bandColor },
      lineStyle: {
        color: bandColor,
        width: 1.5,
        type: "dashed",
        opacity: 0.75,
      },
      z: 2,
      markLine: {
        silent: true,
        symbol: "none",
        lineStyle: { color: t.inkSoft, type: "dashed", width: 1 },
        label: {
          formatter: "Tightest squeeze",
          color: t.inkSoft,
          fontSize: 12,
          position: "insideEndTop",
        },
        data: [{ xAxis: dates[squeezeIndex] }],
      },
    },
    {
      name: "SMA (20)",
      type: "line",
      data: sma,
      symbol: "none",
      itemStyle: { color: t.ink },
      lineStyle: { color: t.ink, width: 2, type: "dotted" },
      z: 3,
    },
    {
      name: "Close",
      type: "line",
      data: closes,
      symbol: "none",
      itemStyle: { color: t.palette[0] },
      lineStyle: { color: t.palette[0], width: 3 },
      z: 4,
    },
  ],
});

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/indicator-bollinger/echarts/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "indicator-bollinger",
  "language": "javascript",
  "library": "echarts",
  "page": "https://anyplot.ai/indicator-bollinger/javascript/echarts",
  "hub": "https://anyplot.ai/indicator-bollinger",
  "code_json": "https://api.anyplot.ai/specs/indicator-bollinger/echarts/code",
  "spec_json": "https://api.anyplot.ai/specs/indicator-bollinger",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/indicator-bollinger/javascript/echarts/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/indicator-bollinger/javascript/echarts/plot-dark.png",
  "interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/indicator-bollinger/javascript/echarts/plot-light.html",
  "interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/indicator-bollinger/javascript/echarts/plot-dark.html",
  "quality_score": 92.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Bollinger Bands Indicator Chart on anyplot.ai.

Other implementations