feat(squid): Brier score for predict-polymarket - #123
Conversation
Mirror predict-omen's Brier scoring in the squid: Bet.impliedProbability (1e18-scaled fill price) set in handleOrderFill, brierSum/brierCount accumulated on the resolution-day DailyProfitStatistic inside processMarketResolution's existing bet loop. No reversal fields — resolutions are write-once. Requires a blue-green re-index. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AykA7kdEeusgyDs8dndpRx
…4964) GHSA-xvcm-6775-5m9r, published after the last green main run; hits every yarn tree via graph-cli > immutable. Same handling as the July/August immutable waves: build-time CLI tooling only, patched in >=5.1.8 but pinned by the graph-cli line; review 2026-11-29. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AykA7kdEeusgyDs8dndpRx
…ries Follow-up to 335e3af: the graph-cli 0.64.0 trees (autonolas, autonolas-base) hit the same immutable GHSA under a second id for the <4.3.9 range (1144965) plus the new node-tar stack-overflow advisory (1145647, graph-cli > binary-install-raw > tar). Build-time tooling only, pinned by the legacy graph-cli line; review 2026-11-29. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AykA7kdEeusgyDs8dndpRx
jmoreira-valory
left a comment
There was a problem hiding this comment.
Review round 1 — ✅ Approve
What this does. Adds Brier-score tracking to the predict-polymarket squid so the predict-economy Performance card can compute windowed Brier from the same query on both indexers. Every order fill now stores its 1e18-scaled implied probability on the Bet, and at market resolution each buy's (p - actual)^2 contribution is credited to the agent's resolution-day DailyProfitStatistic (brierSum/brierCount), with invalid markets scored against 0.5. Requires a blue-green re-index from zero since historical bets lack the new column.
My previous blocker is resolved: the branch moved by one commit (b0d8626) that adds exactly the two missing allowlist entries — 1144965 (immutable 4.x, the range-sibling of the already-added 1144964) and 1145647 (node-tar DoS) — following the file's established same-GHSA-different-range twin pattern, and the previously-red Dependency audit (subgraphs/autonolas) job is now green. CI is fully green at HEAD (79/79 check runs, all 25 audit jobs passing) and the PR is mergeable.
The Brier implementation itself is unchanged since my last pass, and I had verified the load-bearing claims against source at HEAD rather than trusting the PR body:
- Omen parity is exact:
PROBABILITY_SCALE = 1e18,(p - actual)^2 / 1e18, the zero-denominator sentinel, invalid →5e17, and thebrierSum/brierCountfield names all matchsubgraphs/predict-omen/src/utils.ts, so the cross-indexer query-shape claim holds. - Write-once resolution is real:
processMarketResolutionreturns early when aQuestionResolutionexists, and the per-participantsettledguard gives second-layer idempotency — dropping Omen'sbrierSumAppliedreversal fields is justified. Both the UMA and NegRisk paths funnel into it, and equal/shortpayoutscorrectly derivewinningOutcome = -1(invalid). - The moved
cache.set(DailyProfitStatistic, ...)(now after the bets loop) is safe:betsByParticipant's mid-loop flush only writes dirty entities, the in-memory mutations survive, and the deferred set lands in a later/end-of-batch flush. Participants are one-per-agent per market, so no cross-iteration aliasing. - Sells store negative amounts and
computeImpliedProbabilityabsolutes them; sells and zero-share fills are excluded from aggregation by theisBuy && impliedProbability > 0ngate — all covered by tests. - Tests are CI-enforced (the
squid-testsjob runs the vitest suite, green at HEAD), and the migration'sNOT NULL-without-default columns are safe given the documented blue-green re-index from zero — they'd fail loudly if ever misapplied to a populated database, which is the right direction.
One out-of-scope aside carried over: the audit logs show ~40 allowlist entries past their review date — the backlog deserves its own sweep PR before the warnings drown real signal.
Findings — 1 · ⚪ 1 Nit
| # | Sev | Location | Issue |
|---|---|---|---|
| F1 | ⚪ NIT | squids/predict-polymarket/schema.graphql:90 | Zero sentinel also swallows zero-amount buys, not just zero-share fills |
Co-authored-by: jmoreira-valory <96571377+jmoreira-valory@users.noreply.github.com>
d8ffc6e
jmoreira-valory
left a comment
There was a problem hiding this comment.
Review round 1 — ✅ Approve
What this does. Adds Brier-score tracking to the predict-polymarket squid. Every order fill now records its 1e18-scaled implied probability on the Bet, and when a market resolves each buy's (p - actual)^2 contribution is credited to the agent's resolution-day DailyProfitStatistic, with invalid markets scored against 0.5. This gives the predict-economy Performance card the same windowed-Brier query shape on Polymarket as on Omen, at the cost of a blue-green re-index from zero.
Re-review — re-affirming my approval at HEAD. The branch moved by exactly one commit since my previous approve (b0d8626 → d8ffc6e), verified via the compare API: the applied F1 suggestion on the Bet.impliedProbability schema comment. One line, comment-only, no code, tests, migration, or allowlist changes. That push dismissed my earlier approval, so this review restores it.
- CI is fully green at
d8ffc6e(30/30 check runs, including the dependency-audit and lockfile-validation jobs that were the subject of my earlier blocker), and @Adamantios has approved at the same commit. - Nothing to re-verify in the implementation —
src/brier.ts,src/logic.ts,src/handlers.ts, the additive migration, and both test files are byte-identical to the commit I already verified (Omen fixed-point parity, write-once resolution guard, deferredcache.setafter the bets loop, sell / zero-fill exclusion via theisBuy && impliedProbability > 0ngate). - Footgun sweep: the fleet-wide checklist (tx receipts, Safe
execTransaction,TypedDict,CONTRACTS[chain], EIP-150/191, FSM sync, composed-app final states) does not apply to a TypeScript squid indexer. The subgraph-specific ones are moot here too: the migration is purely additive (no schema-drop / graft concern) and this is an SQD squid, not a graph-node file-data-source, so causality regions do not come into play.
The single NIT below is the leftover half of my F1: the suggestion block only covered schema.graphql, so the matching JSDoc in src/brier.ts still names only the zero-share case. Cosmetic; not worth another round on its own.
Findings — 1 · ⚪ 1 Nit
| # | Sev | Location | Issue |
|---|---|---|---|
| F1 | ⚪ NIT | squids/predict-polymarket/src/brier.ts:9 | computeImpliedProbability docstring still names only the zero-share case |
Resolves BDMARK-2258
What
Adds Brier-score tracking to
squids/predict-polymarket, mirroring the predict-omen implementation (seeBRIER_SCORE.md), so the predict-economy Performance card can compute windowed Brier from the same query shape on both indexers.Bet.impliedProbability: BigInt!— per-token fill price, 1e18-scaled (|amount| * 1e18 / |shares|), set inhandleOrderFillfor v1 and v2 fills. Buys: the market-implied probability of the bet's outcome. Sells: closing price, excluded from aggregation. Zero = degenerate zero-share fill, skipped.DailyProfitStatistic.brierSum: BigInt!/brierCount: Int!— per buy,(p − actual)² / 1e18withactual ∈ {1e18 won, 0 lost, 5e17 invalid}, credited on the resolution day insideprocessMarketResolution's existing per-participant bet loop. Windowed mean =Σ brierSum / Σ brierCount.src/brier.tswith the pure primitives (computeImpliedProbability,brierContribution,actualForOutcome); invalid markets are keyed offwinningIndex === -1.1787931352501-Data.js: three additive columns.0.1.0inpackage-lock.json).BRIER_SCORE.mdpolymarket section rewritten from "planning" to "shipped"; one line in rootCLAUDE.md.Omen's
brierSumApplied/brierCountAppliedreversal fields are deliberately absent: Polymarket resolutions are write-once (processMarketResolutionreturns early if aQuestionResolutionexists), so there is no re-answer path.Tests
tests/brier.test.ts— fixed-point primitives (scale, sign handling, zero sentinel, no clamp, invalid = 0.5).tests/lifecycle.test.ts— newbrier scoreblock: win/loss sums on the resolution day, sells and zero-share fills excluded, invalid resolution, repeat-resolution idempotency; plus Brier assertions on the NegRisk path andimpliedProbabilityon the existing buy test.tscclean.migration:applysucceeds, subsequentmigration:generatereports no schema diff.Deployment
Requires a blue-green re-index from zero (historical bets need
impliedProbability; running totals forbid partial replay). The current production stack stays untouched until cutover — procedure per the squid README.🤖 Generated with Claude Code
https://claude.ai/code/session_01AykA7kdEeusgyDs8dndpRx