Value betting against soft books with Pinnacle's no-vig fair odds
Pinnacle's prices are the closest thing the betting market has to a true probability. Remove its margin and you get a fair price; any soft bookmaker paying more than that price offers a bet with positive expected value (+EV). PinnFeed computes the fair price on every market, so the maths is one multiplication.
Step 1 — Understand the fair price#
For a market with decimal prices d_i:
implied_i = 1 / d_i
margin = Σ implied_i − 1
fairProb_i = implied_i / Σ implied_j (multiplicative method)
fair_i = 1 / fairProb_i
Every outcome in the API already carries fair and fairProb, and every market carries margin. Raw prices are included too, so you can apply the power or Shin method yourself if you prefer.
Step 2 — Compute expected value#
If a soft book offers decimal price S on an outcome whose fair probability is p:
EV = S × p − 1
At S = 2.30 and p = 0.488 (fair price 2.049), EV is +12.3%: in the long run you expect 12.3 cents per euro staked. Most bettors set a threshold of 1–3% to absorb noise and the time it takes to place the bet.
Step 3 — Size the stake#
Full Kelly ((S·p − 1) / (S − 1)) is too aggressive for estimated probabilities. A quarter Kelly is a common compromise and is what the script prints.
Step 4 — Run the script#
Fill SOFT with prices you collected from a soft book (by hand, from an odds screen or another API), keyed by the team names as PinnFeed spells them. Install requests, then run python value.py:
# value.py: find +EV prices at a soft bookmaker using Pinnacle's no-vig fair odds.
# pip install requests
import os, requests
BASE = os.environ.get("PINNFEED_BASE", "https://pinnfeed.io")
KEY = os.environ["PINNFEED_KEY"]
# Prices you collected from a soft book: (home team, away team, outcome) -> decimal odds.
SOFT = {
("Osasuna", "Malaga", "home"): 1.95,
("Colon", "Defensor Sporting", "away"): 2.30,
}
MIN_EV = 0.02 # 2% edge
def kelly(price, p, fraction=0.25):
"""Fractional Kelly stake as a share of bankroll."""
return max(0.0, fraction * (price * p - 1) / (price - 1))
r = requests.get(f"{BASE}/v1/odds", headers={"x-api-key": KEY},
params={"sport": "soccer", "market": "moneyline", "main": "true", "limit": 1000}, timeout=20)
r.raise_for_status()
for ev in r.json()["events"]:
for m in ev["markets"]:
for o in m["outcomes"]:
soft = SOFT.get((ev["home"], ev["away"], o["name"]))
if not soft:
continue
ev_pct = soft * o["fairProb"] - 1 # expected value per unit staked
flag = "VALUE" if ev_pct >= MIN_EV else "no edge"
print(f'{flag:8} {ev["home"]} v {ev["away"]} {o["name"]}: soft {soft} vs Pinnacle {o["decimal"]} '
f'(fair {o["fair"]:.3f}, margin {m["margin"]:.1%}) EV {ev_pct:+.1%} '
f'kelly/4 {kelly(soft, o["fairProb"]):.2%}')
Output looks like this:
VALUE Colon v Defensor Sporting away: soft 2.3 vs Pinnacle 1.8065 (fair 2.049, margin 13.4%) EV +12.3% kelly/4 2.36%
The REST call works on every plan; on Free, prices are 30 seconds old. For in-play value you want them live: run the same comparison inside the WebSocket client from the Python and Node guide, with Builder.
Step 5 — Check yourself with CLV#
Short-term results are mostly variance. The fastest honest feedback is closing line value: did the price you took beat Pinnacle's closing fair price? The CLV guide logs every bet and measures it.
Questions people ask#
Why use Pinnacle as the reference?#
Pinnacle runs low margins and high limits and moves prices on sharp money, so its de-vigged price is widely used as the best public estimate of the true probability.
Which de-vig method does PinnFeed use?#
The multiplicative (proportional) method. Raw decimal and American prices are included on every outcome, so you can use another method.
Does PinnFeed provide soft-bookmaker odds?#
No. PinnFeed carries Pinnacle only. Combine it with your own soft-book prices or a multi-book API such as The Odds API.