Copy PnL
curl --request GET \
--url https://api.polynode.dev/v3/wallets/{address}/copy-pnl \
--header 'x-api-key: <api-key>'import requests
url = "https://api.polynode.dev/v3/wallets/{address}/copy-pnl"
headers = {"x-api-key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'x-api-key': '<api-key>'}};
fetch('https://api.polynode.dev/v3/wallets/{address}/copy-pnl', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.polynode.dev/v3/wallets/{address}/copy-pnl",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.polynode.dev/v3/wallets/{address}/copy-pnl"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("x-api-key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.polynode.dev/v3/wallets/{address}/copy-pnl")
.header("x-api-key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.polynode.dev/v3/wallets/{address}/copy-pnl")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["x-api-key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"data": {
"wallet": "0x1111111111111111111111111111111111111111",
"actual_pnl_usdc": "3.000000",
"backtest_copy_pnl_usdc": "2.740000",
"slippage_amount_usdc": "0.260000",
"slippage_cost_rate_pct": "8.666667",
"toxic_for_copying": false,
"trade_count": 2,
"pnl_definition": "cashflow",
"fees_included": false,
"opening_inventory_valued": false,
"open_positions_marked_to_market": false,
"slippage_bps": {
"buy": 200,
"sell": 200,
"buy_price_capped_at": "1.000000"
},
"cashflows": {
"buy_cost_usdc": "5.000000",
"sell_revenue_usdc": "8.000000",
"settlement_in_usdc": "0.000000",
"settlement_out_usdc": "0.000000"
},
"event_counts": {
"buys": 1,
"sells": 1,
"splits": 0,
"merges": 0,
"redemptions": 0,
"neg_risk_conversions": 0
},
"coverage": {
"selection": "latest_wallet_order_fills",
"max_trades": 50000,
"history_truncated": false,
"requested_history": "recent",
"served_history": "full",
"fallback_reason": null,
"lifetime_history_included": true,
"window_start": {
"type": "beginning_of_indexed_history"
},
"window_end": {
"type": "database_snapshot",
"snapshot_at": "2026-09-07T01:00:00.000Z"
},
"first_included_event_timestamp": 1788652800,
"last_included_event_timestamp": 1788739200,
"snapshot_at": "2026-09-07T01:00:00.000Z"
},
"applied_filters": {
"period": null,
"from": null,
"to": 1788742800
},
"realized_pnl_context": {
"available": true,
"scope": "lifetime_wallet",
"date_filters_applied": false,
"source": "api.wallet_pnl_current",
"pnl_definition": "realized",
"total_realized_pnl_usdc": "3.000000",
"position_count": 1,
"open_positions": 0,
"updated_block": "93350000",
"updated_at": "2026-09-07T00:59:00Z",
"refresh_age_seconds": 60,
"latest_indexed_fill_block": "93350000",
"freshness": "no_newer_indexed_fills"
},
"query_ms": 800
},
"cache": {
"status": "miss",
"age_ms": 800
},
"elapsed_ms": 805
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}Wallet Activity
Copy PnL
Estimate copying slippage with date filters, optional full history, and lifetime realized-PnL context.
GET
/
v3
/
wallets
/
{address}
/
copy-pnl
Copy PnL
curl --request GET \
--url https://api.polynode.dev/v3/wallets/{address}/copy-pnl \
--header 'x-api-key: <api-key>'import requests
url = "https://api.polynode.dev/v3/wallets/{address}/copy-pnl"
headers = {"x-api-key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'x-api-key': '<api-key>'}};
fetch('https://api.polynode.dev/v3/wallets/{address}/copy-pnl', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.polynode.dev/v3/wallets/{address}/copy-pnl",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.polynode.dev/v3/wallets/{address}/copy-pnl"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("x-api-key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.polynode.dev/v3/wallets/{address}/copy-pnl")
.header("x-api-key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.polynode.dev/v3/wallets/{address}/copy-pnl")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["x-api-key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"data": {
"wallet": "0x1111111111111111111111111111111111111111",
"actual_pnl_usdc": "3.000000",
"backtest_copy_pnl_usdc": "2.740000",
"slippage_amount_usdc": "0.260000",
"slippage_cost_rate_pct": "8.666667",
"toxic_for_copying": false,
"trade_count": 2,
"pnl_definition": "cashflow",
"fees_included": false,
"opening_inventory_valued": false,
"open_positions_marked_to_market": false,
"slippage_bps": {
"buy": 200,
"sell": 200,
"buy_price_capped_at": "1.000000"
},
"cashflows": {
"buy_cost_usdc": "5.000000",
"sell_revenue_usdc": "8.000000",
"settlement_in_usdc": "0.000000",
"settlement_out_usdc": "0.000000"
},
"event_counts": {
"buys": 1,
"sells": 1,
"splits": 0,
"merges": 0,
"redemptions": 0,
"neg_risk_conversions": 0
},
"coverage": {
"selection": "latest_wallet_order_fills",
"max_trades": 50000,
"history_truncated": false,
"requested_history": "recent",
"served_history": "full",
"fallback_reason": null,
"lifetime_history_included": true,
"window_start": {
"type": "beginning_of_indexed_history"
},
"window_end": {
"type": "database_snapshot",
"snapshot_at": "2026-09-07T01:00:00.000Z"
},
"first_included_event_timestamp": 1788652800,
"last_included_event_timestamp": 1788739200,
"snapshot_at": "2026-09-07T01:00:00.000Z"
},
"applied_filters": {
"period": null,
"from": null,
"to": 1788742800
},
"realized_pnl_context": {
"available": true,
"scope": "lifetime_wallet",
"date_filters_applied": false,
"source": "api.wallet_pnl_current",
"pnl_definition": "realized",
"total_realized_pnl_usdc": "3.000000",
"position_count": 1,
"open_positions": 0,
"updated_block": "93350000",
"updated_at": "2026-09-07T00:59:00Z",
"refresh_age_seconds": 60,
"latest_indexed_fill_block": "93350000",
"freshness": "no_newer_indexed_fills"
},
"query_ms": 800
},
"cache": {
"status": "miss",
"age_ms": 800
},
"elapsed_ms": 805
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}Calculate a wallet’s gross trading cash flow and the cash flow of a simulated copier using Polynode’s indexed fills and settlement events. This replaces the deprecated V2 on-demand endpoint.
The cap counts fills, not positions, tokens, transactions, or distinct orders. The calculation selects the wallet’s own signed-order fill records, including its taker-order execution record. It avoids counting that wallet again as the counterparty to other matched orders. This attribution is different from requesting both address roles on the raw trades endpoint.
There is no default date window. With
The 2% assumption is relative to each execution price. It is a fixed screening model, not a measured execution cost or a forecast. When the absolute gross cash flow is below 1 USDC, both the percentage and
If older matching fills are excluded, settlement inclusion begins at the exact block and log of the first included fill, inclusive. Otherwise, all indexed settlement history within the requested dates is eligible, including events before the first fill. A wallet with settlement activity and no fills can still return a cash-flow result. Explicit timestamps are checked exactly; the database snapshot bounds which events are visible.
Money and percentages are decimal strings with six fractional digits. Event timestamps are Unix seconds.
Authentication, plan access, monthly usage, and account rate-limit failures use the normal V3 edge responses. Do not treat an error as zero PnL.
Request
curl -H "x-api-key: pn_live_YOUR_KEY" \
"https://api.polynode.dev/v3/wallets/0xab03f377164c3726d498e56f7a9398cd8eb74494/copy-pnl"
| Parameter | Location | Default | Description |
|---|---|---|---|
address | Path | Required | 0x followed by 40 hexadecimal characters. Normalized to lowercase. |
max_trades | Query | 50000 | Integer from 1 to 50000. Maximum number of recent wallet-owned order fills to include. |
history | Query | recent | recent, auto, or full. In auto, max_trades is the fallback cap; in full, it is the initial scan size, not a limit on the returned fill count. |
period | Query | None | 7d, 14d, 30d, 60d, 90d, or 180d, ending at to or the calculation snapshot. |
from | Query | None | Inclusive start, as Unix seconds or YYYY-MM-DD at UTC midnight. Overrides period. |
to | Query | Snapshot | Exclusive end, in the same formats. from must be earlier than to. |
history=recent, active wallets can reach 50,000 fills quickly; quieter wallets can cover years. Date filters apply before the fill cap, including to settlement events. A wallet with fewer matching fills returns all matching indexed fills. coverage.history_truncated tells you whether older fills inside the requested date window were excluded by the fill selection.
History modes
recent: return up tomax_tradesrecent fills. This preserves the original V3 default.auto: first complete the recent calculation, then spend up to two additional seconds extending toward all matching history, within the overall calculation deadline. Return full history if it completes; otherwise return the original completed recent calculation with an explicit fallback reason.full: require all matching indexed history. If the work or time limit is reached, return an error instead of a recent-window fallback.
auto and full can return more than 50,000 fills. max_trades remains limited to 50,000 and controls the initial recent calculation. A successful full result contains all matching history, never the first 500,000 fills of a larger history.
For example, request a complete historical month with ?history=full&from=2026-08-01&to=2026-09-01, or use ?history=auto&period=90d to allow an explicit recent-window fallback.
Read coverage.requested_history, served_history (full or recent), and fallback_reason on every result. served_history=full means the requested date window is complete; lifetime_history_included is true only when no date filters or fill truncation restrict the indexed history. Automatic fallback reasons are query_timeout, query_work_limit, activity_limit_exceeded, or history_limit_exceeded. If the initial recent calculation fails, there is no valid fallback and the request returns an error.
Calculation
- Buy: the copier pays
min(price × 1.02, 1.00) × shares. - Sell: the copier receives
price × 0.98 × shares. - Splits consume collateral; merges and redemptions release collateral. Neg-risk conversions include released collateral according to their selected outcomes. These settlement flows receive no simulated slippage.
actual_pnl_usdc = sells - buys + settlement_in - settlement_out
backtest_copy_pnl_usdc = actual_pnl_usdc - slippage_amount_usdc
slippage_cost_rate_pct = slippage_amount_usdc / abs(actual_pnl_usdc) × 100
toxic_for_copying = slippage_cost_rate_pct > 15
toxic_for_copying are null.
pnl_definition is cashflow. Fees are excluded, opening inventory is not valued, and open positions are not marked to market. Sales or redemptions of inventory acquired before the selected window can therefore make this differ substantially from realized or total portfolio PnL. Deposits, withdrawals, and token transfers are outside this calculation. It covers spot order fills, not combo or perpetual trading.Realized-PnL context
realized_pnl_context provides the wallet’s stored lifetime realized profit and position counts alongside the cash-flow simulation. Its total_realized_pnl_usdc is actual realized profit from the wallet summary; it is not the copier’s simulated realized profit and is not used in the slippage or copying flag formulas.
This context always has scope: "lifetime_wallet" and date_filters_applied: false. It is not rewound by from or to and is not a realized-profit result for a historical backtest window. Read updated_at, updated_block, and refresh_age_seconds to see the summary’s last update. latest_indexed_fill_block supports the freshness indicator: behind_indexed_fills, no_newer_indexed_fills, unknown, or unavailable. No newer fills does not prove all settlement or valuation inputs are current.
If no summary is available, available is false and its profit/count/update fields are null, rather than zero. position_count and open_positions describe the stored lifetime summary. They do not reproduce V2’s count of positions with nonzero realized PnL.
Response
The following is an illustrative two-fill calculation: a buy costing 5 USDC followed by a sale returning 8 USDC, with no settlement flows.{
"data": {
"wallet": "0x1111111111111111111111111111111111111111",
"actual_pnl_usdc": "3.000000",
"backtest_copy_pnl_usdc": "2.740000",
"slippage_amount_usdc": "0.260000",
"slippage_cost_rate_pct": "8.666667",
"toxic_for_copying": false,
"trade_count": 2,
"pnl_definition": "cashflow",
"fees_included": false,
"opening_inventory_valued": false,
"open_positions_marked_to_market": false,
"slippage_bps": {"buy": 200, "sell": 200, "buy_price_capped_at": "1.000000"},
"cashflows": {
"buy_cost_usdc": "5.000000",
"sell_revenue_usdc": "8.000000",
"settlement_in_usdc": "0.000000",
"settlement_out_usdc": "0.000000"
},
"event_counts": {"buys": 1, "sells": 1, "splits": 0, "merges": 0, "redemptions": 0, "neg_risk_conversions": 0},
"coverage": {
"selection": "latest_wallet_order_fills",
"max_trades": 50000,
"history_truncated": false,
"requested_history": "recent",
"served_history": "full",
"fallback_reason": null,
"lifetime_history_included": true,
"window_start": {"type": "beginning_of_indexed_history"},
"window_end": {"type": "database_snapshot", "snapshot_at": "2026-09-07T01:00:00.000Z"},
"first_included_event_timestamp": 1788652800,
"last_included_event_timestamp": 1788739200,
"snapshot_at": "2026-09-07T01:00:00.000Z"
},
"applied_filters": {"period": null, "from": null, "to": 1788742800},
"realized_pnl_context": {
"available": true,
"scope": "lifetime_wallet",
"date_filters_applied": false,
"source": "api.wallet_pnl_current",
"pnl_definition": "realized",
"total_realized_pnl_usdc": "3.000000",
"position_count": 1,
"open_positions": 0,
"updated_block": "93350000",
"updated_at": "2026-09-07T00:59:00Z",
"refresh_age_seconds": 60,
"latest_indexed_fill_block": "93350000",
"freshness": "no_newer_indexed_fills"
},
"query_ms": 800
},
"cache": {"status": "miss", "age_ms": 800},
"elapsed_ms": 805
}
snapshot_at is a UTC timestamp. For a truncated history, window_start contains type: "inclusive_trade_cursor", block_number and log_index as strings, and timestamp in Unix seconds. A complete window with a start date uses type: "inclusive_timestamp" and timestamp. window_end is either an exclusive_timestamp or a database_snapshot. applied_filters reports the resolved date bounds; without explicit dates, its to records the snapshot time in whole seconds. An empty wallet has zero cash-flow amounts and null first/last event timestamps.
Results may be cached for 30 seconds after calculation, separately for each wallet and set of options. cache.status is miss, hit, or coalesced (shared in-progress work). cache.age_ms measures time since the calculation’s snapshot; data.query_ms describes that calculation, including any attempted history expansion, while elapsed_ms describes the current request. Each wallet has its own snapshot, including in a batch. Relative periods are anchored to that calculation; supply explicit from and to to fix the same date window across wallets.
Access and errors
Available on paid V3 plans under the account’s normal REST limits and monthly usage policy. The V2-specific one-request-per-five-seconds limit does not apply. Each HTTP request counts as one API request, including a batch. Cache hits still count as requests. Database capacity is bounded; account request limits are not a guarantee of uncached calculation throughput.| HTTP status | Error code | Meaning |
|---|---|---|
400 | invalid_wallet, invalid_max_trades, invalid_history, invalid_period, invalid_timestamp, invalid_window, duplicate_parameter, unknown_parameter | Invalid input. Trade-detail flags remain unsupported. |
422 | activity_limit_exceeded | More than 100,000 settlement events fall in the selected window. No partial financial total is returned. |
422 | unsupported_accounting_data, query_work_limit | Unsupported accounting inputs or calculation work limit reached. No partial total is returned. |
422 | history_limit_exceeded | More than 500,000 fills are required for full history. No partial total is returned. |
503 | server_busy, database_unavailable | Capacity or data access is temporarily unavailable. |
504 | query_timeout | The calculation or queue deadline was exceeded. |
Migrating from V2
UseGET /v3/wallets/{address}/copy-pnl or V3 Copy PnL Batch. Read the summary from data; batch input uses wallets instead of addresses. period, from, and to are supported. V2 defaulted to 30 days; send period=30d explicitly to request that window in V3. Use history=full when the whole requested window is required, or auto when an explicit recent-window fallback is acceptable.
Read actual lifetime realized PnL from realized_pnl_context.total_realized_pnl_usdc, with the scope and freshness limitations above. This does not restore V2’s window-specific weighted-average position metrics. include_trades, individual trade arrays, weighted entry/holding metrics, and V2’s BYOB tracked-wallet pool, snapshot, and leaderboard remain outside these endpoints.Authorizations
Path Parameters
Pattern:
^0x[0-9a-fA-F]{40}$Query Parameters
Recent/fallback fill cap per wallet; initial scan size in full mode. Auto/full may return more fills, up to the 500000-fill work limit.
Required range:
1 <= x <= 50000recent keeps the cap; auto attempts full history with an explicit recent fallback; full requires complete matching history or returns an error.
Available options:
recent, auto, full Optional window ending at to or the calculation snapshot. No default period. Explicit from overrides period.
Available options:
7d, 14d, 30d, 60d, 90d, 180d Inclusive start: Unix seconds or YYYY-MM-DD at UTC midnight. Applies to fills and settlement events, not lifetime realized-PnL context.
Required range:
0 <= x <= 253402300799Exclusive end: Unix seconds or YYYY-MM-DD at UTC midnight. Defaults to the calculation snapshot.
Required range:
0 <= x <= 253402300799
