{"spec_id":"indicator-sma","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nindicator-sma: Simple Moving Average (SMA) Indicator Chart\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-05-19\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    annotate,\n    element_line,\n    element_rect,\n    element_text,\n    geom_line,\n    ggplot,\n    labs,\n    scale_color_manual,\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\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\nGOLDEN_COLOR = \"#DDCC77\"  # imprint amber — \"golden cross\" semantic\nDEATH_COLOR = INK_SOFT  # muted gray — \"death\" signal, theme-adaptive\n\n# Data — bull market (days 1–150) followed by bear market (days 150–300),\n# engineered to produce a visible death cross once SMA 200 is available\nnp.random.seed(42)\nn_days = 300\ndates = pd.date_range(\"2024-01-01\", periods=n_days, freq=\"B\")\n\nbase_price = 150\nreturns = np.random.normal(0.0, 0.012, n_days)\ntrend = np.zeros(n_days)\ntrend[:150] = 0.002  # moderate uptrend\ntrend[150:] = -0.003  # sharper reversal\nreturns = returns + trend\nclose = base_price * np.cumprod(1 + returns)\n\n# Calculate SMAs\ndf = pd.DataFrame({\"date\": dates, \"close\": close})\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# Detect SMA 50 / SMA 200 crossovers (golden cross & death cross)\nvalid = df.dropna(subset=[\"sma_50\", \"sma_200\"]).copy()\nvalid[\"diff\"] = valid[\"sma_50\"] - valid[\"sma_200\"]\nvalid[\"prev_diff\"] = valid[\"diff\"].shift(1)\ncrossovers = valid[valid[\"prev_diff\"].notna() & (valid[\"prev_diff\"] * valid[\"diff\"] < 0)].copy()\ncrossovers[\"cross_type\"] = crossovers[\"diff\"].apply(lambda d: \"Golden Cross\" if d > 0 else \"Death Cross\")\n\n# Reshape to long format for plotnine\ndf_long = pd.melt(\n    df, id_vars=[\"date\"], value_vars=[\"close\", \"sma_20\", \"sma_50\", \"sma_200\"], var_name=\"series\", value_name=\"price\"\n)\nseries_labels = {\"close\": \"Price\", \"sma_20\": \"SMA 20\", \"sma_50\": \"SMA 50\", \"sma_200\": \"SMA 200\"}\ndf_long[\"series\"] = df_long[\"series\"].map(series_labels)\nseries_order = [\"Price\", \"SMA 20\", \"SMA 50\", \"SMA 200\"]\ndf_long[\"series\"] = pd.Categorical(df_long[\"series\"], categories=series_order, ordered=True)\n\ncolors = {\"Price\": IMPRINT[0], \"SMA 20\": IMPRINT[1], \"SMA 50\": IMPRINT[2], \"SMA 200\": IMPRINT[3]}\n\n# Separate data to draw SMAs behind price line at different thicknesses\nprice_data = df_long[df_long[\"series\"] == \"Price\"]\nsma_data = df_long[df_long[\"series\"] != \"Price\"]\n\nplot = (\n    ggplot(df_long, aes(x=\"date\", y=\"price\", color=\"series\"))\n    + geom_line(data=sma_data, size=1.2, alpha=0.85)  # SMAs drawn first (behind price)\n    + geom_line(data=price_data, size=2.5, alpha=0.95)  # Price drawn on top, prominently\n    + scale_color_manual(values=colors)\n    + scale_x_datetime(date_breaks=\"2 months\", date_labels=\"%b %Y\")\n    + labs(x=\"Date\", y=\"Price ($)\", title=\"indicator-sma · python · plotnine · anyplot.ai\", color=\"\")\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        plot_title=element_text(size=24, weight=\"bold\", color=INK),\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(size=16, rotation=30, ha=\"right\", color=INK_SOFT),\n        legend_text=element_text(size=16, color=INK_SOFT),\n        legend_position=(0.02, 0.98),\n        legend_direction=\"vertical\",\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        panel_grid_major_y=element_line(color=INK, size=0.3, alpha=0.10),\n        panel_grid_major_x=element_line(alpha=0),\n        panel_grid_minor=element_line(alpha=0),\n    )\n)\n\n# Annotate crossover signals with vertical dashed lines\nfor _, row in crossovers.iterrows():\n    is_golden = row[\"cross_type\"] == \"Golden Cross\"\n    color = GOLDEN_COLOR if is_golden else DEATH_COLOR\n    plot = plot + annotate(\"vline\", xintercept=row[\"date\"], color=color, size=0.9, alpha=0.7, linetype=\"dashed\")\n\nplot.save(f\"plot-{THEME}.png\", dpi=300)\n"}