{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# What happened after the annual filing?\n", "Use the SEC acceptance timestamp as the event anchor and measure a conservative post-filing return window.\n", "AAPL 10-K filings within 2020\u20132025 price coverage. The return window starts at the next session close after the UTC acceptance date. No attribution of price moves to filings. VIX before the event date supplies market-risk context; it does not explain the return." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"Use the SEC acceptance timestamp as the event anchor and measure a conservative post-filing return window.\n", "\n", "Research example. Live observations can change.\n", "\"\"\"\n", "\n", "from datetime import UTC, datetime\n", "from decimal import Decimal\n", "from textwrap import fill\n", "\n", "import matplotlib.pyplot as plt\n", "import pandas as pd\n", "\n", "AS_OF = datetime.now(UTC).date()\n", "NAVY, BLUE, PINK = \"#132244\", \"#8fb9ff\", \"#e7a2cc\"\n", "\n", "\n", "def fetch(qj):\n", " return {\n", " \"vix_history\": qj.domains.call(\n", " \"macro.indicators.get_fred_data_series_by_id\",\n", " provider=\"fred\",\n", " search_id=\"VIXCLS\",\n", " start=\"2020-01-01\",\n", " end=\"2025-12-31\",\n", " ),\n", " \"filings\": qj.domains.call(\n", " \"regulatory.filings.get_company_filings\", provider=\"sec\", symbol=\"AAPL\", form_type=\"10-K\", limit=12\n", " ),\n", " \"prices\": qj.domains.call(\n", " \"equity.pricing.get_historical_prices\",\n", " provider=\"eod\",\n", " symbols=[\"AAPL\"],\n", " start_date=\"2020-01-01\",\n", " end_date=\"2025-12-31\",\n", " frequency=\"1d\",\n", " ),\n", " }\n", "\n", "\n", "def analyze(raw):\n", " filings = frame(raw[\"filings\"], \"acceptanceDateTime\")\n", " prices = history(raw[\"prices\"], \"AAPL\")[\"price\"]\n", " vix = macro(raw[\"vix_history\"], \"VIXCLS\")\n", " rows = []\n", " for filing in filings.to_dict(\"records\"):\n", " accepted = filing[\"acceptanceDateTime\"]\n", " if accepted < prices.index.min():\n", " continue\n", " window = prices.loc[prices.index > accepted.normalize()]\n", " if len(window) < 6:\n", " continue\n", " prior_vix = vix.loc[vix.index < accepted.normalize()]\n", " if prior_vix.empty:\n", " continue\n", " rows.append(\n", " {\n", " \"Prior VIX\": prior_vix.iloc[-1],\n", " \"Accepted\": accepted,\n", " \"Entry close\": window.index[0],\n", " \"Exit close\": window.index[5],\n", " \"Return %\": 100 * (ratio(window.iloc[5], window.iloc[0]) - 1),\n", " }\n", " )\n", " result = pd.DataFrame(rows)\n", " if result.empty:\n", " raise ValueError(\"No filings with complete post-filing price coverage\")\n", " fig, (ax,) = canvas(\"AAPL \u00b7 the window after a 10-K\", \"Five-session adjusted return %\")\n", " colors = [BLUE if value >= 0 else PINK for value in result[\"Return %\"]]\n", " ax.bar(result[\"Accepted\"].dt.strftime(\"%Y-%m-%d\"), result[\"Return %\"], color=colors)\n", " for number, (_, row) in enumerate(result.iterrows()):\n", " ax.annotate(\n", " f\"VIX {row['Prior VIX']:.1f}\",\n", " (number, row[\"Return %\"]),\n", " xytext=(0, 8 if row[\"Return %\"] >= 0 else -16),\n", " textcoords=\"offset points\",\n", " ha=\"center\",\n", " fontsize=8,\n", " color=\"#e9eef8\",\n", " )\n", " ax.axhline(0, color=\"#afc1dc\", lw=0.8)\n", " ax.set_xlabel(\"SEC acceptance date\")\n", " ax.tick_params(axis=\"x\", rotation=20)\n", " return result, fig\n", "\n", "\n", "def payload(response):\n", " if hasattr(response, \"to_dict\"):\n", " response = response.to_dict()\n", " while isinstance(response, dict):\n", " if response.get(\"error\") or response.get(\"error_code\") or response.get(\"success\") is False:\n", " raise ValueError(\"Provider request failed\")\n", " if \"data\" in response:\n", " response = response[\"data\"]\n", " elif \"value\" in response:\n", " response = response[\"value\"]\n", " else:\n", " break\n", " return response\n", "\n", "\n", "def frame(response, date=\"date\"):\n", " rows = payload(response)\n", " if not isinstance(rows, list) or not rows:\n", " raise ValueError(\"No provider observations returned\")\n", " result = pd.DataFrame(rows)\n", " if date:\n", " result[date] = pd.to_datetime(result[date], utc=True)\n", " result = result.sort_values(date)\n", " return result\n", "\n", "\n", "def decimal(value):\n", " if pd.isna(value):\n", " raise ValueError(\"Required monetary value or quantity is missing\")\n", " return Decimal(str(value))\n", "\n", "\n", "def ratio(numerator, denominator):\n", " denominator = decimal(denominator)\n", " if denominator == 0:\n", " raise ValueError(\"Required denominator is zero\")\n", " return float(decimal(numerator) / denominator)\n", "\n", "\n", "def history(response, symbol, adjusted=True):\n", " values = payload(response)\n", " if not isinstance(values, dict) or symbol not in values:\n", " raise ValueError(f\"No price coverage for {symbol}\")\n", " result = frame(values[symbol]).set_index(\"date\")\n", " field = \"adjusted_close\" if adjusted else \"close\"\n", " result[\"price\"] = result[field].map(decimal)\n", " result[\"volume\"] = result[\"volume\"].map(decimal)\n", " if result.index.has_duplicates:\n", " raise ValueError(\"Duplicate trading dates\")\n", " return result\n", "\n", "\n", "def macro(response, column):\n", " values = frame(response).set_index(\"date\")[column]\n", " values = pd.to_numeric(values, errors=\"coerce\").dropna()\n", " if values.empty:\n", " raise ValueError(f\"No numeric observations for {column}\")\n", " return values\n", "\n", "\n", "def canvas(title, ylabel, panels=1):\n", " plt.rcParams.update(\n", " {\n", " \"figure.facecolor\": NAVY,\n", " \"axes.facecolor\": NAVY,\n", " \"savefig.facecolor\": NAVY,\n", " \"text.color\": \"#e9eef8\",\n", " \"axes.labelcolor\": \"#afc1dc\",\n", " \"xtick.color\": \"#afc1dc\",\n", " \"ytick.color\": \"#afc1dc\",\n", " \"axes.edgecolor\": \"#34486b\",\n", " \"grid.color\": \"#34486b\",\n", " \"font.size\": 10,\n", " \"axes.prop_cycle\": plt.cycler(color=[BLUE, PINK, \"#ba9dea\"]),\n", " }\n", " )\n", " fig, axes = plt.subplots(panels, 1, figsize=(9, 6.1), squeeze=False, layout=\"constrained\")\n", " fig.suptitle(fill(title, width=58), fontsize=17, x=0.04, ha=\"left\", weight=\"medium\")\n", " for ax in axes.flat:\n", " ax.grid(axis=\"y\", alpha=0.45)\n", " ax.set_axisbelow(True)\n", " ax.spines[[\"top\", \"right\"]].set_visible(False)\n", " axes.flat[0].set_ylabel(ylabel)\n", " return fig, list(axes.flat)\n", "\n", "\n", "def run():\n", " from quantjourney.sdk import QuantJourneyAPI\n", "\n", " qj = QuantJourneyAPI.from_env()\n", " raw = fetch(qj)\n", " table, fig = analyze(raw)\n", " print(table.to_string(index=False))\n", " fig.savefig(OUTPUT, dpi=180)\n", " plt.show()\n", "\n", "\n", "OUTPUT = \"sdk_filing_reaction.png\"\n", "\n", "if __name__ == \"__main__\":\n", " run()\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }