Comparing Venue Prices
Fetch matched events, walk matched markets, and read midpoints. The client batches 75 ids.
The same prediction market often trades on more than one venue at the same time, at different
prices. sufwan-local-test's matching pipeline links those markets together as matchedVenueMarkets so you
can build a cross-venue price comparison in three calls:
- List matched events — events confirmed on two or more venues.
- Walk every market and its matched siblings — collect each
venueMarketId. - Batch-fetch midpoints — one
getMidpointscall returns the live Yes-side price per market. The client batches 75 ids.
The midpoint is the live orderbook's headline mark on a 0–1 scale, where 0.55 means the
market implies a 55% probability of "Yes". Comparing midpoints across venues for the same
matched event surfaces the spread an arbitrageur, smart router, or "best price" badge cares
about.
1. Create the client
import { createAggClient, MarketStatus, MatchStatus } from "@agg-build/sdk";
const client = createAggClient({
baseUrl: "https://api.t.sufwankhalid.com",
appId: "your-app-id",
});2. List matched events
getVenueEvents accepts matchStatus as an array. Pass ["matched", "verified"] to restrict
the response to events confirmed across more than one venue — every market in the response
includes its matchedVenueMarkets. Calls GET /venue-events.
const { data: events } = await client.getVenueEvents({
status: [MarketStatus.open],
matchStatus: [MatchStatus.matched, MatchStatus.verified],
limit: 50,
});3. Collect every venueMarketId
Walk the response and gather every market ID — the event's own markets plus each
matchedVenueMarkets[].id.
const ids = new Set<string>();
for (const event of events) {
for (const market of event.venueMarkets ?? []) {
ids.add(market.id);
for (const matched of market.matchedVenueMarkets ?? []) {
ids.add(matched.id);
}
}
}The venueMarkets array on a list event is a preview of up to three markets. Page
GET /venue-markets?venueEventId= for the full set.
Sibling venues stay on matchedVenueMarkets[]. Which id to pass is Ids. Midpoints
take venueMarketId.
4. Batch-fetch midpoints
The client sends at most 75 ids per request and merges the responses. Pass bestPrice=true to add
bestBid, bestAsk, and arbReturn. Two Hyperliquid listings in one cluster are different books.
See Hyperliquid deployers.
const { data: midpoints } = await client.getMidpoints({
venueMarketIds: [...ids],
bestPrice: true,
});Get live midpoints for multiple markets.
Entries are never dropped — check error before trusting a price
Every requested id gets an entry back, even one the API can't price. Unknown or
resolved markets return midpoint: null alongside a structured error rather
than being omitted, so the response array stays aligned with the ids you sent
and a batched request can't silently shift. GET /orderbooks behaves the same way.
error.code | Meaning |
|---|---|
market_not_found | The id doesn't resolve to a known market — check it |
market_resolved | Market has settled; prices are nulled instead of served stale |
for (const m of midpoints) {
if (m.error) {
console.warn(`skipping ${m.venueMarketId}: ${m.error.code}`);
continue;
}
// m.midpoint may still be null when the book is empty but the market is live.
}Don't treat data[0] as "the match" for a single-id lookup. Filter on
venueMarketId, and check error — otherwise a typo'd id yields an entry with
no price that reads like a real, cheap market.
5. Build the comparison
Index midpoints by venueMarketId, then for each market look up its own midpoint plus its
siblings'.
const byId = new Map(midpoints.map((m) => [m.venueMarketId, m]));
for (const event of events) {
for (const market of event.venueMarkets ?? []) {
const own = byId.get(market.id);
const siblings = (market.matchedVenueMarkets ?? [])
.map((m) => byId.get(m.id))
.filter((m): m is NonNullable<typeof m> => !!m);
const usable = [own, ...siblings].filter(
(m): m is NonNullable<typeof m> => !!m && m.midpoint !== null,
);
if (usable.length < 2) continue;
const cheapest = usable.reduce((a, b) => (a.midpoint! < b.midpoint! ? a : b));
const richest = usable.reduce((a, b) => (a.midpoint! > b.midpoint! ? a : b));
console.log(event.id, {
spread: richest.midpoint! - cheapest.midpoint!,
cheapest: { venue: cheapest.venue, midpoint: cheapest.midpoint },
richest: { venue: richest.venue, midpoint: richest.midpoint },
});
}
}Live updates
/midpoints is a snapshot. There is no midpoint WebSocket channel. Live depth is the
orderbook socket, keyed by venueMarketOutcomeId. Snapshots and
deltas on that socket include a midpoint for the outcome.