Player Props Analyzer Example - Python
Analyze player prop bets by fetching props for an event and comparing lines across every Australian bookmaker.
What you'll build
A script that:
- Fetches all player props for an AFL event
- Compares lines across bookmakers
- Shows consensus lines
- Identifies outlier books
- Highlights potential value bets
Perfect for: Finding value in player props, comparing bookmaker offerings, prop research
Prerequisites
- Python 3.8+
- SockOdds API key (free)
- Basic Python
Complete code
Step 1: Setup project
mkdir props-analyzer && cd props-analyzer
python -m venv venv && source venv/bin/activate
pip install requestsStep 2: Create analyzer.py
# analyzer.py
import os, sys, statistics, requests
API_KEY = os.environ["SOCKODDS_KEY"]
BASE = "https://api.sockodds.com/v2"
LEAGUE = sys.argv[1] if len(sys.argv) > 1 else "AFL"
TEAM_SIDES = {"home", "away", "all"}
def fetch_event(league):
r = requests.get(f"{BASE}/events", params={"leagueID": league, "oddsAvailable": "true", "limit": 1}, headers={"x-api-key": API_KEY}); r.raise_for_status()
data = r.json()["data"]
return data[0] if data else None
def is_player_prop(odd):
return odd["statEntityID"] not in TEAM_SIDES # the one check that identifies a prop
def analyze(event):
players = event.get("players", {}); rows = []
for odd in event["odds"].values():
if not is_player_prop(odd) or odd["betTypeID"] != "ou" or odd["sideID"] != "over": continue
lines = {b: float(q["overUnder"]) for b, q in odd["byBookmaker"].items() if q.get("available") and q.get("overUnder")}
prices = {b: q["decimal"] for b, q in odd["byBookmaker"].items() if q.get("available") and q.get("decimal")}
if len(lines) < 2: continue
consensus = statistics.median(lines.values())
name = players.get(odd["statEntityID"], {}).get("name", odd["statEntityID"])
for book, line in lines.items():
if line != consensus:
rows.append((abs(line - consensus), name, odd["statID"], book, line, consensus, prices.get(book)))
return sorted(rows, reverse=True)
if __name__ == "__main__":
e = fetch_event(LEAGUE)
if not e: sys.exit(f"no {LEAGUE} events with odds")
print(f"{e['teams']['away']['names']['medium']} @ {e['teams']['home']['names']['medium']} ({e['eventID']})")
props = [o for o in e["odds"].values() if is_player_prop(o)]
print(f"{len(props)} player-prop odds across {len({o['statID'] for o in props})} stats\n")
print(f"{'player':22} {'stat':14} {'book':14} {'line':>6} {'consensus':>10} {'price':>6}")
for diff, name, stat, book, line, cons, price in analyze(e)[:25]:
flag = " <- value?" if diff >= 1 else ""
print(f"{name:22} {stat:14} {book:14} {line:6.1f} {cons:10.1f} {price or 0:6.2f}{flag}")Step 3: Run it
SOCKODDS_KEY=so_live_… python analyzer.py AFLExpected output
Hawthorn @ Fremantle (afl_2026-09-03_fremantle_vs_hawthorn)
236 player-prop odds across 4 stats
player stat book line consensus price
Caleb Serong disposals pointsbet 28.5 29.5 1.95 <- value?
Jai Newcombe disposals tab 26.5 27.5 1.91 <- value?How it works
1. Identify player props
Any market whose statEntityID is not home/away/all.
2. Process bookmaker odds
Each book has its own overUnder and decimal; skip unavailable ones.
3. Calculate consensus
The median line across books.
4. Find outliers
Books whose line differs from the consensus, sorted by the size of the gap.
5. Identify value
A lower over line at the same price is worth a look — then check fairOdds on Base.
Enhancements
Track props over time
# store (eventID, oddID, book, line, price, lastUpdatedAt) each run and diffFilter by prop type
props = [o for o in props if o["statID"] == "disposals"]Export to spreadsheet
import csv # write rows with csv.writerAdd EV calculations
# on Base: edge = 1/decimal(fairOdds) - 1/price (see the +EV use case)Troubleshooting
"No player props found"
Props are posted a day or three out; try a league in season or includeFinished for a recent event.
Props missing for some players
Not every book prices every player.
Player names showing as IDs
The players map only carries players the source resolved; fall back to the id.