Polyrankdocs
Product guides

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.

The Ranking Builder: weight sliders on selected metrics, universe filters, and the live top-20 preview recomputing as you drag

Drag a weight slider and the live preview re-ranks the 2.9M-wallet universe — 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_score": 0.4, "alpha_vs_mid": 0.3, "sharpe": 0.3 },
    "filters": { "min_trades": 100, "min_volume_usdc": 10000 },
    "limit": 50
  }'
  • 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_filters puts an absolute min/max on ANY displayable metric's raw lifetime value ({"metric_filters": [{"metric": "copier_realized_return", "min": 0}]}, max 12; wallets where the metric isn't measured are excluded), and skill_gates is the relative version (top-X% by percentile, max 8).
  • Sorting: sort_by accepts composite (default), a raw stat (realized_pnl, roi_pct, win_rate_pct, lifetime_volume_usdc, trade_count, copier_realized_return), or any displayable metric via metric:<id> (raw lifetime value) / pctile:<id> (lifetime percentile, direction-corrected so desc is always best-first). Unmeasured values always sort last.
  • na_policy controls wallets missing a weighted metric: exclude (default — only fully-measured wallets qualify) or neutral_50 (missing metrics score a neutral 50th percentile instead).
  • 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:

FieldMeaning
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). Under na_policy: neutral_50 the composite scored it as a neutral P50; under exclude the wallet was dropped if this metric was weighted.
  • true → the percentile in breakdown[id] is a real measured value (even if it happens to be 0).

Recipes to start from

GoalWeights sketch
Pure forecasterbrier 0.5 · calibration ECE 0.3 · IC 0.2
Copyable sharpalpha-vs-mid 0.4 · brier 0.3 · late-entry return 0.3
Steady compounderSharpe 0.4 · max drawdown 0.3 · profit factor 0.3

On this page