4、重采样与对齐:Tick数据的时间魔法

做量化交易的朋友都知道,Tick数据是最原始的市场脉搏。但说实话,直接用Tick数据做策略,就像用显微镜看地图——细节太多反而看不清全局。今天我们就聊聊怎么把Tick数据重采样成1分钟、5分钟、日线,以及处理那些让人头疼的时间对齐问题。

4.1 为什么需要重采样?

我刚开始做高频策略时,总觉得数据越细越好。结果呢?回测跑得慢不说,还经常被微观噪声干扰。后来我明白了——不同策略需要不同粒度的数据。

  • 高频策略:可能需要Tick级或1分钟数据
  • 日内策略:5分钟、15分钟比较合适
  • 中长线策略:日线甚至周线就够了

说白了,重采样就是给数据做「降噪」处理。把那些无意义的跳动过滤掉,保留真正的趋势信息。

4.2 Tick数据重采样为1分钟K线

先看一个最基础的操作。假设我们有一堆Tick数据,每条记录包含时间戳、价格、成交量。要合成1分钟K线,需要做这几件事:

import pandas as pd
import numpy as np

# 假设df是Tick数据,包含['timestamp', 'price', 'volume']
df['timestamp'] = pd.to_datetime(df['timestamp'])
df.set_index('timestamp', inplace=True)

# 重采样为1分钟
minute_data = df.resample('1T').agg({
    'price': 'ohlc',  # Open, High, Low, Close
    'volume': 'sum'
})

# 处理一下结构
minute_data.columns = ['open', 'high', 'low', 'close', 'volume']
minute_data.dropna(inplace=True)

嗯,这里有个坑要注意。我遇到过几次,Tick数据的时间戳可能不是严格对齐整分钟的。比如14:30:01.500这种。resample默认会按左闭右开的方式分组,14:30:01.500会被归到14:30这一组。但有些交易所的规则不一样,你得先确认清楚。

我曾经踩过的坑:某次回测时发现1分钟K线的开盘价总是对不上行情软件。查了半天,原来是Tick数据的时间戳精度是毫秒级,而resample默认按秒对齐。最后加了参数 label='right' 才解决。

4.3 5分钟和日线重采样

5分钟其实就是在1分钟的基础上改个参数:

# 5分钟重采样
five_min_data = df.resample('5T').agg({
    'price': 'ohlc',
    'volume': 'sum'
})

日线稍微复杂一点。因为期货、股票的交易时间不一样,而且有夜盘。我一般这样处理:

# 日线重采样(注意:只取交易时段)
daily_data = df.between_time('09:30', '15:00').resample('1D').agg({
    'price': 'ohlc',
    'volume': 'sum'
})

# 如果有夜盘,需要单独处理
night_data = df.between_time('21:00', '23:00').resample('1D').agg({
    'price': 'ohlc',
    'volume': 'sum'
})

你想想看,如果不加between_time过滤,非交易时段的数据混进来,日线的开盘价和收盘价就全乱了。我见过有人直接把全天24小时的数据resample成日线,结果开盘价取到了凌晨3点的数据——那能对吗?

4.4 不同频率数据的时间对齐

这是实战中最头疼的问题。比如你要把1分钟数据和5分钟数据对齐,或者把股票数据和期货数据对齐。它们的时间戳可能不完全匹配。

我的做法是:

  1. 统一基准时间:选一个最细的粒度作为基准,比如1分钟
  2. 前向填充:用最近的有效值填充缺失的时间点
  3. 后向填充:某些场景下需要用未来的数据填充(注意未来信息泄露)
# 对齐1分钟和5分钟数据
minute_data = minute_data.resample('1T').ffill()  # 前向填充
five_min_aligned = five_min_data.resample('1T').ffill()

# 合并
aligned_data = pd.concat([minute_data, five_min_aligned], axis=1)
aligned_data.dropna(inplace=True)
我的个人习惯:做回测时,我一般用前向填充。但做实时策略时,我会用后向填充加一个延迟标记,避免未来数据泄露。这个细节很多人不注意,结果回测曲线漂亮得不行,实盘一跑就崩。

4.5 处理非交易时间段

非交易时间段包括:

  • 午休时间:比如11:30-13:00
  • 夜盘间隔:比如23:00-次日9:00
  • 节假日:春节、国庆等
  • 临时停牌:个股突发停牌

我的处理策略很简单:

# 定义交易时段
trading_sessions = [
    ('09:30', '11:30'),
    ('13:00', '15:00')
]

# 过滤非交易时段
def filter_trading_hours(df):
    mask = pd.Series(False, index=df.index)
    for start, end in trading_sessions:
        mask |= df.index.indexer_between_time(start, end)
    return df[mask]

# 节假日处理(需要自定义日历)
import pandas_market_calendars as mcal
cal = mcal.get_calendar('XSHG')  # 上交所
schedule = cal.schedule(start_date='2024-01-01', end_date='2024-12-31')
df = df[df.index.normalize().isin(schedule.index)]

这里我特别想强调一点:不要简单地把非交易时段的数据删掉就完事了。有些策略需要知道隔夜跳空、开盘缺口这些信息。我一般会保留一个标记列,标明当前数据是来自哪个交易时段。

4.6 知识体系总览

下面这张图概括了本章的核心逻辑:

Tick数据重采样与对齐流程 Tick数据流 1分钟重采样 5分钟重采样 日线重采样 时间对齐(前向填充 / 后向填充) 对齐后的多频率数据集

4.7 实战中的避坑指南

最后分享几个我实战中总结的经验:

1. 时间戳精度问题

不同交易所的时间戳精度不一样。上交所是毫秒级,深交所是微秒级,期货交易所可能是纳秒级。重采样前一定要统一精度,否则会出现「看起来对齐了,实际差了几毫秒」的情况。

2. 成交量复权

重采样时成交量直接求和没问题,但要注意除权除息日。我遇到过某股票10送10后,成交量突然暴增,其实是送股导致的,不是真实交易活跃。

3. 夜盘归属问题

夜盘的数据到底算当天的还是第二天的?不同品种规则不同。我一般把夜盘数据单独标记,策略里再决定怎么处理。

一个小技巧:如果你在做多品种策略,建议把所有品种的数据都对齐到同一个时间轴上。比如都用北京时间,都用1分钟粒度。这样后续的特征工程会省很多事。

好了,重采样和对齐这块就聊这么多。记住一个原则:数据预处理做得越细致,策略回测就越可信。别嫌麻烦,这一步省下的时间,后面会加倍还给你。


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