Arbitrage Calculator Example - Python

Find cross-bookmaker margins by identifying odds discrepancies across every Australian book.

What you'll build

A script that:

  • Scans all bookmakers for odds discrepancies
  • Calculates arbitrage opportunities on two-sided markets
  • Shows optimal stake sizing
  • Calculates guaranteed profit percentage

Perfect for: Finding cross-book margins, comparing bookmaker odds, beating the vig

Prerequisites

  • Python 3.8+
  • SockOdds API key (free)
  • Basic Python

Complete code

Step 1: Setup project

mkdir arb-finder && cd arb-finder
python -m venv venv && source venv/bin/activate
pip install requests

Step 2: Create arb_calculator.py

# arb_calculator.py
import os, sys, requests

API_KEY = os.environ["SOCKODDS_KEY"]
BASE = "https://api.sockodds.com/v2"
LEAGUE = sys.argv[1] if len(sys.argv) > 1 else "AFL"
MIN_MARGIN = 0.5          # percent

def fetch_events(league):
    events, cursor = [], None
    while True:
        params = {"leagueID": league, "oddsAvailable": "true", "limit": 100, **({"cursor": cursor} if cursor else {})}
        r = requests.get(f"{BASE}/events", params=params, headers={"x-api-key": API_KEY}); r.raise_for_status()
        body = r.json(); events += body["data"]; cursor = body.get("nextCursor")
        if not cursor: break
    return events

def best_price(odd):
    """Best available decimal across books, and which book."""
    best = (None, None)
    for book, q in odd["byBookmaker"].items():
        if q.get("available") and q.get("decimal") and (best[0] is None or q["decimal"] > best[0]):
            best = (q["decimal"], book)
    return best

def find_arbs(event):
    odds = event["odds"]; seen = set(); out = []
    for odd_id, odd in odds.items():
        opp = odd.get("opposingOddID")
        if not opp or opp not in odds or odd_id in seen: continue
        seen.update({odd_id, opp})
        # a two-sided market only arbs when both sides are on the same line
        a_line, b_line = odd.get("bookOverUnder") or odd.get("bookSpread"), odds[opp].get("bookOverUnder") or odds[opp].get("bookSpread")
        (pa, ba), (pb, bb) = best_price(odd), best_price(odds[opp])
        if not pa or not pb: continue
        total = 1 / pa + 1 / pb
        if total < 1:
            margin = (1 / total - 1) * 100
            stake_a, stake_b = (1 / pa) / total, (1 / pb) / total
            out.append(dict(market=odd["marketName"], side_a=(ba, pa, stake_a), side_b=(bb, pb, stake_b), margin=margin, lines=(a_line, b_line)))
    return out

if __name__ == "__main__":
    events = fetch_events(LEAGUE)
    print(f"scanned {len(events)} {LEAGUE} events")
    for e in events:
        if e["info"].get("stale"): continue
        for arb in find_arbs(e):
            if arb["margin"] < MIN_MARGIN: continue
            (ba, pa, sa), (bb, pb, sb) = arb["side_a"], arb["side_b"]
            print(f"\n{e['eventID']}  {arb['market']}  margin {arb['margin']:.2f}%")
            print(f"  {ba:12} @ {pa:.2f}  stake {sa*100:5.1f}%   {e['links']['bookmakers'].get(ba, '')}")
            print(f"  {bb:12} @ {pb:.2f}  stake {sb*100:5.1f}%   {e['links']['bookmakers'].get(bb, '')}")

Step 3: Run it

SOCKODDS_KEY=so_live_… python arb_calculator.py AFL

Expected output

scanned 6 AFL events

afl_2026-09-05_collingwood_vs_geelong  Total Points  margin 1.23%
  sportsbet    @ 2.05  stake 49.4%   https://www.sportsbet.com.au/…
  betfairexchange @ 2.00  stake 50.6%   https://www.betfair.com.au/…

How it works

1. Fetch events with odds

Paged with cursor; every market and every book arrives in one object.

2. Use decimal directly

No American-to-decimal conversion — every book entry carries decimal.

3. Pair the sides

opposingOddID names the other side; each pair is scanned once.

4. Find the best price per side

Skip available: false.

5. Calculate arbitrage

Total implied probability below 1 → margin 1/total − 1.

6. Calculate optimal stakes

Proportional to (1/price)/total.

Enhancements

Minimum profit filter

MIN_MARGIN = 1.0

Middles

# with includeAltLines=true, compare line values across books for each side:
# Home -5.5 at book A and Away +6.5 at book B is a middle on a 6-point margin

Real-time monitoring

import time
while True:
    ...scan...
    time.sleep(150)

Troubleshooting

"No arbitrage opportunities found"

Normal most of the time. Lower MIN_MARGIN to see near-misses, or add includeAltLines=true.

Negative profit calculation

Both sides must be on the same line — the script prints the lines so you can check.

Account limitations

Books limit winners. This is between you and the book.

Next steps

Combine with other examples

Learn more

Need help?FAQ · Email · Contact