Kalshi endpoint fix — results
Date: 2026-04-17
Commit: 0ebb6c1
Continues from: Codex handoff (uncommitted work on execution/kalshi_api.py, watchers/catalog_inventory.py, tests/test_kalshi_api.py).
What changed
- Kalshi API path:
/v1/events→/trade-api/v2/marketswith cursor pagination. - Catalog inventory:
sync_kalshi_oncepulls from/marketsdirectly instead of flattening events. - Tests: 3 new kalshi_api tests, all pass.
- Matcher config (pairing block): added to
config.yamlwith loosened defaults —require_exact_threshold: false,min_question_similarity: 0.70,expiry_tolerance_sec: 86400(was 90 seconds),near_pair_expiry_tolerance_sec: 604800.
What the resync revealed
- Raw inventory: 216,702 Kalshi markets inserted (was ~477 before, all stale long-dated).
- After normalization + contract cache rebuild: 2,573 Kalshi + 7,285 Polymarket.
- In 90-day horizon: 2,077 Kalshi + 2,380 Polymarket.
- Kalshi universe composition (50k open):
- 31,287
KXMVESPORTSMULTIGAMEEXTENDED(62%) - 18,195
KXMVECROSSCATEGORY(36%) - Remainder: commodities (gold/silver/copper/brent/natgas), MLB variants, tennis (ATP/ITF), NBA, Valorant — ~256 non-sports/non-MV candidates out of 50k raw.
Scanner funnel (current state)
Kalshi raw inventory: 216,702
→ normalized: 2,573
→ in 90d horizon: 2,077 ← passed to scanner
Polymarket normalized: 7,285
→ in 90d horizon: 2,380
→ candidate pool: 216 ← scanner narrows here (load_candidate_contracts)
Matcher output: 0 exact, 0 near-pair
Where the remaining leak is
The scanner calls load_candidate_contracts which narrows Polymarket from 2,380 → 216 by requiring (market_family, token) intersection with Kalshi source contracts. This kills the commodities crossover I measured:
- Gold: 80 Kalshi × 66 Polymarket (in-horizon pool) — none matched
- Silver: 80 × 66 — none matched
- Oil/Crude: 110 × 152 — none matched
Candidate examples from Kalshi: KXGOLDD / KXSILVERD / KXBRENTD — daily close-price markets (family=”daily_settle” or similar). Polymarket likely has “Gold above $X by Y” style (family=”threshold”). Same asset, different market_family, so the intersection yields empty.
The fix after this one
Either:
- Widen the candidate pool — intersect on token only, not
(family, token)— let the matcher decide. - Add an asset-based path — if both sides share an
assettag (gold,silver,oil,btc) plus an in-horizon window, include them regardless of family. Matcher then filters on price-axis compatibility (comparator + threshold). - Build a “thematic divergence” workflow — the real edge on Kalshi/Polymarket isn’t strict arbitrage (markets don’t line up exactly); it’s identifying same-asset same-window bets where pricing diverges meaningfully, with a human-in-the-loop validation.
My recommendation: start with #1 (one-line change in load_candidate_contracts) and re-run the scan. If that produces usable near-pair candidates, we ship. If still nothing, #2 is next, and #3 is the reframe.
Ready-to-trade state
Zero tradeable opportunities produced today. €50/venue caps you authorized remain unused. Placing blind trades without validated edge is -EV; holding.
Suggested next step
Attempt the one-line candidate-pool widening in load_candidate_contracts (drop family constraint, keep token). ~10-minute fix, one re-scan, definitive answer on whether this is a pre-filter problem or a deeper matcher problem.