4、时间戳设计:时间戳精度、格式与时区处理

时间戳这东西,看着简单,坑却不少。我见过太多系统因为时间戳没设计好,导致数据对不上、回测结果偏差、甚至实盘交易出问题。今天咱们就把这块彻底聊透。

4.1 时间戳精度:毫秒、微秒、纳秒怎么选?

先说精度。Tick级数据,说白了就是每一笔成交或每一次报价的瞬间快照。精度不够,数据就失真了。

毫秒(ms):大部分交易所的行情数据,比如国内期货、股票,通常就是毫秒级。我个人习惯,如果对接的是CTP这类接口,毫秒就够了。

微秒(μs):高频交易场景,比如股指期货、外盘期货,很多交易所已经提供微秒级时间戳。我记得有一次做美股Level2数据接入,纳斯达克的订单流时间戳就是微秒的。这时候你用毫秒去存,同一毫秒内可能挤进来几十笔订单,顺序都乱了。

纳秒(ns):极高频场景,比如FPGA硬件加速、自营做市商。说实话,软件层面很难达到纳秒级精度,更多是硬件层面的需求。我建议普通量化系统做到微秒就够了,别盲目追求纳秒。

核心原则:精度选择取决于数据源和业务场景。不是越高越好,而是够用就好。
精度 典型场景 存储建议
毫秒 国内股票、期货 int64 存储毫秒数
微秒 外盘、高频策略 int64 存储微秒数
纳秒 FPGA、硬件加速 int64 存储纳秒数

4.2 时间戳格式:Unix时间戳 vs ISO 8601

格式问题,说白了就是「存什么」和「怎么传」。

Unix时间戳:从1970年1月1日0点开始的整数或浮点数。优点是计算快、存储小、跨语言无歧义。我建议内部存储和计算都用Unix时间戳,尤其是微秒或纳秒级的整数。

ISO 8601:比如 2024-03-15T14:30:00.123456Z。可读性好,适合日志、API传输、人眼查看。但解析慢、存储大。我一般在对外接口、日志记录时用ISO 8601,内部处理还是转回Unix时间戳。

我的习惯:数据库里存Unix微秒整数,日志里写ISO 8601字符串。两头兼顾。
// 示例:Unix微秒转ISO 8601
// 输入:1710484200123456 (微秒)
// 输出:2024-03-15T14:30:00.123456Z

func UnixMicroToISO(micro int64) string {
    sec := micro / 1_000_000
    nsec := (micro % 1_000_000) * 1000
    t := time.Unix(sec, nsec).UTC()
    return t.Format("2006-01-02T15:04:05.000000Z")
}

4.3 时区处理与夏令时问题

这是最容易被忽视的坑。我曾经因为时区问题,导致回测数据差了整整一小时,查了两天才找到原因。

时区处理:我建议所有内部存储和计算统一使用UTC时间。为什么?因为UTC没有夏令时,不会出现时间跳变。展示给用户时再转成当地时区。你想想看,如果数据库里混着东八区和UTC,数据对账时得多痛苦。

夏令时问题:美国、欧洲很多市场有夏令时。每年3月和11月,时间会突然跳一小时。如果你用本地时间存储,那3月某一天只有23小时,11月某一天有25小时。回测时数据序列就断了。

避坑指南:我曾经接手过一个系统,数据库里存的是美东时间。结果夏令时切换那天,策略自动交易停了半小时,因为时间戳重复了。从那以后,我所有系统强制使用UTC。

具体做法:

  • 数据入库前,统一转成UTC时间戳
  • 对外API接口,可以同时返回UTC和本地时间
  • 前端展示时,根据用户时区做转换
  • 夏令时切换前后,做好数据校验

4.4 知识体系结构图

下面这张图,把时间戳设计的核心逻辑串起来了。你可以看到,从数据源到存储到展示,每一步都有对应的处理策略。

时间戳设计核心逻辑 数据源 精度选择 毫秒 | 微秒 | 纳秒 格式处理 Unix时间戳(内部) | ISO 8601(对外) 时区处理 统一UTC | 避免夏令时 | 展示时转换 数据流方向

4.5 实战建议总结

嗯,最后总结几条实在的:

  • 存储用Unix微秒整数,计算快、无歧义
  • 日志和API用ISO 8601,方便人看
  • 所有系统统一UTC,别碰本地时间
  • 夏令时切换前后,加个数据校验逻辑
  • 精度选微秒,大部分场景够用了
一个小技巧:如果你对接多个交易所,建议在数据入库时额外存一个「原始时间戳」字段。万一后续发现时区转换有误,还能追溯修复。

时间戳设计,说白了就是「存得准、算得快、看得懂」。做到这三点,你的Tick级数据接口就稳了。


公众号:蓝海数据掘金营,微信deep3321