diff --git a/data/sample/events_sample.json b/data/sample/events_sample.json index a3bcee2..fabad55 100644 --- a/data/sample/events_sample.json +++ b/data/sample/events_sample.json @@ -1,14 +1,14 @@ [ {"event_id": "0f8c2a10-1b2c-4d5e-8a9b-000000000001", "event_type": "EARNINGS_RELEASE", "timing_category": "SCHEDULED", "event_datetime": "2026-01-27T21:00:00Z", "focal_assets": [{"identifier_type": "TICKER", "identifier_value": "MSFT"}]}, {"event_id": "0f8c2a10-1b2c-4d5e-8a9b-000000000002", "event_type": "EARNINGS_RELEASE", "timing_category": "SCHEDULED", "event_datetime": "2026-01-27T21:05:00Z", "focal_assets": [{"identifier_type": "TICKER", "identifier_value": "GOOGL"}]}, - {"event_id": "0f8c2a10-1b2c-4d5e-8a9b-000000000003", "event_type": "MACRO_DATA_CPI", "timing_category": "SCHEDULED", "event_datetime": "2026-01-28T13:30:00Z", "focal_assets": []}, + {"event_id": "0f8c2a10-1b2c-4d5e-8a9b-000000000003", "event_type": "EARNINGS_RELEASE", "timing_category": "SCHEDULED", "event_datetime": "2026-01-28T12:10:00Z", "focal_assets": [{"identifier_type": "TICKER", "identifier_value": "BA"}]}, {"event_id": "0f8c2a10-1b2c-4d5e-8a9b-000000000004", "event_type": "EARNINGS_RELEASE", "timing_category": "SCHEDULED", "event_datetime": "2026-01-28T21:00:00Z", "focal_assets": [{"identifier_type": "TICKER", "identifier_value": "META"}]}, {"event_id": "0f8c2a10-1b2c-4d5e-8a9b-000000000005", "event_type": "EARNINGS_RELEASE", "timing_category": "SCHEDULED", "event_datetime": "2026-01-28T21:02:00Z", "focal_assets": [{"identifier_type": "TICKER", "identifier_value": "TSLA"}]}, - {"event_id": "0f8c2a10-1b2c-4d5e-8a9b-000000000006", "event_type": "FOMC_MEETING", "timing_category": "SCHEDULED", "event_datetime": "2026-01-28T19:00:00Z", "focal_assets": []}, + {"event_id": "0f8c2a10-1b2c-4d5e-8a9b-000000000006", "event_type": "EARNINGS_RELEASE", "timing_category": "SCHEDULED", "event_datetime": "2026-01-28T21:10:00Z", "focal_assets": [{"identifier_type": "TICKER", "identifier_value": "IBM"}]}, {"event_id": "0f8c2a10-1b2c-4d5e-8a9b-000000000007", "event_type": "EARNINGS_RELEASE", "timing_category": "SCHEDULED", "event_datetime": "2026-01-29T21:00:00Z", "focal_assets": [{"identifier_type": "TICKER", "identifier_value": "AAPL"}]}, {"event_id": "0f8c2a10-1b2c-4d5e-8a9b-000000000008", "event_type": "EARNINGS_RELEASE", "timing_category": "SCHEDULED", "event_datetime": "2026-01-29T21:03:00Z", "focal_assets": [{"identifier_type": "TICKER", "identifier_value": "AMZN"}]}, - {"event_id": "0f8c2a10-1b2c-4d5e-8a9b-000000000009", "event_type": "MACRO_DATA_JOBS", "timing_category": "SCHEDULED", "event_datetime": "2026-01-30T13:30:00Z", "focal_assets": []}, + {"event_id": "0f8c2a10-1b2c-4d5e-8a9b-000000000009", "event_type": "EARNINGS_RELEASE", "timing_category": "SCHEDULED", "event_datetime": "2026-01-30T12:00:00Z", "focal_assets": [{"identifier_type": "TICKER", "identifier_value": "CVX"}]}, {"event_id": "0f8c2a10-1b2c-4d5e-8a9b-00000000000a", "event_type": "EARNINGS_RELEASE", "timing_category": "SCHEDULED", "event_datetime": "2026-02-03T21:00:00Z", "focal_assets": [{"identifier_type": "TICKER", "identifier_value": "AMD"}]}, {"event_id": "0f8c2a10-1b2c-4d5e-8a9b-00000000000b", "event_type": "EARNINGS_RELEASE", "timing_category": "SCHEDULED", "event_datetime": "2026-02-04T21:00:00Z", "focal_assets": [{"identifier_type": "TICKER", "identifier_value": "NVDA"}]}, - {"event_id": "0f8c2a10-1b2c-4d5e-8a9b-00000000000c", "event_type": "UNSCHEDULED_8K", "timing_category": "UNSCHEDULED", "event_datetime": "2026-02-04T14:12:00Z", "focal_assets": [{"identifier_type": "TICKER", "identifier_value": "BA"}]} + {"event_id": "0f8c2a10-1b2c-4d5e-8a9b-00000000000c", "event_type": "EARNINGS_RELEASE", "timing_category": "SCHEDULED", "event_datetime": "2026-02-04T21:05:00Z", "focal_assets": [{"identifier_type": "TICKER", "identifier_value": "QCOM"}]} ] diff --git a/notebooks/00_api_quickstart.ipynb b/notebooks/00_api_quickstart.ipynb index 10d45ad..1d24718 100644 --- a/notebooks/00_api_quickstart.ipynb +++ b/notebooks/00_api_quickstart.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "de059abe", + "id": "0", "metadata": {}, "source": [ "# 00 — API quickstart\n", @@ -27,7 +27,7 @@ { "cell_type": "code", "execution_count": null, - "id": "f265bfab", + "id": "1", "metadata": {}, "outputs": [], "source": [ @@ -53,20 +53,21 @@ }, { "cell_type": "markdown", - "id": "ac4f8bec", + "id": "2", "metadata": {}, "source": [ "## 1. Events calendar — `GET /events`\n", "\n", - "The calendar lists scheduled and realized events. Each event has a type\n", - "(`EARNINGS_RELEASE`, `FOMC_MEETING`, `MACRO_DATA_CPI`, …), a timestamp, and its\n", - "focal assets. We flatten it into a tidy, sorted table." + "The calendar lists scheduled and realized events. Each event has a type — an open\n", + "string set; currently only `EARNINGS_RELEASE` is emitted, though new types may be\n", + "added over time — a timestamp, and its focal assets. We flatten it into a tidy,\n", + "sorted table." ] }, { "cell_type": "code", "execution_count": null, - "id": "f10f5b96", + "id": "3", "metadata": {}, "outputs": [], "source": [ @@ -84,7 +85,7 @@ }, { "cell_type": "markdown", - "id": "e4b600a9", + "id": "4", "metadata": {}, "source": [ "A stacked daily bar makes the shape of the calendar obvious at a glance — how many events land each day, split by type." @@ -93,7 +94,7 @@ { "cell_type": "code", "execution_count": null, - "id": "5b235d06", + "id": "5", "metadata": {}, "outputs": [], "source": [ @@ -103,7 +104,7 @@ }, { "cell_type": "markdown", - "id": "f643972a", + "id": "6", "metadata": {}, "source": [ "## 2. Historical archive — `GET /archive`\n", @@ -115,7 +116,7 @@ { "cell_type": "code", "execution_count": null, - "id": "9c6ae5d4", + "id": "7", "metadata": {}, "outputs": [], "source": [ @@ -129,7 +130,7 @@ }, { "cell_type": "markdown", - "id": "7a908689", + "id": "8", "metadata": {}, "source": [ "## 3. Webhook self-test — `POST /webhook/test`\n", @@ -144,7 +145,7 @@ { "cell_type": "code", "execution_count": null, - "id": "0ed1ae69", + "id": "9", "metadata": {}, "outputs": [], "source": [ @@ -164,7 +165,7 @@ }, { "cell_type": "markdown", - "id": "a4d5fd07", + "id": "10", "metadata": {}, "source": [ "## 4. Submission health — `GET /health`\n", @@ -177,7 +178,7 @@ { "cell_type": "code", "execution_count": null, - "id": "4bfe3063", + "id": "11", "metadata": {}, "outputs": [], "source": [ @@ -191,7 +192,7 @@ }, { "cell_type": "markdown", - "id": "b68c2b90", + "id": "12", "metadata": {}, "source": [ "## Next steps\n", diff --git a/notebooks/01_historical_archive.ipynb b/notebooks/01_historical_archive.ipynb index aee20f5..16e97f1 100644 --- a/notebooks/01_historical_archive.ipynb +++ b/notebooks/01_historical_archive.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "fe91093f", + "id": "0", "metadata": {}, "source": [ "# 01 — Historical archive\n", @@ -27,7 +27,7 @@ { "cell_type": "code", "execution_count": null, - "id": "55ec7f88", + "id": "1", "metadata": {}, "outputs": [], "source": [ @@ -49,7 +49,7 @@ }, { "cell_type": "markdown", - "id": "976571f4", + "id": "2", "metadata": {}, "source": [ "## 1. Browse the manifest\n", @@ -62,7 +62,7 @@ { "cell_type": "code", "execution_count": null, - "id": "92bf78c9", + "id": "3", "metadata": {}, "outputs": [], "source": [ @@ -77,7 +77,7 @@ }, { "cell_type": "markdown", - "id": "b458237d", + "id": "4", "metadata": {}, "source": [ "## 2. Download & cache\n", @@ -90,7 +90,7 @@ { "cell_type": "code", "execution_count": null, - "id": "cc9c32ba", + "id": "5", "metadata": {}, "outputs": [], "source": [ @@ -111,7 +111,7 @@ }, { "cell_type": "markdown", - "id": "f91590d0", + "id": "6", "metadata": {}, "source": [ "## 3. Load into pandas\n", @@ -124,7 +124,7 @@ { "cell_type": "code", "execution_count": null, - "id": "606a8720", + "id": "7", "metadata": {}, "outputs": [], "source": [ @@ -136,7 +136,7 @@ }, { "cell_type": "markdown", - "id": "416486f5", + "id": "8", "metadata": {}, "source": [ "## 4. Inspect one event\n", @@ -149,7 +149,7 @@ { "cell_type": "code", "execution_count": null, - "id": "4c85dae3", + "id": "9", "metadata": {}, "outputs": [], "source": [ @@ -173,7 +173,7 @@ }, { "cell_type": "markdown", - "id": "fa6bd3bd", + "id": "10", "metadata": {}, "source": [ "## 5. Reproduce the baseline scoring regressions\n", @@ -204,7 +204,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a2f1e8d0", + "id": "11", "metadata": {}, "outputs": [], "source": [ @@ -230,7 +230,7 @@ { "cell_type": "code", "execution_count": null, - "id": "45e5fce0", + "id": "12", "metadata": {}, "outputs": [], "source": [ @@ -267,7 +267,7 @@ }, { "cell_type": "markdown", - "id": "f757e389", + "id": "13", "metadata": {}, "source": [ "## Where to go from here\n", @@ -277,8 +277,11 @@ "\n", "1. Pair each event with the realized next-day reaction for its focal asset, from\n", " your own market-data source.\n", - "2. Convert that reaction into a percentile against the asset's historical\n", - " distribution — the same `[0, 1]` target the competition scores.\n", + "2. Convert that reaction into a percentile rank in `[0, 1]` **across the\n", + " quarter's cross-section** of scored event outcomes — a rank against all the\n", + " period's events, not against the asset's own history. That's the target the\n", + " competition scores; `examples.scoring.percentile_ranks` is the exact\n", + " transform (see section 5).\n", "3. Score your model's predicted percentile against it offline, and iterate.\n", "\n", "When your model is ready, move it into a deployed webhook handler (see the starter\n", diff --git a/src/examples/schemas.py b/src/examples/schemas.py index 228ddeb..9c48629 100644 --- a/src/examples/schemas.py +++ b/src/examples/schemas.py @@ -3,8 +3,9 @@ These mirror the competition's OpenAPI schemas (``/docs/api``). They are deliberately **lenient** — ``extra="allow"`` — so unknown or newly added fields pass through untouched rather than raising. Event types in particular are an -open string set (``EARNINGS_RELEASE``, ``FOMC_MEETING``, ``MACRO_DATA_CPI``, …), -so we keep them as plain ``str`` and never validate against a closed enum. +open string set — currently only ``EARNINGS_RELEASE`` is emitted — so we keep +them as plain ``str`` and never validate against a closed enum; new types can +appear without breaking parsing. Only the read/exercise endpoints are modeled here. Prediction *submission* (``POST /predictions``) is intentionally omitted — that belongs to a live agent diff --git a/tests/test_frames.py b/tests/test_frames.py index bf6edaf..803719d 100644 --- a/tests/test_frames.py +++ b/tests/test_frames.py @@ -7,7 +7,16 @@ def test_events_frame_sorts_and_flattens(sample_events: list[dict]) -> None: - events = [CalendarEvent.model_validate(e) for e in sample_events] + # event_type is an open string set: an as-yet-unknown type (here with no + # focal assets) must flow through without any special-casing. + future_event = { + "event_id": "0f8c2a10-1b2c-4d5e-8a9b-0000000000ff", + "event_type": "SOME_FUTURE_TYPE", + "timing_category": "SCHEDULED", + "event_datetime": "2026-01-28T13:30:00Z", + "focal_assets": [], + } + events = [CalendarEvent.model_validate(e) for e in [*sample_events, future_event]] df = events_frame(events) assert list(df.columns) == [ @@ -24,10 +33,10 @@ def test_events_frame_sorts_and_flattens(sample_events: list[dict]) -> None: earnings = df[df["event_type"] == "EARNINGS_RELEASE"].iloc[0] assert earnings["n_assets"] == 1 assert earnings["tickers"] != "" - # A macro event with no focal assets renders as empty tickers, zero count. - macro = df[df["event_type"] == "MACRO_DATA_CPI"].iloc[0] - assert macro["tickers"] == "" - assert macro["n_assets"] == 0 + # An event with no focal assets renders as empty tickers, zero count. + no_assets = df[df["event_type"] == "SOME_FUTURE_TYPE"].iloc[0] + assert no_assets["tickers"] == "" + assert no_assets["n_assets"] == 0 def test_events_frame_empty() -> None: @@ -41,7 +50,7 @@ def test_manifest_frame_hides_urls_and_sorts() -> None: { "files": [ { - "event_type": "FOMC_MEETING", + "event_type": "SOME_FUTURE_TYPE", "quarter": "2026Q2", "key": "k2", "url": "https://cdn/secret2", @@ -62,7 +71,7 @@ def test_manifest_frame_hides_urls_and_sorts() -> None: df = manifest_frame(manifest) # Signed URLs never surface in the display frame. assert not any("url" in c for c in df.columns) - # Sorted by (event_type, quarter): EARNINGS before FOMC. + # Sorted by (event_type, quarter): EARNINGS_RELEASE before SOME_FUTURE_TYPE. assert df.iloc[0]["event_type"] == "EARNINGS_RELEASE" assert df.iloc[0]["size_mb"] == 0.0 # 863 bytes rounds to 0.00 MB assert df.iloc[1]["size_mb"] == 2.0