第1章:Pandas数据处理实战
做量化交易这些年,我越来越觉得Pandas就是交易员的瑞士军刀。你想想看,每天面对成千上万条行情数据,没有趁手的工具怎么行?这一章,我就带你从最基础的东西开始,一步步掌握Pandas的核心操作。
本章核心知识点:Series与DataFrame基础、时间序列索引、数据切片与筛选、滚动窗口计算、缺失值处理。这些是量化回测的五大基本功,缺一不可。
1.1 Series与DataFrame:量化数据的基石
说白了,Series就是一列带标签的数据。比如某只股票连续5天的收盘价,就是一个Series。DataFrame呢?就是多列Series拼在一起,像Excel表格一样。
我记得刚入行那会儿,有个同事用Excel处理3000只股票的日线数据,电脑直接卡死。换成Pandas的DataFrame,几秒钟就搞定了。这就是结构化数据的威力。
import pandas as pd
import numpy as np
# 创建一个Series - 比如某只股票的日收益率
daily_returns = pd.Series([0.02, -0.01, 0.03, 0.005, -0.015],
index=['2024-01-05', '2024-01-08', '2024-01-09', '2024-01-10', '2024-01-11'],
name='日收益率')
print(daily_returns)
# 创建一个DataFrame - 多只股票的行情数据
stock_data = pd.DataFrame({
'股票代码': ['000001', '000002', '000003'],
'收盘价': [15.20, 28.50, 42.10],
'成交量': [1200000, 850000, 2300000],
'涨跌幅': [0.025, -0.018, 0.032]
})
print(stock_data)
我的小技巧:给Series和DataFrame的index设置成有意义的名字,比如股票代码或时间戳。这样后续切片、合并时能省不少事。我吃过亏,一开始随便用数字索引,后来数据一多就乱了。
1.2 时间序列索引:交易数据的灵魂
量化交易里,时间就是一切。没有时间戳的数据,就像没有坐标的地图。Pandas的DatetimeIndex就是专门为时间序列数据设计的。
为什么会这么重要?你想想看,计算移动平均线、判断金叉死叉、回测策略表现,哪一步离得开时间?
# 创建时间序列索引
date_range = pd.date_range(start='2024-01-01', end='2024-01-10', freq='D')
print(date_range)
# 实战:生成交易日数据(跳过周末)
trade_dates = pd.bdate_range(start='2024-01-01', end='2024-01-31')
print(f'1月交易日数量:{len(trade_dates)}')
# 设置时间索引
price_data = pd.DataFrame({
'close': np.random.randn(10) * 2 + 50,
'volume': np.random.randint(1000000, 5000000, 10)
}, index=date_range)
print(price_data)
注意:用pd.bdate_range生成交易日序列时,默认跳过周六日。但A股有时会有调休,我建议你从数据源直接获取实际交易日列表,别完全依赖这个函数。我曾经因为没注意国庆调休,回测结果差了5个百分点。
1.3 数据切片与筛选:精准定位你的目标
做量化分析时,你经常需要从海量数据中提取特定时间段、特定股票的数据。Pandas的loc和iloc就是干这个的。
我个人习惯用loc做标签索引,iloc做位置索引。说白了,loc是按名字找,iloc是按位置找。
# 准备示例数据
import pandas as pd
import numpy as np
dates = pd.date_range('2024-01-01', periods=20, freq='D')
df = pd.DataFrame({
'open': np.random.randn(20) * 2 + 50,
'high': np.random.randn(20) * 3 + 52,
'low': np.random.randn(20) * 3 + 48,
'close': np.random.randn(20) * 2 + 50,
'volume': np.random.randint(1000000, 10000000, 20)
}, index=dates)
# 时间切片 - 选取1月5日到1月10日的数据
slice_1 = df.loc['2024-01-05':'2024-01-10']
print('时间切片结果:')
print(slice_1)
# 布尔筛选 - 找出涨幅超过2%的交易日
df['return'] = df['close'].pct_change()
up_days = df[df['return'] > 0.02]
print(f'\n涨幅超过2%的交易日:{len(up_days)}天')
# 组合筛选 - 高成交量且上涨的日子
big_up_days = df[(df['volume'] > 5000000) & (df['return'] > 0)]
print(f'放量上涨天数:{len(big_up_days)}天')
避坑指南:用loc做时间切片时,结束日期是包含的。但如果你用iloc,结束位置是不包含的。我曾经在写回测框架时搞混了,导致策略信号多算了一天,亏了不少模拟资金才发现的。
1.4 滚动窗口计算:捕捉趋势的核心工具
移动平均线、布林带、RSI……这些技术指标的核心都是滚动窗口计算。Pandas的rolling函数就是为此而生。
嗯,这里要注意:滚动窗口计算会引入NaN值,因为窗口期初数据不够。很多新手在这里栽跟头。
# 计算5日移动平均线
df['ma5'] = df['close'].rolling(window=5).mean()
df['ma10'] = df['close'].rolling(window=10).mean()
# 计算20日滚动标准差(布林带用)
df['std20'] = df['close'].rolling(window=20).std()
# 计算滚动最大值(比如20日高点)
df['high_20'] = df['high'].rolling(window=20).max()
print(df[['close', 'ma5', 'ma10', 'std20', 'high_20']].head(25))
# 扩展窗口计算 - 从第一天开始累计
df['cum_max'] = df['high'].expanding().max()
df['cum_mean'] = df['close'].expanding().mean()
核心要点:rolling(window=5)表示用当前行和前4行共5个数据点计算。min_periods参数可以控制最少需要多少有效数据,默认等于window大小。我一般设min_periods=1,避免前期数据全变NaN。
1.5 缺失值处理:数据清洗的必修课
真实交易数据里,缺失值太常见了。停牌、数据源故障、节假日……各种原因都会导致数据不完整。如果不处理,你的策略回测结果就是错的。
我遇到过最离谱的一次,某只股票因为重大资产重组停牌了半年,复牌后直接跌停。如果没处理好缺失值,回测里会以为这只股票一直平稳运行,那策略表现就完全失真了。
# 制造一些缺失值
df_with_na = df.copy()
df_with_na.loc[df_with_na.index[3:5], 'close'] = np.nan
df_with_na.loc[df_with_na.index[8], 'volume'] = np.nan
df_with_na.loc[df_with_na.index[12:15], 'high'] = np.nan
print('缺失值统计:')
print(df_with_na.isnull().sum())
# 方法1:删除缺失值(简单粗暴)
df_drop = df_with_na.dropna()
print(f'删除后剩余行数:{len(df_drop)}')
# 方法2:向前填充(用上一个有效值填充)
df_ffill = df_with_na.fillna(method='ffill')
print('向前填充后的close列:')
print(df_ffill['close'].iloc[2:7])
# 方法3:线性插值(更平滑)
df_interp = df_with_na.interpolate(method='linear')
print('线性插值后的close列:')
print(df_interp['close'].iloc[2:7])
# 方法4:用均值填充
df_mean = df_with_na.fillna(df_with_na.mean())
| 处理方法 | 适用场景 | 注意事项 |
|---|---|---|
| dropna() | 缺失值很少时 | 会丢失数据,可能破坏时间连续性 |
| ffill/bfill | 停牌、节假日 | 停牌太久会失真,建议设最大填充天数 |
| interpolate() | 价格序列 | 线性插值在剧烈波动时可能不准 |
| fillna(mean) | 成交量等指标 | 会降低方差,影响波动率计算 |
重要提醒:处理缺失值时,一定要搞清楚缺失的原因。如果是停牌,用ffill填充是合理的。如果是数据源故障,最好从其他数据源补全。千万别无脑用一种方法,我见过有人把所有缺失值都填0,结果策略回测收益率翻了一倍——全是假的。
好了,这一章的内容就到这里。Pandas的这些基本功,你在后续的每一章都会用到。别着急,慢慢练,代码敲多了自然就熟了。