百倍加速!Python量化策略的算法性能提升指南_技术性策略python(1)

现在能在网上找到很多很多的学习资源,有免费的也有收费的,当我拿到1套比较全的学习资源之前,我并没着急去看第1节,我而是去审视这套资源是否值得学习,有时候也会去问一些学长的意见,如果可以之后,我会对这套学习资源做1个学习计划,我的学习计划主要包括规划图和学习进度表。

分享给大家这份我薅到的免费视频资料,质量还不错,大家可以跟着学习

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

print u’单次耗时:%s秒’ %time_per_test
print u’单个数据点耗时:%s微秒’ %(time_per_point*1000000)
print u’最后10个移动平均值:', result[-10:]


单次耗时指的是遍历完整个测试数据计算移动平均值所需的时间,单个数据点耗时指的是遍历过程中每个数据点的平均计算耗时,最后10个移动平均值用于和后续的算法进行比对,保证计算结果的正确性。  
 **ma\_basic测试结果**


* 单次耗时:1.15699999332秒
* 单个数据点耗时:11.6281406364微秒


大约10万个数据点(说大约因为有500个用于初始化了),这个测试结果不能说很好但也还过得去。考虑到一个简单的双均线CTA策略(Double SMA Strategy),每个数据点来了后会进行两次均线计算,通常均线窗口不会超过500,且比较两根均线交叉情况的算法开销更低,估计策略单纯在信号计算方面的耗时会在30微秒以内,对于一个通常跑在1分钟线甚至更高时间周期上的策略而言已经是绰绰有余。


有了起点,下面来试着一步步提升性能。


### 试试NumPy?


用Python做数值运算性能不够的时候,很多人的第一反应就是上NumPy:之前的ma\_basic里,比较慢的地方应该在每一个新的数据点加入到data\_window中后遍历求平均值的代码,那么改用numpy.array数组来求和应该性能就会有所提升了吧?



改用numpy(首先是一种常见的错误用法)

import numpy as np

def ma_numpy_wrong(data, ma_length):
ma = []
data_window = data[:ma_length]
test_data = data[ma_length:]

for new_tick in test_data:
    data_window.pop(0)
    data_window.append(new_tick)

    # 使用numpy求均线,注意这里本质上每次循环
    # 都在创建一个新的numpy数组对象,开销很大
    data_array = np.array(data_window)
    ma.append(data_array.mean())

return ma

**ma\_numpy\_wrong测试结果**


* 单次耗时:2.11879999638秒
* 单个数据点耗时:21.2944723254微秒  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/72a754fa70e147d59ba436fba19d90cb.png)


WTF?!用NumPy后居然反而速度降低了一半(耗时增加到了快2倍)!


这里的写法是一个非常常见的NumPy错误用法,问题就出在:



> 
> data\_array = np.array(data\_window)
> 
> 
> 


由于NumPy中的对象大多实现得比较复杂(提供了丰富的功能),所以其对象创建和销毁的开销都非常大。上面的这句代码意味着在计算每一个新数据点时,都要创建一个新的array对象,并且仅使用一次后就会销毁,使用array.mean方法求均值带来的性能提升还比不上array对象创建和销毁带来的额外开销。


正确的用法是把np.array作为data\_window时间序列的容器,每计算一个新的数据点时,使用底层数据偏移来实现数据更新:



numpy的正确用法

def ma_numpy_right(data, ma_length):
ma = []

# 用numpy数组来缓存计算窗口内的数据
data_window = np.array(data[:ma_length])

test_data = data[ma_length:]

for new_tick in test_data:
    # 使用numpy数组的底层数据偏移来实现数据更新
    data_window[0:ma_length-1] = data_window[1:ma_length]
    data_window[-1] = new_tick
    ma.append(data_window.mean())

return ma

**ma\_numpy\_right测试结果**


* 单次耗时:0.614300012589秒
* 单个数据点耗时:6.17386947325微秒


速度比ma\_basic提高了大约2倍,看来NumPy也就这么回事了。


### JIT神器:Numba


关心过Python性能的朋友应该都听过PyPy的大名,通过重新设计的Python解释器,PyPy内建的JIT技术号称可以将Python程序的速度提高几十倍(相比于CPython),可惜由于兼容性的问题并不适合于量化策略开发这一领域。


幸运的是,我们还有Anaconda公司推出的Numba。Numba允许用户使用基于LLVM的JIT技术,对程序内想要提高性能的部分(函数)进行局部优化。同时Numba在设计理念上更加务实:可以直接在CPython中使用,和其他常用的Python模块的兼容性良好,并且最爽的是使用方法傻瓜到了极点:



使用numba加速,ma_numba函数和ma_basic完全一样

import numba

@numba.jit
def ma_numba(data, ma_length):
ma = []
data_window = data[:ma_length]
test_data = data[ma_length:]

for new_tick in test_data:
data_window.pop(0)
data_window.append(new_tick)
sum_tick = 0
for tick in data_window:
sum_tick += tick
ma.append(sum_tick/ma_length)

return ma


**ma\_numba测试结果**


* 单次耗时:0.043700003624秒
* 单个数据点耗时:0.439196016321微秒


OMG!就加了一行@numba.jit,性能竟然提高了26倍!这估计是按照代码修改行数算,性价比最高的优化方案了。


### 改写算法


从编程哲学的角度来看,想提高计算机程序的速度,一个最基本的原则就是降低算法复杂度。看到这里估计早就有量化老手ma\_basic不爽了,弄个复杂度O(N)的算法来算平均值,就不能缓存下求和的结果,把复杂度降低到O(1)么?



将均线计算改写为高速算法

def ma_online(data, ma_length):
ma = []
data_window = data[:ma_length]
test_data = data[ma_length:]

# 缓存的窗口内数据求和结果
sum_buffer = 0

for new_tick in test_data:
    old_tick = data_window.pop(0)
    data_window.append(new_tick)

    # 如果缓存结果为空,则先通过遍历求第一次结果
    if not sum_buffer:
        sum_tick = 0
        for tick in data_window:
            sum_tick += tick
        ma.append(sum_tick/ma_length)

        # 将求和结果缓存下来
        sum_buffer = sum_tick
    else:
        # 这里的算法将计算复杂度从O(n)降低到了O(1)
        sum_buffer = sum_buffer - old_tick + new_tick
        ma.append(sum_buffer/ma_length)

return ma

**ma\_online测试结果**


* 单次耗时:0.0348000049591秒
* 单个数据点耗时:0.349748793559微秒


哲学果然才是最强大的力量!!!  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/dc46b354293f4ba69b1ebed5b8d386f7.png)  
 (索罗斯:其实我是个哲学家。)


改写算法后的ma\_online无需JIT就超越了ma\_numba,将性能提高到了33倍(对比ma\_basic),如果再把numba加上会如何?



高速算法和numba结合,ma_online_numba函数和ma_online完全一样

@numba.jit
def ma_online_numba(data, ma_length):
ma = []
data_window = data[:ma_length]
test_data = data[ma_length:]

sum_buffer = 0

for new_tick in test_data:
    old_tick = data_window.pop(0)
    data_window.append(new_tick)

    if not sum_buffer:
        sum_tick = 0
        for tick in data_window:
            sum_tick += tick
        ma.append(sum_tick/ma_length)
        sum_buffer = sum_tick
    else:
        sum_buffer = sum_buffer - old_tick + new_tick
        ma.append(sum_buffer/ma_length)

return ma

**ma\_online\_numba测试结果**


* 单次耗时:0.0290000200272秒
* 单个数据点耗时:0.29145748771微秒


尽管性能进一步提升了到了40倍,不过相比较于ma\_numba对比ma\_basic的提升没有那么明显,果然哲学的力量还是太强大了。


### 终极武器:Cython


到目前为止使用纯Python环境下的优化方法我们已经接近了极限,想要再进一步就得发挥Python胶水语言的特性了:使用其他扩展语言。由于CPython虚拟机的开发语言是C,因此在性能提升方面的扩展语言主要选择就是C/C++,相关的工具包括ctypes、cffi、Swig、Boost.Python等,尽管功能十分强大,不过以上工具都无一例外的需要用户拥有C/C++语言相关的编程能力,对于很多Python用户而言是个比较麻烦的事。


好在Python社区对于偷懒的追求是永无止境的,Cython这一终极武器应运而生。关于Cython的详细介绍可以去官网看,简单来它的主要作用就是允许用户以非常接近Python的语法来实现非常接近C的性能。


先来试试最简单的方法:完全不修改任何代码,只是把函数放到.pyx文件里,调用Cython编译成.pyd扩展模块。



基础的cython加速

def ma_cython(data, ma_length):
ma = []
data_window = data[:ma_length]
test_data = data[ma_length:]

for new_tick in test_data:
    data_window.pop(0)
    data_window.append(new_tick)

    sum_tick = 0
    for tick in data_window:
        sum_tick += tick
    ma.append(sum_tick/ma_length)

return ma

**ma\_cython测试结果**


* 单次耗时:0.600800013542秒
* 单个数据点耗时:6.03819109088微秒


ma\_cython和ma\_basic的代码完全相同,简单使用Cython编译后性能提高了大约1倍,不过这和之前我们已经达成的优化效果比可以说是毫无吸引力。


Cython官方的Quick Start里,第一步是教会用户如何去编译程序,第二步就是如何使用静态声明来大幅提高性能,所以我们的下一步就是:静态声明+高速算法。



cython和高速算法

def ma_cython_online(data, ma_length):
# 静态声明变量
cdef int sum_buffer, sum_tick, old_tick, new_tick

ma = []
data_window = data[:ma_length]
test_data = data[ma_length:]
sum_buffer = 0

for new_tick in test_data:
    old_tick = data_window.pop(0)
    data_window.append(new_tick)

    if not sum_buffer:
        sum_tick = 0
        for tick in data_window:
            sum_tick += tick
        ma.append(sum_tick/ma_length)

        sum_buffer = sum_tick
    else:
        sum_buffer = sum_buffer - old_tick + new_tick
        ma.append(sum_buffer/ma_length)

return ma

**ma\_cython\_online测试结果**


### 最后

Python崛起并且风靡,因为优点多、应用领域广、被大牛们认可。学习 Python 门槛很低,但它的晋级路线很多,通过它你能进入机器学习、数据挖掘、大数据,CS等更加高级的领域。Python可以做网络应用,可以做科学计算,数据分析,可以做网络爬虫,可以做机器学习、自然语言处理、可以写游戏、可以做桌面应用…Python可以做的很多,你需要学好基础,再选择明确的方向。这里给大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!

#### 👉Python所有方向的学习路线👈

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

![](https://img-blog.csdnimg.cn/img_convert/604bae65027d4d67fb62410deb210454.png)

#### 👉Python必备开发工具👈

工欲善其事必先利其器。学习Python常用的开发软件都在这里了,给大家节省了很多时间。

![](https://img-blog.csdnimg.cn/img_convert/fa276175617e0048f79437bd30465479.png)



#### 👉Python全套学习视频👈

我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。

![](https://img-blog.csdnimg.cn/img_convert/16ac689cb023166b2ffa9c677ac40fc0.png)



#### 👉实战案例👈



学python就与学数学一样,是不能只看书不做题的,直接看步骤和答案会让人误以为自己全都掌握了,但是碰到生题的时候还是会一筹莫展。



因此在学习python的过程中一定要记得多动手写代码,教程只需要看一两遍即可。

![](https://img-blog.csdnimg.cn/img_convert/0d8c31c50236a205928a1d8ae8a0b883.png)



#### 👉大厂面试真题👈

我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

![](https://img-blog.csdnimg.cn/img_convert/99461e47e58e503d2bc1dc6f4668534a.png)

**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值