Ranking Builder
Build your own composite ranking from the metric catalog.
The Builder (polyrank.app/rankings/builder, paid plans) lets you weight any combination of catalog metrics into a custom composite and rank the full wallet universe against it.

Drag a weight slider and the live preview re-ranks the full wallet universe (3M+ wallets) — the top-20 column updates as you compose.
Via API
curl -s -X POST https://api.polyrank.app/v1/rankings/custom \
-H "Authorization: Bearer pk_live_…" \
-H "Content-Type: application/json" \
-d '{
"weights": {
"brier": { "weight": 40, "window": "lifetime" },
"alpha_vs_mid": { "weight": 30, "window": "30d" },
"sharpe": { "weight": 30, "window": "lifetime" }
},
"filters": { "min_trades": 100, "min_volume_usdc": 10000 },
"limit": 50
}'Every weight is { "weight", "window" } (window defaults to lifetime) on any of the
84 catalog metrics (GET /v1/metrics/catalog), at any window that metric exists at —
GET https://api-agent.polyrank.app/v1/agent/metrics/windows lists which metric exists at which
window (free). A metric asked for at a window it has no data in is a 400 that names the windows it
does have. The copy-engine score (sk_score_14d) and the entry-timing ids are weightable at
lifetime (their window is part of the id).
- Weights apply to percentile-normalized metric values; direction (higher/lower-is-better) is handled per the catalog.
- Filters cut the universe before ranking — sample-size floors keep
small-N noise out. Beyond the fixed gates,
metric_filtersputs an absolute min/max on ANY displayable metric's raw lifetime value ({"metric_filters": [{"metric": "roi_pct", "min": 0, "window": "30d"}]}, max 12; wallets where the metric isn't measured are excluded), andskill_gatesis the relative version (top-X% by percentile, max 8). Both take awindow. The copy filters —copy_engine(the engine's validated sleeve),timing(an entry-timing slice),cluster_policy(one wallet per wallet farm),copy_placeable,active_within_hours,smart_money_tags_any_of,follower_count_max,categories,exclude_bonders— are all applied;market_kinds/exchange_versionsare refused (no per-wallet data exists for them). - Sorting:
sort_byacceptscomposite(default), a raw stat (realized_pnl,roi_pct,win_rate_pct,lifetime_volume_usdc,trade_count), or any displayable metric viametric:<id>(raw lifetime value) /pctile:<id>(lifetime percentile, direction-corrected sodescis always best-first). Unmeasured values always sort last. na_policycontrols wallets missing a weighted metric:exclude(default — only fully-measured wallets qualify) orneutral_50(missing metrics score a neutral 50th percentile instead).as_of(YYYY-MM-DD) ranks the board as it stood that day, from the daily metric snapshot: lifetime window, the snapshotted metrics and the filters the snapshot can answer. Anything else is a 400 with the reason — never a live board. The metric-windows answer above lists exactly whatas_ofhonours.explain: truereturns how many wallets each gate dropped.- The same request works unchanged on the x402 agent rail
(
POST https://api-agent.polyrank.app/v1/agent/rankings/custom, pay per call, no account) and through the MCP server'spolyrank_rank_walletstool (with your API key). The Builder's goals — "Copy entries", "Copy engine", … — are published as ready-to-send bodies atGET /v1/agent/rankings/goals(free). - Full config schema: API Reference → Rankings.
Reading the breakdown (the measured marker)
Set include_breakdown: true and each ranking row carries a breakdown object —
the per-metric percentile that fed the composite. Two response fields work
together:
| Field | Meaning |
|---|---|
breakdown | { metricId → percentile }. A cell is null when the metric was not measured for that wallet (below the metric's minimum sample size, or no activity in the requested window). |
breakdown_measured | { metricId → boolean }. false mirrors a null cell — the metric is unmeasured. Present only when include_breakdown is set. |
A null cell is not a zero
A windowed percentile join-miss (a wallet with no row in the 30-day table)
serializes to null over the wire — not a real 0 or P50. Reading it as a
measured 0 silently corrupts a re-derived composite. Always branch on
breakdown_measured[id]:
false→ render—(unmeasured). Underna_policy: neutral_50the composite scored it as a neutral P50; underexcludethe wallet was dropped if this metric was weighted.true→ the percentile inbreakdown[id]is a real measured value (even if it happens to be0).
Recipes to start from
| Goal | Weights sketch |
|---|---|
| Pure forecaster | brier 0.5 · ece 0.3 · information_coefficient 0.2 |
| Copyable sharp | alpha_vs_mid 0.4 · brier 0.3 · late_entry_alpha 0.3 |
| Steady compounder | sharpe 0.4 · max_drawdown_pct 0.3 · profit_factor 0.3 |