Matcher refactor — findings and honest path forward
Date: 2026-04-18
Commits today: 0ebb6c1 (Kalshi v2 endpoint), 01d7c09 (candidate pool widening), 5a2e904 (config key typo).
What I found while trying to refactor the matcher
The matcher’s market_family equality gate at pairing.py:949 is technically blocking pairs, but fixing that alone won’t surface arbitrage. Going one layer deeper revealed three compounded problems that make Kalshi ↔ Polymarket strict arbitrage structurally hard today.
Problem 1: Products differ by design
Kalshi commodities are dense binary threshold ladders:
family=threshold asset=None comparator=above threshold=74.25 "silver close > 74.25 on Apr 20"
family=threshold asset=None comparator=above threshold=74.50 "silver close > 74.50 on Apr 20"
family=threshold asset=None comparator=above threshold=74.75 "silver close > 74.75 on Apr 20"
... 40 contracts, 25-cent steps
Polymarket commodities are categorical bucket markets:
family=occurrence asset=None comparator=None threshold=None "What will Silver (SI) settle at in June?"
These are not equivalent pairs. The only way to arbitrage between them is to reconstruct a CDF from Kalshi’s threshold ladder and compare to Polymarket’s bucket distribution. That’s a new matching mode, not a tweak.
Problem 2: Normalizer quality
Scanning normalized contracts revealed two separate normalization bugs:
- Year-as-threshold: Polymarket “2026 NBA Draft: 1st Overall pick” gets
threshold=2026.0. The normalizer is extracting the year from the title as if it were a price. - Missing asset tags: Kalshi has 978 threshold markets but only 21 have
assetpopulated. All the commodity ladders haveasset=None. The Kalshi normalizer isn’t extracting “silver” / “gold” / “oil” / “brent” from titles.
Both need dedicated fixes. Without asset on Kalshi, the matcher can’t build a cross-venue asset bridge even with relaxed family rules.
Problem 3: Overlap actually is thin
After the endpoint fix, 90-day in-horizon pools are 2,077 Kalshi × 2,380 Polymarket. Category breakdown:
| Category | Kalshi | Polymarket |
|---|---|---|
| sports | 13 | 975 |
| politics | 17 | 511 |
| crypto | 8 | 11 |
| commodities | 222 | 104 |
| econ | 15 | 37 |
| entertainment | 16 | 16 |
| other | 1786 | 726 |
The only category where both venues are non-trivially present is commodities — which hits Problem 1.
Honest conclusion
Strict Kalshi ↔ Polymarket arbitrage is a narrow-to-empty opportunity today without larger investment in (a) a CDF matching mode and (b) normalizer fixes. My earlier work and Codex’s endpoint fix were both correct — they just don’t unlock the downstream edge because the downstream is structurally limited.
Three viable paths forward, ranked
1. Intra-Kalshi arbitrage on threshold ladders (recommended — fastest edge)
Kalshi has 40-contract silver ladders, 35-contract oil ladders, etc. On dense ladders, prices occasionally go out of monotonic order (e.g., “silver > 74.50” trades higher than “silver > 74.25”), which is a risk-free arb. The arbitrage/intra_venue/ code exists but intraarb.py is only wired to sample data — wire it to the catalog inventory and scan.
- Effort: ~1 day.
- Risk: Low. Single-venue, atomic execution.
- Revenue shape: Many small edges, compounds. Good for the €50/venue cap.
2. Fix normalizer, then CDF matcher (medium-term — biggest TAM if it works)
Two subprojects:
- Normalizer: extract
assetfrom Kalshi commodity titles; skip year-looking numbers in threshold extraction. -
CDF matcher: new matching mode that takes a Kalshi threshold ladder + Polymarket bucket market on the same asset in the same window, and finds arb between the implied distributions.
-
Effort: ~1 week.
- Risk: Medium. New logic, needs careful testing against synthetic inconsistencies.
3. Reframe as thematic-divergence signal, not arbitrage (product pivot)
Accept that the venues don’t share propositions. Instead: identify same-asset same-window markets where Kalshi’s implied distribution diverges from Polymarket’s implied distribution by more than X. Surface those as signals for human discretion, not auto-arb.
- Effort: ~3-5 days.
- Risk: Lower, because human-in-loop absorbs matching ambiguity.
- Fits Pam’s existing Telegram-approval architecture.
My recommendation
Path 1 this week (intra-Kalshi) for near-term €-capital deployment under your overnight caps. Path 2 behind that if Path 1 produces any revenue at all — it proves the edge exists, worth the invest. Path 3 as a hedge if Path 2 takes too long.
What NOT to do
Do not keep tuning the cross-venue matcher thresholds. I tried. The remaining 0-result isn’t about thresholds; it’s about the underlying pools not sharing arbitrageable propositions.
Changes committed today
0ebb6c1— Kalshi/v1/events→/trade-api/v2/markets(216k inventory pulled, was 477 stale)01d7c09— Candidate pool widening (family+token → token-only, 216 → 1,371 poly candidates)5a2e904— Config key typo fix (expiry_tolerance_seconds→_sec, was silently using 90s default)
Net: the scanner now evaluates the right universe with the right tolerances. No pairs surface because the underlying products differ. That’s a product-reality answer, not a config answer.