'use strict'; /** * Quarter-Kelly sizing (Session 62 / A1-S1 — the pricing page promised it; * now it exists). Pure math, no I/O. * * DATA SEMANTICS: Kelly needs a REAL win probability and REAL book odds. * p comes from the engine's quantile probability estimator (game-log based); * odds come from the captured book row. Either missing → null — a sizing * recommendation is never fabricated from confidence scores or defaults. */ function americanToDecimal(american) { const a = Number(american); if (!Number.isFinite(a) || a === 0) return null; return a > 0 ? 1 + a / 100 : 1 + 100 / Math.abs(a); } /** * quarterKelly(p, americanOdds) → * { full, quarter, pct } — fractions of bankroll (pct = quarter × 100, * rounded to 0.1) — or null when p/odds are unusable or the edge is * non-positive (Kelly says: no bet). */ function quarterKelly(p, americanOdds) { const prob = Number(p); if (!Number.isFinite(prob) || prob <= 0 || prob >= 1) return null; const dec = americanToDecimal(americanOdds); if (dec == null || dec <= 1) return null; const b = dec - 1; const full = (b * prob - (1 - prob)) / b; if (!Number.isFinite(full) || full <= 0) return null; const quarter = full / 4; return { full: Math.round(full * 1000) / 1000, quarter: Math.round(quarter * 1000) / 1000, pct: Math.round(quarter * 1000) / 10, // % of bankroll at quarter-Kelly }; } module.exports = { quarterKelly, americanToDecimal };