get_positions() was the py_clob_client method for retrieving your open Polymarket positions in V1. CLOB V2 removed it — there is no client.get_positions() (and no client.get_balance()) in py-clob-client-v2. In V2, positions come from the public Data API (GET https://data-api.polymarket.com/positions), which needs no authentication and returns richer data — including unrealized P&L computed for you. This page shows the V2 way to track positions, calculate P&L, and gate orders on existing holdings.

⚠️ get_positions() was removed in CLOB V2 (live April 28, 2026). If you’re porting V1 code, replace client.get_positions() with a Data API call (below). The V1 py-clob-client package no longer works against production at all — see Migrating to CLOB V2. Each position represents your holdings of a specific outcome token (how many shares, at what average price).

For the complete V2 method reference, see the py-clob-client-v2 Reference. For Kalshi’s equivalent positions endpoint, see the Prediction Market API Reference.


What happened to get_positions() in V2

The V1 call simply doesn’t exist anymore:

# V1 — no longer works on production:
# positions = client.get_positions()

# V2 — fetch from the Data API (no auth needed):
import requests
positions = requests.get(
    "https://data-api.polymarket.com/positions",
    params={"user": "<your-wallet-address>"},
).json()

If you’re migrating a V1 bot, here’s the full mapping of what changed around position tracking:

V1 (py-clob-client)V2 (py-clob-client-v2)
client.get_positions()Data API GET /positions (no client method)
client.get_balance()client.get_balance_allowance(BalanceAllowanceParams(asset_type=AssetType.COLLATERAL))
client.create_or_derive_api_creds()client.create_or_derive_api_key()
from py_clob_client.order_builder.constants import BUYfrom py_clob_client_v2 import SideSide.BUY
pip install py-clob-clientpip install py-clob-client-v2
USDC.e collateralpUSD collateral

The rest of this page uses the V2 Data API path.


The Data API Positions Endpoint

GET https://data-api.polymarket.com/positions?user=<wallet-address>

No authentication required — you only need a wallet address. The user is the funder address that holds the positions (your proxy / Gnosis Safe / deposit wallet, or the EOA itself), not necessarily the signing key.

Query Parameters

ParameterTypeRequiredDescription
useraddressYesThe wallet address holding the positions
marketstringNoComma-separated condition IDs to filter by (mutually exclusive with eventId)
eventIdstringNoComma-separated event IDs (mutually exclusive with market)
sizeThresholdnumberNoMinimum size to include (default 1)
redeemableboolNoOnly positions in resolved markets awaiting redemption (default false)
mergeableboolNoOnly positions that can be merged (default false)
limitintNoPage size, 0500 (default 100)
offsetintNoPagination offset, max 10000 (default 0)
sortBystringNoTOKENS (default), CURRENT, INITIAL, CASHPNL, PERCENTPNL, PRICE, AVGPRICE, TITLE, RESOLVING
sortDirectionstringNoASC or DESC (default DESC)
titlestringNoFilter by market title substring

Response Fields (per position)

FieldDescription
assetThe outcome token ID (string)
conditionIdThe on-chain condition (market) ID
sizeShares held
avgPriceAverage entry price per share
curPriceCurrent market price
initialValue / currentValueCost basis and current mark-to-market value
cashPnl / percentPnlUnrealized P&L (dollars and percent) — computed for you
realizedPnl / percentRealizedPnlRealized P&L
redeemabletrue when the market resolved and the position can be redeemed
title / slug / eventSlug / iconMarket metadata
outcome / outcomeIndexThe outcome label (e.g. Yes) and its index
oppositeOutcome / oppositeAssetThe other side’s label and token ID
negativeRisktrue for neg-risk markets
proxyWallet / endDateThe holding wallet and market end date

Note asset is the token ID string itself — there’s no nested asset.token_id object (that was a V1 client shape).

List your open positions

import requests

WALLET = "<your-wallet-address>"  # the funder address that holds your positions

positions = requests.get(
    "https://data-api.polymarket.com/positions",
    params={"user": WALLET},
).json()

if not positions:
    print("No open positions")
else:
    for pos in positions:
        print(f"Market:     {pos.get('title', 'Unknown')} ({pos['outcome']})")
        print(f"Token:      {pos['asset'][:20]}...")
        print(f"Shares:     {float(pos['size']):.2f}")
        print(f"Avg Price:  ${float(pos['avgPrice']):.4f}")
        print(f"Cur Price:  ${float(pos['curPrice']):.4f}")
        print("---")

Calculate Portfolio P&L

In V2 you don’t need to fetch prices and do the math yourself — the Data API already returns currentValue, initialValue, cashPnl (unrealized dollar P&L), and percentPnl per position. Just read and sum them:

import requests

WALLET = "<your-wallet-address>"

positions = requests.get(
    "https://data-api.polymarket.com/positions",
    params={"user": WALLET, "sortBy": "CASHPNL"},
).json()

total_pnl = 0.0
total_cost = 0.0

for pos in positions:
    cash_pnl = float(pos["cashPnl"])
    total_pnl += cash_pnl
    total_cost += float(pos["initialValue"])

    print(f"{pos.get('title', 'Unknown')} ({pos['outcome']})")
    print(f"  Entry: ${float(pos['avgPrice']):.4f} → Current: ${float(pos['curPrice']):.4f}")
    print(f"  P&L:   ${cash_pnl:+.2f} ({float(pos['percentPnl']):+.1f}%)")

print(f"\nTotal Cost Basis:     ${total_cost:.2f}")
print(f"Total Unrealized P&L: ${total_pnl:+.2f}")

For realized P&L on closed-out positions, read realizedPnl / percentRealizedPnl (or query the closed-positions endpoint). If you prefer to compute marks yourself, unrealized = (curPrice − avgPrice) × size reproduces cashPnl.


Check for Existing Position Before Trading

Prevent accidentally doubling your exposure when a bot runs repeatedly. Look up positions from the Data API by wallet, then filter by the asset token ID:

import requests
from py_clob_client_v2 import OrderArgs, OrderType, PartialCreateOrderOptions, Side

def get_position_size(wallet, target_token_id):
    """Current position size for a token, or 0 if no position."""
    positions = requests.get(
        "https://data-api.polymarket.com/positions",
        params={"user": wallet},
    ).json()
    for pos in positions:
        if pos["asset"] == target_token_id:
            return float(pos["size"])
    return 0.0

def has_position(wallet, target_token_id):
    """True if we already hold a non-zero position in this outcome."""
    return get_position_size(wallet, target_token_id) > 0

# Example: only buy if we don't already have a position
WALLET = "<your-wallet-address>"
token_id = "<token-id>"

if not has_position(WALLET, token_id):
    client.create_and_post_order(
        order_args=OrderArgs(token_id=token_id, price=0.50, size=10.0, side=Side.BUY),
        options=PartialCreateOrderOptions(tick_size="0.01"),
        order_type=OrderType.GTC,
    )
    print("Order placed")
else:
    current_size = get_position_size(WALLET, token_id)
    print(f"Already holding {current_size:.2f} shares — skipping")

Position Sizing Based on Current Holdings

Scale orders based on your existing exposure (Data API) and available collateral. Note V2 has no get_balance() — read your pUSD balance via get_balance_allowance():

from py_clob_client_v2 import BalanceAllowanceParams, AssetType

def calculate_order_size(client, wallet, token_id, max_position=1000, target_price=0.50):
    """Calculate order size respecting a maximum position limit."""
    current_size = get_position_size(wallet, token_id)   # Data API (see above)
    remaining_capacity = max_position - current_size

    if remaining_capacity <= 0:
        print(f"Position limit reached: {current_size:.0f}/{max_position} shares")
        return 0

    # Check available pUSD balance (V2: get_balance_allowance, not get_balance)
    ba = client.get_balance_allowance(
        BalanceAllowanceParams(asset_type=AssetType.COLLATERAL)
    )
    balance_pusd = int(ba["balance"]) / 1e6
    max_affordable = balance_pusd / target_price

    order_size = min(remaining_capacity, max_affordable)
    print(f"Current: {current_size:.0f} | Capacity: {remaining_capacity:.0f} | "
          f"Affordable: {max_affordable:.0f} | Order: {order_size:.0f}")

    return order_size

curl & Filtering Examples

Because the Data API is public, you can hit it from anything — curl, a dashboard, a serverless function — for any wallet, not just your own:

# All positions for a wallet
curl "https://data-api.polymarket.com/positions?user=0xYOUR_WALLET_ADDRESS"

# Only positions in one market (condition ID), biggest P&L first
curl "https://data-api.polymarket.com/positions?user=0xYOUR_WALLET_ADDRESS&market=0xCONDITION_ID&sortBy=CASHPNL"

# Only resolved positions you can redeem
curl "https://data-api.polymarket.com/positions?user=0xYOUR_WALLET_ADDRESS&redeemable=true"

Division of labor in V2: the Data API owns positions and P&L (this page); the CLOB client (py-clob-client-v2) owns balances/allowances (get_balance_allowance) and orders (create_order, post_order, …). There’s no longer a positions method on the client to choose between — positions are always a Data API call.


Common Errors

ErrorCauseFix
AttributeError: get_positionsCalling the removed V1 method on py-clob-client-v2There is no client positions method in V2 — use the Data API GET /positions
Empty array returnedQuerying the signing EOA instead of the funder, or no holdingsUse the funder address (proxy / Safe / deposit wallet) that actually holds the positions, and confirm it has open positions
KeyError: 'token_id'Reading the V1 client shape on a Data API responseThe token ID is the top-level asset field (a string), not asset['token_id']
ConnectionErrorAPI unreachableRetry with exponential backoff
Stale position dataPositions may lag recent fills by secondsFor real-time fills, use the WebSocket user channel
Missing resolved positionsDefault query hides resolved marketsPass redeemable=true to list positions in resolved markets awaiting redemption

See Also


{{ partial “marketplace-cta.html” . }}


This reference is maintained by AgentBets.ai. Found an error or SDK change we missed? Let us know on Twitter.

Not financial advice. Built for builders.