DEV Community

Matvey
Matvey

Posted on

Ted Cruz Led a 128-Candidate Race at 2.1%. The 44% Favourite Was Eighth by Volume

I run a visa agency in Bali. The other half of my week goes into Apify Actors, and one of them is an election board: every political contract on Kalshi and Polymarket in one table, same field names for both, so you can read the two venues side by side.

What people want from it is one sentence — "who is favoured". What the two APIs hand you is thirty-odd numeric fields, several of which look like they answer that and do not. I spent an afternoon measuring how wrong you get by picking the wrong one.

One race, 128 candidates, $716 million

Polymarket's Republican Presidential Nominee 2028 event is one race in the UI and 128 separate yes/no markets in the API, carrying $716,560,972 of cumulative volume. Nothing on it is quiet.

Sort its candidates by 24-hour volume — Gamma's default, my Actor's default, and the one that sounds most like "what matters right now":

# Candidate volume24hr Price
1 Ted Cruz $77,802 0.0205
2 John McEntee $70,645 0.0015
3 John Thune $70,072 0.0005
4 Marco Rubio $33,909 0.1625
5 Vivek Ramaswamy $7,921 0.0015
6 Ron DeSantis $4,334 0.0320
7 Donald Trump $4,093 0.0195
8 J.D. Vance $2,777 0.4395

The market's actual favourite — 43.95%, nearly half the field's probability mass — is eighth, on $2,777 traded in a day. Three of the seven rows above him are priced at or under 0.15%.

That is not a bug in either API — it is what volume measures: disagreement and churn. Once a contract is near-certain the spread collapses to a tick and nobody trades it. The busy ones are those people still argue about at two cents.

Sorting by cumulative volume instead is worse, not better:

Candidate volumeNum Price
Byron Donalds $44,556,583 0.0015
Mike Pence $43,740,818 0.0015
John Thune $38,383,281 0.0005
Tom Brady $33,013,140 0.0005
Kim Kardashian $28,597,926 0.0005

Tom Brady has thirty-three million dollars of lifetime volume on the Republican nomination at a price of five hundredths of one per cent. Long shots sit on the board for years collecting liquidity rewards, accumulating volume unrelated to anyone's belief. Of the 128 markets, 119 are priced under 1% and 94 report no 24-hour volume — nine contenders wearing a tail of 119.

So sort by price? Also no

The obvious fix — rank by outcomePrices[0] and call the top row the favourite — put this first in the same event:

Robert F. Kennedy Jr.   price 0.4900   volume null   bestBid null   bestAsk 0.98   active false
Enter fullscreen mode Exit fullscreen mode

There are two Robert F. Kennedy Jr. rows in that event. The live one trades at 0.0035 on $16.9M of volume. The other is a retired duplicate with a frozen 49% quote, no volume, no bid and a 98-cent ask — unhittable on either side. The only thing marking it is active: false.

Two one-line defences: query Gamma with active=true&closed=false, as my collector does, so the dead row never arrives; and treat the venue's status flag as your first filter — in my schema that row becomes status: "inactive" and is dropped before sorting. A stale 49% survives review precisely because 49% is a plausible number for a nomination favourite.

What I actually ask for now

The fix is structural: rank races, not rows. Events mode collapses a race into one row with every candidate sorted by probability.

{ "mode": "events", "minVolume": 5000, "maxMarkets": 10 }
Enter fullscreen mode Exit fullscreen mode

Nine of the ten rows Election Odds API returned on 25 October 2026:

Platform Race Favourite Candidates
kalshi 2028 Democratic presidential nominee Jon Ossoff 15.5% 49
kalshi 2028 U.S. Presidential Election winner? J.D. Vance 20.5% 32
kalshi Which party will win the U.S. House? Democratic Party 90.7% 2
polymarket Brazil Presidential Election Flávio Bolsonaro 78.8% 2
polymarket Republican Presidential Nominee 2028 Ted Cruz 2.1% 3
kalshi 2026 Midterms: Congress Balance of Power? Democrats sweep 64.5% 4
kalshi Texas Senate winner? James Talarico 64.5% 2
polymarket Balance of Power: 2026 Midterms Democrats Sweep 66.5% 3
polymarket Which party will win the House in 2026? Democratic Party 91.5% 2

Note row five. Grouping fixed the shape, not the favourite: it happens after the row cap, so with maxMarkets: 10 the collector keeps the busiest markets per venue — Cruz, McEntee and Thune. A 128-candidate race arrived with 3 candidates and a 2% favourite.

favourite is the favourite among the candidates in the row. On a wide race, raise maxMarkets until outcomeCount stops growing, or narrow with query. Kalshi's equivalent race returned 49 outcomes and a sane 15.5% leader for that reason — it fit.

Neither venue will tell you what an election is

The other half of the afternoon went into the filter that sounds trivial: keep elections, drop the rest.

Kalshi looks easy — one category string per event, splitting 1,600 open events cleanly:

Elections 894 · Politics 219 · Sports 137 · Economics 107
Entertainment 93 · Financials 55 · Science and Technology 39
Enter fullscreen mode Exit fullscreen mode

Then you read it. Under Elections: "Who will be the next President of the Federal Reserve Bank of Atlanta?", "Who will the next Pope be?", "Who will be Israeli defense minister after the election?" — an appointment, a conclave, a coalition deal. Under Politics: "Who will be the next DNC Chair?", "Which state will vote first in the 2028 Democratic presidential primary?", "When will Wales call a snap election?". Twenty of the 219 Politics titles carry an electoral word.

Polymarket has no category field at all — just a tag list, 7.9 tags per election event on average and up to 13, mixing geography, theme and promotions: ["United States", "Rewards 20, 4.5, 50", "Politics", "US Election", "Elections", "Midterms", "Earn 3.25%", "Main Election", "Global Elections"].

Filtering on the Politics tag is what most people try. Of the 500 busiest open events, 122 carry it — and 49 of those 122 are not elections: Elon Musk tweet counts, "Will the US confirm that aliens exist by…?", "Will China invade Taiwan by end of 2026?", "Putin out as President of Russia by…?".

The field my schema calls category for Polymarket is just tags[0], in whatever order the venue returned. Of 74 election events, exactly 3 have an election word in the first tag. Values from one ten-row run: Politics, United States, Serbia, Main Election, and — for "Which party will win the House in 2026?" — Parent For Derivative, an internal bookkeeping label.

So: never filter the exported CSV on category. Kalshi says Elections on every row and Polymarket never does, making that a venue filter in disguise. Filter on tags or on the words in question — which is what the Actor does, because tags drag Fed-rate markets onto an election board.

Calling it

Neither venue needs an API key, but the merge, dedup and status filtering live in the Actor:

curl -X POST "https://api.apify.com/v2/acts/lergassy~election-odds-api/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"mode":"markets","minVolume":1000,"sortBy":"volume24h","maxMarkets":10}'
Enter fullscreen mode Exit fullscreen mode

A row, trimmed:

{
  "platform": "kalshi",
  "marketId": "CONTROLH-2026-R",
  "question": "Which party will win the U.S. House? — Republican Party",
  "category": "Elections",
  "probability": 9.9,
  "yesPrice": 0.0985,
  "bestBid": 0.098,
  "bestAsk": 0.099,
  "priceChange24h": 0.01,
  "volume24h": 1314909.71,
  "openInterest": 35669920.8,
  "status": "open",
  "closeTime": "2027-02-01T15:00:00Z",
  "rules": "If the Republican Party has won control of the House in 2026, then the market resolves to Yes."
}
Enter fullscreen mode Exit fullscreen mode

Two fields to read twice: probability is the number to quote, in per cent, and priceChange24h is a change in price — 0.01 is one percentage point of probability, not one per cent of the price.

When this is the wrong tool

It is not a forecast — it is what people are paying right now, fees and thin tail included. Need the whole board rather than elections (Fed decisions, weather, sports)? Use Prediction Markets API; this listing drops those on purpose. Need tick-level depth? Pull the order book from the venue — the Actor gives a snapshot, not a feed. Care about one race? Query it by keyword instead of filtering a 200-row board.

The lesson is older than prediction markets and I keep relearning it: when a payload offers several fields that could rank your rows, the one the endpoint sorts by default is the one the venue cares about, not the one your reader does. Here that gap is Ted Cruz at 2% standing in front of a 44% favourite.

Top comments (0)