4. Python高频数据处理:Pandas与NumPy在毫秒级数据上的高效操作
做高频因子提取,说白了就是跟时间赛跑。
我刚开始接触毫秒级数据时,第一反应是「这玩意儿用Python能搞定?」。毕竟大家印象里Python慢,C++快。但后来我发现,只要把Pandas和NumPy用对了,处理百万级tick数据完全不是问题。关键在于——你得知道哪些操作是「快」的,哪些是「坑」。
4.1 数据加载:别让IO成为瓶颈
先说说数据怎么读进来。我个人习惯用pd.read_parquet(),而不是CSV。为什么?
- Parquet是列式存储,读部分列时速度极快
- 自带压缩,磁盘占用小
- 支持按行组读取,适合分块处理
我在项目中遇到过这样的情况:一个月的tick数据,CSV要读40秒,Parquet只要3秒。差距就是这么明显。
import pandas as pd
import numpy as np
# 推荐做法:只读需要的列
df = pd.read_parquet(
'tick_data.parquet',
columns=['time', 'price', 'volume', 'bid', 'ask']
)
# 设置时间索引
df['time'] = pd.to_datetime(df['time'])
df.set_index('time', inplace=True)
4.2 向量化操作:告别for循环
这是最容易被忽视的一点。很多人写量化代码,习惯用for i in range(len(df))。嗯,这在低频数据上还能忍,但在毫秒级数据上——你想想看,100万行数据,每行循环一次,光循环开销就够你喝一壶的。
NumPy和Pandas的核心优势就是向量化。说白了,就是用C语言级别的循环代替Python级别的循环。
df['col'].values就别用df['col']。直接操作NumPy数组比操作Pandas Series快3-5倍。
# ❌ 慢:Python循环
mid_prices = []
for i in range(len(df)):
mid_prices.append((df['bid'].iloc[i] + df['ask'].iloc[i]) / 2)
# ✅ 快:向量化操作
mid_prices = (df['bid'].values + df['ask'].values) / 2
4.3 分组聚合:用groupby的诀窍
高频数据里,我们经常要按时间窗口做聚合。比如计算每秒的成交量加权均价(VWAP)。
我建议用pd.Grouper配合groupby,而不是手动切分时间窗口。为什么?因为Grouper底层做了优化,比你自己写循环快得多。
# 按秒计算VWAP
df['vwap'] = (df['price'] * df['volume']).groupby(
pd.Grouper(freq='1s')
).transform('sum') / df['volume'].groupby(
pd.Grouper(freq='1s')
).transform('sum')
这里有个坑:transform比agg慢,但能保留原始行数。如果你不需要保留原始行数,用agg更快。
resample做高频聚合时,如果数据有缺失时间戳,记得用fill_method或者先asfreq补全。否则结果会莫名其妙地少几行。
4.4 滚动窗口:别用rolling做高频
嗯,这里我要说点不一样的。df['price'].rolling(100).mean()看起来很优雅,但在毫秒级数据上,它其实很慢。为什么?因为rolling每次都要重新计算窗口内的值。
我个人的做法是:用NumPy的np.convolve或者np.cumsum来实现滑动窗口。
# 用cumsum实现快速滚动均值
def fast_rolling_mean(arr, window):
cumsum = np.cumsum(np.insert(arr, 0, 0))
return (cumsum[window:] - cumsum[:-window]) / window
# 比rolling快5倍以上
prices = df['price'].values
df['ma_100'] = np.concatenate([
np.full(window-1, np.nan),
fast_rolling_mean(prices, 100)
])
4.5 内存优化:别让数据撑爆内存
高频数据最头疼的问题就是内存。一天的数据可能就几个G,一个月呢?一年呢?
我建议做两件事:
- 降精度: 价格用
float32代替float64,成交量用int32代替int64 - 分块处理: 用
pd.read_parquet(..., filters=...)按日期过滤,一次只处理一天的数据
# 内存优化示例
df = df.astype({
'price': 'float32',
'volume': 'int32',
'bid': 'float32',
'ask': 'float32'
})
# 内存占用直接减半
print(f"内存占用: {df.memory_usage(deep=True).sum() / 1024**2:.2f} MB")
4.6 核心知识体系
下面这张图是我自己总结的高频数据处理流程,你可以看看:
4.7 实战:提取tick级因子
最后,我们把这些技巧串起来,写一个完整的因子提取函数。
def extract_tick_factors(df: pd.DataFrame) -> pd.DataFrame:
"""
从tick数据中提取高频因子
"""
# 1. 提取NumPy数组(加速)
prices = df['price'].values.astype('float32')
volumes = df['volume'].values.astype('int32')
bids = df['bid'].values.astype('float32')
asks = df['ask'].values.astype('float32')
# 2. 计算中间价
mid_prices = (bids + asks) / 2
# 3. 计算价格变化(向量化)
price_changes = np.diff(prices, prepend=prices[0])
# 4. 计算成交量加权价格(用cumsum加速)
cum_price_vol = np.cumsum(prices * volumes)
cum_vol = np.cumsum(volumes)
vwap = cum_price_vol / cum_vol
# 5. 计算买卖价差
spread = asks - bids
# 6. 组装结果
factors = pd.DataFrame({
'mid_price': mid_prices,
'price_change': price_changes,
'vwap': vwap,
'spread': spread
}, index=df.index)
return factors
# 使用示例
factors = extract_tick_factors(df)
print(f"因子提取完成,耗时: {time.time() - start:.3f}秒")
%timeit逐行分析。我经常发现瓶颈在某个不起眼的.apply()调用上——换成向量化后速度提升几十倍。
好了,以上就是高频数据处理的核心技巧。说白了就是三句话:用二进制格式读数据,用向量化代替循环,用NumPy数组代替Pandas Series。做到这三点,你的代码就能在毫秒级数据上跑得飞快。
记住,高频数据处理的本质不是「写更快的Python」,而是「让C语言帮你干活」。你想想看,NumPy和Pandas底层都是C写的,你只要把数据喂给它们,剩下的交给底层就好。