{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "# The reporting-lag trap\n### Why joining fundamentals on *period end* silently inflates your backtest\n\nA fiscal year that **ends** in December is not **public** in December — the 10-K arrives weeks or months later. If your backtest joins fundamentals on `period_end`, every rebalance trades on numbers nobody had yet.\n\nThis dataset stores every figure **as first reported** with its **filing date** (`filed`), so you can measure the lag — and query `as_of` any date to see exactly what the market knew.\n\n*Runs as-is on the public `demo` key (25 flagship tickers, full history, no signup).*"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "import json, urllib.request, urllib.parse\n\nBASE = \"https://www.tradingagentapp.com/api/v1\"\nKEY = \"demo\"  # public demo key: 25 flagship tickers, FULL fields & history, no signup\n             # paid keys unlock every ticker -> https://www.tradingagentapp.com/fundamentals\n\ndef api(path, **params):\n    qs = urllib.parse.urlencode({k: v for k, v in params.items() if v is not None})\n    req = urllib.request.Request(f\"{BASE}{path}?{qs}\",\n                                 headers={\"Authorization\": f\"Bearer {KEY}\"})\n    with urllib.request.urlopen(req, timeout=60) as r:\n        return json.loads(r.read().decode())\n\nprint(\"ready — or `pip install pit-fundamentals` for the packaged client\")"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## 1 · Measure the lag: period end → filing date"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "from datetime import date\n\nTICKERS = [\"AAPL\", \"MSFT\", \"NVDA\", \"JPM\", \"WMT\", \"XOM\", \"KO\", \"PG\"]\nlags = []\nfor t in TICKERS:\n    rows = api(\"/fundamentals\", ticker=t, metric=\"revenue\")[\"data\"]\n    for r in rows:\n        d_end = date.fromisoformat(r[\"end\"]); d_filed = date.fromisoformat(r[\"filed\"])\n        lags.append({\"ticker\": t, \"fp\": r[\"fp\"], \"end\": r[\"end\"],\n                     \"filed\": r[\"filed\"], \"lag_days\": (d_filed - d_end).days})\n\nannual = [x[\"lag_days\"] for x in lags if x[\"fp\"] == \"FY\"]\nquarterly = [x[\"lag_days\"] for x in lags if x[\"fp\"].startswith(\"Q\")]\nprint(f\"annual filings:    n={len(annual):3d}  median lag = {sorted(annual)[len(annual)//2]} days\")\nprint(f\"quarterly filings: n={len(quarterly):3d}  median lag = {sorted(quarterly)[len(quarterly)//2]} days\")"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "Typical US result: **quarterly ≈ 5–6 weeks, annual ≈ 8 weeks**. A backtest that rebalances on Jan 1 using the December fiscal year is trading on information from the future — about two months of it, every single year."
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## 2 · The same query, done honestly: `as_of`"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "# What did the world know about Apple on 2022-01-01?\nsnap = api(\"/fundamentals\", ticker=\"AAPL\", as_of=\"2022-01-01\", view=\"latest\")[\"data\"]\nrev = snap[\"revenue\"]\nprint(f\"latest revenue visible on 2022-01-01: FY{rev['fy']} {rev['end']}\"\n      f\"  (filed {rev['filed']})  ${rev['value']:,.0f}\")\n\n# vs. the naive join — FY2021 ended 2021-09-25, so a period-end join uses it\n# ... which is fine HERE (filed 2021-10-29 < Jan 1) — but try 2021-10-01:\nsnap2 = api(\"/fundamentals\", ticker=\"AAPL\", as_of=\"2021-10-01\", view=\"latest\")[\"data\"]\nrev2 = snap2[\"revenue\"]\nprint(f\"latest revenue visible on 2021-10-01: FY{rev2['fy']} {rev2['end']}\"\n      f\"  (filed {rev2['filed']})\")\nassert rev2[\"filed\"] <= \"2021-10-01\"  # the API can never leak — filed <= as_of, always"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "`as_of` filters on **`filed <= as_of`** server-side. There is no way to write a look-ahead join against this API by accident.\n\n## 3 · How much does it matter?\nQuantify it yourself: run your factor twice — once joined on `end`, once on `filed` — and compare. With the full universe (a paid key) the gap on turnover-heavy US value factors is typically *tens of basis points a year*: small enough to miss, large enough to flip a marginal strategy.\n\n---\n**Next:** every ticker, every market → <https://www.tradingagentapp.com/fundamentals> · docs → <https://www.tradingagentapp.com/developers>"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}