第1章:Python性能优化——向量化计算、并行处理、Cython加速、内存管理技巧
做量化交易,尤其是高频的基差套利,最怕什么?
怕行情来了,你的代码还在那吭哧吭哧算。我见过太多人,策略逻辑没问题,但一到实盘就卡死。说白了,Python 本身是慢的,但我们可以用工具把它“武装”起来。
今天这一章,我就把压箱底的性能优化手段掏出来。向量化、并行、Cython、内存管理,一个一个说清楚。
1. 向量化计算:别再用 for 循环了
先问个问题:你写策略时,是不是还在用 for 循环遍历 DataFrame?
如果是,赶紧改。Python 的 for 循环慢得离谱。我刚开始做回测时,一个 5 年的分钟数据,用 for 循环跑了 40 分钟。后来换成向量化,30 秒搞定。
核心思想:用 NumPy 或 Pandas 的内置函数,一次性操作整个数组,而不是逐元素操作。
举个例子,计算两个价格序列的价差:
# 慢的方式(别学)
spread = []
for i in range(len(price1)):
spread.append(price1[i] - price2[i])
# 快的方式(向量化)
spread = price1 - price2 # 假设 price1, price2 是 NumPy 数组
为什么快?因为 NumPy 底层是 C 语言实现的,而且利用了 CPU 的 SIMD 指令集。你想想看,一次处理 1000 个元素,和循环 1000 次,哪个快?
避坑指南:我曾经在计算滚动窗口时,用了 rolling().apply() 配合自定义函数,结果慢到怀疑人生。后来换成 rolling().mean() 这种原生方法,速度提升了 100 倍。记住:能用原生函数的,就别自己写。
2. 并行处理:让多核 CPU 干活
向量化解决的是单核内的效率问题。但如果你有 8 核 CPU,只用一个核,是不是太浪费了?
我个人的习惯是,在回测多个品种组合时,用 multiprocessing 把任务分给不同的核。
import multiprocessing as mp
def backtest_one_pair(pair):
# 假设这是你的回测函数
return sharpe_ratio
pairs = [('RB.SHF', 'HC.SHF'), ('I.DCE', 'JM.DCE'), ...]
with mp.Pool(processes=4) as pool:
results = pool.map(backtest_one_pair, pairs)
这里要注意:multiprocessing 会复制内存,如果你的数据太大,反而会慢。我建议只对计算密集型任务用并行,I/O 密集型的用多线程就行。
警告:Windows 下用 multiprocessing 要加 if __name__ == '__main__':,否则会无限递归。我当年被这个坑过,调试了一下午。
3. Cython 加速:把 Python 变成 C
如果向量化和并行还不够,那就上 Cython。
Cython 说白了,就是把 Python 代码编译成 C 扩展。你只需要加一些类型声明,就能获得接近 C 的速度。
举个例子,计算两个数组的协方差:
# cython_cov.pyx
def cython_cov(double[:] x, double[:] y):
cdef int n = x.shape[0]
cdef double sum_x = 0, sum_y = 0, sum_xy = 0
cdef int i
for i in range(n):
sum_x += x[i]
sum_y += y[i]
sum_xy += x[i] * y[i]
return (sum_xy - sum_x * sum_y / n) / (n - 1)
然后编译:
# setup.py
from distutils.core import setup
from Cython.Build import cythonize
setup(ext_modules=cythonize("cython_cov.pyx"))
编译后,直接 import 就能用。速度提升 10-50 倍是常事。
个人经验:我一般只在最内层的循环用 Cython。比如计算期权 Greeks 时,那个循环要跑几百万次,用 Cython 改写后,从 5 秒降到 0.1 秒。但日常的数据处理,向量化就够了,没必要杀鸡用牛刀。
4. 内存管理技巧:别让内存爆了
做量化交易,数据量动不动就是几个 G。如果你不注意内存管理,程序会直接崩掉。
我总结了几条实用技巧:
- 用
dtype压缩数据:默认的float64改成float32,内存减半。精度损失可以忽略。 - 及时释放大对象:用
del删除不再用的 DataFrame,然后调用gc.collect()。 - 用
chunksize分块读取:别一次性把 10G 的 CSV 读进内存。
# 分块读取示例
chunks = pd.read_csv('tick_data.csv', chunksize=100000)
for chunk in chunks:
process(chunk) # 处理完一块,内存自动释放
注意:我曾经在回测时,因为没释放中间变量,内存从 8G 涨到 32G,最后服务器直接 OOM 了。从那以后,我每写一段代码,都会用 memory_profiler 检查内存占用。
知识体系总览
下面这张图,是我自己总结的 Python 性能优化路线图。你可以把它当成一个检查清单:
这张图里,从左到右,优化力度越来越大,但复杂度也越来越高。我的建议是:先用向量化,不够再加并行,还不行再上 Cython。别一上来就搞 Cython,那是最后的手段。
总结一下
性能优化这件事,说白了就是“用空间换时间,用硬件换效率”。
我个人习惯是:写代码时先追求正确性,然后 profiling 找出瓶颈,最后针对性优化。别一开始就想着优化,那叫过度设计。
嗯,这一章就到这里。记住:向量化是基本功,并行是加分项,Cython 是杀手锏,内存管理是保命符。