timeutils.py
作者有时候对一些代码的修改并不会全部上传修改到GitHub上,因此如果遇到不懂的地方,可以尝试去直接查看安装到本地的模块源代码。至于怎样看源代码,各位可以自行百度之:)为什么要说这个?因为有时候GitHub上的代码和你安装到电脑的源代码内容有差别!
timeutils.py子模块源代码详解
代码内容稍微多一些,咱们也是分块来看。
# coding:utf8 import datetime from datetime import timedelta from functools import lru_cache import requests import time
从引入的模块分析,由于这里是对时间量的分析和操作,引入了datetime/time模块。然后又从该模块引入了表示时间差的timedelta类,从高级函数模块functools 中引入lru_cache这个缓存调度装饰器,还引入了网络请求的requests模块。
这里的lru_cache可能很多朋友没见过。引用网上文章:functools.lru_cache的作用主要是用来做缓存,他能把相对耗时的函数结果进行保存,避免传入相同的参数重复计算。同时,缓存并不会无限增长,不用的缓存会被释放。其原型为functools.lru_cache(maxsize=None, typed=False)。参数maxsize为最多缓存的次数,如果为None,则无限制,设置为偶数值时,性能最佳;如果 typed=True,则不同参数类型的调用将分别缓存,例如 f(3) 和 f(3.0)。加上了这个装饰器的函数如果下一次传入的参数和上一次完全相同的话,那么函数直接从缓存返回return的内容,而不会再次在函数进行计算。现在是不是感觉新学到了一个很有趣的东西呢?
@lru_cache() def is_holiday(day): """ 判断是否节假日, api 来自百度 apistore: http://apistore.baidu.com/apiworks/servicedetail/1116.html :param day: 日期, 格式为 '20160404' :return: bool """ api = 'http://tool.bitefu.net/jiari/' params = {'d': day, 'apiserviceid': 1116} rep = requests.get(api, params) res = rep.text return True if res != "0" else False
这里为is_holiday(day)函数进行定义。可能是这个函数会多次使用或者计算相对耗时,因此这个函数增加了@lru_cache()装饰器来调度缓存。
这里的注释给出的信息是作者忘了修改的信息!其注释中的API已经不能使用了,这个坑搞得我不知道用了多久才发现……作者在下面的代码用的是另一个api地址……这个函数将从http://tool.bitefu.net/jiari这个网址的API获取今天是不是假日的信息。按照’20180314’这类格式向函数内传入参数day之后,函数体将向服务器发送带有params的请求,网站响应发回的内容如果不是”0”,那么返回True(是假日),否则返回False(不是假日)。
def is_holiday_today(): """ 判断今天是否时节假日 :return: bool """ today = datetime.date.today().strftime('%Y%m%d') return is_holiday(today)
写注解时看到注释偶然出现了错别字……哈哈,是人难免犯错嘛。说不定我也正犯着这个错呢。
这里给is_holiday_today()定义,也就是判断今天是不是节假日。通过datetime.date.today()返回datetime格式的日期(注:如果单独datetime.datetime.today()则返回日期和时间,一直到毫秒),再经由.strftime('%Y%m%d')将返回的日期修改为str字符串,格式类似‘20180314’,然后传入上文提到的is_holiday()中进行判断,返回True(是假期)或者False(不是假期)。
def is_tradetime_now(): """ 判断目前是不是交易时间, 并没有对节假日做处理 :return: bool """ now_time = time.localtime() now = (now_time.tm_hour, now_time.tm_min, now_time.tm_sec) if (9, 15, 0) <= now <= (11, 30, 0) or (13, 0, 0) <= now <= (15, 0, 0): return True return False
这里给is_tradetime_now()函数定义。根据注释,这个函数用于判断目前是不是交易时间,但是没有考虑是不是节假日。这或许决定了,在使用这个函数之前,需要用is_holiday_today()这个函数判断是不是假日。如果是假日,就没有必要再用is_tradetime_now()函数了;如果不是假日,也就是当天是交易日,那么才有使用is_tradetime_now()这个函数的必要。
这个函数体首先用time.localtime()给出当地的日期、时间等数据。由于这个当地时间是以电脑使用的时区决定的,个人建议这里换成绝对时区(强制按照东八区,而不是本地时间)。尽管大多数用这个程序的人是国人,不过这样严谨点也好哈。强制转换获取的时间的时区可以使用第三方的pytz模块。具体代码为:
import pytz import datetime timezone = pytz.timezone('Asia/Shanghai') now_time = datetime.datetime.now(timezone) now = (now_time.hour, now_time.minute, now_time.second)
这里的now是(7,22,5)这样的格式,就和前文类型近似了,但是避免了电脑转换时区的问题。
之后就是通过tuple的大小判断功能看当前时间是否在股市的交易时间内,这里用的交易时间是从盘前开始的竞价开始的。关于中国股市的交易时间可以自行百度。(注:不排除未来为了沪伦通可能会开夜盘的可能,因此届时可能还是要修改这个函数的)
def calc_next_trade_time_delta_seconds(): now_time = datetime.datetime.now() now = (now_time.hour, now_time.minute, now_time.second) if now < (9, 15, 0): next_trade_start = now_time.replace(hour=9, minute=15, second=0, microsecond=0) elif (12, 0, 0) < now < (13, 0, 0):
next_trade_start = now_time.replace(hour=13, minute=0, second=0, microsecond=0) elif now > (15, 0, 0): distance_next_work_day = 1 while True: target_day = now_time + timedelta(days=distance_next_work_day) if is_holiday(target_day.strftime('%Y%m%d')): distance_next_work_day += 1 else: break day_delta = timedelta(days=distance_next_work_day) next_trade_start = (now_time + day_delta).replace(hour=9, minute=15, second=0, microsecond=0) else: return 0
time_delta = next_trade_start - now_time return time_delta.total_seconds()
这里给calc_next_trade_time_delta_seconds()函数定义。乍一看这个函数有点儿长,而且恰巧还没有给注释。我看到这个函数一开始也只能从函数名称上猜这是个啥。看名字,大概是计算下一个交易时间的秒时间差?猜完之后开始看这个函数体在干什么吧。
首先用datetime获得当前时间(.now()和.today()应该是一回事),然后将当前时间的时分秒改写为tuple。由于这个函数也不判断是否是交易日,所以在用这个函数前肯定也是要判断当天是否是假期。接下来是一系列判断,判断看上去都是用next_trade_start来储存判断的输出结果。从变量名判断,这个变量存储的是下一段交易开始的时间。
判断1:如果当前的时间在9点15分之前(这里暗含了在0点0时0分之后),也就是盘前时间都没到,那么通过对datetime格式的now_time用replace命令改成9点15分0秒0毫秒并传递给next_trade_start函数。
判断2:如果当前的时间在12点之后和13点之间,next_trade_start的值就是(13,0,0,0)。这里犯了非常明显的错误!因为中国A股上午的收盘时间11点30分,而不是12点。这将导致时间在11点30分至12点之间时,这个函数体将出现错误的输出。
判断3:如果当前的时间在15点之后,也就是收盘之后,那么下一个开盘的时间就需要判断下一天是不是交易日了。函数体所做的工作是:将今天的日期不断加1天,然后用is_holiday()函数去判断是不是假日,直到判断到不是假日的那天出现。然后将这个下一个交易日的9点15分0秒0毫秒传给next_trade_start。
最后计算next_trade_start与函数体开始计算的时间做差,最终返回的是time_delta.total_seconds()。这个返回值是是time_delta经过total_seconds()函数计算总秒数后的返回值。
所以看过函数体之后,大家就能发现,这个函数确实是在求两个时间相差的秒数。但是由于文档没有任何注释,所以只能通过看源代码函数体做了什么才能得知这个函数的用途。此外,只有看源代码才能得知这个函数内部有个时间上的小Bug。这在使用第三方提供的金融API模块时尤为重要,毕竟一个代码错误,分分钟可能亏的就是你的钱!