Documentation Index
Fetch the complete documentation index at: https://docs.predexon.com/llms.txt
Use this file to discover all available pages before exploring further.
If you’re building any kind of trading UI or risk dashboard, this is the loop you need. Pull positions, mark them to market, layer live updates via WebSocket.
You’ll build:
- Initial state load (positions + balances + P&L) from REST
- Live deltas (fills, splits, redemptions) from WebSocket
- A reconciliation step that runs periodically
Endpoints used: 4 REST + 2 WebSocket channels. Dev plan and up.
Architecture
Step 1 — Boot the state
For any account, gather positions on every enabled venue + cash balance + P&L from the Data API.
import os, requests, json, time
from websockets.sync.client import connect
DATA = "https://api.predexon.com"
TRADE = "https://trade.predexon.com"
WSS = "wss://wss.predexon.com/v1"
KEY = os.environ["PREDEXON_API_KEY"]
H = {"x-api-key": KEY}
ACCOUNT_ID = "your-account-id"
def boot_state():
# 1. Account info — gives us per-venue wallets
acct = requests.get(f"{TRADE}/api/accounts/{ACCOUNT_ID}", headers=H).json()
venues = {v: info for v, info in acct["venues"].items() if info["status"] == "active"}
# 2. Aggregated positions across every enabled venue
positions = requests.get(
f"{TRADE}/api/accounts/{ACCOUNT_ID}/positions",
headers=H,
params={"aggregated": "true"},
).json()["positions"]
# 3. Cash balance per venue + USD-equivalent total
balance = requests.get(
f"{TRADE}/api/accounts/{ACCOUNT_ID}/balance",
headers=H,
params={"aggregated": "true"},
).json()
# 4. Polymarket P&L history (other venues TBD)
poly_wallet = venues.get("polymarket", {}).get("address")
pnl = {}
if poly_wallet:
pnl = requests.get(
f"{DATA}/v2/polymarket/wallet/pnl/{poly_wallet}",
headers=H,
).json()
return {
"venues": venues,
"positions": positions,
"balance": balance,
"pnl": pnl,
"loaded_at": time.time(),
}
state = boot_state()
print(f"Loaded {len(state['positions'])} positions across {len(state['venues'])} venues")
Step 2 — Subscribe to live deltas
Two channels matter for portfolio updates: trades (fills) and activity (splits / merges / redemptions). Filter both to your account’s venue wallets.
def subscribe_msgs(wallets):
return [
json.dumps({
"action": "subscribe",
"platform": "polymarket",
"version": 1,
"type": "orders",
"filters": {"users": wallets},
}),
json.dumps({
"action": "subscribe",
"platform": "polymarket",
"version": 1,
"type": "activity",
"filters": {"users": wallets},
}),
]
Step 3 — Apply each delta to state
A fill changes a position’s size and cost basis. An activity event (split/merge/redeem) restructures positions. Worth modeling both — but at minimum, the trades handler is the must-have.
def apply_trade(state, t):
"""Update a position from a fill event."""
token_id = t["token_id"]
size = float(t["shares_normalized"]) * (1 if t["side"] == "BUY" else -1)
price = float(t["price"])
pos = next((p for p in state["positions"] if p.get("token_id") == token_id), None)
if pos is None:
# new position
state["positions"].append({
"token_id": token_id,
"condition_id": t["condition_id"],
"size": size,
"avg_price": price,
"venue": "polymarket",
})
else:
# weighted-average cost basis
new_size = pos["size"] + size
if new_size == 0:
state["positions"].remove(pos)
else:
pos["avg_price"] = (
(pos["avg_price"] * pos["size"] + price * size) / new_size
)
pos["size"] = new_size
def apply_activity(state, a):
"""Activity events restructure positions (splits, merges, redemptions)."""
if a["event_type"] == "redemption":
# winning side pays $1/share, losing side pays $0; remove resolved positions
state["positions"] = [
p for p in state["positions"] if p["condition_id"] != a["condition_id"]
]
# Splits / merges / NegRisk conversions: re-pull positions in step 4
Step 4 — The main loop
def run():
state = boot_state()
wallets = [v["address"] for v in state["venues"].values()]
last_reconcile = time.time()
with connect(f"{WSS}/{KEY}") as ws:
for msg in subscribe_msgs(wallets):
ws.send(msg)
while True:
try:
ws.settimeout(60)
raw = ws.recv()
msg = json.loads(raw)
if msg.get("type") != "event":
continue
if msg["subscription_id"].endswith("trades"):
apply_trade(state, msg["data"])
elif msg["subscription_id"].endswith("activity"):
apply_activity(state, msg["data"])
publish(state) # send to UI / dashboard
except TimeoutError:
pass
# Reconcile every 60s — defends against missed events
if time.time() - last_reconcile > 60:
state = boot_state()
last_reconcile = time.time()
publish(state)
def publish(state):
# your UI delivery — websocket to frontend, Redis pub/sub, etc.
print(f"[{time.strftime('%H:%M:%S')}] {len(state['positions'])} positions, "
f"${state['balance']['total_usd']:.2f} total")
What the UI does with this state
| Display | Source |
|---|
| Open positions table | state["positions"] |
| Mark-to-market | position.size * latest_price — fetch latest_price from Market Price |
| Unrealized P&L | (latest_price - avg_price) * size |
| Realized P&L over time | state["pnl"]["history"] (Polymarket); compute from fills for other venues |
| Recent fills | Append the trades event stream to a feed |
| Withdrawable balance | state["balance"]["deposit"] (the deposit-wallet USDC) |
For periodic mark-to-market, the cheapest pattern is batch the latest prices for all unique token_ids in the positions list and refresh every few seconds.
Variations
- Multi-account dashboard: loop over
GET /api/accounts to enumerate every account this API key owns; boot state for each.
- Alerting: add thresholds — alert on unrealized loss > X, on a new position above $Y, on a redemption event.
- Historical P&L: pair with Wallet Volume Chart for cumulative-equity curves.
- Cross-venue: positions endpoint already returns aggregated cross-venue rows with
?aggregated=true. Group by canonical predexon_id for cleaner UX.
Reference