You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Phase 1 (#5238/#5243) proved resolvability: bet_engine resolves at 0% VOID vs 74% for the legacy detector (Gate 1 passed, live 07-23). But every resolved bet carries the flat 0.4 placeholder prior — Brier 0.26 is the coin-flip baseline, not skill. Phase 2 replaces the placeholder with a calibrated 3-pass LLM-ensemble probability and proves it (Gate 2) on two new domains. Supersedes the #4930 "Bet 4 ensemble" line item.
Full implementation-ready plan (review-hardened by a 5-persona doc review that caught 1 P0 + 6 P1s pre-code) lives at docs/plans/2026-07-23-001-feat-forecast-reengine-phase2-ensemble-calibration-plan.md (local worktree artifact — docs/plans/ is gitignored; this issue carries the substance).
Why these domains
Gate-1 data showed the pilot domains (commodity/EIA prices) are efficient-market — a calibrated forecaster lands ~0.5 and cannot demonstrate skill there. So:
Prediction-markets = the market-anchored slice: real questions with a market price to compare against (vsMarketSkill).
FRED macro/rates = the independence slice: no market price exists to copy, so it is the structurally credible proof the ensemble is derived, not parroted.
Key decisions (validated against the codebase)
KTD2 — the markets bootstrap feed cannot resolve bets.seed-prediction-markets.mjs only publishes open markets (closed:'false', end_date_min: now) and _prediction-scoring.mjs clips yesPrice to [10,90] — a settled ~0/100 price never appears in prediction:markets-bootstrap:v1. Resolution therefore uses a dedicated settlement path: bets store the market slug at generation; a resolver-side loader queries Gamma closed:true by slug into prediction:markets-resolution:v1 (mirrors the acled-resolution precedent); bets pend (not VOID) from endDate until settlement lands.
KTD3 — anti-parroting is a hard gate. A probability = marketPrice copy passes naive "Brier ≤ market" + slope→1 trivially. Gate 2 requires outcome-conditioned deviation skill: on bets where |ensemble − market| exceeds a band, corr(sign(ensemble − market), outcome − market) > 0. Plus the FRED slice must beat its recorded base-rate baseline.
KTD4 — FRED grace derived from the observation calendar. Monthly FRED obs are dated the 1st of the reference month and publish ~2wk after the month ends ⇒ ~45–70d past a mid-month deadline. Grace ≥ ~75d for monthly series (or snap deadlines to obs boundaries + ~45d); DGS10 short. Exact keys: economic:fred:v1:{FEDFUNDS,UNRATE,CPIAUCSL,DGS10}:0 (allowlist is exact-match).
KTD5 — three baselines persisted end-to-end: ensemble, base-rate (baselineProbability), market (calibration.marketPrice). The resolver's createEntry/updateOpenWindow whitelist must pass through baselineProbability/probabilitySource/passes, and updateOpenWindow must never overwrite an ensemble probability with a later base-rate.
KTD6 — per-origin measurement must be built: existing calibrationBuckets/vsMarketSkill are pooled across origins; Gate 2 needs bet_engine-sliced versions + a promotion flag (default off).
KTD1 — per-pass probabilities persisted (trimmed-mean-of-3 is the median; persisting passes makes aggregation falsifiable at Gate 2 post-hoc).
Work units (continue the re-engine sequence; parent plan used U1–U9)
U14 per-origin Gate-2 measurement + promotion flag — sliced calibration/vsMarketSkill, base-rate comparison, deviation-skill statistic; flag is the only promotion path.
U15 staged deploy — Stage A (Gate 1.5): templates with base-rate only; measure the live feed's bet-generation rate (markets passing the floor with endDate ≤ 45d) and derive the Gate-2 calendar from it; verify both slices pend→resolve, VOID ≈ 0. Stage B: provision LLM env vars on seed-forecast-bets (callForecastLLM silently skips missing-key providers), enable the ensemble flag, first-run cost + probability-distribution check, then accrue.
Gate 2 (Definition of Done)
Calibration slope → 1 on the market slice (per-origin buckets)
Ensemble Brier ≤ market Brier and positive deviation skill (market-copy fails this pair by construction)
FRED slice: slope → 1 and ensemble Brier < recorded base-rate baseline
VOID not worsened (market bets pend→settle; FRED pends through the grace)
Measured LLM cost within budget; distribution check before unattended accrual
No false confidence on efficient-market bets (~0.5, honest)
Everything merged + deployed + accruing; three baselines persisted; promotion flag flipped only when 1–6 hold
Deferred
Bet 3 post-hoc recalibration (isotonic/Platt) · Phase 3 fan-out · user-facing publication (Bet 5) · alternative aggregations (gradeable from persisted passes).
Epic: forecast re-engine Phase 2 — calibrated probabilities
Phase 1 (#5238/#5243) proved resolvability:
bet_engineresolves at 0% VOID vs 74% for the legacy detector (Gate 1 passed, live 07-23). But every resolved bet carries the flat0.4placeholder prior — Brier 0.26 is the coin-flip baseline, not skill. Phase 2 replaces the placeholder with a calibrated 3-pass LLM-ensemble probability and proves it (Gate 2) on two new domains. Supersedes the #4930 "Bet 4 ensemble" line item.Why these domains
Gate-1 data showed the pilot domains (commodity/EIA prices) are efficient-market — a calibrated forecaster lands ~0.5 and cannot demonstrate skill there. So:
vsMarketSkill).Key decisions (validated against the codebase)
seed-prediction-markets.mjsonly publishes open markets (closed:'false',end_date_min: now) and_prediction-scoring.mjsclips yesPrice to [10,90] — a settled ~0/100 price never appears inprediction:markets-bootstrap:v1. Resolution therefore uses a dedicated settlement path: bets store the market slug at generation; a resolver-side loader queries Gammaclosed:trueby slug intoprediction:markets-resolution:v1(mirrors theacled-resolutionprecedent); bets pend (not VOID) from endDate until settlement lands.probability = marketPricecopy passes naive "Brier ≤ market" + slope→1 trivially. Gate 2 requires outcome-conditioned deviation skill: on bets where |ensemble − market| exceeds a band,corr(sign(ensemble − market), outcome − market) > 0. Plus the FRED slice must beat its recorded base-rate baseline.economic:fred:v1:{FEDFUNDS,UNRATE,CPIAUCSL,DGS10}:0(allowlist is exact-match).baselineProbability), market (calibration.marketPrice). The resolver'screateEntry/updateOpenWindowwhitelist must pass throughbaselineProbability/probabilitySource/passes, andupdateOpenWindowmust never overwrite an ensemble probability with a later base-rate.calibrationBuckets/vsMarketSkillare pooled across origins; Gate 2 needsbet_engine-sliced versions + a promotion flag (default off).passesmakes aggregation falsifiable at Gate 2 post-hoc).Work units (continue the re-engine sequence; parent plan used U1–U9)
scripts/_forecast-ensemble.mjs— 3 diverse passes (outside-view / inside-view / adversarial refuter) via injectedcallForecastLLM; per-pass 35s budget, no retries (createLiveJudgeModelspattern); stabilized cache digest; all-fail → base-rate (never 0.5); returns{probability, rationale, passes}.scripts/_bet-templates-markets.mjs+ settlement loader — liquid markets only (volume floor, endDate within horizon); slug stored; spec targetsprediction:markets-resolution:v1|yesPrice(market==title); pend→settle→resolve chain tested end-to-end.scripts/_bet-templates-macro.mjs+ FRED enablement —{series:{observations}}unwrap (fix(forecast): FRED macro/rates signals silently dead — shape mismatch kills all 10 (P1, #5097) #5098), exact:0keys, calendar-derived grace with a worst-case-alignment pend/VOID test.probabilitySourcetag, ingest pass-through; never touchesforecast:predictions:v2.seed-forecast-bets(callForecastLLM silently skips missing-key providers), enable the ensemble flag, first-run cost + probability-distribution check, then accrue.Gate 2 (Definition of Done)
Deferred
Bet 3 post-hoc recalibration (isotonic/Platt) · Phase 3 fan-out · user-facing publication (Bet 5) · alternative aggregations (gradeable from persisted
passes).Refs #4930 (epic — Bet 4), #5233 (re-engine), #5523/#5524 (settlement-grace precedent), #5098 (FRED shape). Parent: PRs #5237, #5238, #5243, #5327.