Dutching Calculator in Python: Cover Every Outcome Across 350+ Books
You fancy three outsiders for a tournament. Or you want to cover the draw and the away win and only lose if the favourite delivers. How much do you put on each so your return is identical no matter which one lands? That’s dutching — splitting your stake across several selections so they all pay back the same.
The catch every generic dutching calculator ignores: the margin you pay to cover all those outcomes depends entirely on which prices you get. Take each selection’s best price across many books and the cost of covering collapses — occasionally past break-even into a genuine arbitrage. This guide builds a dutching calculator in pure Python and wires it straight into the OddsPapi odds API so every leg is priced at the best of 350+ bookmakers.
What Dutching Actually Is
Back N selections. Size each stake so that whichever one wins, you collect the same amount. The math is one line: stake each selection in proportion to its inverse odds (its implied probability).
stake_i = total_stake * (1 / odds_i) / sum(1 / odds_j for all j)
The guaranteed return if any selection wins is total_stake / sum(1/odds_j). That denominator — the sum of inverse odds — is the book percentage. It’s the whole game:
- Book % > 100% — you pay a margin to cover everything (normal case).
- Book % = 100% — you break even whoever wins.
- Book % < 100% — you profit no matter what. That’s an arbitrage.
So minimising book % is the entire job, and that means shopping each leg for its best price. Here’s what that looks like on a real fixture.
| Pricing source (3-way market) | Book % | Cost to cover all 3 |
|---|---|---|
| Bet365 alone | 105.54% | -5.25% |
| FanDuel alone | 105.17% | -4.92% |
| DraftKings alone | 104.29% | -4.11% |
| Pinnacle alone | 102.17% | -2.12% |
| Best price per leg (350+ books) | 101.22% | -1.21% |
Same three outcomes, same match. Covering them on Bet365 alone costs 5.25%; line-shopping each leg cuts that to 1.21% — a quarter of the cost.
Step 1: Authenticate and Pull the Market
OddsPapi auth is a query parameter, not a header. Grab a free API key and pull every book’s prices for one fixture in a single call. We’ll use a World Cup 2026 match (snapshot, June 2026) and the Full Time Result market (1X2, market ID 101; outcomes Home=101, Draw=102, Away=103).
import requests, time
API_KEY = "YOUR_API_KEY"
BASE = "https://api.oddspapi.io/v4"
def get(path, **params):
# GET with the ~0.9s same-endpoint cooldown handled
params["apiKey"] = API_KEY
for _ in range(6):
r = requests.get(f"{BASE}/{path}", params=params)
try:
data = r.json()
if data:
return data
except ValueError:
pass
time.sleep(1.0)
return {}
fixture_id = "id1000001666457012"
odds = get("odds", fixtureId=fixture_id)
books = odds["bookmakerOdds"]
print(f"{len(books)} bookmakers on this fixture")
Note: do not pass the bookmakers filter unless you’re certain every slug you list is present — if any requested book is missing on the fixture, the API zeroes the entire response. Pull them all, then pick.
Step 2: Find the Best Price for Each Selection
Walk every book, keep the highest active price for each outcome. The active check matters — a suspended price can sit in the payload looking like a juicy outlier.
OUTCOMES = {101: "Home", 102: "Draw", 103: "Away"}
def best_prices(books, market_id=101, outcome_ids=OUTCOMES):
best = {oid: (0.0, None) for oid in outcome_ids}
for slug, bdata in books.items():
outs = bdata.get("markets", {}).get(str(market_id), {}).get("outcomes", {})
for oid in outcome_ids:
o = outs.get(str(oid))
if not o:
continue
leg = o["players"]["0"]
price, active = leg.get("price"), leg.get("active", True)
if price and active and price > best[oid][0]:
best[oid] = (price, slug)
return best
best = best_prices(books)
for oid, name in OUTCOMES.items():
price, slug = best[oid]
print(f"{name:5}: {price} @ {slug}")
# Home : 2.25 @ pinnacle
# Draw : 3.6 @ bet365
# Away : 3.448 @ kalshi
Step 3: The Dutching Calculator
Two ways to use it. Fix a total stake and see the guaranteed return, or fix a target return and see the total outlay. Both fall straight out of the inverse-odds split.
def dutch(odds_list, total_stake=None, target_return=None):
inv = [1.0 / o for o in odds_list]
book_pct = sum(inv)
if target_return is not None:
# stake_i = target_return / odds_i -> every leg returns target_return
stakes = [target_return / o for o in odds_list]
total_stake = sum(stakes)
guaranteed = target_return
else:
stakes = [total_stake * i / book_pct for i in inv]
guaranteed = total_stake / book_pct
profit = guaranteed - total_stake
return {
"stakes": stakes,
"total_stake": total_stake,
"guaranteed_return": guaranteed,
"profit": profit,
"book_pct": book_pct * 100,
}
prices = [best[oid][0] for oid in OUTCOMES] # [2.25, 3.6, 3.448]
result = dutch(prices, total_stake=100)
for (oid, name), stake in zip(OUTCOMES.items(), result["stakes"]):
print(f"{name:5}: stake {stake:6.2f} @ {best[oid][0]}")
print(f"Book %: {result['book_pct']:.2f}%")
print(f"Guaranteed return on any winner: {result['guaranteed_return']:.2f}")
print(f"P/L: {result['profit']:+.2f}")
Output:
Home : stake 43.91 @ 2.25
Draw : stake 27.44 @ 3.6
Away : stake 28.65 @ 3.448
Book %: 101.22%
Guaranteed return on any winner: 98.79
P/L: -1.21
Stake £100 across the three best prices and you get £98.79 back whoever wins — a fixed £1.21 cost to have the entire match covered. On Bet365 alone that same cover would cost £5.25.
Step 4: Dutch a Subset (Fade the Favourite)
You don’t have to cover every outcome. A common play is to back only the draw and the away win — you collect the same on either, and only lose if the favourite delivers. Just pass the subset:
subset = [best[102][0], best[103][0]] # Draw 3.6, Away 3.448
r = dutch(subset, total_stake=100)
print(f"Draw stake {r['stakes'][0]:.2f}, Away stake {r['stakes'][1]:.2f}")
print(f"Return if draw or away: {r['guaranteed_return']:.2f} (lose 100 if home wins)")
# Book % here is ~56.5%, so the "return" is ~1.77x your stake on a hit
This is risk-shaping, not free money — you’re trading a smaller payout for two ways to win instead of one. The calculator just tells you the exact split.
Step 5: Flag the Arbs Automatically
Run the best-price dutch across your slate and surface any fixture where the combined book % dips below 100% — that’s a covered position that profits no matter what.
def scan_arbs(fixture_ids, market_id=101):
for fid in fixture_ids:
books = get("odds", fixtureId=fid).get("bookmakerOdds", {})
if not books:
continue
best = best_prices(books, market_id)
if not all(best[o][1] for o in best):
continue
book_pct = sum(1.0 / best[o][0] for o in best) * 100
flag = " <<< ARB" if book_pct < 100 else ""
print(f"{fid}: {book_pct:.2f}%{flag}")
time.sleep(1.0) # respect the per-endpoint cooldown
Most fixtures land just over 100% — the books’ margin doing its job — but with 350+ books quoting independently, the occasional cross-book gap drops a line under 100. For a deeper arb engine with stake locking, see our arbitrage betting bot guide; dutching is the staking mechanic underneath it.
The Honest Part: Dutching Doesn’t Create Edge
Read this before you fund anything. Dutching is a staking method, not a money machine. If the combined book % is over 100% — which it almost always is on a single market — covering every outcome locks in a small loss. You’re paying the bookmaker’s margin for certainty. The only ways dutching turns a profit:
- The combined price is mispriced below 100% (a true arb), which line-shopping across 350+ books makes far more findable but never guarantees.
- You’re dutching a subset you have a genuine view on — e.g. you think the favourite is overpriced, so you cover the other two. The profit then comes from your read, not the dutch.
Watch the same traps that bite line-shoppers: a stale or boosted outlier price inflates the apparent best line and evaporates when you click. Always filter on active, and treat a single book that’s wildly off the others with suspicion. If you’re sizing for a known edge rather than equal cover, the Kelly criterion is the right tool, and to lock profit on a bet you already hold, reach for the hedging calculator.
Build It on Real Prices
The calculator is a dozen lines; the leverage is the data feeding it. Best-price-per-leg dutching only works if you can see every book at once — and OddsPapi returns 350+ on a single call, with free historical odds if you want to backtest how often a market actually drifts under 100%.
Stop overpaying to cover your bets. Get your free OddsPapi API key and price every dutching leg at the best of 350+ bookmakers.
Frequently Asked Questions
What is dutching in betting?
Dutching is splitting your stake across multiple selections so that you win the same amount whichever one lands. You size each stake in proportion to its implied probability (inverse odds). It’s used to cover several outcomes in one market or to back a group of selections you fancy.
Is dutching profitable?
Not on its own. Covering every outcome in a market costs the bookmaker’s margin, so the combined “book percentage” is usually above 100% and you lock a small loss. Dutching only profits when the combined best price is below 100% (an arbitrage) or when you’re dutching a subset based on a genuine edge.
How does line shopping reduce dutching cost?
The cost of covering outcomes equals the sum of inverse odds. Taking each selection’s best price across many books minimises that sum. In the worked example, covering a 3-way market cost 5.25% on one book but only 1.21% when each leg was priced at the best of 350+ books.
What’s the difference between dutching, hedging, and arbitrage?
Dutching backs multiple selections at the same time for an equal return. Hedging places an opposing bet to lock profit or limit loss on a bet you already hold. Arbitrage is a dutch (or back/lay) where the combined price guarantees profit. They share the same inverse-odds math but solve different problems.
Which markets work best for dutching?
Any market with mutually exclusive outcomes: 1X2 (home/draw/away), outright tournament winners, correct score, first goalscorer. The more selections you cover, the higher the combined book percentage, so line-shopping each leg matters more as the field grows.