4. 数据获取与处理:用pandas搞定金融数据

做量化回测,数据就是你的弹药。弹药不行,枪法再好也白搭。

我见过太多人,策略逻辑写得漂漂亮亮,一跑回测全是坑。为什么?数据没处理好。缺失值、时间错位、频率不一致……这些问题不解决,回测结果就是垃圾。

这一章,咱们就聊聊怎么用pandas把金融数据收拾得服服帖帖。

核心要点:数据获取 → 清洗 → 对齐 → 重采样,每一步都决定回测的生死。

4.1 数据获取:从哪里来?

金融数据的来源很多。我个人习惯用本地CSV文件,或者直接从数据库拉。为啥?稳定。你想想看,回测跑几个小时,中间网络断了,数据没拉全,那得多崩溃。

先看一个最简单的例子,从CSV读数据:

import pandas as pd

df = pd.read_csv('stock_data.csv', 
                 parse_dates=['date'],
                 index_col='date')
print(df.head())

嗯,这里要注意。很多新手直接读CSV,日期列还是字符串。你后面做时间序列分析,肯定要转成datetime类型。所以我习惯在read_csv里直接指定parse_dates参数,一步到位。

如果是多只股票的数据,我建议用字典或者分层索引来组织:

# 多只股票数据组织
data = {
    '000001.SZ': pd.read_csv('000001.csv', index_col='date', parse_dates=True),
    '600001.SH': pd.read_csv('600001.csv', index_col='date', parse_dates=True)
}

# 或者用Panel(现在推荐用MultiIndex)
df_multi = pd.concat(data, names=['stock', 'date'])

我在项目中遇到过一个问题:不同数据源的时间戳格式不一样。有的用YYYY-MM-DD,有的用YYYY/MM/DD,还有的带时区。这时候统一用pd.to_datetime()处理一下,省心。

4.2 数据清洗:脏数据是回测的毒药

数据拿到手,第一件事不是分析,是清洗。你想想看,金融数据里什么妖魔鬼怪都有:

  • 停牌期间的数据是空的
  • 除权除息导致价格跳变
  • 交易异常导致的价格尖峰
  • 数据源自己搞错了

我一般按这个顺序清洗:

  1. 检查重复行:同一个时间点出现两条数据,肯定有问题
  2. 检查异常值:价格突然涨了100倍,多半是数据错误
  3. 处理缺失值:这个后面单独讲
  4. 检查时间连续性:交易日之间有没有跳空

代码示例:

# 1. 删除重复行
df = df[~df.index.duplicated(keep='first')]

# 2. 检测异常值(比如价格超过均值3个标准差)
mean = df['close'].mean()
std = df['close'].std()
df = df[(df['close'] > mean - 3*std) & 
        (df['close'] < mean + 3*std)]

# 3. 检查时间连续性
date_range = pd.date_range(start=df.index.min(), 
                           end=df.index.max(), 
                           freq='B')  # 仅交易日
missing_dates = date_range.difference(df.index)
if len(missing_dates) > 0:
    print(f"缺失交易日: {missing_dates[:5]}")

警告:千万不要直接删除异常值就完事了。我曾经在回测中删掉了一个"异常"的涨停数据,结果策略在真实交易中遇到涨停时完全失效。先搞清楚异常的原因,再决定怎么处理。

4.3 缺失值处理:填还是不填?

金融数据里缺失值太常见了。停牌、节假日、数据源延迟……都会导致缺失。

处理缺失值,我总结了三种策略:

策略 适用场景 代码
向前填充 停牌期间,用最后交易价格 df.fillna(method='ffill')
向后填充 数据延迟,用下一个交易价格 df.fillna(method='bfill')
插值法 日内数据,用线性插值 df.interpolate()
删除 缺失比例太高,直接放弃 df.dropna()

我个人最常用的是向前填充。为什么?因为停牌期间,你确实无法交易,用最后的价格是合理的。但要注意:如果停牌时间太长(比如超过10个交易日),向前填充的意义就不大了。

# 实际项目中的处理逻辑
def handle_missing(df, max_ffill_days=5):
    """处理缺失值,最多向前填充5天"""
    # 先向前填充
    df_filled = df.fillna(method='ffill', limit=max_ffill_days)
    
    # 超过限制的,标记为NaN
    mask = df.isna().cumsum() > max_ffill_days
    df_filled[mask] = np.nan
    
    # 最后删除剩余NaN
    return df_filled.dropna()

小技巧:处理缺失值时,我习惯先看看缺失的分布。用df.isna().sum()看看每列缺失了多少,用df.isna().sum(axis=1).value_counts()看看哪些时间点缺失最多。这样心里有数。

4.4 数据对齐:让多只股票步调一致

做多因子模型或者配对交易时,你需要多只股票的数据在同一个时间点上对齐。但问题是,A股不同股票的交易日期不完全一样——有的停牌了,有的刚上市。

pandas的align方法就是干这个的:

# 假设有两只股票的数据
stock_a = data['000001.SZ']['close']
stock_b = data['600001.SH']['close']

# 对齐到共同的交易日
aligned_a, aligned_b = stock_a.align(stock_b, join='inner')

print(f"对齐前: {len(stock_a)} 天, {len(stock_b)} 天")
print(f"对齐后: {len(aligned_a)} 天")

join='inner'取交集,join='outer'取并集。我一般用inner,因为回测时你只能在所有股票都交易的日子进行买卖。用outer的话,会出现大量缺失值,处理起来麻烦。

但这里有个坑:如果你用inner,可能会丢掉很多数据。比如某只股票停牌半年,那这半年的数据全没了。我曾经在回测一个沪深300策略时,因为一只股票停牌太久,导致回测期缩短了三分之一。

我的建议是:先看看停牌股票的数量。如果只是少数几只,可以暂时剔除。如果很多,那就用outer,然后填充缺失值。

4.5 重采样:把数据变成你想要的频率

不同策略需要不同频率的数据。日频策略用日线,高频策略用分钟线。但数据源可能只提供日线,你需要自己转成周线或月线。

pandas的resample方法,说白了就是按时间窗口聚合数据:

# 日线转周线
weekly = df['close'].resample('W').last()  # 取每周最后一个交易日

# 日线转月线
monthly = df['close'].resample('M').last()

# 分钟线转日线
daily = df['close'].resample('D').ohlc()  # 生成OHLC数据

常用的重采样规则:

规则 说明 示例
D 日历日 每天一个数据点
B 交易日 跳过周末和节假日
W 每周 默认周日结束
M 每月 月末最后一天
Q 每季度 季度末

重采样时,聚合函数的选择很重要。比如转周线,用last还是mean?我习惯用last,因为周线策略通常是在周五收盘时交易,用最后的价格更合理。但如果你做的是周均线策略,用mean可能更合适。

# 一个完整的重采样示例
def resample_ohlc(df, freq='W'):
    """将日线数据重采样为指定频率的OHLC数据"""
    resampled = pd.DataFrame({
        'open': df['open'].resample(freq).first(),
        'high': df['high'].resample(freq).max(),
        'low': df['low'].resample(freq).min(),
        'close': df['close'].resample(freq).last(),
        'volume': df['volume'].resample(freq).sum()
    })
    return resampled.dropna()

注意:重采样时,volume(成交量)要用sum,不能用lastmean。这个错误我犯过,回测结果看起来不错,但实际交易时发现成交量对不上,策略完全失效。

4.6 知识体系总览

说了这么多,咱们用一张图把数据获取与处理的流程串起来:

数据获取与处理流程 数据获取 CSV / 数据库 / API 数据清洗 去重 / 异常值检测 缺失值处理 填充 / 插值 / 删除 数据对齐 inner / outer 对齐 重采样 日/周/月频率转换 干净、对齐、频率一致的金融数据 常见问题 • 时间戳格式不统一 • 停牌导致数据缺失 • 除权除息价格跳变 • 不同股票交易日不同 • 数据频率不一致 • 异常值干扰 • 重复数据行

这张图把整个流程串起来了。从数据获取开始,经过清洗、缺失值处理、对齐,最后重采样成你需要的频率。每一步都环环相扣,少一步都不行。

好了,数据获取与处理这块就聊到这儿。记住一句话:数据质量决定回测质量。花80%的时间在数据处理上,剩下20%写策略,这才是量化交易的正确姿势。


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