一条记录写着 09:30:03,能不能用于 09:30:04 的决策?

时间序列对齐与时区、交易日历

约 11 分钟6 道自测更新于 2026-10-27

一句话记住先区分事件时间、发布时间、接收时间和可用时间,再统一时区、交易日历与比较区间;向后匹配不等于没有前视偏差。

01学完你能做到

区分事件时间、发布时间、接收时间和可用时间;正确使用时区本地化与换算;结合交易日历、证券状态和数据版本做时间对齐,识别迟到、陈旧和前视偏差。

02这课解决什么问题

一条记录写着 09:30:03,能不能用于 09:30:04 的决策?两只证券日期相同,收益区间就一定相同吗?答案取决于时间字段的含义、市场会话和数据何时真正可用。

03一条数据的四种时间

时间回答的问题例子
事件时间 event_time市场或业务事件何时发生成交发生在 09:30:03
来源发布时间 published_at来源何时公开信息公告在晚间公开
接收时间 received_at我方系统何时收到网络延迟后 09:30:05 收到
可用时间 available_at完成获取和处理后何时可供使用校验并写入查询服务后

此外还要考虑交易日、报告期、主数据生效区间和修订时间。一个 date 字段不能承担全部语义。

用于模拟公开信息市场时点,重点是信息公开时间;复现某套系统当时的决策,还要考虑接收和处理时间。只有公告日期、没有时刻时,不能假设开盘前已知,应采用明确的保守规则。

04时区:先解释钟面,再做换算

没有时区的“09:30”需要先确认来源钟面,再赋予时区,这叫本地化。已经带 UTC 时区的瞬间转换为上海时间,是换算;瞬间本身不变。

如果来源时间实际是纽约钟面,却直接贴上 UTC 标签,错误的瞬间会被写入系统。IANA 名称如 Asia/Shanghai、America/New_York 用于时区历史规则。纽约夏令时会产生不存在或重复的本地时刻,遇到歧义要按来源协议处理。

交易日是业务日期,不必强行转成 UTC 午夜后再取日期。还要检查时间精度、时间戳来源、时钟偏差、网络延迟和所属市场会话;有夜盘的品种不能从自然日机械推断交易日。

05交易日历、证券状态与收益区间

交易日历描述市场开市、休市、提前收市和交易阶段;停牌是证券级状态。市场开市不代表每只证券都在交易。行政调休的周末上班也不能直接推出交易所开市。

跨市场分析时,同一个自然日期的两张日线不一定在同一信息时点。统一时区后仍要按会话和可用时间匹配。

假设某证券在 09-29、09-30 和 10-12 有价格 5.0、5.1、5.2,10-09 已确认停牌:

  1. 原始稀疏表直接计算,10-12 会和上一条观测 09-30 比,结果约为 1.96%,跨过了中间日期。
  2. 先补齐共同日期、保留空值且不填充,10-12 前一行为空,结果为空。
  3. 把停牌日用前价作为估值标记,复牌日约 1.96%。这是估值口径,不代表停牌日存在成交价。

采集失败不能直接当作停牌;简单删掉空值也可能改变研究样本。

06`merge_asof` 的能力与边界

direction="backward" 会匹配不晚于左表时间的最近记录,但它只保证键的方向。用于历史特征时,匹配键必须代表信息可用时间;多证券要用 by,并设置 tolerance、同刻匹配规则和交易阶段约束。

保留匹配到的 event_time、available_at、版本和时间差,比只返回一个数更容易解释。事件陈旧度和可用时间陈旧度是两个不同问题。

例如指数在 09:30:03 发生、09:30:05 才可用:决策在 09:30:04.8 时,按事件时间向后匹配会找到它,但按可用时间匹配应为空;09:30:05.1 才可能使用它。向后匹配工具本身不能消除前视偏差。

07代码:按可用时间匹配

import pandas as pd

local = lambda xs: pd.to_datetime(xs).tz_localize("Asia/Shanghai")
decisions = pd.DataFrame({
    "instrument_id": ["DEMO_A"] * 3,
    "decision_time": local(["2026-10-26 09:30:04.800",
                             "2026-10-26 09:30:05.100",
                             "2026-10-26 09:30:10.000"]),
})
quotes = pd.DataFrame({
    "instrument_id": ["DEMO_A"],
    "event_time": local(["2026-10-26 09:30:03.000"]),
    "available_time": local(["2026-10-26 09:30:05.000"]),
    "value": [3001.5],
})
point_in_time = pd.merge_asof(
    decisions.sort_values("decision_time"),
    quotes.sort_values("available_time"),
    left_on="decision_time", right_on="available_time",
    by="instrument_id", direction="backward",
    tolerance=pd.Timedelta("2s"), allow_exact_matches=False,
)
print(point_in_time[["decision_time", "value"]])

08财报修订与历史可得性

如果某报告期利润 4 月首次公告为 10,6 月更正为 8,模拟 5 月的信息环境应使用当时公开的首发版本 10,而不是今天看到的最终值 8。

支持历史时点研究的数据,需要保留发布与修订版本,或提供可复现的 as-of 查询规则。只有最新值的接口仍可用于最新信息查询,但不能自动承诺能复原历史时点。

09延伸阅读

10一分钟讲给别人

试着用自己的话讲一遍先问记录上的时间代表什么,再按市场会话和数据可用时间对齐。向后匹配只是工具,迟到、陈旧、停牌和修订都需要额外信息。每个计算结果应能说明用了哪个版本,以及当时是否真的可获得。

想一想

6 道自测

先在心里答一遍,再展开对照。能说清理由,比答对数字更重要。

  1. 01`tz_localize` 和 `tz_convert` 分别解决什么问题?

  2. 02为什么事件时间 09:30:03 的数据可能不能用于 09:30:04.8 的决策?

  3. 03市场开市和证券可交易是同一件事吗?

  4. 04只有公告日期、没有公告时刻时,能否默认开盘前已知?

  5. 054 月公告利润 10、6 月更正为 8,模拟 5 月应使用哪个值?

  6. 06`merge_asof` 使用 `backward` 时还需要检查什么?

已看 0/6 题

练动手练习

任务:为一个行情或财报接口写一份时间语义检查表,逐项注明事件时间、可用时间、时区、精度、交易日、修订方式和历史版本。用一个迟到数据例子验证“按事件时间匹配”和“按可用时间匹配”的结果差异。

完成这一课

完成这一课

读完讲义、看完自测答案,这一课就算学完了。

  • 读完讲义
  • 看完全部自测

本课为投资者教育内容,案例数字均为示意,不构成任何投资建议,也不承诺收益。规则和费率会变,实际操作前请以交易所、监管机构和产品的最新公告为准。