第三节:时间戳标准化——把那些乱七八糟的时间格式收拾得服服帖帖

做量化交易的朋友,十有八九都被时间戳坑过。

我刚开始搞多市场数据清洗那会儿,就吃过这个亏。明明两个交易所的数据看起来都对得上,一算价差,全乱套了。查了半天,原来是时间戳差了8个小时——一个用的UTC,一个用的北京时间。

嗯,今天咱们就来聊聊这个事儿。

3.1 时间戳的三大“门派”

说白了,不同交易所的时间格式,无非就这三种:

  • UTC时间:比如 2025-01-15T08:30:00Z,后面带个Z,表示世界标准时间。很多国际交易所都用这个。
  • 本地时间:比如 2025-01-15 16:30:00,不带时区信息。你得自己猜它是哪个时区的。国内交易所喜欢这么干。
  • 毫秒级时间戳:比如 1736901000000,一串数字。看着简单,但不同系统可能用秒、毫秒、甚至微秒,一不小心就搞错。

你想想看,如果A交易所给的是UTC,B交易所给的是北京时间,C交易所给了一串数字。你要把它们对齐到同一根K线上,不统一处理怎么行?

3.2 我的统一方案:全部转成毫秒级时间戳

我个人习惯,不管数据源是什么格式,最终都转成毫秒级时间戳。为什么?

  • 计算快:整数加减,比字符串快得多
  • 精度可控:毫秒级足够应对大部分行情
  • 存储省:一个Long类型就搞定

下面是我常用的处理逻辑,用Python写的,你们感受一下:

import pandas as pd
from datetime import datetime, timezone, timedelta

def normalize_timestamp(ts, source_format='utc'):
    """
    统一转成毫秒级时间戳
    source_format: 'utc', 'local', 'milli', 'micro'
    """
    if source_format == 'utc':
        # 处理 '2025-01-15T08:30:00Z' 这种格式
        dt = datetime.fromisoformat(ts.replace('Z', '+00:00'))
        return int(dt.timestamp() * 1000)
    
    elif source_format == 'local':
        # 假设是北京时间(UTC+8)
        dt = datetime.strptime(ts, '%Y-%m-%d %H:%M:%S')
        dt = dt.replace(tzinfo=timezone(timedelta(hours=8)))
        return int(dt.timestamp() * 1000)
    
    elif source_format == 'milli':
        # 已经是毫秒级,直接返回
        return int(ts)
    
    elif source_format == 'micro':
        # 微秒转毫秒
        return int(ts) // 1000
    
    else:
        raise ValueError(f'未知的时间格式: {source_format}')
小提示:我建议你把这个函数封装成一个工具类,放在项目的utils目录下。因为后面几乎每个清洗步骤都会用到它。

3.3 避坑指南:我曾经踩过的三个坑

坑一:时区信息丢失

我曾经从某交易所拿到一批历史数据,时间戳看起来是UTC,但其实是本地时间。我直接按UTC处理,结果所有K线都偏移了8小时。后来我养成了一个习惯:拿到数据先画个时间分布图,看看交易时间是不是符合常识。

坑二:毫秒和微秒傻傻分不清

有一次,一个数据源给的时间戳是13位的,我以为是毫秒。结果一算,日期对不上。仔细一看,人家给的是微秒(16位)。嗯,从那以后,我写了个自动检测函数:

def detect_timestamp_unit(ts):
    """自动判断时间戳单位"""
    ts_str = str(int(ts))
    length = len(ts_str)
    
    if length == 10:
        return 'second'
    elif length == 13:
        return 'milli'
    elif length == 16:
        return 'micro'
    else:
        return 'unknown'

坑三:夏令时

这个坑主要出现在欧美交易所。有些交易所用夏令时,有些不用。我建议统一转成UTC,彻底避开这个麻烦。

3.4 实战:多市场时间对齐流程

下面这张图,是我在实际项目中用的时间对齐流程。你看一眼就明白了:

多市场时间戳标准化流程 交易所A (UTC) 交易所B (本地时间) 交易所C (毫秒戳) 统一标准化处理 格式检测 → 时区转换 → 精度统一 → 异常过滤 统一毫秒级时间戳 用于K线合成、价差计算、策略回测

流程其实不复杂,核心就三步:

  1. 检测格式:先判断数据源是什么格式
  2. 统一转换:调用标准化函数转成毫秒戳
  3. 校验对齐:检查转换后的时间是否合理
核心原则:不管数据源多乱,最终只保留一种时间格式。我选毫秒级时间戳,因为它兼顾了精度和计算效率。

3.5 校验环节:别让脏数据溜过去

转换完了就完事儿了?不是的。我每次都会做一轮校验:

def validate_timestamps(series):
    """校验时间戳是否合理"""
    # 1. 检查是否有空值
    if series.isnull().any():
        print(f'发现 {series.isnull().sum()} 个空值')
    
    # 2. 检查是否在合理范围内
    now = int(datetime.now().timestamp() * 1000)
    if series.max() > now + 86400000:  # 允许未来1天
        print('警告:存在未来时间戳')
    
    # 3. 检查是否单调递增
    if not series.is_monotonic_increasing:
        print('警告:时间戳不是单调递增的')
    
    # 4. 检查重复值
    if series.duplicated().any():
        print(f'发现 {series.duplicated().sum()} 个重复时间戳')

这四步走完,基本能过滤掉90%的时间戳问题。剩下的10%,嗯,那就得靠经验了。

特别注意:千万不要相信数据源说的时间格式。我遇到过好几次,文档写的是UTC,实际给的是本地时间。一定要自己验证一遍。

好了,时间戳标准化这块儿,说白了就是:统一格式、统一时区、统一精度。把这三点做到位,后面的数据对齐工作就轻松多了。


公众号:蓝海资料掘金营,微信deep3321