├── __init__.py ├── core ├── __init__.py ├── utils_test.py ├── utils.py ├── dispatcher.py ├── base.py ├── dac2.py └── dac2_test.py ├── my └── __init__.py ├── adapter └── __init__.py ├── doc ├── feature.txt ├── devlog.txt └── techlog.txt ├── README.md ├── .gitignore └── LICENSE /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /my/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /adapter/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/feature.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /doc/devlog.txt: -------------------------------------------------------------------------------- 1 | #2013-12-15 2 | 重新开始 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pyctp2 2 | ====== 3 | 4 | 重新定义并实现pyctp. 5 | 6 | 1. 以极简方式定义必须实现的功能 7 | 2. 支持基于ticks的策略验证,但剥离回测. 8 | 3. 支持组合指令即多合约运算和下单 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | *.pyc 3 | #*.py~ 4 | *.sw? 5 | #*.txt~ 6 | #.gitignore~ 7 | #.gitignore.sw* 8 | *~ 9 | *.log 10 | *.csv 11 | data 12 | *deprecated.py 13 | *.test.db 14 | -------------------------------------------------------------------------------- /core/utils_test.py: -------------------------------------------------------------------------------- 1 | #-*- coding: utf-8 -*- 2 | 3 | import unittest 4 | 5 | from utils import * 6 | 7 | class ModuleTest(unittest.TestCase): 8 | def test_merge(self): 9 | a = [1,3,5,7] 10 | b = [2,4,6,8,10] 11 | self.assertEqual([1,2,3,4,5,6,7,8,10],merge(a,b)) 12 | self.assertEqual([1,3,5,7],merge([],a)) 13 | self.assertEqual([1,3,5,7],merge(a,[])) 14 | self.assertEqual([],merge([],[])) 15 | 16 | def test_merge_i(self): 17 | a = [10,30,31,70] 18 | b = [20,40,60,80,100] 19 | c = [11,31,51,71] 20 | d = [21,41,61,81] 21 | e = [12,32,42,52] 22 | f = [22,42,62,82,102,103] 23 | self.assertEqual([10,11,20,30,31,31,40,51,60,70,71,80,100],merge_i(a,b,c)) 24 | self.assertEqual([10,11,12,20,21,22,30,31,31,32,40,41,42,42,51,52,60,61,62,70,71,80,81,82,100,102,103],merge_i(a,b,c,d,e,f)) 25 | 26 | def test_merge_r(self): 27 | a = [10,30,31,70] 28 | b = [20,40,60,80,100] 29 | c = [11,31,51,71] 30 | d = [21,41,61,81] 31 | e = [12,32,42,52] 32 | f = [22,42,62,82,102,103] 33 | self.assertEqual([10,11,20,30,31,31,40,51,60,70,71,80,100],merge_r(a,b,c)) 34 | self.assertEqual([10,11,12,20,21,22,30,31,31,32,40,41,42,42,51,52,60,61,62,70,71,80,81,82,100,102,103],merge_r(a,b,c,d,e,f)) 35 | 36 | 37 | if __name__ == "__main__": 38 | import logging 39 | logging.basicConfig(filename="test.log",level=logging.DEBUG,format='%(name)s:%(funcName)s:%(lineno)d:%(asctime)s %(levelname)s %(message)s') 40 | 41 | unittest.main() 42 | -------------------------------------------------------------------------------- /doc/techlog.txt: -------------------------------------------------------------------------------- 1 | 2 | #20130804 python3环境安装 3 | 1. 本尊 4 | 2. setuptools 5 | https://pypi.python.org/pypi/setuptools/0.9.8 6 | The recommended way to install setuptools on Windows is to download ez_setup.py and run it. The script will download the appropriate .egg file and install it for you. 7 | 8 | python ez_setup.py 9 | 3. pip 10 | python setup.py install 11 | #在python33/Scripts下会创建pip.exe 12 | 4. ipython 13 | #https://pypi.python.org/pypi/ipython 14 | 或 pip install ipython3 15 | 16 | 命令行为ipython3 17 | 18 | 4.1 pyreadline #ipython的颜色设定依赖 19 | https://pypi.python.org/pypi/pyreadline/2.0 20 | 21 | 5. pypy3 22 | 需下载pypy3版本,默认版本是针对python2.X的 23 | 解压后,将pypy3-2.1-beta1-win32复制到d:并改名为pypy3-2.1 24 | 然后将PYPY_HOME改为该目录 25 | 26 | 6. python3使用的是MSC1600即VC2010, 是否须重新编译pyd? 27 | 28 | 7. VS2010 Express 29 | ##http://download.microsoft.com/download/5/C/1/5C156922-CA10-49D8-B7E7-9BF092C3B6EB/VS2010ExpressCHS.iso 30 | 共1.8G,太庞大了 31 | 32 | 7.1 VC2010 Express 33 | http://www.microsoft.com/visualstudio/eng/downloads 34 | #http://www.microsoft.com/visualstudio/chs/products/visual-studio-express-products 35 | 36 | Visual C++ 2010 Express 37 | Build custom applications in Visual C++, a powerful language that gives deep and detailed control when building either native Windows (COM+) applications or .NET Framework-managed Windows applications. After installation, you can try this product for up to 30 days. You must register to obtain a free product key for ongoing use after 30 days. 38 | 39 | 40 | 8. 依赖的包: decorator.py 41 | https://pypi.python.org/pypi/decorator 42 | 下载后, 43 | python setup.py install 44 | 45 | #python3的区别 46 | 1. print 47 | 2. reload ==> imp.reload 48 | 49 | #2013-8-11 50 | #decorate.py从lib改为直接安装后, pypy的执行出现问题.因为只安装到了python3 51 | 故只能将python33的site-packages目录下的decorator.py复制到项目目录的lib下 52 | 本机全路径为 C:\Python33\Lib\site-packages\decorator-3.4.0-py3.3.egg 53 | 54 | 55 | #2013-8-25 56 | #1. 在pypy3-2.1下安装sqlalchemy不成功 57 | 错误是 termios不能导入 58 | #1.1 但在 pypy1.9/python2.7下可成功安装 59 | 初期也有错,重新解压缩sqlalchemy后成功 60 | 初期错误是在2.7下安装了3.X的版本(触发2to3,导致语法问题和导入问题) 61 | 估计是因为原来的sqlalchemy先在3.X下安装,执行了2to3导致的 62 | 63 | 说明同时要在2.X和3.X下安装sqlalchemy时, 源目录必须分开.否则必然混淆 64 | 65 | #2013-12-15 66 | #重新开始 67 | -------------------------------------------------------------------------------- /core/utils.py: -------------------------------------------------------------------------------- 1 | #-*- coding: utf-8 -*- 2 | 3 | import sys 4 | import functools 5 | 6 | import logging 7 | 8 | XBASE = 100 #用于一般化的除数基数 9 | 10 | MY_FORMAT = '%(name)s:%(funcName)s:%(lineno)d:%(asctime)s %(levelname)s %(message)s' 11 | CONSOLE_FORMAT = '**%(message)s' 12 | 13 | 14 | ####日志函数 15 | #设定日志 16 | def config_logging(filename,level=logging.DEBUG,format=MY_FORMAT,to_console=True,console_level=logging.INFO): 17 | logging.basicConfig(filename=filename,level=level,format=format) 18 | if to_console: 19 | add_log2console(console_level) 20 | 21 | #将指定级别的日志同时输出到控制台 22 | def add_log2console(level = logging.INFO): 23 | console = logging.StreamHandler() 24 | console.setLevel(level) 25 | formatter = logging.Formatter(CONSOLE_FORMAT) 26 | console.setFormatter(formatter) 27 | logging.getLogger('').addHandler(console) 28 | 29 | 30 | 31 | ####根据日期得到星期数 32 | def date2week(iday): 33 | #http://blog.csdn.net/hawkfeifei/article/details/4337181 34 | year = iday//10000 35 | month = iday//100%100 36 | day = iday%100 37 | if month <= 2: 38 | month += 12 39 | year -= 1 40 | return (day+2*month+3*(month+1)//5+year+year//4-year//100+year//400)%7 + 1 #转化为1-7 41 | 42 | 43 | ####函数参数定制 44 | def fcustom(func,**kwargs): 45 | ''' 根据kwargs设置func的偏函数,并将此偏函数的名字设定为源函数名+所固定的关键字参数名 46 | ''' 47 | pf = functools.partial(func,**kwargs) 48 | pf.paras = ','.join(['%s=%s' % item for item in pf.keywords.items()]) 49 | pf.__name__ = '%s:%s' % (func.__name__,pf.paras) 50 | return pf 51 | 52 | 53 | ''' 54 | 已排序序列的持续归并 55 | ''' 56 | def merge(l1,l2): 57 | ''' 58 | 抄自http://zh.wikipedia.org/wiki/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F#Python 59 | ''' 60 | final=[] 61 | #对l1,l2进行排序 62 | l1 = sorted(l1) 63 | l2 = sorted(l2) 64 | while l1 and l2: 65 | if l1[0]<=l2[0]: 66 | final.append(l1.pop(0)) 67 | else: 68 | final.append(l2.pop(0)) 69 | return final+l1+l2 70 | 71 | def merge_i(*seqs): 72 | ''' 73 | 迭代版本 74 | 从头部取出比较,加入尾部. 这样如果初始序列长度类似,则效率最高 75 | ''' 76 | rev = list(seqs) 77 | while len(rev) > 1: 78 | rev.append(merge(rev.pop(0),rev.pop(0))) 79 | return rev[0] 80 | 81 | def merge_r(*seqs): 82 | ''' 83 | 递归版本 84 | ''' 85 | slen = len(seqs) 86 | if slen == 1: 87 | return seqs[0] 88 | mid = slen // 2 89 | return merge(merge_r(*seqs[:mid]),merge_r(*seqs[mid:])) 90 | 91 | 92 | -------------------------------------------------------------------------------- /core/dispatcher.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from core.dac2 import XMINUTE 4 | from core.base import ( BaseObject, 5 | create_sep_tick, 6 | create_sep_minute, 7 | ) 8 | 9 | CTYPE_TICK = 1 10 | CTYPE_MIN = 2 11 | 12 | class hooks(object): 13 | def __init__(self): 14 | self.hooks = {CTYPE_TICK:[],CTYPE_MIN:[]} 15 | 16 | class dispatcher(object): 17 | def __init__(self): 18 | self.c2hooks = {} #cname ==> hook 19 | self.ghooks = hooks() 20 | self.env = BaseObject(data={}) 21 | self.data = self.env.data 22 | self.helper = None 23 | 24 | def register(self,cname,ctype,callback): 25 | ''' 26 | ctype 必须为 CTYPE_XXX 27 | ''' 28 | chooks = self.c2hooks.setdefault(cname,hooks()) 29 | chooks.hooks[ctype].append(callback) 30 | if cname not in self.data: 31 | self.data[cname] = BaseObject(ticks=[],min1s=[],dates=[],cached=BaseObject()) #cached用于缓存合约相关的持久数据 32 | self.data[cname].smin1 = BaseObject(sopen=[],sclose=[],shigh=[],slow=[],svolume=[],sholding=[],stype=[]) #分钟序列数据 33 | self.data[cname].stick = BaseObject(sprice=[],sdvolume=[]) #tick序列数据 34 | 35 | def unregister(self,cname,ctype,callback): 36 | chooks = self.c2hooks.setdefault(cname,hooks()) 37 | try: 38 | chooks.hooks[ctype].remove(callback) 39 | except Exception as inst: #要移除的callback不存在 40 | pass 41 | 42 | def day_reset(self): 43 | for cname in self.data: 44 | self.data[cname].stick.sprice = [] #清空ticks数据 45 | self.data[cname].stick.sdvolume = [] 46 | if self.helper != None: 47 | self.helper.reset() 48 | else: 49 | self.helper = helper(list(self.data.keys())) 50 | 51 | 52 | def init_ticks(cname,ticks): #设定已发生的ticks数据 53 | assert cname in self.data 54 | self.data.ticks = ticks 55 | 56 | def init_min1s(cname,min1s): #设定已发生的分钟数据 57 | assert cname in self.data 58 | self.data.min1s = min1s 59 | 60 | def init_dates(cname,dates): #设定已发生的日数据 61 | assert cname in self.data 62 | self.data.dates = dates 63 | 64 | def register_global(self,ctype,callback): 65 | ''' 66 | 全局hook被所有合约调用. 且无撤销方式 67 | 1.用于从tick计算分钟数据,以及从分钟计算X分钟数据 68 | 2.日轮换时清除ticks数据 69 | ''' 70 | self.ghooks[ctype].append(callback) 71 | 72 | def receive(self,ctick): 73 | ''' 74 | 去重, 在实盘中使用, 测试直接用xtick 75 | ''' 76 | if self.helper.is_new_tick(ctick): 77 | self.xtick(ctick) 78 | 79 | def xtick(self,ctick): 80 | ''' 81 | 新TICK到来. 这里并不过滤重复tick, 由调用者保证不重复 82 | ''' 83 | if ctick.cname not in self.c2hooks: #不在盯盘列表中 84 | return 85 | ##必须环节 86 | cdata = self.data[ctick.cname] 87 | if ctick.min1 > 0: 88 | cdata.ticks.append(ctick) 89 | cdata.stick.sprice.append(ctick.price) 90 | cdata.stick.sdvolume.append(ctick.dvolume) 91 | cdata.cached.ctick = ctick 92 | #隔断分钟需要传入后续处理中, 目前用处只有便于XMINUTE识别日结束 93 | xm = XMINUTE(cdata.cached) #这里对XMINUTE的调用及其内部有足够的依赖与纠结. 传入cached是为了保持在各次调用中这个参数id是一致的,以对付缓存机制 94 | for gback in self.ghooks.hooks[CTYPE_TICK]: 95 | gback(self.data[ctick.cname],ctick) 96 | chooks = self.c2hooks[ctick.cname].hooks 97 | for cback in chooks[CTYPE_TICK]: 98 | cback(self.data[ctick.cname],ctick) 99 | if xm.modified: 100 | self.xmin(xm.cmin1) 101 | 102 | def xmin(self,cmin1): 103 | ''' 104 | 新分钟数据到来 105 | ''' 106 | cdata = self.data[cmin1.cname] 107 | if cmin1.imin > 0: 108 | cdata.min1s.append(cmin1) 109 | cdata.smin1.sopen.append(cmin1.iopen) 110 | cdata.smin1.sclose.append(cmin1.iclose) 111 | cdata.smin1.shigh.append(cmin1.ihigh) 112 | cdata.smin1.slow.append(cmin1.ilow) 113 | cdata.smin1.svolume.append(cmin1.ivolume) 114 | cdata.smin1.sholding.append(cmin1.iholding) 115 | cdata.smin1.stype.append(cmin1.itype) 116 | else: #日隔断 117 | self.day_reset() 118 | #隔断分钟需要传入hook中, 目前用处只有便于hook识别日结束,以保存数据 119 | for gback in self.ghooks.hooks[CTYPE_MIN]: 120 | gback(self.data[cmin1.cname],cmin1) 121 | chooks = self.c2hooks[cmin1.cname].hooks 122 | for cback in chooks[CTYPE_MIN]: 123 | cback(self.data[cmin1.cname],cmin1) 124 | 125 | def tick_fence(self): #用于隔离TICK,完成日收束. 仅用在回测中,实盘用tick_fence 126 | for cname in self.data: 127 | sep_tick = create_sep_tick(cname) 128 | self.xtick(sep_tick) 129 | 130 | def min_fence(self): #用于隔离分钟,完成日收束. 仅用在回测中,实盘用tick_fence 131 | for cname in self.data: 132 | sep_min = create_sep_minute(cname) 133 | self.xmin(sep_min) 134 | 135 | 136 | class helper(object): 137 | ''' 138 | 重复tick过滤. 139 | ''' 140 | def __init__(self,contracts): 141 | self.contracts = contracts 142 | self.reset() 143 | 144 | def reset(self): 145 | ''' 146 | 初始或者每日启动前重新初始化 147 | ''' 148 | self.last_map = dict([(id,0) for id in self.contracts]) 149 | 150 | def is_new_tick(self,ctick): 151 | cid = self.iserial(ctick) 152 | if cid > self.last_map[ctick.cname]: 153 | self.last_map[ctick.cname] = cid 154 | return True 155 | return False 156 | 157 | def iserial(self,ctick): 158 | return ctick.min1 << 16 + ctick.sec << 10 + ctick.msec 159 | 160 | 161 | -------------------------------------------------------------------------------- /core/base.py: -------------------------------------------------------------------------------- 1 | #-*- coding: utf-8 -*- 2 | 3 | from inspect import ( 4 | getargspec, 5 | ) 6 | import lib.decorator as decorator 7 | 8 | 9 | #####BaseObject 10 | class BaseObject(object): 11 | def __init__(self,**kwargs): 12 | self.__dict__.update(kwargs) 13 | 14 | #has_attr/get_attr/set_attr没有必要, 系统函数hasattr/getattr/setattr已实现这些功能 15 | def has_attr(self,attr_name): 16 | return attr_name in self.__dict__ 17 | 18 | def get_attr(self,attr_name): 19 | #print('BaseObject,get_attr %s' % (attr_name,)) 20 | return self.__dict__[attr_name] 21 | 22 | def set_attr(self,attr_name,value): 23 | self.__dict__[attr_name] = value 24 | 25 | def __repr__(self): 26 | return 'BaseObject' 27 | 28 | def mydict(self): 29 | return self.__dict__ 30 | 31 | def mydict_simple(self): 32 | return self.__dict__ 33 | 34 | def __len__(self): 35 | return len(self.__dict__) 36 | 37 | class CommonObject(BaseObject): 38 | def __init__(self,id,**kwargs): 39 | BaseObject.__init__(self,**kwargs) 40 | self.id = id 41 | 42 | def __repr__(self): 43 | return 'CommonObject' 44 | 45 | 46 | ###indicator 47 | #快速键值 48 | ''' 49 | 因为这里使用了id函数,所以必须妥善处理临时的输入序列参数 50 | 此时,某个序列对象被回收,然后会重新分配给另一个序列对象, 51 | 而这个对象又用于同一个被indicator修饰的函数,最终导致紊乱 52 | ''' 53 | def quick_id(v): 54 | ''' 55 | 对基础数据类型,返回(v,None) 56 | 对对象类型,返回(id(v),v) 57 | ''' 58 | t = type(v) 59 | if t == list: #用到最多 60 | return id(v),v 61 | elif t == int or t == float: #次多 62 | return v,None 63 | #elif t in [long,bool,complex,str,unicode,xrange]: 64 | elif t in [bool,complex,str,range]: #python3不再有long,unicode(默认str即为unicode) 65 | return v,None 66 | #其余都为对象,用id标识 67 | return id(v),v 68 | 69 | def quick_ids(vs): 70 | idv = [quick_id(v) for v in vs] 71 | return tuple((i for i,v in idv)),[v for i,v in idv if v!=None] 72 | 73 | 74 | import gc 75 | class ObjHolder(object): 76 | ''' 77 | 用于保持对象引用,避免释放、回收后被重新分配 78 | ''' 79 | def __init__(self): 80 | self.holder = {} 81 | 82 | def register_obj(self,obj): 83 | if id(obj) not in self.holder: 84 | self.holder[id(obj)] = obj 85 | assert self.holder[id(obj)] is obj 86 | 87 | def register_objs(self,objs): 88 | for obj in objs: 89 | self.register_obj(obj) 90 | 91 | def reset(self): 92 | self.holder.clear() 93 | gc.collect() 94 | 95 | #用于持有住各indicator的输入对象,避免临时对象被回收后重新分配给用于同一indicator的序列对象,从而导致隐秘错误 96 | GLOBAL_HOLDER = ObjHolder() 97 | 98 | 99 | def _indicator(func, *args, **kw): 100 | ''' 101 | indicator装饰器,用于常规indicator的实现 102 | 向原函数提供暂存对象, 要求原函数的最后一个位置参数必须是_ts 103 | vojb用于固定住输入中用id标识的对象,防止在计算过程中被释放后重新分配给其它对象 104 | ''' 105 | #print 'in _indicator' 106 | vargs = list(args) 107 | key = vargs + kw.values() if kw else vargs 108 | #print vargs 109 | vkey,vobjs = quick_ids(key) 110 | storage = func.storage 111 | if vkey not in storage: 112 | #storage[vkey] = BaseObject() 113 | storage[vkey] = BaseObject(initialized = False) #_ts 114 | GLOBAL_HOLDER.register_objs(vobjs) 115 | #print vargs 116 | #指标调用者直接指定_ts(用位置或命名参数)时,仍然将其替换为暂存者. 要求调用者不得指定这个参数,否则会导致莫名奇妙问题 117 | #vargs[-1] = storage[vkey] 118 | if vargs[-1] == None: #最后一个位置参数为None,就认为是_ts;这里会略为出现紊乱,如出现刻意为None的其它参数 119 | vargs[func.tpos] = storage[vkey] #允许_ts不在最后位置,以支持可变参数*args 120 | else: #后面有可变位置参数, 此时,_ts被可变参数填充, 实际总长度比应当总长度少1,应将_ts插入到该位置 121 | vargs = vargs[:func.tpos] + [storage[vkey]] + vargs[func.tpos:] 122 | #print 'vargs=',vargs 123 | return func(*vargs,**kw) 124 | 125 | def indicator(f): 126 | f.storage = {} 127 | aspecs = getargspec(f).args 128 | f.tpos = aspecs.index('_ts') 129 | assert f.tpos == len(aspecs)-1,'position of _ts is invalid' #_ts必须是最后一个固定位置参数 130 | return decorator.decorator(_indicator,f) 131 | 132 | 133 | ############## 134 | # 一个例子 135 | # 可用的ma 136 | ############## 137 | @indicator 138 | def MA_EXAMPLE(src,mlen,_ts=None): 139 | ''' 140 | 所有指标都必须设定_ts这个参数,且默认值为None,装饰器将传入暂存对象 141 | #_ts必须是最后一个固定位置参数 142 | 143 | 返回值: 144 | 移动平均序列 145 | 当序列中元素个数0, 'mlen should > 0' 148 | if not _ts.initialized: 149 | _ts.initialized = True 150 | _ts.sa = [0]*mlen #哨兵 151 | _ts.ma = [] 152 | 153 | slen = len(_ts.ma) 154 | ss = _ts.sa[-1] 155 | for i in range(slen,len(src)): 156 | ss += src[i] 157 | _ts.sa.append(ss) 158 | #print ss,_ts.sa[i-mlen] 159 | #当累计个数0,'GAND params number less than 1' 127 | if not _ts.initialized: 128 | _ts.initialized = True 129 | _ts.ga = [] 130 | 131 | for i in range(len(_ts.ga),len(args[0])): 132 | rv = all([vs[i] for vs in args]) 133 | _ts.ga.append(rv!=0) 134 | 135 | return _ts.ga 136 | 137 | @indicator 138 | def GOR(_ts=None,*args): 139 | assert len(args)>0,'GOR params number less than 1' 140 | #print 'ts=%s,args=%s' % (_ts,args) 141 | if not _ts.initialized: 142 | _ts.initialized = True 143 | _ts.gor = [] 144 | 145 | for i in range(len(_ts.gor),len(args[0])): 146 | rv = any([vs[i] for vs in args]) 147 | _ts.gor.append(rv!=0) 148 | 149 | return _ts.gor 150 | 151 | #GAND = fcustom(GOPER,oper=all) #有可变参数时,就不能再有_ts之外的参数用fcustom指定默认值 152 | #GOR = fcustom(GOPER,oper=any) #有可变参数时,就不能再有_ts之外的参数用fcustom指定默认值 153 | 154 | 155 | @indicator 156 | def DIV(source1,source2,_ts=None): 157 | ''' 158 | 序列除法 159 | ''' 160 | assert len(source1) == len(source2),'len(source1) != len(source2)' 161 | if not _ts.initialized: 162 | _ts.initialized = True 163 | _ts.ss = [] 164 | 165 | for i in range(len(_ts.ss),len(source1)): 166 | #print 'new data:',source1[i],source2[i] 167 | r = (source1[i]+source2[i]//2)//source2[i] if source2[i] != 0 else source1[i]*1000 168 | _ts.ss.append(r) 169 | 170 | return _ts.ss 171 | 172 | @indicator 173 | def DIV1(source1,vs,_ts=None): 174 | ''' 175 | 序列除常数 176 | ''' 177 | assert vs!=0,'divisor vs == 0' 178 | if not _ts.initialized: 179 | _ts.initialized = True 180 | _ts.ss = [] 181 | 182 | for i in range(len(_ts.ss),len(source1)): 183 | #print 'new data:',source1[i] 184 | _ts.ss.append((source1[i]+vs//2)//vs) 185 | 186 | return _ts.ss 187 | 188 | 189 | ############ 190 | # 常用指标 191 | # 192 | ############ 193 | 194 | 195 | @indicator 196 | def ACCUMULATE(source,_ts=None): 197 | ''' 198 | 累加 199 | ''' 200 | if not _ts.initialized: 201 | _ts.initialized = True 202 | _ts.sa = [] 203 | 204 | ss = _ts.sa[-1] if _ts.sa else 0 205 | for i in range(len(_ts.sa),len(source)): 206 | ss += source[i] 207 | _ts.sa.append(ss) 208 | #print id(_ts),id(source),source,_ts.sa 209 | return _ts.sa 210 | 211 | NSUM = ACCUMULATE 212 | 213 | @indicator 214 | def MSUM(source,mlen,_ts=None): 215 | ''' 216 | 移动求和 217 | ''' 218 | if not _ts.initialized: 219 | _ts.initialized = True 220 | _ts.ms = [] 221 | 222 | ss = ACCUMULATE(source) 223 | for i in range(len(_ts.ms),len(source)): 224 | v = ss[i] - ss[i-mlen] if i>=mlen else ss[i] 225 | _ts.ms.append(v) 226 | return _ts.ms 227 | 228 | 229 | @indicator 230 | def MA(source,mlen,_ts=None): 231 | ''' 232 | 移动平均. 使用MSUM 233 | 使用方式: 234 | rev = MA(source,13) #返回source的13期移动平均 235 | 当序列中元素个数0,'mlen should > 0' 238 | if not _ts.initialized: 239 | _ts.initialized = True 240 | _ts.ma = [] 241 | 242 | ms = MSUM(source,mlen) 243 | for i in range(len(_ts.ma),len(source)): 244 | #当累计个数=mlen else i+1 246 | _ts.ma.append((ms[i]+rlen//2)//rlen) 247 | return _ts.ma 248 | 249 | 250 | @indicator 251 | def MA_2(source,mlen,_ts=None): 252 | ''' 253 | 移动平均. 直接计 254 | 使用方式: 255 | rev = MA(source,13) #返回source的13期移动平均 256 | 当序列中元素个数0,'mlen should > 0' 259 | if not _ts.initialized: 260 | _ts.initialized = True 261 | _ts.sa = [0]*mlen #哨兵 262 | _ts.ma = [] 263 | 264 | slen = len(_ts.ma) 265 | ss = _ts.sa[-1] 266 | for i in range(slen,len(source)): 267 | ss += source[i] 268 | _ts.sa.append(ss) 269 | #print ss,_ts.sa[i-mlen] 270 | #当累计个数0,'mlen should > 0' 303 | if len(source) == 0:#不计算空序列,直接返回 304 | return [] 305 | 306 | if not _ts.initialized: 307 | _ts.initialized = True 308 | #print 'new cexpma ema' 309 | _ts.ema = [source[0]] #哨兵元素是source[0],确保计算得到的值在 sclose[i-1] else sclose[i-1] 345 | ll = slow[i] if slow[i] < sclose[i-1] else sclose[i-1] 346 | _ts.tr.append((hh-ll)*XBASE) 347 | return _ts.tr 348 | 349 | @indicator 350 | def ATR(sclose,shigh,slow,length=20,_ts=None): 351 | ltr = TR(sclose,shigh,slow) 352 | return CEXPMA(ltr,length) 353 | 354 | 355 | @indicator 356 | def XATR(sclose,shigh,slow,length=20,_ts=None): 357 | latr = ATR(sclose,shigh,slow,length) 358 | return DIV(MUL1(latr,YBASE),sclose) 359 | 360 | 361 | @indicator 362 | def STREND(source,_ts=None): 363 | ''' 简单累积趋势2 364 | 与strend相比,上升过程中,平也当作上,下降中平作下 365 | 若当前趋势为上升或0,trend值为n>0 366 | 则新trend值为: 367 | n+1 当前值 >= pre 368 | -1 当前值 < pre 369 | 若当前趋势为下降,trend值为n(负数) 370 | 则下一trend值为: 371 | n-1 当前值 <= pre 372 | 1 当前值 > pre 373 | 0为初始趋势(缺少判断时) 374 | ''' 375 | if len(source) == 0: 376 | return [] 377 | 378 | if not _ts.initialized: 379 | _ts.initialized = True 380 | _ts.sd = [0] #第一个是无趋势 381 | 382 | slen = len(_ts.sd) 383 | scur = _ts.sd[-1] 384 | vpre = source[slen-1] 385 | for i in range(slen,len(source)): 386 | vcur = source[i] 387 | if vcur > vpre: 388 | scur = scur + 1 if scur > 0 else 1 389 | elif vcur < vpre: 390 | scur = scur - 1 if scur < 0 else -1 391 | else: #curv == pre_v 392 | scur = scur + 1 if scur >= 0 else scur-1 #最初为0时,也算上升 393 | _ts.sd.append(scur) 394 | vpre = vcur 395 | return _ts.sd 396 | 397 | 398 | #TMAX,TMIN,UCROSS,DCROSS 399 | @indicator 400 | def TMM(source,covered,vmm,fcmp,fgroup,_ts=None): 401 | ''' 402 | vmm: 比较用的极值 403 | fcmp: 比较函数 404 | fgroup:整体比较函数 405 | 406 | cover=0时,返回的是截止到当前元素的最值(cover<0也如此) 407 | ''' 408 | assert covered >=0, 'TMM: cover <0' 409 | if len(source) == 0: 410 | return [] 411 | 412 | if not _ts.initialized: 413 | _ts.initialized = True 414 | #print 'new tmm' 415 | _ts.tmm = [] #第一个是无趋势 416 | _ts.buffer = None 417 | 418 | slen = len(source) 419 | pre_len = slen if slen <= covered else covered 420 | cmm = _ts.tmm[-1] if _ts.tmm else vmm 421 | for i in range(len(_ts.tmm),pre_len): 422 | if fcmp(source[i],cmm): 423 | cmm = source[i] 424 | _ts.tmm.append(cmm) 425 | if slen <= covered: 426 | return _ts.tmm 427 | tlen = len(_ts.tmm) 428 | if _ts.buffer: 429 | buffer = _ts.buffer 430 | else: 431 | buffer = _ts.buffer = deque(source[tlen-covered:tlen]) 432 | #print 'in tmm:tlen=%s,len(source)=%s' % (tlen,len(source)) 433 | for i in range(tlen,len(source)): 434 | v = source[i] 435 | buffer.append(v) 436 | vquit=buffer.popleft() 437 | if fcmp(v,cmm): 438 | cmm = v 439 | if cmm == vquit and v != cmm: #退出的正好是最大值,计算前covered-1个元素的最大值, pre=source[i-1] 440 | #cmm = fgroup(source[i-covered+1:i+1]) 441 | cmm = fgroup(buffer) 442 | _ts.tmm.append(cmm) 443 | return _ts.tmm 444 | 445 | TMAX = fcustom(TMM,vmm=-99999999,fcmp=operator.gt,fgroup=max) 446 | TMIN = fcustom(TMM,vmm=99999999,fcmp=operator.lt,fgroup=min) 447 | 448 | @indicator 449 | def NMM(source,vmm,fcmp,_ts=None): 450 | ''' 451 | 从index0算起的极值. 452 | 相当于covered取最大值时的TMM 453 | ''' 454 | if len(source) == 0: 455 | return [] 456 | 457 | if not _ts.initialized: 458 | _ts.initialized = True 459 | #print 'new nmm' 460 | _ts.nmm = [] #第一个是无趋势 461 | 462 | slen = len(source) 463 | cmm = _ts.nmm[-1] if _ts.nmm else vmm 464 | for i in range(len(_ts.nmm),len(source)): 465 | if fcmp(source[i],cmm): 466 | cmm = source[i] 467 | _ts.nmm.append(cmm) 468 | return _ts.nmm 469 | NMAX = fcustom(NMM,vmm=-99999999,fcmp=operator.gt) 470 | NMIN = fcustom(NMM,vmm=99999999,fcmp=operator.lt) 471 | 472 | 473 | @indicator 474 | def CROSS(source1,source2,rcmp,_ts=None): 475 | ''' 476 | source2去交叉source1 477 | rcmp为判断已交叉状态的函数 478 | 返回值中,0为未×,1为× 479 | ''' 480 | if len(source1) == 0: 481 | return [] 482 | 483 | if not _ts.initialized: 484 | _ts.initialized = True 485 | _ts.crs = [1 if rcmp(source2[0],source1[0]) else 0] #第一个取决于状态,如果为已×,则为1 486 | 487 | ps = _ts.crs[-1] 488 | for i in range(len(_ts.crs),len(source1)): 489 | cs = rcmp(source2[i],source1[i]) 490 | _ts.crs.append(1 if not ps and cs else 0) 491 | ps = cs 492 | return _ts.crs 493 | 494 | UPCROSS = fcustom(CROSS,rcmp = operator.gt) #追击-平-平-超越,以及超越-平-超越均算× 495 | DOWNCROSS = fcustom(CROSS,rcmp = operator.lt) #追击-平-平-超越,以及超越-平-超越均算× 496 | 497 | @indicator 498 | def NCROSS(source,target,rcmp,_ts=None): 499 | ''' 500 | source去交叉target, target为数字 501 | rcmp为判断已交叉状态的函数 502 | 返回值中,0为未×,1为× 503 | ''' 504 | if len(source) == 0: 505 | return [] 506 | 507 | if not _ts.initialized: 508 | _ts.initialized = True 509 | _ts.crs = [1 if rcmp(source[0],target) else 0] #第一个取决于状态,如果为已×,则为1 510 | 511 | ps = _ts.crs[-1] 512 | for i in range(len(_ts.crs),len(source)): 513 | cs = rcmp(source[i],target) 514 | _ts.crs.append(1 if not ps and cs else 0) 515 | ps = cs 516 | return _ts.crs 517 | 518 | NUPCROSS = fcustom(NCROSS,rcmp = operator.gt) #追击-平-平-超越,以及超越-平-超越均算× 519 | NDOWNCROSS = fcustom(NCROSS,rcmp = operator.lt) #追击-平-平-超越,以及超越-平-超越均算× 520 | 521 | 522 | @indicator 523 | def REF(source,offset=1,_ts=None): 524 | ''' 525 | 取得偏移为offset的序列 526 | 前offset部分用第一元素填充 527 | 如果仅用于比较,不建议用这个函数,而直接用[-1]下标比较 528 | 只有在偏移CROSS时才有意义 529 | ''' 530 | if len(source) == 0: 531 | return [] 532 | 533 | if not _ts.initialized: 534 | _ts.initialized = True 535 | _ts.ref = [source[0]] 536 | #print 'initialize REF' 537 | 538 | for i in range(len(_ts.ref),offset if offset <= len(source) else len(source)): 539 | _ts.ref.append(source[0]) 540 | 541 | for i in range(len(_ts.ref),len(source)): 542 | _ts.ref.append(source[i-offset]) 543 | 544 | return _ts.ref 545 | 546 | ####分钟切换 547 | time2min = lambda t:t//100000 548 | 549 | ''' 550 | 周期缩放示例 551 | ''' 552 | @indicator 553 | def MINUTE(ticks,spre_min1=None,_ts=None): 554 | ''' 555 | 分钟切分, 以ticks为参数 556 | 这个用于示范周期缩放. 但实际上并没有用到. 557 | _ts用于暂存。同时可用于接续历史数据 558 | 如果spre_min1不为空,调用者需保证ticks[0].min1 > spre_min1.stime 559 | ''' 560 | assert len(ticks)>0 561 | 562 | if not _ts.initialized: 563 | _ts.initialized = True 564 | _ts.smin1 = [] if spre_min1 == None else spre_min1 565 | _ts._cur = BaseObject(vopen = ticks[0].price, 566 | vclose = ticks[0].price, 567 | vhigh=ticks[0].price, 568 | vlow=ticks[0].price, 569 | open_dvol=ticks[0].dvolume,#存在初始误差 570 | close_dvol=ticks[0].dvolume, 571 | holding = ticks[0].holding, 572 | min1=ticks[0].min1, #当日第一min1 573 | xdate=ticks[0].date, 574 | xtype = 0, 575 | ) #这里对dvol的处理,使得中断恢复也必须从当日最开始开始,否则所有前述成交量被归结到第一tick 576 | _ts._ilast = 0 577 | _ts.modified = False #上周期完成标志 578 | 579 | scur = _ts._cur 580 | for i in range(_ts._ilast,len(ticks)): 581 | tcur = ticks[i] 582 | #print tcur.min1,scur.min1 583 | if tcur.min1 > scur.min1 or (tcur.min1 == 0 and scur.min1 > 0) or (tcur.date > scur.xdate and scur.min1 > 0): 584 | #tcur.min1 = 0, 分钟强制切换用,要求其它字段均为0. 可用于在确定时间已过的前提下提供强制分钟切换tick 585 | if scur.min1 > 0: #前一分钟不是切换标志 586 | cmin1 = XMIN(tcur.cname,scur.xdate,scur.min1,scur.vopen,scur.vclose,scur.vhigh,scur.vlow,scur.close_dvol - scur.open_dvol,scur.holding,scur.xtype) 587 | _ts.smin1.append(cmin1) 588 | scur.vopen = scur.vclose = scur.vhigh = scur.vlow = tcur.price 589 | scur.open_dvol = scur.close_dvol 590 | scur.close_dvol = tcur.dvolume 591 | scur.dvol = tcur.dvolume 592 | scur.holding = tcur.holding 593 | scur.xdate = tcur.date 594 | scur.min1 = tcur.min1 595 | _ts.modified = True 596 | else: #未切换 597 | scur.vclose = tcur.price 598 | scur.close_dvol = tcur.dvolume 599 | scur.holding = tcur.holding 600 | #print scur.min1,'close:',scur.vclose 601 | if tcur.price > scur.vhigh: 602 | scur.vhigh = tcur.price 603 | scur.xtype = ITYPE_L2H 604 | elif tcur.price < scur.vlow: 605 | scur.vlow = tcur.price 606 | scur.xtype = ITYPE_H2L 607 | _ts.modified = False 608 | 609 | _ts._ilast = len(ticks) 610 | return _ts 611 | 612 | 613 | @indicator 614 | def XMINUTE(cholder,_ts=None): 615 | ''' 616 | 分钟切分, 以cholder为参数. 实际参数是cholder.ctick. 使用cholder是为了保证在多次调用中使用同一对象,从而保持缓存 617 | _ts用于暂存。同时可用于接续历史数据 618 | ''' 619 | ctick = cholder.ctick 620 | if not _ts.initialized: 621 | _ts.initialized = True 622 | _ts._cur = BaseObject(vopen = ctick.price, 623 | vclose = ctick.price, 624 | vhigh= ctick.price, 625 | vlow= ctick.price, 626 | open_dvol= ctick.dvolume,#存在初始误差 627 | close_dvol=ctick.dvolume, 628 | holding = ctick.holding, 629 | min1=ctick.min1, #当日第一min1 630 | xdate=ctick.date, 631 | xtype = 0, 632 | ) #这里对dvol的处理,使得中断恢复也必须从当日最开始开始,否则所有前述成交量被归结到第一tick 633 | _ts.modified = False #上周期完成标志 634 | _ts.cmin1 = None 635 | 636 | scur = _ts._cur 637 | 638 | #print(ctick.min1,scur.min1) 639 | if ctick.min1 > scur.min1 or (ctick.min1 == 0 and scur.min1 > 0) or (ctick.date > scur.xdate and scur.min1 > 0): 640 | #ctick.min1 = 0, 分钟强制切换用,要求其它字段均为0. 可用于在确定时间已过的前提下提供强制分钟切换tick 641 | if scur.min1 > 0: #前一分钟不是切换标志 642 | _ts.cmin1 = XMIN(ctick.cname,scur.xdate,scur.min1,scur.vopen,scur.vclose,scur.vhigh,scur.vlow,scur.close_dvol - scur.open_dvol,scur.holding,scur.xtype) 643 | scur.vopen = scur.vclose = scur.vhigh = scur.vlow = ctick.price 644 | scur.open_dvol = scur.close_dvol 645 | scur.close_dvol = ctick.dvolume 646 | scur.dvol = ctick.dvolume 647 | scur.holding = ctick.holding 648 | scur.xdate = ctick.date 649 | scur.min1 = ctick.min1 650 | _ts.modified = True 651 | else: #未切换 652 | scur.vclose = ctick.price 653 | scur.close_dvol = ctick.dvolume 654 | scur.holding = ctick.holding 655 | #print scur.min1,'close:',scur.vclose 656 | if ctick.price > scur.vhigh: 657 | scur.vhigh = ctick.price 658 | scur.xtype = ITYPE_L2H 659 | elif ctick.price < scur.vlow: 660 | scur.vlow = ctick.price 661 | scur.xtype = ITYPE_H2L 662 | _ts.modified = False 663 | return _ts 664 | 665 | 666 | -------------------------------------------------------------------------------- /core/dac2_test.py: -------------------------------------------------------------------------------- 1 | # -*-coding:utf-8 -*- 2 | 3 | import unittest 4 | 5 | import base 6 | from dac2 import * 7 | 8 | class ModuleTest(unittest.TestCase): 9 | 10 | ###基本运算 11 | def test_oper1(self): #测试NEG 12 | self.assertEqual([],NEG([])) 13 | a = [1,2,-3,4,-5,6] 14 | self.assertEqual([-1,-2,3,-4,5,-6],NEG(a)) 15 | a.append(7) 16 | a.append(-8) 17 | self.assertEqual([-1,-2,3,-4,5,-6,-7,8],NEG(a)) 18 | 19 | 20 | def test_oper2(self): #测试ADD 21 | self.assertEqual([],ADD([],[])) 22 | a = [1,2,3,4,5,6] 23 | b = [10,20,30,40,50,60] 24 | self.assertEqual([11,22,33,44,55,66],ADD(a,b)) 25 | a.append(7) 26 | b.append(70) 27 | self.assertEqual([11,22,33,44,55,66,77],ADD(a,b)) 28 | 29 | def test_oper21(self): #测试ADD 30 | self.assertEqual([],ADD1([],0)) 31 | a = [1,2,3,4,5,6] 32 | self.assertEqual([3,4,5,6,7,8],ADD1(a,2)) 33 | a.append(7) 34 | self.assertEqual([4,5,6,7,8,9,10],ADD1(a,3)) 35 | 36 | def test_and(self): #测试AND 37 | self.assertEqual([],AND([],[])) 38 | a = [1,2,3,4,0,6] 39 | b = [10,-20,30,40,50,60] 40 | self.assertEqual([True,True,True,True,False,True],AND(a,b)) 41 | a.append(7) 42 | b.append(70) 43 | self.assertEqual([True,True,True,True,False,True,True],AND(a,b)) 44 | a.append(9) 45 | b.append(0) 46 | self.assertEqual([True,True,True,True,False,True,True,False],AND(a,b)) 47 | 48 | def test_gand(self): #测试AND 49 | self.assertEqual([],GAND([],[])) 50 | a = [1,2,3,4,0,6] 51 | b = [10,-20,30,40,50,60] 52 | self.assertEqual([True,True,True,True,False,True],GAND(a,b)) 53 | a.append(7) 54 | b.append(70) 55 | self.assertEqual([True,True,True,True,False,True,True],GAND(a,b)) 56 | a.append(9) 57 | b.append(0) 58 | self.assertEqual([True,True,True,True,False,True,True,False],GAND(a,b)) 59 | 60 | def test_gor(self): #测试AND 61 | self.assertEqual([],GOR([],[])) 62 | a = [1,0,3,0,0,6] 63 | b = [10,-2,30,0,50,60] 64 | self.assertEqual([True,True,True,False,True,True],GOR(a,b)) 65 | a.append(0) 66 | b.append(70) 67 | self.assertEqual([True,True,True,False,True,True,True],GOR(a,b)) 68 | a.append(0) 69 | b.append(0) 70 | self.assertEqual([True,True,True,False,True,True,True,False],GOR(a,b)) 71 | 72 | 73 | def test_DIV(self): 74 | self.assertEqual([],DIV([],[])) 75 | a = [10,20,30,15,50,30] 76 | b = [1,2,3,4,0,6] 77 | self.assertEqual([10,10,10,4,50000,5],DIV(a,b)) 78 | a.append(7) 79 | b.append(70) 80 | self.assertEqual([10,10,10,4,50000,5,0],DIV(a,b)) 81 | 82 | def test_DIV1(self): 83 | self.assertEqual([],DIV1([],12)) 84 | a = [10,4,30,15,50,30] 85 | self.assertEqual([1,0,3,2,5,3],DIV1(a,10)) 86 | a.append(7) 87 | self.assertEqual([1,0,3,2,5,3,1],DIV1(a,10)) 88 | 89 | 90 | ##常用指标 91 | def test_sum(self): 92 | self.assertEqual([],ACCUMULATE([])) 93 | a= [1,2,3,4,5,6,7,8,9,0] 94 | self.assertEqual([1,3,6,10,15,21,28,36,45,45],ACCUMULATE(a)) 95 | a.append(100) 96 | self.assertEqual([1,3,6,10,15,21,28,36,45,45,145],ACCUMULATE(a)) 97 | 98 | def test_msum(self): 99 | self.assertEqual([],MSUM([],2)) 100 | a= [1,2,3,4,5,6,7,8,9,0] 101 | self.assertEqual([1,2,3,4,5,6,7,8,9,0],MSUM(a,1)) 102 | self.assertEqual([1,3,5,7,9,11,13,15,17,9],MSUM(a,2)) 103 | a.append(100) 104 | self.assertEqual([1,3,5,7,9,11,13,15,17,9,100],MSUM(a,2)) 105 | 106 | 107 | def test_ma(self): 108 | self.assertEqual([],MA([],3)) 109 | a= [1,2,3,4,5,6,7,8,9,0] 110 | self.assertEqual([1,2,2,3,4,5,6,7,8,6],MA(a,3)) 111 | a.append(100) 112 | self.assertEqual([1,2,2,3,4,5,6,7,8,6,36],MA(a,3)) 113 | 114 | def test_nma(self): 115 | self.assertEqual([],NMA([])) 116 | a= [1,2,3,4,5,6,7,8,9,0] 117 | self.assertEqual([1,2,2,3,3,4,4,5,5,5],NMA(a)) 118 | a.append(100) 119 | self.assertEqual([1,2,2,3,3,4,4,5,5,5,13],NMA(a)) 120 | 121 | def test_nsum(self): 122 | self.assertEqual([],NSUM([])) 123 | a= [1,2,3,4,5,6,7,8,9,0] 124 | self.assertEqual([1,3,6,10,15,21,28,36,45,45],NSUM(a)) 125 | a.append(100) 126 | self.assertEqual([1,3,6,10,15,21,28,36,45,45,145],NSUM(a)) 127 | 128 | def test_cexpma(self): 129 | self.assertEqual([],CEXPMA([],6)) 130 | source = [25000,24875,24781,24594,24500,24625,25219,27250] 131 | self.assertEqual([25000,24958,24899,24797,24698,24674,24856,25654],CEXPMA(source,5)) #相当于5日 132 | source.append(200000) 133 | self.assertEqual([25000,24958,24899,24797,24698,24674,24856,25654,83769],CEXPMA(source,5)) #相当于5日 134 | 135 | 136 | def test_tr(self): 137 | self.assertEqual([],TR([],[],[])) 138 | shigh = [200,250,200,400] 139 | slow = [100,200,100,200] 140 | sclose = [150,220,150,300] 141 | self.assertEqual([100*XBASE,100*XBASE,120*XBASE,250*XBASE],TR(sclose,shigh,slow)) 142 | shigh.append(1000) 143 | slow.append(500) 144 | sclose.append(700) 145 | self.assertEqual([100*XBASE,100*XBASE,120*XBASE,250*XBASE,700*XBASE],TR(sclose,shigh,slow)) 146 | 147 | def test_atr(self): 148 | shigh = [200,250,200,400] 149 | slow = [100,200,100,200] 150 | sclose = [150,220,150,300] 151 | self.assertEqual([100*XBASE,100*XBASE,120*XBASE,250*XBASE],ATR(sclose,shigh,slow,1)) 152 | shigh.append(1000) 153 | slow.append(500) 154 | sclose.append(700) 155 | self.assertEqual([100*XBASE,100*XBASE,120*XBASE,250*XBASE,700*XBASE],ATR(sclose,shigh,slow,1)) 156 | 157 | def test_xatr(self): 158 | self.assertEqual([],XATR([],[],[])) 159 | shigh = [200,250,200,400] 160 | slow = [100,200,100,200] 161 | sclose = [150,220,150,300] 162 | self.assertEqual([666667,454545,679333,386667],XATR(sclose,shigh,slow)) 163 | shigh.append(1000) 164 | slow.append(500) 165 | sclose.append(700) 166 | self.assertEqual([666667,454545,679333,386667,245171],XATR(sclose,shigh,slow)) 167 | 168 | def test_strend(self): 169 | self.assertEqual([],STREND([])) 170 | self.assertEqual([0],STREND([1])) 171 | source = [10,20,30,30,40,50,40,30,20,20,10,20] 172 | self.assertEqual([0,1,2,3,4,5,-1,-2,-3,-4,-5,1],STREND(source)) 173 | source.append(20) 174 | self.assertEqual([0,1,2,3,4,5,-1,-2,-3,-4,-5,1,2],STREND(source)) 175 | source.append(30) 176 | self.assertEqual([0,1,2,3,4,5,-1,-2,-3,-4,-5,1,2,3],STREND(source)) 177 | source.append(20) 178 | self.assertEqual([0,1,2,3,4,5,-1,-2,-3,-4,-5,1,2,3,-1],STREND(source)) 179 | source.append(10) 180 | self.assertEqual([0,1,2,3,4,5,-1,-2,-3,-4,-5,1,2,3,-1,-2],STREND(source)) 181 | 182 | 183 | def test_tmax(self): 184 | self.assertEqual([],TMAX([],10)) 185 | source = [10,12,3,2,5,100,0,13,16,9] 186 | self.assertEqual([10,12,3,2,5,100,0,13,16,9],TMAX(source,1)) 187 | self.assertEqual([10,12,12,3,5,100,100,13,16,16],TMAX(source,2)) 188 | source.append(3) 189 | source.append(30) 190 | self.assertEqual([10,12,12,3,5,100,100,13,16,16,9,30],TMAX(source,2)) 191 | self.assertEqual([10,12,12,12,5,100,100,100,16,16,16,30],TMAX(source,3)) 192 | 193 | def test_tmin(self): 194 | self.assertEqual([],TMIN([],10)) 195 | source = [10,12,3,2,5,100,0,13,16,9] 196 | self.assertEqual([10,12,3,2,5,100,0,13,16,9],TMIN(source,1)) 197 | self.assertEqual([10,10,3,2,2,5,0,0,13,9],TMIN(source,2)) 198 | source.append(3) 199 | source.append(30) 200 | self.assertEqual([10,10,3,2,2,5,0,0,13,9,3,3],TMIN(source,2)) 201 | self.assertEqual([10,10,3,2,2,2,0,0,0,9,3,3],TMIN(source,3)) 202 | 203 | def test_nmax(self): 204 | self.assertEqual([],NMAX([])) 205 | source = [10,12,3,2,5,100,0,13,16,9] 206 | self.assertEqual([10,12,12,12,12,100,100,100,100,100],NMAX(source)) 207 | source.append(3) 208 | source.append(103) 209 | self.assertEqual([10,12,12,12,12,100,100,100,100,100,100,103],NMAX(source)) 210 | 211 | def test_nmin(self): 212 | self.assertEqual([],NMIN([])) 213 | source = [10,12,3,2,5,100,0,13,16,9] 214 | self.assertEqual([10,10,3,2,2,2,0,0,0,0],NMIN(source)) 215 | source.append(3) 216 | source.append(-1) 217 | self.assertEqual([10,10,3,2,2,2,0,0,0,0,0,-1],NMIN(source)) 218 | 219 | 220 | def test_cross(self): # 221 | self.assertEqual([],UPCROSS([],[])) 222 | target = [10,20,30,40,50,40,30,20,10,12,11,12] 223 | follow = [5,15,35,41,60,50,25,26,8,12,13,12] 224 | self.assertEqual([0,0,1,0,0,0,0,1,0,0,1,0],UPCROSS(target,follow)) 225 | self.assertEqual([1,0,0,0,0,0,1,0,1,0,0,0],DOWNCROSS(target,follow)) 226 | target.append(15) 227 | follow.append(11) 228 | self.assertEqual([0,0,1,0,0,0,0,1,0,0,1,0,0],UPCROSS(target,follow)) 229 | self.assertEqual([1,0,0,0,0,0,1,0,1,0,0,0,1],DOWNCROSS(target,follow)) 230 | target.append(13) 231 | follow.append(25) 232 | self.assertEqual([0,0,1,0,0,0,0,1,0,0,1,0,0,1],UPCROSS(target,follow)) 233 | self.assertEqual([1,0,0,0,0,0,1,0,1,0,0,0,1,0],DOWNCROSS(target,follow)) 234 | 235 | def test_ncross(self): # 236 | self.assertEqual([],NUPCROSS([],10)) 237 | follow = [5,15,35,41,60,50,25,26,8,12,13,12] 238 | self.assertEqual([0,1,0,0,0,0,0,0,0,1,0,0],NUPCROSS(follow,10)) 239 | self.assertEqual([1,0,0,0,0,0,0,0,1,0,0,0],NDOWNCROSS(follow,10)) 240 | follow.append(8) 241 | self.assertEqual([0,1,0,0,0,0,0,0,0,1,0,0,0],NUPCROSS(follow,10)) 242 | self.assertEqual([1,0,0,0,0,0,0,0,1,0,0,0,1],NDOWNCROSS(follow,10)) 243 | follow.append(25) 244 | self.assertEqual([0,0,1,0,0,0,0,0,0,0,0,0,0,1],NUPCROSS(follow,15)) 245 | self.assertEqual([1,0,0,0,0,0,0,0,1,0,0,0,1,0],NDOWNCROSS(follow,10)) 246 | 247 | 248 | def test_ref(self): 249 | self.assertEqual([],REF([])) 250 | a= [1,2,3,4,5,6,7,8,9,0] 251 | self.assertEqual([1,2,3,4,5,6,7,8,9,0],REF(a,0)) 252 | self.assertEqual([1,1,2,3,4,5,6,7,8,9],REF(a,1)) 253 | self.assertEqual([1,1,1,2,3,4,5,6,7,8],REF(a,2)) 254 | self.assertEqual([1,1,1,1,1,1,1,1,1,2],REF(a,8)) 255 | self.assertEqual([1,1,1,1,1,1,1,1,1,1],REF(a,9)) 256 | self.assertEqual([1,1,1,1,1,1,1,1,1,1],REF(a,10)) 257 | self.assertEqual([1,1,1,1,1,1,1,1,1,1],REF(a,11)) 258 | self.assertEqual([1,1,1,1,1,1,1,1,1,1],REF(a,100)) 259 | a.append(100) 260 | self.assertEqual([1,1,1,2,3,4,5,6,7,8,9],REF(a,2)) 261 | 262 | def test_xminute(self): 263 | ticks = [TICK(),TICK(),TICK(),TICK()] 264 | ticks[0].cname='IF1203' 265 | ticks[0].price = 100 266 | ticks[0].time = 91400000 267 | ticks[0].date = 20120111 268 | ticks[0].dvolume = 10 269 | ticks[0].holding = 10 270 | ticks[0].min1 = time2min(ticks[0].time) 271 | ticks[1].cname='IF1203' 272 | ticks[1].price = 110 273 | ticks[1].time = 91500000 274 | ticks[1].date = 20120111 275 | ticks[1].min1 = time2min(ticks[1].time) 276 | ticks[1].dvolume = 30 277 | ticks[1].holding = 11 278 | ticks[2].cname='IF1203' 279 | ticks[2].price = 115 280 | ticks[2].time = 91501000 281 | ticks[2].date = 20120111 282 | ticks[2].dvolume = 50 283 | ticks[2].holding = 12 284 | ticks[2].min1 = time2min(ticks[2].time) 285 | ticks[3].cname='IF1203' 286 | ticks[3].price = 91 287 | ticks[3].time = 91600000 288 | ticks[3].date = 20120111 289 | ticks[3].dvolume = 51 290 | ticks[3].holding = 13 291 | ticks[3].min1 = time2min(ticks[3].time) 292 | cholder = BaseObject(ctick=ticks[0]) 293 | cm2 = XMINUTE(cholder) 294 | self.assertFalse(cm2.modified) 295 | self.assertEqual(None,cm2.cmin1) 296 | cm2 = XMINUTE(cholder) 297 | self.assertFalse(cm2.modified) 298 | cholder.ctick = ticks[1] 299 | cm2 = XMINUTE(cholder) 300 | self.assertTrue(cm2.modified) 301 | self.assertEqual(100,cm2.cmin1.iclose) 302 | self.assertEqual(100,cm2.cmin1.ilow) 303 | self.assertEqual(100,cm2.cmin1.ihigh) 304 | self.assertEqual(914,cm2.cmin1.imin) 305 | self.assertEqual(0,cm2.cmin1.ivolume) 306 | self.assertEqual(base.ITYPE_UNKNOWN,cm2.cmin1.itype) 307 | cholder.ctick = ticks[2] 308 | cm2 = XMINUTE(cholder) 309 | self.assertFalse(cm2.modified) 310 | cholder.ctick = ticks[3] 311 | cm2 = XMINUTE(cholder) 312 | self.assertTrue(cm2.modified) 313 | self.assertEqual(115,cm2.cmin1.iclose) 314 | self.assertEqual(110,cm2.cmin1.ilow) 315 | self.assertEqual(115,cm2.cmin1.ihigh) 316 | self.assertEqual(915,cm2.cmin1.imin) 317 | self.assertEqual(40,cm2.cmin1.ivolume) 318 | self.assertEqual(base.ITYPE_L2H,cm2.cmin1.itype) 319 | ## 320 | ticks.extend([TICK(),TICK(),TICK()]) 321 | ticks[4].cname='IF1203' 322 | ticks[4].price = 93 323 | ticks[4].time = 91601000 324 | ticks[4].date = 20120111 325 | ticks[4].min1 = time2min(ticks[4].time) 326 | ticks[4].dvolume = 80 327 | ticks[4].holding = 10 328 | ticks[5].cname='IF1203' 329 | ticks[5].price = 90 330 | ticks[5].time = 91602000 331 | ticks[5].date = 20120111 332 | ticks[5].dvolume = 88 333 | ticks[5].holding = 10 334 | ticks[5].min1 = time2min(ticks[5].time) 335 | ticks[6].cname='IF1203' 336 | ticks[6].price = 90 337 | ticks[6].time = 91700000 338 | ticks[6].date = 20120111 339 | ticks[6].dvolume = 89 340 | ticks[6].holding = 10 341 | ticks[6].min1 = time2min(ticks[6].time) 342 | cholder.ctick = ticks[4] 343 | cm2 = XMINUTE(cholder) 344 | self.assertFalse(cm2.modified) 345 | cholder.ctick = ticks[5] 346 | cm2 = XMINUTE(cholder) 347 | self.assertFalse(cm2.modified) 348 | cholder.ctick = ticks[6] 349 | cm2 = XMINUTE(cholder) 350 | self.assertTrue(cm2.modified) 351 | self.assertEqual(90,cm2.cmin1.iclose) 352 | self.assertEqual(90,cm2.cmin1.ilow) 353 | self.assertEqual(93,cm2.cmin1.ihigh) 354 | self.assertEqual(916,cm2.cmin1.imin) 355 | self.assertEqual(38,cm2.cmin1.ivolume) 356 | self.assertEqual(base.ITYPE_H2L,cm2.cmin1.itype) 357 | ## 358 | ticks.append(TICK()) 359 | ticks[7].cname='IF1203' 360 | ticks[7].price = 91 361 | ticks[7].time = 91701000 362 | ticks[7].date = 20120111 363 | ticks[7].dvolume = 91 364 | ticks[7].holding = 10 365 | ticks[7].min1 = time2min(ticks[7].time) 366 | cholder.ctick = ticks[7] 367 | cm2 = XMINUTE(cholder) 368 | self.assertFalse(cm2.modified) 369 | self.assertEqual(916,cm2.cmin1.imin) 370 | ##测试终结符 371 | ticks.append(TICK()) 372 | ticks[-1].cname='IF1203' 373 | ticks[-1].price = 0 374 | ticks[-1].time = 0 375 | ticks[-1].date = 0 376 | ticks[-1].dvolume = 0 377 | ticks[-1].holding = 0 378 | ticks[-1].min1 = time2min(ticks[-1].time) 379 | cholder.ctick = ticks[-1] 380 | cm2 = XMINUTE(cholder) 381 | self.assertTrue(cm2.modified) 382 | self.assertEqual(917,cm2.cmin1.imin) 383 | self.assertEqual(91,cm2.cmin1.iclose) 384 | self.assertEqual(90,cm2.cmin1.ilow) 385 | self.assertEqual(91,cm2.cmin1.ihigh) 386 | self.assertEqual(917,cm2.cmin1.imin) 387 | self.assertEqual(3,cm2.cmin1.ivolume) 388 | ##测试重复的终结符 389 | ticks.append(TICK()) 390 | ticks[-1].cname='IF1203' 391 | ticks[-1].price = 0 392 | ticks[-1].time = 0 393 | ticks[-1].date = 0 394 | ticks[-1].dvolume = 0 395 | ticks[-1].holding = 0 396 | ticks[-1].min1 = time2min(ticks[-1].time) 397 | cholder.ctick = ticks[-1] 398 | cm2 = XMINUTE(cholder) 399 | self.assertFalse(cm2.modified) 400 | self.assertEqual(917,cm2.cmin1.imin) 401 | 402 | 403 | def test_minute(self): 404 | #m1 = MINUTE([]) 405 | #self.assertEqual([],m1) 406 | ticks = [TICK(),TICK(),TICK(),TICK()] 407 | ticks[0].cname='IF1203' 408 | ticks[0].price = 100 409 | ticks[0].time = 91400000 410 | ticks[0].date = 20120111 411 | ticks[0].dvolume = 10 412 | ticks[0].holding = 10 413 | ticks[0].min1 = time2min(ticks[0].time) 414 | ticks[1].cname='IF1203' 415 | ticks[1].price = 110 416 | ticks[1].time = 91500000 417 | ticks[1].date = 20120111 418 | ticks[1].min1 = time2min(ticks[1].time) 419 | ticks[1].dvolume = 30 420 | ticks[1].holding = 11 421 | ticks[2].cname='IF1203' 422 | ticks[2].price = 115 423 | ticks[2].time = 91501000 424 | ticks[2].date = 20120111 425 | ticks[2].dvolume = 50 426 | ticks[2].holding = 12 427 | ticks[2].min1 = time2min(ticks[2].time) 428 | ticks[3].cname='IF1203' 429 | ticks[3].price = 91 430 | ticks[3].time = 91600000 431 | ticks[3].date = 20120111 432 | ticks[3].dvolume = 51 433 | ticks[3].holding = 13 434 | ticks[3].min1 = time2min(ticks[3].time) 435 | cm2 = MINUTE(ticks) 436 | m2 = cm2.smin1 437 | #print(m2) 438 | self.assertEqual(2,len(m2)) 439 | self.assertEqual(100,m2[0].iclose) 440 | self.assertEqual(115,m2[1].iclose) 441 | self.assertEqual(100,m2[0].ilow) 442 | self.assertEqual(110,m2[1].ilow) 443 | self.assertEqual(100,m2[0].ihigh) 444 | self.assertEqual(115,m2[1].ihigh) 445 | self.assertEqual(914,m2[0].imin) 446 | self.assertEqual(915,m2[1].imin) 447 | self.assertEqual(0,m2[0].ivolume) 448 | self.assertEqual(40,m2[1].ivolume) 449 | self.assertEqual(base.ITYPE_UNKNOWN,m2[0].itype) 450 | self.assertEqual(base.ITYPE_L2H,m2[1].itype) 451 | 452 | self.assertTrue(cm2.modified) 453 | # 454 | ticks.extend([TICK(),TICK(),TICK()]) 455 | ticks[4].cname='IF1203' 456 | ticks[4].price = 93 457 | ticks[4].time = 91601000 458 | ticks[4].date = 20120111 459 | ticks[4].min1 = time2min(ticks[4].time) 460 | ticks[4].dvolume = 80 461 | ticks[4].holding = 10 462 | ticks[5].cname='IF1203' 463 | ticks[5].price = 90 464 | ticks[5].time = 91602000 465 | ticks[5].date = 20120111 466 | ticks[5].dvolume = 88 467 | ticks[5].holding = 10 468 | ticks[5].min1 = time2min(ticks[5].time) 469 | ticks[6].cname='IF1203' 470 | ticks[6].price = 90 471 | ticks[6].time = 91700000 472 | ticks[6].date = 20120111 473 | ticks[6].dvolume = 89 474 | ticks[6].holding = 10 475 | ticks[6].min1 = time2min(ticks[6].time) 476 | cm2 = MINUTE(ticks) 477 | m2 = cm2.smin1 478 | self.assertEqual(90,m2[2].iclose) 479 | self.assertEqual(90,m2[2].ilow) 480 | self.assertEqual(93,m2[2].ihigh) 481 | self.assertEqual(916,m2[2].imin) 482 | self.assertEqual(38,m2[2].ivolume) 483 | self.assertEqual(base.ITYPE_H2L,m2[2].itype) 484 | self.assertTrue(cm2.modified) 485 | ## 486 | ticks.append(TICK()) 487 | ticks[7].cname='IF1203' 488 | ticks[7].price = 91 489 | ticks[7].time = 91701000 490 | ticks[7].date = 20120111 491 | ticks[7].dvolume = 81 492 | ticks[7].holding = 10 493 | ticks[7].min1 = time2min(ticks[7].time) 494 | cm2 = MINUTE(ticks) 495 | m2 = cm2.smin1 496 | self.assertFalse(cm2.modified) 497 | self.assertEqual(916,m2[2].imin) 498 | ##测试终结符 499 | ticks.append(TICK()) 500 | ticks[-1].cname='IF1203' 501 | ticks[-1].price = 0 502 | ticks[-1].time = 0 503 | ticks[-1].date = 0 504 | ticks[-1].dvolume = 0 505 | ticks[-1].holding = 0 506 | ticks[-1].min1 = time2min(ticks[-1].time) 507 | cm2 = MINUTE(ticks) 508 | m2 = cm2.smin1 509 | self.assertTrue(cm2.modified) 510 | self.assertEqual(917,m2[3].imin) 511 | ##测试重复的终结符 512 | ticks.append(TICK()) 513 | ticks[-1].cname='IF1203' 514 | ticks[-1].price = 0 515 | ticks[-1].time = 0 516 | ticks[-1].date = 0 517 | ticks[-1].dvolume = 0 518 | ticks[-1].holding = 0 519 | ticks[-1].min1 = time2min(ticks[-1].time) 520 | cm2 = MINUTE(ticks) 521 | m2 = cm2.smin1 522 | self.assertFalse(cm2.modified) #无变化 523 | self.assertEqual(917,m2[3].imin) 524 | ##测试pre_min 525 | spre_min1 = [BaseObject(),BaseObject()] #占位 526 | ticks = ticks[:4] 527 | cm2 = MINUTE(ticks,spre_min1=spre_min1) 528 | m2 = cm2.smin1 529 | self.assertEqual(4,len(m2)) 530 | self.assertEqual(100,m2[2].iclose) 531 | self.assertEqual(115,m2[3].iclose) 532 | 533 | 534 | if __name__ == "__main__": 535 | import logging 536 | logging.basicConfig(filename="test.log",level=logging.DEBUG,format='%(name)s:%(funcName)s:%(lineno)d:%(asctime)s %(levelname)s %(message)s') 537 | 538 | unittest.main() 539 | --------------------------------------------------------------------------------