Wallet P&L
curl --request GET \
--url https://api.polynode.dev/v3/wallets/{address}/pnl \
--header 'x-api-key: <api-key>'import requests
url = "https://api.polynode.dev/v3/wallets/{address}/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}/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}/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}/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}/pnl")
.header("x-api-key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.polynode.dev/v3/wallets/{address}/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_bodyWallet P&L
Wallet P&L
Get profit and loss for a wallet in USD. Supports time-windowed P&L with ?period=1d|7d|30d|1y plus tag, category, market, event, and condition filters.
GET
/
v3
/
wallets
/
{address}
/
pnl
Wallet P&L
curl --request GET \
--url https://api.polynode.dev/v3/wallets/{address}/pnl \
--header 'x-api-key: <api-key>'import requests
url = "https://api.polynode.dev/v3/wallets/{address}/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}/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}/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}/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}/pnl")
.header("x-api-key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.polynode.dev/v3/wallets/{address}/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_bodyReturns P&L data for a wallet in USD. Without a time parameter, it returns the wallet’s current all-time P&L. With
Both boundaries are inclusive. A state change timestamped exactly at
The boundary calculation uses Polynode’s recovered Polymarket weighted-average-cost state. The difference is scope and timing, not a simpler cost-basis formula: all-time P&L uses the current recovered portfolio state, including terminal treatment for resolved holdings and current unrealized P&L, while a time window reports only the change in realized state across its boundaries.
For time-windowed or tag-filtered P&L, the response stays successful. If combo P&L cannot be safely added to that filtered view,
Additional fields (with
period, after, or before, it returns this boundary calculation across all matching positions:
time-window realized P&L = realized state at or before `before`
- realized state immediately before `after`
after or before is included.
Fees and accounting scope
For standard spot market positions, realized and unrealized P&L exclude separately reported trader-paid fees, rebates, and rewards.net_realized_pnl
means the combined realized gains and losses, not an after-fee return.
Use Fees Paid, Rebates, and
the optional Wallet Summary accounting fields when
you need those separate amounts.
include_combos=true adds a component with different fee treatment:
Combo P&L includes each signed order’s recorded
exchange fill fee in its cost basis or sale proceeds. That does not make the
standard spot component fee-inclusive. Perps has its own endpoint accounting
model; do not infer its fee or funding treatment from either component.
Time-window behavior
The two modes have different counting semantics: all-timewins and losses are current position counts; time-windowed wins, losses, and the legacy events field count positions whose realized state changed. Time-windowed responses declare count_type: "positions_changed". Do not compare the counts directly.
Time-windowed P&L is a realized-state change, not a historical snapshot of the all-time leaderboard. It captures realized changes reflected by sells, merges, redemptions, and timestamped corrections. A buy-and-hold position can therefore contribute
0 to a window even if terminal treatment makes the resolved position profitable in current all-time P&L. For the same reason, an after=1&before=now result is not expected to equal the all-time response or leaderboard total.Wallet P&L supports focused market filters such as
category, tags/tag_slug, market/market_slug, event_slug, and condition_id. For leaderboard views, use one primary filter dimension per request.Request
GET /v3/wallets/{address}/pnl
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
period | string | — | Time window: 1d, 7d, 30d, 1y |
after | integer | — | Start timestamp (Unix seconds). Time-windowed queries clamp values below 1 to 1. |
before | integer | — | End timestamp (Unix seconds) |
category | string | — | Filter to one market category, case-insensitive |
tags | string | — | Comma-separated tag slugs to filter by, case-insensitive (e.g. politics,crypto, nfl, Iran) |
tag_slug | string | — | Alias for a single tags value |
market | string | — | Filter by condition ID or market slug |
market_slug | string | — | Filter by market slug |
event_slug | string | — | Filter by parent event slug |
condition_id | string | — | Filter by market condition ID |
include_unrealized | string | false | Set to true to add current unrealized P&L to the realized event-window result. This is the current value, not the change between the two requested boundaries. Only applies to time-filtered queries. |
include_combos | boolean | false | Add combo position P&L to all-time wallet P&L. If the wallet has no combo exposure, the response remains 200 with a zero combo contribution. |
Examples
All-time P&L
curl https://api.polynode.dev/v3/wallets/0x56687bf447db6ffa42ffe2204a05edaa20f55839/pnl
All-time P&L including combos
curl "https://api.polynode.dev/v3/wallets/0x63613e3b96f418332d43cd2af8dc321014d15907/pnl?include_combos=true"
{
"address": "0x63613e3b96f418332d43cd2af8dc321014d15907",
"net_realized_pnl": 113.510513,
"realized_pnl": 113.510513,
"gross_profit": 184.901354,
"gross_loss": -71.390841,
"unrealized_pnl": 0.0,
"total_pnl": 113.510513,
"position_count": 21496,
"open_positions": 1495,
"total_volume": 129.837476,
"include_combos": true,
"included_position_types": ["market", "combo"],
"combo_pnl": {
"position_type": "combo",
"included": true,
"realized_pnl": 113.510513,
"unrealized_pnl": 0.0,
"total_pnl": 113.510513,
"position_count": 21492,
"open_positions": 1495
}
}
combo_pnl.included is false and the standard market P&L fields remain unchanged.
{
"address": "0x56687bf447db6ffa42ffe2204a05edaa20f55839",
"net_realized_pnl": 22053845.825455,
"gross_profit": 22057977.181649,
"gross_loss": -4131.356194,
"unrealized_pnl": 0.000688755,
"total_pnl": 22053845.826143753,
"position_count": 22,
"open_positions": 1,
"total_volume": 43013258.515682,
"wins": 18,
"losses": 4,
"elapsed_ms": 41
}
30-day P&L
curl https://api.polynode.dev/v3/wallets/0xbddf61af533ff524d27154e589d2d7a81510c684/pnl?period=30d
{
"address": "0xbddf61af533ff524d27154e589d2d7a81510c684",
"realized_pnl": 1815261.28,
"gross_profit": 1918402.27,
"gross_loss": -103140.98,
"wins": 33039,
"losses": 58867,
"events": 91906,
"count_type": "positions_changed",
"source": "recovered_realized_boundaries",
"period": "30d",
"after": 1776134996,
"before": 1778726996,
"elapsed_ms": 5902
}
Filtered P&L
curl "https://api.polynode.dev/v3/wallets/0x952d11ebff81d6bd3185e608ed3515b94618ab8a/pnl?category=crypto"
curl "https://api.polynode.dev/v3/wallets/0x952d11ebff81d6bd3185e608ed3515b94618ab8a/pnl?tag_slug=us-election&period=30d"
curl "https://api.polynode.dev/v3/wallets/0x952d11ebff81d6bd3185e608ed3515b94618ab8a/pnl?market_slug=will-donald-trump-win-the-popular-vote-in-the-2024-presidential-election"
curl "https://api.polynode.dev/v3/wallets/0x952d11ebff81d6bd3185e608ed3515b94618ab8a/pnl?event_slug=presidential-election-popular-vote-winner-2024"
30-day P&L with unrealized
Passinclude_unrealized=true to add current unrealized P&L to the realized-state change. This does not turn the response into a historical portfolio-value change between after and before.
curl https://api.polynode.dev/v3/wallets/0xbddf61af533ff524d27154e589d2d7a81510c684/pnl?period=30d&include_unrealized=true
{
"address": "0xbddf61af533ff524d27154e589d2d7a81510c684",
"realized_pnl": 1815261.28,
"gross_profit": 1918402.27,
"gross_loss": -103140.98,
"unrealized_pnl": 14714.71,
"total_pnl": 1829975.99,
"open_positions_at_date": 363,
"wins": 33039,
"losses": 58867,
"events": 91906,
"count_type": "positions_changed",
"source": "recovered_realized_boundaries",
"period": "30d",
"after": 1776135002,
"before": 1778727002,
"elapsed_ms": 3803
}
Response fields (all-time)
| Field | Type | Description |
|---|---|---|
net_realized_pnl | number | Net realized P&L (USD) |
gross_profit | number | Sum of winning positions (USD) |
gross_loss | number | Sum of losing positions (USD, negative) |
unrealized_pnl | number | Paper P&L from open positions (USD) |
total_pnl | number | net_realized_pnl + unrealized_pnl (USD) |
wins | integer | Winning position count |
losses | integer | Losing position count |
position_count | integer | Total positions |
open_positions | integer | Currently held positions |
total_volume | number | Total volume traded (USD) |
include_combos | boolean | Present when include_combos=true was requested |
included_position_types | array | Position families included in aggregate totals |
combo_pnl | object | Combo-only contribution to the aggregate response |
Response fields (with period)
| Field | Type | Description |
|---|---|---|
realized_pnl | number | Weighted-average-cost realized state at before minus the state immediately before after (USD) |
gross_profit | number | Sum of positive changed-position deltas in the window (USD) |
gross_loss | number | Sum of negative changed-position deltas in the window (USD, negative) |
wins | integer | Positions with a positive realized-state delta |
losses | integer | Positions with a negative realized-state delta |
events | integer | Legacy field name; total positions with a nonzero realized-state delta |
count_type | string | positions_changed for time-windowed queries |
period | string | The requested period |
after | integer | Start timestamp (Unix seconds) |
before | integer | End timestamp (Unix seconds) |
source | string | Dataset label for time-windowed queries; currently recovered_realized_boundaries |
Additional fields (with include_unrealized=true)
| Field | Type | Description |
|---|---|---|
unrealized_pnl | number | Current unrealized P&L for open positions (USD), not the change in unrealized P&L between the requested boundaries. |
total_pnl | number | realized_pnl + unrealized_pnl (USD) |
open_positions_at_date | integer | Current open position count returned with the unrealized P&L estimate |
Errors
| HTTP | Error | When |
|---|---|---|
400 | Invalid request | The wallet address, period, filter, or boundary is invalid |
503 | time_filtered_pnl_unavailable | The selected wallet and window exceed the serving deadline. Retry with a narrower window. The response includes reason: "query_timeout". |
Authorizations
Path Parameters
Wallet address
Query Parameters
Time window shortcut
Available options:
1d, 7d, 30d, 1y Inclusive Unix timestamp lower bound; the realized-state baseline is taken immediately before this timestamp
Inclusive Unix timestamp upper bound
Add current unrealized P&L to the time-windowed realized-state change; this is not the unrealized-P&L change between the requested boundaries
Response
Wallet P&L

