{"spec_id":"indicator-sma","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nindicator-sma: Simple Moving Average (SMA) Indicator Chart\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 89/100 | Updated: 2026-05-19\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nimport pygal\nfrom pygal.style import Style\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_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\nIMPRINT = (\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\")\n\n# Data — geometric Brownian motion for realistic stock price simulation\nnp.random.seed(42)\ndates = pd.date_range(\"2025-01-02\", periods=300, freq=\"B\")\ninitial_price = 150.0\nreturns = np.random.normal(0.0003, 0.015, len(dates))\nprices = initial_price * np.cumprod(1 + returns)\n\ndf = pd.DataFrame({\"date\": dates, \"close\": prices})\ndf[\"sma_20\"] = df[\"close\"].rolling(window=20).mean()\ndf[\"sma_50\"] = df[\"close\"].rolling(window=50).mean()\ndf[\"sma_200\"] = df[\"close\"].rolling(window=200).mean()\n\n# X-axis labels — show date every 30 trading days, blank otherwise\nx_labels = [d.strftime(\"%Y-%m-%d\") if i % 30 == 0 else \"\" for i, d in enumerate(dates)]\n\n# Style\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=IMPRINT,\n    title_font_size=72,\n    label_font_size=48,\n    major_label_font_size=42,\n    legend_font_size=48,\n    value_font_size=36,\n    stroke_width=5,\n    opacity=\".9\",\n    opacity_hover=\".95\",\n)\n\n# Chart — cubic interpolation smooths the SMA lines for better readability\nchart = pygal.Line(\n    width=4800,\n    height=2700,\n    style=custom_style,\n    title=\"indicator-sma · python · pygal · anyplot.ai\",\n    x_title=\"Date\",\n    y_title=\"Price (USD)\",\n    show_x_guides=False,\n    show_y_guides=True,\n    x_label_rotation=45,\n    show_dots=False,\n    legend_at_bottom=True,\n    legend_box_size=45,\n    margin=60,\n    spacing=30,\n    interpolate=\"cubic\",\n    value_formatter=lambda x: f\"${x:.2f}\",\n)\n\nchart.x_labels = x_labels\n\n# Convert NaN periods (before SMA window fills) to None for pygal\nclose_list = [float(v) for v in df[\"close\"]]\nsma_20_list = [None if pd.isna(v) else float(v) for v in df[\"sma_20\"]]\nsma_50_list = [None if pd.isna(v) else float(v) for v in df[\"sma_50\"]]\nsma_200_list = [None if pd.isna(v) else float(v) for v in df[\"sma_200\"]]\n\n# Annotate market bottom with a tooltip label to guide the viewer's eye\nmin_idx = int(df[\"close\"].idxmin())\nclose_annotated = [\n    {\"value\": float(v), \"label\": \"Market Bottom\"} if i == min_idx else float(v) for i, v in enumerate(df[\"close\"])\n]\n\n# Close price as bold solid line; heavier width creates clear primary/secondary hierarchy\nchart.add(\"Close Price\", close_annotated, stroke_style={\"width\": 7})\nchart.add(\"SMA 20\", sma_20_list, stroke_style={\"width\": 3, \"dasharray\": \"8, 4\"})\nchart.add(\"SMA 50\", sma_50_list, stroke_style={\"width\": 4, \"dasharray\": \"16, 6\"})\nchart.add(\"SMA 200\", sma_200_list, stroke_style={\"width\": 5, \"dasharray\": \"24, 8\"})\n\n# Save\nchart.render_to_png(f\"plot-{THEME}.png\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}