DOCS/CONCEPTS

Timestamps & clocks

Every row carries two timestamps: the venue's and ours. They answer different questions, and most timing bugs in crypto research come from using one where the other belongs.

Two clocks

ts_exchangets_capture
Set byThe venueOur capture host
MeaningWhen the venue says the event happened (match time, book update time or block time, by venue)When the message arrived at our network interface
Unitint64 ns since epoch, UTCint64 ns since epoch, UTC
Real precisionVenue-dependent: ms, µs or ns (table below)ns resolution, accuracy under 50 µs
Clock qualityMaintained by the venue. We do not observe it directly, so we never adjust it.PTP-disciplined, monitored every 16 seconds, out-of-budget intervals published
NullableYes, for message types without a venue timeOnly on venue_archive rows, which we did not capture
Comparable across venuesNoYes, within one region

Both are stored as integers. We never convert to floating-point seconds, because a float64 cannot hold a nanosecond epoch timestamp exactly (it keeps about 0.24 µs of resolution at today's dates).

How ts_capture is taken

ts_capture is the kernel receive timestamp of the network read that delivered the message, recorded with SO_TIMESTAMPING and carried through TLS decryption and parsing. Decompression, parsing and normalization all happen after the timestamp, so our own processing time never leaks into it. When one read delivers several venue messages, they share a ts_capture and keep their order through seq.

Clock synchronization

Capture hosts run on AWS Nitro instances with the Amazon Time Sync Service exposed as a PTP hardware clock on the network adapter. chrony disciplines the system clock against that hardware clock, and a separate monitor reads the clock error bound every 16 seconds. If the bound on any host exceeds 50 µs, the affected interval is listed with that day's quality metrics on Status, with start and end times on the same clock. The rows are kept; you decide whether to drop them.

Error budget

Worst-case contributions to the difference between ts_capture and true UTC on a Tokyo capture host, at the 99th percentile:

ComponentBudgetNotes
Reference to UTC≤ 25 µsTime Sync Service bound for the hardware clock in the region.
Hardware clock to system clock≤ 5 µschrony offset, p99 over a trailing hour.
Timestamp point≤ 15 µsInterrupt and softirq delay between packet arrival and the kernel stamp. Interrupt coalescing is disabled on capture interfaces.
Total, Tokyo (ap-northeast-1)< 50 µsSum of the bounds above with headroom.
Total, Ireland (eu-west-1)< 50 µsSame configuration.
Total, N. Virginia (us-east-1)< 50 µsFrom Phase 2.

A 50 µs bound is three orders of magnitude below the latency between a venue and our hosts, so it is fine for ordering events across venues and for measuring feed latency. It is not fine for comparing two events that our hosts received 10 µs apart; for those, treat the order as unknown unless they share a seq stream.

Venue timestamp precision

ts_exchange is always scaled to nanoseconds, but it is only as precise as the venue publishes. A millisecond venue's timestamps always end in six zeros. The field we read and its precision, by venue:

VenuePrecisionSource fieldNotes
Hyperliquidmstime, block timeL3 and L4 events carry the block timestamp; every event in a block shares it.
BinancemsT (trade, transaction), else E (event)Futures depth updates carry both; we use transaction time T.
BybitmsT, ctscts is the matching engine time for book updates.
OKXmsts
Coinbaseµstime (RFC 3339)Six fractional digits.
Deribitmstimestamp
Krakenµstimestamp (RFC 3339)Spot. Kraken Futures publishes ms.
Bitgetmsts
KuCoinnstimeSpot trades in ns; book updates and futures in ms.
BitfinexmsMTS
Bitstampµsmicrotimestamp
dYdXmscreatedAt, block heightBlock height is kept in seq.

Every venue other than Hyperliquid is planned, subject to a data agreement with the exchange. Their rows follow each venue's public API documentation and will be confirmed with the venue before capture starts; any change is recorded in the changelog. Per-venue details are on Venues.

Which clock to use

TaskOrder or join onWhy
Replay one venue's book or tapeseqThe venue's own ordering. Millisecond timestamps tie constantly; seq never does.
Align several venues or datasetsts_captureOne clock, one location. Venue clocks are not synchronized with each other.
Backtest what a strategy could have knownts_captureA message can only drive a decision after it arrives. Using ts_exchange here leaks future information equal to the feed latency.
Match venue-defined events (funding settlement, candle buckets, venue reports)ts_exchangeThese are defined on the venue's clock.
Measure feed latencyts_capture − ts_exchangeIncludes venue clock error; see below.

Extended fields are computed on ts_capture for the same reason, and published on a 100 ms grid of that clock. See Extended fields.

As-of join across venues

PYTHON · POLARS
import polars as pl
from fdc import Client

fdc = Client()
window = dict(symbol="BTC-PERP", start="2027-09-01T00:00:00Z", end="2027-09-01T01:00:00Z")

hl = fdc.trades(venue="hyperliquid", **window).to_polars()
bn = (fdc.trades(venue="binance", **window).to_polars()
        .select("ts_capture", pl.col("price").alias("binance_price")))

# for each Hyperliquid trade, the last Binance trade we had received by then
joined = hl.sort("ts_capture").join_asof(
    bn.sort("ts_capture"),
    on="ts_capture", strategy="backward",
)

Both venues in this example are captured in Tokyo, so ts_capture on both sides comes from the same region. Joining on ts_exchange instead would mix two venue clocks with different precision and unknown offsets.

Multi-region capture

Each venue is captured independently from two regions: Tokyo (ap-northeast-1, primary) and Ireland (eu-west-1, secondary), with N. Virginia (us-east-1) added in Phase 2. The streams are merged by venue sequence number. In the merged stream, ts_capture is the earliest receive time across the regions, and the region column names the region that received the message first.

  • Earliest receive wins. When both regions have a message, the row keeps the earlier of the two receive times. For a Tokyo-hosted venue that is Tokyo for all but a few messages in ten thousand, usually during a brief stall on the Tokyo side.
  • Secondary fills. When only the secondary region has a message, that row is used and marked region = euw1. Its ts_capture is honest Ireland time, which for a Tokyo-hosted venue is roughly 105 ms later than Tokyo would have recorded. We do not adjust it.
  • Consequence for latency work. Filter to region = apne1 before computing latency statistics, or the secondary fills will fatten the tail.
  • Consequence for ordering. A secondary fill can carry a ts_capture later than the next primary row. Replays that need venue order should sort by seq.

The merge procedure and how often secondary fills occur are on Data quality.

Latency distribution

The difference ts_capture − ts_exchange combines the venue's internal delay, network transit and the venue clock's error. The example below shows the shape for Hyperliquid BTC-PERP trades captured in Tokyo.

HYPERLIQUID · BTC-PERP · TRADES · TOKYOSYNTHETIC
0%5%10%15%020406080100120ts_capture − ts_exchange, msp50 18.4p99 77.8
4 MS BINS · SHARE OF TRADESBARS PAST P90 DIMMED
Capture regionp50p90p99p99.9
Tokyo (apne1)18.4 ms40.7 ms77.8 ms125.0 ms
Ireland (euw1)124.1 ms147.9 ms189.3 ms262.4 ms

SYNTHETIC: illustrative figures that show the shape of the distribution.

Three things to expect when you compute this yourself:

  • Venue time is often the block or batch time. On Hyperliquid every event in a block shares a timestamp, so latency within a block varies with the event's position in it.
  • Millisecond truncation adds up to 1 ms. A venue that truncates to milliseconds reports the event up to 999 µs early, which inflates measured latency by that amount.
  • Negative values can happen. If a venue clock and ours differ by more than the feed latency, some rows show ts_capture before ts_exchange. We publish them as recorded and never clamp or shift venue timestamps.

For a longer treatment of measured latency and clock offsets, see Capture versus exchange timestamps.

Live stream timing

On the WebSocket stream, rows carry the same ts_capture as in the historical archive, so live and historical data line up. The rare exception is a message that reached the other region earlier but arrived at the live merger later; the historical merge then records the earlier time. Our target for added delivery latency is under 5 ms at the median, measured from ts_capture to the moment the message is written to your connection in the same region. Each delivered message also carries ts_send, our send time on the same clock, so you can measure that leg yourself; see WebSocket.