Polymarket RTDS vs Struct Trades WS: A Latency Benchmark

We benchmarked Polymarket's RTDS feed against the Struct Trades WebSocket across ~15,000 live trades. Struct's confirmed feed leads by ~0.5s, its mempool feed by 2.6s.

Struct Team
Struct Team
6 min read
Polymarket RTDS vs Struct Trades WS: A Latency Benchmark

Polymarket's RTDS is the standard way to get live trades. We recently updated the Struct Trades WebSocket, and we wanted to see exactly how the latency compares. We set up a harness to track both feeds on the same clock, matching every fill by transaction hash over a sample of 15,000 live trades.

Here are the results: Struct was faster on both of its paths. The confirmed on-chain feed led RTDS by about half a second. The mempool feed led it by 2.6 seconds, on every single trade.

The harness is open source at structbuild/polymarket-rtds-vs-struct-benchmark. Grab a free API key and reproduce every number here in one command.

The short version:

  • Struct's confirmed feed is already faster than RTDS. Both wait for the same on-chain block, yet Struct pushed each trade a median of ~0.5s sooner and led on roughly 85 to 90 percent of trades. If you never touch mempool data, you are still ahead.
  • Struct's mempool feed is far faster. In pending mode it reads the Polygon mempool and surfaced each trade a median of 2.6s before RTDS, leading on 100% of matched trades. Best case: +5.0s.
  • Comprehensive coverage. Struct caught 100% of the trades RTDS reported, surfacing ~97% of them in the mempool before they confirmed on-chain.
  • The early signal is real. 99.9 to 100 percent of mempool order fills went on to confirm on-chain.
  • Method: two sockets, one process, one clock. ~15,000 trades, 11,000+ paired latency measurements, live against Polygon mainnet.

The result, in seconds

Every trade RTDS reports also shows up on Struct, and it shows up sooner. How much sooner depends on which Struct path you read:

Struct Trades WS pathTypical lead over RTDSFaster onBest case
Confirmed (on-chain)~0.5 seconds~85 to 90%~2s
Pending (mempool)~2.6 seconds100%~5s

Read the rows as "Struct surfaced the trade this far ahead of Polymarket's public feed." Even the slower Struct path, the one that waits for the block just like RTDS does, is ahead most of the time. The mempool path is ahead always.

Why is Struct faster than RTDS?

Two different reasons, one per path.

The mempool path wins on timing. A Polymarket CLOB trade settles as an on-chain transaction. RTDS reports it once that transaction has mined and been indexed. Struct's pending mode reports it while it is still sitting in the Polygon mempool, before it mines. Same trade, earlier sighting. The gap between "seen pending" and "confirmed and indexed" is the entire 2.6-second head start, and on Polygon that gap is consistently multi-second.

The confirmed path wins on plumbing. Here both feeds are waiting for the exact same thing: the confirmed block. Struct still delivered the fill a median of half a second sooner, because the path from a confirmed block to a socket push is shorter on Struct than the pipeline feeding RTDS. This is the part worth sitting with. It means Struct is faster even when you strip the mempool advantage out entirely and compare like for like on confirmed data.

How we measured it

The setup is deliberately simple so the numbers are hard to argue with. One Node process opens two WebSocket connections and stamps every incoming event with Date.now() off the same clock, so there is no cross-machine time skew to correct for.

Struct's side is one subscription. Passing status: "all" delivers the pending sighting first and the confirmed sighting later for the same trade, so a single socket gives us both Struct paths:

import { StructWebSocket } from "@structbuild/sdk";

const struct = new StructWebSocket({ apiKey: process.env.STRUCT_API_KEY! });
const seen = new Map<string, { pending?: number; confirmed?: number; rtds?: number }>();

struct.on("trade_stream_update", (trade) => {
	const now = Date.now();
	const row = seen.get(trade.hash) ?? {};
	const isPending = trade.received_at != null && trade.block == null;
	if (isPending) row.pending ??= now;
	else row.confirmed ??= now;
	seen.set(trade.hash, row);
});

await struct.connect();
await struct.subscribe("polymarket_trades", { status: "all", subscribe_all: true });

A pending event carries received_at (Unix milliseconds) and omits block and confirmed_at, which is exactly how we tell the two sightings apart. The trades room docs list every field and filter.

Polymarket's side is the RTDS activity topic, the one that powers the live trade tape on their site:

const rtds = new WebSocket("wss://ws-live-data.polymarket.com");

rtds.addEventListener("open", () => {
	rtds.send(
		JSON.stringify({
			action: "subscribe",
			subscriptions: [
				{ topic: "activity", type: "trades" },
				{ topic: "activity", type: "orders_matched" },
			],
		}),
	);
});

rtds.addEventListener("message", (event) => {
	const msg = JSON.parse(event.data.toString());
	if (msg.topic !== "activity") return;
	const hash = msg.payload.transactionHash.toLowerCase();
	const row = seen.get(hash) ?? {};
	row.rtds ??= Date.now();
	seen.set(hash, row);
});

Both feeds report the same on-chain transactionHash, so it is the join key. For each hash we keep the first sighting from each feed, and the latency between any two is the difference of their first-sighting times. We trim the first and last seconds of each window so socket warm-up and cool-down do not skew the medians.

Every window, same result

The takeaway is consistency. Both Struct paths led RTDS in every single window we captured. These results are representative of typical performance across all our testing.

WindowTrades matchedConfirmed feed led RTDS byMempool feed led RTDS by
75s2,300+0.51s+2.88s
120s2,317+0.54s+2.64s
120s1,418+0.36s+2.59s
180s3,624+0.56s+2.68s
180s2,911+0.32s+2.66s

Every figure is a median across trades matched by hash. The confirmed feed's lead holds in a tight 0.3 to 0.6 second band; the mempool lead holds between 2.6 and 2.9 seconds. On the mempool path, Struct was never once slower than RTDS on a matched trade.

Does Struct miss any trades?

Speed is worthless if the fast feed is also lossy, so this was the number we cared about most. After trimming, Struct caught 100% of the trades RTDS reported in every window.

Trades on RTDS's activity feedResult
Also seen by Struct100%
Caught early in the mempool~97%
Missed by Struct0%

The ~3% not caught in the mempool skip the public broadcast, private orderflow and non-broadcast operator settlements, and still land on Struct at confirmation, just without the head start. The coverage gap actually runs the other way: Struct carries hundreds of lifecycle transactions per window (redemptions, merges, splits, conversions) that RTDS's activity feed never streams at all.

Is the early mempool signal reliable?

A mempool sighting is only useful if the transaction actually mines. A feed that fires on pending transactions that later get dropped or replaced would be fast and wrong.

It holds up. Of the CLOB order fills we saw in the mempool, 99.9 to 100 percent confirmed on-chain. Across every pending transaction type, including redemptions and merges, it was about 99 percent. We gate this with an eight-second confirmation grace so a trade seen pending in the last moment of a window is not falsely counted as unconfirmed.

Reconcile for anything financial

The mempool tick is the right signal for a live tape, a fast chart, or a trigger. For settlement, PnL, or accounting, reconcile against the confirmed sighting. Struct gives you both from one subscription: the pending event for speed, the confirmed event for truth.

Subscribe to the stream

The feed we benchmarked is a single subscription. Confirmed on-chain trades, already faster than RTDS, are the default:

await struct.subscribe("polymarket_trades");

Mempool-only, for the full 2.6-second lead:

await struct.subscribe("polymarket_trades", { status: "pending" });

Both sightings, pending then confirmed, for reconciliation:

await struct.subscribe("polymarket_trades", { status: "all" });

Scope it to what you care about with condition_ids, market_slugs, event_slugs, position_ids, or traders, and narrow to trade_types like OrderFilled and OrdersMatched. The WebSocket reference has the full filter surface.

Get a Struct API key: the trades room, pending mode included, starts on the free plan

Run the benchmark yourself: the full harness, open source

WebSocket documentation

TypeScript SDK

Frequently asked questions

Ship faster with Struct

REST API, WebSockets, and Webhooks for Polymarket — free to start, no credit card required.