🎯 学习目标

  • 掌握Pandas时间序列数据的核心操作
  • 理解时间索引的创建与使用
  • 能够进行时间序列的切片、重采样和滑动窗口计算
  • 学会在量化分析中处理复杂时间序列问题
时间序列数据

时间序列数据处理

时间序列是金融数据的核心特征。Pandas提供了强大的时间序列处理能力, 包括时间索引创建、频率转换、重采样、滚动窗口等功能。本节将详细介绍时间序列数据处理的各种技巧。

📚 时间索引创建

创建时间索引

import pandas as pd
import numpy as np

# 从字符串创建时间索引
dates = pd.to_datetime(['2024-01-01', '2024-01-02', '2024-01-03'])
print(dates)

# 使用date_range创建时间范围
# 日频率
dates = pd.date_range('2024-01-01', '2024-01-31', freq='D')
# 月频率
dates = pd.date_range('2024-01-01', periods=12, freq='M')
# 小时频率
dates = pd.date_range('2024-01-01', periods=24, freq='H')

# 创建时间序列DataFrame
df = pd.DataFrame({
    'price': np.random.randn(30).cumsum() + 100
}, index=pd.date_range('2024-01-01', periods=30, freq='D'))

# 设置列为索引
df = df.set_index(pd.to_datetime(df['date']))

时间频率

# 常见频率
freq_options = {
    'D': '日历日',
    'B': '工作日',
    'H': '小时',
    'T': '分钟',
    'S': '秒',
    'L': '毫秒',
    'M': '月末',
    'MS': '月初',
    'Q': '季度末',
    'QS': '季度初',
    'Y': '年末',
    'YS': '年初',
    'W': '周',
}

# 自定义频率
# 每3天
pd.date_range('2024-01-01', periods=10, freq='3D')
# 每2小时
pd.date_range('2024-01-01', periods=12, freq='2H')
# 每月第一个周五
pd.date_range('2024-01-01', periods=12, freq='WOM-1FRI')

🔍 时间序列索引与切片

基本索引

# 创建时间序列
index = pd.date_range('2024-01-01', periods=100, freq='D')
df = pd.DataFrame({
    'price': np.random.randn(100).cumsum() + 100
}, index=index)

# 位置索引
df.iloc[0]       # 第一行
df.iloc[-1]      # 最后一行

# 时间索引
df.loc['2024-01-15']           # 特定日期
df.loc['2024-01']             # 整月
df.loc['2024-01-15':'2024-01-20']  # 日期范围

# 部分字符串索引
df['2024-01']        # 2024年1月
df['2024']           # 2024年
df['2024-01-15']     # 2024年1月15日

高级切片

# 使用between_time选择特定时间段
df = df.set_index(pd.date_range('2024-01-01', periods=1000, freq='T'))  # 分钟数据
df.between_time('09:30', '11:30')  # 9:30-11:30

# 使用at_time选择特定时间
df.at_time('09:30')  # 每天9:30的数据

# 使用asfreq设置频率
df.asfreq('D')       # 每日数据
df.asfreq('B')       # 工作日数据
df.asfreq('H')       # 每小时数据

⚙️ 重采样与频率转换

降采样(从高频率到低频率)

# 创建分钟数据
index = pd.date_range('2024-01-01 09:30', periods=240, freq='T')
df = pd.DataFrame({
    'price': np.random.randn(240).cumsum() + 100,
    'volume': np.random.randint(100, 1000, 240)
}, index=index)

# 重采样为5分钟
df_5min = df.resample('5T').agg({
    'price': ['first', 'max', 'min', 'last'],
    'volume': 'sum'
})

# 重采样为日数据
df_daily = df.resample('D').agg({
    'price': ['first', 'max', 'min', 'last'],
    'volume': 'sum'
})

# 重采样为周数据
df_weekly = df.resample('W').agg({
    'price': ['first', 'max', 'min', 'last'],
    'volume': 'sum'
})

升采样(从低频率到高频率)

# 将日数据升采样为小时数据
df_hourly = df.resample('H').asfreq()  # 使用asfreq填充NaN
df_hourly = df.resample('H').ffill()   # 前向填充
df_hourly = df.resample('H').interpolate()  # 插值填充

# 填充方法
df_hourly = df.resample('H').fillna(method='ffill')  # 前向填充
df_hourly = df.resample('H').fillna(method='bfill')  # 后向填充
df_hourly = df.resample('H').fillna(0)  # 填充0

常用聚合方法

# Ohlcv重采样
def ohlcv_resample(df, freq):
    return df.resample(freq).agg({
        'open': 'first',
        'high': 'max',
        'low': 'min',
        'close': 'last',
        'volume': 'sum'
    })

df_5min = ohlcv_resample(df, '5T')
df_1h = ohlcv_resample(df, 'H')
df_1d = ohlcv_resample(df, 'D')

🔧 滚动窗口与扩展窗口

滚动窗口

# 创建价格数据
df['MA5'] = df['close'].rolling(5).mean()
df['MA20'] = df['close'].rolling(20).mean()

# 滚动标准差
df['std20'] = df['close'].rolling(20).std()

# 滚动最大值和最小值
df['max20'] = df['close'].rolling(20).max()
df['min20'] = df['close'].rolling(20).min()

# 滚动相关系数
df['corr'] = df['close'].rolling(30).corr(df['volume'])

# 滚动回归系数
def rolling_slope(y, window):
    x = np.arange(len(y))
    slope = [np.polyfit(x[i:i+window], y[i:i+window], 1)[0]
             for i in range(len(y)-window+1)]
    return [np.nan]*(window-1) + slope

df['slope'] = rolling_slope(df['close'], 20)

扩展窗口

# 扩展窗口计算
df['expanding_mean'] = df['close'].expanding().mean()
df['expanding_std'] = df['close'].expanding().std()
df['expanding_max'] = df['close'].expanding().max()

# 扩展窗口应用自定义函数
def custom_expanding_apply(series, func):
    result = []
    for i in range(1, len(series)+1):
        result.append(func(series[:i]))
    return result

df['expanding_custom'] = custom_expanding_apply(df['close'], lambda x: x.std())

指数加权移动

# 指数加权移动平均
df['EMA12'] = df['close'].ewm(span=12).mean()
df['EMA26'] = df['close'].ewm(span=26).mean()

# 指数加权标准差
df['ewm_std'] = df['close'].ewm(span=20).std()

# 自定义衰减
df['ewm_alpha'] = df['close'].ewm(alpha=0.1).mean()

📊 时间序列分析示例

计算收益率和波动率

# 日收益率
df['daily_return'] = df['close'].pct_change()

# 累计收益率
df['cumulative_return'] = (1 + df['daily_return']).cumprod()

# 对数收益率
df['log_return'] = np.log(df['close'] / df['close'].shift(1))

# 滚动波动率
df['volatility_20d'] = df['daily_return'].rolling(20).std()

# 年化波动率
df['annualized_vol'] = df['volatility_20d'] * np.sqrt(252)

交易信号生成

# 计算移动平均交叉
df['MA_short'] = df['close'].rolling(5).mean()
df['MA_long'] = df['close'].rolling(20).mean()

# 生成信号
df['signal'] = 0
df.loc[df['MA_short'] > df['MA_long'], 'signal'] = 1  # 买入
df.loc[df['MA_short'] < df['MA_long'], 'signal'] = -1  # 卖出

# 计算仓位变化
df['position'] = df['signal'].diff()
时间序列处理技巧

1. 使用datetime64[ns]类型提高性能
2. 合理使用索引加快查询速度
3. 重采样时注意聚合函数的选择
4. 滚动窗口计算考虑边界效应
5. 处理交易日时使用freq='B'

📝 本节小结

  • • 掌握了时间索引的创建和使用
  • • 学会了时间序列的切片和查询
  • • 熟练使用重采样和频率转换
  • • 能够进行滚动窗口和扩展窗口计算