从数据清洗到金融分析:Pandas时间序列实战进阶
1. 数据清洗从杂乱文本到规整时间序列处理金融数据的第一步往往是清洗原始数据。我见过太多从交易所或第三方平台导出的CSV文件里面混杂着缺失值、错误格式和冗余信息。就拿股票数据来说交易所代码可能带着奇怪的尾缀日期字段可能有多种格式混用。最近处理过一个港股数据集里面腾讯控股的股票代码写着00700.HK而日期列同时存在2023-01-01和01/01/2023两种格式。这时候Pandas的向量化字符串操作就是救星import pandas as pd # 读取包含杂乱数据的CSV df pd.read_csv(stock_data.csv) # 清洗股票代码去掉.HK后缀 df[stock_code] df[stock_code].str.replace(.HK, ) # 统一日期格式将混合格式转为datetime df[date] pd.to_datetime(df[date], formatmixed)更复杂的情况是处理金融新闻文本数据。比如要分析上市公司公告对股价的影响需要从PDF转换的文本中提取关键信息。我常用的组合拳是# 示例清洗金融新闻文本 news_df pd.read_csv(financial_news.csv) # 去除HTML标签 news_df[content] news_df[content].str.replace(r[^]*, ) # 提取公司简称假设在标题中 news_df[company] news_df[title].str.extract(r【(.*?)】) # 处理缺失值 news_df[company].fillna(未知, inplaceTrue)清洗完文本数据后常见的坑是忘记检查编码问题。特别是处理A股数据时中文乱码可能让你前功尽弃。我的经验是先用chardet检测编码import chardet with open(a_stock_data.csv, rb) as f: result chardet.detect(f.read()) df pd.read_csv(a_stock_data.csv, encodingresult[encoding])2. 时间索引金融分析的基石干净的日期时间数据是金融分析的基石。在实战中我发现很多新手会忽略时区处理这个坑。比如同时分析美股和A股数据时如果不统一时区计算相关性就会出错。创建时间索引时我推荐优先使用pd.to_datetime()的infer_datetime_format参数它能智能识别大多数常见格式# 智能识别日期格式 df[datetime] pd.to_datetime(df[datetime], infer_datetime_formatTrue) # 设置为索引 df.set_index(datetime, inplaceTrue)处理高频交易数据时经常遇到时间戳不连续的问题。比如1分钟K线数据可能在非交易时段出现空缺。这时可以用asfreq()补全时间轴# 补全9:30-11:30,13:00-15:00的交易时间 trading_hours pd.date_range(2023-01-01 09:30, 2023-01-01 15:00, freq1min) df df.reindex(trading_hours)对于跨时区的分析一定要统一时区。我吃过亏后才养成这个习惯# 假设原始数据是UTC时间 df.index df.index.tz_localize(UTC) # 转换为上海时区 df.index df.index.tz_convert(Asia/Shanghai)处理财报日期时季度末日期可能落在非交易日。这时需要找到最近的交易日# 找到季度末最近的工作日 quarter_end pd.date_range(start2023-01-01, end2023-12-31, freqQ) nearest_bday quarter_end.to_period(D).to_timestamp().to_period(B).to_timestamp()3. 金融时间序列的进阶操作重采样(resample)是金融分析中最强大的武器之一。去年分析茅台股价时我发现周线数据比日线更能反映长期趋势# 计算周收益率 weekly_return df[close].resample(W-FRI).last().pct_change() # 月波动率 monthly_vol df[close].resample(M).std()但要注意resample和asfreq的区别resample是聚合操作asfreq是选择操作。比如获取每月最后一个交易日收盘价# 正确做法 - 使用asfreq month_end_price df[close].asfreq(BM) # BMBusiness Month End # 错误做法 - 使用resample会计算均值 wrong_month_end df[close].resample(BM).mean()滚动窗口计算(rolling)在技术分析中必不可少。但新手常犯的错误是忽略窗口大小的选择# 计算20日、60日、120日均线 windows [20, 60, 120] for w in windows: df[fma_{w}] df[close].rolling(windoww, min_periodsint(w*0.8)).mean()处理财报季节效应时需要同比和环比计算。我常用的模式是# 同比计算今年Q1 vs 去年Q1 year_on_year df[revenue].resample(Q).last().pct_change(4) # 环比计算本季 vs 上季 quarter_on_quarter df[revenue].resample(Q).last().pct_change(1)4. 实战构建量化交易因子结合前面所有技巧我们来构建一个简单的动量因子。这个因子在过去6个月的回测中表现不错# 计算过去20日收益率 df[return_20d] df[close].pct_change(20) # 计算过去60日波动率 df[vol_60d] df[close].pct_change().rolling(60).std() # 动量因子收益率/波动率 df[momentum] df[return_20d] / df[vol_60d] # 去除极端值 df[momentum] df[momentum].clip(lower-3, upper3)处理多只股票时需要groupby操作。比如计算行业动量# 假设df中有stock_code和industry列 industry_momentum df.groupby([industry, pd.Grouper(freqM)])[momentum].mean()对于事件驱动策略需要准确对齐事件日期和股价数据。比如财报公布后的异常收益# 假设earnings_dates是财报公布日期列表 for date in earnings_dates: # 获取财报后3天窗口期 window df.loc[date:datepd.Timedelta(days3)] # 计算累计异常收益 car (window[return] - window[market_return]).sum()最后提醒一个性能优化技巧处理大规模高频数据时先resample到较低频率# 原始数据是1分钟线先降采样到5分钟线 df_5min df.resample(5T).agg({ open: first, high: max, low: min, close: last, volume: sum })5. 避免常见陷阱在时间序列分析中我踩过不少坑。最大的教训是未来数据泄露(look-ahead bias)。比如计算移动平均时一定要设置min_periods# 错误做法会使用未来数据 wrong_ma df[close].rolling(20).mean() # 正确做法确保只使用历史数据 correct_ma df[close].shift(1).rolling(20, min_periods10).mean()处理财务数据时公告日期和生效日期可能不同。比如分红公告日与实际除权日# 假设df有announce_date和ex_date两列 df[pre_ex_period] (df[ex_date] - df[announce_date]).dt.days时区转换也是个暗坑。特别是夏令时切换时可能产生不存在的时间或重复时间# 处理夏令时转换 df df.tz_localize(America/New_York, ambiguousinfer, nonexistentshift)最后保存处理好的数据时建议同时存储元数据import json meta { created: pd.Timestamp.now().isoformat(), timezone: str(df.index.tz), columns: list(df.columns) } df.to_parquet(processed_data.parquet) with open(meta.json, w) as f: json.dump(meta, f)