# Arbitrage Calculator Example - Python
URL: https://sockodds.com/docs/examples/arbitrage-calculator/

# Arbitrage Calculator Example - Python [#arbitrage-calculator-example---python]

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

## What you'll build [#what-youll-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 [#prerequisites]

- Python 3.8+
- SockOdds API key ([free](https://sockodds.com/signup/))
- Basic Python

## Complete code [#complete-code]

### Step 1: Setup project [#step-1-setup-project]

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

### Step 2: Create arb_calculator.py [#step-2-create-arb_calculatorpy]

```python
# 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 [#step-3-run-it]

```bash
SOCKODDS_KEY=so_live_… python arb_calculator.py AFL
```

## Expected output [#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 [#how-it-works]

### 1. Fetch events with odds [#1-fetch-events-with-odds]

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

### 2. Use decimal directly [#2-use-decimal-directly]

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

### 3. Pair the sides [#3-pair-the-sides]

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

### 4. Find the best price per side [#4-find-the-best-price-per-side]

Skip `available: false`.

### 5. Calculate arbitrage [#5-calculate-arbitrage]

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

### 6. Calculate optimal stakes [#6-calculate-optimal-stakes]

Proportional to `(1/price)/total`.

## Enhancements [#enhancements]

### Minimum profit filter [#minimum-profit-filter]

```python
MIN_MARGIN = 1.0
```

### Middles [#middles]

```python
# 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 [#real-time-monitoring]

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

## Troubleshooting [#troubleshooting]

### "No arbitrage opportunities found" [#no-arbitrage-opportunities-found]

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

### Negative profit calculation [#negative-profit-calculation]

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

### Account limitations [#account-limitations]

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

## Next steps [#next-steps]

### Combine with other examples [#combine-with-other-examples]

- [Live odds tracker](https://sockodds.com/docs/examples/live-odds-tracker/)
- [Odds comparison dashboard](https://sockodds.com/docs/examples/odds-comparison-dashboard/)
- [Player props analyzer](https://sockodds.com/docs/examples/player-props-analyzer/)
- [Parlay builder](https://sockodds.com/docs/examples/parlay-builder/)

### Learn more [#learn-more]

- [The oddID grammar](https://sockodds.com/docs/data-types/odds/)
- [Best practices](https://sockodds.com/docs/info/best-practices/)
- [API reference](https://sockodds.com/docs/reference/)

> Need help?[FAQ](https://sockodds.com/docs/faq/) · [Email](mailto:api@sockodds.com) · [Contact](https://sockodds.com/contact-us/)
