Reproducible
Methodology
Anyone should be able to check these numbers, so here is exactly what was done.
What was measured
For each feed we record, for every item that appears after we start watching:
seen_ts— wall-clock UTC of the first poll in which that item's unique id appeared in the feed body.pub_ts— the publisher's own timestamp on that item (pubDate,publishedorupdated).lag_s = seen_ts - pub_ts.
Procedure
- Poll every feed once to build a baseline set of item ids. Those items are recorded but excluded from all latency statistics — they existed before we were watching, so their lag is meaningless.
- Re-poll every feed on a fixed 15-second cycle for the measurement window (2026-08-13 16:46–16:59 UTC, 13 minutes).
- Any id not previously seen is a new item: record its lag.
- Report the median and p90 of each feed's lag distribution, its item rate, its median HTTP response time, and its poll error count.
Known limits — stated because they change how you read the table
- 15-second granularity. Measured lag includes up to 15s of our own polling delay. Sub-15s figures mean “at or below our resolution”, not a precise value.
- Publisher timestamps are trusted. If a publisher back-stamps an item to when its reporter filed rather than when it published, its lag looks worse than its true serving delay. That is a real property of the feed a consumer sees, so we do not correct for it.
- Minute-quantised stamps. Several feeds emit timestamps with no seconds component, which adds up to 60s of noise in either direction on individual items. The median over many items absorbs most of this.
- One window, one location. This is a single measurement window from a single host. It is a snapshot, not an SLA. Repeat runs are what turn it into a trend, which is why the collector script is published rather than described.
- CDN caching. Some publishers front their feed with a CDN whose TTL sets a floor under observable lag. That floor is part of what a reader experiences.
The collector
Standard library only, no dependencies. Point FEEDS at any RSS/Atom URLs you care
about and run it.
import json, time, urllib.request, email.utils, datetime
import xml.etree.ElementTree as ET
FEEDS = {"CNBC Markets": "https://www.cnbc.com/id/10000664/device/rss/rss.html",
"SEC EDGAR 8-K": "https://www.sec.gov/cgi-bin/browse-edgar?action=getcompany"
"&type=8-K&count=40&output=atom"} # add your own
def parse_dt(s):
try:
d = email.utils.parsedate_to_datetime(s)
except Exception:
d = datetime.datetime.fromisoformat(s.replace("Z", "+00:00"))
if d.tzinfo is None:
d = d.replace(tzinfo=datetime.timezone.utc)
return d.timestamp()
def items(xml):
for it in ET.fromstring(xml).iter():
if it.tag.split("}")[-1] not in ("item", "entry"):
continue
d = {}
for ch in it:
t = ch.tag.split("}")[-1]
if t == "link" and ch.get("href"):
d.setdefault("link", ch.get("href"))
elif ch.text:
d.setdefault(t, ch.text.strip())
uid = d.get("guid") or d.get("id") or d.get("link")
stamp = d.get("pubDate") or d.get("published") or d.get("updated")
if uid:
yield uid, (parse_dt(stamp) if stamp else None)
seen, first = {k: set() for k in FEEDS}, {k: True for k in FEEDS}
end = time.time() + 900 # 15-minute window
while time.time() < end:
for name, url in FEEDS.items():
try:
req = urllib.request.Request(url, headers={"User-Agent": "latency-check/1.0"})
xml, now = urllib.request.urlopen(req, timeout=12).read(), time.time()
except Exception:
continue
for uid, pub in items(xml):
if uid in seen[name]:
continue
seen[name].add(uid)
if not first[name] and pub: # baseline poll is excluded
print(json.dumps({"feed": name, "lag_s": round(now - pub, 1)}))
first[name] = False
time.sleep(15)
Download our raw measurements (CSV)
Measuring your feed is step one. Widening it is step two.
The feeds above are what a retail trader gets for free: one publisher each, polled on the publisher's schedule. If you want the other end of the curve — 40+ sources merged into a single stream with sentiment tagging and sub-2-second latency — that is what Top Tier Newswire is built for. The live feed is free to try; the real-time tier is paid.