Lakeshore Edge Create Free Account
Methodology

How the model actually works.

Here's how the model works, step by step. There are four parts: it predicts a probability for each pick, logs every prediction, learns from how those predictions actually did, then checks itself against the closing line — the sharpest test of whether a pick was really good. The logging part is what most paid services skip.

Current state, live: Avg CLV+0.4pt Beat close43% Picks graded738

On this page
  1. The probability model
  2. From probability to pick
  3. The journal
  4. The calibration loop
  5. Closing-line value, the only metric that matters
  6. Honest limits
  7. Stack and operations

01. The probability model

For every game, the model puts a number on each side — "this team has a 57% chance to win." Here's where that number comes from.

Every market gets a model probability for each side. The recipe varies a bit per sport, but the spine is the same: a sport-specific Elo rating, blended with recent-form signal, then nudged by a handful of structural features.

Elo plus form

The base team rating is an Elo number maintained per sport, with K factors and home-field constants tuned per league. On top of that, a last-10-games form score adjusts the rating for hot or cold streaks. The blend weights are league-specific: MLB leans more on Elo because last-10 is only 6% of a season, NFL will lean more on last-10 because last-10 is closer to 60% of the season (rolling out later this year).

Sport-specific features

Layered on top of the team rating, the model pulls in features that matter for the specific sport:

Sharp-money signals (all sports)

The model watches the market itself, not just the game. Five separate signals look at how the line is moving and where the smart money is sitting. They feed the verdict tier and surface inline on the picks:

From probability to edge

Sportsbook prices are converted to implied probabilities then de-vigged (the overround is stripped so the two sides sum to 1.0). The edge for a pick is model_p - market_p. Anything above the threshold becomes a recommendation, tiered by magnitude — internally HERO / STRONG BET / BOSS PICK / GOOD BET for positive picks and MARGINAL / PASS for the rejected ones. User-facing surfaces collapse those six into three labels: Top Pick, Small Play, and Pass. The full granularity stays visible on the /model audit page.

Two distinct mechanisms stop a runaway feature from blowing up stake size. First, a hard clamp on the raw model probability: 80% globally (NBA, NFL), tightened to 72% for MLB, since no pre-game baseball matchup realistically clears that given the sport's variance. MMA is the exception and runs the other way: heavy UFC favorites genuinely win at a high clip (priced 0.90+ win 98% historically), so MMA is exempt from the 80% clamp and capped high at 93% instead. The clamped number is what you see. Second, a verdict-demote ceiling: when a pick's probability exceeds a per-sport threshold (NBA 78%, MLB 75%, MMA 92%, and 85% by default), the verdict drops one rung (HERO → STRONG BET, STRONG BET → GOOD BET) so the stake recommendation shrinks — the probability is left as-is, only the sizing is pulled back. For NBA / MLB these thresholds sit where picks historically underperformed their own predicted probability; MMA's is raised to 92% because the opposite is true there (the market, and so the model, has historically under-rated favorites, not over-rated them).


02. From probability to pick

Every game, every side, every market gets a model probability. Almost none of them become a pick we publish. Here's the filter.

Knowing a team has, say, a 54% chance to win isn't the same as saying "bet on them." A pick has to clear several gates first. The model considers every game on the slate four ways (home moneyline, away moneyline, home spread, away spread), so a twelve-game MLB night produces roughly 48 candidate rows. Most of them never make it to Track Record.

Edge, not probability, is the headline

For each side we compute edge = model_p − market_p, where market_p is FanDuel's implied probability after we strip the vig. A 60% model number on a side the market also prices at 60% means we agree with the book — no value, no pick. The same 60% on a side the market prices at 53% is a +7pp edge — that's the kind of disagreement we publish.

Verdict tiers

Every surviving row gets one of these labels:

Only STRONG BET and GOOD BET get written to the journal. Everything below that tier is informational — you'll see it on the dashboard but it never becomes a tracked pick or contributes to P/L. The reason is honesty: a 1.5pp edge isn't meaningfully different from "the model and market agree," and treating it as a recommendation would dilute the track record with picks the model itself doesn't really believe in.

Safety filters

A surviving row can still get rejected before it lands as a pick. Each of these has a story behind it — usually a specific failure the model walked into early on:

What this means for Track Record

Every row on /track-record is a pick that:

  1. Cleared the 2pp edge floor at FanDuel,
  2. Survived every safety filter above,
  3. Was published to the journal as STRONG BET or GOOD BET,
  4. Belonged to a game ESPN later returned a final score for.

We don't show every game the model considered — that'd be the universe, not the track record. We show every pick the model actually recommended. The math is honest because the filter is automatic: a 2pp threshold and a verdict label are code, not editorial judgment.


03. The journal

Every pick gets saved to disk the moment it's published — not just the pick, but every input that produced it. So later we can ask: was the pick good?

Every recommendation gets snapshotted at publish time. Not just the pick and the price, but every input that fed it. When the game settles, the entry auto-grades and gets a closing-line snapshot. That's the loop.

What's captured

Why this matters

Without saved inputs, you can't fairly test "what if I'd weighted X half as much" — by the time you'd want to retest, the inputs (today's pitcher ERAs, etc.) have moved on. Saving the snapshot at publish time fixes that, so the model can be backtested against the exact numbers it actually saw when it made the call.

On top of that backtest substrate, an ablation framework (added 2026-05-24) re-runs the same live games through the model with specific feature buckets disabled — full, no-injuries, no-lines, no-consensus, elo-only — and writes a parallel journal for each variant. Once each variant has accumulated ≥50 graded picks, we can attribute the model's hit-rate and CLV to specific features instead of arguing on vibes. The runner is tools/run_variants.py; the comparison panel on /model follows once the data is in.

Operational note

Auto-grading runs against ESPN scoreboards and the spread is computed from the leg's stored point at publish time. Pushes void. Anything the auto-grader can't match (ESPN doesn't list the game, team-name fuzz miss, ambiguous side) stays in the journal as open rather than silently dropping. The tools/diagnose_stale.py diagnostic classifies each stuck row with its likely cause for manual review.


04. The calibration loop

Every night the model grades itself. If it's been calling 60% too often when reality says 50%, tomorrow's predictions get nudged down.

A nightly job replays the journal and computes calibration statistics per sport, per market type, and per verdict tier. The output is a set of offsets that get applied to tomorrow's predictions.

Sport-by-sport correction

If MLB moneyline favorites have hit at 45% but the model predicted 56% over a 200-pick sample, the calibration gap is -11 points. The next morning, MLB moneyline predictions get -11 points subtracted before they become edges. The bias is reactive, not assumed.

Offsets are capped, and the cap is sample-size dependent. A noisy 20-pick gap of -33 points shouldn't propagate the full -33 into tomorrow's predictions; it should propagate a fraction.

Sample sizeMax offsetEffect
n ≥ 50±25 ppLarge sample, full structural bias allowed
n ≥ 20±18 ppSolid sample, moderate bias allowed
n ≥ 8±10 ppMinimum sample, conservative
n < 80 ppNo correction, ride at neutral

"pp" = percentage points on the probability scale; ±25 pp means a calibration offset of up to ±0.25 in probability space (e.g. 60% nudged to 35% or 85%). The caps above govern per-(sport, market) offsets — the fine-grained correction. A separate sport-only offset (broader bucket, no market split) runs alongside it and caps at 6pp under n=50 and 12pp above; that's what the "Sport-offset cap is currently 12pt" hint on the /model NBA panel refers to.

Per-verdict shrink

If a verdict tier (internally STRONG BET, surfaced to users as Top Pick) has gone 4-and-10 on a recent sample, that tier is overpromising. Next day's predictions in that tier get their probability pulled toward 0.5 proportional to the calibration gap. Shrink only applies when the tier is underperforming (gap < 0), not when it's outperforming.

shrink = min(0.5, abs(calibration_gap) * 4.0)

calibration_gap is on the decimal scale (0.10 = 10pp), so a -12pt gap evaluates to min(0.5, 0.12 × 4) = 0.48, just under the 0.5 ceiling. Any gap past ~12.5pt clamps to the full 0.5 cap, so a single bad tier can never be shrunk more than halfway to a coin flip.

Per-confidence-band shrink

Per-verdict shrink corrects whole tiers. But a tier can be well-calibrated on average while a specific confidence band inside it leaks. The 65-74% band is the classic offender: a May 2026 audit found picks the model called ~69% were hitting ~52% on n=92, a -17pt gap that survived the sport offset, the verdict shrink, and the Platt fit. So a final, narrower correction runs last: bucket every resolved single into probability bands (0.25-0.35, 0.35-0.45, ... , 0.75-1.00) and shrink each band on its own measured gap.

shrink = min(0.40, abs(band_gap) * 3.0)

A band only gets a non-zero shrink when it has n ≥ 20 resolved picks AND is overconfident by more than 3pt (positive gaps and near-perfect bands are left alone, so the loop never disturbs what's working). The cap is 0.40, tighter than the per-verdict 0.5, because band gaps sit on smaller samples. This is the last calibration step before the verdict ceiling.

Verdict ceiling

We cap the model at 85% confidence on any single pick — empirically, anything above that turns out overconfident more often than sharp, and the higher the recommended stake, the worse the asymmetry. When raw model probability exceeds 0.85, the verdict demotes one tier (HERO becomes STRONG BET, STRONG BET becomes GOOD BET).

What this isn't

The calibration loop doesn't change the model's signal. It scales and shifts the model's outputs based on how reliably those outputs have predicted reality. If the model picks well but claims too large an edge, the loop tightens the claim without throwing out the pick.

How well-calibrated is the model? Brier and the gap

Two scalars surfaced on the Model page measure calibration directly:

The two metrics catch different failure modes. A model can have perfect calibration gap (predictions average out to the hit rate) but a terrible Brier score (every individual prediction is wrong in opposite directions). The Model page reports both side-by-side so neither can hide.


05. Closing-line value

The sharpest measure of a real edge: did we get a better price than the market settled on?

The only sports-betting metric that actually predicts long-run profitability. CLV is the difference between the price you got and the price the market closed at. If you bought a team at +150 and the line closed at +130, that's positive CLV: the market moved toward your side, which means you got a sharper price than the consensus.

Hit rate is noisy in small samples. CLV is not. A bettor with consistent +CLV will, mathematically, win over a large sample because they're consistently buying mispriced assets. A bettor with -CLV can hit 60% short-term and still be on a path to ruin.

How it's measured here

Why Pinnacle is primary

Pinnacle is the global low-margin sportsbook used by professional bettors. They run on roughly half the vig of FanDuel, never limit winning players, and post some of the sharpest lines in the industry. Sharp shops measure CLV against Pinnacle's close because anything else is just measuring against another softer line. We can't bet at Pinnacle from the US, but that's not the point: we use Pinnacle's close as the fair-line reference and bet at FanDuel, DraftKings, or BetMGM (whichever has the best execution price).

Caveat I'm honest about

Pinnacle CLV is forward-only — Pinnacle snapshots started landing in the journal on 2026-05-23, so picks settled before then have only a FanDuel-close CLV stamped, not a Pinnacle one. The Pinnacle sample grows each day and promotes to the headline on /model once n ≥ 30. Until then, the FanDuel-close number is shown first (less sharp but it's all we have history on).

Separate from the broader closing-line snapshot fix on 2026-05-18: that one patched the loop that was evicting pre-game ticks before the grader could read them, which retroactively un-stamped a handful of bad CLV outliers but didn't add Pinnacle as a benchmark. The /model provenance note tracks that fix separately.


06. Honest limits

Where the model is still wrong, in plain English.

Overconfidence on magnitude

Hit rate sits around 42.9%, calibration gap around -5.5pt. The model picks sharp (positive CLV), but it claims a bigger edge than the close ratifies. Calibration loop is the fix; it's working but not done.

NBA hit rate dip

NBA is currently running 60.0% on n=5. Open question whether it's longshot bias (model loves dogs that don't cover) or coinflip bias (a wash of 50/50 calls that lose on price). There's a diagnostic tool for that; haven't run it through to a fix yet.

Football is offseason

NFL and NCAAF aren't calibrated yet. There's no journal data to fit against. They're queued for late August 2026 when training camps wrap and Week 1 lines post. Until then, those sports are not surfaced as picks.

Variant ablation hasn't shipped yet

Long-term, I want to run five model variants in parallel (full, no-Polymarket, no-injuries, no-line-movement, Elo-only) and compare them on the same slate. That's the cleanest way to attribute edge to features. It's in the plan, not in production.


07. Stack and operations

What's running this thing, for the curious.

The numbers cited throughout this page are live as of today, pulled from the same journal that grades every pick. The full audit, with calibration plots, per-pick waterfalls, and CLV by sport, is on /model.