Sharp vs Soft Sportsbooks: What the Price Data Shows

What separates sharp and soft sportsbooks in the price data: margin, reaction speed and distance from the close. How to measure each from an odds feed and build a book scorecard.

"Sharp" and "soft" are the two labels everyone in sports odds uses and few people define precisely. A sharp book is usually described as one that prices tightly, moves quickly and is willing to take informed money; a soft book as one that caters to recreational customers, posts wider margins and follows the market rather than leading it. Much of that description concerns things an odds feed cannot see, such as who a book accepts bets from and how much. Some of it, though, is directly measurable in prices.

This guide looks at sharp versus soft from the data side only. It covers how PreciseAPI labels sources, the three properties you can measure from a feed (margin, reaction speed and distance from the close), how to compute each, and how to combine them into a scorecard. Nothing here describes any operator's internal policies; where operator behaviour comes up, treat it as general industry description rather than a statement about a specific book.

How sources are labelled

Every source in the catalog carries a type (sportsbook, exchange or dfs) and a list of tags. Four sportsbooks are tagged sharp:

SourceidTags
Pinnaclepinnaclesharp
Circacircaregulated, sharp
BookMakerbookmakeroffshore, sharp
Bet105bet105offshore, sharp

Other sportsbook tags describe what kind of operator a source is rather than how it prices: regulated (for example DraftKings, FanDuel and BetMGM), offshore, social, sweepstakes and crypto. Exchanges are tagged exchange and prediction markets prediction. Exchanges are not tagged sharp, but many teams treat liquid exchange prices as a reference in the same way; the exchange odds post covers that.

Tags come back from the free /v1/sportsbooks endpoint. It filters by type, not by tag, so select tags client-side:

curl "https://api.preciseapi.com/v1/sportsbooks?type=sportsbook" \
  -H "Authorization: Bearer $PRECISEAPI_KEY"
const API = 'https://api.preciseapi.com/v1';
const headers = { Authorization: `Bearer ${process.env.PRECISEAPI_KEY}` };
const get = async (path) => (await (await fetch(`${API}${path}`, { headers })).json()).data;

const books = await get('/sportsbooks?type=sportsbook');
const sharp = books.filter((b) => b.tags.includes('sharp')).map((b) => b.id);
// ['circa', 'pinnacle', 'bookmaker', 'bet105']

A tag is a starting point, not a ranking. It reflects a book's general reputation in the market for its main lines. Whether a particular book is sharp in a particular league, market type or period is an empirical question, and the rest of this post is about answering it with data.

Property 1: margin

The simplest difference between sharp and soft pricing is margin. A sharp book typically competes on price and posts tight two-way markets on its main lines; a recreational book typically posts wider ones. In the data that shows up as hold: sum the implied probabilities of every outcome in a market and compute 1 - 1 / sum.

The hold comparison post covers the full method, aggregation and dashboard, so here is only the part specific to this comparison: group by tag as well as by book.

// rows: [{ book, market, period, hold }] computed as in the hold post
const tagOf = new Map(books.map((b) => [b.id, b.tags.includes('sharp') ? 'sharp' : b.tags[0]]));
const median = (xs) => { const s = [...xs].sort((a, b) => a - b); return s[Math.floor(s.length / 2)]; };
const byTag = Map.groupBy(rows, (r) => `${tagOf.get(r.book)}|${r.market}|${r.period}`);
for (const [k, rs] of byTag) console.log(k, (median(rs.map((r) => r.hold)) * 100).toFixed(2) + '%', rs.length);

Two cautions. First, margin varies a lot by market: a book can be tight on full-game spreads and wide on first-half totals or props, so a single "hold" number per book hides most of the story. Second, low margin is not the same as accuracy. A book could post tight prices that sit in the wrong place. That is why margin is only the first of three measurements.

Property 2: reaction speed

The behaviour most associated with sharp books is that they move first. When information arrives, whether news or informed money, prices at market-leading books change, and other books follow over seconds or minutes. The steam move post builds a real-time detector on that pattern. For a scorecard, you want something simpler: for each book, how long after the reference moves does it catch up?

Measuring lag

  1. Build a reference fair probability per market from sharp-tagged books, de-vigged. The no-vig guide covers de-vigging and averaging several books.
  2. Detect reference moves: the reference changes by at least a threshold, say 2 percentage points, within a short window.
  3. For each other book, find the first time after the move that its own de-vigged probability crosses the halfway point between the old and new reference.
  4. The difference is that book's lag for that move. Take the median over many moves.

A worked example. At 18:00:00 the reference for Team A moves from 0.548 to 0.588 (from about -127 / +117 to -150 / +138). The halfway point is 0.568.

TimeBook X pricesBook X fair, Team AAbove 0.568?
17:58:00-140 / +1200.5620No
18:00:00-140 / +1200.5620No
18:04:30-155 / +1350.5882Yes

Book X's lag for this move is 4 minutes 30 seconds. For those four and a half minutes its Team A price of -140 was longer than the reference fair price of about -143: a stale price, in data-quality terms. Over hundreds of moves the median lag separates leaders from followers far more reliably than any single example.

Data sources for lag

/v1/history returns timestamped prices for main full-game moneyline, spread and total markets, one book and market per call at 3 credits. That is enough for a retrospective study on a sample of events. For continuous measurement, the WebSocket stream pushes every change as it happens and lets you timestamp moves yourself.

// history: { open, points: [{ t, line, prices: [home, away] }] } with format=decimal
function fairSeries(h) {
  return [h.open, ...h.points].map((p) => {
    const [a, b] = p.prices.map((x) => 1 / x);
    return { t: Date.parse(p.t), line: p.line, fair: a / (a + b) };
  });
}

function lagAfter(series, moveAt, halfway, direction) {
  const hit = series.find((p) => p.t >= moveAt && (direction > 0 ? p.fair >= halfway : p.fair <= halfway));
  return hit ? hit.t - moveAt : null; // null: never caught up in the window
}

A measurement caveat

Measured lag includes collection latency, not only the book's own reaction. Every source is collected on its own schedule, and the timestamp on a change is when the feed observed it. Differences of a few seconds between books are within that noise; differences of minutes are not. Report lag in coarse buckets and compare books against their own history rather than reading small gaps as precise measurements of operator behaviour.

Property 3: distance from the close

The third property is accuracy: how close a book's prices sit to the best available estimate of the true probability. The standard estimate is the de-vigged closing line from sharp sources, and the CLV post covers how to capture closes reliably.

For each market, take each book's de-vigged probability at a fixed time before the start, for example one hour, and compare it with the sharp consensus close:

BookFair prob at T-60 minSharp closeAbsolute difference
Book X0.5720.6002.8 points
Book Y0.5940.6000.6 points
Book Z0.6110.6001.1 points

These figures are illustrative. Averaged over many markets, the mean absolute difference is a clean accuracy score: books whose earlier prices consistently sit near the eventual close are the ones whose prices carry the most information. It also tests the tags. If a sharp-tagged book scores worse than a regulated book in some league or market type, that is worth knowing before you use it as a reference there.

A fourth view: who posts the best price

/v1/best-lines returns, for every comparable market, the best price per outcome and every book offering it in best_books. Counting how often each book appears gives its "best price share":

const events = await get('/best-lines?league=nba&markets=main&format=decimal');
const share = new Map();
let total = 0;
for (const e of events) for (const m of e.markets) for (const o of m.outcomes) {
  total++;
  for (const b of o.best_books) share.set(b, (share.get(b) ?? 0) + 1);
}

Read this one carefully. A book can lead the best-price count for two very different reasons: tight margins on both sides, or a stale price on one side after the market moved, as with Book X above. Best-price share alone cannot tell them apart. Combined with lag and hold, it can: a low-hold, low-lag book at the top of the share list is pricing tightly; a high-hold, high-lag book there is usually showing yesterday's price. The line shopping entry explains why best-price displays are standard on odds screens.

Building a book scorecard

Put the four measurements in one table per league and market type:

MetricSourceComputed as
Median hold/v1/odds snapshots1 - 1 / Σ implied, median per book
Median lag/v1/history or streamTime to cross halfway after a reference move
Mean distance from closeSnapshots + closing pricesMean absolute difference in fair probability
Best-price share/v1/best-linesShare of outcomes where the book is in best_books

Recompute on a rolling window, for example 30 days, and keep at least a few hundred markets per cell before drawing conclusions. Expect the results to vary by league and by market: a book can lead on NFL sides and follow on NBA props.

On cost, the snapshot side is cheap. /v1/odds costs 2 credits per call and /v1/best-lines 3, and each covers many events. History is the expensive part at 3 credits per market per call, so sample events rather than pulling every market. History is included on the sandbox, Starter, Pro and Enterprise plans; pricing has the details.

Choosing a reference set per league

The scorecard feeds back into the reference itself. Every measurement above compares books against a sharp consensus, and that consensus should be built from the sources that score best where you are measuring, not from a fixed list.

A practical loop:

  1. Start with the four sharp-tagged books as the reference for every league.
  2. Run the scorecard for a month. For each league and market type, check which books lead on lag and sit closest to the close.
  3. Narrow or widen the reference per league. A tagged book that rarely quotes a league, or quotes it late, adds noise there; a liquid exchange that leads on a market type may earn a place.
  4. Re-run on the next window and keep the reference set versioned, so historical comparisons always say which set they used.

Coverage is a real constraint. Not every source prices every league or every period, and /v1/sportsbooks reports the leagues each source currently covers in its leagues field. A reference that is empty for a league's first-half markets is worse than a slightly weaker one that is always present.

What the data cannot show

Several things commonly said about sharp and soft books are outside what any price feed can measure:

Keep scorecards to what the data supports, and label them that way.

Frequently asked questions

Which books are tagged sharp in PreciseAPI?

Pinnacle, Circa, BookMaker and Bet105. The tags come back on every source from GET /v1/sportsbooks, which costs no credits. Exchanges are tagged exchange and prediction markets prediction.

Can I filter the API by tag?

/v1/sportsbooks filters by type (sportsbook, exchange or dfs). Select tags client-side, then pass the resulting ids to books= (or bookmakers=) on the odds endpoints.

Does PreciseAPI publish hold or reaction-speed figures per book?

No. The API returns each source's posted prices, so you compute hold, lag and distance from the close with your own definitions and windows. That keeps the methodology visible and lets you segment by league and market.

Is a soft book's price wrong?

Not necessarily. A wider margin or a slower update is a property of how the price is constructed and when it changes. Measured against a sharp close, some soft-book prices sit closer than others; the distance-from-close metric tells you which.

Can price data show which books limit customers?

No. Limits and account policies are not in any odds feed. Any claim about them should come from the operator, not from price analysis.

Should exchanges be part of the sharp reference?

Many teams include liquid exchange prices, weighted by available liquidity and adjusted for fees. Thin exchange prices can be noisy, so apply a liquidity floor before treating them as a reference.