ComboCrusher

The engine

Five stages. Most days it ends at stage four.

The pipeline runs once a day against Kalshi's public market data, with order-book refreshes every three hours. Nothing here is proprietary magic — it's ordinary data engineering applied consistently to a market structure that rewards consistency.

StagesDaily, 11:00 CT
01_ingestBronze

Pulls open markets across 40+ series — MLB, WNBA, UFC, MLS, EPL, La Liga and others — through Kalshi's public API using signed requests, with rate limiting and exponential backoff. Raw responses land in bronze untouched, so any later mistake can be traced back to what the exchange actually returned.

02_transformBronze → Silver

Derives mid price, implied probability, bid-ask spread in cents, and hours to close. The important part is unification: markets for the same game arrive under different series tickers, so a moneyline and a total for one game are separate rows with nothing linking them. A derived game key joins them.

Without that join the scorer never sees both legs of a game in the same slate and the entire premise fails silently. It is the least interesting line of code in the project and the one most likely to break everything.

03_scoreSilver → Candidates

One call per game slate to a pinned Claude model, given the full set of that game's markets and asked for strict JSON. The job is to distinguish three cases that look identical to a naive correlation screen:

  • Redundant — one leg logically implies the other. A cover of −1.5 implies the win. There is no combo here, only a mispriced tautology.
  • Correlated — legs share a driver without implying each other. Same game script, same pace, same weather.
  • Independent — the product of the legs is roughly fair.

Only the middle case is interesting, and only when the quoted price disagrees with the structure.

04_quote_probeVerification

A scored candidate is a hypothesis about a price, not a price. This stage reads the live public order book and checks whether the quote is real and reachable: is there depth, how wide is the spread, and has the market moved since scoring.

This runs as pure read-only requests against public endpoints. It creates no orders and cancels no orders. An earlier design that probed pricing by opening and withdrawing requests was scrapped, because a pattern of requests that are never intended to be filled looks like exactly the thing market regulators exist to stop, regardless of intent.

04_alertGold → Out

Sends by SMS and email only if the candidate clears the delta threshold, has verified book data, and has not gone stale. Every run writes a row to the log — including, and especially, the runs that produce nothing. The first live run produced zero eligible combos and wrote a sentinel row saying so. That's the system working.

GatesReasons to say nothing

Four ways a candidate dies.

A gap between the quoted price and the product of the legs is necessary but nowhere near sufficient. Most flagged candidates are killed by one of these before anyone hears about them.

GateKills the candidate when
RedundancyThe legs imply each other, so the product was never the right benchmark.
LiquidityThe book is too thin to fill at any size that matters, or the spread eats the gap.
StalenessThe price moved materially between scoring and alerting. However large the delta looks, it is describing a price that no longer exists.
FeesThe gap does not survive Kalshi's fee schedule at the relevant contract count.
On staleness

This is the failure mode that produces the most convincing false signals. A large delta computed against a price from forty minutes ago is not an edge, it's a lag artifact — and it looks better the more stale it is. Everything downstream carries a flag for whether the price moved since scoring.

Open questionsNot solved

What we don't know yet.

Listing these is not modesty, it's the point. A tool that claims to have everything figured out on a sample this small is telling you something about itself.

  • Size-dependent pricing has at least two causes. Part of it is a deterministic fee step-up at contract-count thresholds, which is a lookup table. Part of it appears to be maker quotes widening as size grows, which is empirical and is the more interesting lane. Separating the two cleanly is unfinished work.
  • Marginal leg pricing is inconsistent. The same leg has been observed carrying different prices depending on what it was combined with. That inconsistency is either a huntable pattern or an artifact of how quotes are assembled. Not yet resolved.
  • Edge decay is likely. As liquidity professionalizes, the crude version of this gets arbitraged away. The assumption is that it does, and that the durable asset is the measurement discipline, not any particular discrepancy.
  • Platform risk is real. If the exchange builds combo pricing analytics natively, most of this becomes redundant overnight. That's a known and accepted risk.
StackFor the curious

Delta Lake medallion architecture on Databricks with Unity Catalog, orchestrated as a scheduled job with task dependencies and failure alerting. Infrastructure defined as code rather than clicked together in a UI. Market data from Kalshi's public API. Scoring by a pinned Claude model with a cached system prompt, one call per game slate. Alerts via Twilio and Resend. The pinning matters: an unpinned model version means yesterday's scores and today's scores came from different judges, which quietly ruins the record.