Bollinger Bands Indicator Chart — MUI X Charts

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 MUI X Charts

Renders

JavaScript source (MUI X Charts)

// anyplot.ai
// indicator-bollinger: Bollinger Bands Indicator Chart
// Library: muix 7.29.1 | JavaScript 22.23.2
// Quality: 93/100 | Created: 2026-09-02
import { LineChart } from "@mui/x-charts/LineChart";
import { Box, Typography } from "@mui/material";

const t = window.ANYPLOT_TOKENS;
const [BRAND, SMA_COLOR, BAND_COLOR] = t.palette;

const hexToRgba = (hex, alpha) => {
  const r = parseInt(hex.slice(1, 3), 16);
  const g = parseInt(hex.slice(3, 5), 16);
  const b = parseInt(hex.slice(5, 7), 16);
  return `rgba(${r}, ${g}, ${b}, ${alpha})`;
};
const BAND_FILL = hexToRgba(BAND_COLOR, 0.16);

// --- Data (in-memory, deterministic) ----------------------------------------
const SYMBOL = "MRDN";
const N = 120;
const WINDOW = 20;
const START_PRICE = 162.5;

// Fixed-seed LCG (Numerical Recipes constants) — the browser has no seeded RNG.
let seed = 42;
const rand = () => {
  seed = (seed * 1664525 + 1013904223) % 4294967296;
  return seed / 4294967296;
};
const gaussian = () => {
  const u1 = Math.max(rand(), 1e-9);
  const u2 = rand();
  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
};

// Trading dates (weekdays only)
const dates = [];
const cursor = new Date(2024, 0, 2);
while (dates.length < N) {
  const day = cursor.getDay();
  if (day !== 0 && day !== 6) dates.push(new Date(cursor));
  cursor.setDate(cursor.getDate() + 1);
}

// Daily close, with a calm "squeeze" regime and a high-volatility breakout
// regime so the bands visibly narrow and then widen (see spec "Notes").
const close = [START_PRICE];
for (let i = 1; i < N; i += 1) {
  const squeeze = i >= 40 && i < 65;
  const breakout = i >= 90;
  const dailyVolPct = squeeze ? 0.35 : breakout ? 1.7 : 0.9;
  const driftPct = 0.05;
  const changePct = driftPct + dailyVolPct * gaussian();
  close.push(Math.max(20, close[i - 1] * (1 + changePct / 100)));
}

const round2 = (v) => Math.round(v * 100) / 100;

// Rolling 20-period SMA and population std dev -> upper/lower bands.
const sma = [];
const upperBand = [];
const lowerBand = [];
const bandWidth = [];
for (let i = 0; i < N; i += 1) {
  if (i < WINDOW - 1) {
    sma.push(null);
    upperBand.push(null);
    lowerBand.push(null);
    bandWidth.push(null);
    continue;
  }
  const windowSlice = close.slice(i - WINDOW + 1, i + 1);
  const mean = windowSlice.reduce((sum, v) => sum + v, 0) / WINDOW;
  const variance =
    windowSlice.reduce((sum, v) => sum + (v - mean) ** 2, 0) / WINDOW;
  const stdDev = Math.sqrt(variance);
  const upper = round2(mean + 2 * stdDev);
  const lower = round2(mean - 2 * stdDev);
  sma.push(round2(mean));
  upperBand.push(upper);
  lowerBand.push(lower);
  bandWidth.push(round2(upper - lower));
}
const closeRounded = close.map(round2);

// Explicit y-axis bounds: the stacked band series' domain runs from 0 (its
// hidden base) to the upper band, which would force the axis to include 0
// and waste most of the canvas on empty space above/below the real range.
const allValues = [...closeRounded, ...sma, ...upperBand, ...lowerBand].filter(
  (v) => v !== null,
);
const dataMin = Math.min(...allValues);
const dataMax = Math.max(...allValues);
const axisPadding = (dataMax - dataMin) * 0.15;
const yMin = Math.floor((dataMin - axisPadding) / 5) * 5;
const yMax = Math.ceil((dataMax + axisPadding) / 5) * 5;

const TITLE = `${SYMBOL} · indicator-bollinger · javascript · muix · anyplot.ai`;

// --- Chart (default-exported component — the harness mounts it) -------------
export default function Chart() {
  const { width, height } = window.ANYPLOT_SIZE;
  const titleHeight = 64;

  return (
    <Box sx={{ width, height, display: "flex", flexDirection: "column" }}>
      <Typography
        color="text.primary"
        sx={{
          height: titleHeight,
          lineHeight: `${titleHeight}px`,
          pl: 1,
          fontSize: 22,
          fontWeight: 600,
        }}
      >
        {TITLE}
      </Typography>
      <LineChart
        width={width}
        height={height - titleHeight}
        skipAnimation
        margin={{ top: 72, right: 48, bottom: 76, left: 132 }}
        xAxis={[
          {
            data: dates,
            scaleType: "time",
            label: "Trading Date",
            labelStyle: { fontSize: 16 },
            tickLabelStyle: { fontSize: 14 },
            valueFormatter: (date) =>
              date.toLocaleDateString("en-US", {
                month: "short",
                day: "numeric",
              }),
          },
        ]}
        yAxis={[
          {
            label: "Price (USD)",
            labelStyle: { fontSize: 16 },
            // tickFontSize pushes the rotated axis title clear of the "$NNN"
            // tick labels — the actual tick text size is set via tickLabelStyle.
            tickFontSize: 40,
            tickLabelStyle: { fontSize: 14 },
            valueFormatter: (v) => `$${v.toFixed(0)}`,
            min: yMin,
            max: yMax,
          },
        ]}
        series={[
          {
            id: "bandBase",
            data: lowerBand,
            area: true,
            stack: "band",
            color: BAND_COLOR,
            showMark: false,
            connectNulls: false,
            curve: "monotoneX",
          },
          {
            id: "bandWidth",
            data: bandWidth,
            area: true,
            stack: "band",
            color: BAND_COLOR,
            showMark: false,
            connectNulls: false,
            curve: "monotoneX",
            label: "Bollinger Band (SMA ± 2σ)",
          },
          {
            id: "sma",
            data: sma,
            color: SMA_COLOR,
            showMark: false,
            connectNulls: false,
            curve: "monotoneX",
            label: "20-Day SMA",
          },
          {
            id: "close",
            data: closeRounded,
            color: BRAND,
            showMark: false,
            curve: "monotoneX",
            label: `${SYMBOL} Close`,
          },
        ]}
        grid={{ horizontal: true }}
        slotProps={{
          legend: {
            direction: "row",
            position: { vertical: "top", horizontal: "middle" },
            labelStyle: { fontSize: 14 },
          },
        }}
        sx={{
          "& .MuiLineElement-root": { strokeWidth: 2 },
          "& .MuiLineElement-series-close": { strokeWidth: 3.5 },
          "& .MuiLineElement-series-sma": {
            strokeWidth: 2.25,
            strokeDasharray: "10 6",
          },
          "& .MuiLineElement-series-bandBase": {
            strokeWidth: 1.5,
            strokeOpacity: 0.6,
          },
          "& .MuiLineElement-series-bandWidth": {
            strokeWidth: 1.5,
            strokeOpacity: 0.6,
          },
          "& .MuiAreaElement-series-bandBase": { fill: "none" },
          "& .MuiAreaElement-series-bandWidth": { fill: BAND_FILL },
        }}
      />
    </Box>
  );
}

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/indicator-bollinger/muix/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": "muix",
  "page": "https://anyplot.ai/indicator-bollinger/javascript/muix",
  "hub": "https://anyplot.ai/indicator-bollinger",
  "code_json": "https://api.anyplot.ai/specs/indicator-bollinger/muix/code",
  "spec_json": "https://api.anyplot.ai/specs/indicator-bollinger",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/indicator-bollinger/javascript/muix/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/indicator-bollinger/javascript/muix/plot-dark.png",
  "interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/indicator-bollinger/javascript/muix/plot-light.html",
  "interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/indicator-bollinger/javascript/muix/plot-dark.html",
  "quality_score": 93.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Bollinger Bands Indicator Chart on anyplot.ai.

Other implementations