d887664fce
- docs/V3-GIGA-PLAN.md: the v3 plan (data lake to 7+GB, GIGA ensemble, scoreboard, UI clarity) with the honest reframe: perfect accuracy = perfect calibration; 'beat the betting sites' = a live public head-to-head, scored with proper rules. - src/lib/odds.ts: American-ml → decimal, de-vig (normalize away the margin), overround; 3 vitest cases incl. tonight's real opening lines. - ESPN core odds adapter (server/src/ingest/espnOdds.ts): captures DraftKings 1X2 moneylines + O/U + spread per fixture; follows $ref pointer items. - odds_history table (insert-if-changed → clean line-movement history); scheduler odds sweep every 3h over a 14-day horizon, first sweep at boot; /api/odds + /api/odds/:num. Odds are a benchmark ONLY — never a model input. - Boot speed-up: fixture↔event mapping skips dates already fully mapped. - Verified: 54 fixtures captured with real moneylines locally. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
33 lines
1.5 KiB
TypeScript
33 lines
1.5 KiB
TypeScript
// Bookmaker-odds math, shared by the server (capture) and the client
|
|
// (Model-vs-Market scoreboard). Odds are a BENCHMARK only — never a model input.
|
|
|
|
import type { MatchProbs } from './types';
|
|
|
|
/** American moneyline → decimal odds (e.g. -235 → 1.4255, +340 → 4.40). */
|
|
export function americanToDecimal(ml: number): number {
|
|
if (!Number.isFinite(ml) || ml === 0) return NaN;
|
|
return ml < 0 ? 1 + 100 / Math.abs(ml) : 1 + ml / 100;
|
|
}
|
|
|
|
/**
|
|
* De-vig a 1X2 set of American moneylines into fair probabilities: convert to
|
|
* implied probabilities (1/decimal) and normalize away the bookmaker margin.
|
|
* Returns null if any line is missing/invalid.
|
|
*/
|
|
export function deVig(homeMl: number | null, drawMl: number | null, awayMl: number | null): MatchProbs | null {
|
|
if (homeMl == null || drawMl == null || awayMl == null) return null;
|
|
const dh = americanToDecimal(homeMl);
|
|
const dd = americanToDecimal(drawMl);
|
|
const da = americanToDecimal(awayMl);
|
|
if (!Number.isFinite(dh) || !Number.isFinite(dd) || !Number.isFinite(da)) return null;
|
|
const ih = 1 / dh, id = 1 / dd, ia = 1 / da;
|
|
const total = ih + id + ia;
|
|
if (total <= 0) return null;
|
|
return { home: ih / total, draw: id / total, away: ia / total };
|
|
}
|
|
|
|
/** Bookmaker overround (margin) for display: e.g. 1.052 → "5.2%". */
|
|
export function overround(homeMl: number, drawMl: number, awayMl: number): number {
|
|
return 1 / americanToDecimal(homeMl) + 1 / americanToDecimal(drawMl) + 1 / americanToDecimal(awayMl);
|
|
}
|