Generalize the no-edge guard: suppress by the BOOK'S PRICE, not a stat whitelist
Follow-up to the rare-event under fix — the whitelist (doubles/triples/HR/SB) was fragile: the same juiced-under problem exists for steals, blocks, and any other low-frequency market, and a new stat would slip through. The real signal is the book's own price. The doubles unders were priced -625 to -1100 — laying 6-11x to win 1x on an ~82% event, with no value the model could recover. So the PRIMARY guard is now stat/sport-agnostic: analyzeViaEngine1 refuses any read whose graded-side odds are past the juice floor (JUICE_ODDS_FLOOR, default -400, env-tunable). That catches every version of this — steals, blocks, anything — and it also keeps the public record honest (those -800 "wins" hit ~82% of the time and would inflate the hit rate, the same class as the projection-0 degradation). The structural rare-event rules stay as the BACKUP for props with no odds (list also expanded cross-sport: + steals, blocks). Normal + longshot prices (-110, -250, +600) are preserved. 16 tests cover both layers. Reported: the doubles projection was REAL per-player (not a fallback); the fix is the price guard, not a bigger list. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,7 @@
|
||||
const { computeFeaturesForProp } = require('./computeFeatures');
|
||||
const engine1 = require('./engine1');
|
||||
const { toLegacyShape } = require('../../utils/gradeAdapter');
|
||||
const { isSuppressedRareUnder, isSuppressedRareOver } = require('../../config/rareEventMarkets');
|
||||
const { isSuppressedRareUnder, isSuppressedRareOver, isTooJuiced, gradedSideOdds } = require('../../config/rareEventMarkets');
|
||||
|
||||
// Map an error code from computeFeaturesForProp.meta.errors into a human
|
||||
// sentence the user will see in reasoning.summary.
|
||||
@@ -384,9 +384,18 @@ function buildIntelFields(features = {}, opts = {}) {
|
||||
}
|
||||
|
||||
async function analyzeViaEngine1(rawProp = {}) {
|
||||
// Betting-logic audit — suppress the juiced UNDER up front (no compute spent):
|
||||
// a 0.5-line under on a rare counting stat (doubles/triples/HR/SB) is never a
|
||||
// takeable edge. (The OVER is gated on the projection below.)
|
||||
// Betting-logic audit — the GENERAL no-edge guard (stat/sport-agnostic): the
|
||||
// book's own price is the truth. A side priced past the juice floor (e.g.
|
||||
// those doubles unders at -625 to -1100) has no takeable edge, so refuse it up
|
||||
// front. This is what makes the fix robust instead of a fragile whitelist.
|
||||
if (isTooJuiced(rawProp)) {
|
||||
return suppressedRareResult(rawProp, 'juiced_no_edge',
|
||||
`No read — the book prices this side at ${gradedSideOdds(rawProp)}; the vig has eaten any edge.`);
|
||||
}
|
||||
|
||||
// Backup for props with NO odds — the structural rare-event rule. Suppress the
|
||||
// juiced UNDER on a 0.5-line rare counting stat (doubles/triples/HR/SB/steals/
|
||||
// blocks). (The OVER is gated on the projection below.)
|
||||
if (isSuppressedRareUnder(rawProp.stat_type, rawProp.line, rawProp.direction)) {
|
||||
return suppressedRareResult(rawProp, 'rare_event_under',
|
||||
`No read — a ${rawProp.line} under on ${rawProp.stat_type} is a juiced rare-event market, not a takeable edge.`);
|
||||
|
||||
Reference in New Issue
Block a user