{"spec_id":"line-stock-comparison","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nline-stock-comparison: Stock Price Comparison Chart\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-05-23\n\"\"\"\n\nimport os\nimport sys\n\n\n_script_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if not p or os.path.abspath(p) != _script_dir]\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_hline,\n    geom_line,\n    geom_point,\n    geom_ribbon,\n    geom_text,\n    ggplot,\n    labs,\n    scale_color_manual,\n    scale_x_date,\n    theme,\n    theme_minimal,\n)\n\n\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\", \"#AE3030\", \"#4467A3\"]\n\n# Data\nnp.random.seed(42)\nn_days = 252\ndates = pd.date_range(\"2024-01-02\", periods=n_days, freq=\"B\")\n\nsymbols = [\"AAPL\", \"GOOGL\", \"MSFT\", \"SPY\"]\nparams = [(0.0005, 0.013), (0.0003, 0.015), (0.0006, 0.012), (0.0004, 0.008)]\n\ndfs = []\nfor symbol, (drift, vol) in zip(symbols, params, strict=True):\n    daily_returns = np.random.normal(drift, vol, n_days)\n    prices = 100 * np.exp(np.cumsum(daily_returns))\n    dfs.append(pd.DataFrame({\"date\": dates, \"symbol\": symbol, \"rebased\": prices}))\n\ndf = pd.concat(dfs, ignore_index=True)\n\n# End-of-line annotation data\nlast_df = df.groupby(\"symbol\").apply(lambda g: g.iloc[-1]).reset_index(drop=True)\nlast_df[\"label\"] = last_df[\"rebased\"].apply(lambda x: f\"{x:.0f}\")\nlast_df[\"label_date\"] = last_df[\"date\"] + pd.Timedelta(days=7)\n\n# SPY outperformance ribbon: shades the region where SPY beats the 100 baseline\nspy_df = df[df[\"symbol\"] == \"SPY\"].copy()\nothers_df = df[df[\"symbol\"] != \"SPY\"].copy()\nspy_ribbon_df = spy_df[[\"date\", \"rebased\"]].rename(columns={\"rebased\": \"ymax\"}).copy()\nspy_ribbon_df[\"ymin\"] = 100.0\nspy_ribbon_df = spy_ribbon_df[spy_ribbon_df[\"ymax\"] > 100]\n\nx_max = dates[-1] + pd.Timedelta(days=30)\n\n# Plot\nplot = (\n    ggplot(df, aes(x=\"date\", y=\"rebased\", color=\"symbol\"))\n    + geom_ribbon(\n        data=spy_ribbon_df,\n        mapping=aes(x=\"date\", ymin=\"ymin\", ymax=\"ymax\"),\n        fill=IMPRINT[3],\n        alpha=0.08,\n        inherit_aes=False,\n    )\n    + geom_hline(yintercept=100, linetype=\"dashed\", color=INK_SOFT, size=0.6)\n    + geom_line(data=others_df, size=0.9)\n    + geom_line(data=spy_df, size=1.6)\n    + geom_point(data=last_df, size=2.5, show_legend=False)\n    + geom_text(data=last_df, mapping=aes(x=\"label_date\", label=\"label\"), size=7, ha=\"left\", show_legend=False)\n    + scale_color_manual(values=IMPRINT)\n    + scale_x_date(date_labels=\"%b '%y\", date_breaks=\"2 months\", limits=[dates[0], x_max])\n    + labs(\n        x=\"Date\",\n        y=\"Rebased Price (Start = 100)\",\n        title=\"line-stock-comparison · python · plotnine · anyplot.ai\",\n        color=\"Symbol\",\n    )\n    + theme_minimal()\n    + theme(\n        figure_size=(8, 4.5),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        panel_border=element_blank(),\n        panel_grid_major=element_line(color=INK, size=0.3, alpha=0.10),\n        panel_grid_minor=element_blank(),\n        axis_line=element_line(color=INK_SOFT, size=0.5),\n        axis_title=element_text(color=INK, size=10),\n        axis_text=element_text(color=INK_SOFT, size=8),\n        axis_text_x=element_text(angle=45, ha=\"right\"),\n        plot_title=element_text(color=INK, size=12, fontweight=\"bold\"),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_text=element_text(color=INK_SOFT, size=8),\n        legend_title=element_text(color=INK, size=9),\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\")\n"}