3. 时间序列基础:时间索引处理、重采样、滚动窗口计算、滞后特征构建

做量化交易的朋友都知道,时间序列数据是我们的命根子。股票价格、期货合约的每分钟成交数据、甚至是一些高频的tick数据,它们都有一个共同点——带时间戳。我刚开始接触量化那会儿,觉得处理时间不就是把字符串转成日期格式嘛,有什么难的?结果呢?数据对齐出错、回测结果跟实盘差一大截,排查了半天才发现是时区没处理好。嗯,从那以后我再也不敢小看时间序列的基础操作了。

今天咱们就聊聊时间序列处理里最核心的四个技能:时间索引处理、重采样、滚动窗口计算、滞后特征构建。说白了,这些就是量化特征工程的“地基”。地基打不牢,后面盖多高的楼都得塌。

核心观点: 时间序列特征工程不是简单的数据变换,而是对市场节奏的数学化表达。你处理时间的方式,决定了你能捕捉到什么样的市场规律。

时间序列基础 时间索引处理 重采样 滚动窗口计算 滞后特征构建 → 特征工程 → 模型训练 → 策略回测

3.1 时间索引处理:别让时区坑了你

时间索引,说白了就是让数据框的每一行都有一个明确的时间标签。Pandas里最常用的就是DatetimeIndex。我个人习惯是一拿到数据,第一件事就是把时间列转成datetime类型,然后设为索引。

import pandas as pd
import numpy as np

# 读取数据后,先处理时间列
df = pd.read_csv('future_1min.csv')
df['datetime'] = pd.to_datetime(df['datetime'])
df.set_index('datetime', inplace=True)

# 检查时间索引是否连续
print(df.index.is_monotonic_increasing)  # 看看有没有乱序
print(df.index.duplicated().sum())       # 看看有没有重复

这里有个坑,我踩过好几次。有些数据源的时间是字符串格式,比如"2024-01-15 09:30:00",但里面可能混着"2024/01/15 09:30:00"这种。直接用pd.to_datetime()有时候会报错,或者解析成NaT。我建议用errors='coerce'参数,先把异常值标记出来,再手动处理。

避坑指南: 我曾经在回测一个股指期货策略时,因为时间索引没有排序,导致滚动窗口计算出的均线全是错的。回测结果看起来年化收益30%,实盘一跑直接亏了。排查了两天才发现,原来是数据里混了几条前一天收盘后的数据,时间戳是乱的。从那以后,我每次做时间序列分析,第一行代码一定是 df.sort_index(inplace=True)

3.2 重采样:把数据“揉”成你想要的节奏

重采样,说白了就是把高频数据降成低频,或者反过来。比如你有1分钟K线,想合成5分钟K线,这就是降采样。你想想看,如果直接拿1分钟数据去训练模型,计算量巨大不说,很多噪声也会被模型学进去。

Pandas的resample()函数就是干这个的。我常用的写法是这样的:

# 将1分钟数据重采样为5分钟
df_5min = df['close'].resample('5T').ohlc()  # 生成OHLC
df_5min['volume'] = df['volume'].resample('5T').sum()

# 有时候我也需要把日线数据“填充”到分钟级别
# 比如用前向填充
df_daily = df['close'].resample('D').last()
df_minute_filled = df_daily.resample('1T').ffill()

重采样的规则有很多,比如'T'代表分钟,'H'代表小时,'D'代表天,'W'代表周。我建议你记住几个常用的就行,用的时候查文档也来得及。

个人经验: 做期货高频策略时,我经常把tick数据重采样成1秒、5秒、30秒三个级别。为什么?因为不同频率的数据能捕捉到不同时间尺度的市场微观结构。比如1秒数据看的是订单簿的瞬时变化,30秒数据看的是短期趋势的延续性。三个频率的特征组合起来,模型效果往往比单一频率好很多。

3.3 滚动窗口计算:捕捉趋势的“移动放大镜”

滚动窗口,就是用一个固定大小的窗口在时间序列上滑动,每滑动一步就计算一次统计量。最常见的例子就是移动平均线。但说实话,很多人只会用rolling().mean(),其实能玩的花样多着呢。

# 计算20期滚动均值和标准差
df['ma_20'] = df['close'].rolling(window=20).mean()
df['std_20'] = df['close'].rolling(window=20).std()

# 滚动相关系数:看看价格和成交量的关系是否稳定
df['corr_vol_price'] = df['close'].rolling(window=60).corr(df['volume'])

# 滚动最大值和最小值(用于布林带)
df['high_20'] = df['high'].rolling(window=20).max()
df['low_20'] = df['low'].rolling(window=20).min()

这里有个细节要注意:滚动窗口的min_periods参数。默认情况下,窗口不满指定长度时返回NaN。但有时候你希望窗口一开始就有值,哪怕只有几条数据。我一般会设置min_periods=int(window*0.8),这样既能保证统计量的可靠性,又不会丢失太多早期数据。

核心技巧: 滚动窗口计算不仅仅是算均值和标准差。你可以自定义函数,比如计算滚动窗口内的动量、滚动夏普比率、滚动最大回撤。这些自定义特征往往比简单的均线更有预测能力。我在做CTA策略时,就经常用滚动窗口计算过去N天的“价格突破强度”——也就是当前价格相对于滚动窗口内价格分布的分位数。

3.4 滞后特征构建:让模型“看到”过去

滞后特征,就是把过去某个时间点的数据“搬”到当前时间点来。为什么需要这个?因为很多模型(比如线性回归、随机森林)默认认为样本之间是独立的,但时间序列数据显然不是。滞后特征就是告诉模型:“嘿,昨天的价格对今天有影响哦。”

Pandas里用shift()函数就能实现:

# 构建滞后1期、2期、3期的收盘价
df['close_lag1'] = df['close'].shift(1)
df['close_lag2'] = df['close'].shift(2)
df['close_lag3'] = df['close'].shift(3)

# 也可以构建滞后收益率
df['return_lag1'] = df['close'].pct_change().shift(1)

# 有时候我需要滞后多个时间点,用循环生成
for lag in range(1, 11):
    df[f'close_lag{lag}'] = df['close'].shift(lag)

嗯,这里要注意一个容易犯的错:滞后特征会引入NaN值,因为第一行没有前一天的数。我建议在构建完滞后特征后,用dropna()把前几行删掉,不然模型训练时会报错或者学到奇怪的东西。

避坑指南: 我曾经在构建滞后特征时,忘了对数据按时间排序。结果shift()出来的数据根本不是真正的“过去”,而是数据框里上一行的值——如果数据是乱序的,那就完全错了。所以再次强调:先排序,再shift

3.5 实战组合:把四个技能串起来

光讲理论没意思,咱们来个实战例子。假设你有一份沪深300股指期货的1分钟数据,你想构建一个预测未来1分钟涨跌的特征集。我会这样做:

# 1. 加载并清洗数据
df = pd.read_csv('IF_1min.csv', parse_dates=['datetime'])
df.set_index('datetime', inplace=True)
df.sort_index(inplace=True)

# 2. 重采样到5分钟,降低噪声
df_5min = df.resample('5T').agg({
    'open': 'first',
    'high': 'max',
    'low': 'min',
    'close': 'last',
    'volume': 'sum'
})

# 3. 构建滚动窗口特征
df_5min['ma_20'] = df_5min['close'].rolling(20).mean()
df_5min['volatility'] = df_5min['close'].rolling(20).std()
df_5min['volume_ma'] = df_5min['volume'].rolling(20).mean()

# 4. 构建滞后特征
for lag in [1, 2, 3, 5, 10]:
    df_5min[f'close_lag{lag}'] = df_5min['close'].shift(lag)
    df_5min[f'volume_lag{lag}'] = df_5min['volume'].shift(lag)

# 5. 构建目标变量:未来1期收益率
df_5min['target'] = df_5min['close'].pct_change().shift(-1)

# 6. 删除NaN
df_5min.dropna(inplace=True)

print(df_5min.head())

你看,这个流程里时间索引、重采样、滚动窗口、滞后特征全用上了。这就是一个非常标准的时间序列特征工程流水线。我个人习惯把这个流程封装成一个函数,每次拿到新数据直接调用,省时省力。

小技巧: 构建滞后特征时,不要只滞后价格本身。试试滞后成交量、滞后波动率、滞后持仓量。有时候这些辅助指标的滞后值比价格滞后值更有预测力。我在一个商品期货策略中,发现滞后3期的持仓量变化对价格方向有很强的指示作用——这其实就是大资金进场的痕迹。

好了,时间序列基础就聊到这儿。这四个技能——时间索引、重采样、滚动窗口、滞后特征——是量化特征工程的“四件套”。你用得越熟练,构建出来的特征就越能捕捉市场的真实规律。记住,好的特征不是靠堆砌,而是靠对时间结构的深刻理解。


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