{"spec_id":"stock-event-flags","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nstock-event-flags: Stock Chart with Event Flags\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 87/100 | Updated: 2026-05-27\n\"\"\"\n\nimport os\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_line,\n    geom_point,\n    geom_segment,\n    geom_smooth,\n    geom_text,\n    geom_vline,\n    ggplot,\n    guides,\n    labs,\n    scale_color_manual,\n    scale_shape_manual,\n    scale_size_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\n# Imprint palette — canonical order\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\ncolor_map = {\n    \"Earnings\": IMPRINT_PALETTE[0],  # #009E73 brand green\n    \"Dividend\": IMPRINT_PALETTE[1],  # #C475FD lavender\n    \"News\": IMPRINT_PALETTE[2],  # #4467A3 blue\n    \"Split\": IMPRINT_PALETTE[3],  # #BD8233 ochre\n}\nshape_map = {\"Earnings\": \"s\", \"Dividend\": \"D\", \"News\": \"^\", \"Split\": \"o\"}\n# Earnings get larger markers — primary price catalyst deserves visual emphasis\nsize_map = {\"Earnings\": 5, \"Dividend\": 3, \"News\": 3, \"Split\": 3}\n\n# Data\nnp.random.seed(42)\nn_days = 180\ndates = pd.date_range(\"2024-01-02\", periods=n_days, freq=\"B\")\nreturns = np.random.normal(0.0005, 0.018, n_days)\nprice = 150 * np.cumprod(1 + returns)\n\ndf_price = pd.DataFrame({\"date\": dates, \"close\": price})\n\n# 2:1 split adjustment: halve prices from the day after the split effective date\nsplit_date = pd.Timestamp(\"2024-05-20\")\ndf_price.loc[df_price[\"date\"] > split_date, \"close\"] /= 2\n\nevents = pd.DataFrame(\n    {\n        \"event_date\": pd.to_datetime(\n            [\n                \"2024-01-25\",\n                \"2024-02-15\",\n                \"2024-04-02\",\n                \"2024-04-25\",\n                \"2024-05-20\",\n                \"2024-06-10\",\n                \"2024-07-25\",\n                \"2024-08-15\",\n            ]\n        ),\n        \"event_type\": [\"Earnings\", \"Dividend\", \"News\", \"Earnings\", \"Split\", \"Dividend\", \"Earnings\", \"News\"],\n        \"event_label\": [\n            \"Q4 Beat\",\n            \"Div $0.50\",\n            \"New Product\",\n            \"Q1 Miss\",\n            \"2:1 Split\",\n            \"Div $0.55\",\n            \"Q2 Beat\",\n            \"Partnership\",\n        ],\n    }\n)\n\nevents[\"matched_date\"] = events[\"event_date\"].apply(\n    lambda x: df_price.loc[(df_price[\"date\"] - x).abs().idxmin(), \"date\"]\n)\nevents[\"price_at_event\"] = events[\"matched_date\"].apply(\n    lambda x: df_price.loc[df_price[\"date\"] == x, \"close\"].values[0]\n)\n\nmax_price = df_price[\"close\"].max()\nmin_price = df_price[\"close\"].min()\nprice_range = max_price - min_price\n\n# Alternate flags above/below price to avoid overlap\nflag_offsets = []\nfor i, (_, row) in enumerate(events.iterrows()):\n    if i % 2 == 0:\n        flag_offsets.append(row[\"price_at_event\"] + price_range * 0.15 + (i % 3) * price_range * 0.08)\n    else:\n        flag_offsets.append(row[\"price_at_event\"] - price_range * 0.15 - (i % 3) * price_range * 0.08)\n\nevents[\"flag_y\"] = flag_offsets\n\n# Separate earnings labels (larger) from secondary event labels for size hierarchy\nevents_earnings = events[events[\"event_type\"] == \"Earnings\"]\nevents_other = events[events[\"event_type\"] != \"Earnings\"]\n\n# Plot\nplot = (\n    ggplot()\n    + geom_line(data=df_price, mapping=aes(x=\"date\", y=\"close\"), color=INK_SOFT, size=1.0, alpha=0.9)\n    + geom_smooth(\n        data=df_price,\n        mapping=aes(x=\"date\", y=\"close\"),\n        method=\"lowess\",\n        color=IMPRINT_PALETTE[0],\n        fill=IMPRINT_PALETTE[0],\n        size=0.7,\n        alpha=0.1,\n    )\n    + geom_vline(\n        data=events, mapping=aes(xintercept=\"matched_date\"), linetype=\"dashed\", color=INK_SOFT, alpha=0.35, size=0.4\n    )\n    + geom_segment(\n        data=events,\n        mapping=aes(x=\"matched_date\", xend=\"matched_date\", y=\"price_at_event\", yend=\"flag_y\", color=\"event_type\"),\n        size=0.7,\n    )\n    + geom_point(\n        data=events,\n        mapping=aes(x=\"matched_date\", y=\"flag_y\", color=\"event_type\", shape=\"event_type\", size=\"event_type\"),\n        fill=ELEVATED_BG,\n        stroke=1.5,\n    )\n    + geom_text(\n        data=events_other,\n        mapping=aes(x=\"matched_date\", y=\"flag_y\", label=\"event_label\", color=\"event_type\"),\n        size=3.5,\n        ha=\"center\",\n        va=\"bottom\",\n        nudge_y=price_range * 0.03,\n        fontweight=\"bold\",\n    )\n    + geom_text(\n        data=events_earnings,\n        mapping=aes(x=\"matched_date\", y=\"flag_y\", label=\"event_label\", color=\"event_type\"),\n        size=4,\n        ha=\"center\",\n        va=\"bottom\",\n        nudge_y=price_range * 0.03,\n        fontweight=\"bold\",\n    )\n    + scale_color_manual(values=color_map, name=\"Event Type\")\n    + scale_shape_manual(values=shape_map, name=\"Event Type\")\n    + scale_size_manual(values=size_map)\n    + guides(size=\"none\")\n    + scale_x_datetime(date_labels=\"%b %Y\", date_breaks=\"1 month\")\n    + labs(title=\"stock-event-flags · python · plotnine · anyplot.ai\", x=\"Date\", y=\"Stock Price ($)\")\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_grid_major=element_line(color=INK, size=0.3, alpha=0.15),\n        panel_grid_minor=element_blank(),\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(rotation=45, ha=\"right\", color=INK_SOFT, size=8),\n        axis_line=element_line(color=INK_SOFT),\n        plot_title=element_text(color=INK, size=12, weight=\"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=10, weight=\"bold\"),\n        legend_position=\"right\",\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\", verbose=False)\n"}