Skip to main content

Documentation Index

Fetch the complete documentation index at: https://polynode.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

GET https://api.polynode.dev/v2/copy-pnl/{wallet}

TL;DR — which PnL field do you actually want?

This endpoint returns two different PnL numbers that can look very different for the same wallet. Pick the right one for your use case:
FieldWhat it answersWhen to use
total_realized_pnl_usdc”Did this wallet make money trading?”Default for customer-facing UI. Closest to what other trading platforms call “PnL”. Equivalent to summing per-position WAVG-realized PnL across every position the wallet touched.
actual_pnl_usdc”How much net cash moved through this wallet’s USDC balance from trading?”Diagnostic / slippage-analysis. Used internally to compute slippage_* fields. Inflated for active wallets that hold inventory.
Both numbers are correct — they measure different things. See the worked example below for the math.

Path parameters

wallet
string
required
The Polymarket wallet address (Safe proxy or EOA, lowercased internally).

Query parameters

period
string
default:"30d"
Convenience window preset. One of: 7d, 14d, 30d, 60d, 90d, 180d. Anchored to “now”. If you pass period, you don’t need from.
from
string | number
Start of the window. Accepts YYYY-MM-DD (UTC midnight) or unix seconds. Overrides period if both are passed.
to
string | number
End of the window. Same format as from. Defaults to current time when omitted.
include_trades
boolean
default:"false"
When 1 or true, response includes a trades array with one entry per fill (ts, side, price, shares, actual_usd, backtest_usd). Useful for transparency and debugging.

Response fields

FieldTypeDescription
walletstringEcho of the requested wallet (lowercased)
total_realized_pnl_usdcnumberRealized PnL: signed sum of per-position WAVG-realized P/L across every position the matcher touched in the window. Counts only profit/loss on positions the wallet actually closed (or partially closed). Recommended primary “PnL” for customer UI.
actual_pnl_usdcnumberCashflow PnL over the window: -buys + sells + settlements. Counts every USDC inflow/outflow from trading. Includes inventory bought but not yet sold (so often very negative for active buyers). Used as input to slippage formula.
backtest_copy_pnl_usdcnumberSame cashflow walk with 2% slippage applied to buys (capped at $1) and sells. Settlements unchanged.
slippage_amount_usdcnumberactual_pnl_usdc − backtest_copy_pnl_usdc. The dollar friction the copier eats.
slippage_cost_rate_pctnumber | nullslippage / abs(actual_pnl) × 100. null when abs(actual_pnl) < 1 (denominator unstable).
toxic_for_copyingbooleantrue when slippage_cost_rate_pct > 15.
trade_countintNumber of fills walked in the window.
positions_closedintCount of positions that contributed non-zero realized PnL in the window.
avg_entry_prob_weightednumber | nullPnL-weighted average entry probability across closed positions. `Σ(entry_price ×realized) / Σrealized. Null when Σrealized< $1`.
avg_hold_seconds_weightednumber | nullPnL-weighted average hold duration. Currently always null (WAVG matcher doesn’t track per-segment timestamps; planned for future).
pnl_definitionstringAlways "cashflow" for actual_pnl_usdc. Reminds callers actual_pnl_usdc differs from PM website PnL (which marks open positions).
applied_filtersobjectResolved from (unix), to (unix), and window_days (only set when default 30d window was used).
partialbooleantrue if the underlying walk hit the 30s budget and returned incomplete data.
sourcesobjectBreakdown: window_trades, window_activity, per-event-type counts, cashflow_breakdown (buys/sells/settlements), fifo_breakdown (matcher diagnostics including total_realized_pnl_usdc). For transparency.
tradesarrayOnly present when include_trades=1. One entry per fill.

Understanding cashflow PnL vs realized PnL

The most common source of confusion with this endpoint is “why don’t actual_pnl_usdc and total_realized_pnl_usdc agree?” — they’re answering different questions. Worked example. A wallet’s first 30 days:
Day  | Action                | actual_pnl_usdc | total_realized_pnl_usdc
-----+-----------------------+-----------------+-------------------------
 1   | BUY  100 YES @ $0.40  |     -$40.00     |        $0.00
 5   | SELL  50 YES @ $0.60  |     -$10.00     |       +$10.00
10   | BUY  100 YES @ $0.50  |     -$60.00     |       +$10.00
15   | SELL  80 YES @ $0.55  |     -$16.00     |       +$17.20
30   | (still hold 70)       |     -$16.00     |       +$17.20
Final state explained:
  • actual_pnl_usdc = -$16 — the wallet paid out $16 more USDC than it received over the 30 days.
  • total_realized_pnl_usdc = +$17.20 — actual trading profit on the 130 shares the wallet closed.
  • The wallet still HOLDS 70 shares (cost basis $32.20). Cashflow counts that as money “spent”. Realized ignores it.
Reconciliation: cashflow_pnl + cost_basis_of_open_positions ≈ realized_pnl. In this example: -$16 + $32.20 = $16.20 ≈ $17.20 (small rounding from WAVG cost basis updates). Which to use:
  • For “did the wallet make money trading?” → total_realized_pnl_usdc. This is what trading platforms typically call “PnL” and what users intuitively expect.
  • For “how much net cash flowed through this wallet?” → actual_pnl_usdc. Useful for liquidity analysis and the slippage formula, but not what most people mean by “PnL”.
  • For copy-trading slippage analysis → use slippage_* fields (which are derived from cashflow). The slippage formula needs apples-to-apples cashflow comparison; this is why actual_pnl_usdc is defined as cashflow.
One-line summary: realized = closed-trade profit; cashflow = USDC delta in/out of the wallet. They reconcile when the wallet holds zero open inventory.

Example: default 30-day window

Request:
curl -H "x-api-key: $YOUR_KEY" \
  "https://api.polynode.dev/v2/copy-pnl/0x0c73d8abc3cd918bc62d0204e33ee05c3f5a68e7"
Response (200 OK):
{
  "wallet": "0x0c73d8abc3cd918bc62d0204e33ee05c3f5a68e7",
  "actual_pnl_usdc": -5697.9,
  "backtest_copy_pnl_usdc": -8932.81,
  "slippage_amount_usdc": 3234.91,
  "slippage_cost_rate_pct": 56.77,
  "toxic_for_copying": true,
  "trade_count": 1726,
  "avg_entry_prob_weighted": 0.4892,
  "avg_hold_seconds_weighted": null,
  "positions_closed": 142,
  "total_realized_pnl_usdc": -2412.06,
  "pnl_definition": "cashflow",
  "applied_filters": {
    "from": 1774916101,
    "to": 1777508101,
    "window_days": 30
  },
  "partial": false,
  "sources": {
    "window_trades": 1726,
    "window_activity": 51,
    "activity_breakdown": { "redemption": 51 },
    "cashflow_breakdown": {
      "actual_buy_cost": 85766.5,
      "actual_sell_rev": 77266.87,
      "settlement_in": 2801.73,
      "settlement_out": 0
    },
    "fifo_breakdown": {
      "over_sells": 3,
      "unresolved_activity": 0,
      "total_abs_pnl_usdc": 4823.18,
      "total_realized_pnl_usdc": -2412.06
    },
    "fetch_ms": 1361
  }
}
Reading this response: the wallet’s realized trading P/L is **-2,412(lost 2,412** (lost ~2.4K on 142 positions that closed in the window). The cashflow says **-5,697theextra5,697** — the extra 3,285 is the cost basis of positions still open at window-end (inventory the wallet bought but hasn’t sold yet). For “did this wallet make money?” the answer is total_realized_pnl_usdc — and the answer is no.

Example: 7-day window via preset

Request:
curl -H "x-api-key: $YOUR_KEY" \
  "https://api.polynode.dev/v2/copy-pnl/0x9977760c6bd6f824cac834d1a36ee99478d63020?period=7d"
Response (200 OK):
{
  "wallet": "0x9977760c6bd6f824cac834d1a36ee99478d63020",
  "actual_pnl_usdc": 8082.45,
  "backtest_copy_pnl_usdc": 7077.07,
  "slippage_amount_usdc": 1005.38,
  "slippage_cost_rate_pct": 12.44,
  "toxic_for_copying": false,
  "trade_count": 1753,
  "avg_entry_prob_weighted": 0.5210,
  "avg_hold_seconds_weighted": null,
  "positions_closed": 87,
  "total_realized_pnl_usdc": 6428.91,
  "pnl_definition": "cashflow",
  "applied_filters": {
    "from": 1776903319,
    "to": 1777508119,
    "window_days": null
  },
  "partial": false,
  "sources": {
    "window_trades": 1753,
    "window_activity": 144,
    "activity_breakdown": { "redemption": 144 },
    "cashflow_breakdown": {
      "actual_buy_cost": 339438.1,
      "actual_sell_rev": 596.28,
      "settlement_in": 346924.27,
      "settlement_out": 0
    },
    "fifo_breakdown": {
      "over_sells": 0,
      "unresolved_activity": 0,
      "total_abs_pnl_usdc": 12857.82,
      "total_realized_pnl_usdc": 6428.91
    },
    "fetch_ms": 632
  }
}

Example: explicit date range

Request — score the window from April 15 to April 25 inclusive:
curl -H "x-api-key: $YOUR_KEY" \
  "https://api.polynode.dev/v2/copy-pnl/0x9977760c6bd6f824cac834d1a36ee99478d63020?from=2026-04-15&to=2026-04-25"
Response (200 OK):
{
  "wallet": "0x9977760c6bd6f824cac834d1a36ee99478d63020",
  "actual_pnl_usdc": -70450.73,
  "backtest_copy_pnl_usdc": -74413.34,
  "slippage_amount_usdc": 3962.61,
  "slippage_cost_rate_pct": 5.62,
  "toxic_for_copying": false,
  "trade_count": 3787,
  "pnl_definition": "cashflow",
  "applied_filters": {
    "from": 1776211200,
    "to": 1777075200,
    "window_days": null
  },
  "partial": false,
  "sources": { "...": "..." }
}

Example: per-fill drill-down

Request:
curl -H "x-api-key: $YOUR_KEY" \
  "https://api.polynode.dev/v2/copy-pnl/0x0c73d8abc3cd918bc62d0204e33ee05c3f5a68e7?period=7d&include_trades=1"
Response (200 OK, truncated):
{
  "wallet": "0x0c73d8abc3cd918bc62d0204e33ee05c3f5a68e7",
  "actual_pnl_usdc": -5027.89,
  "backtest_copy_pnl_usdc": -6783.42,
  "slippage_amount_usdc": 1755.53,
  "slippage_cost_rate_pct": 34.92,
  "toxic_for_copying": true,
  "trade_count": 838,
  "pnl_definition": "cashflow",
  "trades": [
    {
      "ts": 1777232322,
      "side": "BUY",
      "price": 0.7916,
      "shares": 252.66,
      "actual_usd": 200,
      "backtest_usd": 204
    },
    {
      "ts": 1777232322,
      "side": "SELL",
      "price": 0.21,
      "shares": 48,
      "actual_usd": 10.08,
      "backtest_usd": 9.88
    }
  ]
}

Errors

400 Invalid period:
{ "error": "Invalid period. Allowed: 7d, 14d, 30d, 60d, 90d, 180d" }
401 No API key:
{ "error": "API key required. Pass via ?key= or x-api-key header." }
403 Free tier:
{ "error": "V2 endpoints require a paid plan. See polynode.dev/pricing for details." }
429 Rate limited:
{
  "error": "copy-pnl rate limited (1 req per 5s per key).",
  "retryAfterMs": 4231
}
The Retry-After header (in seconds, rounded up) is also set on 429 responses. The rate limit is shared across all /v2/copy-pnl/* calls per API key — calling the endpoint with different wallets or different params still counts toward the same 1 req per 5 seconds budget.

Notes

  • Wallet address is case-insensitive. Internally lowercased.
  • Time precision: when from is YYYY-MM-DD, it resolves to that date at 00:00 UTC. Pass unix seconds for sub-day precision.
  • Settlement events (redemption, merge, split, neg_risk_conversion) are processed at face value on both actual_pnl and backtest_copy_pnl, so they cancel out in slippage_amount but remain in the absolute PnL numbers.
  • High-volume wallets: the underlying walk is paginated by time bucket and runs in parallel. Wallets with up to ~1.6M fills in the window have been validated. Beyond that, pass a tighter window or expect partial: true.