A Python tool to measure latency between Polymarket websocket events and local receipt time.
Key Findings: Testing from an NTP-synced VPS in Amsterdam shows median latency of ~40-42ms to Polymarket's servers with network variance (std dev) of ~28-48ms.
For accurate production measurements:
# On your VPS
git clone <repo>
cd poly-latency
pip install -r requirements.txt
sudo ./sync-clock.sh # Answer 'y' for continuous sync
# Run measurement
python polymarket_latency.py btc-updown-15m-1769050800 500 0
# Expected: Median ~40-60ms (Europe), ~10-30ms (US East)For local/WSL2 testing:
# Calibration handles clock offset automatically
python polymarket_latency.py btc-updown-15m-1769050800 100 10poly-latency/
├── polymarket_latency.py # Main latency measurement script
├── sync-clock.sh # Clock synchronization script (VPS/Ubuntu)
├── requirements.txt # Python dependencies
├── README.md # Documentation
└── .gitignore # Git ignore rules
This project connects to Polymarket's market websocket, subscribes to a specific market by slug, collects a specified number of events (default: 100), and calculates latency statistics by comparing event timestamps with local receipt times.
- Use when: Running on a VPS with NTP time synchronization
- Calibration: DISABLED (
calibration_events=0) - What it measures: True network latency from Polymarket servers to your location
- Typical results: 30-100ms median latency depending on geographic location
- Use when: Running on WSL2 or local machines without NTP sync
- Calibration: ENABLED (default: 10 events)
- What it measures: Network latency after removing clock offset between systems
- Typical results: Small positive/negative values after offset correction
- Install dependencies:
pip install -r requirements.txtFor accurate latency measurements on a VPS, synchronize your system clock with NTP servers:
# Make the sync script executable
chmod +x sync-clock.sh
# Run the sync script (requires sudo)
sudo ./sync-clock.shThe script will:
- Install
ntpdateif needed - Sync your clock with NTP servers
- Optionally enable continuous time synchronization
- Show before/after time status
If you didn't enable continuous sync:
sudo ./sync-clock.sh
python polymarket_latency.py <market-slug> 100timedatectl statusLook for System clock synchronized: yes
python polymarket_latency.py <market-slug> [num_events] [calibration_events]market-slug(required): The Polymarket market slug (e.g., "btc-updown-15m-1769050800")num_events(optional): Number of events to collect before closing (default: 100)calibration_events(optional): Number of initial events to use for clock offset calibration (default: 10)--verbose, -v(optional): Show detailed output for each event including timestamp gaps
VPS with NTP sync (calibration disabled):
python polymarket_latency.py btc-updown-15m-1769050800 500 0Local machine / WSL2 (calibration enabled):
python polymarket_latency.py btc-updown-15m-1769050800 100 10Quick test with defaults (100 events, 10 calibration events):
python polymarket_latency.py will-bitcoin-hit-100k-in-2024Verbose mode to diagnose batching/queueing:
python polymarket_latency.py btc-updown-15m-1769050800 100 0 --verbose
# Shows each event with timestamp gaps to detect batching- Fetch Market Info: Queries Polymarket's REST API to get token IDs for the specified market slug
- WebSocket Connection: Connects to
wss://ws-subscriptions-clob.polymarket.com/ws/market - Subscribe: Sends subscription message with the market's token IDs
- Clock Offset Calibration: Uses the first N events (default: 10) to estimate the clock offset between your system and Polymarket's servers
- Collect Events: For each event received:
- Records the event's timestamp (from Polymarket)
- Records local receipt time
- Calculates raw latency = local_time - event_timestamp
- After calibration: applies offset correction for adjusted latency
- Calculate Statistics: After collecting the specified number of events, displays:
- Raw measurements (with clock offset)
- Adjusted measurements (clock offset removed)
- Median, mean, min, max latency
- Standard deviation
- Percentiles (25th, 75th, 95th, 99th)
$ python polymarket_latency.py btc-updown-15m-1769050800 500 0Fetching market info for slug: btc-updown-15m-1769050800
Market: Bitcoin Up or Down - January 21, 10:00PM-10:15PM ET
Token IDs: ['54680...', '62534...']
Connecting to WebSocket...
Collecting 500 events...
First event received! Type: price_change
Raw latency: 42.93ms
Clock calibration DISABLED - using raw measurements only
Received 10/500 events | Type: price_change | Raw latency: 38.45ms
Received 20/500 events | Type: price_change | Raw latency: 45.32ms
...
============================================================
LATENCY STATISTICS
============================================================
LATENCY MEASUREMENTS (NTP-synced, no calibration):
Total events: 500
Median latency: 42.32ms
Mean latency: 46.01ms
Min latency: 9.69ms
Max latency: 132.23ms
Std deviation: 28.13ms
Percentiles:
25th: 27.15ms
75th: 58.92ms
95th: 95.44ms
99th: 118.32ms
Interpretation:
Median latency of 42.32ms represents the typical time
from when Polymarket creates an event to when you receive it.
Std deviation of 28.13ms shows network variability.
============================================================
What this means:
- Events take ~42ms to reach you from Polymarket (median)
- Network variance causes ±28ms of jitter
- 95% of events arrive within 95ms
- From a VPS in Amsterdam to Polymarket servers
$ python polymarket_latency.py btc-updown-15m-1769050800 100 10First event received! Type: price_change
Raw latency: -961.23ms
Calibrating clock offset using first 10 events...
✓ Calibration complete!
Estimated clock offset: -961.02ms
Collecting remaining events with offset correction...
Received 20/100 events | Type: price_change | Adjusted latency: 12.45ms
...
============================================================
LATENCY STATISTICS
============================================================
RAW MEASUREMENTS (before calibration):
Total events: 100
Median: -961.02ms ← Clock was 961ms behind
Mean: -955.18ms
Std deviation: 28.50ms
────────────────────────────────────────────────────────────
ADJUSTED MEASUREMENTS (clock offset removed):
Clock offset applied: -961.02ms
Events used: 90 (after calibration)
Median latency: 13.25ms
Mean latency: 14.12ms
Std deviation: 28.45ms
Interpretation:
Median latency of 13.25ms represents the typical time
from when Polymarket creates an event to when you receive it.
Std deviation of 28.45ms shows network variability.
============================================================
What this means:
- Local clock was 961ms behind Polymarket's clock
- After calibration: ~13ms estimated network latency
- Calibration is less accurate than NTP sync (for reference only)
Based on real-world testing from an NTP-synced VPS in Amsterdam (DigitalOcean):
| Metric | Value | Notes |
|---|---|---|
| Median Latency | 40-42ms | Typical event delivery time |
| Mean Latency | 46-57ms | Average (affected by outliers) |
| Std Deviation | 28-48ms | Network jitter/variance |
| 95th Percentile | ~95ms | 95% of events arrive within this time |
| Min Latency | 8-10ms | Best-case delivery |
| Max Latency | 130-202ms | Worst-case (during high load/network issues) |
Test parameters: 500 events per measurement, multiple markets tested
Latency will vary based on your VPS location relative to Polymarket's servers:
- US East Coast: ~10-30ms (closest)
- Europe (Amsterdam): ~40-50ms (tested)
- Asia Pacific: ~150-250ms (estimated)
All event types show similar latency:
price_change: Most common, ~42ms medianbook: Order book updates, ~42ms medianlast_trade_price: Trade executions, ~42ms median
You can find market slugs in Polymarket URLs:
- URL:
https://polymarket.com/event/btc-updown-15m-1769050800 - Slug:
btc-updown-15m-1769050800
# ONE-TIME SETUP
sudo ./sync-clock.sh # Enable continuous sync when prompted
timedatectl status # Verify: "synchronized: yes"
# ONGOING MEASUREMENTS
python polymarket_latency.py btc-updown-15m-1769050800 500 0
# Args: market-slug, 500 events, 0 = no calibration
# EXPECTED RESULTS (Europe)
# Median: ~40-50ms
# Std Dev: ~25-50ms
# All values positive# Run with automatic clock offset calibration
python polymarket_latency.py btc-updown-15m-1769050800 100 10
# Args: market-slug, 100 events, 10 = calibrate with first 10
# EXPECTED RESULTS
# Raw median: may be negative (clock offset)
# Adjusted median: ~10-50ms (estimated)Seeing negative latencies on VPS?
# Check sync status
timedatectl status # Should show "synchronized: yes"
# Check time offset
ntpdate -q pool.ntp.org # Should be < 0.05 seconds
# Force resync
sudo ./sync-clock.shHigh latency or variance?
# Test multiple times to confirm
for i in {1..3}; do
python polymarket_latency.py <market-slug> 200 0
sleep 60
done
# If consistently high:
# - Check VPS network quality
# - Try different geographic region
# - Check Polymarket status pageVery high variance (std dev > 80ms)?
# Run in verbose mode to see batching pattern
python polymarket_latency.py <market-slug> 500 0 --verbose
# Look for:
# - Clusters of high latencies (200-300ms)
# - Then clusters of low latencies (5-20ms)
# - Large timestamp gaps between events
# This indicates server-side batching, not network issuesGetting warnings about high variance?
⚠️ High Variance Detected:
Std deviation (84.35ms) is 2.5x the median.
This suggests server-side batching/queueing, not just network jitter.
This is normal - it means Polymarket is batching events internally. Your network is fine. The median latency is still the most reliable metric for typical delivery time.
The tool automatically handles clock synchronization differences between your local system and Polymarket's servers:
- Calibration Phase: The first 10 events (configurable) are used to estimate the clock offset
- Offset Calculation: The median latency from calibration events is used as the clock offset
- Adjustment: All subsequent measurements have the offset removed to show true network latency
Without clock synchronization:
- Raw latency might be negative (your clock is behind)
- Raw latency might be inflated (your clock is ahead)
With calibration:
- Adjusted latency shows actual message transmission time
- Standard deviation reflects real network variability
Clock offset calibration is reliable when:
- ✓ The actual network latency is much smaller than the clock offset
- ✓ Both clocks are stable (not drifting rapidly)
- ✓ You collect enough calibration events (10+ recommended)
- ✓ The measurement period is short (minutes, not hours)
When to use:
- Production latency monitoring
- Trading/arbitrage systems
- Accurate absolute measurements
- Multi-server comparisons
Setup:
# One-time: Enable NTP sync
sudo ./sync-clock.sh
# Answer 'y' when prompted for continuous sync
# Verify sync
timedatectl status # Should show "synchronized: yes"
# Run measurements (calibration disabled)
python polymarket_latency.py <market-slug> 500 0Expected results:
- ✓ Positive latencies (30-100ms typical)
- ✓ Median shows true network latency
- ✓ Std deviation shows real network variance
- ✗ Negative latencies = clock sync failed
Why this is best:
- Both clocks (yours and Polymarket's) synced to same NTP reference
- No assumptions or calibration needed
- Measurements are reproducible and comparable
- Accurate to within ±1-50ms (NTP accuracy)
When to use:
- WSL2/local development (clock drifts)
- No sudo access for NTP
- Quick one-off testing
- Clock offset >> network latency
Setup:
# Run with calibration (default)
python polymarket_latency.py <market-slug> 100 10Limitations:
- ⚠ Assumes median of first N events = clock offset
- ⚠ Only works if clock offset >> network latency
- ⚠ Less accurate than NTP (reference only)
- ⚠ Not suitable for sub-50ms precision
Why calibration is less reliable:
If true latency = 40ms and clock offset = 50ms:
Calibration will estimate offset as ~90ms (40+50)
All subsequent measurements will be wrong by ~40ms
DON'T use calibration with NTP sync:
# WRONG - will remove real network variance
sudo ./sync-clock.sh
python polymarket_latency.py <market-slug> 500 10 # ✗ Don't calibrate!DON'T trust absolute values without NTP:
# On local machine without NTP
python polymarket_latency.py <market-slug> 100 0 # ✗ May show negative latenciesWhen running on an NTP-synced VPS with calibration disabled, here's what the numbers mean:
✓ Healthy Results (Clock Properly Synced):
Median latency: 42.32ms ← Positive value = clocks synced
Mean latency: 46.01ms ← Close to median = consistent
Std deviation: 28.13ms ← Network jitter/variance
Min: 9.69ms ← Best-case latency
Max: 132.23ms ← Worst-case (spikes)
95th percentile: 95ms ← 95% arrive within this time
Interpretation:
- Median (42ms): Typical time from event creation to receipt
- Std Dev (28ms): Network is variable but stable
- Range (9-132ms): Network conditions vary, but no major issues
- 95th %ile (95ms): Good for SLA planning
✗ Problem Results (Clock Not Synced):
Median latency: -51.56ms ← NEGATIVE = CLOCK PROBLEM!
Mean latency: -46.38ms ← Confirms clock offset
Troubleshooting negative latencies:
# Check NTP sync status
timedatectl status
# Should show: "System clock synchronized: yes"
# Query actual time difference
ntpdate -q pool.ntp.org
# Should show: offset < 0.05 seconds
# Force resync if needed
sudo ./sync-clock.shBased on testing, these factors impact latency:
-
Geographic Distance (biggest factor)
- Same region as Polymarket: 10-30ms
- Cross-Atlantic: 40-60ms
- Cross-Pacific: 150-250ms
-
Network Route Quality
- Premium networks (AWS, GCP): Lower variance
- Budget VPS: Higher variance
- Observed: 28-48ms std deviation (normal)
- Observed: 80-100ms std deviation (indicates batching)
-
Server-Side Batching/Queueing (significant impact!)
- Most common cause of high variance
- Events timestamped at creation but queued before sending
- Results in clusters of high latency (200-300ms) events
- Then clusters of low latency (5-20ms) events
- Creates bimodal distribution instead of normal distribution
-
Market Activity
- Low activity: More consistent (~28ms std dev)
- High activity: More variance (~48-80ms std dev)
- Spikes during major events (100-300ms possible)
-
Event Type (minimal impact)
- All types show similar latency
- No significant difference observed
Normal Network Variance:
Std dev: 25-50ms
95th percentile: 2-3x median
Max latency: 3-4x median
Pattern: Random fluctuations
Server-Side Batching/Queueing:
Std dev: 80-100ms+
95th percentile: 5-8x median
Max latency: 10x+ median
Pattern: Clusters of high/low latencies
Example of Batching Pattern:
Events 200-250: ALL 225-309ms (queued batch released)
Events 260-420: ALL 4-50ms (fresh events)
Events 430-470: Rising 30-90ms (queue building up)
This is normal Polymarket behavior, not a network problem. Use the --verbose flag to see timestamp gaps and detect batching.
-
Use NTP-synced VPS with calibration disabled (
0)- Most accurate and reliable measurements
- Reproducible across different servers
- No assumptions or corrections needed
-
Expect ~40-60ms median latency from Europe
- Actual value depends on your location
- Lower from US East Coast (~10-30ms)
- Higher from Asia Pacific (~150-250ms)
-
Network variance is normal
- Std deviation of 25-50ms is typical
- 95th percentile 2-3x median is expected
- Occasional spikes to 100-200ms during peak activity
-
Monitor continuously for changes
- Run every 15-30 minutes to detect issues
- Compare median over time (should be stable)
- Alert if median increases >50% or std dev doubles
-
Calibration works for local/WSL2
- Good enough for relative comparisons
- Don't trust absolute values
- Re-calibrate periodically
-
Negative raw latencies = clock offset
- Normal on unsynchronized systems
- Calibration will correct it
- For accurate values, use NTP instead
-
One-way latency assumption
- Assumes Polymarket uses NTP (likely, but not guaranteed)
- Accuracy depends on both clocks being synced
- True accuracy is ±1-50ms (NTP precision)
-
Application-layer delays unknown
- Measures time from event timestamp to receipt
- Doesn't account for Polymarket's internal delays
- Actual "freshness" may be slightly worse
-
Network path can change
- Routing changes affect latency
- Measurements valid for current network conditions
- Monitor over time to detect changes
- All timestamps are in milliseconds (Unix epoch)
- Raw latency:
local_receipt_time - event_timestamp - Adjusted latency:
raw_latency - clock_offset - Connection auto-closes after collecting specified events
- Non-timestamped messages (e.g., subscription confirmations) are excluded from statistics
- PING messages sent every 10 seconds to maintain WebSocket connection