How to Calculate EV From Pinnacle Odds, Step by Step

How to calculate EV from Pinnacle odds: convert to probability, remove the vig properly, compare to your book's price. Worked example, code, and the honest caveats.
Expected value from a Pinnacle price is three lines of arithmetic and one decision that most people get wrong. The arithmetic: turn Pinnacle's odds into a probability, strip the margin, compare that probability to the price your book still has up. The decision: how you strip the margin. Get that wrong on a long shot and you will manufacture edges that do not exist.
An odds drop, by itself, is not an edge. It is evidence that the sharpest price in the market moved. Whether that evidence is worth money depends entirely on the number you compute next.
The short version: the whole calculation
Take Pinnacle's post-move price on a two-way market and the price your book offers on the same selection.
- Implied probabilities.
q = 1 / decimal_odds, both sides. - Remove the vig so the two sides sum to 1. The multiplicative way:
p = q_side / (q_home + q_away). - EV against your book.
EV = p × (book_odds − 1) − (1 − p).
Worked example. Pinnacle prices a moneyline 1.95 / 1.95 after a drop. Each side implies 0.5128, which sums to 1.0256 — the 2.56% is the margin. De-vigged, each side is exactly 0.5000. Your book still shows the home side at 2.10.
EV = 0.50 × (2.10 − 1) − 0.50
= 0.55 − 0.50
= +0.05 → +5% per unit staked
Stake 100, expect 5 back over a long enough run of identical spots. That's it. Everything below this line is about not lying to yourself with that number.
One shortcut worth knowing: if you're reacting to a pinnodds odds-drop alert, step 2 is already done. The alert carries nvp, the no-vig fair price for the selection that moved, so p = 1 / nvp and you jump straight to step 3.
EV is not closing line value, and it is not "beating the vig"
These get conflated constantly, so let's separate them.
CLV is a backward-looking scorecard. You bet 2.10, the market closed at 1.95, you beat the close. That's a good sign about your process, but it isn't a payout and it isn't EV — it's a proxy measured after the fact.
EV is forward-looking and specific to a price you can actually bet right now. It needs two inputs: a fair probability and a stake-able price. A feed can give you the first. Only your book gives you the second, which is why no odds API — ours included — can hand you EV directly.
The other confusion: people compute a fair price from Pinnacle and then think the gap between fair and Pinnacle's own posted price is their edge. It isn't. That gap is the margin you just removed.
De-vigging is where the real decision lives
Multiplicative normalisation — divide each implied probability by the overround — is the method everyone learns first, and on a two-way market near even money it's fine. The error is basis points. Don't overthink a 1.95 / 1.95.
It falls apart on lopsided markets. Bookmakers don't spread margin evenly; they load proportionally more of it onto the long shot. So when you divide a 1.15 / 7.00 market by its overround, you hand the 7.00 side more probability than it deserves and invent EV out of nothing. Chase enough of those and your "+3% plays" turn into a rake-paying hobby with extra steps.
Two better tools:
- Power method. Solve for exponent
kwhereΣ qᵢ^k = 1. It compresses the favourite less and the long shot more, which matches how margin actually gets applied. - Shin method. Models an insider-trading proportion
z. Better theoretical story, similar practical result on most markets.
My stance, and I'll defend it: use multiplicative only when both sides sit roughly between 1.60 and 2.60. Use power everywhere else — three-way 1X2, big handicap favourites, any prop with an 8.00 tail. It costs you one numeric solve and it removes a bias that runs in exactly the direction that empties bankrolls.
from scipy.optimize import brentq
def devig_power(odds):
q = [1 / o for o in odds]
k = brentq(lambda k: sum(x ** k for x in q) - 1, 0.5, 3.0)
return [x ** k for x in q]
print(devig_power([1.15, 7.00]))
# multiplicative hands the 7.00 side ~0.1385; power gives noticeably less
Run both on a lopsided market once and the difference stops being academic.
Why the number matters more after a drop
Pinnacle moves because money it respects showed up. When a total slides from over 2.75 at -105 to -120, the fair probability behind that line genuinely changed. Soft books lag. The gap between Pinnacle repricing and everyone else catching up is the only window where the EV you just computed is collectable.
Which means the input has to be the post-move fair price. An /odds-drop SSE alert names the market in sect, the moving selection in outcome, and gives you both from_price and to_price. Use to_price — or better, nvp. Using from_price computes the EV of a bet that no longer exists.
Then there's limit, Pinnacle's max stake on that market. This field does more work than people give it credit for. A drop at a healthy limit means Pinnacle is confident enough to take size at the new number. A drop on a market with a small limit is closer to a shrug — real information, but not the same information. I gate on it before I gate on edge size.
Kelly, once you have the number
EV says whether to bet. Kelly says how much.
f* = (p × b − (1 − p)) / b where b = book_odds − 1
With p = 0.50 and book odds 2.10, b = 1.10, so f* = 0.05 / 1.10 ≈ 4.5% of bankroll. Full Kelly on de-vigged Pinnacle probabilities is too aggressive, because Kelly assumes you know p. You don't — you estimated it, and your estimate inherits whatever bias your de-vig model carries. Quarter to half Kelly is the sane band. Below about 1% EV the fraction is small enough that limits, rounding and your own click speed dominate the outcome anyway.
The honest caveats
This is the section that saves you money, so read it before the code.
Your fair price is an estimate, not truth. De-vigging assumes margin is distributed the way your chosen model says. That's a good assumption, not a fact. A +1.5% EV reading sits inside the noise band created by your own choice of de-vig method — switch from multiplicative to power and it may vanish.
Stale comparison kills more edges than bad math. If your book's price is twenty seconds old and already gone, your EV is fiction. The Pinnacle half of the comparison arrives pushed, sub-second, over /ws/feed. The soft-book half is your problem, and in practice it's the slower half.
Pinnacle is one book. pinnodds carries Pinnacle only. That's deliberate — it's the reference price everyone else is measured against — but it means you cannot build a market consensus from this feed alone, and you cannot line-shop inside it. If your workflow needs forty books in one payload, use an aggregator and use this for the fair-price anchor. Both, honestly, is the right architecture for most people.
No historical odds archive. The feed is live and prematch, pushed in real time. You cannot backtest your de-vig model against three years of closing lines from these endpoints. If you want history, you start recording it today.
SSE alerts carry no drop percentage. The stream gives you sect, outcome, from_price, to_price, id, limit, nvp. If you want a precomputed drop_pct, that lives on the enriched REST rows at /api/drops (market, designation, from, to, event_id, drop_pct). Neither shape gives you EV, because EV needs your book's price and no odds feed can know that.
A +4% play into a 20-unit ceiling is a hobby. Check limit before you get excited.
Wiring it up, end to end
npm install pinnodds
import { OddsDropStream } from 'pinnodds';
const stream = new OddsDropStream({ apiKey: process.env.PINNODDS_KEY });
const ev = (p, bookOdds) => p * (bookOdds - 1) - (1 - p);
stream.on('drop', async (a) => {
// a: { id, sect, outcome, from_price, to_price, limit, nvp }
const p = 1 / a.nvp; // fair probability, vig already gone
const book = await myBook.price(a.id, a.sect, a.outcome);
if (!book) return;
const edge = ev(p, book.odds);
if (edge > 0.02 && a.limit >= 500) {
console.log(`${a.outcome} ${a.sect}: fair ${a.nvp} vs ${book.odds} → ${(edge * 100).toFixed(2)}% EV`);
}
});
stream.start();
Both thresholds are opinions. edge > 0.02 because anything under 2% is inside my model's error bars. limit >= 500 because a market Pinnacle won't take size on is a market whose price deserves less trust. Tune them to your bankroll and your book set.
For prematch scanning rather than live reaction, pull GET /kit/v1/prematch/lines and de-vig it yourself. Every line Pinnacle prices comes through, quarter lines included — soccer totals at 1.75 / 2.25 / 2.75 / 3.25, quarter-ball handicaps at -0.75 / -1.25 / -1.75 / -2.25. Quarter lines matter for EV work specifically because half the stake pushes, so the payoff structure your formula models is not a clean win/lose binary. Handle that split explicitly or your numbers drift. Field-by-field detail is in the docs.
Want props, exact scores and outrights in the same pipeline? Add include_specials=1. Budget for the payload: soccer prematch is roughly 1,500 events without the flag and roughly 12,400 with it, each special row carrying special, special_category, special_markets and a parent_id back to its fixture. Specials are also precisely where multiplicative de-vigging does the most damage, because long-shot prices dominate the book.
The mistake almost everyone makes once
Computing EV against Pinnacle itself. If your fair probability came from de-vigging Pinnacle, betting Pinnacle at its own posted price is zero EV minus the margin you just removed, by construction. Pinnacle is the yardstick, not the target.
The subtler near-miss: comparing a de-vigged Pinnacle price to a raw soft-book price and worrying that you're comparing apples to oranges. You aren't — that's correct. The soft book's margin is baked into its number, and that's exactly why the gap exists. What you must not do is de-vig the soft book too. That erases the thing you're hunting.
Takeaway
EV = p × (book_odds − 1) − (1 − p). The formula is trivial; p is the whole job. Take it from nvp when you're reacting to a pushed drop alert, or de-vig /kit/v1/prematch/lines yourself with a power method whenever the market is lopsided — never plain multiplicative on a 7.00 tail. Then gate on limit, size with fractional Kelly, and treat anything under 1% as noise. A trial key takes seconds and no card, which is enough to run this loop against live markets tonight and see how often your soft book actually lags.
Frequently asked questions
How do you calculate EV from Pinnacle odds?
Convert Pinnacle's decimal odds to implied probability (1 / odds), remove the bookmaker margin so the outcomes sum to 1, then apply EV = p × (book_odds − 1) − (1 − p) using the price your own book offers. The de-vigged Pinnacle probability is your fair estimate; the EV lives in the gap against a book that hasn't repriced.
What is the nvp field in a pinnodds odds-drop alert?
nvp is the no-vig fair price for the selection that moved, so the margin has already been removed for you. Use p = 1 / nvp and skip straight to the EV formula.
Why shouldn't I use multiplicative de-vigging on long shots?
Bookmakers load proportionally more margin onto long shots, so dividing every implied probability by the overround overstates the long shot's true chance. On a market like 1.15 / 7.00 that manufactures EV that isn't there — use the power or Shin method instead.
Can I bet Pinnacle itself for positive EV?
No. If your fair probability came from de-vigging Pinnacle, then betting Pinnacle at its own posted price is zero EV minus the margin you just stripped out. Pinnacle is the reference price you measure other books against.
Does pinnodds give me the EV or drop percentage directly?
SSE alerts on /odds-drop carry sect, outcome, from_price, to_price, id, limit and nvp but no drop percentage; the enriched REST rows at /api/drops add a precomputed drop_pct. Neither returns EV, because EV requires your book's price, which no odds feed can know.
Is there historical Pinnacle odds data for backtesting a de-vig model?
Not from this feed. pinnodds serves live and prematch odds pushed in real time, with no historical archive, so if you want closing-line history you record it yourself from today onward.
Get real-time Pinnacle odds in your code
Live & prematch markets with instant odds-drop alerts over SSE and WebSocket. Free trial key in seconds — no card.
Start free trial