Voltar ao blog
EngineeringDeFiResearch

Sports Arbitrage: Finding the Trade Was the Easy Part

What we learned building a cross-platform prediction-market arbitrage system: detection is the easy part; execution, capital, and reconciliation are the real product.

Baltasar Aroso, Bernardo Cardoso14 min de leitura

Seis pontos. É o que as pessoas costumam lembrar. O texto completo está no outro separador.

  1. 1 / 6 · The edge

    Buy both sides for less than $1.

    If two contracts cover the same event, and you can buy them after fees for $0.97, the locked payout is $1. Depth matters. A cheap ask with no size is not a trade.

  2. 2 / 6 · The catch

    Nothing is locked until both legs fill.

    The formula is clean. The path is not. One fill without the hedge is just a directional bet.

  3. 3 / 6 · Two shapes

    Complements and partitions are different products.

    Polymarket versus Kalshi needs two APIs and two cash balances. Intra-Kalshi partitions can share a venue, but they are only valid when the logic is actually exhaustive.

  4. 4 / 6 · Truth stack

    WebSockets find. REST checks. APIs confirm.

    Subscriptions are for speed. Polling recovers missed updates. Preflight rechecks depth. Order and portfolio APIs are the record of what actually filled.

  5. 5 / 6 · Orphans

    FOK is not atomic across venues.

    You can complete the missing leg, unwind, hold, or stop. Those are recovery choices. None of them make two venues one transaction.

  6. 6 / 6 · Capital

    A positive edge can still be a bad use of cash.

    Venue balances, settlement lockup, and the next better trade all compete. The allocator was a constraint system, not a perfect portfolio solver.

Isto não é aconselhamento financeiro, jurídico, fiscal ou de investimento. Não é uma oferta nem uma recomendação para negociar. Os exemplos são ilustrativos. Os termos das plataformas, as restrições geográficas e a lei local prevalecem. Os avisos completos estão no artigo.

The first version of our sports arbitrage engine was an NCAA prototype. The idea fit in a few lines: find two prediction-market contracts on the same event, check if their combined cost is below the payout, and buy both.

That idea survived. Almost everything around it changed.

As we moved across Polymarket and Kalshi, then into linked World Cup markets, the hard part stopped being "is there a gap?" It became: are these actually complements, is the size real, can both orders fill, and can we reconstruct what the exchanges later report?

Finding the trade was the easy part. Completing, sizing, tracking, and reconciling both legs was the system.

Disclaimer. This article is an engineering discussion of software design. It is not financial, investment, trading, legal, or tax advice. It is not an offer, solicitation, or recommendation to buy, sell, or trade any contract, token, or event market. You are responsible for your own eligibility, local law, and venue terms.

Core Takeaway

The formula is clean. The path is not.

A detector can print a valid relationship on paper. The engineering job is to keep that meaning through execution, cash, and reconciliation.

The $1 payout is locked only after both correctly mapped legs fill in matching size. Until then, this is not risk-free.

A $0.03 example

Suppose two contracts really are complements:

  • A pays $1 if a team wins.
  • B pays $1 if that team does not win.

Exactly one should pay $1. If A's executable ask is $0.46 and B's is $0.50, the displayed cost is $0.96. Add $0.01 in fees and the all-in cost is $0.97. Edge: $0.03 per paired share.

edge = 1 − (price A + price B + fees)

That number only counts at executable depth. If A shows 80 shares at $0.46 and B only has 30 at $0.50, you have 30 paired shares, not 80. Midpoints do not matter. Best asks without size do not matter.

And even then, the $1 is not locked until both legs fill. One fill is a bet. Two fills is the hedge.

Five-step flow: detect a gap, check fees and depth, send two independent orders, either both fill or one becomes an orphan, then reconcile against exchange truth
The detector sees a gap. The system has to survive the next four steps.

Why sports?

Many sports markets are binary, or close to it, and they reprice when information hits. "Team wins" versus "team does not win" is a complement. Two contracts can also form a partition when they split the outcome space cleanly.

Not every sports market qualifies. Props, spreads, overtime rules, mismatched dates, and slightly different resolution text can kill an apparent pair. Same team names are not the same contract.

We ended up with two products, not one:

  1. Cross-platform complements. A Polymarket outcome plus the complementary Kalshi side.
  2. Intra-Kalshi partitions. Two Kalshi contracts that divide a real outcome, such as advance versus eliminated at a stage, but only once that stage is actually live.

Cross-platform trading means two APIs, two cash balances, and no atomic order. Intra-Kalshi can share a venue. The logic still has to be valid.

Matching contracts across venues

The first architecture was NCAA-shaped. That stopped scaling. We moved to generic MarketLeg and ArbPair objects: a leg is a venue, instrument, side, fees, and settlement; a pair is the relationship between two legs.

Polymarket uses token-specific 0 to 1 books. Kalshi uses YES/NO around a ticker, and sometimes an ask has to be derived from the other side. Team names show up as full names, abbreviations, codes, or aliases.

Ambiguity meant reject. A missed trade is cheap. Buying two correlated claims that are not exhaustive destroys the guarantee.

Speed versus truth

WebSockets are for speed. They are not the record.

A book update marked affected pairs dirty and triggered an incremental scan. That kept the hot path on markets that actually moved.

Connections drop. Sequence state gets weird. Updates get missed. So a timeout full scan recovered coverage, and slower loops refreshed outcomes, portfolios, and tournament facts.

Before an order, REST or a fresh preflight book rechecked that the size was still there. After submission, order and portfolio APIs confirmed fills and cash. The detector cache was never the final word.

  1. WebSockets find the move.
  2. Full scans catch what subscriptions miss.
  3. REST and preflight recheck depth.
  4. Order APIs confirm fills and cash.
  5. Reconciliation rebuilds what you actually own.

API capacity is part of strategy capacity. More subscriptions help coverage. They also hit rate limits. We treated 429s as a trading problem, not an infra footnote.

Hetzner, AWS, and why ping is not the whole path

We tested Hetzner and AWS placements with venue proximity in mind. That is operator experience, not an audited benchmark. We are not publishing latency numbers.

A machine that detects fast but cannot keep subscriptions alive, or cannot revalidate before sending, does not have a real speed advantage. Ping is one segment. Book age, decision time, ack, and terminal order state are the rest.

Two-leg execution and orphan risk

The economics are paired. The operations are split. There is no transaction that commits both Polymarket and Kalshi together. Fill-or-kill is per venue, not across venues.

Send one venue first, or send both at once. Each choice moves risk. First-leg favors the harder fill and leaves you exposed if the second vanishes. Concurrent cuts delay and can orphan either side.

An orphan is a filled leg without its hedge. Then the original calculation is no longer the decision. You complete, unwind, hold, or stop.

We reserved cash against the actual order instructions, not the pretty detection prices. Duplicate-entry guards stopped rapid scans from opening the same pair twice. Circuit breakers limited how failures could stack.

None of that made execution risk-free. It made failure visible and bounded.

Cash is scarce

A positive edge can still be a bad use of money.

Cash on Polymarket does not pay a Kalshi bill. A headline account total is less useful than venue buying power plus global headroom. Settlement duration matters too. The same $0.03 looks different if it frees in a day versus a month.

Early exits can recycle capital. They are also another two-leg problem. Exiting because a dashboard shows green can give up the locked terminal value, or create a new orphan.

The allocator ranked what it could see and approved against current state. It did not know tomorrow's opportunities. Capital allocation got better. It was not "solved."

World Cup: when the pair is not even valid yet

Kalshi had linked markets for reaching rounds, advancing, and being eliminated at a stage. Once a team is in that knockout stage, "reaches the next round" and "eliminated here" can split the space.

Before the team gets there, the same pair is not exhaustive. They can lose earlier. Price matching without tournament state is not enough.

We classified relationships as pending, active, or dead. Exchange results were authoritative. Scoreboard data and manual facts could activate known transitions sooner.

The bigger lesson: entry eligibility, tracking, and settlement cannot be the same set. A pair can be illegal for new entry and still be required to value inventory you already hold.

Stale books, simulation, and reconciliation

A stale or phantom book can show profitable depth that is already gone. Full scans restore coverage. They do not restore freshness. Detection data and execution truth had to stay separate.

A dry run that "fills" displayed size, then lets the next snapshot put it back, will trade the same fake depth forever. The simulation harness modeled thinning books and cache divergence. It was useful R&D. It was not a production safeguard on every branch.

The invariant that mattered: pruning an opportunity must never erase a held position from subscriptions, exits, or settlement.

What this is not

This is not a P&L report. It is not a fill-rate study. It is not a latency claim. Code shows what the system was designed to do. That is not an audited trading record.

The useful result was architectural. We started with a pricing equation and ended up asking:

  • Is the mapping correct?
  • Is the book current and executable?
  • Did both legs fill?
  • Where is cash actually available?
  • Is capital locked pending settlement?
  • Which positions still need tracking after entry rules change?
  • Can exchange history reconstruct the truth?

The gap detector stayed necessary. It became a small part of the whole. In a two-leg system, the edge exists on paper first. The work is keeping that meaning through execution without pretending the path is as certain as the formula.

Disclaimers

Not financial, legal, tax, or investment advice.

  • This post describes how we thought about a software system. It is not advice to trade, bet, hedge, or allocate capital. Nothing here is an offer or solicitation to buy or sell any contract, security, commodity, token, or other instrument.
  • Examples, prices, fees, and the $0.03 edge are illustrative only. They are not quotes, backtests, or a record of live results. We make no claim of profit, fill rate, latency, or future performance.
  • Diagrams in this post are architecture illustrations. They are not statements of positions, balances, or P&L.
  • Polymarket, Kalshi, and other named venues are independent. Naming them does not mean affiliation, sponsorship, or approval. Their APIs, geoblocks, KYC, and terms of use control access. Those terms can change.
  • Eligibility depends on who you are, where you are, and which entity would trade. Event contracts can be treated as derivatives, gambling, or something else depending on jurisdiction. You must confirm that any activity is lawful for you before you attempt it.
  • BitFashioned and the authors are not brokers, dealers, commodity trading advisors, investment advisers, or gambling operators. We do not accept client funds to trade these markets.
  • Do not treat this write-up as a production trading playbook. Copying the design without your own risk controls, venue permissions, and counsel is on you.
  • The material is provided as is, without warranty of any kind. To the fullest extent permitted by law, BitFashioned and the authors are not liable for losses, account closures, regulatory action, or other damages arising from use of this information.