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.

Polymarket trades have to wait for the next Polygon block to confirm. Predexon decodes the mempool so you see those trades 3–5 seconds before they settle on-chain. That window is enough to lift the book or take the other side, depending on your strategy. You’ll build:
  1. A WebSocket subscription that fires on pending trades
  2. A latency-honest decision function
  3. (Optional) An execution path that acts inside the 3–5s window
Endpoints used: 1 WebSocket channel + 1 Trading API. Requires Dev+ for WebSocket.
This is a latency-sensitive strategy. If your handler takes >1 second to decide or your network round-trip to trade.predexon.com is high, you’ll miss the window. Co-locate near us-east-1 and benchmark before sizing.

How pending trades work

You’re seeing the fill before the rest of the world does — including most other Polymarket traders. The strategies that work in this window are typically:
  • Lift the book: a large pending buy means the next mid-tick will be higher. Buy ahead of it.
  • Pre-emptive cancel: if a pending trade is about to take your resting order, cancel and re-quote.
  • Quote tightening: maker strategies tightening spread when the book is about to move.

Step 1 — Subscribe to pending trades

Same orders channel as confirmed trades — just add filters.status: "pending".
import os, json, time
from websockets.sync.client import connect

KEY = os.environ["PREDEXON_API_KEY"]
WSS = "wss://wss.predexon.com/v1"

def sub_msg(market_slugs):
    return json.dumps({
        "action": "subscribe",
        "platform": "polymarket",
        "version": 1,
        "type": "orders",
        "filters": {
            "market_slugs": market_slugs,
            "status": "pending",   # the magic flag
        },
    })

MARKETS = ["bitcoin-100k-by-end-2026", "us-recession-2026"]  # your watch list

with connect(f"{WSS}/{KEY}") as ws:
    ws.send(sub_msg(MARKETS))
    for raw in ws:
        msg = json.loads(raw)
        if msg.get("type") == "event":
            handle_pending(msg["data"])
For maximum coverage, replace market_slugs with ["*"] (firehose — Pro plan only).

Step 2 — Decide fast

The handler runs on every pending event. It has 3–5 seconds to make a decision and ship an order. Anything longer and the window closes.
import requests

DATA = "https://api.predexon.com"
TRADE = "https://trade.predexon.com"
H = {"x-api-key": KEY}
ACCOUNT_ID = "your-account-id"

MIN_NOTIONAL = 5_000      # ignore noise — only act on $5k+ pending trades
LIFT_SIZE = 100           # shares to buy

def handle_pending(t):
    notional = float(t["shares_normalized"]) * float(t["price"])
    if notional < MIN_NOTIONAL:
        return
    if t["side"] != "BUY":
        return            # only lift on buy pressure for this example

    # current best ask — we'll target ask + tick to actually fill
    book = requests.get(
        f"{DATA}/v2/polymarket/market-price/{t['token_id']}",
        headers=H,
    ).json()
    target_price = round(min(book["best_ask"] + 0.001, 0.999), 3)

    # place market order to lift the book before the pending trade settles
    requests.post(
        f"{TRADE}/api/accounts/{ACCOUNT_ID}/orders",
        headers={**H, "Content-Type": "application/json"},
        json={
            "venue": "polymarket",
            "market": {"tokenId": t["token_id"]},
            "side": "buy",
            "type": "limit",       # use limit not market — slippage protection
            "size": str(LIFT_SIZE),
            "price": str(target_price),
            "post_only": False,
        },
    )
Latency budget for this handler: <500ms ideally. If you’re processing more than a few events per second, queue them — your decision must happen before the next block confirms.

Step 3 — Measure your hit rate

You don’t actually know if your strategy is working until you compare pending events to confirmed events. Log both, reconcile after the fact.
# in your handler, log the pending event with timestamp_pending
# in a separate trades subscription (no status filter), log the same trade_id with timestamp_confirmed

# offline analysis:
# - lead_time = timestamp_confirmed - timestamp_pending  (target: 3–5s)
# - hit_rate  = fraction of your lift orders that filled at or below target_price
# - net_pnl   = (avg_fill_price - mid_after_confirm) * shares for each filled lift
If lead time is <1s consistently, the strategy doesn’t have edge — you’re not getting enough lookahead. If hit rate is <30%, your sizing is too aggressive or your target price too tight.

Avoiding self-front-running

If you’re running this on the same account that places the parent order, your handler will see your own pending trade and try to act on it. Two fixes:
  1. Track your own outgoing orders: keep a set of token_ids and timestamps for orders you’ve placed in the last 10s; skip pending events that match.
  2. Use a separate account for the strategy: your front-running account never places anything you’d want to detect.
MY_RECENT_ORDERS = {}  # token_id → timestamp_placed

def handle_pending(t):
    last = MY_RECENT_ORDERS.get(t["token_id"], 0)
    if time.time() - last < 10:
        return   # probably my own order
    ...

Reality check

What you might assumeWhat’s actually true
”3–5s is plenty to do anything”Network round-trip alone is often 200–500ms. Real budget: 2–4s of decision time.
”All pending trades confirm”Some get reverted. Build for 5–10% revert rate.
”Wildcard subscription = full coverage”Wildcard delivers conflated batches every ~250ms. You lose per-event timing precision. Use specific market subscriptions for latency-sensitive work.
”I can run this from anywhere”Polygon RPC is fastest from us-east-1 and Frankfurt. Far-region latency will eat your edge.
”Free plan should be fine for testing”WebSocket is Dev+. Even for testing you need a Dev key.
For honest backtesting before going live, see Signal Backtesting → pending-trade approach. Plan on collecting a week of real pending+confirmed data before sizing in.

Reference