市场数据时间戳:SIP与交易所时钟
市场数据时间戳来自四种时钟。按不同时间排序同一批成交会得到不同序列,了解每种时钟的含义,判断何时使用SIP或交易所时间。
市场数据时间戳是交易成交记录从执行交易的撮合引擎传到显示屏时附带的时间。美国股票交易在公开记录中有三个时间戳,每个时间戳回答的问题不同。用一个时间戳、再用另一个时间戳对同一天的成交记录排序,得到的是两条真正不同的成交序列。
单笔成交记录经过的四个时间
一笔成交记录在传到您面前的过程中会多次加盖时间戳。顺序如下:
- 撮合引擎时间。 交易场所的撮合引擎撮合两笔订单的瞬间。交易场所之外的任何人都无法直接读取这一时间。它是交易发生时间的真实基准,后续每个时间戳都只是对它的近似。
- 参与者时间,也称交易场所时间或交易所时间。 交易场所在自己的数据源上发布成交记录时写入的时间戳,位于
participant_timestamp字段。在所有实际可读取的时间中,它最接近撮合引擎时间。 - SIP时间。 当成交记录到达证券信息处理器、进入合并成交数据流时,由证券信息处理器写入的时间戳。合并成交数据流是整合美国所有股票交易场所的唯一官方数据源。这是
sip_timestamp字段,官方成交序列也按该时间排序。该数据源与交易场所自有数据源的区别,见SIP与交易所直连数据源。 - 采集时间。 数据包到达您自己的网卡时,由网卡写入的时间戳。供应商的记录中不会出现这一时间,因为它描述的是您的数据路径,而不是市场。数据包采集与回放工作完全以这一时间为准。
场外成交记录还带有第五个时间戳,即 trf_timestamp,表示交易报告机构接收交易报告的时间。
为什么不同交易场所的市场数据时间戳不一致
交易场所时间戳与合并时间戳之间的差值,是成交记录在传输过程中以及在处理器队列中等待的时间。它不是一个固定数值。每个交易场所与处理器的距离不同,使用的硬件不同,所处的队列也不同。下方面板统计了2026年6月10日固定半小时内所有打印 AAPL 成交记录的交易场所,测量其时间差。
每个数字背后的完整 SQL
WITH venues AS
(
SELECT
toUInt32(id) AS exchange_id,
any(coalesce(nullIf(acronym, ''), name)) AS venue_name
FROM global_markets.stocks_exchanges
WHERE asset_class = 'stocks'
GROUP BY exchange_id
)
SELECT
if(v.venue_name = '', concat('Venue ', toString(t.exchange)), v.venue_name) AS venue,
count() AS print_count,
round(quantileDeterministic(0.5)(
toFloat64(toUnixTimestamp64Nano(t.sip_timestamp)
- toUnixTimestamp64Nano(t.participant_timestamp)) / 1000,
toUInt64(t.sequence_number)), 1) AS median_lag_us,
round(quantileDeterministic(0.99)(
toFloat64(toUnixTimestamp64Nano(t.sip_timestamp)
- toUnixTimestamp64Nano(t.participant_timestamp)) / 1000,
toUInt64(t.sequence_number)), 1) AS p99_lag_us
FROM global_markets.stocks_trades AS t
LEFT JOIN venues AS v ON v.exchange_id = toUInt32(t.exchange)
WHERE t.ticker = 'AAPL'
AND t.sip_timestamp >= '2026-06-10 14:30:00'
AND t.sip_timestamp < '2026-06-10 15:00:00'
AND ifNull(toUnixTimestamp64Nano(t.trf_timestamp), 0) = 0
GROUP BY venue
HAVING count() >= 200
ORDER BY median_lag_us DESC
LIMIT 15在这些交易场所中,交易场所时间戳与合并时间戳之间的中位数差值最大为 346.2 微秒,出现在 NYSE Arca, Inc.。同一时间窗口内,差值最小的交易场所为 13.7 微秒。最值得关注的是 p99 列:对于同一个最慢的交易场所,该数值达到 420.8 微秒,显示出中位数掩盖的尾部延迟。
应使用哪个时间戳
四条规则几乎涵盖所有情况。
- 微观结构研究和事件研究使用参与者时间。 任何衡量某个交易场所发生了什么以及发生顺序的研究,都应使用交易场所时间。订单簿重建是其中一例;MBO与MBP订单簿数据说明了这一点,使用其他时间无法完成可靠重建。
- 凡是必须与官方成交数据核对的内容,使用SIP时间。 包括监管报告、最佳执行审查、官方开盘价和收盘价,以及交易对手会拿合并记录核对的任何数据。
- 只有测量您自己的数据路径时,才使用采集时间。 它告诉您数据用了多长时间到达您的机器,但无法说明交易何时发生,而且不同机器的采集时间不会一致。
- 不要在同一数据集中混用不同时间戳。 如果用一个时间戳匹配报价、用另一个时间戳匹配成交,得到的结果可能看似合理,却会在最关键的时刻失真。
同一批成交记录的两种排序
排序会让这种抽象差异变得明显。下方面板取出该半小时内成交最密集的十毫秒,保留按交易场所时间排序的前十二笔场内成交记录,然后再次按合并成交数据流的顺序对同十二笔记录排序。表格按每笔成交记录在两种排序中的名次变化幅度排列。
每个数字背后的完整 SQL
WITH
burst AS
(
SELECT intDiv(toUnixTimestamp64Nano(participant_timestamp), 10000000) AS slice_10ms
FROM global_markets.stocks_trades
WHERE ticker = 'AAPL'
AND sip_timestamp >= '2026-06-10 14:30:00'
AND sip_timestamp < '2026-06-10 15:00:00'
AND ifNull(toUnixTimestamp64Nano(trf_timestamp), 0) = 0
GROUP BY slice_10ms
ORDER BY count() DESC, slice_10ms ASC
LIMIT 1
),
sample AS
(
SELECT
participant_timestamp,
sip_timestamp,
toUInt64(sequence_number) AS seq,
toUnixTimestamp64Nano(sip_timestamp)
- toUnixTimestamp64Nano(participant_timestamp) AS lag_ns
FROM global_markets.stocks_trades
WHERE ticker = 'AAPL'
AND sip_timestamp >= '2026-06-10 14:30:00'
AND sip_timestamp < '2026-06-10 15:00:00'
AND ifNull(toUnixTimestamp64Nano(trf_timestamp), 0) = 0
AND intDiv(toUnixTimestamp64Nano(participant_timestamp), 10000000)
IN (SELECT slice_10ms FROM burst)
ORDER BY participant_timestamp ASC, seq ASC
LIMIT 12
),
ranked AS
(
SELECT
participant_timestamp,
sip_timestamp,
lag_ns,
row_number() OVER (ORDER BY participant_timestamp ASC, seq ASC) AS participant_rank,
row_number() OVER (ORDER BY sip_timestamp ASC, seq ASC) AS sip_rank
FROM sample
)
SELECT
concat('P', leftPad(toString(participant_rank), 2, '0')) AS print_label,
concat(formatDateTime(toTimeZone(participant_timestamp, 'America/New_York'), '%H:%i:%S'), '.',
leftPad(toString(intDiv(toUnixTimestamp64Nano(participant_timestamp) % 1000000000, 1000)), 6, '0')) AS venue_clock_et,
concat(formatDateTime(toTimeZone(sip_timestamp, 'America/New_York'), '%H:%i:%S'), '.',
leftPad(toString(intDiv(toUnixTimestamp64Nano(sip_timestamp) % 1000000000, 1000)), 6, '0')) AS tape_clock_et,
participant_rank,
sip_rank,
abs(toInt32(sip_rank) - toInt32(participant_rank)) AS places_moved,
round(lag_ns / 1000, 1) AS sip_lag_delta_us
FROM ranked
ORDER BY places_moved DESC, participant_rank ASC某笔成交记录在两种排序中的最大名次差为 6。该笔成交记录在 10:44:17.160712 离开交易场所,并在 305.7 微秒后、即 10:44:17.161018 到达成交数据流;它在交易场所时间排序中位于第 6 位,在成交数据流中位于第 12 位。两种排序都没有错误。它们回答的是不同问题。使用成交数据流时间进行交易序列研究,会以任何交易场所都未产生过的顺序读取这段成交密集区间;而使用交易场所时间进行最佳执行审查,则会与官方记录不一致。
场外成交记录远远晚于事件才到达
在交易所之外、由批发商或暗池执行的交易,不是在公开订单簿上撮合,而是报告给交易报告机构。报告带有执行时间,但会在之后才到达成交数据流。这段间隔属于报告延迟,而不是传输时间,规模也大得多。
每个数字背后的完整 SQL
WITH off_exchange AS
(
SELECT
(toUnixTimestamp64Nano(sip_timestamp)
- toUnixTimestamp64Nano(participant_timestamp)) / 1000000.0 AS delay_ms
FROM global_markets.stocks_trades
WHERE ticker = 'AAPL'
AND sip_timestamp >= '2026-06-10 14:30:00'
AND sip_timestamp < '2026-06-10 15:00:00'
AND ifNull(toUnixTimestamp64Nano(trf_timestamp), 0) > 0
)
SELECT
multiIf(delay_ms < 1, 'under 1 ms',
delay_ms < 10, '1 to 10 ms',
delay_ms < 100, '10 to 100 ms',
delay_ms < 1000, '100 ms to 1 s',
delay_ms < 10000, '1 s to 10 s',
'over 10 s') AS reporting_delay,
count() AS print_count,
round(100 * count() / (SELECT count() FROM off_exchange), 2) AS share_pct
FROM off_exchange
GROUP BY reporting_delay
ORDER BY min(delay_ms) ASC在该时间窗口内的场外 AAPL 成交记录中,24.8% 笔落入 under 1 ms 区间。尾部延迟最长达到 1 s to 10 s 区间,其中包含 74 笔成交记录。一笔晚到十秒的成交记录,仍会带有实际执行时的交易场所时间,但在成交数据流中会落后实际事件十秒。按成交数据流时间排序时,它看起来就像出现在错误的分钟内。在正常序列之外报告的成交记录会带有说明这一情况的销售条件代码,这也是交易条件代码用于标记的事项之一。
为什么两个人会用同一批成交记录构建出不同的K线
几乎所有“数据错误”工单最终都涉及这一问题。K线是成交记录的时间区间集合,而一笔成交记录属于哪个区间,取决于按哪个时间戳划分。下方面板统计了在四种常见K线周期下,从交易场所时间切换到成交数据流时间后发生区间变化的成交记录数量。
每个数字背后的完整 SQL
WITH
prints AS
(
SELECT
toUnixTimestamp64Nano(participant_timestamp) AS venue_ns,
toUnixTimestamp64Nano(sip_timestamp) AS tape_ns
FROM global_markets.stocks_trades
WHERE ticker = 'AAPL'
AND sip_timestamp >= '2026-06-10 14:30:00'
AND sip_timestamp < '2026-06-10 15:00:00'
),
grids AS
(
SELECT arrayJoin([1, 10, 60, 300]) AS bar_seconds
)
SELECT
multiIf(bar_seconds = 1, '1 second',
bar_seconds = 10, '10 seconds',
bar_seconds = 60, '1 minute',
'5 minutes') AS bar_length,
countIf(intDiv(venue_ns, toInt64(bar_seconds) * 1000000000)
!= intDiv(tape_ns, toInt64(bar_seconds) * 1000000000)) AS moved_print_count,
round(100 * countIf(intDiv(venue_ns, toInt64(bar_seconds) * 1000000000)
!= intDiv(tape_ns, toInt64(bar_seconds) * 1000000000)) / count(), 3) AS moved_pct
FROM prints
CROSS JOIN grids
GROUP BY bar_seconds
ORDER BY bar_seconds ASC在 1 second 的时间网格下,该时间窗口内有 8.966% 的成交记录会因两种时间戳不同而落入不同K线,共计 8944 笔。将周期延长至 5 minutes 后,该比例降至 0.024%。其规律是机械性的:只要某笔成交记录的两个时间戳跨过区间边界,它就会被分入不同区间;周期越短,边界越多。因此,两个供应商都可能是正确的,却会为同一分钟公布不同的成交量。OHLCV K线的构建方法详细说明了具体构建过程。
纳秒字段不等于纳秒级准确度
两个时间戳都是以整数表示,并具有纳秒分辨率。分辨率表示字段能够表达的精细程度。准确度表示该数值与真实时间的接近程度。两者由完全不同的因素决定。
行业内的时钟同步由监管容差而非物理极限决定。FINRA的时钟同步规则要求会员机构的业务时钟与 NIST 参考时间的偏差不超过50毫秒。交易所和处理器则使用精度更高的精确时间协议(PTP,标准化为 IEEE 1588),通过承载数据的同一网络分发参考时钟,使机器保持亚微秒级同步。
由此产生两点结论。在同一组织内部的时间戳之间,微秒级排序具有实际意义。在不同组织之间,两个时间戳相差数百纳秒时,该差值仍处于误差范围内;将其视为真实的先后顺序,实际上是在解读噪声。
常见问题
SIP时间戳与参与者时间戳有什么区别?
参与者时间戳由交易场所在自己的数据源上发布交易时写入。SIP时间戳由合并成交数据流处理器在交易到达官方合并数据源时写入。两者之间的差值是传输和排队时间。场内成交记录通常以微秒计,经过交易报告机构报告的成交记录则往往以毫秒甚至更长时间计。
回测应使用哪个市场数据时间戳?
如果模型要反映市场参与者在某个交易场所可能看到或执行的情况,应使用参与者时间戳。如果模型必须与官方合并记录核对,应使用 SIP 时间戳。无论选择哪一种,都应将其应用于研究中的每张表,包括报价表。
为什么我的一分钟K线与数据供应商的不一致?
通常是时间戳不一致。一笔交易的交易场所时间戳可能刚好早于分钟边界,但成交数据流时间戳却刚好晚于该边界。因此,在两种规则下,同一笔交易会被分入不同K线。延迟报告的场外成交记录会进一步扩大差异。
纳秒时间戳是否精确到纳秒?
不是。字段具有纳秒分辨率,但准确度取决于写入时间戳的机器与参考时钟的同步程度。使用 PTP 的交易所和处理器系统可保持亚微秒级同步,而经纪商的业务时钟须符合50毫秒的监管容差。对于同步容差更大的时钟,任何精细到超出其容差的比较都没有实际意义。
上面每个面板都附带生成它的 SQL,因此您可以准确查看每个数值使用的是哪一种时间戳。若要在自己的成交记录时间窗口中切换排序时间戳,并观察成交数据流如何变化,请在 Strasmore terminal 中用自然语言提问。