{"spec_id":"stock-event-flags","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nstock-event-flags: Stock Chart with Event Flags\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 89/100 | Updated: 2026-05-27\n\"\"\"\n\n# When run directly (python seaborn.py), this directory is prepended to\n# sys.path, which causes local files like matplotlib.py to shadow the\n# installed matplotlib package.  Remove it so the venv package is found.\nimport pathlib as _pathlib\nimport sys as _sys\n\n\ntry:\n    _here = str(_pathlib.Path(__file__).resolve().parent)\n    _sys.path = [p for p in _sys.path if p not in (\"\", _here)]\nexcept NameError:\n    pass  # exec() context — sys.path is already clean\ndel _sys, _pathlib\n\nimport os\n\nimport matplotlib.dates as mdates\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib.lines import Line2D\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\nBRAND = IMPRINT_PALETTE[0]\n\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.15,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Data — 180 trading days; 2:1 split applied at May 2025 to show real price adjustment\nnp.random.seed(42)\nn_days = 180\ndates = pd.date_range(\"2025-01-02\", periods=n_days, freq=\"B\")\nreturns = np.random.normal(0.0005, 0.018, n_days)\nprice = 150 * np.cumprod(1 + returns)\n\nsplit_date = pd.Timestamp(\"2025-05-08\")\nsplit_idx = dates.searchsorted(split_date)\nprice[split_idx:] = price[split_idx:] * 0.5\n\ndf = pd.DataFrame({\"date\": dates, \"close\": price})\n# Numeric x using matplotlib date floats — matches the internal scale of the date axis,\n# so sns.regplot overlays correctly without a secondary axis\ndf[\"x_mpl\"] = mdates.date2num(df[\"date\"].values)\n\n# Event type styling — Imprint palette positions 2–5\nevent_colors = {\n    \"earnings\": IMPRINT_PALETTE[1],\n    \"dividend\": IMPRINT_PALETTE[2],\n    \"split\": IMPRINT_PALETTE[3],\n    \"news\": IMPRINT_PALETTE[4],\n}\nevent_markers = {\"earnings\": \"s\", \"dividend\": \"D\", \"split\": \"^\", \"news\": \"o\"}\n\nevents = pd.DataFrame(\n    {\n        \"event_date\": pd.to_datetime(\n            [\n                \"2025-01-28\",\n                \"2025-02-14\",\n                \"2025-03-15\",\n                \"2025-04-22\",\n                \"2025-05-08\",\n                \"2025-05-28\",\n                \"2025-06-18\",\n                \"2025-07-24\",\n            ]\n        ),\n        \"event_type\": [\"earnings\", \"dividend\", \"news\", \"earnings\", \"split\", \"dividend\", \"news\", \"earnings\"],\n        \"event_label\": [\n            \"Q4 Earnings\",\n            \"Div $0.50\",\n            \"Product Launch\",\n            \"Q1 Earnings\",\n            \"2:1 Split\",\n            \"Div $0.55\",\n            \"Partnership\",\n            \"Q2 Earnings\",\n        ],\n    }\n)\n\n# Plot\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Stock price line (seaborn lineplot)\nsns.lineplot(data=df, x=\"date\", y=\"close\", ax=ax, color=BRAND, linewidth=2.0, label=\"Stock Price\")\ny_base = df[\"close\"].min() * 0.9\nax.fill_between(df[\"date\"], y_base, df[\"close\"], alpha=0.12, color=BRAND)\n\n# Seaborn-distinctive: per-segment linear regression trend with 95% CI.\n# Two separate regplots (pre- and post-split) avoid a spurious downward slope\n# from the discontinuity; the CI bands give statistical context for each period.\nfor mask in [df[\"date\"] < split_date, df[\"date\"] >= split_date]:\n    df_seg = df[mask]\n    if len(df_seg) > 2:\n        sns.regplot(\n            data=df_seg,\n            x=\"x_mpl\",\n            y=\"close\",\n            ax=ax,\n            scatter=False,\n            color=INK_MUTED,\n            ci=95,\n            line_kws={\"linewidth\": 1.0, \"linestyle\": \":\", \"alpha\": 0.9},\n        )\n\n# Flags — percentage-based offset keeps proportions consistent across the split\nfor idx, (_, event) in enumerate(events.iterrows()):\n    event_date = event[\"event_date\"]\n    event_type = event[\"event_type\"]\n    event_label = event[\"event_label\"]\n\n    closest_idx = int(np.abs(df[\"date\"] - event_date).values.argmin())\n    actual_date = df[\"date\"].iloc[closest_idx]\n    price_at_event = df[\"close\"].iloc[closest_idx]\n\n    flag_pct = price_at_event * 0.06 * (1 + (idx % 3) * 0.3)\n    if idx % 2 == 0:\n        flag_y = price_at_event + flag_pct\n        va = \"bottom\"\n    else:\n        flag_y = price_at_event - flag_pct\n        va = \"top\"\n\n    color = event_colors[event_type]\n    marker = event_markers[event_type]\n\n    ax.plot([actual_date, actual_date], [price_at_event, flag_y], color=color, linestyle=\"--\", linewidth=1.0, alpha=0.7)\n    ax.scatter(\n        [actual_date], [price_at_event], color=color, s=70, marker=marker, zorder=5, edgecolors=PAGE_BG, linewidths=0.8\n    )\n    ax.annotate(\n        event_label,\n        xy=(actual_date, flag_y),\n        fontsize=8,\n        fontweight=\"bold\",\n        color=color,\n        ha=\"center\",\n        va=va,\n        bbox={\n            \"boxstyle\": \"round,pad=0.3\",\n            \"facecolor\": ELEVATED_BG,\n            \"edgecolor\": color,\n            \"linewidth\": 1.0,\n            \"alpha\": 0.95,\n        },\n    )\n\n# Legend — lower right avoids the early-session price area and first-event flags\nlegend_elements = [\n    Line2D([0], [0], color=BRAND, linewidth=2.0, label=\"Stock Price\"),\n    Line2D([0], [0], color=INK_MUTED, linewidth=1.0, linestyle=\":\", alpha=0.9, label=\"Trend (95% CI)\"),\n]\nfor etype, color in event_colors.items():\n    legend_elements.append(\n        plt.scatter(\n            [],\n            [],\n            color=color,\n            marker=event_markers[etype],\n            s=70,\n            label=etype.capitalize(),\n            edgecolors=PAGE_BG,\n            linewidths=0.8,\n        )\n    )\n\nax.legend(\n    handles=legend_elements,\n    loc=\"lower right\",\n    fontsize=8,\n    title=\"Events\",\n    title_fontsize=8,\n    facecolor=ELEVATED_BG,\n    edgecolor=INK_SOFT,\n    framealpha=0.95,\n)\n\n# Style\ntitle = \"stock-event-flags · python · seaborn · anyplot.ai\"\nax.set_title(title, fontsize=12, fontweight=\"medium\", color=INK, pad=10)\nax.set_xlabel(\"Date\", fontsize=10, color=INK)\nax.set_ylabel(\"Stock Price ($)\", fontsize=10, color=INK)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nax.spines[\"left\"].set_color(INK_SOFT)\nax.spines[\"bottom\"].set_color(INK_SOFT)\nax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)\n\nfig.autofmt_xdate(rotation=30)\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}