feat(forecast): fast-resolving commodity price bets — Gate-1 accelerator (#5233)#5243
Conversation
…or, #5233) The EIA energy bets resolve weekly (deadline 07-19). This adds a fast lane on the daily-updating commodities feed so bet_engine evidence lands within the week (~4-day horizon → 07-16) and proves the engine generalizes to a 2nd feed. - _bet-templates-commodities.mjs: crude (CL=F/BZ=F), nat-gas (NG=F), gold (GC=F) bets over market:commodities-bootstrap:v1. Directional (from the daily change), 2.5% threshold, 2-dp prices, resolvable via the existing price(symbol==X) fn. - seed-forecast-resolutions.mjs: shapeResolutionFeed now exposes the nested {data:{quotes:[...]}} array — the eval's iterateRecords only descends into array children, so the doubly-nested quotes were unreadable. This ALSO unblocks the pre-existing market commodity-price forecast resolution path. - seed-forecast-bets.mjs: reads both feeds, emits both families. Commodity bets use the honest thin-history prior (0.4) until their own series accrues. Prod preview: 8 bets total (4 energy @07-19, 4 commodity @07-16), all crisp + resolvable, e.g. "Will the WTI crude oil price fall to at most 69.62 USD/bbl by 2026-07-16?". 8 new tests (templates, shaper, end-to-end YES/NO/pending); 870/870 forecast+bet green; api tsc exit 0. Refs #5233, #4930 Claude-Session: https://claude.ai/code/session_01StNurp4TGC3JLHbTtKJhbp
Strix Security ReviewNo security issues found. Updated for Reviewed by Strix |
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
| kind: 'hard', | ||
| metricKey: `${COMMODITY_FEED}|price(symbol==${symbol})`, | ||
| operator: 'crosses', |
There was a problem hiding this comment.
Zero
change always generates a "rising" bet
wantUp = changePct >= 0, so when change is absent from the feed (mapped to 0 by extractMetric) or exactly 0.0 (a flat day), the template always mints a "rise to at least" bet. A missing or zero daily-change field is a plausible feed state — Yahoo Finance futures data occasionally omits change on non-trading sessions — and the resulting bias silently skews the Gate-1 evidence pool toward bullish direction for that day's run. Consider using a null sentinel to skip bet generation when the daily direction is undetermined rather than defaulting to up.
…d (PR #5243 review) Greptile: wantUp = changePct >= 0 defaulted an absent or exactly-flat `change` to a "rise" bet, biasing the shadow pool bullish on non-trading sessions (Yahoo omits `change` then). Now extractMetric keeps a null sentinel for absent change, and buildResolutionSpec emits no bet when direction is undetermined (null or 0) — no silent bullish default. Test: absent/flat change → 0 bets. 9 commodity tests, 871/871 forecast+bet green; api tsc exit 0. Refs #5233, #5243 Claude-Session: https://claude.ai/code/session_01StNurp4TGC3JLHbTtKJhbp
…eeder logs (PR #5243 review) P2 (correctness) — stale kept-warm quote could resolve as a deadline price. The commodities feed uses extendExistingTtl on fetch failure, preserving the OLD _seed.fetchedAt, but shapeResolutionFeed discarded it — so a quote fetched days before the deadline scored a false YES/NO (reproduced). Now shapeResolutionFeed stamps each quote with the envelope fetchedAt as `asOf`, and the eval's settlement gate (previously value-only) also covers the `price` fn: a quote dated before the deadline day pends, and VOIDs after the 10d grace. This also hardens the pre-existing market commodity-price forecast path. P3 (ops) — the combined seeder logged "shadow energy bet(s)" / "no energy bets" even on commodity-only runs. Now family-neutral with a per-domain breakdown (e.g. "published 8 shadow bet(s) [energy:4, market:4]"). Tests: stale-envelope pending + VOID-after-grace regressions, asOf stamping. 873/873 forecast+bet green; api tsc exit 0. Refs #5233, #5243 Claude-Session: https://claude.ai/code/session_01StNurp4TGC3JLHbTtKJhbp
…5243 review) P1 (correctness) — a stale post-deadline sample survived feed recovery and was scored over the fresh quote. samplePendingEntries stamped samples with the CYCLE time, so a stale kept-warm reading got a post-deadline ts; the next fresh cycle then (a) skipped sampling (a post-deadline sample already existed) and (b) had the at-deadline path prefer that stored stale value. Now samples are stamped with the SOURCE observation time (asOf) via a new extractMetricObservation, so a stale reading carries a pre-deadline ts, is never selected, and never blocks the fresh quote. Two-cycle stale→fresh regression added. P2 #2 — a partial refresh that dropped a symbol VOIDed the bet immediately (missing record → gate fell through → no_establishable_metric). Now a present feed with an absent matched record pends through the settlement grace and VOIDs only if it never returns. P2 #3 — the seeder could mint a newly dated bet from a stale kept-warm envelope. A per-feed generation freshness contract (commodities: 5d, tolerates any weekend/holiday) drops a feed whose _seed.fetchedAt is beyond it, so no bet is generated from stale prices. Also: the settlement gate is now scoped to POINT windows (at-deadline) — a within-horizon spec resolves from the asOf-stamped sample timeline, not the current feed record, and must not be gated on it (caught by an existing test). 876/876 forecast+bet green; api tsc exit 0. Refs #5233, #5243 Claude-Session: https://claude.ai/code/session_01StNurp4TGC3JLHbTtKJhbp
…regression (#5327) * fix(forecast): present() observation must be 0 (not NaN) on absence — VOID-rate regression While triaging the 68% resolution VOID rate, found a regression I introduced in the sampling refactor (#5243 P1): extractMetricObservation returned NaN for a missing matched record, but present() is a boolean existence check — an absent subject means the event did NOT occur → 0, not NaN. samplePendingEntries then stored an error sample instead of a valid 0, so a legitimately-NO within-horizon present spec (e.g. infra:outages present(country==X) for a quiet country) stranded with an empty timeline and VOIDed as no_establishable_metric instead of resolving NO. Now mirrors extractMetricValue's `record ? 1 : 0`. Tests: extractMetricObservation present-absent → 0 (matches extractMetricValue); present-within-horizon resolves NO on a sampled absence. 908/908 forecast+bet green; api tsc exit 0. Refs #5233, #4930 Claude-Session: https://claude.ai/code/session_01StNurp4TGC3JLHbTtKJhbp * fix(review): assert present observation timestamp
Why
The energy pilot's EIA bets resolve weekly (deadline 2026-07-19), so
bet_enginescorecard evidence was a week out. This adds a fast lane on the daily-updating commodities feed — bets resolve within the week (~4-day horizon → 2026-07-16) — so Gate-1 evidence lands sooner, and it proves the engine generalizes to a second feed with a different metric fn.What
_bet-templates-commodities.mjs— bets overmarket:commodities-bootstrap:v1for WTI (CL=F), Brent (BZ=F), nat-gas (NG=F), gold (GC=F). Directional (from the dailychange), 2.5% threshold, 2-decimal prices, resolvable via the resolver's existingprice(symbol==X)fn.shapeResolutionFeed— the commodities feed is{_seed, data:{quotes:[...]}}, and the eval'siterateRecordsonly descends into array children, so the doubly-nestedquoteswere unreadable. Now exposed as the array. This also unblocks the pre-existing market commodity-price forecast path (it had the same read problem).seed-forecast-bets.mjs— reads both feeds, emits both families. Commodity bets use the honest thin-history prior (0.4) until their own series accrues (Phase 2's ensemble replaces the probability regardless).Evidence
Scope
Shadow lane only (invisible to users; held out of the skill Brier). Short-horizon price bets are ~coin-flip on direction by design — Gate 1 tests resolves + crisp + not-silly, not skill; calibration is Phase 2. A per-symbol commodity base-rate accumulator is a fast-follow (they're at the prior until then).
Refs #5233, #4930
https://claude.ai/code/session_01StNurp4TGC3JLHbTtKJhbp