Design record (decided 2026-06-01) for the pmv2 scoring engine — Step 2 of the from-scratch rebuild. A two-tier design: Tier 1 computes a per-trader trader_score (stored on pmv2_traders) from edge (not raw W/L) weighted by track-record consistency; Tier 2 computes a per-candidate-bet score at signal-time (not stored) by aggregating the Tier-1 scores of every holding trader, each scaled by that trader’s conviction on the specific bet. This note is the design rationale — what each number means and why, plus the gotchas that bit us on the way.

For Agents

pmv2 = a from-scratch rebuild; tables are prefixed pmv2_* (distinct from the legacy pm_* schema). Step 1 (leaderboard collection across Day/Week/Month/All, intersection of traders in ≥2 intervals, self-contained resolutions) already shipped. This is Step 2: scoring.

  • Tier 1 — trader_score (per trader, stored on pmv2_traders):
    • Qualify gate: resolved_bets >= min_resolved (default 50), else unscored (the trader sinks — no score, not ranked).
    • edge (skill base) = mean(result − entry_price) over resolved event bets, where result ∈ {0,1} and entry_price = volume-weighted avg BUY price. Chosen over raw W/L to remove favorite-bias.
    • consistency = split resolved bets early/recent halves by time; min-of-halves edge held vs overall (0 if either half non-positive). span_factor = min(1, active_days/365). long_term = consistency × span_factor.
    • trader_score = edge × (0.5 + 0.5·long_term) — long-term is a 0.5–1.0 trust multiplier (never zeroes a real edge; ranks proven durability above flashes).
  • Tier 2 — RESOLVED 2026-06-01 = FIRST-MOVER copy, not consensus. Act on the first high-edge (high-trader_score) trader’s new event bet; do not wait for a quorum. The summed-over-holders single_bet_score consensus framing below is superseded — see Resolved (2026-06-01) — Tier-2 is FIRST-MOVER, not consensus.
    • bet_confidence = where this bet’s size sits between the trader’s avg LOST-bet size and avg WON-bet size, clamped 0–1. A conviction proxy, not a profit signal. Repurposed (2026-06-01) as the position-SIZING input for the first-mover copy (bigger copy when a high-edge trader bets big for them) — not a multi-trader trigger.
  • Likely execution path: reuse the existing on-chain copy paper lane (copytrade-shipped), already a first-mover detector on Polygon — feed pmv2’s edge-selected cohort into it + add conviction sizing (wiring TBD at design time).
  • Status: Tier-1 being implemented now (migration 20260602010000_pmv2_scoring.sql adds edge/consistency/span/avg-bet columns). Tier-2 = first-mover lane next; remaining validation question is whether the EDGE-selected cohort matches/beats the old PnL-selected one under the purged harness.
  • EMPIRICAL (run 3, 2026-06-02): Tier-1 ran in prod and the edge ranking reshuffled the board as designed (favorite-riders sank, edge-pickers rose). KEY FINDING — the leaderboard is NOT a skill list: across 35 qualified (≥50-bet) traders avg edge is −0.9pp, 11 have ≤0 edge, only ~5–8 clear 3pp ⇒ the Tier-2 copy cohort is small & must be edge/score-gated. Confirmed limit: trade history hard-capped at 3500/wallet by an API offset ceiling (NOT --trades-max-pages). OPEN before Tier-2: -<timestamp>-suffixed wallet identities (sub-account vs parse quirk) affect trader identity. Full results in Tier-1 validation (run 3) — first prod run, 2026-06-02.
  • PHASE 1 SHIPPED (2026-06-02) — the copy-lane positions snapshot is built and ran in prod. New pmv2-positions command + pmv2_positions current-state table (migration 20260602020000) snapshot the cohort’s open event positions. First prod run: 35 cohort wallets → ~2,546 open event positions across 32 traders and 757 distinct events (≈3.3 markets per event), confirming the multi-version/variant reality the design accounts for. Detection model = poll the Polymarket Data API positions endpoint (NOT on-chain WSS). Built TDD, reviewed (spec + code-quality), migration applied. Status: Phase 1 done. Next = Phase 0 (purged backtest) and/or Phase 2 (poller → Telegram alerts + paper copies + resolution PnL). Details: Phase 1 shipped (2026-06-02) — positions snapshot.
  • GOTCHA (2026-06-02): proxied reqwest clients need connect_timeout, not just timeoutpmv2-positions hung ~75 min silently at --concurrency 8. Fix = .connect_timeout(10s) on shared clients + a per-wallet tokio::time::timeout(90s) + default concurrency 8→4. See GOTCHA (2026-06-02) — proxied clients need connect_timeout, not just timeout.
  • v1 → pmv2 HARD CUT COMPLETE (2026-06-02) — pmv2 is now the entire system; the legacy pm_* pipeline is fully decommissioned (~27,000 lines removed across 4 commits on main: 96104b9 pm-arb crate −12,087, 7b3132d backtest/short-term/wallet-monitor −6,296, 0befd3b consensus/ranked/safe-bets CLI + bot cmds −1,297, 5ffc330 repoint scheduled-run + delete consensus/safe-bets pipeline + polymarket-data consensus.rs/safe_bets.rs −7,440). Binary now exposes only migrate, resolutions-sync, telegram-, self-, scheduled-run, schedule-, bot/bot-, pmv2-collect/positions/score-bet. Bot lost /safebets //ranked //snapshot, gained the bet-link auto-reply. scheduled-run = resolutions-sync → pmv2-collect → pmv2-positions → self-wallet/resolution/change-detection (self-wallet retained). Code-only cut — pm_* prod tables left dormant, NOT dropped. Build/clippy/fmt green; 151 + 99 tests pass (3 pre-existing pedantic nits in scoring.rs). Deploy of 5ffc330 + bet-link live paste-test handed to @deploy (Round 49) — still PENDING. See v1 → pmv2 hard cut COMPLETE (2026-06-02).
  • PHASE 2a SHIPPED (2026-06-02) — the per-bet scoring engine is built (the Tier-2 read-side). New pmv2-score-bet --bet <condition_id | market-slug | Polymarket URL> CLI + pmv2::scoring module. Per held bet, bet_score = Σ trader_score × bet_confidence, grouped per (condition_id, outcome_index); event URLs/slugs fan out across variant markets (multi-version). Read-only (joins pmv2_positions ⋈ pmv2_traders), no migration. Built TDD (26 pmv2 tests) + reviewed. The single-bet-input is the same function the future Telegram link-check and the Phase-2b poller will call. Validated read-only in prod: all three selector forms returned identical output; example — MSTR “sells BTC by May 31” NO 16 edge holders (0.199) vs YES 4 (0.024), a $849k NO whale with negative trader_score correctly DRAGS the score down (skill-weighted, not money-weighted). Output is honestly labeled “current cohort EXPOSURE (consensus), not a first-mover signal” + shows snapshot age. See Phase 2a shipped (2026-06-02) — per-bet scoring engine. Status: Phase 2a done. Remaining = the Telegram link-handler (wire score_bet to the bot) and Phase 2b (poller → delta detection → auto-score → alerts + paper copies + resolution PnL).

Context — where this sits

pmv2 is a clean-room rebuild of the Polymarket copy-trade / consensus system on a fresh pmv2_* schema (the legacy pm_* pipeline in polymarket-fetch is left intact). Step 1 shipped: collect leaderboards across Day / Week / Month / All intervals, keep traders that appear in ≥2 intervals, and resolve their bets self-contained. This note is Step 2 — turning that collected population into a ranked signal.

The problem this fixes — raw W/L is useless for ranking skill

The naive score (a trader’s raw win/loss rate) fails on two independent axes, and both make it worthless for ranking:

Raw W/L is FAVORITE-BIASED

A 0.96 win rate doesn’t mean skill — it almost always means the trader bets heavy favorites (high entry price). Picking a 95¢ YES that resolves YES is trivially “winning” while adding ~zero edge over the market’s own implied probability. Raw W/L rewards buying things that were already going to happen.

Raw W/L is sample-blind

Tiny-sample traders tie or beat 800-bet pros at the top of the board: a 5-0 (or even 1-0) trader shows 1.000. Without a sample gate, the leaderboard is dominated by lucky noise, not durable skill.

Tier 1 attacks both: edge (not W/L) removes the favorite-bias; the min-50 gate + consistency weighting remove the small-sample illusion.

Tier 1 — trader_score (per trader, stored on pmv2_traders)

A single number per trader, recomputed on each scoring pass, persisted on pmv2_traders.

Qualify gate — resolved_bets >= min_resolved (default 50)

A trader needs ≥50 resolved bets to be scored at all. Below that they are unscored — they sink (no score, excluded from ranking), rather than being assigned a misleadingly high 1.000. This is the direct fix for the sample-blindness above. min_resolved is configurable; default 50.

Skill base — edge, not win/loss

edge = mean( result − entry_price )   over resolved event bets
  result      ∈ {0, 1}                (did the bet's outcome resolve true)
  entry_price = volume-weighted avg BUY price for that event bet

edge is the average amount the trader beat the market’s own implied probability. A positive edge means: across their resolved bets, outcomes came true more often than the price they paid implied. This is deliberately chosen over raw W/L specifically to strip out favorite-bias — buying a 95¢ favorite that resolves yes contributes 1 − 0.95 = +0.05, not a full “win,” so loading up on favorites no longer inflates the score.

Why entry_price is volume-weighted

A trader may scale into an event bet across several BUYs at different prices. The bet’s effective entry is the volume-weighted average BUY price, so result − entry_price reflects the price they actually paid in aggregate, not just their first tick.

Long-term weighting — track-record CONSISTENCY (not raw W/L over time)

edge alone still can’t tell a durable trader from one hot streak. The long-term term measures whether the edge holds up across the trader’s own history:

split resolved bets into early-half / recent-half  (by time)
consistency = min( edge_early , edge_recent ) measured against overall edge
              = 0 if EITHER half is non-positive          (min-of-halves)
span_factor = min(1, active_days / 365)
long_term   = consistency × span_factor
  • consistency is min-of-halves: it asks how well the WORST half’s edge holds up. A trader who was great early and mediocre lately (or vice-versa) gets pulled down; only someone edge-positive in both halves keeps a high consistency. If either half is non-positive, consistency is 0.
  • span_factor caps at 1 after ~a year active — a longer demonstrated track record counts for more, up to 365 days.

Composite — trader_score

trader_score = edge × (0.5 + 0.5 · long_term)

long_term ∈ [0,1] enters as a 0.5–1.0 trust multiplier on the edge base:

  • A proven, durable trader (long_term ≈ 1) keeps ~all their edge.
  • A real-edge-but-unproven trader (long_term ≈ 0) keeps half their edge — never zeroed.

Why a 0.5–1.0 multiplier, not a 0–1 gate

The design choice is that long-term consistency should rank proven durability above flashes without erasing a genuine edge. A trader with a real positive edge but a short/uneven record still scores positively (just discounted), rather than being thrown out. It tilts the ranking toward durability instead of hard-gating on it.

Tier 2 — single-bet score (per candidate bet, signal-time, NOT stored)

SUPERSEDED 2026-06-01 — Tier-2 is FIRST-MOVER, not the consensus sum below.

The summed-over-holders single_bet_score described in this section was the consensus framing. It is superseded (see Resolved (2026-06-01) — Tier-2 is FIRST-MOVER, not consensus): Tier-2 acts on the first high-edge trader’s new bet (no quorum), and bet_confidence becomes the position-SIZING input for that single copy rather than a per-holder term in a sum. The text below is retained as the original rationale.

Tier 1 ranks traders. Tier 2 scores a specific candidate bet at the moment we’d consider copying it, by asking how much qualified, convinced skill is behind this exact bet:

single_bet_score = Σ  ( trader_score_i × bet_confidence_i )
                   over all qualifying traders i currently holding this bet

This is the consensus mechanism: more confident, higher-trader_score traders agreeing on one bet ⇒ a stronger copy signal. It is computed live at signal-time and not persisted. (Superseded 2026-06-01 — see banner above; Tier-2 ships as first-mover, with bet_confidence as sizing.)

bet_confidence — a CONVICTION proxy

bet_confidence = position of THIS bet's size between
                 the trader's avg LOST-bet size  (→ 0)
                 and the trader's avg WON-bet size (→ 1)
                 clamped to [0, 1]

The idea: a trader tends to size up on bets they’re more sure of. If this bet is as big as their typical winner, confidence ≈ 1; as small as their typical loser, ≈ 0. It’s a per-trader, self-relative conviction signal.

Key decisions & gotchas

avg bet sizes are TRADE-based, not position-based (corrected after two mis-scopes)

The avg LOST/WON bet sizes that feed bet_confidence are computed per tradeeach BUY trade is treated as a bet, then averaged per trader. They are NOT per-market aggregated positions, and NOT first-buy-only. This was corrected after two wrong scopings (first per-position, then first-buy-only). Get this wrong and bet_confidence is measured against the wrong baseline.

bet_confidence measures CONVICTION, not profit — so it must be a MULTIPLIER, never a trigger

Bigger bets correlate with favorites / price, and edge is roughly flat across bet size — so a large bet is not itself evidence of a profitable bet. bet_confidence therefore must only ever scale an already-edge-positive bet inside Tier 2 (multiplying trader_score, which carries the edge). It must never be used as a standalone copy trigger — “this is a big bet” alone is not a buy signal.

long-term weighting must multiply the EDGE base, not raw W/L

If long-term consistency were applied to raw W/L instead of edge, “has long been successful” would silently decay into “has long bet favorites” — re-introducing exactly the favorite-bias Tier 1 exists to kill. The consistency term is computed on, and multiplies, edge for this reason.

Cross-run persistence is a FUTURE longevity signal (accrues for free)

A trader showing up consistently across collection runs over time is a longevity signal not yet folded into trader_score. It accrues automatically because every collection run is tagged with a run_id — so “appeared in N of the last M runs” can be derived later without new plumbing. Noted as a future enhancement, not in the current formula.

Resolved (2026-06-01) — Tier-2 is FIRST-MOVER, not consensus

RESOLVED 2026-06-01 — Tier-2 = first-mover copy. Consensus framing dropped; the prior evidence wins.

The open question below (preserved for the record) asked whether Tier-2’s summed single_bet_score consensus framing contradicted the verified prior result that quorum/agreement HURTS ROI. The user decided 2026-06-01 and explicitly agreed to reverse their earlier consensus push. The reconciliation is settled in favor of the prior evidence:

  • DECISION — Tier-2 = FIRST-MOVER, not consensus. Act on the first high-edge (high-Tier-1) trader’s new event bet; do NOT wait for a quorum of agreeing traders. This aligns Tier-2 with xref-consensus-rejected-fresh-copy-wins (first cohort BUY K1 = +7.59%, K2/K3 progressively worse — consensus is structurally late). The summed-across-holders single_bet_score is dropped as a trigger.
  • bet_confidence is repurposed as POSITION SIZING for the first-mover copy — not a multi-trader trigger. It scales the size of the single first-mover copy: a high-edge trader betting big for them (this bet’s size vs that trader’s own won/lost averages) ⇒ a larger copy. It is conviction-based sizing on one trader’s bet, not an aggregation over multiple traders.
  • Likely execution path — reuse the existing on-chain copy paper lane. That lane (copytrade-shipped / the on-chain copy paper lane on Polygon) is already a first-mover detector. Plan: feed pmv2’s edge-selected cohort into it and add conviction sizing, rather than building a first-mover detector from scratch. Wiring to be verified at design time.
  • Validation still gates real money. Tier-2 must still clear the purged + embargoed harness vs the first-mover / random baselines before live trading (purged-backtest-gate-no-edge). The now-open validation question is narrower: does the EDGE-selected cohort (pmv2 Tier-1) match or beat the old PnL-selected cohort under that harness?

Status & implementation

  • Tier 1 — being implemented now: edge + consistency + span_factor + the min-50 qualify gate + trade-based avg-bet sizes. New migration 20260602010000_pmv2_scoring.sql adds the edge / consistency / span / avg-bet columns to pmv2_traders.
  • Tier 2 — next step (DECIDED 2026-06-01 = first-mover, not consensus): act on the first high-edge trader’s new event bet (no quorum); bet_confidence becomes the position-SIZING input for that copy. Likely execution path: reuse the existing on-chain copy paper lane (copytrade-shipped) — already a first-mover detector on Polygon — by feeding it pmv2’s edge-selected cohort and adding conviction sizing (wiring to verify at design time), rather than a from-scratch build. Remaining validation question: does the EDGE-selected cohort match or beat the old PnL-selected one under the purged + embargoed harness before real money. See Resolved (2026-06-01) — Tier-2 is FIRST-MOVER, not consensus.

Tier-1 validation (run 3) — first prod run, 2026-06-02

Tier-1 ran in prod (run 3, 2026-06-02) and the edge ranking did exactly what it was designed to do.

The board reshuffled away from favorite-riders toward edge-pickers, and the run surfaced a leaderboard-is-not-a-skill-list finding plus one open identity question for Tier-2.

The edge ranking reshuffled the board exactly as designed

Favorite-riders with sky-high W/L but ~zero/negative edge sank; lower-W/L traders who consistently beat their entry price rose:

TraderW/LAvg entryEdgeRank moveNote
ArmageddonRewardsBilly0.9620.96+0.003#3 → #21classic favorite-rider, edge ≈ 0
”The Spirit of Ukraine>UMA”0.8900.90−0.007→ #27NEGATIVE edge despite 0.89 W/L
kinderSman0.9350.93~0→ #25high W/L, no edge
Dropper0.7230.64+0.081→ #1453-day span, consistency 0.81
betwick0.5850.54+0.042→ #3just 0.585 W/L, 377-day span

This is the favorite-bias fix (The problem this fixes — raw W/L is useless for ranking skill) working on live data: a 0.962 W/L at 0.96 entry adds ~nothing over the market, while a 0.585 W/L at 0.54 entry is genuinely beating the price it paid.

KEY FINDING — the leaderboard is NOT a skill list

Across the 35 qualified (≥50-bet) traders on run 3:

  • Average edge is −0.9pp. The typical leaderboard trader is edge-negative.
  • 11 of 35 have ≤0 edge. Only ~24 are edge-positive, and only ~5–8 clear 3pp.
  • Most top-PnL traders are favorite-riders or price-traders, not resolution-pickers — they make money riding high-probability favorites, not by out-predicting the market.

This directly validates the design premise and the user’s strategy: with a buy-and-hold-to-resolution strategy, edge = result − entry_price measures exactly the thing that matters, and it shows that PnL rank ≠ skill rank.

Implication for Tier-2 — the copy-worthy cohort is small and sharp

If only ~5–8 of 35 qualified traders clear 3pp edge, the cohort worth first-mover-copying in Tier-2 is small (~top 5–10) and must be gated on an edge/trader_score threshold, not taken as “the whole leaderboard.” This sharpens the Status & implementation plan: feed only the edge-threshold-passing cohort into the copy lane.

Run diagnostics (clean)

  • 0 × HTTP 429 at concurrency 16 — proxy rotation is clean under load.
  • min-50 floor dropped 15 of 50 collected traders as noise (the qualify gate doing its job — see [[#qualify-gate—resolved_bets—min_resolved-default-50|Qualify gate — resolved_bets >= min_resolved (default 50)]]).
  • The lone error was the benign gamma empty-batch (25 markets skipped) — not a scoring fault.

Confirmed limitation — trade history capped at 3500/wallet (API offset ceiling)

Trade history is hard-capped at 3500 trades per wallet by an API offset ceiling — and it is NOT --trades-max-pages.

Bumping --trades-max-pages to 100 changed nothing, confirming the cap is the API’s offset ceiling, not our paging knob. Consequence: ~14 heavy traders are scored on their most-recent 3500 trades only (their earlier history is truncated). Their edge/consistency reflect a recent window, not full lifetime. Carry this when interpreting heavy-trader scores and before Tier-2 selection.

Span / long-term integrity — confirmed sound

OPEN ITEM (investigate before Tier-2) — -<timestamp> suffixed wallet identities

Open: wallet addresses with a numeric -<timestamp> suffix recur — possible sub-account vs parse/join quirk.

Examples: 0xcF609D…-1771809916847 at #13 (n=1293), 0xE32F…. These suffixed identities show up as distinct ranked traders. Unknown whether they are legitimate sub-accounts (a real distinct trading identity) or a parse/join artifact that splits/duplicates one trader. This affects trader identity — and therefore which entity Tier-2 would copy — so it must be resolved before Tier-2 wires the edge-selected cohort into the copy lane.

Phase 1 shipped (2026-06-02) — positions snapshot

Phase 1 is DONE. The copy-lane positions snapshot is built, reviewed, and ran against prod.

New pmv2-positions command + pmv2_positions current-state table (migration 20260602020000). Built TDD; reviewed under both the spec lens and the code-quality lens; migration applied.

  • What it does: snapshots the cohort’s current open event positions into the pmv2_positions current-state table — the source-of-truth for what each tracked trader holds right now (the input to the Phase-2 poller/copy logic).
  • Detection model = POLL the Polymarket Data API positions endpoint, not on-chain WSS. This is a deliberate divergence from the legacy on-chain copy lane (copytrade-shipped / copy-trade-lane, which subscribes to Polygon OrderFilled logs): pmv2’s positions snapshot reads the Data API positions endpoint per wallet.
  • First prod run: 35 cohort wallets → ~2,546 open event positions across 32 traders and 757 distinct events — ≈3.3 markets per event. This confirms the multi-version/variant reality the design already accounts for: one “event” spans several related markets (versions/variants), so position aggregation is keyed at the event level, not per-market.

GOTCHA (2026-06-02) — proxied clients need connect_timeout, not just timeout

Proxied reqwest clients need connect_timeout, not just timeout.

A proxy that accepts the TCP connection but then stalls the HTTPS CONNECT tunnel hangs past the total .timeout(30s) — the total-request timeout does not bound a stuck connect.

Symptom

pmv2-positions hung ~75 minutes silently at --concurrency 8: zero rotation / retry / eviction warnings, no completion. Endpoint, proxies, and data were all fine — red herrings ruled out one by one.

Root cause

A proxy accepting the TCP connection then stalling the HTTPS CONNECT tunnel never trips reqwest’s total .timeout(30s), so the per-wallet fetch hangs indefinitely with no log signature.

Fix (defense-in-depth)

  • .connect_timeout(10s) on the shared build_http_client (covers DataApiClient + GammaClient) and on build_proxy_client.
  • A per-wallet tokio::time::timeout(90s) wrapping fetch_positions.
  • Lowered the positions default --concurrency 8 → 4.

Honest caveat

No deterministic repro (reproducing would risk another long hang), so the two timeouts are defense-in-depth — a silent hang is now impossible regardless of the true root cause.

Durable lessons

  • Always set BOTH timeout AND connect_timeout on networked/proxied clients.
  • Bound fan-out fetches with a per-item timeout (tokio::time::timeout), not just a global client timeout.
  • Watch for the “silent stall, zero warns” signature — it points at a stuck connect, not at retries/rotation.

Phase 2a shipped (2026-06-02) — per-bet scoring engine

Phase 2a is DONE. The per-bet scoring engine (the Tier-2 read-side) is built, reviewed, and validated against prod.

New pmv2-score-bet command + pmv2::scoring module. Computes a bet_score for a single named bet by aggregating the cohort’s current pmv2_positions exposure, weighted by each holder’s Tier-1 trader_score and bet_confidence. Read-only join (pmv2_positions ⋈ pmv2_traders), no migration. Built TDD (26 pmv2 tests, incl. multi-version grouping fan-out).

What it does

pmv2-score-bet --bet <selector> where <selector> is a condition_id, a market-slug, or a full Polymarket URL. The same single-bet-input function is what the future Telegram link-check and the Phase-2b poller will call — one scoring entry point, three callers.

  • Module pmv2::scoring: score_bet, group_into_bet_scores, parse_selector, bet_confidence-based BetHolder::conviction, format_bet_scores.
  • Repo additions: repo::load_bet_holders and latest_position_snapshot_ts.
  • Score formula (per held bet): bet_score = Σ ( trader_score × bet_confidence ) over the cohort traders currently holding it, grouped per (condition_id, outcome_index). bet_confidence is the conviction proxy (this bet’s stake vs the trader’s own avg won/lost bet sizes).
  • Multi-version fan-out: event URLs / slugs expand to all variant markets, so one “event” selector scores every related market (the same multi-version reality the positions snapshot surfaced — ≈3.3 markets per event).

Validation (read-only prod run)

All three selector forms (condition_id, slug, URL) returned identical output, and the result matched the earlier ad-hoc demo (bet_score 0.19870).

Example finding — MSTR “sells BTC by May 31”:

OutcomeEdge holdersbet_score
NO160.199
YES40.024

A $849k NO whale with a NEGATIVE trader_score correctly DRAGS the NO score down — the engine is skill-weighted, not money-weighted, exactly as Tier-1 intends. Big-but-unskilled money does not buy consensus.

Honest framing — this is EXPOSURE, not a first-mover signal

pmv2-score-bet output is labeled "current cohort EXPOSURE (consensus), not a first-mover signal".

Per the Resolved (2026-06-01) — Tier-2 is FIRST-MOVER, not consensus decision, the Tier-2 trigger is first-mover, not consensus. This per-bet score is a consensus snapshot of who currently holds the bet, useful for on-demand link-checking (“what does the cohort think of this market right now?”), not the entry signal. The output says so explicitly and shows the snapshot age so a stale pmv2_positions snapshot can’t masquerade as live.

GOTCHA — don’t trust truncated slugs from display queries

An ad-hoc left(slug, 40) in an earlier diagnostic truncated the slug.

...by-may-3 was really ...by-may-31-2026. Display-side truncation (left(), terminal width) can silently mangle a slug into a different, wrong identifier. Match selectors on the full slug/condition_id, never on a truncated render.

Status

  • Phase 2a — DONE (scoring engine + CLI, read-only, validated in prod).
  • Remaining: (1) the Telegram link-handler — wire score_bet to the bot for on-demand link checks; (2) Phase 2b — the poller (delta detection on pmv2_positions → auto-score → alerts + paper copies + resolution PnL).

v1 → pmv2 hard cut COMPLETE (2026-06-02)

pmv2 is now the entire system — v1 fully decommissioned

The legacy pm_* pipeline is gone from the codebase. ~27,000 lines removed across 4 commits on main. pmv2 is the scheduled algo. Code-only cut — the pm_* prod tables are left in place (dormant, not dropped).

What was deleted (4 commits)

CommitRemovedLines
96104b9the pm-arb on-chain copy-lane crate−12,087
7b3132dbacktest / short-term / wallet-monitor / persistence analysis−6,296
0befd3bconsensus / ranked / safe-bets CLI + /safebets /ranked /snapshot bot commands−1,297
5ffc330repoint scheduled-run to pmv2 + delete the consensus/safe-bets/trades pipeline + polymarket-data consensus.rs/safe_bets.rs + dead repo/output/notifier surface−7,440

New binary surface (only these remain)

migrate, resolutions-sync, telegram-*, self-*, scheduled-run, schedule-*, bot/bot-*, pmv2-collect, pmv2-positions, pmv2-score-bet.

The bot lost /safebets //ranked //snapshot and gained the bet-link auto-reply (paste a Polymarket link → score_bet reply).

scheduled-run is now pmv2

Pipeline: resolutions-sync → pmv2-collect → pmv2-positions → self-wallet / resolution / change-detection steps. Self-wallet tracking is retained. pmv2 is the scheduled algo.

Verification

Build / clippy / fmt green. polymarket-fetch 151 tests + polymarket-data 99 tests pass. (3 pre-existing pedantic clippy nits remain in pmv2/scoring.rs — non-blocking.)

GOTCHA avoided — sqlx migration checksum

A stray edit to the already-applied 20260530000100_pm_paper_trades_mid_history.sql was accidentally committed, then reverted. Editing an applied migration would have tripped the sqlx checksum on startup migrate and broken boot. Never edit a migration that has already run in prod.

Deploy — handed to @deploy (AGENT_HANDOFF.md Round 49)

  • Deploy 5ffc330.
  • Decommission apps-pm-arb-validate (ends the rounds-44–48 copy-lane validation).
  • Decommission apps-polymarket-fetch-wallet-monitor.
  • Redeploy the bot.
  • STATUS: deploy + the bet-link live paste-test still PENDING.
  • xref-consensus-rejected-fresh-copy-winsthe prior result Tier-2 reconciled with (resolved 2026-06-01 in its favor): requiring agreement (higher K) monotonically lowers ROI; the first informed entry (K1, +7.59%) is the well-priced one. Tier-2 is now first-mover copy (act on the first high-edge trader, no quorum); the summed single_bet_score consensus framing was dropped and bet_confidence repurposed as sizing.
  • copytrade-shippedthe likely Tier-2 execution path: the on-chain copy paper lane already shipped on main is a first-mover detector on Polygon. Plan (wiring TBD at design time): feed pmv2’s edge-selected cohort into it and add conviction-based sizing, instead of building a first-mover detector from scratch.
  • purged-backtest-gate-no-edge — the leakage-clean purged + embargoed harness and the Phase 2 ship bar: any new signal must beat ~+2.75% random (and the +7.59% first-mover arm) under this harness. The remaining pmv2 validation question is whether the EDGE-selected cohort matches/beats the old PnL-selected one here before real money.
  • consensus-validation-leakage — the leakage playbook this scoring must be validated under (point-in-time signal from trades, purged+embargoed CPCV, shuffled-label negative control, DSR). The Tier-1 edge is a point-in-time result − entry_price quantity, which is the right shape — but the validation discipline still applies.
  • selector-vs-skill-gap — the legacy gap pmv2 answers in a clean rebuild: the old default selector ranked purely by PnL with no luck/edge control; pmv2 Tier-1 ranks by edge + consistency + a sample gate instead. trader_score is the edge-based selector that note called for.
  • best-consensus-algo-design — the other (legacy pm_*) consensus redesign; pmv2 is a simpler, from-scratch take on the same goal. Both weight by skill + conviction and gate on sample size; pmv2 uses min-of-halves consistency rather than that note’s EB-shrinkage / effective-N machinery.
  • copy-trade-algorithm — the original edge-not-PnL framing pmv2 inherits (per-trade edge → per-trader aggregate → conviction-weighted live signal). pmv2’s two tiers map onto that note’s Layer-1/2 (trader score) and Layer-5 (per-trade signal) split.
  • polymarket-fetch — the legacy pm_* workspace pmv2 rebuilds; schema, selectors, CLI surface.
  • TOPICS — project theme index.