3. 金融数据特征工程:时间特征提取与滚动统计
各位同学,咱们今天聊点实在的。金融数据里,时间就是金钱——这句话真不是开玩笑。我做了这么多年量化,见过太多人把原始时间戳直接扔进模型,结果跑出来一堆垃圾。说白了,时间特征工程就是把「几月几号几点几分」这种原始信息,翻译成模型能理解的数学语言。
我个人习惯,拿到金融数据第一件事,先看时间列。不看时间,后面所有分析都是空中楼阁。好,咱们直接进入正题。
3.1 时间特征提取:从日历里挖金子
时间特征提取,说白了就是把一个时间戳拆成多个维度。为什么要拆?因为金融市场的规律往往藏在特定的时间节点里。
核心维度包括:
- 年、月、日:捕捉年度周期、月度效应、月末效应
- 星期几:周一效应、周五效应,这些在A股市场特别明显
- 节假日:节前节后的异常波动,我吃过不少亏
- 交易日序号:月内第几个交易日,这个很多人忽略
避坑指南:我曾经在做一个股指期货策略时,直接用日期作为特征,结果模型学到的全是「每月15号附近涨」这种伪规律。后来拆成星期和月内日,效果才正常。记住:模型不懂日历,它只认数字。
import pandas as pd
import numpy as np
from datetime import datetime
# 假设df已经加载,'date'列是时间戳
df['date'] = pd.to_datetime(df['date'])
# 基础时间特征
df['year'] = df['date'].dt.year
df['month'] = df['date'].dt.month
df['day'] = df['date'].dt.day
df['weekday'] = df['date'].dt.weekday # 0=周一, 6=周日
df['is_weekend'] = df['weekday'].isin([5, 6]).astype(int)
# 节假日特征(以中国A股为例)
# 这里需要外部节假日数据,我一般用tushare或自己维护的日历
holiday_list = ['2024-01-01', '2024-02-10', '2024-02-11', ...] # 实际需要完整列表
df['is_holiday'] = df['date'].isin(holiday_list).astype(int)
# 交易日序号(月内第几个交易日)
df['trading_day_in_month'] = df.groupby(['year', 'month'])['date'].rank(method='first').astype(int)
我的小技巧:对于节假日特征,别只做0/1二值化。我习惯把「节前第N天」「节后第N天」也做出来,因为市场情绪是渐变的,不是突变的。比如春节前5天和春节前1天,市场行为完全不同。
3.2 滞后特征(Lag Features):让数据「记住」过去
金融数据最核心的假设是什么?历史会重演。滞后特征就是把这个假设变成数学公式。
滞后特征,说白了就是把过去N天的数据「搬」到今天来用。比如今天的收盘价,和昨天的收盘价、前天的收盘价,它们之间是有依赖关系的。
# 滞后特征示例
# 假设df已经按时间排序
df = df.sort_values('date')
# 创建滞后1天、2天、5天的收盘价
df['close_lag1'] = df['close'].shift(1)
df['close_lag2'] = df['close'].shift(2)
df['close_lag5'] = df['close'].shift(5)
# 滞后收益率(更常用)
df['return_lag1'] = df['return'].shift(1)
df['return_lag5'] = df['return'].shift(5)
# 注意:shift后会产生NaN,需要处理
df = df.dropna(subset=['close_lag1'])
⚠️ 重要警告:做滞后特征时,千万注意数据泄露!我见过有人用未来数据预测过去,结果回测曲线漂亮得像假的一样——因为它本来就是假的。记住:shift()的方向要正确,永远用过去预测未来。
我个人习惯,滞后阶数不是随便选的。对于日频数据,我一般试1、2、5、10、20、60这几个阶数。为什么?1天是超短期,5天是一周,20天是一个月,60天是一个季度。这些时间窗口在金融里都有实际意义。
3.3 滚动窗口统计:捕捉趋势与波动
滚动窗口统计,说白了就是「过去N天的平均值/标准差」。它能帮我们平滑噪声,抓住趋势。
常用滚动统计量:
- 滚动均值(Rolling Mean):趋势指标,比如20日均线
- 滚动标准差(Rolling Std):波动率指标,衡量风险
- 滚动最大值/最小值:支撑位/压力位
- 滚动偏度/峰度:分布形态,用于异常检测
# 滚动窗口统计示例
# 20日滚动均值和标准差
df['ma20'] = df['close'].rolling(window=20).mean()
df['std20'] = df['close'].rolling(window=20).std()
# 滚动波动率(年化)
df['volatility_20d'] = df['return'].rolling(window=20).std() * np.sqrt(252)
# 滚动最大值/最小值(过去20天)
df['high_20d'] = df['high'].rolling(window=20).max()
df['low_20d'] = df['low'].rolling(window=20).min()
# 滚动偏度(用于检测尾部风险)
df['skew_20d'] = df['return'].rolling(window=20).skew()
经验之谈:滚动窗口大小的选择,取决于你的交易频率。做日频策略,20天和60天是标配;做高频,可能用5分钟或30分钟窗口。我刚开始做量化时,总想用复杂的窗口,后来发现简单窗口往往更稳健。
3.4 知识体系总览
下面这张图,是我自己总结的时间特征工程知识体系。你看一眼,就能明白今天讲的内容是怎么串起来的。
3.5 实战组合:把三个模块串起来
光讲理论没意思,咱们来个实战案例。假设我们要预测某只股票明天的涨跌,特征工程怎么做?
# 完整特征工程流水线
def create_features(df):
"""
输入:原始DataFrame,包含date, open, high, low, close, volume
输出:带有全部特征的DataFrame
"""
df = df.copy()
df['date'] = pd.to_datetime(df['date'])
df = df.sort_values('date')
# 1. 时间特征
df['month'] = df['date'].dt.month
df['weekday'] = df['date'].dt.weekday
df['is_month_end'] = df['date'].dt.is_month_end.astype(int)
# 2. 滞后特征(过去1天、5天、20天的收益率)
df['ret'] = df['close'].pct_change()
df['ret_lag1'] = df['ret'].shift(1)
df['ret_lag5'] = df['ret'].shift(5)
df['ret_lag20'] = df['ret'].shift(20)
# 3. 滚动统计(20日均线、20日波动率)
df['ma20'] = df['close'].rolling(20).mean()
df['vol20'] = df['ret'].rolling(20).std()
# 4. 衍生特征(价格与均线的偏离度)
df['price_to_ma20'] = df['close'] / df['ma20'] - 1
# 清理NaN
df = df.dropna()
return df
# 使用示例
featured_df = create_features(raw_data)
print(featured_df[['date', 'ret_lag1', 'ma20', 'vol20', 'price_to_ma20']].head())
核心要点:特征工程不是越多越好。我见过有人一口气造了200个特征,结果模型过拟合得一塌糊涂。记住奥卡姆剃刀——简单有效的特征,远胜于花哨无用的特征。我个人习惯,先造20-30个核心特征,跑一轮模型,再根据特征重要性筛选。
嗯,今天的内容就到这儿。时间特征、滞后特征、滚动统计,这三板斧用好了,你的金融模型就已经赢在起跑线上了。下次咱们聊更高级的特征交互,到时候你会发现,原来特征还能这么玩。