Suppress rare-event 0.5 unders (juiced, no-edge) — config-driven grade + board fix
Betting-logic audit: the CONSENSUS-vs-MODEL board flooded with fake reads like "DOUBLES u0.5 · MODEL 0.2 · +edge" — the juiced under side of rare counting-stat markets (doubles/triples/HR/SB), which is never a takeable edge and violates the no-unders-default doctrine. Report finding (item 3/4): the doubles projection is REAL per-player, not a flat fallback — 'doubles' maps to a real game-log field (MLB_LOG_FIELD doubles→ doubles) and the live values varied (0.03/0.16/0.2/0.22). So no projection-gate refusal for fakeness; the problem is purely structural (a rare event's real projection always sits below a 0.5 line, so the under always "wins"). Fix (config-driven — src/config/rareEventMarkets.js, tunable stat list + line threshold): - Grade layer (analyzeViaEngine1): a rare-event UNDER at ≤0.5 is always REFUSED (grade null + suppressed flag/reason). A rare-event OVER at ≤0.5 is refused UNLESS the model genuinely projects the event above the line — because a 0.2-over-0.5 carries the SAME |edge| as the suppressed under and would just take its rank on the board. The over grades normally once projection > line. - Board layer (marketBreadth.collectBreadth): drops null-model rows so a suppressed/ungraded prop can't rank a "MODEL —" placeholder onto the board. 10 suppression tests + config locks; also fixed a settingsPage book assertion left over from the ESPN→theScore swap. Suite 274/3289 green, web build exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Rare-event markets (betting-logic audit, 2026-07-19).
|
||||
*
|
||||
* On a 0.5-line rare counting stat — doubles, triples, home runs, stolen bases —
|
||||
* the UNDER is the juiced side the book wants action on: the event just usually
|
||||
* doesn't happen, so a real projection of (say) 0.2 always "favors" under 0.5,
|
||||
* but there's no takeable edge (the book prices it -250+). Surfacing those
|
||||
* unders floods the board with fake B-grade "under 0.5" reads and violates the
|
||||
* standing no-unders-default doctrine.
|
||||
*
|
||||
* So we SUPPRESS the under side on these markets at/below the line threshold —
|
||||
* it is not graded. The OVER side still grades normally and only surfaces when
|
||||
* the model genuinely projects the event above the line.
|
||||
*
|
||||
* Config-driven (a stat list + a line threshold) so it's tunable without
|
||||
* touching grade logic. CommonJS so it's unit-testable + requireable everywhere.
|
||||
*/
|
||||
|
||||
// Low-frequency counting stats where a 0.5 under is structurally a bad bet.
|
||||
const RARE_EVENT_STATS = ['doubles', 'triples', 'home_runs', 'stolen_bases'];
|
||||
|
||||
// The under is suppressed only at/below this line (0.5 is the juiced rare line;
|
||||
// a 1.5+ line is a different market where an under can be a real read).
|
||||
const RARE_EVENT_LINE_MAX = 0.5;
|
||||
|
||||
function isRareEventStat(statType) {
|
||||
return RARE_EVENT_STATS.includes(String(statType || '').toLowerCase());
|
||||
}
|
||||
|
||||
/** True when this prop is a rare-event UNDER at/below the line threshold —
|
||||
* the juiced side, always refused. Needs no projection. */
|
||||
function isSuppressedRareUnder(statType, line, direction) {
|
||||
const dir = String(direction || '').toLowerCase();
|
||||
const ln = Number(line);
|
||||
return dir === 'under'
|
||||
&& isRareEventStat(statType)
|
||||
&& Number.isFinite(ln)
|
||||
&& ln <= RARE_EVENT_LINE_MAX;
|
||||
}
|
||||
|
||||
/** True when this prop is a rare-event OVER at/below the threshold that the
|
||||
* model does NOT genuinely project (projection <= line). Refusing it is what
|
||||
* keeps the board clean — a 0.2-projection over 0.5 carries the SAME |edge| as
|
||||
* the suppressed under, so if we let it grade it just takes the under's place.
|
||||
* The over is a real read only when the model projects ABOVE the line. */
|
||||
function isSuppressedRareOver(statType, line, direction, projection) {
|
||||
const dir = String(direction || '').toLowerCase();
|
||||
if (dir !== 'over') return false;
|
||||
const ln = Number(line);
|
||||
const proj = Number(projection);
|
||||
return isRareEventStat(statType)
|
||||
&& Number.isFinite(ln)
|
||||
&& ln <= RARE_EVENT_LINE_MAX
|
||||
&& !(Number.isFinite(proj) && proj > ln);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
RARE_EVENT_STATS,
|
||||
RARE_EVENT_LINE_MAX,
|
||||
isRareEventStat,
|
||||
isSuppressedRareUnder,
|
||||
isSuppressedRareOver,
|
||||
};
|
||||
Reference in New Issue
Block a user