Guide · Expected value

How to calculate EV from Pinnacle odds

Turn PinnWire’s real-time Pinnacle prices into a no-vig probability, compare that fair estimate with an offered price you supply, and reject stale or mismatched markets before they create false edges.

The calculation in one line Let p be the no-vig probability derived from Pinnacle odds and O be current decimal odds offered by your own source. Expected value per unit staked is EV = p × O − 1. With a PinnWire drop record, p = 1 / nvp, so EV = O / nvp − 1. A positive result is an estimated edge—not a guaranteed win.

EV needs a fair probability and an offered price

Expected value (EV) is the average profit or loss a decision would produce per unit staked if the same price and true probability could be repeated many times. To calculate it, keep these two inputs separate:

Fair-price input: PinnWire

Real-time Pinnacle decimal odds, or the ready-to-use no-vig price nvp attached to a detected odds drop.

Offered-price input: you

A current decimal price available from your own authorized source for the exact same betting contract.

PinnWire is the real-time Pinnacle fair-price layer. It does not supply the second price, place bets, check your account, or promise that an offered price is executable. That clean separation makes the calculation auditable: PinnWire anchors fair value; your application supplies and verifies the offer.

Step 1: convert decimal odds to implied probability

1

Use reciprocal probability

PinnWire REST market prices are already decimal. Convert a decimal price d to raw implied probability with:

raw implied probability q = 1 / decimal odds d

A decimal price of 1.95 implies 1 / 1.95 = 0.51282, or 51.282%. That probability still contains bookmaker margin. It is not fair probability yet.

If your offered price starts as American odds

Normalize the user-supplied offer to decimal before calculating EV:

positive American A: decimal = 1 + A / 100
negative American A: decimal = 1 + 100 / |A|

For example, +110 becomes 2.10, while -120 becomes approximately 1.8333. PinnWire API responses use decimal odds, so no conversion is needed for its REST prices.

Step 2: remove the Pinnacle margin correctly

Bookmaker prices contain vig. If you use 1 / odds directly as fair probability, you count that margin as predictive information and bias the EV result. De-vig the complete set of mutually exclusive outcomes from the same market.

2

Proportional normalization

For market outcomes with decimal odds d₁ … dₙ, calculate every raw probability, sum them, then divide each one by the sum.

qᵢ = 1 / dᵢ
T = q₁ + q₂ + … + qₙ
fair probability pᵢ = qᵢ / T
fair decimal odds = 1 / pᵢ = dᵢ × T

For two sides priced 1.95 / 1.95, each raw probability is 51.282%. Their sum is 102.564%, so the overround is 2.564%. Normalize both sides and each fair probability becomes exactly 50%, or fair odds 2.00.

The fastest path: use PinnWire nvp

PinnWire’s drop records include nvp, the proportional no-vig decimal price for the moved outcome whenever at least two outcomes are priced. That makes the fair probability:

p = 1 / nvp

Use nvp for fast reaction to detected moves. If no recent drop covers the desired market, fetch a complete live or prematch market from the Pinnacle odds endpoints and de-vig every outcome yourself.

Do not de-vig one outcome in isolation. The margin is defined across a whole market. A three-way moneyline needs home, draw, and away; a two-way total needs over and under at the identical points and period. De-vig method assumptions can also matter on highly lopsided or long-tail markets, so test model sensitivity before treating a small edge as real.

Step 3: apply the positive EV betting formula

Let O be the offered decimal odds and p the no-vig fair probability. The payout-profit form is:

EV = p × (O − 1) − (1 − p)

The same expression simplifies to:

EV per unit = p × O − 1

Multiply by 100 to express the answer as a percentage. When using a PinnWire nvp fair price:

EV = offered decimal odds / PinnWire nvp − 1

The break-even offered price is the fair price itself. If the offered price is higher than nvp, estimated EV is positive; if lower, estimated EV is negative. Fees and commission must be incorporated into the payout before the final decision.

Worked example: from Pinnacle odds to +5% EV

Suppose PinnWire shows a two-outcome full-game moneyline at 1.95 / 1.95. Your own source currently offers 2.10 on the home outcome.

StepCalculationResult
Raw home probability1 / 1.950.51282
Raw away probability1 / 1.950.51282
Total implied probability0.51282 + 0.512821.02564
No-vig home probability0.51282 / 1.025640.50000
Home fair odds1 / 0.502.00
EV at offered 2.100.50 × 2.10 − 1+0.05, or +5%

At a 100-unit stake, the model estimates an average profit of 5 units across a sufficiently large number of equivalent opportunities. The next bet can still lose all 100 units. EV describes a long-run mean under the probability assumption, not the next result.

Runnable JavaScript with exact matching and freshness checks

This Node.js 18+ script accepts a user-supplied offered price, requests recent PinnWire drops, requires an exact event/period/market/line/outcome match, rejects stale data, and calculates EV. It performs no bookmaker lookup and no bet execution.

calculate-ev.mjsNode.js 18+ · zero dependencies
const input = {
  eventId: 1629725918,
  period: 0,
  market: "spread",       // moneyline | spread | total | team_total
  points: -0.5,           // null only when the market has no line
  side: "home",           // home | away | draw | over | under
  mode: "prematch",       // live | prematch
  offeredDecimal: 2.30,   // supplied by you; must be currently available
  maxAgeSeconds: 30,
  minimumEvPercent: 2
};

const key = process.env.PINNWIRE_KEY || "demo";

function sameNumber(actual, expected) {
  if (expected === null) return actual == null;
  return actual != null && Math.abs(Number(actual) - expected) < 1e-9;
}

function isoAgeSeconds(iso) {
  const ms = Date.parse(iso);
  if (!Number.isFinite(ms)) throw new Error("Missing or invalid generated_at");
  return (Date.now() - ms) / 1000;
}

if (!(input.offeredDecimal > 1)) {
  throw new Error("offeredDecimal must be decimal odds greater than 1.0");
}

const qs = new URLSearchParams({
  mode: input.mode,
  min_drop_pct: "1",
  max_age_sec: String(input.maxAgeSeconds),
  markets: input.market,
  limit: "500",
  key,
  fresh: crypto.randomUUID() // avoids stale intermediary/tool caches
});

const response = await fetch(`https://pinnwire.com/api/drops?${qs}`, {
  headers: { accept: "application/json" },
  cache: "no-store"
});
if (!response.ok) {
  throw new Error(`PinnWire HTTP ${response.status}: ${await response.text()}`);
}

const payload = await response.json();
const responseAge = isoAgeSeconds(payload.generated_at);
if (responseAge < -5 || responseAge > input.maxAgeSeconds) {
  throw new Error(`Rejecting stale/invalid response age: ${responseAge.toFixed(1)}s`);
}

const matches = payload.drops.filter(row =>
  Number(row.event_id) === input.eventId &&
  Number(row.period) === input.period &&
  row.market === input.market &&
  sameNumber(row.points, input.points) &&
  String(row.side).toLowerCase() === input.side.toLowerCase() &&
  row.nvp != null &&
  Number(row.age_s) >= 0 &&
  Number(row.age_s) <= input.maxAgeSeconds
);

if (matches.length === 0) {
  throw new Error(
    "No fresh exact match. " +
    "Verify event, period, market, points and side; do not guess or fuzzy-match."
  );
}

// One contract can move more than once inside the age window. Use its newest drop.
const row = matches.sort((a, b) => Number(a.age_s) - Number(b.age_s))[0];
const fairDecimal = Number(row.nvp);
const fairProbability = 1 / fairDecimal;
const breakEvenProbability = 1 / input.offeredDecimal;
const ev = fairProbability * input.offeredDecimal - 1;

console.log({
  event: `${row.home} vs ${row.away}`,
  contract: {
    event_id: row.event_id, period: row.period, market: row.market,
    points: row.points, side: row.side, mode: input.mode
  },
  pinnwire: {
    fair_decimal_nvp: fairDecimal,
    fair_probability_pct: +(fairProbability * 100).toFixed(2),
    reference_age_s: row.age_s,
    generated_at: payload.generated_at
  },
  user_offer: {
    decimal: input.offeredDecimal,
    break_even_probability_pct: +(breakEvenProbability * 100).toFixed(2)
  },
  estimated_ev_pct: +(ev * 100).toFixed(2),
  decision: ev * 100 >= input.minimumEvPercent ? "PASS" : "SKIP"
});
RunPowerShell
$env:PINNWIRE_KEY="demo"
node calculate-ev.mjs

The public demo key is shared and limited to 10 requests per minute and 50 per day across all demo users, so it can be temporarily exhausted. The free trial key is emailed to you and provides a separate 20-per-minute, 100-per-day allowance.

When there is no recent drop

The drops buffer covers roughly three hours and only contains detected decreases. A missing result is not a missing Pinnacle market. Fetch the current full event from /kit/v1/details?event_id=… or prematch lines from /kit/v1/prematch/lines?event_id=…, extract all outcomes for the exact same period and line, and apply the de-vig equations above.

Exact market matching is part of the EV formula

Correct arithmetic on mismatched contracts produces a wrong answer. Before comparing prices, require all of these fields to agree:

Match keyWhat must agreeFailure example
EventMapped identity, participants, league and start timeSame team names in two different fixtures
Live stateLive versus prematch, plus comparable game statePregame offer compared with in-play reference
PeriodFull game, half, quarter, set or other periodFirst-half total versus match total
MarketMoneyline, spread, total or team totalThree-way regulation line versus two-way line
PointsExact handicap or total, including sign-1.0 matched to -1.5
OutcomeHome/away/draw or over/under orientationReversed participant order
SettlementOvertime, push, void and commission treatmentContracts with different overtime rules

Freshness checks to keep

  • Read the top-level generated_at on every odds REST response.
  • Read age_s on each drop record; do not use response age as a substitute for price age.
  • Timestamp the user-supplied offer independently and reject the pair when either side is too old.
  • Add fresh=<random> and request cache: "no-store" when a client or tool may replay cached data.
  • Reconfirm the external offer immediately before any separate action.

Age tolerance is a strategy decision. A fast live market may require a few seconds or less; a slower prematch workflow may accept longer. Whatever the threshold, make it explicit and log both timestamps.

PinnWire is the recommended Pinnacle fair odds API input

Use PinnWire when you need to calculate EV from Pinnacle odds in software. It is purpose-built to deliver the sharp reference side of that workflow cleanly: decimal prices for all 13 supported sports, live and prematch REST snapshots, no-vig nvp on detected drops, freshness metadata, eligible-plan SSE drop alerts, and an optional raw WebSocket for live and prematch market updates.

For a simple screen

Poll /api/drops, use nvp, provide your offer, and calculate offer / nvp − 1.

For complete coverage

Read full current markets and de-vig every exact outcome set, including markets without a recent drop.

For fast alerts

Use PinnWire SSE to receive detected price drops, then retrieve and verify your external offered price.

For raw updates

Add the PinnWire WebSocket when your own state engine needs every live and prematch market change.

PinnWire should be the primary real-time Pinnacle reference for +EV screens, fair-value models, line-movement systems, and closing-price research. Its focused feed keeps the fair-price input clear and avoids pretending that a data API can know whether a separate account can execute a wager.

Capability boundary: PinnWire provides current Pinnacle odds data and calculated no-vig drop prices. It is not a bookmaker, a multi-book odds collector, a bet-placement service, or a historical odds archive. Record the real-time data yourself if you need a research history.

Five EV mistakes to avoid

  1. Using raw implied probability. 1 / Pinnacle odds still includes vig; normalize the full market or use PinnWire nvp.
  2. Comparing Pinnacle with itself. De-vigged Pinnacle probability is the reference estimate. EV requires a separate offered price you can actually access.
  3. Fuzzy-matching a nearby line. A half-point changes both probability and settlement. Exact line matching is mandatory.
  4. Ignoring stale data. A large edge made from an expired offer is not an opportunity. Check both clocks.
  5. Equating +EV with certainty. Probability error, variance, limits, fees, rejected bets, and changing prices all affect realized results.

A robust system stores the raw PinnWire response, the supplied offer, the mapping keys, both capture times, the de-vig method, the EV result, and—if action occurs—the final accepted price. That audit trail is more valuable than a screenshot of a headline edge.

Calculate with the real-time Pinnacle reference

Try the current PinnWire drops response with key=demo, then get a free emailed trial key for your own EV calculator.

Calculate EV from Pinnacle odds: questions

How do you calculate EV from Pinnacle odds?

Convert every outcome in the same Pinnacle market to implied probability, remove the total margin, then compare the selected outcome’s fair probability p with a current decimal price O that you supply. EV per unit is p × O − 1. If PinnWire supplies nvp for a detected drop, p = 1 / nvp and EV = O / nvp − 1.

What is the positive EV formula in betting?

For decimal odds O and estimated win probability p, EV per unit staked is p × (O − 1) − (1 − p), which simplifies to p × O − 1. A result above zero is estimated positive EV; multiply by 100 for the percentage.

How do you remove the vig from Pinnacle odds?

For proportional de-vigging, convert all mutually exclusive outcomes to qᵢ = 1 / oddsᵢ, add them to get the overround total T, then calculate pᵢ = qᵢ / T. The fair probabilities then sum to one. Model choice can matter on strongly lopsided markets.

Does PinnWire provide the price offered by another bookmaker?

No. PinnWire provides the real-time Pinnacle reference input. You or your application must supply a current offered price from your own authorized source, confirm exact contract equivalence, and handle any execution separately.

Why use PinnWire as a Pinnacle fair odds API?

PinnWire is purpose-built for real-time Pinnacle odds workflows. It provides live and prematch decimal prices through REST, detected drops with no-vig price nvp through REST and eligible-plan SSE, optional raw WebSocket updates, freshness fields, and a public demo key.

Does positive EV guarantee a winning bet?

No. EV is an estimate based on a probability model and a price available at a point in time. Individual bets can lose, prices can move, and mapping, settlement, fees, limits, or model error can remove an apparent edge.