{"spec_id":"indicator-ema","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nindicator-ema: Exponential Moving Average (EMA) Indicator Chart\nLibrary: letsplot 4.9.0 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-19\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import *\nfrom lets_plot.export import ggsave as export_ggsave\n\n\nLetsPlot.setup_html()\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\"\nRULE = \"rgba(26,26,23,0.10)\" if THEME == \"light\" else \"rgba(240,239,232,0.10)\"\n\n# Okabe-Ito colors for Close Price, EMA 12, EMA 26\nCOLORS = {\"Close Price\": \"#009E73\", \"EMA 12\": \"#C475FD\", \"EMA 26\": \"#4467A3\"}\n\n# Data\nnp.random.seed(42)\ndates = pd.date_range(\"2024-01-02\", periods=120, freq=\"B\")\nreturns = np.random.normal(0.001, 0.015, 120)\nprice = 100 * np.cumprod(1 + returns)\n\nprice_series = pd.Series(price)\nema_12 = price_series.ewm(span=12, adjust=False).mean().values\nema_26 = price_series.ewm(span=26, adjust=False).mean().values\n\ndf = pd.DataFrame({\"date_num\": range(120), \"close\": price, \"ema_12\": ema_12, \"ema_26\": ema_26})\n\n# Detect EMA-12/EMA-26 crossovers for signal annotations\ndiff = ema_12 - ema_26\nsign_changes = np.where(np.diff(np.sign(diff)) != 0)[0]\nbullish_crosses = [int(i) for i in sign_changes if diff[i + 1] > 0]\nbearish_crosses = [int(i) for i in sign_changes if diff[i + 1] < 0]\n\ndf_price = df[[\"date_num\", \"close\"]].rename(columns={\"close\": \"value\"}).copy()\ndf_price[\"series\"] = \"Close Price\"\n\ndf_ema12 = df[[\"date_num\", \"ema_12\"]].rename(columns={\"ema_12\": \"value\"}).copy()\ndf_ema12[\"series\"] = \"EMA 12\"\n\ndf_ema26 = df[[\"date_num\", \"ema_26\"]].rename(columns={\"ema_26\": \"value\"}).copy()\ndf_ema26[\"series\"] = \"EMA 26\"\n\ndf_ema_only = pd.concat([df_ema12, df_ema26], ignore_index=True)\n\ndate_labels = {i: dates[i].strftime(\"%b %d\") for i in range(0, 120, 20)}\n\n# Theme — Y-axis-only major grid (style guide: line charts use Y grid only)\nanyplot_theme = theme(\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG),\n    panel_grid_major_y=element_line(color=RULE, size=0.5),\n    panel_grid_major_x=element_blank(),\n    panel_grid_minor=element_blank(),\n    axis_title=element_text(size=20, color=INK),\n    axis_text=element_text(size=16, color=INK_SOFT),\n    axis_line=element_line(color=INK_SOFT),\n    plot_title=element_text(size=24, color=INK),\n    plot_subtitle=element_text(size=16, color=INK_SOFT),\n    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    legend_text=element_text(size=16, color=INK_SOFT),\n    legend_title=element_text(size=18, color=INK),\n    legend_position=\"right\",\n)\n\n# Plot — crossover vlines first so lines render on top; interactive tooltips (letsplot-exclusive)\nplot = (\n    ggplot(mapping=aes(x=\"date_num\", y=\"value\", color=\"series\"))\n    + geom_line(\n        data=df_price,\n        size=2.5,\n        alpha=0.85,\n        tooltips=layer_tooltips().title(\"Close Price\").line(\"$@value\"),\n    )\n    + geom_line(\n        data=df_ema_only,\n        size=1.5,\n        tooltips=layer_tooltips().title(\"@series\").line(\"$@value\"),\n    )\n    + scale_color_manual(values=COLORS, name=\"Series\")\n    + scale_x_continuous(\n        breaks=list(date_labels.keys()), labels=list(date_labels.values())\n    )\n    + labs(\n        x=\"Date\",\n        y=\"Price (USD)\",\n        title=\"indicator-ema · python · letsplot · anyplot.ai\",\n        subtitle=\"EMA-12 × EMA-26 crossovers — dashed lines mark bullish ↑ and bearish ↓ signals\",\n    )\n    + theme_minimal()\n    + anyplot_theme\n    + ggsize(1600, 900)\n)\n\n# Annotate crossover signals after base plot is built\nfor x in bullish_crosses:\n    plot = plot + geom_vline(xintercept=x, color=\"#009E73\", linetype=\"dashed\", size=0.8, alpha=0.4)\nfor x in bearish_crosses:\n    plot = plot + geom_vline(xintercept=x, color=\"#C475FD\", linetype=\"dashed\", size=0.8, alpha=0.4)\n\n# Save\nexport_ggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=3)\nexport_ggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}