Read-only forward-validation dashboard for the copy-trade lane — a Rust service that mirrors 5 tracked Polygon traders’ cheap-outcome buys as paper $10 “copy” trades. Was the default /polymarket landing when shipped; as of 2026-06-02 it is a secondary tab (?tab=copy) — the Trader-Scout dashboard is now the default landing. The old Longshot dashboard is the third tab (?tab=longshot). Merged to master locally as commit bf66a6f (push pending), branch feat/copy-lane-dashboard. Built via the superpowers brainstorm→spec→plan→subagent-TDD→adversarial-verification workflow.

For Agents

  • Route: /polymarket is now a segmented-control tab shell (web/src/pages/polymarket/index.tsx). Default tab = Copy; ?tab=longshot deep-links the demoted dashboard.
  • Copy page: web/src/pages/polymarket/copy/index.tsx + copy/components/*.
  • Longshot (old) page: moved to web/src/pages/polymarket/longshot/LongshotDashboard.tsx; now excludes lane='copy'.
  • Domain core (pure, unit-tested): web/src/lib/copy-trades.ts.
  • Hooks: web/src/lib/hooks/polymarket/useCopyTrades.ts (+ useCopyConfig, useCopyWallets).
  • Data: pm_paper_trades WHERE lane='copy' in the CRM’s single Supabase (mkofmdtdldxgmmolxxhc, internally “mgmt” — there is NO separate client, use useSupabase(); see paper-trading-dashboard).
  • Read-only. Bets are produced by the external Rust copy service (polymarket-fetch).

What it is

A forward-validation view: did the copy strategy’s edge survive contact with live execution? Rows are pm_paper_trades filtered to lane='copy' — paper $10 mirrors of cheap-outcome (min_pricecap_price) buys made by 5 tracked wallets. The dashboard exists to gate “is this real?” — not to trade.

Architecture (three layers, repo-standard Page → hook → Supabase)

1. Domain — web/src/lib/copy-trades.ts (pure, framework-free)

  • parseCopyTradeNumber()-coerces every numeric column (PostgREST serializes numeric as strings).
  • deriveCopyKpis / deriveCopyGates / deriveCopyCumulative / deriveCopyByBand / deriveCopyByCohort / deriveCopyExecQuality.
  • tStatmean(perTradeROI) / (sampleStd / √n).
  • Constants: COPY_FIX_CUTOFF_MS (2026-05-31T15:00:00Z post-fix boundary), CHASE_KILL = 0.017, BACKTEST_ROI = 0.248.

2. Hooks — web/src/lib/hooks/polymarket/

  • useCopyTrades.ts.eq('lane','copy'), .limit(5000) DESC, 30s poll.
  • useCopyConfig — reads pm_copy_config singleton (id=1).
  • useCopyWallets — reads pm_copy_wallets.

3. Page + components — web/src/pages/polymarket/copy/

CopyScorecards, GateTracker (stepper), CopyCumulativeChart, CopyBandTable, CohortTable, RecentCopiesTable, ExecQualityStrip, CopyConfigPanel. Tab shell shares the extracted web/src/pages/polymarket/components/Pill.tsx.

Database (new)

ObjectNotes
pm_copy_configSingleton id=1: enabled, cap_price, min_price, size_usd, min_window_left_secs, max_daily_notional_usd
pm_copy_walletsThe 5 tracked Polygon wallets
pm_paper_trades (+5 cols, nullable)chase_spread, cohort_price, cohort_wallet, detect_latency_ms, fee_usd

Types hand-added to BOTH files

New tables/columns were added by hand to packages/shared/src/database.types.ts AND types.ts — keep them in sync if you regenerate.

Metric / domain rules (subtle + reusable)

Resolved-only P&L — pending is NEVER $0

P&L / win-rate / ROI count resolved trades only (realized_pnl_usd NOT NULL). Pending is a separate count, never folded in as $0.

  • Net ROI% = Σ resolved pnl / Σ resolved sized × 100.
  • Win = realized_pnl_usd > 0 strictly.

Forward-validation gates

GateSamplePass condition
G1n ≥ 50n-progress + win% only — no fabricated cohort comparison
G2n ≥ 150t ≥ 1.5 ∧ ROI > 0 ∧ avgChase ≤ 1.7¢
G3n ≥ 300t ≥ 2.0 ∧ ROI ≥ +8%

t = mean(perTradeROI) / (sampleStd / √n).

Exec-quality uses a different population than P&L

Latency/chase metrics are POST-FIX filtered (created_at ≥ 2026-05-31T15:00:00Z = COPY_FIX_CUTOFF_MS) and exclude the detect_latency_ms = 0 sentinel; the strip always shows coverage %. The latency/chase history spans 3 code-version regimes, so aggregates over all rows are meaningless. P&L panels, by contrast, use the full resolved set.

Timestamp & numeric gotchas

  • window_close_ts is epoch SECONDS× 1000 for JS.
  • PostgREST numericSTRING → always Number()-coerce.

Hardening lessons (from the adversarial verification pass)

1 — Zero-variance guard fails on bit-identical floats

A sd === 0 guard never fires for bit-identical float inputs: rounding makes sd ≈ 1.7e-17, so the t-stat explodes to ~1e16 and FALSELY passes the statistical gates. Use a relative-epsilon guard: sd <= 1e-12 * max(1, |mean|).

2 — Null/0 size poisons the t-stat

realized / sized with null/0 size (null→0 coercion) yields ±Infinity → mean/std → t-stat NaN, rendered literally as “NaN”. Filter to sizedUsd > 0 && Number.isFinite(...) before the stat.

3 — Bucketing needs an out-of-range catch-all

Price/value buckets must include a catch-all, else rows below the floor / null / negative silently vanish and per-bucket counts won’t reconcile with the total. (Same exhaustive-bucket lesson as paper-trading-dashboard.)

4 — Formatters guard non-finite

Shared formatters must guard !Number.isFinite(x) → '—' (covers null / NaN / ±Inf) so a leaked non-finite never renders as $NaN / NaN%.

5 — Fee-% must keep populations consistent

Fees-as-%-of-resolved-notional must use resolved fees in the numerator, not an all-rows total — mixing populations overstated it ~0.7pp.

6 — UI-polish bar (standing requirement)

  • Tab switcher = a real segmented control (role=tablist/tab + aria-selected), not loose rounded buttons.
  • Gate tracker = an actual stepper (icons + progress bars + status badges), not flat text rows.
  • Backtest reference line = a visibly distinct color (not gray-on-gray) with an actual−expected “edge gap” tooltip row.
  • Compound scorecard data goes in the StatCard delta/subtitle slot, not crammed into the label.

If you touch this next

  • Adding a metric? Put the math in copy-trades.ts as a pure derive* fn with unit tests — never compute in a component.
  • Touching the t-stat / gates? Re-check lessons 1 & 2 above — the false-pass and NaN bugs both live in that path.
  • Adding a bucket dimension? Include an out-of-range catch-all (lesson 3).
  • Editing exec-quality? Remember it is a separate, post-fix-filtered population from P&L — don’t unify them.
  • Schema change? Update both database.types.ts and types.ts.
  • Spec/plan: docs/superpowers/specs/2026-05-31-copy-lane-dashboard-design.md, docs/superpowers/plans/ (+ 2026-05-31-copy-lane-dashboard-ui-design.md for the UI pass).
  • paper-trading-dashboard — the demoted Longshot dashboard (now the secondary ?tab=longshot tab; excludes lane='copy')
  • polymarket-fetch — the external Rust pipeline that produces the copy-lane bets
  • integrations — CRM external integrations index
  • tech-debtusePaperTrades still unbounded (latent row-cap truncation)