Building an arbitrage bot requires reliable, fast access to pre-match football odds across multiple bookmakers. The core challenge is programmatically identifying pricing discrepancies before kickoff and acting on them. This tutorial explains how to build an arbitrage bot, focusing on data acquisition from a UK bookmaker odds API and the logic for detecting profitable opportunities.
An arbitrage bot automates the process of finding "sure bets" by comparing odds from different bookmakers for the same event. When the combined implied probability of all outcomes for an event is less than 100% across different bookmakers, an arbitrage opportunity exists. This allows a developer to place bets on all outcomes and guarantee a profit, regardless of the match result. The key is getting accurate, up-to-date pre-match football odds JSON without resorting to unreliable web scraping.
Prerequisites
Before you start building your arbitrage bot, ensure you have the following:
- Python 3.8+: Our code examples use Python.
requestslibrary: For making HTTP requests to the API.- UK Odds API Key: You'll need an API key from ukoddsapi.com. The Arbitrage API is included with the Business tier.
- Basic understanding of arbitrage betting: Familiarity with implied probability and how it relates to odds.
- Development environment: A code editor and terminal.
Step 1: Understanding Arbitrage Opportunities
An arbitrage opportunity, often called a "sure bet," arises when different bookmakers offer odds that allow you to bet on all possible outcomes of an event and guarantee a profit. This is possible when the implied probabilities of all outcomes, calculated from the odds, sum to less than 100%.
To calculate the implied probability for a single outcome, use the formula: Implied Probability = 1 / Decimal Odds.
For an arbitrage opportunity to exist, the sum of the implied probabilities for all outcomes across different bookmakers must be less than 1.0 (or 100%).
For example, in a football match with three outcomes (Home Win, Draw, Away Win), you'd look for the best odds for each outcome from different bookmakers.
Let's say:
- Home Win: Bookmaker A offers 2.20
- Draw: Bookmaker B offers 3.80
- Away Win: Bookmaker C offers 4.50
The implied probabilities would be:
- Home Win: 1 / 2.20 = 0.4545 (45.45%)
- Draw: 1 / 3.80 = 0.2632 (26.32%)
- Away Win: 1 / 4.50 = 0.2222 (22.22%)
Sum of implied probabilities = 0.4545 + 0.2632 + 0.2222 = 0.9399 (93.99%).
Since 0.9399 < 1.0, an arbitrage opportunity exists. The profit margin is (1 - 0.9399) * 100% = 6.01%.
Your bot needs to perform these calculations automatically, finding the best odds for each outcome across all available bookmakers for every pre-match football fixture.

Step 2: Fetching Pre-Match Football Odds
The foundation of any arbitrage bot is reliable data. Scraping bookmaker websites is fragile and often leads to IP bans or stale data. A dedicated odds API without scraping is the only way to get consistent, structured pre-match football odds JSON. ukoddsapi.com provides a robust API specifically for UK bookmakers.
First, you need to get a list of upcoming football events. You can filter these by date and ensure they have odds available.
Here's how to fetch events using Python:
import os
import requests
from datetime import datetime, timedelta
API_KEY = os.environ.get("UKODDSAPI_KEY", "YOUR_API_KEY") # Replace with your actual API key or set as env var
BASE_URL = "https://api.ukoddsapi.com"
HEADERS = {"X-Api-Key": API_KEY}
def get_upcoming_events(date_str: str):
"""Fetches upcoming football events for a specific date."""
endpoint = f"{BASE_URL}/v1/football/events"
params = {
"schedule_date": date_str,
"has_odds": "true",
"per_page": "100" # Fetch up to 100 events
}
try:
response = requests.get(endpoint, headers=HEADERS, params=params, timeout=30)
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
return response.json()
except requests.exceptions.RequestException as e:
print(f"Error fetching events for {date_str}: {e}")
return None
# Example usage: Get events for tomorrow
tomorrow = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d")
events_data = get_upcoming_events(tomorrow)
if events_data and events_data.get("events"):
print(f"Found {len(events_data['events'])} events for {tomorrow}.")
for event in events_data["events"][:3]: # Print first 3 events for brevity
print(f" Event ID: {event['event_id']}, Match: {event['home_team']} vs {event['away_team']}")
else:
print(f"No events found for {tomorrow} or an error occurred.")
This Python script fetches a list of football events scheduled for tomorrow. The schedule_date parameter ensures you get fixtures for a specific day, and has_odds=true filters for events where odds are already available. The event_id is crucial for fetching detailed odds in the next step.
Once you have an event_id, you can fetch the detailed odds for that specific fixture across all markets and bookmakers. For arbitrage, the best odds endpoint is particularly useful, as it aggregates the best odds for each selection.
def get_best_odds_for_event(event_id: str):
"""Fetches the best odds for a given event ID."""
endpoint = f"{BASE_URL}/v1/football/events/{event_id}/odds/best"
params = {
"odds_format": "decimal",
"package": "core" # Use 'full' for advanced markets on higher tiers
}
try:
response = requests.get(endpoint, headers=HEADERS, params=params, timeout=60)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Error fetching best odds for event {event_id}: {e}")
return None
# Use an event_id from the previous step
if events_data and events_data.get("events"):
first_event_id = events_data["events"][0]["event_id"]
best_odds_data = get_best_odds_for_event(first_event_id)
if best_odds_data and best_odds_data.get("markets"):
print(f"\nBest odds for {best_odds_data['event_title']} (Event ID: {first_event_id}):")
for market in best_odds_data["markets"][:2]: # Print first 2 markets for brevity
print(f" Market: {market['market_name']}")
for selection in market["selections"]:
print(f" - {selection['selection_name']}: {selection['odds']} (Bookmaker: {selection['bookmaker_code']})")
else:
print(f"No best odds found for event {first_event_id} or an error occurred.")
This snippet retrieves the best available odds for each selection within an event's markets. The odds_format=decimal ensures consistent parsing. The response includes the bookmaker_code for each best odd, which is critical for placing the actual bets later.
Example best_odds_data JSON (truncated for clarity):
{
"schema_version": "1.0",
"event_id": "EV0012345",
"event_title": "Manchester Utd vs Arsenal",
"kickoff_utc": "2026-04-29T19:00:00Z",
"markets": [
{
"market_id": "MA001",
"market_name": "Match Winner",
"market_group": "main",
"selection_count": 3,
"selections": [
{
"selection_name": "Home",
"line": null,
"odds": 2.25,
"bookmaker_code": "UO003",
"status": "active"
},
{
"selection_name": "Draw",
"line": null,
"odds": 3.60,
"bookmaker_code": "UO015",
"status": "active"
},
{
"selection_name": "Away",
"line": null,
"odds": 4.10,
"bookmaker_code": "UO027",
"status": "active"
}
]
},
{
"market_id": "MA002",
"market_name": "Both Teams to Score",
"market_group": "goals",
"selection_count": 2,
"selections": [
{
"selection_name": "Yes",
"line": null,
"odds": 1.80,
"bookmaker_code": "UO005",
"status": "active"
},
{
"selection_name": "No",
"line": null,
"odds": 2.05,
"bookmaker_code": "UO010",
"status": "active"
}
]
}
],
"note": "Response truncated for example."
}
This response provides the highest odds for each selection within the "Match Winner" market, along with the bookmaker_code that offers that price. This is exactly the data you need to calculate arbitrage.
Step 3: Processing Odds and Detecting Arbs
With the best odds for each selection, the next step is to implement the arbitrage detection logic. This involves iterating through the markets (starting with simple ones like "Match Winner") and calculating the implied probability sum.
Here's Python code to process the best_odds_data and identify arbitrage opportunities:
def find_arbitrage_in_match_winner(best_odds_data: dict, min_profit_percent: float = 0.5):
"""
Analyzes 'Match Winner' market for arbitrage opportunities.
Returns a dictionary with arbitrage details if found, otherwise None.
"""
if not best_odds_data or not best_odds_data.get("markets"):
return None
event_title = best_odds_data.get("event_title", "Unknown Event")
event_id = best_odds_data.get("event_id", "Unknown ID")
for market in best_odds_data["markets"]:
if market["market_name"] == "Match Winner" and market["selection_count"] == 3:
selections = market["selections"]
# Ensure we have odds for all three outcomes (Home, Draw, Away)
home_odds = next((s['odds'] for s in selections if s['selection_name'] == 'Home'), None)
draw_odds = next((s['odds'] for s in selections if s['selection_name'] == 'Draw'), None)
away_odds = next((s['odds'] for s in selections if s['selection_name'] == 'Away'), None)
if None in [home_odds, draw_odds, away_odds]:
continue # Skip if any outcome is missing odds
# Calculate implied probabilities
implied_prob_home = 1 / home_odds
implied_prob_draw = 1 / draw_odds
implied_prob_away = 1 / away_odds
total_implied_prob = implied_prob_home + implied_prob_draw + implied_prob_away
if total_implied_prob < 1.0:
profit_margin = (1 - total_implied_prob) * 100
if profit_margin >= min_profit_percent:
print(f"\n--- Arbitrage Opportunity Found! ---")
print(f"Event: {event_title} (ID: {event_id})")
print(f"Total Implied Probability: {total_implied_prob:.4f}")
print(f"Profit Margin: {profit_margin:.2f}%")
arb_details = {
"event_id": event_id,
"event_title": event_title,
"profit_margin": profit_margin,
"bets": []
}
# Calculate stakes for a total stake of £100
total_stake = 100.0
for selection in selections:
outcome_odds = selection['odds']
outcome_prob = 1 / outcome_odds
stake = (outcome_prob / total_implied_prob) * total_stake
payout = stake * outcome_odds
print(f" Bet {selection['selection_name']} @ {outcome_odds} with £{stake:.2f} (Bookmaker: {selection['bookmaker_code']})")
arb_details["bets"].append({
"selection": selection['selection_name'],
"odds": outcome_odds,
"bookmaker_code": selection['bookmaker_code'],
"stake": stake,
"payout": payout
})
print(f" Total Stake: £{sum(b['stake'] for b in arb_details['bets']):.2f}")
print(f" Guaranteed Payout: £{payout:.2f} (from any outcome)") # Payout should be consistent
return arb_details
return None
# Example usage with previously fetched best_odds_data
if best_odds_data:
arbitrage_found = find_arbitrage_in_match_winner(best_odds_data, min_profit_percent=0.5)
if not arbitrage_found:
print(f"\nNo arbitrage found for {best_odds_data.get('event_title')} in Match Winner market.")
This function specifically targets the "Match Winner" market, which is the most common for arbitrage. It calculates the total implied probability and, if it's below 1.0, determines the profit margin. It then calculates the proportional stake for each outcome to ensure an equal payout regardless of the result, assuming a total stake (e.g., £100). This is a crucial step in any how to build an arbitrage bot explained guide.

Step 4: Automating the Bot Logic
To make your arbitrage bot truly useful, you need to automate the process of fetching odds, detecting opportunities, and potentially placing bets. This involves continuous polling, managing state, and handling rate limits.
Polling for Updated Pre-Match Odds
Pre-match odds can change frequently as kickoff approaches. Your bot needs to periodically check for updates. However, constantly hammering the API will lead to rate limits. The UK Odds API Business tier offers 20,000 requests/hour, which allows for frequent polling across many events.
import time
def run_arbitrage_scanner(interval_seconds: int = 60, lookahead_days: int = 1):
"""
Continuously scans for arbitrage opportunities.
"""
print(f"Starting arbitrage scanner. Polling every {interval_seconds} seconds.")
while True:
try:
target_date = (datetime.now() + timedelta(days=lookahead_days)).strftime("%Y-%m-%d")
print(f"\nScanning events for {target_date}...")
events_data = get_upcoming_events(target_date)
if events_data and events_data.get("events"):
print(f"Processing {len(events_data['events'])} events.")
for event in events_data["events"]:
event_id = event["event_id"]
best_odds_data = get_best_odds_for_event(event_id)
if best_odds_data:
find_arbitrage_in_match_winner(best_odds_data)
# Add a small delay between event odds fetches to be polite and manage rate limits
time.sleep(0.5)
else:
print(f"No events to process for {target_date}.")
except Exception as e:
print(f"An unexpected error occurred in scanner loop: {e}")
print(f"Waiting {interval_seconds} seconds before next scan...")
time.sleep(interval_seconds)
# To run the scanner (uncomment to execute)
# run_arbitrage_scanner(interval_seconds=300, lookahead_days=1) # Scan every 5 minutes for tomorrow's events
This run_arbitrage_scanner function creates a continuous loop. It fetches events for a target date (e.g., tomorrow), then iterates through each event to get its best odds and check for arbitrage. A time.sleep() call is crucial to avoid hitting rate limits. The interval_seconds parameter controls how often the entire scan runs.
Placing Bets (Conceptual)
The UK Odds API provides the data to find arbitrage opportunities, but it does not place bets. To complete your bot, you would need to integrate with individual bookmakers' APIs (if they offer them) or use a service that provides programmatic betting. This is the most complex part of a full how to build an arbitrage bot integration.
When an arbitrage is detected, your bot would:
- Verify odds: Quickly re-fetch the odds to ensure they haven't changed.
- Calculate stakes: Determine the precise stake for each outcome based on your total available bankroll.
- Place bets: Send requests to the respective bookmakers' APIs to place the calculated stakes on each outcome.
- Log: Record the bets placed, profit, and any errors.
This step involves significant risk management and careful handling of bookmaker-specific APIs, which often have their own rate limits and terms of service.
Common Mistakes When Building an Arbitrage Bot
Building an arbitrage bot is complex. Avoid these common pitfalls:
- Ignoring API Rate Limits: Hitting rate limits will get your IP blocked or API key suspended. Implement proper delays and backoff strategies.
- Using Stale Odds: Odds change rapidly. An arbitrage opportunity can disappear in seconds. Fetch data frequently but within limits.
- Insufficient Bookmaker Coverage: Without a wide range of UK bookmakers, you'll miss many opportunities. Ensure your data source covers the key players.
- Neglecting Bookmaker Commissions/Fees: Some bookmakers (especially exchanges like Betfair) charge commission on winnings, which eats into your profit. Factor this into calculations.
- Miscalculating Stakes: Incorrect stake distribution can turn a sure bet into a loss. Double-check your arbitrage calculation logic.
- Not Handling Market Suspensions: Bookmakers can suspend markets or change odds instantly. Your bot needs to handle these changes gracefully.
- Overlooking Bet Limits: Bookmakers impose maximum stake limits, especially on arbitrage bets. Your bot should be aware of these.
- Relying on Web Scraping: It's unreliable, prone to breaking, and often violates terms of service. Use a dedicated odds API.
Options and Alternatives for Odds Data
When you want to build an arbitrage bot, getting reliable data is paramount. Here's a comparison of common approaches:
| Feature | UK Odds API | Web Scraping | Other Generic Odds APIs |
|---|---|---|---|
| Data Source | Aggregated, normalized from UK bookmakers | Direct from bookmaker websites (DIY) | Aggregated from various global sources |
| Reliability | High (managed service, stable endpoints) | Low (prone to breakage, IP bans) | Varies (may lack UK-specific depth) |
| Maintenance | Low (API provider handles changes) | High (constant updates needed for scrapers) | Low to Medium (depends on provider) |
| Coverage Focus | UK pre-match football (27+ bookmakers) | Whatever you build scrapers for | Often global, may miss UK niche bookmakers |
| Rate Limits | Clearly defined, tiered access (e.g., 20k/hr) | Undefined, often aggressive blocking | Varies by provider and plan |
| Data Format | Clean, consistent JSON | Raw HTML, requires parsing | Varies, usually JSON |
| Cost | Subscription-based (free tier available) | Time (development, maintenance) + proxies | Subscription-based |
| Arbitrage API | Dedicated /v1/football/arbitrage endpoint (Business tier) |
Requires custom logic across all scraped data | May or may not have dedicated arb endpoints |
The choice depends on your technical resources, budget, and the specific bookmaker coverage you need. For developers focused on UK football, a managed API like ukoddsapi.com offers a significant advantage over building and maintaining custom scrapers or relying on generic feeds that might lack depth for the UK market.
FAQ
How quickly do pre-match odds update via the API?
Pre-match odds are updated regularly by ukoddsapi.com as bookmakers adjust their prices. While not "in-play" (during a match), these refreshed snapshots provide current prices before kickoff, suitable for arbitrage detection.
What football markets are typically available for arbitrage detection?
The "Match Winner" (1X2) market is the most common for arbitrage due to its simplicity. Higher tiers of the UK Odds API also offer advanced markets (e.g., handicaps, goals) which can also yield arbitrage opportunities, though they are more complex to process.
How many bookmakers do I need for an effective arbitrage bot?
More bookmakers increase the likelihood of finding arbitrage opportunities. ukoddsapi.com offers coverage of up to 27 UK bookmakers on its Pro and Business tiers, providing a broad data set for detection.
What are the main challenges in deploying an arbitrage bot?
Beyond data acquisition, key challenges include managing bookmaker accounts, handling varying bet placement APIs, dealing with rapid odds changes, ensuring sufficient liquidity, and adhering to bookmaker terms of service.
Can I test the arbitrage detection logic without a paid API plan?
You can develop and test the core arbitrage logic using sample JSON data or by manually fetching odds for a few events with the Free or Starter plan. However, the dedicated /v1/football/arbitrage endpoint is available on the Business tier, and comprehensive bookmaker coverage requires higher plans.
Conclusion
Building an arbitrage bot is a challenging but rewarding project for developers. The most critical component is access to reliable, structured pre-match football odds JSON from a wide range of UK bookmaker odds API sources. By leveraging a dedicated API like ukoddsapi.com, you can bypass the complexities and unreliability of web scraping, allowing you to focus on the core logic of finding and acting on profitable opportunities.
Ready to start building your arbitrage bot? Explore the API documentation and get started with UK Odds API.