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:
/polymarketis now a segmented-control tab shell (web/src/pages/polymarket/index.tsx). Default tab = Copy;?tab=longshotdeep-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 excludeslane='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, useuseSupabase(); 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_price–cap_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)
parseCopyTrade—Number()-coerces every numeric column (PostgREST serializesnumericas strings).deriveCopyKpis/deriveCopyGates/deriveCopyCumulative/deriveCopyByBand/deriveCopyByCohort/deriveCopyExecQuality.tStat—mean(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— readspm_copy_configsingleton (id=1).useCopyWallets— readspm_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)
| Object | Notes |
|---|---|
pm_copy_config | Singleton id=1: enabled, cap_price, min_price, size_usd, min_window_left_secs, max_daily_notional_usd |
pm_copy_wallets | The 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.tsANDtypes.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 > 0strictly.
Forward-validation gates
| Gate | Sample | Pass condition |
|---|---|---|
| G1 | n ≥ 50 | n-progress + win% only — no fabricated cohort comparison |
| G2 | n ≥ 150 | t ≥ 1.5 ∧ ROI > 0 ∧ avgChase ≤ 1.7¢ |
| G3 | n ≥ 300 | t ≥ 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 thedetect_latency_ms = 0sentinel; 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_tsis epoch SECONDS →× 1000for JS.- PostgREST
numeric→ STRING → alwaysNumber()-coerce.
Hardening lessons (from the adversarial verification pass)
1 — Zero-variance guard fails on bit-identical floats
A
sd === 0guard never fires for bit-identical float inputs: rounding makessd ≈ 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 / sizedwith null/0 size (null→0 coercion) yields ±Infinity → mean/std → t-statNaN, rendered literally as “NaN”. Filter tosizedUsd > 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.tsas a purederive*fn with unit tests — never compute in a component. - Touching the t-stat / gates? Re-check lessons 1 & 2 above — the false-pass and
NaNbugs 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.tsandtypes.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.mdfor the UI pass).
Related
- paper-trading-dashboard — the demoted Longshot dashboard (now the secondary
?tab=longshottab; excludeslane='copy') - polymarket-fetch — the external Rust pipeline that produces the copy-lane bets
- integrations — CRM external integrations index
- tech-debt —
usePaperTradesstill unbounded (latent row-cap truncation)