From 3539ae101e5b8019618dd543e2dad9f1af149fe7 Mon Sep 17 00:00:00 2001 From: helloiamvu Date: Wed, 15 Jul 2026 01:15:26 +0200 Subject: [PATCH 1/4] =?UTF-8?q?fix(stations):=2020-city=20Kalshi=20univers?= =?UTF-8?q?e=20=E2=80=94=20live-API=20audit=20drops=204=20phantom=20cities?= =?UTF-8?q?,=20adds=20NOLA/OKC/SATX?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v0.1.0 scope doc drifted from the live exchange (verified 2026-07-15 against GET /trade-api/v2/series?category=Climate and Weather + per-series settlement_sources): - DTW/CVG/BNA/SLC have no Kalshi daily-temperature markets (all series probes 404) — dropped from KALSHI_SETTLEMENT_STATIONS; their station records remain as bare weather stations (Phase 23 precedent). - NOLA→KMSY (CLI issuedby=MSY, NOT Lakefront KNEW), OKC→KOKC, SATX→KSAT (NOT Stinson KSSF) are live markets the registry missed — added, with KNEW/KSSF added to KNOWN_WRONG_STATIONS. - _KALSHI_TICKER_ALIASES now covers the live KXHIGHT/KXLOWT series family (plus KXHIGHPHIL/KXHIGHNYD/KXHIGHTEMPDEN), which previously failed city-ticker resolution for every city but Las Vegas. - Core venue tags, satellite backfill roster (union 66→65, satellite roster 65→64, batch.tf/workflow task math), schemas, and generated TS data modules updated in lockstep; Python + TS contract tests re-pinned. Python: core/markets/weather suites green (one pre-existing unrelated failure in tests/test_parity.py reproduces on clean main). TS: core 806 passed, markets 296 passed after building core. Co-Authored-By: Claude Fable 5 --- .github/workflows/run-weather-backfill.yml | 16 +++---- CHANGELOG.md | 21 +++++++++ infra/batch.tf | 6 +-- .../core/src/data/generated/stations.ts | 28 ++++++------ packages-ts/core/tests/stations.test.ts | 23 +++++----- .../src/data/generated/kalshi-stations.ts | 30 ++++++------- packages/core/src/mostlyright/_compose.py | 32 +++++++++++++- .../src/mostlyright/_internal/_stations.py | 44 +++++++++---------- packages/core/tests/test_stations_catalog.py | 35 ++++++++------- .../markets/catalog/kalshi_stations.py | 30 ++++++++----- .../tests/catalog/test_kalshi_stations.py | 30 +++++++++++-- .../mostlyright/weather/satellite/_roster.py | 34 +++++++------- .../weather/tests/satellite/test_roster.py | 16 ++++--- schemas/EXPORT_MANIFEST.json | 8 ++-- schemas/kalshi-settlement-stations.json | 32 +++++++------- schemas/stations.json | 28 ++++++------ 16 files changed, 249 insertions(+), 164 deletions(-) diff --git a/.github/workflows/run-weather-backfill.yml b/.github/workflows/run-weather-backfill.yml index 687963fc..b62d116f 100644 --- a/.github/workflows/run-weather-backfill.yml +++ b/.github/workflows/run-weather-backfill.yml @@ -7,7 +7,7 @@ name: Run weather backfill fleet (28-21) # ROLLOUT GATE (operator sequence): the DEFAULT run is a 1-STATION PILOT # (task_count=1) — cheap, proves the read→reduce→R2-upload loop + the big-bytes # firewall end to end. The FULL fleet shards station×year (28-28): one array task -# per (station, year), so task_count = 65 stations × N years (the Kalshi∪Polymarket +# per (station, year), so task_count = 64 stations × N years (the Kalshi∪Polymarket # roster minus the non-satellite HKO, D-28.8; N = current UTC year - 2017 + 1). It # reduces ~28 TB of raw imagery in-region and is the phase's # largest spend — it is BLOCKED unless the operator sets mode=full AND @@ -40,7 +40,7 @@ on: default: "latest" type: string mode: - description: "pilot = 1 station, 1 monolithic task (cheap smoke test, default). station = 1+ stations sharded across ALL years (task_count = stations × years; the fast full-single-station cost/speed run, e.g. KNYC 2017..now ACMC+DSRF — NOT cost-gated, bounded by station count). full = the 65-station×year fleet (~28 TB reduce; needs cost sign-off; non-GOES shards no-op, native ring is 28-26)." + description: "pilot = 1 station, 1 monolithic task (cheap smoke test, default). station = 1+ stations sharded across ALL years (task_count = stations × years; the fast full-single-station cost/speed run, e.g. KNYC 2017..now ACMC+DSRF — NOT cost-gated, bounded by station count). full = the 64-station×year fleet (~28 TB reduce; needs cost sign-off; non-GOES shards no-op, native ring is 28-26)." required: true default: "pilot" type: choice @@ -116,7 +116,7 @@ jobs: - name: Enforce the cost sign-off gate for a full run run: | if [ "${{ inputs.mode }}" = "full" ] && [ "${{ inputs.confirm_cost_signoff }}" != "true" ]; then - echo "::error::mode=full runs the 65-shard ~28 TB fleet (the phase's largest spend)." + echo "::error::mode=full runs the 64-shard ~28 TB fleet (the phase's largest spend)." echo "::error::Set confirm_cost_signoff=true (H5 pilot cost sign-off) to proceed, or use mode=pilot." exit 1 fi @@ -154,8 +154,8 @@ jobs: IMAGE="${AR_HOST}/${AR_PROJECT}/${AR_REPO}/${IMAGE_NAME}:${IMAGE_TAG}" if [ "$MODE" = "full" ]; then # Station×year sharding (28-28): one array task per (station, year), so - # TASK_COUNT = stations × years. Stations = 65 (the Kalshi∪Polymarket - # union (66) minus the one non-satellite station HKO, + # TASK_COUNT = stations × years. Stations = 64 (the Kalshi∪Polymarket + # union (65) minus the one non-satellite station HKO, # _roster._NON_SATELLITE_STATIONS — one shard base per satellite- # resolvable station). Years = the roster default window # [2017 .. current UTC year] inclusive. year_start MUST match the CLI's @@ -163,7 +163,7 @@ jobs: # year and passed EXPLICITLY so the CLI's derived num_years matches this # TASK_COUNT exactly (else the station×year decomposition would not # trigger and shards would round-robin over the full range). - STATIONS=65 + STATIONS=64 FULL_YEAR_START=2017 FULL_YEAR_END=$(date -u +%Y) NUM_YEARS=$(( FULL_YEAR_END - FULL_YEAR_START + 1 )) @@ -175,7 +175,7 @@ jobs: PARALLELISM=48 CPU_MILLI=16000 MEM_MIB=65536 - # Roster mode: the CLI resolves the 65-station roster and decomposes + # Roster mode: the CLI resolves the 64-station roster and decomposes # BATCH_TASK_INDEX into (station, year). --year-start/--year-end pin the # window so num_years == this NUM_YEARS. --max-workers 16 matches the # n2-standard-16 fleet machine (16 vCPU). Only override the roster's @@ -230,7 +230,7 @@ jobs: # Mid-tier machine: n2-standard-8 (8 vCPU / 32 GiB) — DSRF full-disk # decode is CPU/mem heavy (--executor process), so bigger than the # pilot's n2-standard-4, but one station is far cheaper than the - # 65-station fleet. Run all year-shards concurrently (parallelism + # 64-station fleet. Run all year-shards concurrently (parallelism # capped at 48 to match the fleet's Spot-availability ceiling). # n2-standard-16 (compute budget approved 2026-07-05): with the # spawn process pool + the probe-raised rate cap, decode scales diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f3c6688..212f03b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ All notable changes to `mostlyright`. The format follows [Keep a Changelog](http ## [Unreleased] +### Fixed + +- **Settlement-station registry: the Kalshi daily-temp universe is 20 cities, + not 21 (live-universe audit, 2026-07-15).** The v0.1.0 scope doc drifted from + the live exchange: DTW/CVG/BNA/SLC never had daily-temperature markets (every + series probe 404s) and three real market cities were missing. Parity-critical — + a backtest keyed to the phantom four settled against data no market resolves on. + - `KALSHI_SETTLEMENT_STATIONS`: dropped `DTW`/`CVG`/`BNA`/`SLC`; added + `NOLA` → KMSY (NOT Lakefront KNEW), `OKC` → KOKC, `SATX` → KSAT (NOT + Stinson KSSF). `KNOWN_WRONG_STATIONS` gains KNEW + KSSF. + - Core catalog `kalshi` venue tags follow (KMSY/KOKC/KSAT tagged; + KDTW/KCVG/KBNA/KSLC remain as bare weather stations, records kept). + - `_KALSHI_TICKER_ALIASES` now resolves the live `KXHIGHT` / + `KXLOWT` series family (`KXHIGHTATL`, `KXLOWTNYC`, …, plus + `KXHIGHPHIL`/`KXHIGHNYD`/`KXHIGHTEMPDEN`), which previously failed + city-ticker lookup for every city except Las Vegas. + - Satellite backfill roster: union is 65 stations (satellite roster 64; + `batch.tf`/workflow task math updated in lockstep). + - Schemas + generated TS data modules re-exported; Python and TS contract + tests re-pinned to the 20-city universe. + ## [1.17.0] — 2026-07-14 — Registry API redesign: the `align` / `spine` / `sources` ladder + per-domain `pairs()` Minor release, dual-SDK (PyPI 1.17.0 + npm 1.17.0 in lockstep). Phase 34 answers diff --git a/infra/batch.tf b/infra/batch.tf index 40356d7e..1aa417e6 100644 --- a/infra/batch.tf +++ b/infra/batch.tf @@ -83,15 +83,15 @@ resource "google_storage_bucket_iam_member" "backfill_progress_rw" { # # task_group: # # Station×year sharding (28-28): ONE array task per (station, year), so -# # task_count = 65 stations × N years. -# task_count = 65 * N # 65 = Kalshi∪Polymarket roster (66) MINUS the one non- +# # task_count = 64 stations × N years. +# task_count = 64 * N # 64 = Kalshi∪Polymarket roster (65) MINUS the one non- # # satellite station HKO (_roster._NON_SATELLITE_STATIONS; # # HKO has no satellite StationInfo → a shard for it would # # resolve to zero partitions). One shard BASE per satellite- # # resolvable station (D-28.8), fanned out across N years. # # N = current_UTC_year - 2017 + 1 (roster default window). # # run-weather-backfill.yml computes the SAME -# # TASK_COUNT = 65 * NUM_YEARS and pins --year-start 2017 +# # TASK_COUNT = 64 * NUM_YEARS and pins --year-start 2017 # # / --year-end so the CLI's derived num_years # # matches — keep them in LOCKSTEP with the roster + window. # # NOTE: the container passes no --satellites, so each shard diff --git a/packages-ts/core/src/data/generated/stations.ts b/packages-ts/core/src/data/generated/stations.ts index dd24bbb0..f1c2ee3f 100644 --- a/packages-ts/core/src/data/generated/stations.ts +++ b/packages-ts/core/src/data/generated/stations.ts @@ -222,9 +222,7 @@ export const STATIONS: ReadonlyArray = [ longitude: -86.6782, name: "Nashville International", tz: "America/Chicago", - venues: [ - "kalshi", - ], + venues: [], }, { code: "BOS", @@ -248,9 +246,7 @@ export const STATIONS: ReadonlyArray = [ longitude: -84.6678, name: "Cincinnati/Northern Kentucky International", tz: "America/New_York", - venues: [ - "kalshi", - ], + venues: [], }, { code: "DAL", @@ -313,9 +309,7 @@ export const STATIONS: ReadonlyArray = [ longitude: -83.3534, name: "Detroit Metropolitan Wayne County", tz: "America/New_York", - venues: [ - "kalshi", - ], + venues: [], }, { code: "HOU", @@ -432,7 +426,9 @@ export const STATIONS: ReadonlyArray = [ longitude: -90.258, name: "New Orleans Louis Armstrong International", tz: "America/Chicago", - venues: [], + venues: [ + "kalshi", + ], }, { code: "NYC", @@ -456,7 +452,9 @@ export const STATIONS: ReadonlyArray = [ longitude: -97.6007, name: "Oklahoma City Will Rogers World", tz: "America/Chicago", - venues: [], + venues: [ + "kalshi", + ], }, { code: "ORD", @@ -506,7 +504,9 @@ export const STATIONS: ReadonlyArray = [ longitude: -98.4698, name: "San Antonio International", tz: "America/Chicago", - venues: [], + venues: [ + "kalshi", + ], }, { code: "SEA", @@ -545,9 +545,7 @@ export const STATIONS: ReadonlyArray = [ longitude: -111.9778, name: "Salt Lake City International", tz: "America/Denver", - venues: [ - "kalshi", - ], + venues: [], }, { code: "LEBL", diff --git a/packages-ts/core/tests/stations.test.ts b/packages-ts/core/tests/stations.test.ts index e9092c00..e9c688d5 100644 --- a/packages-ts/core/tests/stations.test.ts +++ b/packages-ts/core/tests/stations.test.ts @@ -3,7 +3,9 @@ import { describe, expect, it } from "vitest"; import { STATIONS } from "../src/data/generated/stations.js"; import { CATALOG, type Station, StationCatalog } from "../src/index.js"; -// The 21 Kalshi NHIGH/NLOW settlement ICAOs (mirrors the markets citations). +// The 20 Kalshi NHIGH/NLOW settlement ICAOs (mirrors the markets citations). +// 2026-07 live-universe audit: KDTW/KCVG/KBNA/KSLC out (no live markets), +// KMSY/KOKC/KSAT in (NOLA/OKC/SATX series are live). const KALSHI_ICAOS = new Set([ "KATL", "KAUS", @@ -21,20 +23,21 @@ const KALSHI_ICAOS = new Set([ "KSEA", "KSFO", "KIAH", - "KDTW", - "KCVG", - "KBNA", - "KSLC", "KLAS", + "KMSY", + "KOKC", + "KSAT", ]); // Registry stations no prediction-market venue settles against. Phase 23 grew -// this from 4 to 29: 3 US bare (KHOU moved TO polymarket) + 26 intl (roster +// this from 4 to 29; the 2026-07 audit swapped the US membership (4 bare US +// stations now: the ex-Kalshi KDTW/KCVG/KBNA/KSLC) + 26 intl (roster // drops + the London/Moscow/Taipei/HK/Paris move-sources). const NO_VENUE_ICAOS = new Set([ - "KMSY", - "KOKC", - "KSAT", + "KBNA", + "KCVG", + "KDTW", + "KSLC", "EDDB", "EDDF", "EGKK", @@ -95,7 +98,7 @@ describe("StationCatalog", () => { it("kalshi venue equals the settlement universe, NOT every US station", () => { expect(icaosOf(CATALOG.filterByVenue("kalshi"))).toEqual(KALSHI_ICAOS); - expect(CATALOG.filterByVenue("kalshi").length).toBe(21); + expect(CATALOG.filterByVenue("kalshi").length).toBe(20); }); it("polymarket venue is the explicit 50-station roster, NOT every intl", () => { diff --git a/packages-ts/markets/src/data/generated/kalshi-stations.ts b/packages-ts/markets/src/data/generated/kalshi-stations.ts index 5b95979f..3d8ef2c9 100644 --- a/packages-ts/markets/src/data/generated/kalshi-stations.ts +++ b/packages-ts/markets/src/data/generated/kalshi-stations.ts @@ -16,10 +16,6 @@ export const KALSHI_SETTLEMENT_STATIONS: Readonly> citation: "https://kalshi.com/markets/khighaus (Austin-Bergstrom; the only Austin station Kalshi cites)", station: "KAUS", }, - BNA: { - citation: "https://kalshi.com/markets/khighbna (Nashville International)", - station: "KBNA", - }, BOS: { citation: "https://kalshi.com/markets/khighbos (Boston Logan)", station: "KBOS", @@ -28,10 +24,6 @@ export const KALSHI_SETTLEMENT_STATIONS: Readonly> citation: "https://kalshi.com/markets/khighchi (Midway, NOT ORD)", station: "KMDW", }, - CVG: { - citation: "https://kalshi.com/markets/khighcvg (Cincinnati/Northern Kentucky International)", - station: "KCVG", - }, DAL: { citation: "https://kalshi.com/markets/khighdal (DFW, NOT Love Field)", station: "KDFW", @@ -44,10 +36,6 @@ export const KALSHI_SETTLEMENT_STATIONS: Readonly> citation: "https://kalshi.com/markets/khighden (Denver International)", station: "KDEN", }, - DTW: { - citation: "https://kalshi.com/markets/khighdtw (Detroit Metropolitan)", - station: "KDTW", - }, HOU: { citation: "https://kalshi.com/markets/khighhou (Intercontinental, NOT Hobby; Kalshi cites IAH)", station: "KIAH", @@ -64,10 +52,18 @@ export const KALSHI_SETTLEMENT_STATIONS: Readonly> citation: "https://kalshi.com/markets/khighmsp (Minneapolis-St. Paul International)", station: "KMSP", }, + NOLA: { + citation: "https://kalshi.com/markets/kxhightnola (Louis Armstrong Intl, NOT Lakefront KNEW; settles vs NWS CLIMSY)", + station: "KMSY", + }, NYC: { citation: "https://kalshi.com/markets/khighny (Central Park, NOT LGA/JFK)", station: "KNYC", }, + OKC: { + citation: "https://kalshi.com/markets/kxhightokc (Will Rogers World Airport)", + station: "KOKC", + }, PHL: { citation: "https://kalshi.com/markets/khighphl (Philadelphia International)", station: "KPHL", @@ -76,6 +72,10 @@ export const KALSHI_SETTLEMENT_STATIONS: Readonly> citation: "https://kalshi.com/markets/khighphx (Sky Harbor International)", station: "KPHX", }, + SATX: { + citation: "https://kalshi.com/markets/kxhightsatx (San Antonio Intl, NOT Stinson KSSF)", + station: "KSAT", + }, SEA: { citation: "https://kalshi.com/markets/khighsea (SeaTac, NOT BFI)", station: "KSEA", @@ -84,10 +84,6 @@ export const KALSHI_SETTLEMENT_STATIONS: Readonly> citation: "https://kalshi.com/markets/khighsfo (San Francisco International, NOT OAK)", station: "KSFO", }, - SLC: { - citation: "https://kalshi.com/markets/khighslc (Salt Lake City International)", - station: "KSLC", - }, TLV: { citation: "https://kalshi.com/markets/kxhightlv (Harry Reid/McCarran; settles vs NWS CLILAS)", station: "KLAS", @@ -102,6 +98,8 @@ export const KNOWN_WRONG_STATIONS: ReadonlySet = new Set([ "KIAD", "KJFK", "KLGA", + "KNEW", "KOAK", "KORD", + "KSSF", ]); diff --git a/packages/core/src/mostlyright/_compose.py b/packages/core/src/mostlyright/_compose.py index 4f125abf..89298df3 100644 --- a/packages/core/src/mostlyright/_compose.py +++ b/packages/core/src/mostlyright/_compose.py @@ -31,8 +31,36 @@ #: ROADMAP example) would fail to resolve. _KALSHI_TICKER_ALIASES: dict[str, str] = { "NY": "NYC", - # All other Kalshi cities use the canonical 3-letter slug as their - # ticker suffix verbatim (identity mapping is implicit). + "NYD": "NYC", # KXHIGHNYD — NYC daily series variant + "PHIL": "PHL", # KXHIGHPHIL / KXLOWPHIL + # The modern "KXHIGHT" / "KXLOWT" series family (verified + # against the live Kalshi series list, 2026-07-15): after the KX→K + # normalization the parser sees KHIGHT, so the extracted suffix + # carries a leading "T" that must be stripped to reach the canonical + # catalog key. (TLV is NOT aliased — it is itself the catalog key for + # Las Vegas, from exactly this parsing of KXHIGHTLV.) + "TATL": "ATL", + "TAUS": "AUS", + "TBOS": "BOS", + "TCHI": "CHI", + "TDAL": "DAL", + "TDC": "DCA", + "TDEN": "DEN", + "TEMPDEN": "DEN", # KXHIGHTEMPDEN — legacy Denver series + "THOU": "HOU", + "TLAX": "LAX", + "TMIA": "MIA", + "TMIN": "MSP", + "TNOLA": "NOLA", + "TNYC": "NYC", + "TOKC": "OKC", + "TPHIL": "PHL", + "TPHX": "PHX", + "TSATX": "SATX", + "TSEA": "SEA", + "TSFO": "SFO", + # All other Kalshi cities use the canonical slug as their ticker + # suffix verbatim (identity mapping is implicit). } diff --git a/packages/core/src/mostlyright/_internal/_stations.py b/packages/core/src/mostlyright/_internal/_stations.py index c9862f6d..79e74152 100644 --- a/packages/core/src/mostlyright/_internal/_stations.py +++ b/packages/core/src/mostlyright/_internal/_stations.py @@ -79,14 +79,13 @@ class StationInfo: STATIONS: dict[str, StationInfo] = { # ------------------------------------------------------------------ - # US — the v0.14.1 20-station set (verbatim). NOTE: four of these - # (KHOU/KMSY/KOKC/KSAT) are NOT Kalshi settlement stations — the - # v0.14.1 registry predated the Kalshi citation audit. The venue-tagging - # pass below assigns ``kalshi`` by ICAO membership, so those four carry - # no venue tag while the real Kalshi stations (KIAH etc.) are added in - # the Phase 22 block that follows. KLAS (Las Vegas) IS a Kalshi - # settlement station (series KXHIGHTLV/KXLOWTLV, issue #39) and is - # tagged ``kalshi`` below. + # US — the v0.14.1 20-station set (verbatim). NOTE: KHOU is NOT a + # Kalshi settlement station (Houston settles on KIAH, added in the + # Phase 22 block that follows); the venue-tagging pass below assigns + # ``kalshi`` by ICAO membership, so KHOU carries no ``kalshi`` tag. + # KMSY/KOKC/KSAT ARE Kalshi settlement stations (NOLA/OKC/SATX — + # 2026-07 live-universe audit; the v0.1.0 scope doc had missed them), + # as is KLAS (Las Vegas, series KXHIGHTLV/KXLOWTLV, issue #39). # ------------------------------------------------------------------ "ATL": StationInfo( code="ATL", @@ -269,14 +268,13 @@ class StationInfo: longitude=-122.3790, ), # ------------------------------------------------------------------ - # US — Kalshi settlement stations NOT in the v0.14.1 20-station set - # (Phase 22). The v0.14.1 registry block above used the wrong station - # for four Kalshi cities — it carried KHOU/KMSY/KOKC/KSAT, which - # are NOT the stations Kalshi settles against. Kalshi cites KIAH (not - # KHOU) for Houston and adds Detroit/Cincinnati/Nashville/Salt-Lake. - # These five complete the Kalshi NHIGH/NLOW universe; the venue-tagging - # pass below assigns the ``kalshi`` tag by ICAO membership, not by - # country, so the stale four carry no ``kalshi`` tag. + # US — stations NOT in the v0.14.1 20-station set (Phase 22). KIAH is + # the real Kalshi Houston settlement station (Kalshi cites KIAH, not + # KHOU/Hobby). KDTW/KCVG/KBNA/KSLC were added by Phase 22 as Kalshi + # cities per the v0.1.0 scope doc, but the 2026-07 live-universe audit + # found no such markets exist (all series probes 404) — they remain + # here as bare weather stations (the Phase 23 precedent), untagged by + # the ICAO-membership venue pass below. # ------------------------------------------------------------------ "IAH": StationInfo( code="IAH", @@ -1074,7 +1072,10 @@ class StationInfo: # loudly on any drift. # ---------------------------------------------------------------------- -#: The 21 Kalshi NHIGH/NLOW settlement ICAOs (== markets citation ICAOs). +#: The 20 Kalshi NHIGH/NLOW settlement ICAOs (== markets citation ICAOs). +#: 2026-07 live-universe audit: KDTW/KCVG/KBNA/KSLC dropped (no live Kalshi +#: markets — their StationInfo records remain as bare weather stations, the +#: Phase 23 precedent); KMSY/KOKC/KSAT added (NOLA/OKC/SATX series are live). _KALSHI_ICAOS: frozenset[str] = frozenset( { "KATL", @@ -1092,12 +1093,11 @@ class StationInfo: "KPHX", "KSEA", "KSFO", # 15 shared - "KIAH", - "KDTW", - "KCVG", - "KBNA", - "KSLC", # 5 Kalshi-only vs the v0.14.1 set + "KIAH", # Kalshi-only vs the v0.14.1 set (Houston = Intercontinental) "KLAS", # Las Vegas (TLV) — issue #39; in the v0.14.1 registry, now Kalshi-tagged + "KMSY", # New Orleans (NOLA) — 2026-07 audit; in the v0.14.1 registry + "KOKC", # Oklahoma City (OKC) — 2026-07 audit; in the v0.14.1 registry + "KSAT", # San Antonio (SATX) — 2026-07 audit; in the v0.14.1 registry } ) diff --git a/packages/core/tests/test_stations_catalog.py b/packages/core/tests/test_stations_catalog.py index da376251..5aff15c7 100644 --- a/packages/core/tests/test_stations_catalog.py +++ b/packages/core/tests/test_stations_catalog.py @@ -23,7 +23,9 @@ from mostlyright import CATALOG, Station, StationCatalog from mostlyright._internal._stations import STATIONS -# The 21 Kalshi NHIGH/NLOW settlement ICAOs (mirrors the markets citations). +# The 20 Kalshi NHIGH/NLOW settlement ICAOs (mirrors the markets citations). +# 2026-07 live-universe audit: KDTW/KCVG/KBNA/KSLC out (no live markets), +# KMSY/KOKC/KSAT in (NOLA/OKC/SATX series are live). _KALSHI_ICAOS = { "KATL", "KAUS", @@ -41,22 +43,24 @@ "KSEA", "KSFO", "KIAH", - "KDTW", - "KCVG", - "KBNA", - "KSLC", "KLAS", + "KMSY", + "KOKC", + "KSAT", } # Registry stations no prediction-market venue settles against. Phase 23 -# untagged 27 Polymarket cities (records kept) + move-sources, so the bare -# set grew from 4 to 29: 3 US (KHOU moved TO polymarket so it left this set) +# untagged 27 Polymarket cities (records kept) + move-sources; the 2026-07 +# live-universe audit then swapped the US membership — KMSY/KOKC/KSAT gained +# the kalshi tag (live NOLA/OKC/SATX series) while KDTW/KCVG/KBNA/KSLC lost +# it (no live markets; records kept as bare weather stations). 4 US # plus 26 intl (roster drops + the London/Moscow/Taipei/HK/Paris move-sources). _NO_VENUE_ICAOS = { - # US bare weather stations. - "KMSY", - "KOKC", - "KSAT", + # US bare weather stations (2026-07 audit: no live Kalshi markets). + "KBNA", + "KCVG", + "KDTW", + "KSLC", # Intl untagged in Phase 23 (cities dropped from the roster). "EDDB", "EDDF", @@ -102,7 +106,8 @@ def test_get_by_code_icao_and_intl() -> None: assert CATALOG.get("NYC").icao == "KNYC" assert CATALOG.get("KNYC").code == "NYC" assert CATALOG.get("EGLL").country == "GB" - # Phase 22 — the five added Kalshi settlement stations resolve. + # Phase 22 additions resolve (KDTW is a bare weather station since the + # 2026-07 audit, but its record remains). assert CATALOG.get("IAH").icao == "KIAH" assert CATALOG.get("KDTW").code == "DTW" @@ -125,11 +130,11 @@ def test_venues_union() -> None: def test_kalshi_venue_equals_settlement_universe() -> None: # The kalshi tag is the Kalshi settlement universe — NOT "every US - # station". Four US registry stations (KHOU/KMSY/KOKC/KSAT) are - # NOT Kalshi settlement stations. + # station". KHOU (Polymarket's Houston) and the 2026-07 audit drops + # (KDTW/KCVG/KBNA/KSLC) are NOT Kalshi settlement stations. kalshi = {s.icao for s in CATALOG.filter_by_venue("kalshi")} assert kalshi == _KALSHI_ICAOS - assert len(kalshi) == 21 + assert len(kalshi) == 20 def test_polymarket_venue_is_the_explicit_roster() -> None: diff --git a/packages/markets/src/mostlyright/markets/catalog/kalshi_stations.py b/packages/markets/src/mostlyright/markets/catalog/kalshi_stations.py index f6270ca9..01b1c503 100644 --- a/packages/markets/src/mostlyright/markets/catalog/kalshi_stations.py +++ b/packages/markets/src/mostlyright/markets/catalog/kalshi_stations.py @@ -40,7 +40,11 @@ def __post_init__(self) -> None: #: City ticker → (settlement station ICAO, citation URL). -#: The 21-city Kalshi NHIGH/NLOW universe per v0.1.0 scope. The citation +#: The 20-city Kalshi NHIGH/NLOW universe, verified against the live Kalshi +#: series API on 2026-07-15 (GET /trade-api/v2/series?category=Climate and +#: Weather + per-series settlement_sources). The v0.1.0 scope doc listed 21 +#: cities, four of which (DTW/CVG/BNA/SLC) never had live markets — every +#: series probe 404s — and it missed NOLA/OKC/SATX, which do. The citation #: URL is the live Kalshi contract page; if the issuer renames the city #: ticker the citation URL still resolves and the test stays green. #: @@ -87,19 +91,23 @@ def __post_init__(self) -> None: "MSP": StationCitation( "KMSP", "https://kalshi.com/markets/khighmsp (Minneapolis-St. Paul International)" ), - "DTW": StationCitation("KDTW", "https://kalshi.com/markets/khighdtw (Detroit Metropolitan)"), - "CVG": StationCitation( - "KCVG", - "https://kalshi.com/markets/khighcvg (Cincinnati/Northern Kentucky International)", - ), - "BNA": StationCitation("KBNA", "https://kalshi.com/markets/khighbna (Nashville International)"), - "SLC": StationCitation( - "KSLC", "https://kalshi.com/markets/khighslc (Salt Lake City International)" - ), "TLV": StationCitation( "KLAS", "https://kalshi.com/markets/kxhightlv (Harry Reid/McCarran; settles vs NWS CLILAS)", ), + # 2026-07 live-universe audit additions (series confirmed 200 with + # settlement_sources pointing at the NWS CLI product for each station). + "NOLA": StationCitation( + "KMSY", + "https://kalshi.com/markets/kxhightnola (Louis Armstrong Intl, NOT Lakefront KNEW; settles vs NWS CLIMSY)", + ), + "OKC": StationCitation( + "KOKC", "https://kalshi.com/markets/kxhightokc (Will Rogers World Airport)" + ), + "SATX": StationCitation( + "KSAT", + "https://kalshi.com/markets/kxhightsatx (San Antonio Intl, NOT Stinson KSSF)", + ), } @@ -116,6 +124,8 @@ def __post_init__(self) -> None: "KOAK", # SFO — Oakland (SFO is KSFO) "KHOU", # HOU — Hobby (HOU is KIAH) "KDAL", # DAL — Love Field (DAL is KDFW) + "KNEW", # NOLA — Lakefront (NOLA is KMSY) + "KSSF", # SATX — Stinson (SATX is KSAT) } ) diff --git a/packages/markets/tests/catalog/test_kalshi_stations.py b/packages/markets/tests/catalog/test_kalshi_stations.py index dd66d438..f5789017 100644 --- a/packages/markets/tests/catalog/test_kalshi_stations.py +++ b/packages/markets/tests/catalog/test_kalshi_stations.py @@ -14,9 +14,18 @@ ) -def test_whitelist_has_21_entries(): - """v0.1.0 scope — 20 original cities + Las Vegas (TLV, issue #39).""" - assert len(KALSHI_SETTLEMENT_STATIONS) == 21 +def test_whitelist_has_20_entries(): + """The 20-city live universe (2026-07 audit). + + The v0.1.0 scope doc's 21 included four cities with no live markets + (DTW/CVG/BNA/SLC — series probes 404) and missed NOLA/OKC/SATX. + """ + assert len(KALSHI_SETTLEMENT_STATIONS) == 20 + + +def test_phantom_cities_absent(): + """DTW/CVG/BNA/SLC have no Kalshi daily-temp markets — must stay out.""" + assert not {"DTW", "CVG", "BNA", "SLC"} & set(KALSHI_SETTLEMENT_STATIONS) def test_citations_match_core_kalshi_venue_tags(): @@ -70,6 +79,21 @@ def test_DCA_is_KDCA_not_KIAD_or_KBWI(): assert KALSHI_SETTLEMENT_STATIONS["DCA"].station == "KDCA" +def test_NOLA_is_KMSY_not_KNEW(): + """2026-07 audit addition — CLI issued by MSY (NWS office LIX).""" + assert KALSHI_SETTLEMENT_STATIONS["NOLA"].station == "KMSY" + + +def test_OKC_is_KOKC(): + """2026-07 audit addition — CLI issued by OKC (NWS office OUN).""" + assert KALSHI_SETTLEMENT_STATIONS["OKC"].station == "KOKC" + + +def test_SATX_is_KSAT_not_KSSF(): + """2026-07 audit addition — CLI issued by SAT (NWS office EWX).""" + assert KALSHI_SETTLEMENT_STATIONS["SATX"].station == "KSAT" + + def test_StationCitation_rejects_bad_codes(): with pytest.raises(ValueError, match="4-letter ICAO"): StationCitation("KNY", "") diff --git a/packages/weather/src/mostlyright/weather/satellite/_roster.py b/packages/weather/src/mostlyright/weather/satellite/_roster.py index c694e931..ae1af787 100644 --- a/packages/weather/src/mostlyright/weather/satellite/_roster.py +++ b/packages/weather/src/mostlyright/weather/satellite/_roster.py @@ -1,8 +1,8 @@ """Committed Kalshi/Polymarket settlement-station roster for the fleet backfill. Phase 28 (28-21). The hosted weather backfill runs as a Cloud Batch ARRAY JOB: -one shard per SATELLITE-RESOLVABLE settlement station (``task_count = 65``: the -66-station Kalshi∪Polymarket union MINUS the one non-satellite station HKO — see +one shard per SATELLITE-RESOLVABLE settlement station (``task_count = 64``: the +65-station Kalshi∪Polymarket union MINUS the one non-satellite station HKO — see ``_NON_SATELLITE_STATIONS``). Each array task resolves its shard from a STABLE, DETERMINISTIC roster so that shard ``N`` always maps to the same station across Spot retries — a stable @@ -18,10 +18,10 @@ - ``markets.polymarket.load_polymarket_city_stations()`` — the Polymarket city→role→ICAO map (inner values are the ICAO strings). -The union of those two — sorted, deduped — is 66 ICAOs; the satellite roster -frozen below is those 66 MINUS ``_NON_SATELLITE_STATIONS`` (HKO), i.e. the 65 +The union of those two — sorted, deduped — is 65 ICAOs; the satellite roster +frozen below is those 65 MINUS ``_NON_SATELLITE_STATIONS`` (HKO), i.e. the 64 that resolve to a satellite ``StationInfo`` (D-28.8; ``infra/batch.tf`` -``task_count = 65``). We snapshot it here, in ``packages/weather``, rather than +``task_count = 64``). We snapshot it here, in ``packages/weather``, rather than importing ``markets`` at runtime because: 1. The satellite/weather deploy image MUST NOT pull the ``markets`` package @@ -44,9 +44,9 @@ "shard_roster", ] -#: The canonical Kalshi/Polymarket SATELLITE-backfill roster (D-28.8): the 65 +#: The canonical Kalshi/Polymarket SATELLITE-backfill roster (D-28.8): the 64 #: 4-letter ICAO codes that are the union of the live Kalshi + Polymarket -#: settlement catalogs (66) MINUS ``_NON_SATELLITE_STATIONS`` (HKO, which has no +#: settlement catalogs (65) MINUS ``_NON_SATELLITE_STATIONS`` (HKO, which has no #: satellite StationInfo), SORTED and deduped. Verified against the live #: ``markets`` union by ``test_roster.py`` (both drift AND non-resolvable-station #: regressions fail CI). Sorted + frozen so the shard-index→station mapping is @@ -62,14 +62,11 @@ "KATL", "KAUS", "KBKF", - "KBNA", "KBOS", - "KCVG", "KDAL", "KDCA", "KDEN", "KDFW", - "KDTW", "KHOU", "KIAH", "KLAS", @@ -78,13 +75,15 @@ "KMDW", "KMIA", "KMSP", + "KMSY", "KNYC", + "KOKC", "KORD", "KPHL", "KPHX", + "KSAT", "KSEA", "KSFO", - "KSLC", "LEMD", "LFPB", "LIMC", @@ -124,31 +123,30 @@ # second source of truth — ``test_roster.py`` asserts (a) the full union equals # the live Kalshi/Polymarket catalog and (b) this set equals every live Kalshi # settlement ICAO, so drift in either fails CI. The two markets overlap (many -# stations appear in both), which is why the union — not the sum — is 66 (the satellite roster is 65: that's 66 minus HKO). The +# stations appear in both), which is why the union — not the sum — is 65 (the satellite roster is 64: that's 65 minus HKO). The # split rosters below are derived from this set so they stay in lockstep. _KALSHI_STATIONS: frozenset[str] = frozenset( { "KATL", "KAUS", - "KBNA", "KBOS", - "KCVG", "KDCA", "KDEN", "KDFW", - "KDTW", "KIAH", "KLAS", "KLAX", "KMDW", "KMIA", "KMSP", + "KMSY", "KNYC", + "KOKC", "KPHL", "KPHX", + "KSAT", "KSEA", "KSFO", - "KSLC", } ) @@ -156,8 +154,8 @@ # Committed sub-snapshot: the Polymarket-only membership of the union (the inner # ICAOs of ``load_polymarket_city_stations()``). Like ``_KALSHI_STATIONS`` this is # NOT a second source of truth — ``test_roster.py`` asserts it equals the live -# Polymarket catalog. Kalshi + Polymarket == 66 (the two overlap); the satellite -# roster is those 66 minus HKO (65). len(_KALSHI) 21 + len(_POLYMARKET) 51 != 66. +# Polymarket catalog. Kalshi + Polymarket == 65 (the two overlap); the satellite +# roster is those 65 minus HKO (64). len(_KALSHI) 20 + len(_POLYMARKET) 51 != 65. _POLYMARKET_STATIONS: frozenset[str] = frozenset( { "CYYZ", diff --git a/packages/weather/tests/satellite/test_roster.py b/packages/weather/tests/satellite/test_roster.py index e1697c14..8c4da502 100644 --- a/packages/weather/tests/satellite/test_roster.py +++ b/packages/weather/tests/satellite/test_roster.py @@ -56,9 +56,13 @@ def test_roster_equals_live_satellite_union() -> None: assert list(SETTLEMENT_STATION_ROSTER) == _live_satellite_union() -def test_roster_count_is_65() -> None: - """D-28.8 / batch.tf task_count = 65 (66-station union minus the non-satellite HKO).""" - assert len(SETTLEMENT_STATION_ROSTER) == 65 +def test_roster_count_is_64() -> None: + """D-28.8 / batch.tf task_count = 64 (65-station union minus the non-satellite HKO). + + 2026-07 live-universe audit: Kalshi dropped KBNA/KCVG/KDTW/KSLC (no live + markets) and gained KMSY/KOKC/KSAT, so the union went 66 → 65. + """ + assert len(SETTLEMENT_STATION_ROSTER) == 64 def test_non_satellite_stations_excluded_but_in_live_catalog() -> None: @@ -106,10 +110,10 @@ def test_roster_sorted_and_deduped() -> None: def test_resolve_roster_kalshi_polymarket() -> None: - """The batch.tf literal 'kalshi,polymarket' resolves to the 65 satellite stations.""" + """The batch.tf literal 'kalshi,polymarket' resolves to the 64 satellite stations.""" resolved = resolve_roster("kalshi,polymarket") assert resolved == SETTLEMENT_STATION_ROSTER - assert len(resolved) == 65 + assert len(resolved) == 64 def test_resolve_roster_splits_match_live_catalogs() -> None: @@ -138,7 +142,7 @@ def test_shard_roster_station_year_full_grid_coverage() -> None: year_start, year_end = 2017, 2025 # num_years = 9 num_years = year_end - year_start + 1 count = len(roster) * num_years - assert count == 65 * 9 == 585 + assert count == 64 * 9 == 576 seen: list[tuple[str, int]] = [] for index in range(count): diff --git a/schemas/EXPORT_MANIFEST.json b/schemas/EXPORT_MANIFEST.json index f42a6087..7a32f0e1 100644 --- a/schemas/EXPORT_MANIFEST.json +++ b/schemas/EXPORT_MANIFEST.json @@ -87,8 +87,8 @@ { "gated": false, "path": "kalshi-settlement-stations.json", - "sha256": "0cd896804d54bb14328aa9530b5c085b3e5ef4bedd525979f0fc94feb4c9abca", - "size_bytes": 2915 + "sha256": "69d2a3b44eb42026ecea846c59aea5821fa1df1682278ee1c5aaff45ac8e6410", + "size_bytes": 2854 }, { "gated": false, @@ -111,8 +111,8 @@ { "gated": false, "path": "stations.json", - "sha256": "3d82674b4ddd2707f49c42f783bb889db214be6b6ea2a89e99faf663f64535de", - "size_bytes": 25606 + "sha256": "b43dc2f9a033020f77cac2e79a9a4c82468c8df47867b375b2b1f69e7559faa8", + "size_bytes": 25582 }, { "gated": false, diff --git a/schemas/kalshi-settlement-stations.json b/schemas/kalshi-settlement-stations.json index f70bfbbc..99c123a7 100644 --- a/schemas/kalshi-settlement-stations.json +++ b/schemas/kalshi-settlement-stations.json @@ -7,8 +7,10 @@ "KIAD", "KJFK", "KLGA", + "KNEW", "KOAK", - "KORD" + "KORD", + "KSSF" ], "stations": { "ATL": { @@ -19,10 +21,6 @@ "citation": "https://kalshi.com/markets/khighaus (Austin-Bergstrom; the only Austin station Kalshi cites)", "station": "KAUS" }, - "BNA": { - "citation": "https://kalshi.com/markets/khighbna (Nashville International)", - "station": "KBNA" - }, "BOS": { "citation": "https://kalshi.com/markets/khighbos (Boston Logan)", "station": "KBOS" @@ -31,10 +29,6 @@ "citation": "https://kalshi.com/markets/khighchi (Midway, NOT ORD)", "station": "KMDW" }, - "CVG": { - "citation": "https://kalshi.com/markets/khighcvg (Cincinnati/Northern Kentucky International)", - "station": "KCVG" - }, "DAL": { "citation": "https://kalshi.com/markets/khighdal (DFW, NOT Love Field)", "station": "KDFW" @@ -47,10 +41,6 @@ "citation": "https://kalshi.com/markets/khighden (Denver International)", "station": "KDEN" }, - "DTW": { - "citation": "https://kalshi.com/markets/khighdtw (Detroit Metropolitan)", - "station": "KDTW" - }, "HOU": { "citation": "https://kalshi.com/markets/khighhou (Intercontinental, NOT Hobby; Kalshi cites IAH)", "station": "KIAH" @@ -67,10 +57,18 @@ "citation": "https://kalshi.com/markets/khighmsp (Minneapolis-St. Paul International)", "station": "KMSP" }, + "NOLA": { + "citation": "https://kalshi.com/markets/kxhightnola (Louis Armstrong Intl, NOT Lakefront KNEW; settles vs NWS CLIMSY)", + "station": "KMSY" + }, "NYC": { "citation": "https://kalshi.com/markets/khighny (Central Park, NOT LGA/JFK)", "station": "KNYC" }, + "OKC": { + "citation": "https://kalshi.com/markets/kxhightokc (Will Rogers World Airport)", + "station": "KOKC" + }, "PHL": { "citation": "https://kalshi.com/markets/khighphl (Philadelphia International)", "station": "KPHL" @@ -79,6 +77,10 @@ "citation": "https://kalshi.com/markets/khighphx (Sky Harbor International)", "station": "KPHX" }, + "SATX": { + "citation": "https://kalshi.com/markets/kxhightsatx (San Antonio Intl, NOT Stinson KSSF)", + "station": "KSAT" + }, "SEA": { "citation": "https://kalshi.com/markets/khighsea (SeaTac, NOT BFI)", "station": "KSEA" @@ -87,10 +89,6 @@ "citation": "https://kalshi.com/markets/khighsfo (San Francisco International, NOT OAK)", "station": "KSFO" }, - "SLC": { - "citation": "https://kalshi.com/markets/khighslc (Salt Lake City International)", - "station": "KSLC" - }, "TLV": { "citation": "https://kalshi.com/markets/kxhightlv (Harry Reid/McCarran; settles vs NWS CLILAS)", "station": "KLAS" diff --git a/schemas/stations.json b/schemas/stations.json index 5efc47a3..2d57759e 100644 --- a/schemas/stations.json +++ b/schemas/stations.json @@ -207,9 +207,7 @@ "longitude": -86.6782, "name": "Nashville International", "tz": "America/Chicago", - "venues": [ - "kalshi" - ] + "venues": [] }, { "code": "BOS", @@ -233,9 +231,7 @@ "longitude": -84.6678, "name": "Cincinnati/Northern Kentucky International", "tz": "America/New_York", - "venues": [ - "kalshi" - ] + "venues": [] }, { "code": "DAL", @@ -298,9 +294,7 @@ "longitude": -83.3534, "name": "Detroit Metropolitan Wayne County", "tz": "America/New_York", - "venues": [ - "kalshi" - ] + "venues": [] }, { "code": "HOU", @@ -417,7 +411,9 @@ "longitude": -90.258, "name": "New Orleans Louis Armstrong International", "tz": "America/Chicago", - "venues": [] + "venues": [ + "kalshi" + ] }, { "code": "NYC", @@ -441,7 +437,9 @@ "longitude": -97.6007, "name": "Oklahoma City Will Rogers World", "tz": "America/Chicago", - "venues": [] + "venues": [ + "kalshi" + ] }, { "code": "ORD", @@ -491,7 +489,9 @@ "longitude": -98.4698, "name": "San Antonio International", "tz": "America/Chicago", - "venues": [] + "venues": [ + "kalshi" + ] }, { "code": "SEA", @@ -530,9 +530,7 @@ "longitude": -111.9778, "name": "Salt Lake City International", "tz": "America/Denver", - "venues": [ - "kalshi" - ] + "venues": [] }, { "code": "LEBL", From 4642a39d9f3a4d7a5db33ab9b0bfb0e2149b692f Mon Sep 17 00:00:00 2001 From: minereda <84080887+minereda@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:07:12 +0200 Subject: [PATCH 2/4] fix(ts): resolve modern Kalshi temperature tickers --- packages-ts/meta/src/compose.ts | 22 ++++++++++++++++++++++ packages-ts/meta/tests/compose.test.ts | 10 ++++++++++ 2 files changed, 32 insertions(+) diff --git a/packages-ts/meta/src/compose.ts b/packages-ts/meta/src/compose.ts index 40653f67..5876447c 100644 --- a/packages-ts/meta/src/compose.ts +++ b/packages-ts/meta/src/compose.ts @@ -24,6 +24,28 @@ export type SelectorName = (typeof SELECTOR_NAMES)[number]; */ const KALSHI_TICKER_ALIASES: Record = { NY: "NYC", + NYD: "NYC", + PHIL: "PHL", + TATL: "ATL", + TAUS: "AUS", + TBOS: "BOS", + TCHI: "CHI", + TDAL: "DAL", + TDC: "DCA", + TDEN: "DEN", + TEMPDEN: "DEN", + THOU: "HOU", + TLAX: "LAX", + TMIA: "MIA", + TMIN: "MSP", + TNOLA: "NOLA", + TNYC: "NYC", + TOKC: "OKC", + TPHIL: "PHL", + TPHX: "PHX", + TSATX: "SATX", + TSEA: "SEA", + TSFO: "SFO", }; /** diff --git a/packages-ts/meta/tests/compose.test.ts b/packages-ts/meta/tests/compose.test.ts index f39e6314..7ed46949 100644 --- a/packages-ts/meta/tests/compose.test.ts +++ b/packages-ts/meta/tests/compose.test.ts @@ -75,6 +75,16 @@ describe("resolveContract", () => { expect(resolveContract("kalshi:KXLOWNY-25MAY26-T50")).toEqual(["KNYC", "kalshi"]); }); + it.each([ + ["KXHIGHTATL", "KATL"], + ["KXHIGHTNOLA", "KMSY"], + ["KXHIGHTOKC", "KOKC"], + ["KXHIGHTSATX", "KSAT"], + ["KXLOWTDC", "KDCA"], + ])("kalshi:%s resolves modern T-prefixed series to %s", (ticker, station) => { + expect(resolveContract(`kalshi:${ticker}-26JUL16-T80`)).toEqual([station, "kalshi"]); + }); + it("kalshi:KHIGHCHI → KMDW (Kalshi uses Midway)", () => { expect(resolveContract("kalshi:KHIGHCHI")).toEqual(["KMDW", "kalshi"]); }); From b806350e3b3f8b82f7a67ed2c152ec955eaea6f6 Mon Sep 17 00:00:00 2001 From: minereda <84080887+minereda@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:09:48 +0200 Subject: [PATCH 3/4] fix(markets): resolve live Kalshi series aliases --- packages-ts/markets/src/kalshi-ticker.ts | 22 +++++++++++++++++++ .../markets/tests/strike-parsers.test.ts | 9 ++++++++ .../markets/src/mostlyright/markets/kalshi.py | 22 +++++++++++++++++++ .../markets/tests/test_markets_dataset.py | 12 ++++++++++ 4 files changed, 65 insertions(+) diff --git a/packages-ts/markets/src/kalshi-ticker.ts b/packages-ts/markets/src/kalshi-ticker.ts index f60e3a0d..f7a9cdb1 100644 --- a/packages-ts/markets/src/kalshi-ticker.ts +++ b/packages-ts/markets/src/kalshi-ticker.ts @@ -59,6 +59,28 @@ export interface ParsedKalshiTicker { */ const SERIES_CITY_ALIASES: Readonly> = { NY: "NYC", + NYD: "NYC", + PHIL: "PHL", + TATL: "ATL", + TAUS: "AUS", + TBOS: "BOS", + TCHI: "CHI", + TDAL: "DAL", + TDC: "DCA", + TDEN: "DEN", + TEMPDEN: "DEN", + THOU: "HOU", + TLAX: "LAX", + TMIA: "MIA", + TMIN: "MSP", + TNOLA: "NOLA", + TNYC: "NYC", + TOKC: "OKC", + TPHIL: "PHL", + TPHX: "PHX", + TSATX: "SATX", + TSEA: "SEA", + TSFO: "SFO", }; // `KX`|`K` prefix, HIGH|LOW measure, city suffix, then the strike suffix. diff --git a/packages-ts/markets/tests/strike-parsers.test.ts b/packages-ts/markets/tests/strike-parsers.test.ts index ad7e2578..88387da6 100644 --- a/packages-ts/markets/tests/strike-parsers.test.ts +++ b/packages-ts/markets/tests/strike-parsers.test.ts @@ -38,6 +38,15 @@ describe("parseTicker (Kalshi)", () => { expect(p.station).toBe("KNYC"); }); + it.each([ + ["KXHIGHTNOLA-26JUL16-T80", "KMSY"], + ["KXHIGHTOKC-26JUL16-T80", "KOKC"], + ["KXHIGHTSATX-26JUL16-T80", "KSAT"], + ["KXLOWTDC-26JUL16-T40", "KDCA"], + ])("parses modern T-prefixed series %s", (ticker, station) => { + expect(parseTicker(ticker).station).toBe(station); + }); + it("accepts the K (no X) prefix", () => { const p = parseTicker("KHIGHNYC-25MAY26-T79"); expect(p.station).toBe("KNYC"); diff --git a/packages/markets/src/mostlyright/markets/kalshi.py b/packages/markets/src/mostlyright/markets/kalshi.py index 25720e9c..9eddef65 100644 --- a/packages/markets/src/mostlyright/markets/kalshi.py +++ b/packages/markets/src/mostlyright/markets/kalshi.py @@ -84,6 +84,28 @@ #: miss. Confirmed live example: ``KXHIGHNY`` (New York → KNYC). _SERIES_CITY_ALIASES: dict[str, str] = { "NY": "NYC", + "NYD": "NYC", + "PHIL": "PHL", + "TATL": "ATL", + "TAUS": "AUS", + "TBOS": "BOS", + "TCHI": "CHI", + "TDAL": "DAL", + "TDC": "DCA", + "TDEN": "DEN", + "TEMPDEN": "DEN", + "THOU": "HOU", + "TLAX": "LAX", + "TMIA": "MIA", + "TMIN": "MSP", + "TNOLA": "NOLA", + "TNYC": "NYC", + "TOKC": "OKC", + "TPHIL": "PHL", + "TPHX": "PHX", + "TSATX": "SATX", + "TSEA": "SEA", + "TSFO": "SFO", } diff --git a/packages/markets/tests/test_markets_dataset.py b/packages/markets/tests/test_markets_dataset.py index 615df07a..bc02c269 100644 --- a/packages/markets/tests/test_markets_dataset.py +++ b/packages/markets/tests/test_markets_dataset.py @@ -112,6 +112,18 @@ def test_threshold_high(self): assert p.strike_kind == "threshold" assert p.threshold == 79 + @pytest.mark.parametrize( + ("ticker", "station"), + [ + ("KXHIGHTNOLA-26JUL16-T80", "KMSY"), + ("KXHIGHTOKC-26JUL16-T80", "KOKC"), + ("KXHIGHTSATX-26JUL16-T80", "KSAT"), + ("KXLOWTDC-26JUL16-T40", "KDCA"), + ], + ) + def test_modern_t_prefixed_series(self, ticker, station): + assert parse_ticker(ticker).station == station + def test_range_low(self): p = parse_ticker("KXLOWCHI-25MAY26-B40-42") assert p.measure == "low" From 78fa8b23259e9922d0ec1d8a744eaf43335810bb Mon Sep 17 00:00:00 2001 From: minereda <84080887+minereda@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:16:40 +0200 Subject: [PATCH 4/4] fix: reject non-CLI NYD contract family --- packages-ts/markets/src/kalshi-ticker.ts | 1 - packages-ts/meta/src/compose.ts | 1 - packages/core/src/mostlyright/_compose.py | 1 - packages/markets/src/mostlyright/markets/kalshi.py | 1 - 4 files changed, 4 deletions(-) diff --git a/packages-ts/markets/src/kalshi-ticker.ts b/packages-ts/markets/src/kalshi-ticker.ts index f7a9cdb1..ac6a72d8 100644 --- a/packages-ts/markets/src/kalshi-ticker.ts +++ b/packages-ts/markets/src/kalshi-ticker.ts @@ -59,7 +59,6 @@ export interface ParsedKalshiTicker { */ const SERIES_CITY_ALIASES: Readonly> = { NY: "NYC", - NYD: "NYC", PHIL: "PHL", TATL: "ATL", TAUS: "AUS", diff --git a/packages-ts/meta/src/compose.ts b/packages-ts/meta/src/compose.ts index 5876447c..cefa2169 100644 --- a/packages-ts/meta/src/compose.ts +++ b/packages-ts/meta/src/compose.ts @@ -24,7 +24,6 @@ export type SelectorName = (typeof SELECTOR_NAMES)[number]; */ const KALSHI_TICKER_ALIASES: Record = { NY: "NYC", - NYD: "NYC", PHIL: "PHL", TATL: "ATL", TAUS: "AUS", diff --git a/packages/core/src/mostlyright/_compose.py b/packages/core/src/mostlyright/_compose.py index 89298df3..5fbbae4f 100644 --- a/packages/core/src/mostlyright/_compose.py +++ b/packages/core/src/mostlyright/_compose.py @@ -31,7 +31,6 @@ #: ROADMAP example) would fail to resolve. _KALSHI_TICKER_ALIASES: dict[str, str] = { "NY": "NYC", - "NYD": "NYC", # KXHIGHNYD — NYC daily series variant "PHIL": "PHL", # KXHIGHPHIL / KXLOWPHIL # The modern "KXHIGHT" / "KXLOWT" series family (verified # against the live Kalshi series list, 2026-07-15): after the KX→K diff --git a/packages/markets/src/mostlyright/markets/kalshi.py b/packages/markets/src/mostlyright/markets/kalshi.py index 9eddef65..6cd67c8a 100644 --- a/packages/markets/src/mostlyright/markets/kalshi.py +++ b/packages/markets/src/mostlyright/markets/kalshi.py @@ -84,7 +84,6 @@ #: miss. Confirmed live example: ``KXHIGHNY`` (New York → KNYC). _SERIES_CITY_ALIASES: dict[str, str] = { "NY": "NYC", - "NYD": "NYC", "PHIL": "PHL", "TATL": "ATL", "TAUS": "AUS",