How Pro Bettors Make Money: 5 Edges Tested on Live Odds Data
Search for how professional bettors make money and you get net worths, nicknames, and Netflix-grade anecdotes. None of it tells you what they actually did. Every one of the names below won with a repeatable technique, and most of those techniques leave a signature in the odds feed you can measure today.
So we measured them. On July 31, 2026 we pulled the full 15-game MLB slate from the OddsPapi API, every bookmaker on every moneyline, and tested five classic edges against live prices. Two of the five are gone. One survives in a smaller form than the folklore suggests. Two still work, and they are the two nobody writes listicles about.
The five edges, and which ones survived
| Edge | Who ran it | What it needed then | Our live measurement |
|---|---|---|---|
| Price discrepancy (arbitrage) | Early cross-border arbers | Phone lines, runners, accounts in three countries | Dead. Best arb across 14 books: 0.01% |
| Line shopping | Syndicate desks | A room of clerks calling shops | Alive. 7.43% mean gap between best and worst price |
| Beating the closing line | Every serious bettor since | Getting a number before the market caught it | Harder. Best price beat the sharp fair on 12 of 30 sides |
| Modelling (xG, ratings) | Bloom, Benham, Benter | Bespoke data collection nobody else had | Commoditised. The market already prices the model |
| Size and speed | Syndicate operations | Beards, runners, dozens of accounts | Alive. Pinnacle repriced 23 times while DraftKings moved 5 |
1. Arbitrage: the edge that closed
The oldest professional edge in betting was two books disagreeing badly enough that you could back both sides and bank the difference. It needed nothing but capital, accounts, and the willingness to phone three shops before the price moved.
Here is that edge on tonight’s baseball. For each game we took the best available price on side 1 across every book, the best on side 2, and summed the implied probabilities. Under 1.0 means a risk-free position.
arbitrage: 2/15 games under 1.0, best 0.99990
Two games out of fifteen, and the better of the two pays 0.01%. Stake $10,000 across two books and you clear a dollar, before you account for the price moving between your two clicks. The 383 books in the catalogue have closed the gap on each other. If you want the full mechanics anyway, the Python arbitrage scanner builds one from scratch, and it will tell you the same thing: on liquid US markets, the sum almost never drops below one.
2. Line shopping: still the largest number on the board
Syndicate operations used to employ people whose whole job was knowing which shop had the best price. That job still exists. It is now four lines of Python.
Across the same 15 games, the gap between the best and worst quoted price on an identical outcome averaged 7.43% and peaked at 14.93% on Minnesota at Seattle. Nothing else in this article is that big.
| Bookmaker | Mean moneyline margin, 15 games |
|---|---|
| Kalshi | 1.00% |
| Polymarket | 1.00% |
| Pinnacle | 2.10% |
| Circa Sports | 2.81% |
| FanDuel | 4.02% |
| Caesars / William Hill | 4.27% |
| DraftKings | 4.61% |
| BetMGM / Borgata | 4.65% |
| SBOBet | 7.04% |
That ladder is the whole argument for shopping. A bettor stuck on one retail app pays roughly 4.5% per bet. The same wager at the top of the board costs 1%. You have not predicted anything better, you have just stopped donating three points of margin.
One result surprised us. Of the 30 best prices on the slate, prediction markets took 23: Kalshi 15, Polymarket 8. Pinnacle took 4. The sharpest number in US baseball right now sits on an exchange, which is why our line shopping guide treats exchanges as first-class books rather than a curiosity.
Count opinions, not logos
The 14 books on each game are not 14 views of the match. Deduping on the exact price pair collapsed 13.93 slugs to 10.80 independent quotes. BetMGM and Borgata posted byte-identical prices on 12 of 15 games; Caesars and William Hill on 9. Neither pair is flagged as a clone in the bookmaker catalogue, so a naive consensus average triple-weights one trading desk.
3. Closing line value: the edge got thinner than the folklore
Beating the closing number is the standard proof that you are picking well. The test people skip is whether the best price you can find is actually better than the sharp market’s own fair price.
We de-vigged Pinnacle’s moneyline on each game to get a fair probability, then asked whether the best price anywhere on the board cleared it. It did on 12 of 30 sides, and the average gap across all 30 was -0.32%.
Shopping the whole board gets you back to break-even against the sharp price. It does not hand you an edge. The edge has to come from somewhere else, which is what the EV and closing line value post works through, and why the no-vig calculator matters before you call anything value.
4. Modelling: the market ate the edge
Tony Bloom founded Starlizard and owns Brighton & Hove Albion. Matthew Benham founded Smartodds and owns Brentford. Both built soccer models years before the phrase expected goals reached television. The advantage was data collection nobody else was doing.
That advantage is now priced in, and we can show it two ways.
Bill Benter published his method in 1994, which makes him the one profitable bettor in this article you can read rather than infer. His headline table covers 2,313 races: his own fundamental model scored a pseudo-R² of 0.1016, the public odds scored 0.1237, and blending them reached 0.1327. The crowd beat his model on its own. He won by treating the market price as his strongest single input.
We ran the modern version of that test on 176 finished MLB games and found the retail consensus adds under 1% of independent information over a de-vigged Pinnacle close. Full method, code, and the negative result are in Benter’s second-stage test.
The Bloom and Benham version is in power ratings from closing odds, where we fit a 30-club MLS rating to nothing but Pinnacle’s closing prices. It beat a league-average baseline by 40% on unseen games and still cannot beat the book it learned from. A rating fitted to a market is a compression of that market.
5. Size and speed: what the syndicates were really selling
Zeljko Ranogajec’s operation is remembered for scale rather than for picks. The reason is structural. Knowing a price is worth nothing until you get money down at it before the number moves, and both halves of that leave a trace in the feed.
Speed
We pulled 16 hours of price history on Minnesota at Seattle for three books. Pinnacle recorded 38 snapshots and changed its number 23 times. DraftKings posted 5 updates in the same window. FanDuel posted 4.
The clearest single move: Pinnacle stepped the Mariners from 1.578 to 1.602 at 13:16:41 UTC. DraftKings did not reprice until 13:22:16, 5 minutes and 35 seconds later. That window is the modern descendant of the runner sprinting between shops, and you can watch for it with the steam move detector.
Size
Of the 14 books pricing these games, three publish a limit: Pinnacle, Kalshi, and Polymarket. US retail books set limits per account, so they ship none. Pinnacle’s number is a capped maximum win rather than a capped stake, and its base swings by more than 20x between markets on a single fixture, which makes it a direct readout of what the book is confident about.
A 3% edge on $200 is not a business. Betting limits and stake sizing covers reading the limit field and walking exchange depth ladders to find out what your number is worth after size.
Run the study yourself
Everything above came from one script and a free API key. It fetches today’s slate, pulls every bookmaker’s moneyline, dedupes identical feeds, de-vigs the sharp price, and reports the three numbers.
Step 1: authenticate
The key goes in the query string on every call. It is not a header.
import requests, time, datetime, statistics
from collections import defaultdict
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.oddspapi.io/v4"
MONEYLINE, SIDE_1, SIDE_2 = "131", "131", "132"
def call(path, **params):
"""One request, with the documented 429 retry. Never parallelise this."""
params["apiKey"] = API_KEY
for _ in range(4):
r = requests.get(f"{BASE_URL}/{path}", params=params, timeout=90)
body = r.json()
if r.status_code == 200:
return body
time.sleep(body["error"]["retryMs"] / 1000 + 0.3)
raise RuntimeError(f"{path} kept failing")
A 429 response is valid JSON with an error key and a retryMs value. Code that only checks for a bookmakerOdds key reads a rate limit as “no coverage” and poisons your study.
Step 2: pick a clean slate
def todays_slate(sport_id=13, tournament="MLB", min_hours=3, max_hours=13):
now = datetime.datetime.now(datetime.timezone.utc)
fixtures = call("fixtures", sportId=sport_id,
**{"from": now.strftime("%Y-%m-%dT%H:%M:%SZ"),
"to": (now + datetime.timedelta(days=2)).strftime("%Y-%m-%dT%H:%M:%SZ")})
out = []
for f in fixtures:
if not f.get("hasOdds") or f.get("tournamentName") != tournament:
continue
start = datetime.datetime.fromisoformat(f["startTime"].replace("Z", "+00:00"))
hours = (start - now).total_seconds() / 3600
if min_hours <= hours <= max_hours:
out.append(f)
return out
The window matters. US books post baseball moneylines late, so a fixture 20 hours out carries two books and a fixture inside 30 minutes starts suspending. Three to thirteen hours out gives a full board of stable pre-game prices.
Step 3: read the nested payload
def moneyline_quotes(fixture_id):
"""{slug: (price_side1, price_side2)} for every book pricing the moneyline."""
payload = call("odds", fixtureId=fixture_id)
quotes = {}
for slug, book in (payload.get("bookmakerOdds") or {}).items():
market = (book.get("markets") or {}).get(MONEYLINE)
if not market:
continue
outcomes = market.get("outcomes") or {}
try:
a = outcomes[SIDE_1]["players"]["0"]
b = outcomes[SIDE_2]["players"]["0"]
except KeyError:
continue
# active can be None on a live pre-game price, so test for False, not falsiness
if a.get("active") is False or b.get("active") is False:
continue
if not a.get("price") or not b.get("price"):
continue
quotes[slug] = (a["price"], b["price"])
return quotes
Two defensive rules earn their keep here. Filter on active is False rather than on truthiness, because a valid pre-game price can ship active: null. And leave the bookmakers filter off during a census, because requesting a slug that is absent from a fixture zeroes the entire response.
Step 4: dedupe, de-vig, compare
def independent(quotes):
"""Collapse books that quote byte-identical prices into one opinion."""
groups = defaultdict(list)
for slug, prices in quotes.items():
groups[prices].append(slug)
return groups
def devig(price_1, price_2):
"""Two-way proportional de-vig. Returns (fair_prob_1, fair_prob_2, margin_pct)."""
overround = 1 / price_1 + 1 / price_2
return (1 / price_1) / overround, (1 / price_2) / overround, (overround - 1) * 100
def scan(fixtures):
spreads, gaps, arbs = [], [], []
for f in fixtures:
quotes = moneyline_quotes(f["fixtureId"])
time.sleep(1.05)
if "pinnacle" not in quotes or len(quotes) < 6:
continue
fair_1, fair_2, margin = devig(*quotes["pinnacle"])
best_1 = max(quotes.items(), key=lambda kv: kv[1][0])
best_2 = max(quotes.items(), key=lambda kv: kv[1][1])
worst_1 = min(p[0] for p in quotes.values())
worst_2 = min(p[1] for p in quotes.values())
arb = 1 / best_1[1][0] + 1 / best_2[1][1]
spreads += [(best_1[1][0] / worst_1 - 1) * 100, (best_2[1][1] / worst_2 - 1) * 100]
gaps += [(best_1[1][0] * fair_1 - 1) * 100, (best_2[1][1] * fair_2 - 1) * 100]
arbs.append(arb)
print(f'{f["participant1Name"][:22]:<22} {len(quotes):>2} books / '
f'{len(independent(quotes)):>2} opinions | pinnacle margin {margin:4.2f}% | '
f'best {best_1[1][0]:.3f} {best_1[0]:<11} {best_2[1][1]:.3f} {best_2[0]:<11} | '
f'arb sum {arb:.5f}')
print(f"\nprice spread best vs worst: mean {statistics.mean(spreads):.2f}% max {max(spreads):.2f}%")
print(f"best price vs Pinnacle fair: mean {statistics.mean(gaps):+.2f}% "
f"beat it on {sum(1 for g in gaps if g > 0)}/{len(gaps)} sides")
print(f"arbitrage: {sum(1 for a in arbs if a < 1)}/{len(arbs)} games under 1.0, "
f"best {min(arbs):.5f}")
if __name__ == "__main__":
scan(todays_slate())
The time.sleep(1.05) is load-bearing. The free tier rate-limits per endpoint, and running these calls concurrently gets almost every one of them a 429. Serial with a one-second gap runs the whole slate clean.
The output
Chicago Cubs 14 books / 11 opinions | pinnacle margin 2.07% | best 1.700 betmgm 2.381 kalshi | arb sum 1.00823
Cincinnati Reds 14 books / 10 opinions | pinnacle margin 1.97% | best 2.174 kalshi 1.852 polymarket | arb sum 0.99994
Baltimore Orioles 14 books / 10 opinions | pinnacle margin 2.61% | best 1.923 polymarket 2.083 kalshi | arb sum 1.00010
Toronto Blue Jays 14 books / 12 opinions | pinnacle margin 2.02% | best 1.639 polymarket 2.564 kalshi | arb sum 1.00014
Tampa Bay Rays 14 books / 11 opinions | pinnacle margin 1.95% | best 1.754 polymarket 2.326 kalshi | arb sum 1.00005
Cleveland Guardians 14 books / 12 opinions | pinnacle margin 2.00% | best 1.818 polymarket 2.190 pinnacle | arb sum 1.00668
New York Mets 14 books / 11 opinions | pinnacle margin 2.15% | best 1.819 pinnacle 2.174 kalshi | arb sum 1.00973
Atlanta Braves 14 books / 11 opinions | pinnacle margin 1.89% | best 1.923 kalshi 2.083 kalshi | arb sum 1.00010
Houston Astros 14 books / 10 opinions | pinnacle margin 2.07% | best 1.852 kalshi 2.150 pinnacle | arb sum 1.00507
Colorado Rockies 14 books / 10 opinions | pinnacle margin 1.99% | best 2.000 polymarket 1.970 pinnacle | arb sum 1.00761
Los Angeles Angels 14 books / 11 opinions | pinnacle margin 2.17% | best 2.564 kalshi 1.639 polymarket | arb sum 1.00014
Athletics 13 books / 10 opinions | pinnacle margin 2.09% | best 2.439 polymarket 1.670 pointsbet.com.au | arb sum 1.00881
San Diego Padres 14 books / 11 opinions | pinnacle margin 1.98% | best 1.724 kalshi 2.326 kalshi | arb sum 1.00997
Los Angeles Dodgers 14 books / 10 opinions | pinnacle margin 2.45% | best 1.852 kalshi 2.150 fourwinds | arb sum 1.00507
Seattle Mariners 14 books / 12 opinions | pinnacle margin 2.10% | best 1.613 kalshi 2.632 kalshi | arb sum 0.99990
price spread best vs worst: mean 7.43% max 14.93%
best price vs Pinnacle fair: mean -0.32% beat it on 12/30 sides
arbitrage: 2/15 games under 1.0, best 0.99990
What is actually left in 2026
Four things came out of this slate that a working bettor can use.
The margin ladder is the first decision. Betting a 4.6% market when a 1.0% market prices the same game is a choice, and it costs more per season than most people's model is worth.
Prediction markets took 23 of 30 best prices. Kalshi and Polymarket quoted a 1.00% margin against Pinnacle's 2.10%. Whether that holds through the football season is an open question, and it is measurable with the same script.
Speed still pays, in minutes. A sharp book moving 23 times while a retail book moves 5 leaves a repricing gap you can detect. Chasing it needs an account that survives winning, which is the part the syndicate stories gloss over.
Free history is the leverage everyone in this article lacked. Bloom, Benham, and Benter each paid to build a data room. The 16 hours of Pinnacle price history in section 5 cost one API call on the free tier, and it runs back months. Every backtest in this article came from the free tier.
Before any of it turns into profit, size the bets so variance cannot end the experiment. The Kelly criterion calculator covers that arithmetic.
FAQ
How do professional bettors make money?
By buying a better price than the market's fair price and getting enough money down on it before the number moves. Modern versions of that are line shopping across many books, reacting to sharp price moves faster than retail books reprice, and running a model that adds information the market has not already absorbed.
Does arbitrage betting still work?
Barely, on liquid markets. Across 15 MLB games and 14 bookmakers, only two games priced below 1.0 on the best-of-both-sides sum, and the better one was worth 0.01%. Thinner sports and slower books produce larger gaps, so the scan is still worth running, but the days of comfortable margins on US majors are over.
Is line shopping worth the effort?
It is the largest number in this study. The gap between the best and worst price on the same outcome averaged 7.43% across the slate. That gap is bigger than the edge most models claim to find.
How does Starlizard make money?
Starlizard is Tony Bloom's soccer betting and consultancy operation, built on modelling and data collection that predated the public availability of expected goals. Nobody outside the firm can verify current numbers, so treat published figures with caution. The replicable part is the method: derive team strength from de-vigged market prices, then price the fixtures the market has not posted yet.
Can I do this with a free API key?
Yes. Every number in this article came from the free tier: 383 bookmakers across 69 sports, live odds, and historical price snapshots. Rate limits mean you run calls one at a time with a one-second gap rather than in parallel.
Get the same feed
The people in this article spent years building private access to prices that now arrive as JSON. Sharps, exchanges, US retail, and months of price history sit behind one key.
Grab a free OddsPapi API key and run the script above on tonight's slate.