To track how many shares a wallet holds on Polymarket you can either poll the public API — and be 3 to 120 seconds behind — or subscribe to a trade stream and update the position yourself the millisecond each trade happens. If a bot is making decisions from that number, the second approach isn’t an optimization; it’s the difference between reacting to the market and reacting to its history.
This comes up the moment you build anything serious: you need wallet state. Your own bot’s position (so it knows its exposure before adding to it), or the position of a whale you’re copy-trading (are they opening, adding, or unwinding?). Same problem either way — and two very different ways to solve it.
Way 1: poll the public API
Polymarket’s public data API will tell you any wallet’s current positions — every trade is settled on-chain, so holdings are public information. The naive tracker is a loop:
import time, requests
WALLET = "0xWhale..."
while True:
positions = requests.get(
"https://data-api.polymarket.com/positions",
params={"user": WALLET},
).json()
# ... react to changes ...
time.sleep(5)
Simple, and fine for a dashboard you glance at. For a trading system it has three structural problems:
- It’s stale by design. The API serves processed on-chain state — activity flows through confirmation, indexing, and caching before it reaches you. In practice, position data lags real activity by ~3 to 120 seconds, sometimes more. You’re not seeing the whale’s position; you’re seeing what it was.
- Polling doesn’t scale. Tighten the loop and you hit rate limits. Track ten wallets and you’ve multiplied every request. There is no polling interval that is both fresh and polite.
- You see states, not events. If the whale bought and fully sold between two polls, you saw nothing. The diff between snapshots hides the very activity you wanted to catch.
Way 2: maintain state from the trade stream
The alternative flips the direction: instead of repeatedly asking “what’s the position now?”, derive the position yourself from the events that change it. Every trade streams to you; a buy adds shares, a sell removes them:
import asyncio, json, websockets
from collections import defaultdict
FEED = "wss://stream.polyflux.io/polymarket/<your_key>"
WATCHED = {"0xwhale...", "0xmybot..."} # any wallets you care about
positions = defaultdict(float) # (wallet, outcome) -> shares
def apply(t):
wallet = t["wallet_address"].lower()
if wallet not in WATCHED:
return
key = (wallet, outcome_of(t)) # the outcome token traded —
delta = float(t["size"]) # print a raw event to see it
positions[key] += delta if t["operation_type"] == "buy" else -delta
print(f"{wallet} now holds {positions[key]:.0f} @ {key[1]}")
async def main():
async with websockets.connect(FEED) as ws:
async for raw in ws:
msg = json.loads(raw)
if msg.get("type") != "events":
continue
for t in msg["events"]:
if t.get("event_type") == "trade":
apply(t)
asyncio.run(main())
This is event sourcing, and it changes what you know and when:
- The position updates the moment the trade streams in — while the same trade still has confirmation processing, indexing, and caching ahead of it before any polled API will reflect it.
- One connection covers every wallet. The stream carries all trades; tracking more wallets is adding an address to a set, not adding load.
- You see every event, not periodic snapshots. The buy-then-sell that polling missed is two events you processed in order.
The practical pattern: snapshot + deltas
Don’t choose between them — sequence them. Fetch the wallet’s position from the API once at startup (your snapshot), then apply streamed trades from there (your deltas). This is the same pattern exchanges recommend for order book maintenance, applied to positions. Two honest details to handle:
- Non-trade events move balances too. Redeems, splits, and merges change holdings without a trade. The feed labels these
event_types, so you can process them or simply trigger a re-sync when one touches a watched wallet. - Re-sync occasionally. A periodic API check (minutes, not seconds) trues up drift. The stream gives you real-time truth; the API gives you a checkpoint. Used this way, its delay stops mattering.
Side by side
| Polling the API | Trade streaming | |
|---|---|---|
| Freshness | ~3–120 s behind (sometimes more) | Seconds ahead — as trades stream |
| What you see | Periodic snapshots | Every event, in order |
| Missed activity | Anything between polls | Nothing |
| Scaling to N wallets | N × requests, rate limits | Same one connection |
| Setup | One GET request | ~20 lines + a snapshot |
| Best for | Dashboards, startup snapshot, re-sync | Bots, copy-trading, live signals |
The takeaway
The public API answers “what did this wallet hold, recently?” — and that’s genuinely enough for a portfolio page. But a bot acting on positions needs “what does it hold now” — and the only way to know now is to watch the trades as they happen and do the arithmetic yourself.
The arithmetic is the easy part — you just saw all of it. The hard part is the real-time feed, and that’s one WebSocket: start with the Python streaming guide, then grab a key and your tracker is live against every wallet on Polymarket — seconds ahead of the API you’d otherwise be polling.