{"spec_id":"stock-event-flags","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nstock-event-flags: Stock Chart with Event Flags\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 89/100 | Updated: 2026-05-27\n\"\"\"\n\nimport os\n\nimport matplotlib.patches as mpatches\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\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\n# Imprint palette — canonical order; first series is always brand green\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\nBRAND = IMPRINT_PALETTE[0]\n\n# Data\nnp.random.seed(42)\ndates = pd.date_range(\"2025-01-01\", periods=180, freq=\"B\")\n\ninitial_price = 150.0\nreturns = np.random.normal(0.0005, 0.02, size=180)\nprices = initial_price * np.cumprod(1 + returns)\n\nclose = prices\nhigh = close * (1 + np.abs(np.random.normal(0, 0.01, size=180)))\nlow = close * (1 - np.abs(np.random.normal(0, 0.01, size=180)))\nopen_price = (close + np.random.normal(0, 1, size=180)).clip(low, high)\n\ndf = pd.DataFrame({\"date\": dates, \"open\": open_price, \"high\": high, \"low\": low, \"close\": close})\n\nevents = [\n    {\"date\": \"2025-01-28\", \"type\": \"earnings\", \"label\": \"Q4\"},\n    {\"date\": \"2025-02-14\", \"type\": \"dividend\", \"label\": \"0.50\"},\n    {\"date\": \"2025-03-10\", \"type\": \"news\", \"label\": \"Launch\"},\n    {\"date\": \"2025-04-22\", \"type\": \"earnings\", \"label\": \"Q1\"},\n    {\"date\": \"2025-05-08\", \"type\": \"split\", \"label\": \"2:1\"},\n    {\"date\": \"2025-05-20\", \"type\": \"dividend\", \"label\": \"0.50\"},\n    {\"date\": \"2025-06-25\", \"type\": \"news\", \"label\": \"Interview\"},\n    {\"date\": \"2025-07-28\", \"type\": \"earnings\", \"label\": \"Q2\"},\n    {\"date\": \"2025-08-20\", \"type\": \"dividend\", \"label\": \"0.55\"},\n]\n\nevents_df = pd.DataFrame(events)\nevents_df[\"date\"] = pd.to_datetime(events_df[\"date\"])\n\n# Event colors — palette positions 2-5 (position 1 used by price line)\nevent_colors = {\n    \"earnings\": IMPRINT_PALETTE[2],  # #4467A3 blue — financial analytics\n    \"dividend\": IMPRINT_PALETTE[3],  # #BD8233 ochre — value/commodity\n    \"split\": IMPRINT_PALETTE[1],  # #C475FD lavender — corporate action\n    \"news\": IMPRINT_PALETTE[4],  # #AE3030 red — alert semantic fit\n}\n\nevent_markers = {\"earnings\": \"E\", \"dividend\": \"D\", \"split\": \"S\", \"news\": \"!\"}\n\n# Plot\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Price line — first series, brand green\n(price_line,) = ax.plot(df[\"date\"], df[\"close\"], color=BRAND, linewidth=2.5, label=\"Close Price\", zorder=2)\n\n# Light fill under price line\nax.fill_between(df[\"date\"], df[\"close\"].min() * 0.95, df[\"close\"], alpha=0.08, color=BRAND, zorder=1)\n\nprice_min = df[\"close\"].min()\nprice_max = df[\"close\"].max()\nprice_range = price_max - price_min\n\n# Event flags with alternating heights to avoid overlap\nfor idx, event in events_df.iterrows():\n    event_date = event[\"date\"]\n    event_type = event[\"type\"]\n    event_label = event[\"label\"]\n\n    price_row = df.loc[df[\"date\"] == event_date, \"close\"]\n    if len(price_row) == 0:\n        nearest_idx = np.abs(df[\"date\"] - event_date).argmin()\n        price_at_date = df.iloc[nearest_idx][\"close\"]\n    else:\n        price_at_date = price_row.values[0]\n\n    color = event_colors.get(event_type, INK_SOFT)\n    marker_text = event_markers.get(event_type, \"?\")\n\n    height_level = idx % 3\n    flag_y = price_max + price_range * (0.12 + height_level * 0.12)\n\n    # Connector line from price point up to flag\n    ax.plot(\n        [event_date, event_date],\n        [price_at_date, flag_y],\n        color=color,\n        linestyle=\"--\",\n        linewidth=1.2,\n        alpha=0.7,\n        zorder=3,\n    )\n\n    # Marker dot at price level\n    ax.scatter([event_date], [price_at_date], color=color, s=80, zorder=4, edgecolors=PAGE_BG, linewidth=1.0)\n\n    # Flag annotation box\n    ax.annotate(\n        f\"{marker_text} {event_label}\",\n        xy=(event_date, flag_y),\n        fontsize=8,\n        fontweight=\"bold\",\n        color=\"white\",\n        ha=\"center\",\n        va=\"center\",\n        bbox={\n            \"boxstyle\": \"round,pad=0.3\",\n            \"facecolor\": color,\n            \"edgecolor\": ELEVATED_BG,\n            \"linewidth\": 1.5,\n            \"alpha\": 0.92,\n        },\n        zorder=10,\n    )\n\n# Legend — price line + all four event types\nlegend_handles = [price_line] + [mpatches.Patch(color=c, label=et.capitalize()) for et, c in event_colors.items()]\nlegend_labels = [\"Close Price\"] + [et.capitalize() for et in event_colors]\nleg = ax.legend(handles=legend_handles, labels=legend_labels, loc=\"lower right\", fontsize=8, framealpha=0.9)\nleg.get_frame().set_facecolor(ELEVATED_BG)\nleg.get_frame().set_edgecolor(INK_SOFT)\nplt.setp(leg.get_texts(), color=INK_SOFT)\n\n# Style\ntitle = \"stock-event-flags · python · matplotlib · anyplot.ai\"\nax.set_title(title, fontsize=12, fontweight=\"medium\", color=INK)\nax.set_xlabel(\"Date\", fontsize=10, color=INK)\nax.set_ylabel(\"Price (USD)\", fontsize=10, color=INK)\nax.tick_params(axis=\"both\", labelsize=8, labelcolor=INK_SOFT, color=INK_SOFT)\nax.tick_params(which=\"both\", length=0)\n\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nfor s in (\"left\", \"bottom\"):\n    ax.spines[s].set_color(INK_SOFT)\n\nax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)\nax.set_ylim(price_min * 0.95, price_max + price_range * 0.55)\n\nplt.setp(ax.get_xticklabels(), rotation=30, ha=\"right\")\n\nfig.subplots_adjust(left=0.09, right=0.97, top=0.91, bottom=0.15)\n\n# Save\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}