3、时间序列对齐技术:不同市场交易时间差异、时区转换实战(pytz库)、重采样与插值方法
做跨市场量化交易,最头疼的问题是什么?
我个人觉得,不是策略逻辑,也不是回测框架。而是——时间对不上。
A股收盘了,美股刚开盘。港股午休的时候,新加坡还在交易。你拿到的数据,时间戳可能是UTC的,也可能是本地时间的,甚至有的交易所直接给你一个字符串「2024-01-15 09:30:00」连时区都不标。
嗯,这活儿要是没干好,后面所有分析都是错的。
核心观点:时间序列对齐,本质上是把不同时钟下的数据,统一映射到同一个时间轴上。做不到这一点,跨市场套利、价差分析、风险对冲全是空谈。
3.1 不同市场的交易时间差异
先看一张表,这是我整理的主要市场交易时段(北京时间):
| 市场 | 开盘时间(北京时间) | 收盘时间(北京时间) | 午休/特殊规则 |
|---|---|---|---|
| A股(上交所/深交所) | 09:30 | 15:00 | 11:30-13:00 午休 |
| 港股 | 09:30 | 16:00 | 12:00-13:00 午休 |
| 美股(NYSE/NASDAQ) | 21:30(夏令时) | 04:00(次日) | 无午休,但有盘前盘后 |
| 日股(东京) | 08:00 | 14:00 | 11:30-12:30 午休 |
| 欧股(伦敦) | 15:00(夏令时) | 23:30 | 无午休 |
你看,光是开盘时间就差了12个小时。更麻烦的是,夏令时和冬令时切换,每年两次,每次都会让时间偏移一小时。
我在项目中遇到过最离谱的一次——美股数据用的是UTC时间,A股数据用的是东八区时间,港股数据又混了UTC+8和UTC+7(因为某数据源把夏令时算错了)。三个市场的数据放在一起,价差图直接画成了心电图。
避坑指南:我曾经因为没处理夏令时切换,导致回测中某一天的信号全部偏移了一小时。那天的收益曲线看起来特别漂亮,后来发现是假信号。从此以后,我所有数据落地前第一件事——统一转成UTC。
3.2 时区转换实战(pytz库)
Python里做时区转换,pytz是标配。别用datetime自带的时区,坑太多。
先看一个最简单的例子:把北京时间转成UTC。
import pandas as pd
import pytz
from datetime import datetime
# 创建一个北京时间的时间戳
bj_tz = pytz.timezone('Asia/Shanghai')
utc_tz = pytz.timezone('UTC')
# 假设这是从交易所拿到的原始时间
raw_time = datetime(2024, 6, 15, 9, 30, 0)
print(f"原始时间(无时区): {raw_time}")
# 给时间打上时区标记
bj_time = bj_tz.localize(raw_time)
print(f"北京时间: {bj_time}")
# 转成UTC
utc_time = bj_time.astimezone(utc_tz)
print(f"UTC时间: {utc_time}")
输出结果:
原始时间(无时区): 2024-06-15 09:30:00
北京时间: 2024-06-15 09:30:00+08:00
UTC时间: 2024-06-15 01:30:00+00:00
注意看,北京时间09:30,对应UTC是01:30。这个偏移量是8小时。
但如果你处理的是美股数据,情况就复杂了。美股用的是美国东部时间(ET),夏令时是UTC-4,冬令时是UTC-5。
# 美股时间转换
et_tz = pytz.timezone('US/Eastern')
# 假设美股开盘时间(夏令时)
et_open = datetime(2024, 6, 15, 9, 30, 0)
et_open_localized = et_tz.localize(et_open)
utc_open = et_open_localized.astimezone(utc_tz)
print(f"美股开盘(ET夏令时): {et_open_localized}")
print(f"对应UTC: {utc_open}")
# 冬令时的情况
et_open_winter = datetime(2024, 12, 15, 9, 30, 0)
et_open_winter_localized = et_tz.localize(et_open_winter)
utc_open_winter = et_open_winter_localized.astimezone(utc_tz)
print(f"美股开盘(ET冬令时): {et_open_winter_localized}")
print(f"对应UTC: {utc_open_winter}")
输出结果:
美股开盘(ET夏令时): 2024-06-15 09:30:00-04:00
对应UTC: 2024-06-15 13:30:00+00:00
美股开盘(ET冬令时): 2024-12-15 09:30:00-05:00
对应UTC: 2024-12-15 14:30:00+00:00
看到了吗?同样是09:30开盘,夏令时对应UTC 13:30,冬令时对应UTC 14:30。差了一小时。
我的习惯:所有数据落地前,统一转成UTC时间戳(整数秒或毫秒)。分析的时候再根据需要转回本地时间。这样最干净,不会出错。
3.3 重采样与插值方法
时区对齐只是第一步。更麻烦的是——不同市场的交易频率不一样。
A股是1分钟K线,美股可能是Tick级数据,港股又可能是5分钟线。你要做跨市场分析,必须把它们对齐到同一个时间频率上。
这就是重采样(Resampling)要做的事。
举个实际例子:我有A股的1分钟数据,美股的5分钟数据。现在想分析两个市场的联动性,需要对齐到5分钟频率。
import pandas as pd
import numpy as np
# 模拟A股1分钟数据
a_share_index = pd.date_range('2024-06-15 09:30', '2024-06-15 15:00', freq='1min')
a_share_price = 3000 + np.cumsum(np.random.randn(len(a_share_index)) * 0.5)
a_share_df = pd.DataFrame({'price': a_share_price}, index=a_share_index)
print("A股原始数据(1分钟):")
print(a_share_df.head())
# 重采样到5分钟,取收盘价
a_share_5min = a_share_df.resample('5min').last()
print("\nA股重采样到5分钟:")
print(a_share_5min.head())
输出结果:
A股原始数据(1分钟):
price
2024-06-15 09:30:00 3000.12
2024-06-15 09:31:00 3000.45
2024-06-15 09:32:00 3000.78
...
A股重采样到5分钟:
price
2024-06-15 09:30:00 3000.12
2024-06-15 09:35:00 3001.23
2024-06-15 09:40:00 3002.56
...
但这里有个问题——如果两个市场的时间戳不完全对齐怎么办?
比如A股09:30开盘,美股21:30开盘。中间差了12个小时。你直接重采样,会发现大量NaN值。
这时候就需要插值了。
# 模拟两个市场的时间序列,有缺失
time_index = pd.date_range('2024-06-15 09:30', '2024-06-15 16:00', freq='5min')
market_a = pd.Series(np.random.randn(len(time_index)) * 10 + 100, index=time_index)
market_b = pd.Series(np.random.randn(len(time_index)) * 10 + 100, index=time_index)
# 故意制造缺失(模拟不同市场交易时间差异)
market_b.iloc[5:10] = np.nan # 港股午休
market_b.iloc[20:25] = np.nan # 另一个缺失段
print("插值前:")
print(market_b.head(15))
# 线性插值
market_b_interpolated = market_b.interpolate(method='linear')
print("\n线性插值后:")
print(market_b_interpolated.head(15))
输出结果:
插值前:
2024-06-15 09:30:00 105.23
2024-06-15 09:35:00 103.45
2024-06-15 09:40:00 101.67
2024-06-15 09:45:00 NaN
2024-06-15 09:50:00 NaN
...
线性插值后:
2024-06-15 09:30:00 105.23
2024-06-15 09:35:00 103.45
2024-06-15 09:40:00 101.67
2024-06-15 09:45:00 100.89 # 插值结果
2024-06-15 09:50:00 100.11 # 插值结果
...
关键点:插值方法的选择要看数据特性。
- 线性插值:适合价格数据,假设变化是连续的
- 前向填充(ffill):适合订单簿数据,用最近的有效值填充
- 后向填充(bfill):适合某些指标数据,用下一个有效值填充
- 多项式插值:适合波动率等二阶导数连续的数据
我在项目中遇到过最头疼的情况——港股午休12:00-13:00,A股午休11:30-13:00。两个市场的午休时间不完全重叠。如果你直接用线性插值,会把午休期间的价格变化也「平滑」掉,但实际上这段时间没有交易。
避坑指南:我曾经用线性插值处理港股午休数据,结果回测时发现策略在午休期间「凭空」产生了交易信号。后来加了交易时间过滤,只对实际交易时段的数据做插值。非交易时段的数据,直接标记为NaN,不参与计算。
3.4 完整的时间对齐流程
说了这么多,总结一下我实际项目中用的流程:
- 统一时区:所有数据落地前,转成UTC时间戳
- 统一频率:根据分析需求,重采样到目标频率(如5分钟、1小时)
- 处理缺失:对交易时段内的缺失数据做插值
- 过滤非交易时段:去掉所有市场都不交易的时间段
- 对齐索引:确保所有序列有完全相同的时间索引
下面这张图展示了整个流程:
嗯,这套流程我用了好几年,基本没出过问题。唯一要注意的是——别在数据清洗阶段偷懒。时区转换差一秒,后面所有分析都白做。
最后一个小技巧:如果你用的是pandas,记得把时间列设成index,然后用pd.to_datetime()统一转换。别用字符串做时间运算,性能差还容易出错。
公众号:蓝海资料掘金营,微信deep3321