Every position a wallet has held — open and closed — with realized + unrealized P&L, per-position activity timestamps, market resolution timestamps, and parent event slugs. Optional time-window filtering.
Returns every position a wallet has ever held, including closed positions that no longer appear in the standard positions endpoint. Each position includes precomputedDocumentation Index
Fetch the complete documentation index at: https://polynode.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
realized_pnl, avg_price, total_bought, plus per-position activity, redemption, and resolution timestamps that let you build leaderboards, trim to a 30-day or 90-day window, or filter to a parent event.
The standard /v1/wallets/{addr}/positions endpoint only returns open positions. Once a position is fully exited or a market resolves, it disappears. This endpoint fills that gap.
| Parameter | Type | Description |
|---|---|---|
address | path | Wallet address (0x-prefixed, 40 hex chars) |
since | query (optional) | Unix seconds. Drop positions whose last_trade_at is before this timestamp. |
until | query (optional) | Unix seconds. Drop positions whose last_trade_at is after this timestamp. |
tag_slug | query (optional) | Keep only positions whose market carries this Polymarket event tag (e.g. nba, politics, crypto, 2026-fifa-world-cup-winner-595). Matches against the per-market tag_slugs array. See Filtering by tag. |
since, until, and tag_slug are all opt-in. When none are supplied, the response is identical to a request with no parameters at all — same shape, same aggregates over the full position set. When any is supplied, positions are filtered and aggregates are recomputed (see Filtering).
| Field | Type | Description |
|---|---|---|
wallet | string | Queried wallet address (lowercased) |
source | string | Always "onchain" |
count | number | Total positions returned |
open_count | number | Positions with size > 0 |
closed_count | number | Positions with size = 0 and nonzero realized_pnl |
total_realized_pnl | number | Sum of realized_pnl across all returned positions |
total_unrealized_pnl | number | Sum of unrealized_pnl across all returned positions. Reflects open-size paper P&L at current (or terminal) market prices. |
total_pnl | number | total_realized_pnl + total_unrealized_pnl. Matches the net portfolio PnL shown on a Polymarket profile page. |
positions_with_pnl | number | Positions where realized_pnl != 0 |
filtered | boolean | Present only when since or until was supplied. Always true in that case. Absent in default responses. |
applied_filters | object | Present only when filtering. Echoes {since, until} (each is the supplied unix seconds or null). Absent in default responses. |
positions[].token_id | string | CTF token ID (called asset on the closed-positions endpoint) |
positions[].size | number | Current token balance (0 = fully exited) |
positions[].avg_price | number | Volume-weighted average entry price |
positions[].realized_pnl | number | Realized profit/loss in USDC |
positions[].unrealized_pnl | number | Paper P&L on remaining open shares. For resolved markets, uses the terminal settlement price (1.0 winner / 0.0 loser). For live markets, uses the current market price. 0 when size = 0. |
positions[].current_price | number | null | Price used for unrealized_pnl. 1.0 or 0.0 on resolved markets, live market price on active markets, null when size = 0 or no price is available. |
positions[].market_status | string | "live" / "resolved-win" / "resolved-loss" / "resolved-unknown" / "closed". See positions feed for full enum. |
positions[].total_bought | number | Total tokens acquired for this position |
positions[].market | string | Market question |
positions[].slug | string | Market slug — identifies the specific market within an event (e.g. nba-bos-cle-2026-03-08-spread). |
positions[].event_slug | string | null | Event slug — identifies the parent event a market belongs to (e.g. nba-bos-cle-2026-03-08). For single-market events, equals slug. For multi-market events (NBA games with several lines, election markets with several candidates, FIFA World Cup with one market per team, etc.) this is the parent the markets share. null when the event metadata is not yet known. |
positions[].outcome | string | Outcome label (e.g. “Yes”, “No”, “Up”) |
positions[].image | string | Market image URL |
positions[].condition_id | string | Market condition ID |
positions[].last_trade_at | number | null | Unix seconds. Latest fill (maker or taker) on this token across V1 + V2 exchanges. null when polynode has no fill history for the token (e.g. positions acquired purely via splits/merges or NegRisk conversion with no later trade). |
positions[].closed_at | number | null | Unix seconds. Latest moment this wallet redeemed any outcome of this market’s condition_id for collateral. null when the wallet has not redeemed (positions sold to zero before resolution, or open positions, or wins held but not yet redeemed). |
positions[].resolved_at | number | null | Unix seconds. Moment the market was resolved on-chain (when payouts became redeemable). null for markets that resolved before polynode began tracking, or for markets that have not yet resolved. Recent markets are fully covered. |
positions[].tag_slugs | string[] | null | Polymarket event-level tags applied to this market. Examples: ["nba","basketball","sports","all"] for an NBA game market, ["crypto","up-or-down","recurring","all"] for a 5-min crypto market. Order is most-specific → most-general. Tags live on the parent event (markets sharing an event_slug share the same tag_slugs). null only on the rare event that doesn’t carry any tag or hasn’t been ingested yet. |
| Field | Whose action | When |
|---|---|---|
last_trade_at | The wallet’s most recent fill on this exact token | Each fill event (buy or sell) updates this |
closed_at | The wallet’s most recent collateral redemption for the market | Set when the wallet calls redeemPositions on the CTF contract |
resolved_at | The market itself (not the wallet) becoming redeemable | Set once per market, when the oracle reports payouts on-chain |
resolved_at populated and closed_at null — the market resolved but the wallet has not yet redeemed. A position can have closed_at set and resolved_at null — the wallet redeemed during a window polynode does not have full historical resolution coverage for, but the redemption itself was observed.
For windowing a leaderboard to “the last 30 days of trading activity,” last_trade_at is the right field — it captures every position the wallet was active in. For “positions resolved in the last 90 days,” resolved_at is the right one. For “positions the wallet has actually closed out,” closed_at is the right one.
since and until are optional unix-seconds query parameters that filter positions by last_trade_at. Designed to support 30d / 90d / 365d leaderboard windows without fetching multi-year history just to trim it client-side.
last_trade_at = null are dropped under any active filter — there’s no timestamp to compare against.count, open_count, closed_count, total_realized_pnl, total_unrealized_pnl, total_pnl, positions_with_pnl) recompute over the filtered set.filtered: true and applied_filters: { since, until }. These keys are absent from default responses.400 with a descriptive error.since/until/tag_slug, the response shape and every aggregate is identical to what you got before. The filtered and applied_filters keys are only added when you opt in.tag_slug is an optional query parameter that keeps only positions whose market carries the matching Polymarket event tag.
Tags come from Polymarket’s event taxonomy — typical values include nba, basketball, sports, politics, crypto, fed, world, 2025-predictions, and event-specific slugs like 2026-fifa-world-cup-winner-595. Each market inherits the tag set of its parent event, so all 32 markets under “Will X win the World Cup?” share the same tag list. The full set is visible in the per-row tag_slugs array on every response.
?tag_slug=nba matches markets whose tag_slugs array contains "nba").tag_slugs = null are dropped under an active filter — same logic as last_trade_at for since/until (cannot match what we don’t know).applied_filters.tag_slug echoes the value back so the response is self-documenting.since / until to get e.g. “all NBA positions in the last 30 days” — all three filters AND together./v2/onchain/tags endpoint for the full tag namespace (5,779 tags currently). Returns a flat slug array by default, or ?details=true for per-tag market counts and timestamps:
/v2/onchain/tags are the exact strings to pass to ?tag_slug= here.
For a wallet-specific view, you can also fetch the wallet’s positions without a filter and read the union of tag_slugs across the response — that’s the tag namespace the wallet has actually traded in.
realized_pnl Worksrealized_pnl reflects the profit or loss after the position is closed (sold or redeemed). For a winning binary position bought at 1.00:
avg_price = 0.40total_bought = 10,000 (tokens acquired)realized_pnl = 4,000 = $6,000realized_pnl = total_bought × (1 - avg_price) for positions that resolve at $1.
Per-position values (realized_pnl, avg_price, total_bought) come from the same onchain settlement data that Polymarket uses. Individual position P&L matches what Polymarket shows on its portfolio page.
| Total | Answers | Matches |
|---|---|---|
total_realized_pnl | How much profit has this wallet actually taken off the table from closed + resolved positions? | Not shown on Polymarket profile |
total_unrealized_pnl | What is the paper P&L on the wallet’s currently-held shares at current (or terminal) prices? | Not shown directly |
total_pnl | What is the wallet’s net portfolio P&L right now? | The “Profit” number on a Polymarket profile page |
total_pnl is what most dashboards want — it’s the single number users recognize from Polymarket. total_realized_pnl is useful for attribution (which markets made money vs which positions are still out). total_unrealized_pnl is useful for risk views (how much is still at stake).
total_pnl = total_realized_pnl + total_unrealized_pnl. Reach for total_pnl when you want a number that matches a Polymarket profile page. Reach for total_realized_pnl when you want only closed-out gains.since or until, all three totals recompute over the filtered set — the same logic, just narrower input.
since/until does not invalidate the underlying cache.
| Positions | First request | Cached |
|---|---|---|
| < 500 | ~1s | < 50ms |
| 500-1,000 | ~1.5s | < 50ms |
| 1,000-5,000 | ~3s | < 50ms |
filtered or applied_filters keys.
event_slug is included on every row, so once you have a wallet’s positions you can group them by parent event without an extra round-trip: