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 requestsStep 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 AFLExpected 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.0Middles
# 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 marginReal-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.