LIVE
▲ $39.5M 0x5b5d…c060 +21.8% today▲ $38.6M 0xb83d…6e36 +22.6% today▲ $8.9M 0xfc66…ca06 +8.2% today▲ $8.3M 0x45d2…4029 +39.7% today▲ $7.9M 0xdd53…2b13 +13.1% today▲ $6.9M 0xf5d8…ad53 +9.4% today▲ $6.6M 0xb695…a92d +15.2% today▲ $6.5M BobbyBigSize +16.7% today▲ $5.5M 0x87f9…e2cf +5.5% today▲ $5.4M 0x0242…ffa1 +36.6% today▲ $4.9M 0x8def…2dae +9.2% today▲ $4.9M 0xdf95…7d72 +13.9% today▲ $39.5M 0x5b5d…c060 +21.8% today▲ $38.6M 0xb83d…6e36 +22.6% today▲ $8.9M 0xfc66…ca06 +8.2% today▲ $8.3M 0x45d2…4029 +39.7% today▲ $7.9M 0xdd53…2b13 +13.1% today▲ $6.9M 0xf5d8…ad53 +9.4% today▲ $6.6M 0xb695…a92d +15.2% today▲ $6.5M BobbyBigSize +16.7% today▲ $5.5M 0x87f9…e2cf +5.5% today▲ $5.4M 0x0242…ffa1 +36.6% today▲ $4.9M 0x8def…2dae +9.2% today▲ $4.9M 0xdf95…7d72 +13.9% today

Scoring method

How every number on the site is computed. The same 0–100 for a day-trader, a long-term investor or a fund — return, risk, consistency, track length. Deterministic and published: if you can't reproduce a number on this site from the raw data and the code below, that's a bug and we'll say so.

For the plain-language version — the ways to prove a record, the exchanges, what an owner controls — see How it works.

1 · proof

Proving who owns a record

A wallet signature (on-chain) or a read-only exchange key (encrypted, and restricted to our server where the exchange allows), or a code a copy-trading lead places in their exchange profile.

2 · return

Time-weighted, percent only

Each period's gain is measured against the money in the account at that time, and the periods are chained. Deposits and withdrawals count as neither gains nor losses. Shown next to the capital band.

3 · score

One score, every window

Drawdown floored at 5%, return capped at 300%, so tiny drawdowns can't explode the score. Sub-$1k wallets, market-maker turnover and deposit artifacts are excluded — and coarser data never outranks finer.

How channel verification works

demo · sample code

The ✓ needs two proofs: the trading record, and the channel. For the channel, you place your code where only the owner can edit — try it:

  1. Copy your code

    PROVE-4F2A9C
  2. Paste it into your channel description. Only you can edit that.

    YouTube Studio → Customization → Description

    Channel description

    PROVE-4F2A9C
  3. Press Verify channel.

    YouTube, Telegram, Discord, Whop and websites are checked on the spot.


This demo checks nothing — your real code is generated when you claim. X, Instagram and TikTok are checked by a person, usually within a day. Your claim is held while that happens.

Per-exchange basis rules

Bands: on Hyperliquid the capital band comes from the minimum equity in the window (a wallet that started small is a small wallet); copy-trading and GMX records band on the source's own capital figure.

Exclusions, exactly: wallets under $1k minimum equity, market-maker turnover (30-day volume over 2,000× equity), and anomalies (over +100% with under 5% drawdown — a deposit artifact until proven otherwise).

Hyperliquid windows are time-weighted. Each step's PnL is measured against the equity standing before it, so deposits can't inflate a return, withdrawals can't fake a drawdown, and a return below −100% is impossible. Steps taken on near-zero equity are bridged, never guessed.

No activity = no score. A window with no PnL movement doesn't exist for scoring — a dead account can never outrank a live one on "zero drawdown, perfect consistency".

The headline score is history-weighted. 30% of it is the record's 30-day window score, 40% the 1-year, 30% the max — and windows a record hasn't earned yet count as neutral 50, so nothing tops the board without a proven year. A max window spanning under 45 days also counts as neutral 50: it would just be the same month twice. Consistency inside the 7-day, 30-day and max windows is gain-to-pain (total gains over total losses) scaled down by the best day's share of those gains — one 2,000% trade with no losses scores near zero on consistency, because it's a lottery ticket, not a method. In the 1-year window, consistency is the month hit-rate: how many of the last 12 months ended green. Two loud months and ten dead ones read as 2/12, not as a good year — and one trade can only ever paint one month. On top of that, integrity guards: a record whose long windows show a ≥95% drawdown caps at 69.9 and is flagged (the same cap, with its own flag, when a long window can't be checked at all because its balance history passes through zero); a long record with negative lifetime PnL caps at 84.9 and is flagged. A hot month never buries the full history.

One reference, frozen daily. Every component is ranked against the scored board as it stood when the day's reference was set; that reference is rebuilt once a day, and only from a healthy board — when an exchange's feed goes quiet, the previous one stands (for up to a week) instead of re-basing everyone on who is left. A record's score moves when the record moves. The reference is published as JSON, so any score can be recomputed.

Source selection bias, disclosed: KuCoin's copy-trading API lists only its current leaderboard — winners by construction — so read the KuCoin board as "the best of KuCoin", never a cross-section. Binance and OKX list losing leads too. On Hyperliquid and GMX the data is public on-chain, but the accounts we score are a selection: on Hyperliquid, named accounts plus accounts picked by 30-day return in each capital band; on GMX, the top accounts by return and by profit. None of the scored boards is a cross-section of all traders.

Coarser data must not outrank finer data: records built from weekly PnL (OKX copy-trading stats) use a 15% drawdown floor and a 100% return cap; records built from realized-only PnL (GMX — open-position drawdowns invisible) use a 10% drawdown floor.

Copy-trading bases: Binance reports ROI on margin, so we re-base to equity (base = AUM minus window PnL, floored at 20% of AUM) — conservative, but an approximation where the source hides deposit flows. KuCoin windows use the exchange's own equity ratios, falling back to the lead principal when a ratio is degenerate. Where a source only provides 90 days (Binance, KuCoin) or 12 weeks (OKX), MAX means the longest window that source publishes — the Track figure shows the account's real age.

Why return alone can't rank — a worked example

+120% sounds better than +40%. Until you see the path.

Trader30d returnWorst dipUp-daysPnL Score
A — steady compounder+40%8%71%higher
B — coin flip that landed+120%60%52%lower

B out-returns A — and return is the biggest weight (50% in the 7-day and 30-day frames, 40% in the 1-year and max). But B's followers who joined mid-path could have lost more than half before any win, and the curve shows it: the risk and consistency components pull B below A.

Score tiers

Diamond ≥ 99 · Gold ≥ 95 · Silver ≥ 85 · Bronze ≥ 60. These are score thresholds, not shares of the board: the higher tiers are rare by design.

The three states of a record

Unclaimed — the numbers are computed by us from public data, and the owner is unknown. Owner proven · identity not linked — a wallet signature or a read-only key, or a code in the exchange profile, shows who controls the account, but no public channel is linked, so who they are isn't confirmed. A page can stay this way. ✓ Verified — owner proven, and the identity confirmed on a channel they control. A copy-trading profile that is only linked proves nothing and never earns the ✓. A code in the profile proves the record is theirs; with the identity confirmed, it can earn the ✓. A record built from a read-only key starts the day the key is connected.

Data sources

Hyperliquid: the public leaderboard (day, week, month and all-time windows, refreshed hourly) and each scored account's value and portfolio history (named and claimed accounts hourly, the rest about once a day) — on-chain balances are public by nature; we display bands, but anyone can look them up. OKX: the official public copy-trading API — lead-trader stats, 12 weeks of PnL, win ratio, copier PnL. Binance: public copy-trading lead portfolios — 90 days of daily ROI, AUM, PnL and win rate per lead, re-based to equity. KuCoin: public copy-trading leaderboard — lead principal plus 7/30/90-day daily PnL curves and the exchange's own ratios. Bybit, Gate, HTX and MEXC: their public copy-trading boards, each lead's daily figures checked against the exchange's own totals; a record that contradicts itself is left out. GMX (Arbitrum and Avalanche): public on-chain data, per-account realized PnL history. How open each exchange is about its traders' results is graded on Exchange openness.

The exact engine

The scoring code, served from the same file the site imports, so it can't drift from what runs:

"""The open part of the product: every number on a page comes from here, deterministically."""
from __future__ import annotations

def max_drawdown(equity: list[float]) -> float:
    """Max peak-to-trough drawdown as a fraction (0.12 = 12%) over an equity curve."""
    peak, mdd = None, 0.0
    for v in equity:
        if v is None: continue
        if peak is None or v > peak: peak = v
        if peak and peak > 0:
            # An equity series that dips below zero (a wiped account with a negative point)
            # would make this exceed 1.0 — a 1,388% drawdown was on a record page. A fall
            # cannot exceed everything, so the floor of the curve is zero.
            dd = (peak - max(v, 0.0)) / peak
            if dd > mdd: mdd = dd
    return mdd

def equity_from_pnl(start_value: float, pnl_history: list[float]) -> list[float]:
    """Equity curve implied by cumulative PnL on a fixed starting value — deposits/withdrawals don't move it (time-weighted approximation)."""
    start = max(start_value, 1.0)
    return [start + p for p in pnl_history]

FLOORS = {"daily": 0.05, "weekly": 0.15, "realized": 0.10}
CAPS = {"daily": 3.0, "weekly": 1.0, "realized": 3.0}

def score(roi: float, mdd: float, trades_or_volume_ok: bool, band_label: str, resolution: str = "daily") -> float | None:
    """ELIGIBILITY GATE + risk-adjusted component (calmar). Returns None for ineligible records:
    - minimum equity under $1k in the window (lottery accounts can't qualify by pumping a tiny stake);
    - inactive / market-maker turnover;
    - anomalies: >100% return with <5% visible drawdown at daily/realized resolution — artifact until proven otherwise.
    The ratio itself uses per-resolution drawdown floors and return caps, because coarser data
    (weekly copy stats, realized-only curves) must not outrank finer data on missing information.
    The public 0-100 score is computed from this + return + consistency + track length in composite()."""
    if not trades_or_volume_ok or band_label == "<$1k": return None
    if resolution in ("daily", "realized") and roi > 1.0 and mdd < 0.05: return None
    return round(min(roi, CAPS.get(resolution, 3.0)) / max(mdd, FLOORS.get(resolution, 0.05)), 2)

def _pct_sorted(sv):
    """Mid-rank percentile function over an already sorted population (rank-based → robust to outliers and caps)."""
    import bisect
    n = max(len(sv), 1)
    return lambda v: (bisect.bisect_left(sv, v) + bisect.bisect_right(sv, v)) / 2 / n

def _pct_rank(values):
    return _pct_sorted(sorted(values))

W_RET, W_RISK, W_CON, W_TRK = 0.40, 0.25, 0.20, 0.15          # long windows (1y, max)
WEIGHTS_LONG = (W_RET, W_RISK, W_CON, W_TRK)
WEIGHTS_SHORT = (0.50, 0.20, 0.20, 0.10)                       # 7d + 30d: return leads harder, consistency keeps its anti-lottery weight

def _parts(i):
    """One record's four inputs: return, return per unit of drawdown, consistency, track length.
    Never rounded: the stored reference keeps full precision (JSON round-trips floats exactly), so a
    record scores the same against a reference in memory and against the same one read from disk."""
    return (i["ret"], i["ret"] / max(i["mdd"], FLOORS.get(i["res"], 0.05)), i["consist"], i["track"])

def _raw(parts, ref, weights):
    w_ret, w_risk, w_con, w_trk = weights
    return (w_ret * _pct_sorted(ref["ret"])(parts[0]) + w_risk * _pct_sorted(ref["cal"])(parts[1])
            + w_con * _pct_sorted(ref["con"])(parts[2]) + w_trk * _pct_sorted(ref["trk"])(parts[3]))

def reference(items: list[dict], weights: tuple = WEIGHTS_LONG) -> dict | None:
    """The population a score is measured against, frozen: the sorted values of each input and of
    the blended result. Scoring against a stored reference means a score moves when the record
    moves — not when another exchange's feed goes quiet and its traders drop out of the comparison."""
    if not items: return None
    parts = [_parts(i) for i in items]
    ref = {"ret": sorted(p[0] for p in parts), "cal": sorted(p[1] for p in parts),
           "con": sorted(p[2] for p in parts), "trk": sorted(p[3] for p in parts), "n": len(items)}
    ref["raw"] = sorted(_raw(p, ref, weights) for p in parts)
    return ref

def composite(items: list[dict], weights: tuple = WEIGHTS_LONG, ref: dict | None = None) -> list[dict]:
    """The public PnL Score, 0-100: literally 'beats X% of every scored trader on the board'.
    Weighted blend of percentiles — return, risk-adjusted return, consistency, track length —
    then re-percentiled so the number IS a percentile. Short windows weigh return at 50%
    (impressive weeks/months should look impressive); long windows keep 40/25/20/15 so
    history stays strict. items: [{"ret", "mdd", "res", "consist", "track"}] (already gated).
    ref: the reference population (see reference()); without one the items are their own."""
    if not items: return []
    ref = ref or reference(items, weights)
    p_ret, p_cal, p_con, p_trk, p_raw = (_pct_sorted(ref[k]) for k in ("ret", "cal", "con", "trk", "raw"))
    out = []
    for i in items:
        parts = _parts(i)
        out.append({"score": min(round(100 * p_raw(_raw(parts, ref, weights)), 1), 99.9),
                    "comps": {"ret": int(100 * p_ret(parts[0])), "risk": int(100 * p_cal(parts[1])),
                              "steady": int(100 * p_con(parts[2])), "track": int(100 * p_trk(parts[3]))}})
    return out

def return_on_average_equity(pnl_total: float, account_values: list[float]) -> float | None:
    """Our own 30-day return: total PnL over average equity in the window. Deposits raise the denominator, so they can't inflate it."""
    vals = [v for v in account_values if v and v > 0]
    if not vals: return None
    return pnl_total / (sum(vals) / len(vals))

def score_math(comps: dict | None) -> str:
    """The score's own components, for display next to it — every number shows its work.
    Returns "" (never None) when a source publishes only a single window: Jinja prints None
    as the word None, and it did, on ~1,900 record pages."""
    if not comps: return ""
    return f"return {comps.get('ret', '—')}p · risk {comps.get('risk', '—')}p · steadiness {comps.get('steady', '—')}p · track {comps.get('track', '—')}p"

def pct(x) -> str:
    try:
        v = float(x) * 100
        return "0.0%" if abs(v) < 0.05 else f"{v:+.1f}%"   # never "-0.0%"
    except Exception: return "—"

TIERS = [(99, "Diamond"), (95, "Gold"), (85, "Silver"), (60, "Bronze")]  # percentile cutoffs: Diamond = top 1%
def tier(score) -> str | None:
    if score is None: return None
    for lim, name in TIERS:
        if score >= lim: return name
    return None

def monthly_from_stamped(stamped, ms_hint=None):
    """Calendar-month returns from a dated (timestamp, index) series.

    Must run BEFORE any downsampling: connectors compress a multi-year curve to 60 points for
    display, at which point one point spans ~15 days and months are no longer recoverable. A
    month is only emitted if the series actually covers it — a gap stays a gap rather than
    being interpolated into a fabricated 0.0%.

    Returns [{"ym": "2026-08", "roi": 0.121, "mdd": 0.043, "pts": 22}, ...] oldest first.
    """
    import time as _t
    if not stamped or len(stamped) < 2:
        return []
    norm = []
    for ts, v in stamped:
        ts = float(ts)
        if ts > 1e11:            # milliseconds
            ts /= 1000.0
        if ts < 946_684_800 or v is None or v <= 0:   # before 2000, or a non-positive index
            continue
        norm.append((ts, float(v)))
    if len(norm) < 2:
        return []
    norm.sort()
    buckets = {}
    for ts, v in norm:
        ym = _t.strftime("%Y-%m", _t.gmtime(ts))
        buckets.setdefault(ym, []).append(v)
    out, prev_close = [], None
    for ym in sorted(buckets):
        vals = buckets[ym]
        opening = prev_close if prev_close is not None else vals[0]
        if opening <= 0:
            prev_close = vals[-1]; continue
        peak, dd = opening, 0.0
        for v in [opening] + vals:
            if v > peak: peak = v
            if peak > 0: dd = max(dd, (peak - v) / peak)
        # Store the EXACT ratio — no rounding, no floor. The month figures must telescope
        # back to the headline return, and across a near-zero pass (an account wiped to
        # 0.01% and rebuilt) the product is hypersensitive: rounding one month to four
        # places collapsed the compounding to zero, and flooring it at -99.9999% overstated
        # the recovery sixty-fold. The TWR already floors each STEP at -99.99%, so the index
        # never reaches exactly zero and the raw ratio is always finite. Round at display.
        out.append({"ym": ym,
                    "roi": vals[-1] / opening - 1,
                    "mdd": round(min(dd, 1.0), 4),
                    "pts": len(vals)})
        prev_close = vals[-1]
    return out


def monthly_grid(monthly):
    """Reshape monthly rows into calendar years for display.

    Absent months stay None — never 0.0%. A month the source could not cover and a month the
    trader was flat are different facts, and a registry may not blur them. Year figures are
    compounded from the months actually present, and say how many that was."""
    if not monthly:
        return []
    by_year = {}
    for m in monthly:
        try:
            y, mo = m["ym"].split("-"); y, mo = int(y), int(mo)
        except (KeyError, ValueError):
            continue
        by_year.setdefault(y, {})[mo] = m
    rows = []
    for y in sorted(by_year, reverse=True):
        cells = [by_year[y].get(i + 1) for i in range(12)]
        got = [c for c in cells if c]
        comp = 1.0
        for c in got:
            comp *= 1 + (c["roi"] or 0)
        rows.append({"year": y, "cells": cells,
                     "roi": round(comp - 1, 4),
                     "mdd": round(max((c["mdd"] or 0) for c in got), 4) if got else 0.0,
                     "n": len(got)})
    return rows


# ---- window reliability: one rule, used by scoring AND by every page that shows a window ----
IMPLAUSIBLE_GAIN = 100.0   # +10,000% over the window
CROSS_CHECK = 20.0         # ...and more than 20x what the exchange itself reports for the span
STALE_AFTER = 72 * 3600    # numbers older than this leave the board until they are refreshed


def assess_windows(ex: dict, venue_roi: dict) -> dict:
    """Mark, in place, every window whose numbers we can't stand behind. Returns the problems.
    crossed    — the reconstructed equity went below zero (a return under -100%): impossible, so
                 the whole record is unscored.
    implausible— a gain over +10,000% that is more than 20x the exchange's own figure for the
                 span, or that no exchange figure corroborates: deposits/withdrawals the curve
                 couldn't separate. The window is hidden and counts as neutral in the blend."""
    out = {"crossed": [], "implausible": []}
    for k, w in list((ex.get("windows") or {}).items()):
        if not isinstance(w, dict): continue
        w.pop("unreliable", None)
        roi = w.get("roi")
        pts = [p for p in (w.get("pts") or []) if isinstance(p, (int, float))]
        # at or past the floor: a curve pinned at exactly -100% (or the TWR's -99.99% step floor) is the
        # clamp talking, not a result — an account that really went to zero is under $1k and never scored
        if (roi is not None and roi <= -0.9999) or any(p <= -99.99 for p in pts):
            w["unreliable"] = "crossed zero"; out["crossed"].append(k); continue
        if roi is not None and roi > IMPLAUSIBLE_GAIN:
            ref = venue_roi.get(k)
            if ref is None or roi > CROSS_CHECK * max(ref, 0.05):
                w["unreliable"] = "implausible"; out["implausible"].append(k)
    return out


def stamp(win: dict | None, ts: list) -> dict | None:
    """Give a window its own time axis: the first and last point's time (seconds), plus every
    point's time when they aren't evenly spaced. Pages draw dates from these — never from today's
    date and an assumed spacing."""
    if not win or not ts or len(ts) != len(win.get("pts") or []): return win
    ts = [float(x) / 1000.0 if float(x) > 1e11 else float(x) for x in ts]
    t0, t1 = ts[0], ts[-1]
    if t0 < 946_684_800 or t1 < t0: return win
    win["t0"], win["t1"] = int(t0), int(t1)
    n = len(ts)
    if n > 2 and t1 > t0 and max(abs(x - (t0 + (t1 - t0) * i / (n - 1))) for i, x in enumerate(ts)) > 0.02 * (t1 - t0):
        win["t"] = [int(x) for x in ts]
    return win


def point_times(win) -> list | None:
    """The time (seconds) of every point of a window's curve, or None when the window carries no time
    axis. A duration on a page is measured on these — a count of points is not a count of days."""
    if not isinstance(win, dict): return None
    pts = win.get("pts") or []
    t = win.get("t")
    if isinstance(t, list) and len(t) == len(pts) and len(pts) >= 2: return [float(x) for x in t]
    t0, t1 = win.get("t0"), win.get("t1")
    if t0 and t1 and len(pts) >= 2 and t1 > t0:
        return [t0 + (t1 - t0) * i / (len(pts) - 1) for i in range(len(pts))]
    return None


def reliable(windows) -> dict:
    """The windows a page may chart, share or rank: everything not marked unreliable."""
    return {k: w for k, w in (windows or {}).items() if isinstance(w, dict) and not w.get("unreliable")}

Daily board hashes

Every re-rank computes a SHA-256 of the full scored board — sha256(json([[address, score], …])) over all records ordered by score, then 30-day return, then address — and this table keeps the last one of each day. To check one, page through the API with &offset= right after an update and hash it yourself; if history were ever quietly edited, it would stop matching in public.

DateRecordsSHA-256
2026-10-1513526f03eab2c0a62e5d1d0c9eab0c54513246d47375fbec9f31c797ac3df5104de8d
2026-10-141353356a6dc00fb27523641ab839b73383ffbe4d8f4a48a6b087b4aaef00139100953
2026-10-13135780f415e0084b10db2fc1f01dbbf732058b1e7d812ee132a3ef9c39d72712cdb65
2026-10-12110900f8c36bbec0952435f3ff20a7a53bcdcc82153a74a0268d366db66b3e2f78d3e

Seen someone claim big returns?

Make them prove it. Check any trader in seconds — and if they're not verified, one click starts the public count that gets them there. Free, no account.