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

For developers

Everything a record page shows is available as JSON: the curves, the proof level, the score and the inputs it was computed from. No key and no account. The data is public and read-only.

One record

GET https://provepnl.com/api/record/address

The address is what follows /w/ in a record's link. Example: /api/record/0x176a657e1d013b6f97f6b…

statusverified (owner proven and identity confirmed), owner-proven, unclaimed, or preview (a wallet someone looked up: not listed, not ranked)
ownerThe trader page this record belongs to, when someone has proven it is theirs: handle, link, whether it carries the ✓, the channel the identity was confirmed on, and the kind of proof.
sourceWhere the numbers come from: on-chain data, an exchange's public copy-trading board, or a read-only key.
as_ofWhen the numbers were last computed (Unix seconds).
score, tier, rank, of, topThe 0 to 100 score, its tier, and where the record stands among all scored records.
score_partsThe three periods the score blends: 30 days (30%), 1 year (40%), all time (30%). A period the record has not earned yet counts as 50.
score_inputsPer period: return, worst fall, consistency and track length, exactly as they went into the score.
windowsFor 7d, 30d, 1y and max: return, worst fall (max_drawdown), score, and points: the cumulative return in percent from the start of the window. t0 and t1 are the first and last point's time; t lists every point's time when they are not evenly spaced.
windows_hiddenPeriods we do not publish for this record, with the reason.
monthsReturn and worst fall for each calendar month on record.
capital_band, usdThe capital band, unless the owner hides it. Dollar figures only where the record's page shows them.
flags, stale, pausedWarnings shown on the page, whether the numbers are too old for the board, and whether a read-only key has stopped answering.

One trader page

GET https://provepnl.com/api/trader/handle

Status, the channel the identity was confirmed on, every proven account with its kind of proof, and which record is the headline.

The board

GET https://provepnl.com/api/leaderboard?limit=100&offset=0&win=30d&venue=hyperliquid

win is 7d, 30d, 1y or max. venue is optional: hyperliquid, binance, okx, kucoin, bybit, gate, htx, mexc, gmx, gmx-avax. Up to 5,000 rows a call.

Recompute a score yourself

A score is a ranking against a fixed population. That population is frozen once a day and published, so a score only moves when the record moves.

GET https://provepnl.com/api/reference

With a record's score_inputs and this reference you can reproduce each period's score. This runs as it is:

import bisect, gzip, json, urllib.request
BASE = "https://provepnl.com"
def get(path):
    req = urllib.request.Request(BASE + path, headers={"Accept-Encoding": "gzip", "User-Agent": "recompute-example"})
    with urllib.request.urlopen(req) as r:
        body = r.read()
        return json.loads(gzip.decompress(body) if r.headers.get("Content-Encoding") == "gzip" else body)

rec = get("/api/record/0x176a657e1d013b6f97f6beef3f5fbbfb07193fb4")
ref = get("/api/reference")

def pct(sorted_values, v):          # mid-rank percentile
    n = max(len(sorted_values), 1)
    return (bisect.bisect_left(sorted_values, v) + bisect.bisect_right(sorted_values, v)) / 2 / n

def period_score(period):
    i, r = rec["score_inputs"][period], ref["windows"][period]
    w_ret, w_risk, w_con, w_trk = ref["weights"][period]
    cal = i["ret"] / max(i["mdd"], ref["drawdown_floor"].get(i["res"], 0.05))
    raw = (w_ret * pct(r["ret"], i["ret"]) + w_risk * pct(r["cal"], cal)
           + w_con * pct(r["con"], i["consist"]) + w_trk * pct(r["trk"], i["track"]))
    return min(round(100 * pct(r["raw"], raw), 1), 99.9)

for part in rec["score_parts"]:
    if part["score"] is not None:
        print(part["window"], "published", part["score"], "recomputed", period_score(part["window"]))
print("page score", rec["score"], "blend of the parts", round(sum(p["weight"] * p["counts_as"] for p in rec["score_parts"]), 1))

Two caps can make the page score lower than the blend, and both are shown on the page as a warning: an account that lost 95% or more at some point, or whose longer history can't be checked, is capped at 69.9, and one whose lifetime result is a loss is capped at 84.9. The full scoring code is on the scoring method page.

A live card for your site

A small card that always shows the current numbers and links to the full record. It says exactly what the page says: only a verified trader's card carries the ✓.

<iframe src="https://provepnl.com/embed/w/address" width="340" height="190" style="border:0;border-radius:14px" loading="lazy" title="Trading record on ProvePnL"></iframe>
<iframe src="https://provepnl.com/embed/t/handle" width="340" height="190" style="border:0;border-radius:14px" loading="lazy" title="Trading record on ProvePnL"></iframe>

Add ?theme=light or ?theme=dark to fix the colours; otherwise the card follows the visitor's system setting.

Fair use