Skip to content

feat(forecast): Phase 2 re-engine — LLM ensemble + calibration on prediction-markets + FRED (#4930 Bet 4) #5525

Description

@koala73

Epic: forecast re-engine Phase 2 — calibrated probabilities

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)

  1. 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.
  2. 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.
  3. 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).
  4. 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.
  5. 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).
  6. 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)

  • U10 scripts/_forecast-ensemble.mjs — 3 diverse passes (outside-view / inside-view / adversarial refuter) via injected callForecastLLM; per-pass 35s budget, no retries (createLiveJudgeModels pattern); stabilized cache digest; all-fail → base-rate (never 0.5); returns {probability, rationale, passes}.
  • U11 scripts/_bet-templates-markets.mjs + settlement loader — liquid markets only (volume floor, endDate within horizon); slug stored; spec targets prediction:markets-resolution:v1|yesPrice(market==title); pend→settle→resolve chain tested end-to-end.
  • U12 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 :0 keys, calendar-derived grace with a worst-case-alignment pend/VOID test.
  • U13 seeder + ingest wiring — top-K = 6 (env-tunable), ~120s ensemble deadline, open-window skip (no re-scoring), probabilitySource tag, ingest pass-through; never touches forecast:predictions:v2.
  • 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)

  1. Calibration slope → 1 on the market slice (per-origin buckets)
  2. Ensemble Brier ≤ market Brier and positive deviation skill (market-copy fails this pair by construction)
  3. FRED slice: slope → 1 and ensemble Brier < recorded base-rate baseline
  4. VOID not worsened (market bets pend→settle; FRED pends through the grace)
  5. Measured LLM cost within budget; distribution check before unattended accrual
  6. No false confidence on efficient-market bets (~0.5, honest)
  7. 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).

Refs #4930 (epic — Bet 4), #5233 (re-engine), #5523/#5524 (settlement-grace precedent), #5098 (FRED shape). Parent: PRs #5237, #5238, #5243, #5327.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions