{"spec_id":"indicator-bollinger","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nindicator-bollinger: Bollinger Bands Indicator Chart\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-17\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prioritize venv's site-packages over current directory\nif sys.prefix not in sys.path:\n    import site\n\n    site_packages = site.getsitepackages()\n    if isinstance(site_packages, list):\n        sys.path = site_packages + sys.path\n    else:\n        sys.path.insert(0, site_packages)\n\nimport numpy as np\nimport pandas as pd\nimport plotly.graph_objects as go\n\n\n# Theme tokens (see prompts/default-style-guide.md)\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nGRID = \"rgba(26,26,23,0.10)\" if THEME == \"light\" else \"rgba(240,239,232,0.10)\"\nBRAND = \"#009E73\"  # Okabe-Ito position 1 — close price\nIMPRINT = [\n    \"#C475FD\",  # position 2 — upper/lower bands\n    \"#4467A3\",  # position 3 — SMA\n    \"#BD8233\",\n    \"#AE3030\",\n    \"#2ABCCD\",\n    \"#954477\",\n]\n\n# Data - Generate realistic stock price data with Bollinger Bands\nnp.random.seed(42)\nn_periods = 120\n\n# Generate synthetic stock price data with trends and volatility\ndates = pd.date_range(\"2024-01-01\", periods=n_periods, freq=\"B\")  # Business days\nreturns = np.random.normal(0.0005, 0.018, n_periods)\n# Add some trending behavior\ntrend = np.sin(np.linspace(0, 3 * np.pi, n_periods)) * 0.003\nreturns = returns + trend\nprice = 100 * np.cumprod(1 + returns)\n\n# Calculate Bollinger Bands (20-period SMA, 2 standard deviations)\nwindow = 20\ndf = pd.DataFrame({\"date\": dates, \"close\": price})\ndf[\"sma\"] = df[\"close\"].rolling(window=window).mean()\ndf[\"std\"] = df[\"close\"].rolling(window=window).std()\ndf[\"upper_band\"] = df[\"sma\"] + 2 * df[\"std\"]\ndf[\"lower_band\"] = df[\"sma\"] - 2 * df[\"std\"]\n\n# Remove NaN values from rolling calculation\ndf = df.dropna().reset_index(drop=True)\n\n# Create figure\nfig = go.Figure()\n\n# Add the filled area between bands (volatility envelope)\nfig.add_trace(\n    go.Scatter(x=df[\"date\"], y=df[\"upper_band\"], mode=\"lines\", line={\"width\": 0}, showlegend=False, hoverinfo=\"skip\")\n)\n\nfig.add_trace(\n    go.Scatter(\n        x=df[\"date\"],\n        y=df[\"lower_band\"],\n        mode=\"lines\",\n        line={\"width\": 0},\n        fill=\"tonexty\",\n        fillcolor=f\"rgba({int(IMPRINT[0][1:3], 16)}, {int(IMPRINT[0][3:5], 16)}, {int(IMPRINT[0][5:7], 16)}, 0.15)\",\n        name=\"Bollinger Bands (2σ)\",\n        hoverinfo=\"skip\",\n    )\n)\n\n# Add upper band line\nfig.add_trace(\n    go.Scatter(\n        x=df[\"date\"],\n        y=df[\"upper_band\"],\n        mode=\"lines\",\n        line={\"color\": IMPRINT[0], \"width\": 2, \"dash\": \"solid\"},\n        name=\"Upper Band (+2σ)\",\n        hovertemplate=\"Upper: $%{y:.2f}<extra></extra>\",\n    )\n)\n\n# Add lower band line\nfig.add_trace(\n    go.Scatter(\n        x=df[\"date\"],\n        y=df[\"lower_band\"],\n        mode=\"lines\",\n        line={\"color\": IMPRINT[0], \"width\": 2, \"dash\": \"solid\"},\n        name=\"Lower Band (-2σ)\",\n        hovertemplate=\"Lower: $%{y:.2f}<extra></extra>\",\n    )\n)\n\n# Add middle band (SMA) - dashed line\nfig.add_trace(\n    go.Scatter(\n        x=df[\"date\"],\n        y=df[\"sma\"],\n        mode=\"lines\",\n        line={\"color\": IMPRINT[1], \"width\": 3, \"dash\": \"dash\"},\n        name=\"20-day SMA\",\n        hovertemplate=\"SMA: $%{y:.2f}<extra></extra>\",\n    )\n)\n\n# Add price line (close) - most prominent, brand color\nfig.add_trace(\n    go.Scatter(\n        x=df[\"date\"],\n        y=df[\"close\"],\n        mode=\"lines\",\n        line={\"color\": BRAND, \"width\": 3},\n        name=\"Close Price\",\n        hovertemplate=\"Date: %{x|%Y-%m-%d}<br>Close: $%{y:.2f}<extra></extra>\",\n    )\n)\n\n# Update layout with theme-adaptive colors\nfig.update_layout(\n    title={\n        \"text\": \"indicator-bollinger · plotly · anyplot.ai\",\n        \"font\": {\"size\": 28, \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n    },\n    xaxis={\n        \"title\": {\"text\": \"Date\", \"font\": {\"size\": 22, \"color\": INK}},\n        \"tickfont\": {\"size\": 18, \"color\": INK_SOFT},\n        \"showgrid\": True,\n        \"gridcolor\": GRID,\n        \"gridwidth\": 1,\n        \"linecolor\": INK_SOFT,\n    },\n    yaxis={\n        \"title\": {\"text\": \"Price ($)\", \"font\": {\"size\": 22, \"color\": INK}},\n        \"tickfont\": {\"size\": 18, \"color\": INK_SOFT},\n        \"tickformat\": \"$.0f\",\n        \"showgrid\": True,\n        \"gridcolor\": GRID,\n        \"gridwidth\": 1,\n        \"linecolor\": INK_SOFT,\n    },\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    legend={\n        \"font\": {\"size\": 18, \"color\": INK_SOFT},\n        \"orientation\": \"h\",\n        \"yanchor\": \"bottom\",\n        \"y\": 1.02,\n        \"xanchor\": \"center\",\n        \"x\": 0.5,\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n    },\n    margin={\"l\": 100, \"r\": 60, \"t\": 100, \"b\": 80},\n    hovermode=\"x unified\",\n)\n\n# Save as PNG (4800 x 2700 px)\nfig.write_image(f\"plot-{THEME}.png\", width=1600, height=900, scale=3)\n\n# Save interactive HTML version\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}