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

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

Find guaranteed profit opportunities by identifying odds discrepancies across bookmakers.

What You'll Build [#what-youll-build]

A Python script that:

* Scans all bookmakers for odds discrepancies
* Calculates arbitrage opportunities
* Shows optimal bet sizing
* Calculates guaranteed profit percentage

**Perfect for:** Finding risk-free profit opportunities, comparing bookmaker odds, beating the vig

What is Arbitrage? [#what-is-arbitrage]

**Arbitrage** (or "arbing") is betting on all possible outcomes across different bookmakers to guarantee profit regardless of the result.

**Example:**

* **Book A:** Lakers -5.5 @ +105
* **Book B:** Celtics +6.0 @ -105

By betting both sides across different books, you can sometimes lock in profit.

Prerequisites [#prerequisites]

* Python 3.8+
* SportsGameOdds API key ([Get one free](/pricing))
* Basic Python knowledge

Complete Code [#complete-code]

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

```bash
mkdir arb-calculator && cd arb-calculator
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
pip install requests
```

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

```python
# arb_calculator.py

from collections import defaultdict

API_KEY = os.environ.get('SPORTSGAMEODDS_KEY')
API_BASE = 'https://api.sportsgameodds.com/v2'

def fetch_events(league='NBA'):
    """Fetch upcoming events with odds"""
    try:
        response = requests.get(
            f'{API_BASE}/events',
            params={
                'leagueID': league,
                'finalized': 'false',
                'oddsAvailable': 'true',
                'limit': 100
            },
            headers={'x-api-key': API_KEY}
        )
        response.raise_for_status()
        return response.json()['data']
    except requests.exceptions.RequestException as e:
        print(f'Error fetching events: {e}')
        return []

def american_to_decimal(american_odds):
    """Convert American odds to decimal odds"""
    if american_odds > 0:
        return (american_odds / 100) + 1
    else:
        return (100 / abs(american_odds)) + 1

def calculate_arbitrage(odds_list):
    """
    Calculate if arbitrage exists and profit percentage

    odds_list: List of decimal odds for all outcomes
    Returns: (has_arb, profit_percentage, stake_distribution)
    """
    # Calculate implied probability sum
    implied_prob_sum = sum(1 / odd for odd in odds_list)

    # Arbitrage exists when sum < 1 (overround is negative)
    has_arb = implied_prob_sum < 1

    if not has_arb:
        return False, 0, []

    # Calculate profit percentage
    profit_pct = ((1 / implied_prob_sum) - 1) * 100

    # Calculate optimal stake distribution (for $100 total)
    total_stake = 100
    stakes = [(total_stake / odd) / implied_prob_sum for odd in odds_list]

    return True, profit_pct, stakes

def find_arbitrage_opportunities(events):
    """Find arbitrage opportunities in events"""
    opportunities = []

    for event in events:
        # Get team names - API returns names in a nested structure
        away_name = event['teams']['away']['names']['long']
        home_name = event['teams']['home']['names']['long']
        matchup = f"{away_name} @ {home_name}"

        # Group odds by market type and side
        # Structure: markets[betType][periodID][side] = list of {bookmaker, price, line, decimal}
        markets = defaultdict(lambda: defaultdict(lambda: defaultdict(list)))

        for odd_id, odd in (event.get('odds') or {}).items():
            bet_type = odd['betTypeID']
            side = odd['sideID']
            period_id = odd.get('periodID', 'game')

            # Only process full game odds for arbitrage
            if period_id != 'game':
                continue

            # Iterate through each bookmaker's odds
            for bookmaker_id, bookmaker_data in (odd.get('byBookmaker') or {}).items():
                # Skip if not available
                if not bookmaker_data.get('available', True):
                    continue

                # Get odds value (API returns as string)
                odds_str = bookmaker_data.get('odds')
                if not odds_str:
                    continue

                try:
                    price = int(odds_str)
                except (ValueError, TypeError):
                    continue

                # Get line value based on bet type
                if bet_type == 'sp':
                    line = bookmaker_data.get('spread')
                elif bet_type == 'ou':
                    line = bookmaker_data.get('overUnder')
                else:
                    line = None

                decimal_odds = american_to_decimal(price)

                markets[bet_type][period_id][side].append({
                    'bookmaker': bookmaker_id,
                    'american': price,
                    'decimal': decimal_odds,
                    'line': line
                })

        # Check each market for arbitrage
        for bet_type, periods in markets.items():
            for period_id, sides in periods.items():
                # For spread markets
                if bet_type == 'sp':
                    if sides.get('home') and sides.get('away'):
                        # Find best odds for each side
                        best_home = max(sides['home'], key=lambda x: x['decimal'])
                        best_away = max(sides['away'], key=lambda x: x['decimal'])

                        # Calculate arbitrage
                        has_arb, profit_pct, stakes = calculate_arbitrage([
                            best_home['decimal'],
                            best_away['decimal']
                        ])

                        if has_arb:
                            opportunities.append({
                                'matchup': matchup,
                                'market': 'spread',
                                'profit_pct': profit_pct,
                                'legs': [
                                    {
                                        'side': 'home',
                                        'bookmaker': best_home['bookmaker'],
                                        'odds': best_home['american'],
                                        'line': best_home['line'],
                                        'stake_pct': stakes[0]
                                    },
                                    {
                                        'side': 'away',
                                        'bookmaker': best_away['bookmaker'],
                                        'odds': best_away['american'],
                                        'line': best_away['line'],
                                        'stake_pct': stakes[1]
                                    }
                                ]
                            })

                # For total (over/under) markets
                elif bet_type == 'ou':
                    if sides.get('over') and sides.get('under'):
                        best_over = max(sides['over'], key=lambda x: x['decimal'])
                        best_under = max(sides['under'], key=lambda x: x['decimal'])

                        has_arb, profit_pct, stakes = calculate_arbitrage([
                            best_over['decimal'],
                            best_under['decimal']
                        ])

                        if has_arb:
                            opportunities.append({
                                'matchup': matchup,
                                'market': 'total',
                                'profit_pct': profit_pct,
                                'legs': [
                                    {
                                        'side': 'over',
                                        'bookmaker': best_over['bookmaker'],
                                        'odds': best_over['american'],
                                        'line': best_over['line'],
                                        'stake_pct': stakes[0]
                                    },
                                    {
                                        'side': 'under',
                                        'bookmaker': best_under['bookmaker'],
                                        'odds': best_under['american'],
                                        'line': best_under['line'],
                                        'stake_pct': stakes[1]
                                    }
                                ]
                            })

                # For moneyline markets
                elif bet_type == 'ml':
                    if sides.get('home') and sides.get('away'):
                        best_home = max(sides['home'], key=lambda x: x['decimal'])
                        best_away = max(sides['away'], key=lambda x: x['decimal'])

                        has_arb, profit_pct, stakes = calculate_arbitrage([
                            best_home['decimal'],
                            best_away['decimal']
                        ])

                        if has_arb:
                            opportunities.append({
                                'matchup': matchup,
                                'market': 'moneyline',
                                'profit_pct': profit_pct,
                                'legs': [
                                    {
                                        'side': 'home',
                                        'bookmaker': best_home['bookmaker'],
                                        'odds': best_home['american'],
                                        'stake_pct': stakes[0]
                                    },
                                    {
                                        'side': 'away',
                                        'bookmaker': best_away['bookmaker'],
                                        'odds': best_away['american'],
                                        'stake_pct': stakes[1]
                                    }
                                ]
                            })

    return opportunities

def display_opportunities(opportunities, total_stake=100):
    """Display arbitrage opportunities"""
    if not opportunities:
        print('No arbitrage opportunities found')
        print('\nNote: True arbitrage is rare. The market is usually efficient.')
        return

    # Sort by profit percentage (highest first)
    opportunities.sort(key=lambda x: x['profit_pct'], reverse=True)

    print(f'\nFound {len(opportunities)} ARBITRAGE OPPORTUNIT{"Y" if len(opportunities) == 1 else "IES"}!\n')

    for i, opp in enumerate(opportunities, 1):
        print('=' * 60)
        print(f'#{i} - {opp["matchup"]}')
        print(f'Market: {opp["market"].upper()}')
        print(f'Guaranteed Profit: {opp["profit_pct"]:.2f}%')
        print('-' * 60)

        for leg in opp['legs']:
            stake = total_stake * (leg['stake_pct'] / 100)
            payout = stake * american_to_decimal(leg['odds'])

            line_str = f" {leg['line']}" if leg.get('line') else ""

            print(f"  {leg['side'].upper()}{line_str} @ {leg['odds']:+d} ({leg['bookmaker']})")
            print(f"  Bet: ${stake:.2f} -> Payout: ${payout:.2f}")
            print()

        profit = total_stake * (opp['profit_pct'] / 100)
        print(f"Total Stake: ${total_stake:.2f}")
        print(f"Guaranteed Profit: ${profit:.2f}")
        print('=' * 60)
        print()

def main():
    print('Scanning for arbitrage opportunities...\n')

    # Scan multiple leagues
    leagues = ['NBA', 'NFL', 'NHL']
    all_opportunities = []

    for league in leagues:
        print(f'Fetching {league} events...')
        events = fetch_events(league)

        if events:
            print(f'Found {len(events)} {league} events')
            opportunities = find_arbitrage_opportunities(events)
            all_opportunities.extend(opportunities)
        else:
            print(f'No {league} events found')

        print()

    display_opportunities(all_opportunities, total_stake=100)

if __name__ == '__main__':
    main()
```

Step 3: Run It [#step-3-run-it]

```bash
export SPORTSGAMEODDS_KEY=your_api_key_here
python arb_calculator.py
```

Expected Output [#expected-output]

```
Scanning for arbitrage opportunities...

Fetching NBA events...
Found 8 NBA events

Fetching NFL events...
Found 14 NFL events

Fetching NHL events...
Found 6 NHL events

Found 2 ARBITRAGE OPPORTUNITIES!

============================================================
#1 - Boston Celtics @ Los Angeles Lakers
Market: SPREAD
Guaranteed Profit: 2.34%
------------------------------------------------------------
  HOME -5.5 @ +105 (fanduel)
  Bet: $48.78 -> Payout: $100.00

  AWAY +6.0 @ +100 (draftkings)
  Bet: $51.22 -> Payout: $102.44

Total Stake: $100.00
Guaranteed Profit: $2.34
============================================================

============================================================
#2 - Tampa Bay Lightning @ Florida Panthers
Market: TOTAL
Guaranteed Profit: 1.15%
------------------------------------------------------------
  OVER 6.0 @ -105 (betmgm)
  Bet: $51.22 -> Payout: $100.00

  UNDER 6.5 @ +110 (caesars)
  Bet: $48.78 -> Payout: $102.44

Total Stake: $100.00
Guaranteed Profit: $1.15
============================================================
```

How It Works [#how-it-works]

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

```python
response = requests.get(
    f'{API_BASE}/events',
    params={
        'leagueID': league,
        'finalized': 'false',  # Upcoming games only
        'oddsAvailable': 'true'  # Must have odds
    }
)
```

2. Convert American to Decimal Odds [#2-convert-american-to-decimal-odds]

```python
def american_to_decimal(american_odds):
    if american_odds > 0:
        return (american_odds / 100) + 1  # e.g., +150 -> 2.50
    else:
        return (100 / abs(american_odds)) + 1  # e.g., -110 -> 1.909
```

**Why decimal?** Makes arbitrage math easier.

3. Process Odds by Bookmaker [#3-process-odds-by-bookmaker]

The API returns odds with a nested `byBookmaker` structure:

```python
for odd_id, odd in event.get('odds').items():
    bet_type = odd['betTypeID']  # 'sp', 'ml', 'ou'
    side = odd['sideID']         # 'home', 'away', 'over', 'under'

    # Each odd contains prices from multiple bookmakers
    for bookmaker_id, bookmaker_data in odd.get('byBookmaker', {}).items():
        price = int(bookmaker_data['odds'])
        decimal_odds = american_to_decimal(price)
```

4. Find Best Odds for Each Side [#4-find-best-odds-for-each-side]

```python
best_home = max(sides['home'], key=lambda x: x['decimal'])
best_away = max(sides['away'], key=lambda x: x['decimal'])
```

We want the **highest odds** (best payout) for each outcome.

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

```python
implied_prob_sum = sum(1 / odd for odd in [home_decimal, away_decimal])

if implied_prob_sum < 1:
    # Arbitrage exists!
    profit_pct = ((1 / implied_prob_sum) - 1) * 100
```

**Math explained:**

* Normal market: implied probabilities sum to >100% (bookmaker edge)
* Arbitrage: implied probabilities sum to \<100% (your edge)

**Example:**

* Home @ 2.10 (decimal) = 47.62% implied probability
* Away @ 2.10 (decimal) = 47.62% implied probability
* Sum = 95.24% \< 100% -> **4.76% arbitrage!**

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

```python
stakes = [(100 / odd) / implied_prob_sum for odd in odds_list]
```

This distributes your total stake to guarantee equal profit on all outcomes.

Real-World Example [#real-world-example]

**Scenario:** Lakers vs Celtics

| Bookmaker  | Market       | Odds | Decimal | Implied Prob |
| ---------- | ------------ | ---- | ------- | ------------ |
| FanDuel    | Lakers -5.5  | +105 | 2.05    | 48.78%       |
| DraftKings | Celtics +6.0 | +100 | 2.00    | 50.00%       |

**Sum:** 48.78% + 50.00% = **98.78%**

**Profit:** (1 / 0.9878) - 1 = **1.23%**

**Stakes (for $100 total):**

* Lakers: $100 x (48.78 / 98.78) = **$49.38**
* Celtics: $100 x (50.00 / 98.78) = **$50.62**

**Outcome 1:** Lakers cover -5.5

* Win $49.38 x 2.05 = $101.23
* Profit: $101.23 - $100 = **$1.23**

**Outcome 2:** Celtics cover +6.0

* Win $50.62 x 2.00 = $101.24
* Profit: $101.24 - $100 = **$1.24**

**Guaranteed profit either way!**

Enhancements [#enhancements]

Add Minimum Profit Filter [#add-minimum-profit-filter]

```python
MIN_PROFIT_PCT = 1.0  # Only show 1%+ opportunities

opportunities = [
    opp for opp in opportunities
    if opp['profit_pct'] >= MIN_PROFIT_PCT
]
```

Include Middle Opportunities [#include-middle-opportunities]

A "middle" is when you can win both bets:

```python
# Lakers -5.5 @ Book A
# Celtics +6.0 @ Book B
# If Lakers win by exactly 6, you win BOTH bets!

def find_middles(sides):
    for home_odd in sides['home']:
        for away_odd in sides['away']:
            home_line = float(home_odd['line'] or 0)
            away_line = float(away_odd['line'] or 0)
            gap = abs(home_line) - abs(away_line)
            if gap >= 0.5:  # Middle exists
                yield {
                    'gap': gap,
                    'home': home_odd,
                    'away': away_odd
                }
```

Real-Time Monitoring [#real-time-monitoring]

```python

def monitor_arbitrage(interval=60):
    """Check for arbitrage every minute"""
    while True:
        opportunities = find_all_arbitrage()
        display_opportunities(opportunities)
        time.sleep(interval)

monitor_arbitrage()
```

Send Alerts [#send-alerts]

```python

def send_telegram_alert(opportunity):
    """Send Telegram notification"""
    bot_token = os.environ.get('TELEGRAM_BOT_TOKEN')
    chat_id = os.environ.get('TELEGRAM_CHAT_ID')

    message = f"Arbitrage Alert!\n"
    message += f"{opportunity['matchup']}\n"
    message += f"Profit: {opportunity['profit_pct']:.2f}%"

    requests.post(
        f'https://api.telegram.org/bot{bot_token}/sendMessage',
        json={'chat_id': chat_id, 'text': message}
    )
```

Important Considerations [#important-considerations]

Account Limitations [#account-limitations]

**Warning:** Bookmakers may limit or ban accounts that consistently arbitrage.

**Tips to avoid detection:**

* Don't bet only arbitrage opportunities
* Vary bet sizes
* Use round numbers ($50 not $49.38)
* Space out bets across time
* Use different devices/IPs

Execution Speed [#execution-speed]

Arbitrage opportunities disappear quickly (seconds to minutes).

**Solutions:**

* Use our [Real-time Streaming](/docs/guides/realtime-streaming-api) (AllStar plan)
* Automate bet placement (requires bookmaker APIs)
* Set up price alerts

Line Differences [#line-differences]

In our example:

* Book A: -5.5
* Book B: +6.0

The 0.5 point difference creates the arbitrage opportunity (a "middle").

Commission & Fees [#commission--fees]

Remember to account for:

* Withdrawal fees
* Currency conversion
* Deposit bonuses with rollover requirements

Troubleshooting [#troubleshooting]

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

**Causes:**

1. **Efficient markets** - Bookmakers adjust quickly
2. **Not enough bookmakers** - Need odds from 5+ books
3. **Wrong timing** - Arbitrage appears closer to game time

**Solutions:**

* Scan more leagues
* Check closer to event start
* Look for less popular markets

Negative profit calculation [#negative-profit-calculation]

**Cause:** Bug in odds conversion or stake calculation.

**Solution:** Add validation:

```python
def validate_arbitrage(opportunity):
    """Verify calculations are correct"""
    total_stake = sum(leg['stake_pct'] for leg in opportunity['legs'])
    assert abs(total_stake - 100) < 0.01, "Stakes don't sum to 100%"

    # Verify profit on all outcomes
    for leg in opportunity['legs']:
        payout = leg['stake_pct'] * american_to_decimal(leg['odds'])
        profit = payout - 100
        assert profit > 0, f"Negative profit on {leg['side']}"
```