Skip to main content

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:
  1. Initial state load (positions + balances + P&L) from REST
  2. Live deltas (fills, splits, redemptions) from WebSocket
  3. 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

DisplaySource
Open positions tablestate["positions"]
Mark-to-marketposition.size * latest_price — fetch latest_price from Market Price
Unrealized P&L(latest_price - avg_price) * size
Realized P&L over timestate["pnl"]["history"] (Polymarket); compute from fills for other venues
Recent fillsAppend the trades event stream to a feed
Withdrawable balancestate["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