{"spec_id":"indicator-bollinger","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nindicator-bollinger: Bollinger Bands Indicator Chart\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-05-17\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    element_line,\n    element_rect,\n    element_text,\n    geom_line,\n    geom_ribbon,\n    ggplot,\n    labs,\n    scale_x_datetime,\n    theme,\n    theme_minimal,\n)\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nRULE = \"rgba(26,26,23,0.10)\" if THEME == \"light\" else \"rgba(240,239,232,0.10)\"\n\n# Okabe-Ito palette\nBRAND = \"#009E73\"  # Close price - first series\nBAND_COLOR = \"#4467A3\"  # Bollinger bands - second series\n\n# Data - Generate realistic stock price with Bollinger Bands\nnp.random.seed(42)\nn_periods = 120\ndates = pd.date_range(\"2024-01-01\", periods=n_periods, freq=\"B\")\n\n# Generate price with trend and volatility\nreturns = np.random.normal(0.001, 0.018, n_periods)\nprice = 100 * np.cumprod(1 + returns)\n\n# Add volatility clusters for interesting band patterns\nvolatility_shock = np.zeros(n_periods)\nvolatility_shock[30:45] = np.random.normal(0, 0.025, 15)\nvolatility_shock[80:95] = np.random.normal(0, 0.02, 15)\nprice = price * (1 + volatility_shock)\n\n# Calculate Bollinger Bands (20-period SMA, 2 standard deviations)\nwindow = 20\nsma = pd.Series(price).rolling(window=window).mean()\nstd = pd.Series(price).rolling(window=window).std()\nupper_band = sma + 2 * std\nlower_band = sma - 2 * std\n\n# Create DataFrame\ndf = pd.DataFrame({\"date\": dates, \"close\": price, \"sma\": sma, \"upper_band\": upper_band, \"lower_band\": lower_band})\n\n# Remove NaN values from rolling calculation\ndf = df.dropna().reset_index(drop=True)\n\n# Plot\nplot = (\n    ggplot(df)\n    + geom_ribbon(aes(x=\"date\", ymin=\"lower_band\", ymax=\"upper_band\"), fill=BAND_COLOR, alpha=0.15)\n    + geom_line(aes(x=\"date\", y=\"upper_band\"), color=BAND_COLOR, size=0.8, linetype=\"dashed\")\n    + geom_line(aes(x=\"date\", y=\"lower_band\"), color=BAND_COLOR, size=0.8, linetype=\"dashed\")\n    + geom_line(aes(x=\"date\", y=\"sma\"), color=BAND_COLOR, size=1.0, linetype=\"dotted\")\n    + geom_line(aes(x=\"date\", y=\"close\"), color=BRAND, size=1.3)\n    + scale_x_datetime(date_labels=\"%b %Y\", date_breaks=\"1 month\")\n    + labs(x=\"Date\", y=\"Price (USD)\", title=\"indicator-bollinger · plotnine · anyplot.ai\")\n    + theme_minimal()\n    + theme(\n        figure_size=(16, 9),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        panel_grid_major=element_line(color=INK, size=0.3, alpha=0.10),\n        panel_grid_minor=element_line(color=INK, size=0.2, alpha=0.05),\n        panel_border=element_rect(color=INK_SOFT, fill=None),\n        axis_title=element_text(size=20, color=INK),\n        axis_text=element_text(size=16, color=INK_SOFT),\n        axis_text_x=element_text(angle=45, ha=\"right\"),\n        axis_line=element_line(color=INK_SOFT),\n        plot_title=element_text(size=24, color=INK),\n        text=element_text(size=14, color=INK),\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=300, verbose=False)\n"}