CRIUS Docs
API status Get API key
Concepts

How trader and wallet PnL is computed on Robinhood Chain

How the Crius API computes realized and unrealized PnL, ROI, cost basis and win rate for Robinhood Chain traders and wallets, worked through on real trades.

Updated 3 min read

Crius computes PnL with the average-cost method on the trade tape: every buy and sell is valued in USD at the moment it happened, the tokens sold are charged at the average price paid for all tokens bought, and what is still held is valued at the current price. Realized PnL is what the sells made over that cost; unrealized PnL is the current value of the balance over its cost. Top traders of a token, a wallet’s profile, holder snapshots and holder cohorts all use the same rules.

A trader’s PnL, worked through

CHARGE's top three traders by PnL

Who made the most on Charge AI (0x7b8892cea949bc60d494681d1bc3ea96071ab0b8), and how is their PnL calculated?

GET/v1/robinhood/token/0x7b8892cea949bc60d494681d1bc3ea96071ab0b8/traders?limit=3
curl "https://api.crius.sh/v1/robinhood/token/0x7b8892cea949bc60d494681d1bc3ea96071ab0b8/traders?limit=3" \
-H "x-api-key: $CRIUS_API_KEY"
Real response 200 Captured Oct 28, 2026 · 91 lines (arrays trimmed)
{
"data": [
{
"address": "0x9e38fedc40987c19396cc20ea59eb2b1b7928881",
"avg_buy_usd": 0.00000842823,
"avg_sell_usd": 0.0000532304,
"balance": 0,
"bought": 29080767.335830362668108408,
"bought_eth": 0.099,
"bought_usd": 245.099,
"buys": 1,
"cost_basis": "complete",
"first_trade_ts": 1791983208000,
"gas_spent_usd": 0.0875722,
"holding_time_s": 118676,
"last_trade_ts": 1792101884000,
"pnl_usd": 1302.88,
"rank": 1,
"realized_pnl_usd": 1302.88,
"roi_pct": 531.573,
"sells": 7,
"sold": 29080767.335830362668108408,
"sold_eth": 0.635695,
"sold_usd": 1547.98,
"tags": [],
"unrealized_pnl_usd": 0
},
{
"address": "0x7c79ad74d36dea424d08a5d326271bf03da81f6a",
"avg_buy_usd": 0.00000790248,
"avg_sell_usd": 0.0000343631,
"balance": 0,
"bought": 46475393.982901663958413733,
"bought_eth": 0.1485,
"bought_usd": 367.271,
"buys": 2,
"cost_basis": "complete",
"first_trade_ts": 1791983183000,
"gas_spent_usd": 0.0712204,
"holding_time_s": 125872,
"last_trade_ts": 1792109055000,
"pnl_usd": 994.45,
"rank": 2,
"realized_pnl_usd": 994.45,
"roi_pct": 270.767,
"sells": 4,
"sold": 37582304.39247174197236032,
"sold_eth": 0.528147,
"sold_usd": 1291.44,
"tags": [],
"unrealized_pnl_usd": 0
},
{
"address": "0x83055f3e7be4e289733bbc8c7bd42a6443177f07",
"avg_buy_usd": 0.000023621,
"avg_sell_usd": 0.0000471805,
"balance": 0,
"bought": 35569497.20243776424709037,
"bought_eth": 0.3465,
"bought_usd": 840.189,
"buys": 3,
"cost_basis": "complete",
"first_trade_ts": 1791988168000,
"gas_spent_usd": 0.0631376,
"holding_time_s": 4039,
"last_trade_ts": 1791992207000,
"pnl_usd": 838,
"rank": 3,
"realized_pnl_usd": 838,
"roi_pct": 99.7395,
"sells": 3,
"sold": 35569497.20243776424709037,
"sold_eth": 0.691944,
"sold_usd": 1678.19,
"tags": [],
"unrealized_pnl_usd": 0
}
],
"meta": {
"block": 100406823,
"block_age_ms": 829,
"block_ts": 1793225080000,
"chain": "robinhood",
"finalized_block": 100397649,
"lag_ms": 64,
"next_cursor": "3",
"pnl_method": "average cost; USD at trade time; unrealized at current price",
"safe_block": 100401329,
"window": "all"
}
}

What this tells you

  • Rank 1, 0x9e38fedc40987c19396cc20ea59eb2b1b7928881: one buy of 29,080,767 CHARGE for bought_usd: 245.099, then seven sells of all of it for sold_usd: 1547.98. Realized PnL is 1,547.98 − 245.099 = realized_pnl_usd: 1302.88, and roi_pct: 531.573 is 1,302.88 ÷ 245.099.
  • Rank 2, 0x7c79ad74d36dea424d08a5d326271bf03da81f6a: bought 46,475,393 tokens for $367.271 (avg_buy_usd: 0.00000790248) and sold 37,582,304 of them for $1,291.44. The sold tokens cost 37,582,304 × 0.00000790248 ≈ $296.99, so realized_pnl_usd is 994.45.
  • Rank 2 also has balance: 0 although it sold only part of what it bought. The other 8.9 million tokens left the wallet without a sale (a transfer), so they count neither as a sale nor as a loss.
  • gas_spent_usd (0.0875722 for rank 1) is reported separately and not subtracted from pnl_usd.
  • cost_basis: "complete" on all three: every token they sold was bought, so the numbers are exact.

The formulas

For one wallet and one token over the window (bought and sold in tokens, bought_usd and sold_usd at trade time):

FieldHow it is computed
avg_buy_usdbought_usd ÷ bought: the average cost of one token
realized_pnl_usdsold_usd − min(sold, bought) × avg_buy_usd
unrealized_pnl_usdCurrent value of balance − min(balance, bought − sold) × avg_buy_usd
pnl_usdrealized_pnl_usd + unrealized_pnl_usd
roi_pctpnl_usd ÷ bought_usd × 100
holding_time_sFrom the first trade to now while the wallet still holds the token, otherwise from the first trade to the last
  • USD at trade time. Each trade’s value_usd is the quote amount times the quote’s USD price at that block. ETH-quoted trades use that block’s ETH/USD (eth_usd_at_trade on the trade tape). Later price moves never change a past trade’s value.
  • Current value. The balance is valued at the token’s current price, capped at what the price pool’s quote side could pay out. Ten million tokens in a pool holding $50 of ETH are never worth more than $50.
  • Windows. /traders takes window=1h, 24h, 7d or all (the default); the wallet profile takes 1d, 7d, 30d (the default) or all. Only trades inside the window count.

When PnL is unknown

When a wallet sold more than it bought, the extra tokens came from somewhere else (a transfer, an airdrop, a launch allocation) and their cost is unknown. Crius doesn’t guess: cost_basis says "unknown (sold tokens received by transfer)", realized_pnl_usd, unrealized_pnl_usd, pnl_usd and roi_pct are null, and the row sorts last when you rank by PnL.

Tokens received by transfer and still held are different: they are part of the balance, valued at the current price, with no cost against them, so they show up as unrealized gain. A dev wallet holding its launch allocation is the common case. Use the dev tag and exclude=dev when you rank traders (see holder classes).

Wallet PnL

The wallet profile applies the same per-token formulas to every token the wallet traded in the window and adds them up. Tokens with an unknown cost are left out of the totals.

pnl fieldMeaning
realized_usd, unrealized_usd, total_usdSums over the tokens with a known cost
win_rate_pctShare of those tokens with a positive total PnL (per token, not per trade)
tokens_traded, tokens_with_known_costHow many tokens the wallet traded, and how many the totals cover
best_trade, worst_tradeThe token with the highest and lowest total PnL
gas_spent_usd, gas_scopeGas for all transactions the wallet sent in the window, trades or not
avg_hold_time_sAverage time from first to last trade per token

A creator wallet with no trades in 30 days

What did CHARGE's creator wallet (0x3ac82d45ff9e4963cefbc3f74dbe293f34c927ca) make or lose in the last 30 days?

GET/v1/robinhood/wallet/0x3ac82d45ff9e4963cefbc3f74dbe293f34c927ca
curl "https://api.crius.sh/v1/robinhood/wallet/0x3ac82d45ff9e4963cefbc3f74dbe293f34c927ca" \
-H "x-api-key: $CRIUS_API_KEY"
Real response 200 Captured Oct 28, 2026 · 84 lines (arrays trimmed)
{
"data": {
"account_type": "smart_account",
"address": "0x3ac82d45ff9e4963cefbc3f74dbe293f34c927ca",
"delegate": "0x63c0c19a282a1b52b07dd5a65b58948a07dae32b",
"holdings": [
{
"balance": 25,
"pnl": null,
"price_confidence": "low",
"token": {
"address": "0xfaaf8ffa8a29ceb6c85aff8d3961f65ae1bfc775",
"logo": null,
"symbol": "USDG"
},
"value_eth": 0.00000500064,
"value_usd": 0.0117789
},
{
"balance": 25,
"pnl": null,
"price_confidence": "low",
"token": {
"address": "0x06993ae1230d3755b54f25022028daa66772bc3d",
"logo": null,
"symbol": "USDG"
},
"value_eth": 0.00000500061,
"value_usd": 0.0117789
}
],
"holdings_truncated": false,
"holdings_value_usd": 0.0235578,
"is_contract": false,
"label": null,
"native_balance": 0.71473787532832551,
"native_value_usd": 1683.56,
"pnl": {
"avg_hold_time_s": null,
"best_trade": null,
"gas_scope": "all transactions sent by the wallet in the window",
"gas_spent_usd": 0.198772,
"realized_usd": 0,
"tokens_traded": 0,
"tokens_with_known_cost": 0,
"total_usd": 0,
"trades": 0,
"unrealized_usd": 0,
"volume_usd": 0,
"win_rate_pct": null,
"window": "30d",
"worst_trade": null
},
"profile": {
"first_seen_block": 91351250,
"first_seen_ts": 1792283372000,
"fresh": false,
"funded_by": {
"address": "0xd782268f600403843484b70710daee6846fc0db8",
"amount_eth": 0.008148386337868892,
"block": 87601233,
"label": "bridge:Relay solver",
"ts": 1791903516300
},
"history": "complete",
"tags": [
"dev",
"smart_account"
],
"tx_count": 14,
"wallet_age_days": 10.8994
}
},
"meta": {
"balances_block": 100406864,
"block": 100406864,
"block_age_ms": 1301,
"block_ts": 1793225084000,
"chain": "robinhood",
"finalized_block": 100397649,
"lag_ms": 59,
"safe_block": 100401329
}
}

What this tells you

  • pnl.window: "30d" is the default window. trades: 0 and tokens_traded: 0: the wallet made no trades in it, so realized_usd, unrealized_usd and total_usd are 0.
  • win_rate_pct, best_trade, worst_trade and avg_hold_time_s are null: with no tokens traded there is nothing to rate.
  • gas_spent_usd: 0.198772 is still counted, because gas_scope is “all transactions sent by the wallet in the window”, not only trades.
  • Each holding has pnl: null here: neither of the two tokens it holds was traded in the window.

Holder endpoints use the same cost basis. The holder snapshot adds cost_basis_usd and unrealized_pnl_usd to each holder at the snapshot’s price, and the holder cohort adds pnl_usd to each wallet that held through the window.

Use it in your app

  1. Rank outside traders. /traders?sort=pnl&window=7d&exclude=dev,sniper gives the best traders of the week who neither created nor sniped the token.

  2. Show the basis. Next to a PnL number, show bought_usd, sold_usd and cost_basis, so users can see what it rests on.

  3. Subtract gas if your users care. pnl_usd − gas_spent_usd is the net result after fees paid to the chain.

// Net PnL of a token's top traders this week, outside traders only (Free plan key).
const url = `https://api.crius.sh/v1/robinhood/token/${token}/traders?sort=pnl&window=7d&exclude=dev,sniper&limit=10`;
const { data } = await (await fetch(url, { headers: { "x-api-key": process.env.CRIUS_API_KEY } })).json();
for (const t of data) {
if (t.pnl_usd === null) continue; // cost unknown: tokens arrived by transfer
const net = t.pnl_usd - (t.gas_spent_usd ?? 0);
console.log(t.rank, t.address, `pnl $${t.pnl_usd}`, `net $${net.toFixed(2)}`, `roi ${t.roi_pct}%`);
}

Frequently asked questions

Does Crius PnL include gas?

No. PnL is computed from the USD value of the trades. Gas is reported next to it as gas_spent_usd so you can subtract it yourself: per token on /traders, and for every transaction the wallet sent in the window on the wallet profile.

Is PnL FIFO or average cost?

Average cost. Every token bought costs the average USD price of all the wallet's buys of that token, so the order of buys and sells doesn't change the result. Each response says so in meta.pnl_method on /traders.

Why is a trader's PnL null?

The wallet sold more tokens than it bought: the extra tokens arrived by transfer, airdrop or launch allocation, so their cost is unknown. Those rows have cost_basis: "unknown (sold tokens received by transfer)" and null PnL, and they sort last when you rank by PnL.

Is the wallet win rate per trade?

Per token. win_rate_pct is the share of tokens with a known cost whose total PnL (realized + unrealized) is positive in the window. Ten losing trades and one big winning trade on the same token count as one win if the token's total PnL is positive.

Which trades count toward a wallet's PnL?

The trades attributed to it as the trader: the address whose balance changed, not the router, solver or ERC-4337 bundler that sent the transaction. See trader attribution.

Get your free Crius API key

Every example on this page works with a free key: higher rate limits than the anonymous plan, holders, wallets and the WebSocket stream. No card needed.

  • Robinhood Chain (4663) only, from our own archive nodes
  • REST + WebSocket
  • Pay on-chain or by card