欢迎大家订阅《教你用 Python 进阶量化交易》专栏!为了能够提供给大家更轻松的学习过程,笔者在专栏内容之外已陆续推出一些手记来辅助同学们学习本专栏内容,目前已推出如下扩展篇:
在第一篇《管理概率==理性交易》中笔者结合一个简单的市场模型介绍了为什么在没有概率优势的前提下参与交易会亏钱,其实股票交易和玩一个游戏、做一个项目理念是相通的,需要章法、需要制定策略,否则就和抛硬币赌博一样一样的,用量化交易可以帮助我们管理好概率,更理性的去下单。
在第二篇《线性回归拟合股价沉浮》中笔者在专栏《股票交易策略开发:走势线性回归选股策略》小节的基础上对线性回归方法的策略应用做进一步的扩展介绍。由于线性回归作用于股票收盘价的整个周期,前后两段完全相反的周期会彼此作用,最终影响拟合的角度值,于是笔者设定窗口期用移动窗口的方式拟合股价的走势,寻找角度曲线的拐点以预示新一轮的反转走势,给大家提供一个衍生的策略思路。
在第三篇《最大回撤评价策略风险》中笔者在专栏
《股票交易数据可视化:买卖区间下策略收益绘制》的基础上对策略的最大回撤指标做一定的扩展介绍。投资是有风险的,那么如何去衡量这个风险呢?最大回撤率就是一种直观的将风险切实量化的指标,它描述了买入股票后,在策略出现最糟糕的情况下会损失多少钱,这也直接关系到了风险策略中止损因子的设定。
在第四篇《寻找最优化策略参数》中笔者在专栏
《股票交易策略开发:趋势突破择时策略》的基础上对寻找最优化策略参数的方法做一些扩展介绍。对于寻找最优化参数的方法可以选择枚举法或者蒙特卡洛法。枚举法适用于解决效率要求不高,样本规模小的问题。蒙特卡洛法得到的结果并不一定是最优的,但是在大规模样品的场合下可以更快地找到近似最优结果。
在第五篇《标记A股市场涨跌周期》中笔者在专栏
《股票交易数据的自动下载》的基础上对matplotlib绘图工具的使用方法做一些扩展介绍,在A股历史走势图中标记出市场涨跌周期。
在第六篇《Tushare Pro接口介绍》中笔者在《股票交易数据的自动下载》的基础上扩展介绍使用Tushare Pro版本获取财经和股票交易数据的方法。
本次场外篇笔者在《用装饰器注册股票池》的基础上对装饰器的进行扩展介绍,通过装饰器方式实现timeit测试函数执行时间功能。
当我们对一个函数或若干语句的执行时间要求较高时,希望能够测试得到该代码的开销时间是多少。比如遍历计算每个交易日股票数据的价格波动,如下所示:
df_sh['diff_price'] = df_sh.apply(lambda row: (row['high']-row['low']), axis =1)
"""
date open close ... volume code diff_price
0 2018-01-02 3314.03 3348.33 ... 202278860.0 sh 35.02
1 2018-01-03 3347.74 3369.11 ... 213836149.0 sh 34.63
2 2018-01-04 3371.00 3385.71 ... 206955288.0 sh 27.53
3 2018-01-05 3386.46 3391.75 ... 213060681.0 sh 21.82
4 2018-01-08 3391.55 3409.48 ... 236165106.0 sh 28.17
"""
最先想到的是采用Python内置time模块来测试代码运行时间,使用时导入模块,如下所示:
import time
其中time.perf_counter()和time.process_time() 可以实现我们的需求。
time.perf_counter():返回计时器的精准时间(系统的运行时间),包含整个系统的睡眠时间。由于返回值的基准点是未定义的,所以,只有连续调用的结果之间的差才是有效的。
time.process_time() :返回当前进程执行 CPU 的时间总和,不包含睡眠时间。由于返回值的基准点是未定义的,所以,只有连续调用的结果之间的差才是有效的。
此处使用time.perf_counter()方法实现,如下所示:
start = time.perf_counter()
#运行的程序
df_sh['diff_price'] = df_sh.apply(lambda row: (row['high']-row['low']), axis =1)
#运行的程序
elapsed = (time.perf_counter() - start)
print("Time used:",elapsed)
# Time used: 0.00546605699999958
另外可以使用Python更强大的计时库timeit测试执行时间,使用时导入timeit,如下所示:
from timeit import timeit
用timeit()测试一个函数的执行时间可按timeit(函数名_字符串,运行环境_字符串,number=运行次数)这个方式来实现,如下所示:
elapsed = timeit('func()', 'from __main__ import func', number=1)
print("Time used:",elapsed)
#Time used: 0.005263746999999985
由于电脑永远都有其他程序也在占用着资源,因此我们的程序不可能最高效地执行该函数。所以一般会进行多次试验,取最少的执行时间为真正的执行时间。此时可以使用repeat方法,使用时需要导入repeat ,如下所示:
from timeit import repeat
使用repeat方法和timeit用法相似,多了一个repeat参数,表示重复测试的次数(默认值为3),返回值为一个时间的列表,如下所示:
t_elapsed = repeat('func()', 'from __main__ import func', number=1, repeat=5)
print("Time used:",t_elapsed)
print("Time of min used:",min(t_elapsed))
#Time used: [0.005254602999999136, 0.005191876999999678, 0.00529747400000069, 0.005051099999999309, 0.005246075000000516]
#Time of min used: 0.005051099999999309
回到本篇的主题内容——装饰器,装饰器顾名思义就是“装饰”其他函数,为其他函数添加附加功能,原则上不能修改被装饰的函数的代码,也不能修改被装饰的函数的调用方式,这也是装饰器“厉害”的地方。
以上无论使用time,还是timeit,都需要对被测函数添加实现的代码,那么尝试使用装饰器来解决这个痛点,如下所示:
def timeit(func):
def wrapper(*args, **kwargs):
start = time.perf_counter()
func(*args, **kwargs)
elapsed = (time.perf_counter() - start)
print("Time used: %s ", elapsed)
return wrapper
@timeit
def func_diff():
df_sh['diff_price'] = df_sh.apply(lambda row: (row['high']-row['low']), axis =1)
# timeit(func_diff)()
func_diff()#Time used: %s 0.005055395000000074
关于装饰器的基础原理介绍,大家可以参阅专栏中的讲解,此处再次提炼下其关键点。执行func_diff()等价于执行timeit(func_diff)(),把函数作为一个变量传递给timeit函数, 此处timeit为二层嵌套的高阶函数。电动叉车
假如要像timeit模块那样有repeat、number参数扩展测试的需求,可以再进一步升级下装饰器,如下所示:
# use decorator method with parameter
def timer_para(number = 3, repeat = 3):
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(repeat):
start = time.perf_counter()
for _ in range(number):
func(*args, **kwargs)
elapsed = (time.perf_counter() - start)
print("Time of %s used: %s ", i, elapsed)
return wrapper
return decorator
@timer_para(number = 2, repeat = 2)
def func_diff_par():
df_sh['diff_price'] = df_sh.apply(lambda row: (row['high']-row['low']), axis =1)
# timer_para(number = 2, repeat = 2)(func_diff_par)()
func_diff_par()
#Time of %s used: %s 0 0.01577713199999997
#Time of %s used: %s 1 0.015201944999999939
执行func_diff_par ()等价于执行timer_para(number = 2)(func_diff_par)(),此处timeit为三层嵌套的高阶函数,第1层是timer_para(number = 2),返回第2层decorator函数,第2层函数decorator接受func_diff_par函数作为参数进行调用,返回第三层函数wrapper。如此一来只需要使用@xxxx就可以一劳永逸地对所有函数测试执行时间了!!!