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:
- A WebSocket subscription that fires on pending trades
- A latency-honest decision function
- (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:
- 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.
- 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 assume | What’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