首页 > 编程语言 >仅需增加2行代码,Python量化策略速度提升20+倍!

仅需增加2行代码,Python量化策略速度提升20+倍!

时间:2024-07-23 22:57:47浏览次数:12  
标签:std 20 clip Python periods time 量化 line mean

今天分享一个Python量化策略加速的小技巧,不用修改原有代码,只需在原有代码里新增2行,策略执行速度便可能提高20+倍,正文开始~

现如今,无论是入门量化投资,还是做数据分析、机器学习和深度学习,Python成为了首选编程语言,直观的原因就是容易上手和资源丰富,但Python有个根深蒂固的标签,那就是“开发快,执行慢”,特别是执行for循环和大规模科学计算,速度很是“感人”。

图片

小孩子才做选择,成年人全都要,那有没有可能既有开发效率,也有执行速度呢?

当然可以,现在安排!

那先来假设一个这样的场景,策略在不断接收实时Tick数据,或者是在回测当中模拟实际的数据接收,每进来一个新数据,就重新计算一次布林带(Bollinger Band)。

选择使用布林带作为例子是因为它在股票、期货、外汇和Coin量化中都被经常使用,它由三条线组成,一般这三条线从上至下被称为上轨、中轨、下轨,一般情况下,计算方式如下:

上轨 = MA20 + 2×STD

中轨 = MA20

下轨 = MA20 + 2×STD

其中,MA20是长度为20的均线,STD是与均线同长数据序列的标准差,“20”是默认的常用均线长度,人为可调。

图片

假设有100万个数据点(random模块生成100万个随机数),取10次执行时间的平均值作为耗时结果,来看看不利用任何第三方库实现时的执行耗时。

测试环境如下:

处理器: Intel(R) Core(TM) i7-7700HQ @ 2.80GHz

内存:8G

操作系统:Windows10

import time
import random

# 随机生成100万个数据点
data = [random.randint(1, 100) for i in range(1000000)]
# 循环次数
iter_times = 10         

# 计算布林带,返回上中下轨数据
def boll(data, periods=20):
    up_line = []    # 上轨
    mid_line = []   # 中轨
    down_line = []  # 下轨
    clip = data[:periods]       #缓存接收到的数据,控制与periods等长
    
    # 模拟实盘不断接收到新数据
    for new_tick in data[periods:]:
        # 剔除旧数据点,纳入新数据点,与periods等长
        clip.pop(0)
        clip.append(new_tick)

        # 计算均值
        v_sum = 0
        for tick in clip:
            v_sum += tick
        v_mean = v_sum / periods
        
        # 计算标准差
        v_sum_std = 0
        for tick in clip:
            v_sum_std += (tick - v_mean)**2
        v_std = (v_sum_std / periods)**0.5
        
        up_line.append(v_mean+2*v_std)
        mid_line.append(v_mean)
        down_line.append(v_mean-2*v_std)

    return up_line,mid_line,down_line

# 记录测试开始时点
start_time = time.time()

for i in range(iter_times):
    up_line,mid_line,down_line = boll(data, periods=20)
    
# 记录测试结束时点
end_time = time.time()

comsued_time = (end_time - start_time) / iter_times

print('布林带boll计算平均耗时:%s秒' %comsued_time)

输出结果:

布林带boll计算平均耗时:8.128175449371337秒

这个耗时结果看上去还行,因为这个策略本身任务量也不大,但执行执行速度可不可以更快,耗时可不可以被压缩到1秒内呢?

答案是肯定的,这就要引入量化萌新的Python加速神器——Numba,它是Anaconda公司推出的针对Python的即时(Just-in-time,JIT)编译器,当你调用函数的时候,可以将全部或部分代码转换为“即时”执行的机器码,以本地机器码的速度运行。

简单来说,你不用理会复杂的实现技术,只需要导入这个库,在你的舒适区范围内,就能对函数代码进行优化,将执行速度明显提高。

图片

Numba库只需要“pip install numba”就可以直接安装上了,实现加速只需要在原始代码上加入2行代码,第一句就是导入Numba库:from numba import jit,第二句就是在函数前使用Numba的装饰器:@jit(nopython=True)。

Numba编译有两种模式——object模式和nopython模式。

object模式表示JIT解析器无法理解/加速/优化该代码内容,编译后代码执行速度跟原生Python一样慢,还可能更慢。

nopython模式表示强制不进入object模型,生成更快的机器码,若无法编译成功则会抛出异常。

from numba import jit

@jit(nopython=True)
def boll_fast(data, periods=20):
    up_line = []    # 上轨
    mid_line = []   # 中轨
    down_line = []  # 下轨
    clip = data[:periods]       #缓存接收到的数据,控制与periods等长
    
    # 模拟实盘不断接收到新数据
    for new_tick in data[periods:]:
        # 剔除旧数据点,纳入新数据点,与periods等长
        clip.pop(0)
        clip.append(new_tick)

        # 计算均值
        v_sum = 0
        for tick in clip:
            v_sum += tick
        v_mean = v_sum / periods
        
        # 计算标准差
        v_sum_std = 0
        for tick in clip:
            v_sum_std += (tick - v_mean)**2
        v_std = (v_sum_std / periods)**0.5
        
        up_line.append(v_mean+2*v_std)
        mid_line.append(v_mean)
        down_line.append(v_mean-2*v_std)

    return up_line,mid_line,down_line

输出结果:

布林带boll_fast计算平均耗时:0.3585397005081177秒

8.128175449371337/0.3585397005081177≈22.7,增加2行代码一下子速度提高了20多倍,Numba的“加速神器”的头衔可不是浪得虚名的,以前需要跑一整天的程序,现在可能都不用看完一部国产电影就可以跑完了。

图片

有的小伙伴可能就有疑问了,你在这个程序里面用了两个for循环计算均值和标准差,太麻烦了,为啥不使用Numpy模块中的mean()和std()函数分别计算均值和标准差,而且Numpy是经过科学计算优化的,速度会更快。

那就利用Numpy模块在相同的情况下重新计算一遍。

# 计算布林带,返回上中下轨数据
def boll_numpy(data, periods=20):
    up_line = []    # 上轨
    mid_line = []   # 中轨
    down_line = []  # 下轨
    clip = np.array(data[:periods])   #缓存接收到的数据,控制与periods等长
    
    # 模拟实盘不断接收到新数据
    for new_tick in data[periods:]:
        # 剔除旧数据点,纳入新数据点,与periods等长
        clip[0:periods-1] = clip[1:periods]
        clip[-1] = new_tick
        
        v_mean = np.mean(clip)
        v_std = np.std(clip)
        
        up_line.append(v_mean+2*v_std)
        mid_line.append(v_mean)
        down_line.append(v_mean-2*v_std)

    return up_line,mid_line,down_line

输出结果:

布林带boll_numpy计算平均耗时:42.0557097196579秒

图片

WTF!怎么计算速度还比原来的慢了5倍,这是因为Numpy在复杂对象的开销耗时要比计算优化节省的时间要多,说人话就是,单次处理的数据序列要长(也就是periods数值要大),Numpy的计算优化效果才能展现出来,因为布林带的默认计算均线长度periods是20,算是比较短的,我们把它增加到200试一试。

start_time = time.time()
for i in range(iter_times):
    up_line,mid_line,down_line = boll(data, periods=200)   
end_time = time.time()
comsued_time = (end_time - start_time) / iter_times
print('布林带boll计算平均耗时:%s秒' %comsued_time)

start_time = time.time()
for i in range(iter_times):
    up_line,mid_line,down_line = boll_numpy(data, periods=200)   
end_time = time.time()
comsued_time = (end_time - start_time) / iter_times
print('布林带boll_numpy计算平均耗时:%s秒' %comsued_time)

输出结果:

布林带boll计算平均耗时:68.52809438705444秒

布林带boll_numpy计算平均耗时:43.69762210845947秒

你看是不是这下子使用Numpy库的速度比原来使用for循环的速度快多了,而且随着单次处理数据序列的增加,Numpy的执行耗时提升不明显,所以说,在单次处理数据序列短的情况下,使用Numpy的效果未必有时用for循环的要好。

如果也对使用Numpy计算布林带的程序(periods=20)也进行加速,看看效果如何。

from numba import jit

@jit(nopython=True)
# 计算布林带,返回上中下轨数据
def boll_numpy_fast(data, periods=20):
    up_line = []    # 上轨
    mid_line = []   # 中轨
    down_line = []  # 下轨
    clip = np.array(data[:periods])   #缓存接收到的数据,控制与periods等长
    
    # 模拟实盘不断接收到新数据
    for new_tick in data[periods:]:
        # 剔除旧数据点,纳入新数据点,与periods等长
        clip[0:periods-1] = clip[1:periods]
        clip[-1] = new_tick
        
        v_mean = np.mean(clip)
        v_std = np.std(clip)
        
        up_line.append(v_mean+2*v_std)
        mid_line.append(v_mean)
        down_line.append(v_mean-2*v_std)

    return up_line,mid_line,down_line

输出结果:

布林带boll_numpy_fast计算平均耗时:0.5330439805984497秒

42.0557097196579/0.5330439805984497≈78.8,当periods=20时,Numba为布林带的计算加速了70+倍。

当periods=200时也进行加速测试,输出结果:

布林带boll_numpy_fast计算平均耗时:1.3904209852218627秒

43.69762210845947/1.3904209852218627≈31.4,当periods=200时,Numba为布林带的计算加速了30+倍。这从侧面也说明了,单次处理序列长度越长,Numpy的计算优化效果就越好,剩余的优化“压榨空间”也就不多了。

小结一下,无论是对“原始Python”,还是对第三方库Numpy,Numba都有明显的加速作用,而且对Numpy的加速作用更明显。所以,大家可以在“使用for循环”和“使用Numpy模块做大量科学计算”时,使用Numba模块进行加速。

使用Numba进行加速的方式也非常舒适:

在源码文件头部加入:from numba import jit在需要加速的函数前加入:@jit(nopython=True)

最后补充说明:

Numba并不能对所有的程序优化和加速,常用的优化场景是for循环和Numpy、cmath模块的计算。不能优化和加速的情况,若带有nopython=True参数,会出现异常(Exception),此时可改为使用其他Python解析器(例如PyPy),或者优化算法。

参考资料:

http://numba.pydata.org/numba-doc/latest/user/index.html

https://github.com/ContinuumIO/gtc2018-numba

http://stephanhoyer.com/2015/04/09/numba-vs-cython-how-to-choose/

标签:std,20,clip,Python,periods,time,量化,line,mean
From: https://blog.csdn.net/iamquantman/article/details/140620546

相关文章

  • 2024年pt市S组暑假集训游记
    记录而已,不必在意Day1上午第一天,一中的lzy老师把我们分配到一楼的机房,好,有沙发,有三个大空调,听说原来是一中的监控室。结果,我们去6楼等了好久,还是没有开课,然后我们就被叫去5楼听课了,火大。幸好听完课之后还是去一楼机房耍,开心。老师上课给我们复习了一些基础的东西,这......
  • 20240723(30.2)AH股行情总结:创业板收跌3%,消费股、有色、黑色系齐跌,高股息资产及国债上涨
    半导体产业链全线回调,光刻机、GPU方向领跌,白酒领跌消费股。银行股逆势走强,四大行股价再创新高。黑色系及有色金属齐跌,沪锡跌4%,铁矿石跌超3%。周二,A股低开低走,午后跌幅加剧上证指数收跌1.6%,深成指跌近3%,创业板跌3%,两市成交额超6600亿,下跌股票数量超4600只。半导体产业链大幅走......
  • 题解|2024暑期牛客多校03
    【原文链接】比赛链接:2024牛客暑期多校训练营3A.BridgingtheGap2题目大意nnn个人过河,第i......
  • 昇思25天学习打卡营第20天|K近邻算法实现红酒聚类
    K近邻算法实现红酒聚类实验目的K近邻算法原理介绍分类问题回归问题距离的定义实验环境数据处理数据准备数据读取与处理模型构建--计算距离模型预测实验小结本实验主要介绍使用MindSpore在部分wine数据集上进行KNN实验。实验目的了解KNN的基本概念;了解如何使用Mind......
  • Python基础-Anaconda,Spyder,数据类型
    1、Python与Anaconda在想使用Python之前需先安装Python,以及PythonIDE和Python的库,而用Anaconda就可以一键安装。Anaconda包含了Python,常用的python库以及IDE,还具有强大的环境和python包的管理能力。PythonIDE(IntegratedDevelopmentEnvironment,集成开发环境)是一个为开发......
  • python实现图像特征提取算法2
    python实现广义Hough变换算法、Hough变换算法1.广义Hough变换算法详解算法步骤Python实现详细解释优缺点2.Hough变换算法详解算法步骤Python实现详细解释优缺点实现广义Hough变换算法(GeneralizedHoughTransform)可以用于检测任意形状的......
  • 塔子哥的树-小红书2024笔试(codefun2000)
    题目链接塔子哥的树-小红书2024笔试(codefun2000)题目内容塔子哥是一个热爱冒险和探索的年轻人。有一天,他看到了一张神秘的照片,照片上有一颗挂着红薯的树。这个景象让塔子哥觉得非常有趣,他决定也要种一颗树,并挂上一些红薯,以此分享他的冒险故事。塔子哥收集了一颗神奇的......
  • .NET周刊【7月第3期 2024-07-21】
    国内文章给博客园的寄语https://www.cnblogs.com/jingc/p/18307859作者是一名39岁的大龄C#开发程序员,对博客园的艰难处境深感触动,并购买会员支持。回顾他与博客园16年的渊源,博客园在他的学习和工作中提供了大量帮助。尽管在职业生涯中经历多种开发工作,他始终坚持C#开发。面对当......
  • [极客大挑战 2019]BuyFlag
    [极客大挑战2019]BuyFlag源代码的提示<!-- ~~~postmoneyandpassword~~~if(isset($_POST['password'])){ $password=$_POST['password']; if(is_numeric($password)){ echo"passwordcan'tbenumber</br>"; }elseif($p......
  • P3957[NOIP2017普及组]跳房子
    https://www.luogu.com.cn/problem/P3957https://class.51nod.com/Html/Textbook/ChapterIndex.html#textbookId=126&chapterId=337显然,但是维护滑动窗口有技巧,不能每次插入一个值,因为维护\(x\)不方便。所以考虑一个\(cur\),每次对于新的\(i\)不能跳过时移动\(cur\),然后队......