3. 数据获取与预处理:用Pandas搞定金融数据清洗
做量化仿真,第一步就是跟数据打交道。我见过太多人一上来就写策略,结果数据没处理好,回测结果全是错的。说白了,数据预处理占整个量化工作的60%以上。这一章,咱们就聊聊怎么用Pandas把CSV和Excel数据收拾得服服帖帖。
3.1 读取数据:CSV和Excel的打开方式
我个人习惯用pd.read_csv()和pd.read_excel()。这两个函数看着简单,但有几个参数特别关键。
import pandas as pd
import numpy as np
# 读取CSV文件
df = pd.read_csv('stock_data.csv', parse_dates=['date'], index_col='date')
# 读取Excel文件
df_excel = pd.read_excel('stock_data.xlsx', sheet_name='Sheet1', header=0)
嗯,这里要注意:parse_dates参数一定要用。我刚开始做量化时,经常忘记把日期列转成datetime格式,结果后面画图、计算时间差全乱了。你想想看,日期是字符串的话,排序都会出问题。
chunksize参数分块读取。我在处理A股全市场数据时,经常用这个参数,避免内存爆炸。
3.2 处理缺失值:别让NaN毁了你的策略
真实数据里,缺失值太常见了。停牌、节假日、数据源问题,都会导致NaN。我曾经因为没处理好缺失值,回测时收益率曲线直接断掉,找了半天才发现是某只股票停牌了三天。
处理缺失值,我一般分三步走:
- 先看看缺失情况:
df.isnull().sum()快速扫描 - 决定处理方式:删除还是填充?
- 执行操作
# 查看缺失值
print(df.isnull().sum())
# 方法1:删除缺失行(慎用)
df_clean = df.dropna()
# 方法2:向前填充(金融数据常用)
df_filled = df.fillna(method='ffill')
# 方法3:用均值填充
df_mean = df.fillna(df.mean())
dropna()!我曾经在回测中删除了所有缺失行,结果把连续的时间序列搞断了,收益率计算全错。金融数据建议用前向填充(ffill),因为停牌期间的价格应该沿用上一交易日的数据。
3.3 数据标准化:让不同资产站在同一起跑线
为什么需要标准化?你想想看,茅台股价1800块,工商银行才5块,直接比较没意义。标准化就是把不同量纲的数据拉到同一个尺度上。
常用的标准化方法有两种:
| 方法 | 公式 | 适用场景 |
|---|---|---|
| Z-score标准化 | (x - μ) / σ | 数据近似正态分布时 |
| Min-Max标准化 | (x - min) / (max - min) | 数据有明确边界时 |
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# Z-score标准化
scaler = StandardScaler()
df_zscore = pd.DataFrame(
scaler.fit_transform(df[['close', 'volume']]),
columns=['close_scaled', 'volume_scaled'],
index=df.index
)
# Min-Max标准化
scaler_mm = MinMaxScaler()
df_mm = pd.DataFrame(
scaler_mm.fit_transform(df[['close', 'volume']]),
columns=['close_mm', 'volume_mm'],
index=df.index
)
我个人习惯用Z-score。为什么?因为金融数据经常有极端值,Min-Max会把异常值压缩得太厉害。不过,如果你做的是波动率相关的策略,Min-Max可能更合适。
3.4 对数收益率计算:量化分析的核心指标
收益率计算有两种:简单收益率和对数收益率。做量化仿真,我强烈推荐用对数收益率。
原因有三:
- 可加性:多期对数收益率可以直接相加
- 对称性:涨10%和跌10%在数值上对称
- 正态性:对数收益率更接近正态分布,方便后续建模
# 计算对数收益率
df['log_return'] = np.log(df['close'] / df['close'].shift(1))
# 计算简单收益率(对比用)
df['simple_return'] = df['close'].pct_change()
# 去掉第一行的NaN
df_returns = df.dropna()
嗯,这里有个坑要注意:shift(1)会产生NaN,因为第一行没有前一天的数据。记得用dropna()清理掉。
3.5 完整流程:从原始数据到可用数据
把上面这些串起来,就是一个完整的数据预处理流水线。我一般写成函数,方便复用。
def preprocess_data(filepath, date_col='date', price_col='close'):
"""
数据预处理流水线
"""
# 1. 读取数据
df = pd.read_csv(filepath, parse_dates=[date_col])
df.set_index(date_col, inplace=True)
# 2. 处理缺失值(前向填充)
df.fillna(method='ffill', inplace=True)
# 3. 删除仍然存在的NaN(比如开头几行)
df.dropna(inplace=True)
# 4. 计算对数收益率
df['log_return'] = np.log(df[price_col] / df[price_col].shift(1))
# 5. 标准化(可选)
scaler = StandardScaler()
df['return_scaled'] = scaler.fit_transform(df[['log_return']])
return df.dropna()
# 使用示例
data_clean = preprocess_data('stock_data.csv')
print(data_clean.head())
这个函数我用了好几年,从A股到美股,从股票到加密货币,基本都能直接套用。你想想看,数据预处理的核心逻辑其实是一样的,只是参数不同而已。
3.6 知识体系总览
下面这张图,把本章的核心逻辑串起来了。从原始数据到最终可用的收益率序列,每一步都有讲究。
这张图把整个流程串起来了。你从左边开始,一步步走到右边,就完成了从原始数据到可用数据的转换。我每次做新项目,都会先跑一遍这个流程,确保数据质量没问题。