{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# StashGamma EOD Data API — Jupyter quickstart\n",
    "\n",
    "Pulls daily OHLCV bars for a symbol, loads them into a pandas `DataFrame`, and plots\n",
    "close price against volume. Useful as a starting point for backtesting or research\n",
    "notebooks.\n",
    "\n",
    "**Setup**\n",
    "```bash\n",
    "pip install -r requirements.txt\n",
    "export STASHGAMMA_API_KEY=sg_live_...   # or set it in the next cell\n",
    "```\n",
    "Get a free key (no card required): https://www.stashgamma.com/profile?tab=developer\n",
    "\n",
    "Full API reference: https://www.stashgamma.com/developer-docs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import time\n",
    "\n",
    "import pandas as pd\n",
    "import requests\n",
    "\n",
    "BASE_URL = \"https://www.stashgamma.com/api/dataapi/v1\"\n",
    "API_KEY = os.environ.get(\"STASHGAMMA_API_KEY\", \"YOUR_API_KEY\")\n",
    "\n",
    "\n",
    "def get_eod_df(symbol: str, date_from: str, date_to: str, max_retries: int = 3) -> pd.DataFrame:\n",
    "    \"\"\"Fetch daily OHLCV bars and return them as a date-indexed DataFrame.\"\"\"\n",
    "    params = {\"from\": date_from, \"to\": date_to}\n",
    "    headers = {\"X-Api-Key\": API_KEY}\n",
    "\n",
    "    for attempt in range(max_retries + 1):\n",
    "        resp = requests.get(f\"{BASE_URL}/eod/{symbol}\", params=params, headers=headers, timeout=10)\n",
    "\n",
    "        if resp.status_code == 429 and attempt < max_retries:\n",
    "            retry_after = int(resp.headers.get(\"Retry-After\", \"5\"))\n",
    "            print(f\"Rate limited, retrying in {retry_after}s...\")\n",
    "            time.sleep(retry_after)\n",
    "            continue\n",
    "\n",
    "        resp.raise_for_status()\n",
    "        break\n",
    "\n",
    "    bars = resp.json()[\"bars\"]\n",
    "    df = pd.DataFrame(bars)\n",
    "    df[\"date\"] = pd.to_datetime(df[\"date\"])\n",
    "    return df.set_index(\"date\").sort_index()\n",
    "\n",
    "\n",
    "df = get_eod_df(\"QQQ\", \"2025-08-01\", \"2026-08-01\")\n",
    "df.tail()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## A quick look — daily return and a rolling volatility column\n",
    "\n",
    "Standard first step in most research notebooks: derive a return series, then a rolling\n",
    "annualized volatility estimate, straight off the `close` column."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[\"daily_return\"] = df[\"close\"].pct_change()\n",
    "df[\"rolling_vol_20d\"] = df[\"daily_return\"].rolling(20).std() * (252 ** 0.5)\n",
    "df[[\"close\", \"daily_return\", \"rolling_vol_20d\"]].tail(10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Plot: close price + volume\n",
    "\n",
    "Two stacked panels sharing the x-axis — the standard price/volume layout."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "fig, (ax_price, ax_vol) = plt.subplots(\n",
    "    2, 1, figsize=(11, 6), sharex=True, gridspec_kw={\"height_ratios\": [3, 1]}\n",
    ")\n",
    "\n",
    "ax_price.plot(df.index, df[\"close\"], color=\"#1f77b4\", linewidth=1.5)\n",
    "ax_price.set_ylabel(\"Close ($)\")\n",
    "ax_price.set_title(\"QQQ — daily close & volume (via StashGamma EOD API)\")\n",
    "ax_price.grid(alpha=0.3)\n",
    "\n",
    "ax_vol.bar(df.index, df[\"volume\"], color=\"#888888\", width=1.0)\n",
    "ax_vol.set_ylabel(\"Volume\")\n",
    "ax_vol.grid(alpha=0.3)\n",
    "\n",
    "fig.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Next steps\n",
    "\n",
    "- Loop `get_eod_df` over a symbol list to build a multi-asset panel (mind the free tier's\n",
    "  300 req/hour limit — space out calls or upgrade if you're scanning a large universe).\n",
    "- Swap in your own backtest engine — `df` is already date-indexed OHLCV, ready for\n",
    "  `vectorbt`, `backtrader`, `zipline-reloaded`, or a hand-rolled loop.\n",
    "- See [`../mcp/`](../mcp/) if you'd rather have an LLM agent pull this data for you\n",
    "  conversationally instead of writing the fetch code yourself."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
