How the post-fee edge is computed
Every edge on /arbs is net of both exchanges’ fees. Here are the exact formulas the scanner runs, with a worked example. No hand-waving — these are the same numbers in the code.
1 · Kalshi trade fee
Kalshi charges a per-trade fee that peaks at a 50¢ contract and shrinks toward the edges (cheap or near-certain contracts cost less to trade):
Example: 100 contracts at 46¢ → 0.07 × 100 × 0.46 × 0.54 = $1.74 ≈ $0.0174 per contract. All-in Kalshi cost per share = 0.46 + 0.0174 = $0.4774.
2 · Polymarket fee
Polymarket takes 2% of your net winnings (profit, not the gross payout) plus a flat $0.02 of gas per fill. Losing trades pay only the gas:
Example: buy the NO side at 47¢ to cover a $100 payout → stake $47, net winnings $53, fee = 0.02 × 53 + 0.02 = $1.08. All-in Polymarket cost per share = (47 + 1.08) / 100 = $0.4808.
3 · The arb: cost per guaranteed $1
Buy YES on one venue and NO on the other for the same event. Exactly one resolves true, so the pair pays $1.00 no matter the outcome. The arb exists when the combined all-in cost is under a dollar:
edge = $1.00 − cost
From the example above: 0.4774 + 0.4808 = $0.9582 for a guaranteed $1 → 4.18% edge. The raw quotes summed to 93¢ (a naïve 7% gap); fees ate ~3 points of it. We only surface a pair once that post-fee edge clears 2%.
4 · Why the shown edge is conservative
Polymarket only charges its 2% on the winning leg. We don’t know in advance which leg wins, so the scanner assumes the Polymarket side wins — the case that maximizes the Polymarket fee. That gives the guaranteed-minimum edge: the number you see is the worst case, never an optimistic one. Real fills are usually a touch better.
5 · What we refuse to call an arb
- Wide books.If either side’s bid-ask spread is over 10¢, the quote isn’t a price you can actually hit — skipped.
- Untradeable size. If less than $50 is fillable at the quoted prices, the edge is real on paper but not worth a trade — skipped.
- Implausible edges.An edge above 15% almost always means one side’s book is stale or mis-parsed, not a real gap — bucketed out of the live feed.