在 backtrader 源代码的文件夹 utils 中主要分为 7 个文件,
- *initi*.py
- py3.py
- autodict.py
- date.py
- dateintern.py
- flushfile.py
- ordereddefaultdict.py
这个文件主要用于 python 包和模块,方便在包和模块之间进行 import
###############################################################################
from __future__ import (absolute_import, division, print_function,
unicode_literals)
from collections import OrderedDict
import sys
from .date import *
from .ordereddefaultdict import *
from .autodict import * 这个是 backtrader 的基础之一,在很多地方都用到了这个文件中的内容,主要是提供 python2 和 python3 的兼容,目前大家普遍都使用 python3,可以考把 python2 给删除了,理论上来说,应该能提高一些效率。
###############################################################################
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import itertools
import sys
PY2 = sys.version_info.major == 2 # 获取当前 python 的版本,看是否是 python2,如果是 python2,返回值就是 True,否则就是 False
# 如果是 python2
if PY2:
# 尝试用于 import _winreg 模块,如果可以调用,就证明这个系统是 windows 系统,可以用于 windows 注册表相关的操作;
# 如果调用出现了错误,就说明系统不是 windows 系统,winreg 设置成 None
try:
import _winreg as winreg
except ImportError:
winreg = None
# 系统允许的最大整数
MAXINT = sys.maxint
# 系统允许的最小整数
MININT = -sys.maxint - 1
# 系统允许的最大浮点数
MAXFLOAT = sys.float_info.max
# 系统允许的最小浮点数
MINFLOAT = sys.float_info.min
# 字符串类型
string_types = str, unicode
# 整数类型
integer_types = int, long
# 过滤函数 filter
filter = itertools.ifilter
# 映射函数 map
map = itertools.imap
# 创建整数迭代器函数 range
range = xrange
# 把元素成对打包成元组的函数 zip
zip = itertools.izip
# 整数
long = long
# 对比函数
cmp = cmp
# 生成 bytes
bytes = bytes
bstr = bytes
# 字符串缓存
from io import StringIO
# 爬虫模块
from urllib2 import urlopen, ProxyHandler, build_opener, install_opener
from urllib import quote as urlquote
# 字典迭代
def iterkeys(d): return d.iterkeys()
def itervalues(d): return d.itervalues()
def iteritems(d): return d.iteritems()
# 字典值
def keys(d): return d.keys()
def values(d): return d.values()
def items(d): return d.items()
import Queue as queue
else:
# python3 的注释和上面的注释差不多
try:
import winreg
except ImportError:
winreg = None
MAXINT = sys.maxsize
MININT = -sys.maxsize - 1
MAXFLOAT = sys.float_info.max
MINFLOAT = sys.float_info.min
string_types = str,
integer_types = int,
filter = filter
map = map
range = range
zip = zip
long = int
# 需要注意,这个 cmp 是自定义的函数,返回值是 1,0,-1
def cmp(a, b): return (a > b) - (a < b)
def bytes(x): return x.encode('utf-8')
def bstr(x): return str(x)
from io import StringIO
from urllib.request import (urlopen, ProxyHandler, build_opener,
install_opener)
from urllib.parse import quote as urlquote
def iterkeys(d): return iter(d.keys())
def itervalues(d): return iter(d.values())
def iteritems(d): return iter(d.items())
def keys(d): return list(d.keys())
def values(d): return list(d.values())
def items(d): return list(d.items())
import queue as queue
# This is from Armin Ronacher from Flash simplified later by six
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
# This requires a bit of explanation: the basic idea is to make a dummy
# metaclass for one level of class instantiation that replaces itself with
# the actual metaclass.
# 这个函数创建一个带有元类的基类,主要作用是兼容 python2 和 python3 的语法,现在有了一个更新的方案,是使用装饰器@six.add_metaclass(Meta)
# 参考文献:https://qa.1r1g.com/sf/ask/1295967501/
# https://zhuanlan.zhihu.com/p/354828950
# https://www.jianshu.com/p/224ffcb8e73e
class metaclass(meta):
def __new__(cls, name, this_bases, d):
return meta(name, bases, d)
return type.__new__(metaclass, str('temporary_class'), (), {}) 通过继承,实现了排序的默认字典,在 backtrader 中,并没有使用到,可以考虑忽略。
from __future__ import (absolute_import, division, print_function,
unicode_literals)
from collections import OrderedDict
from .py3 import iteritems
# 这是一个没有使用到的类,创建的意图应该是保持添加进 orderedDict 类中保持 DefaultDict 的特征
# 在整个 backtrader 中没有找到这个类的使用,大家忽略就好,甚至可以删除,不会影响使用。
class OrderedDefaultdict(OrderedDict):
# 类初始化,传入*args 参数和**kwargs 参数
def __init__(self, *args, **kwargs):
# 如果没有传入*args 的话,默认 self.default_factory 是 None
if not args:
self.default_factory = None
# 如果传入了*args,如果 args[0]不满足是 None 或者可调用,将会报错,如果满足了,默认将是 atgs[0],剩下的参数将是 args[1:]
else:
if not (args[0] is None or callable(args[0])):
raise TypeError('first argument must be callable or None')
self.default_factory = args[0]
args = args[1:]
super(OrderedDefaultdict, self).__init__(*args, **kwargs)
# 当 key 值不存在的时候,如果 self.default_factory 是 None 的话,将会返回 key error;如果不是 None 的话,将会返回 self.default_factory()
def __missing__(self, key):
if self.default_factory is None:
raise KeyError(key)
self[key] = default = self.default_factory()
return default
# 可选方法,用于支持 pickle
def __reduce__(self): # optional, for pickle support
args = (self.default_factory,) if self.default_factory else ()
return self.__class__, args, None, None, iteritems(self) 这个文件主要是改变标准的输出方式,在 backtrader 中基本没用到,应该不会影响使用,忽略。
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import sys
# 这个类按照字面意思来看,应该是输出的时候刷新,让输出立即显示,但是看这个类的使用,好像并没有起到这个作用
# 只有在 btrun 文件中 import 这个文件,import backtrader.utils.flushfile,import 的时候会直接判断这个系统
# 是不是 win32,如果是 win32 就用 flushfile 创建两个实例,初始化的时候使用 sys.stdout,sys.stderr 这两个方法
# 实际上看起来,并没有起到什么作用。就跟 py3 的文件一样,可能是为了起到兼容的作用,但是现在谁还用 python2 呀,几乎很少了
# 所以整个框架看起来冗余了不少的函数和类
class flushfile(object):
def __init__(self, f):
self.f = f
def write(self, x):
self.f.write(x)
self.f.flush()
def flush(self):
self.f.flush()
if sys.platform == 'win32':
sys.stdout = flushfile(sys.stdout)
sys.stderr = flushfile(sys.stderr)
# 没有用到的类,看类型的话,应该是输出的
class StdOutDevNull(object):
def __init__(self):
self.stdout = sys.stdout
sys.stdout = self
def write(self, x):
pass
def flush(self):
pass
def stop(self):
sys.stdout = self.stdout 这个文件主要是继承 python 的 dict 并提供一些额外的功能。在实际中使用比较多,但是由于 python 的字典总体上效率是非常高的,虽然可能是以牺牲内存为代价,所以,这部分暂时不用考虑优化。
from __future__ import (absolute_import, division, print_function,
unicode_literals)
from collections import OrderedDict, defaultdict
# from .py3 import values as py3lvalues
from backtrader.utils.py3 import values as py3lvalues #修改相对引用为绝对引用
def Tree():
# 不知道定义这个函数有什么用,其他地方没有用到过,忽略
# 可以考虑删除
return defaultdict(Tree)
class AutoDictList(dict):
# 继承字典,当访问缺失的的 key 的时候,将会自动生成一个 key 值,对应的 value 值是一个空的列表
# 这个新创建的类仅仅用在了 collections.defaultdict(AutoDictList)这一行代码中。
def __missing__(self, key):
value = self[key] = list()
return value
class DotDict(dict):
# If the attribut is not found in the usual places try the dict itself
# 这个类仅仅用于了下面一行代码,当访问属性的时候,如果没有这个属性,将会调用 __getattr__
# _obj.dnames = DotDict([(d._name, d) for d in _obj.datas if getattr(d, '_name', '')])
def __getattr__(self, key):
if key.startswith('__'):
return super(DotDict, self).__getattr__(key)
return self[key]
# 这个函数用途稍微广泛一些,主要在 tradeanalyzer 和 ibstore 里面调用了,是对 python 字典的扩展
# 相比于 python 内置的 dict,额外增加了一个属性:_closed,增加了函数 _close,_open,__missing__,__getattr__,重写了 __setattr__
class AutoDict(dict):
# 初始化默认属性 _closed 设置成 False
_closed = False
# _close 方法
def _close(self):
# 类的属性修改为 True
self._closed = True
# 对于字典中的值,如果这些值是 AutoDict 或者 AutoOrderedDict 的实例,就调用 _close 方法设置属性 _closed 为 True
for key, val in self.items():
if isinstance(val, (AutoDict, AutoOrderedDict)):
val._close()
# _open 方法,设置 _closed 属性为 False
def _open(self):
self._closed = False
# __missing__ 方法处理当 key 不存在的情况,如果是 _closed,就返回 KeyError,如果不是,就给这个 key 创建一个 AutoDict()实例
def __missing__(self, key):
if self._closed:
raise KeyError
value = self[key] = AutoDict()
return value
# __getattr__ 这个方法很多余,if 永远访问不到,可以删除 if 语句,甚至这个方法都可以删除
def __getattr__(self, key):
if False and key.startswith('_'):
raise AttributeError
return self[key]
# __setattr__ 这个方法也比较多余,可以考虑删除
def __setattr__(self, key, value):
if False and key.startswith('_'):
self.__dict__[key] = value
return
self[key] = value
# 创建的一个新的有序的字典,增加了一些函数,和 AutoDict 有些类似
class AutoOrderedDict(OrderedDict):
_closed = False
def _close(self):
self._closed = True
for key, val in self.items():
if isinstance(val, (AutoDict, AutoOrderedDict)):
val._close()
def _open(self):
self._closed = False
def __missing__(self, key):
if self._closed:
raise KeyError
# value = self[key] = type(self)()
value = self[key] = AutoOrderedDict()
return value
# __getattr__ 和 __setattr__ 这两个函数相比于 AutoDict 的正常了很多
def __getattr__(self, key):
if key.startswith('_'):
raise AttributeError
return self[key]
def __setattr__(self, key, value):
if key.startswith('_'):
self.__dict__[key] = value
return
self[key] = value
# 定义的数学操作,暂时还没明白是什么意思,但是看起来只有 __iadd__ 和 __isub__ 是正常的
# Define math operations
def __iadd__(self, other):
if type(self) != type(other):
return type(other)() + other
return self + other
def __isub__(self, other):
if type(self) != type(other):
return type(other)() - other
return self - other
def __imul__(self, other):
if type(self) != type(other):
return type(other)() * other
return self + other
def __idiv__(self, other):
if type(self) != type(other):
return type(other)() // other
return self + other
def __itruediv__(self, other):
if type(self) != type(other):
return type(other)() / other
return self + other
def lvalues(self):
return py3lvalues(self)
if __name__ == "__main__":
aod = AutoOrderedDict()
print("aod",dir(aod))
od = OrderedDict()
print("od",dir(od)) 主要用于日期相关的变量、函数的 import 用,可能单独使用一个文件,控制 import 的变量,有提高效率的作用。这个结论是否有效,大家可以验证下哈,这个仅仅是猜想。
from __future__ import (absolute_import, division, print_function,
unicode_literals)
from .dateintern import (num2date, num2dt, date2num, time2num, num2time,
UTC, TZLocal, Localizer, tzparse, TIME_MAX, TIME_MIN)
# 控制可以 import 哪些对象
__all__ = ('num2date', 'num2dt', 'date2num', 'time2num', 'num2time',
'UTC', 'TZLocal', 'Localizer', 'tzparse', 'TIME_MAX', 'TIME_MIN') 这个文件中保存了很多日期、时间相关的变量、函数和类,并且在 backtrader 中有着大量的使用,所以这个文件可能是比较关键的,并且考虑重点优化其中的一些函数。
根据使用 backtrader 的经验,考虑优化这个文件中的 num2date, num2dt, date2num, time2num, num2time,在下一篇文章中,就考虑使用 cython 改写这几个函数。
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import datetime
import math
import time as _time
from .py3 import string_types
# 0 的时间差
ZERO = datetime.timedelta(0)
# 使用 time 模块的 timezone 属性可以返回当地时区(未启动夏令时)距离格林威治的偏移秒数(>0,美洲<=0 大部分欧洲,亚洲,非洲)
# STDOFFSET 代表非夏令时时候的偏移量
STDOFFSET = datetime.timedelta(seconds=-_time.timezone)
# time.daylight 为 0 的时候代表没有夏令时,非 0 代表是夏令时
if _time.daylight:
# time.altzone 返回当地的 DST 时区的偏移,在 UTC 西部秒数(如果有一个定义)
# DSTOFFSET 夏令时时的偏移量
DSTOFFSET = datetime.timedelta(seconds=-_time.altzone)
else:
DSTOFFSET = STDOFFSET
# DSTDIFF 代表夏令时与非夏令时的偏移量的差
DSTDIFF = DSTOFFSET - STDOFFSET
# To avoid rounding errors taking dates to next day
# 为了避免四舍五入偏差导致日期进入下一天,设定 TIME_MAX
TIME_MAX = datetime.time(23, 59, 59, 999990)
# To avoid rounding errors taking dates to next day
# 为了避免四舍五入偏差导致日期进入下一天,设定 TIME_MIN
TIME_MIN = datetime.time.min
def tzparse(tz):
# 这个函数尝试对 tz 进行转换
# If no object has been provided by the user and a timezone can be
# found via contractdtails, then try to get it from pytz, which may or
# may not be available.
tzstr = isinstance(tz, string_types)
if tz is None or not tzstr:
return Localizer(tz)
try:
import pytz # keep the import very local
except ImportError:
return Localizer(tz) # nothing can be done
tzs = tz
if tzs == 'CST': # usual alias
tzs = 'CST6CDT'
try:
tz = pytz.timezone(tzs)
except pytz.UnknownTimeZoneError:
return Localizer(tz) # nothing can be done
return tz
def Localizer(tz):
# 这个函数是给 tz 增加一个 localize 的方法,这个 localize 的方法是给 dt 添加一个时区信息
# tzparse 和 Localizer 主要是实盘的时候处理不同的时区的时候考虑到的
import types
def localize(self, dt):
return dt.replace(tzinfo=self)
if tz is not None and not hasattr(tz, 'localize'):
# patch the tz instance with a bound method
tz.localize = types.MethodType(localize, tz)
return tz
# A UTC class, same as the one in the Python Docs
class _UTC(datetime.tzinfo):
"""UTC"""
# UTC 类
def utcoffset(self, dt):
return ZERO
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return ZERO
def localize(self, dt):
return dt.replace(tzinfo=self)
class _LocalTimezone(datetime.tzinfo):
'''本地时区相关的处理'''
# 时区的偏移量
def utcoffset(self, dt):
if self._isdst(dt):
return DSTOFFSET
else:
return STDOFFSET
# 夏令时的偏移量,不是夏令时,偏移量为 0
def dst(self, dt):
if self._isdst(dt):
return DSTDIFF
else:
return ZERO
# 可能是时区名称
def tzname(self, dt):
return _time.tzname[self._isdst(dt)]
# 判断当前时间是否是夏令时
def _isdst(self, dt):
tt = (dt.year, dt.month, dt.day,
dt.hour, dt.minute, dt.second,
dt.weekday(), 0, 0)
try:
stamp = _time.mktime(tt)
except (ValueError, OverflowError):
return False # Too far in the future, not relevant
tt = _time.localtime(stamp)
return tt.tm_isdst > 0
# 给 dt 增加一个时区信息
def localize(self, dt):
return dt.replace(tzinfo=self)
UTC = _UTC()
TZLocal = _LocalTimezone()
HOURS_PER_DAY = 24.0 # 一天 24 小时
MINUTES_PER_HOUR = 60.0 # 1 小时 60 分钟
SECONDS_PER_MINUTE = 60.0 # 1 分钟 60 秒
MUSECONDS_PER_SECOND = 1e6 # 1 秒有多少微秒
MINUTES_PER_DAY = MINUTES_PER_HOUR * HOURS_PER_DAY # 1 天有多少分钟
SECONDS_PER_DAY = SECONDS_PER_MINUTE * MINUTES_PER_DAY # 1 天有多少秒
MUSECONDS_PER_DAY = MUSECONDS_PER_SECOND * SECONDS_PER_DAY # 1 天有多少微秒
# 下面这四个函数是经常使用的,注释完成之后,尝试使用 cython 进行改写,看能提高多少的运算速度
def num2date(x, tz=None, naive=True):
# Same as matplotlib except if tz is None a naive datetime object
# will be returned.
"""
*x* is a float value which gives the number of days
(fraction part represents hours, minutes, seconds) since
0001-01-01 00:00:00 UTC *plus* *one*.
The addition of one here is a historical artifact. Also, note
that the Gregorian calendar is assumed; this is not universal
practice. For details, see the module docstring.
Return value is a :class:`datetime` instance in timezone *tz* (default to
rcparams TZ value).
If *x* is a sequence, a sequence of :class:`datetime` objects will
be returned.
"""
ix = int(x) # 对 x 进行取整数
dt = datetime.datetime.fromordinal(ix) # 返回对应 Gregorian 日历时间对应的 datetime 对象
remainder = float(x) - ix # x 的小数部分
hour, remainder = divmod(HOURS_PER_DAY * remainder, 1) # 小时
minute, remainder = divmod(MINUTES_PER_HOUR * remainder, 1) # 分钟
second, remainder = divmod(SECONDS_PER_MINUTE * remainder, 1) # 秒
microsecond = int(MUSECONDS_PER_SECOND * remainder) # 微妙
# 如果微秒数小于 10,舍去
if microsecond < 10:
microsecond = 0 # compensate for rounding errors
# 这个写的不怎么样,True 应该去掉的,没有意义
# if True and tz is not None:
if tz is not None:
# 合成时间
dt = datetime.datetime(
dt.year, dt.month, dt.day, int(hour), int(minute), int(second),
microsecond, tzinfo=UTC)
dt = dt.astimezone(tz)
if naive:
dt = dt.replace(tzinfo=None)
else:
# 如果没有传入 tz 信息,生成不包含时区信息的时间
# If not tz has been passed return a non-timezoned dt
dt = datetime.datetime(
dt.year, dt.month, dt.day, int(hour), int(minute), int(second),
microsecond)
if microsecond > 999990: # compensate for rounding errors
dt += datetime.timedelta(microseconds=1e6 - microsecond)
return dt
# 数字转换成日期
def num2dt(num, tz=None, naive=True):
return num2date(num, tz=tz, naive=naive).date()
# 数字转换成时间
def num2time(num, tz=None, naive=True):
return num2date(num, tz=tz, naive=naive).time()
# 日期时间转换成数字
def date2num(dt, tz=None):
"""
Convert :mod:`datetime` to the Gregorian date as UTC float days,
preserving hours, minutes, seconds and microseconds. Return value
is a :func:`float`.
"""
if tz is not None:
dt = tz.localize(dt)
if hasattr(dt, 'tzinfo') and dt.tzinfo is not None:
delta = dt.tzinfo.utcoffset(dt)
if delta is not None:
dt -= delta
base = float(dt.toordinal())
if hasattr(dt, 'hour'):
# base += (dt.hour / HOURS_PER_DAY +
# dt.minute / MINUTES_PER_DAY +
# dt.second / SECONDS_PER_DAY +
# dt.microsecond / MUSECONDS_PER_DAY
# )
base = math.fsum(
(base, dt.hour / HOURS_PER_DAY, dt.minute / MINUTES_PER_DAY,
dt.second / SECONDS_PER_DAY, dt.microsecond / MUSECONDS_PER_DAY))
return base
# 时间转成数字
def time2num(tm):
"""
Converts the hour/minute/second/microsecond part of tm (datetime.datetime
or time) to a num
"""
num = (tm.hour / HOURS_PER_DAY +
tm.minute / MINUTES_PER_DAY +
tm.second / SECONDS_PER_DAY +
tm.microsecond / MUSECONDS_PER_DAY)
return num